fix compile problem on win32
[xonotic/darkplaces.git] / fs.c
1 /*
2         DarkPlaces file system
3
4         Copyright (C) 2003-2006 Mathieu Olivier
5
6         This program is free software; you can redistribute it and/or
7         modify it under the terms of the GNU General Public License
8         as published by the Free Software Foundation; either version 2
9         of the License, or (at your option) any later version.
10
11         This program is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15         See the GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to:
19
20                 Free Software Foundation, Inc.
21                 59 Temple Place - Suite 330
22                 Boston, MA  02111-1307, USA
23 */
24
25 #ifdef WIN32
26 # include <direct.h>
27 # include <io.h>
28 # include <shlobj.h>
29 #endif
30
31 // on *x, we MUST include "quakedef.h" before anything that may include IO
32 // functions to get _FILE_OFFSET_BITS
33 #include "quakedef.h"
34
35 #ifdef __APPLE__
36 // include SDL for IPHONEOS code
37 # include <TargetConditionals.h>
38 # if TARGET_OS_IPHONE
39 #  include <SDL.h>
40 # endif
41 #endif
42
43 #include <limits.h>
44 #include <fcntl.h>
45
46 #ifndef WIN32
47 # include <pwd.h>
48 # include <sys/stat.h>
49 # include <unistd.h>
50 #endif
51
52 #include "fs.h"
53 #include "wad.h"
54
55 // Win32 requires us to add O_BINARY, but the other OSes don't have it
56 #ifndef O_BINARY
57 # define O_BINARY 0
58 #endif
59
60 // In case the system doesn't support the O_NONBLOCK flag
61 #ifndef O_NONBLOCK
62 # define O_NONBLOCK 0
63 #endif
64
65 // largefile support for Win32
66 #ifdef WIN32
67 # define lseek _lseeki64
68 #endif
69
70 #if _MSC_VER >= 1400
71 // suppress deprecated warnings
72 # include <sys/stat.h>
73 # include <share.h>
74 # define read _read
75 # define write _write
76 # define close _close
77 # define unlink _unlink
78 # define dup _dup
79 #endif
80
81 /** \page fs File System
82
83 All of Quake's data access is through a hierchal file system, but the contents
84 of the file system can be transparently merged from several sources.
85
86 The "base directory" is the path to the directory holding the quake.exe and
87 all game directories.  The sys_* files pass this to host_init in
88 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
89 line parm to allow code debugging in a different directory.  The base
90 directory is only used during filesystem initialization.
91
92 The "game directory" is the first tree on the search path and directory that
93 all generated files (savegames, screenshots, demos, config files) will be
94 saved to.  This can be overridden with the "-game" command line parameter.
95 The game directory can never be changed while quake is executing.  This is a
96 precaution against having a malicious server instruct clients to write files
97 over areas they shouldn't.
98
99 */
100
101
102 /*
103 =============================================================================
104
105 CONSTANTS
106
107 =============================================================================
108 */
109
110 // Magic numbers of a ZIP file (big-endian format)
111 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
112 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
113 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
114
115 // Other constants for ZIP files
116 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
117 #define ZIP_END_CDIR_SIZE                       22
118 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
119 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
120
121 #ifdef LINK_TO_ZLIB
122 #include <zlib.h>
123
124 #define qz_inflate inflate
125 #define qz_inflateEnd inflateEnd
126 #define qz_inflateInit2_ inflateInit2_
127 #define qz_inflateReset inflateReset
128 #define qz_deflateInit2_ deflateInit2_
129 #define qz_deflateEnd deflateEnd
130 #define qz_deflate deflate
131 #define Z_MEMLEVEL_DEFAULT 8
132 #else
133
134 // Zlib constants (from zlib.h)
135 #define Z_SYNC_FLUSH    2
136 #define MAX_WBITS               15
137 #define Z_OK                    0
138 #define Z_STREAM_END    1
139 #define Z_STREAM_ERROR  (-2)
140 #define Z_DATA_ERROR    (-3)
141 #define Z_MEM_ERROR     (-4)
142 #define Z_BUF_ERROR     (-5)
143 #define ZLIB_VERSION    "1.2.3"
144
145 #define Z_BINARY 0
146 #define Z_DEFLATED 8
147 #define Z_MEMLEVEL_DEFAULT 8
148
149 #define Z_NULL 0
150 #define Z_DEFAULT_COMPRESSION (-1)
151 #define Z_NO_FLUSH 0
152 #define Z_SYNC_FLUSH 2
153 #define Z_FULL_FLUSH 3
154 #define Z_FINISH 4
155
156 // Uncomment the following line if the zlib DLL you have still uses
157 // the 1.1.x series calling convention on Win32 (WINAPI)
158 //#define ZLIB_USES_WINAPI
159
160
161 /*
162 =============================================================================
163
164 TYPES
165
166 =============================================================================
167 */
168
169 /*! Zlib stream (from zlib.h)
170  * \warning: some pointers we don't use directly have
171  * been cast to "void*" for a matter of simplicity
172  */
173 typedef struct
174 {
175         unsigned char                   *next_in;       ///< next input byte
176         unsigned int    avail_in;       ///< number of bytes available at next_in
177         unsigned long   total_in;       ///< total nb of input bytes read so far
178
179         unsigned char                   *next_out;      ///< next output byte should be put there
180         unsigned int    avail_out;      ///< remaining free space at next_out
181         unsigned long   total_out;      ///< total nb of bytes output so far
182
183         char                    *msg;           ///< last error message, NULL if no error
184         void                    *state;         ///< not visible by applications
185
186         void                    *zalloc;        ///< used to allocate the internal state
187         void                    *zfree;         ///< used to free the internal state
188         void                    *opaque;        ///< private data object passed to zalloc and zfree
189
190         int                             data_type;      ///< best guess about the data type: ascii or binary
191         unsigned long   adler;          ///< adler32 value of the uncompressed data
192         unsigned long   reserved;       ///< reserved for future use
193 } z_stream;
194 #endif
195
196
197 /// inside a package (PAK or PK3)
198 #define QFILE_FLAG_PACKED (1 << 0)
199 /// file is compressed using the deflate algorithm (PK3 only)
200 #define QFILE_FLAG_DEFLATED (1 << 1)
201 /// file is actually already loaded data
202 #define QFILE_FLAG_DATA (1 << 2)
203 /// real file will be removed on close
204 #define QFILE_FLAG_REMOVE (1 << 3)
205
206 #define FILE_BUFF_SIZE 2048
207 typedef struct
208 {
209         z_stream        zstream;
210         size_t          comp_length;                    ///< length of the compressed file
211         size_t          in_ind, in_len;                 ///< input buffer current index and length
212         size_t          in_position;                    ///< position in the compressed file
213         unsigned char           input [FILE_BUFF_SIZE];
214 } ztoolkit_t;
215
216 struct qfile_s
217 {
218         int                             flags;
219         int                             handle;                                 ///< file descriptor
220         fs_offset_t             real_length;                    ///< uncompressed file size (for files opened in "read" mode)
221         fs_offset_t             position;                               ///< current position in the file
222         fs_offset_t             offset;                                 ///< offset into the package (0 if external file)
223         int                             ungetc;                                 ///< single stored character from ungetc, cleared to EOF when read
224
225         // Contents buffer
226         fs_offset_t             buff_ind, buff_len;             ///< buffer current index and length
227         unsigned char                   buff [FILE_BUFF_SIZE];
228
229         ztoolkit_t*             ztk;    ///< For zipped files.
230
231         const unsigned char *data;      ///< For data files.
232
233         const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
234 };
235
236
237 // ------ PK3 files on disk ------ //
238
239 // You can get the complete ZIP format description from PKWARE website
240
241 typedef struct pk3_endOfCentralDir_s
242 {
243         unsigned int signature;
244         unsigned short disknum;
245         unsigned short cdir_disknum;    ///< number of the disk with the start of the central directory
246         unsigned short localentries;    ///< number of entries in the central directory on this disk
247         unsigned short nbentries;               ///< total number of entries in the central directory on this disk
248         unsigned int cdir_size;                 ///< size of the central directory
249         unsigned int cdir_offset;               ///< with respect to the starting disk number
250         unsigned short comment_size;
251         fs_offset_t prepended_garbage;
252 } pk3_endOfCentralDir_t;
253
254
255 // ------ PAK files on disk ------ //
256 typedef struct dpackfile_s
257 {
258         char name[56];
259         int filepos, filelen;
260 } dpackfile_t;
261
262 typedef struct dpackheader_s
263 {
264         char id[4];
265         int dirofs;
266         int dirlen;
267 } dpackheader_t;
268
269
270 /*! \name Packages in memory
271  * @{
272  */
273 /// the offset in packfile_t is the true contents offset
274 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
275 /// file compressed using the deflate algorithm
276 #define PACKFILE_FLAG_DEFLATED (1 << 1)
277 /// file is a symbolic link
278 #define PACKFILE_FLAG_SYMLINK (1 << 2)
279
280 typedef struct packfile_s
281 {
282         char name [MAX_QPATH];
283         int flags;
284         fs_offset_t offset;
285         fs_offset_t packsize;   ///< size in the package
286         fs_offset_t realsize;   ///< real file size (uncompressed)
287 } packfile_t;
288
289 typedef struct pack_s
290 {
291         char filename [MAX_OSPATH];
292         char shortname [MAX_QPATH];
293         int handle;
294         int ignorecase;  ///< PK3 ignores case
295         int numfiles;
296         qboolean vpack;
297         packfile_t *files;
298 } pack_t;
299 //@}
300
301 /// Search paths for files (including packages)
302 typedef struct searchpath_s
303 {
304         // only one of filename / pack will be used
305         char filename[MAX_OSPATH];
306         pack_t *pack;
307         struct searchpath_s *next;
308 } searchpath_t;
309
310
311 /*
312 =============================================================================
313
314 FUNCTION PROTOTYPES
315
316 =============================================================================
317 */
318
319 void FS_Dir_f(void);
320 void FS_Ls_f(void);
321 void FS_Which_f(void);
322
323 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
324 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
325                                                                         fs_offset_t offset, fs_offset_t packsize,
326                                                                         fs_offset_t realsize, int flags);
327
328
329 /*
330 =============================================================================
331
332 VARIABLES
333
334 =============================================================================
335 */
336
337 mempool_t *fs_mempool;
338
339 searchpath_t *fs_searchpaths = NULL;
340 const char *const fs_checkgamedir_missing = "missing";
341
342 #define MAX_FILES_IN_PACK       65536
343
344 char fs_userdir[MAX_OSPATH];
345 char fs_gamedir[MAX_OSPATH];
346 char fs_basedir[MAX_OSPATH];
347 static pack_t *fs_selfpack = NULL;
348
349 // list of active game directories (empty if not running a mod)
350 int fs_numgamedirs = 0;
351 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
352
353 // list of all gamedirs with modinfo.txt
354 gamedir_t *fs_all_gamedirs = NULL;
355 int fs_all_gamedirs_count = 0;
356
357 cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
358 cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
359 cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
360
361
362 /*
363 =============================================================================
364
365 PRIVATE FUNCTIONS - PK3 HANDLING
366
367 =============================================================================
368 */
369
370 #ifndef LINK_TO_ZLIB
371 // Functions exported from zlib
372 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
373 # define ZEXPORT WINAPI
374 #else
375 # define ZEXPORT
376 #endif
377
378 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
379 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
380 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
381 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
382 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
383 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
384 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
385 #endif
386
387 #define qz_inflateInit2(strm, windowBits) \
388         qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
389 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
390         qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
391
392 #ifndef LINK_TO_ZLIB
393 //        qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
394
395 static dllfunction_t zlibfuncs[] =
396 {
397         {"inflate",                     (void **) &qz_inflate},
398         {"inflateEnd",          (void **) &qz_inflateEnd},
399         {"inflateInit2_",       (void **) &qz_inflateInit2_},
400         {"inflateReset",        (void **) &qz_inflateReset},
401         {"deflateInit2_",   (void **) &qz_deflateInit2_},
402         {"deflateEnd",      (void **) &qz_deflateEnd},
403         {"deflate",         (void **) &qz_deflate},
404         {NULL, NULL}
405 };
406
407 /// Handle for Zlib DLL
408 static dllhandle_t zlib_dll = NULL;
409 #endif
410
411 #ifdef WIN32
412 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
413 static dllfunction_t shfolderfuncs[] =
414 {
415         {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
416         {NULL, NULL}
417 };
418 static dllhandle_t shfolder_dll = NULL;
419 #endif
420
421 /*
422 ====================
423 PK3_CloseLibrary
424
425 Unload the Zlib DLL
426 ====================
427 */
428 void PK3_CloseLibrary (void)
429 {
430 #ifndef LINK_TO_ZLIB
431         Sys_UnloadLibrary (&zlib_dll);
432 #endif
433 }
434
435
436 /*
437 ====================
438 PK3_OpenLibrary
439
440 Try to load the Zlib DLL
441 ====================
442 */
443 qboolean PK3_OpenLibrary (void)
444 {
445 #ifdef LINK_TO_ZLIB
446         return true;
447 #else
448         const char* dllnames [] =
449         {
450 #if defined(WIN32)
451 # ifdef ZLIB_USES_WINAPI
452                 "zlibwapi.dll",
453                 "zlib.dll",
454 # else
455                 "zlib1.dll",
456 # endif
457 #elif defined(MACOSX)
458                 "libz.dylib",
459 #else
460                 "libz.so.1",
461                 "libz.so",
462 #endif
463                 NULL
464         };
465
466         // Already loaded?
467         if (zlib_dll)
468                 return true;
469
470         // Load the DLL
471         return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
472 #endif
473 }
474
475 /*
476 ====================
477 FS_HasZlib
478
479 See if zlib is available
480 ====================
481 */
482 qboolean FS_HasZlib(void)
483 {
484 #ifdef LINK_TO_ZLIB
485         return true;
486 #else
487         PK3_OpenLibrary(); // to be safe
488         return (zlib_dll != 0);
489 #endif
490 }
491
492 /*
493 ====================
494 PK3_GetEndOfCentralDir
495
496 Extract the end of the central directory from a PK3 package
497 ====================
498 */
499 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
500 {
501         fs_offset_t filesize, maxsize;
502         unsigned char *buffer, *ptr;
503         int ind;
504
505         // Get the package size
506         filesize = lseek (packhandle, 0, SEEK_END);
507         if (filesize < ZIP_END_CDIR_SIZE)
508                 return false;
509
510         // Load the end of the file in memory
511         if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
512                 maxsize = filesize;
513         else
514                 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
515         buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
516         lseek (packhandle, filesize - maxsize, SEEK_SET);
517         if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
518         {
519                 Mem_Free (buffer);
520                 return false;
521         }
522
523         // Look for the end of central dir signature around the end of the file
524         maxsize -= ZIP_END_CDIR_SIZE;
525         ptr = &buffer[maxsize];
526         ind = 0;
527         while (BuffBigLong (ptr) != ZIP_END_HEADER)
528         {
529                 if (ind == maxsize)
530                 {
531                         Mem_Free (buffer);
532                         return false;
533                 }
534
535                 ind++;
536                 ptr--;
537         }
538
539         memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
540         eocd->signature = LittleLong (eocd->signature);
541         eocd->disknum = LittleShort (eocd->disknum);
542         eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
543         eocd->localentries = LittleShort (eocd->localentries);
544         eocd->nbentries = LittleShort (eocd->nbentries);
545         eocd->cdir_size = LittleLong (eocd->cdir_size);
546         eocd->cdir_offset = LittleLong (eocd->cdir_offset);
547         eocd->comment_size = LittleShort (eocd->comment_size);
548         eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
549         eocd->cdir_offset += eocd->prepended_garbage;
550
551         Mem_Free (buffer);
552
553         return true;
554 }
555
556
557 /*
558 ====================
559 PK3_BuildFileList
560
561 Extract the file list from a PK3 file
562 ====================
563 */
564 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
565 {
566         unsigned char *central_dir, *ptr;
567         unsigned int ind;
568         fs_offset_t remaining;
569
570         // Load the central directory in memory
571         central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
572         lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
573         if(read (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
574         {
575                 Mem_Free (central_dir);
576                 return -1;
577         }
578
579         // Extract the files properties
580         // The parsing is done "by hand" because some fields have variable sizes and
581         // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
582         remaining = eocd->cdir_size;
583         pack->numfiles = 0;
584         ptr = central_dir;
585         for (ind = 0; ind < eocd->nbentries; ind++)
586         {
587                 fs_offset_t namesize, count;
588
589                 // Checking the remaining size
590                 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
591                 {
592                         Mem_Free (central_dir);
593                         return -1;
594                 }
595                 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
596
597                 // Check header
598                 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
599                 {
600                         Mem_Free (central_dir);
601                         return -1;
602                 }
603
604                 namesize = BuffLittleShort (&ptr[28]);  // filename length
605
606                 // Check encryption, compression, and attributes
607                 // 1st uint8  : general purpose bit flag
608                 //    Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
609                 //
610                 // LordHavoc: bit 3 would be a problem if we were scanning the archive
611                 // but is not a problem in the central directory where the values are
612                 // always real.
613                 //
614                 // bit 3 seems to always be set by the standard Mac OSX zip maker
615                 //
616                 // 2nd uint8 : external file attributes
617                 //    Check bits 3 (file is a directory) and 5 (file is a volume (?))
618                 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
619                 {
620                         // Still enough bytes for the name?
621                         if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
622                         {
623                                 Mem_Free (central_dir);
624                                 return -1;
625                         }
626
627                         // WinZip doesn't use the "directory" attribute, so we need to check the name directly
628                         if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
629                         {
630                                 char filename [sizeof (pack->files[0].name)];
631                                 fs_offset_t offset, packsize, realsize;
632                                 int flags;
633
634                                 // Extract the name (strip it if necessary)
635                                 namesize = min(namesize, (int)sizeof (filename) - 1);
636                                 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
637                                 filename[namesize] = '\0';
638
639                                 if (BuffLittleShort (&ptr[10]))
640                                         flags = PACKFILE_FLAG_DEFLATED;
641                                 else
642                                         flags = 0;
643                                 offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
644                                 packsize = (unsigned int)BuffLittleLong (&ptr[20]);
645                                 realsize = (unsigned int)BuffLittleLong (&ptr[24]);
646
647                                 switch(ptr[5]) // C_VERSION_MADE_BY_1
648                                 {
649                                         case 3: // UNIX_
650                                         case 2: // VMS_
651                                         case 16: // BEOS_
652                                                 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
653                                                         // can't use S_ISLNK here, as this has to compile on non-UNIX too
654                                                         flags |= PACKFILE_FLAG_SYMLINK;
655                                                 break;
656                                 }
657
658                                 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
659                         }
660                 }
661
662                 // Skip the name, additionnal field, and comment
663                 // 1er uint16 : extra field length
664                 // 2eme uint16 : file comment length
665                 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
666                 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
667                 remaining -= count;
668         }
669
670         // If the package is empty, central_dir is NULL here
671         if (central_dir != NULL)
672                 Mem_Free (central_dir);
673         return pack->numfiles;
674 }
675
676
677 /*
678 ====================
679 FS_LoadPackPK3
680
681 Create a package entry associated with a PK3 file
682 ====================
683 */
684 pack_t *FS_LoadPackPK3FromFD (const char *packfile, int packhandle, qboolean silent)
685 {
686         pk3_endOfCentralDir_t eocd;
687         pack_t *pack;
688         int real_nb_files;
689
690         if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
691         {
692                 if(!silent)
693                         Con_Printf ("%s is not a PK3 file\n", packfile);
694                 close(packhandle);
695                 return NULL;
696         }
697
698         // Multi-volume ZIP archives are NOT allowed
699         if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
700         {
701                 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
702                 close(packhandle);
703                 return NULL;
704         }
705
706         // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
707         // since eocd.nbentries is an unsigned 16 bits integer
708 #if MAX_FILES_IN_PACK < 65535
709         if (eocd.nbentries > MAX_FILES_IN_PACK)
710         {
711                 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
712                 close(packhandle);
713                 return NULL;
714         }
715 #endif
716
717         // Create a package structure in memory
718         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
719         pack->ignorecase = true; // PK3 ignores case
720         strlcpy (pack->filename, packfile, sizeof (pack->filename));
721         pack->handle = packhandle;
722         pack->numfiles = eocd.nbentries;
723         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
724
725         real_nb_files = PK3_BuildFileList (pack, &eocd);
726         if (real_nb_files < 0)
727         {
728                 Con_Printf ("%s is not a valid PK3 file\n", packfile);
729                 close(pack->handle);
730                 Mem_Free(pack);
731                 return NULL;
732         }
733
734         Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
735         return pack;
736 }
737 pack_t *FS_LoadPackPK3 (const char *packfile)
738 {
739         int packhandle;
740 #if _MSC_VER >= 1400
741         _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
742 #else
743         packhandle = open (packfile, O_RDONLY | O_BINARY);
744 #endif
745         if (packhandle < 0)
746                 return NULL;
747         return FS_LoadPackPK3FromFD(packfile, packhandle, false);
748 }
749
750
751 /*
752 ====================
753 PK3_GetTrueFileOffset
754
755 Find where the true file data offset is
756 ====================
757 */
758 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
759 {
760         unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
761         fs_offset_t count;
762
763         // Already found?
764         if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
765                 return true;
766
767         // Load the local file description
768         lseek (pack->handle, pfile->offset, SEEK_SET);
769         count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
770         if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
771         {
772                 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
773                 return false;
774         }
775
776         // Skip name and extra field
777         pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
778
779         pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
780         return true;
781 }
782
783
784 /*
785 =============================================================================
786
787 OTHER PRIVATE FUNCTIONS
788
789 =============================================================================
790 */
791
792
793 /*
794 ====================
795 FS_AddFileToPack
796
797 Add a file to the list of files contained into a package
798 ====================
799 */
800 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
801                                                                          fs_offset_t offset, fs_offset_t packsize,
802                                                                          fs_offset_t realsize, int flags)
803 {
804         int (*strcmp_funct) (const char* str1, const char* str2);
805         int left, right, middle;
806         packfile_t *pfile;
807
808         strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
809
810         // Look for the slot we should put that file into (binary search)
811         left = 0;
812         right = pack->numfiles - 1;
813         while (left <= right)
814         {
815                 int diff;
816
817                 middle = (left + right) / 2;
818                 diff = strcmp_funct (pack->files[middle].name, name);
819
820                 // If we found the file, there's a problem
821                 if (!diff)
822                         Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
823
824                 // If we're too far in the list
825                 if (diff > 0)
826                         right = middle - 1;
827                 else
828                         left = middle + 1;
829         }
830
831         // We have to move the right of the list by one slot to free the one we need
832         pfile = &pack->files[left];
833         memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
834         pack->numfiles++;
835
836         strlcpy (pfile->name, name, sizeof (pfile->name));
837         pfile->offset = offset;
838         pfile->packsize = packsize;
839         pfile->realsize = realsize;
840         pfile->flags = flags;
841
842         return pfile;
843 }
844
845
846 /*
847 ============
848 FS_CreatePath
849
850 Only used for FS_OpenRealFile.
851 ============
852 */
853 void FS_CreatePath (char *path)
854 {
855         char *ofs, save;
856
857         for (ofs = path+1 ; *ofs ; ofs++)
858         {
859                 if (*ofs == '/' || *ofs == '\\')
860                 {
861                         // create the directory
862                         save = *ofs;
863                         *ofs = 0;
864                         FS_mkdir (path);
865                         *ofs = save;
866                 }
867         }
868 }
869
870
871 /*
872 ============
873 FS_Path_f
874
875 ============
876 */
877 void FS_Path_f (void)
878 {
879         searchpath_t *s;
880
881         Con_Print("Current search path:\n");
882         for (s=fs_searchpaths ; s ; s=s->next)
883         {
884                 if (s->pack)
885                 {
886                         if(s->pack->vpack)
887                                 Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
888                         else
889                                 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
890                 }
891                 else
892                         Con_Printf("%s\n", s->filename);
893         }
894 }
895
896
897 /*
898 =================
899 FS_LoadPackPAK
900 =================
901 */
902 /*! Takes an explicit (not game tree related) path to a pak file.
903  *Loads the header and directory, adding the files at the beginning
904  *of the list so they override previous pack files.
905  */
906 pack_t *FS_LoadPackPAK (const char *packfile)
907 {
908         dpackheader_t header;
909         int i, numpackfiles;
910         int packhandle;
911         pack_t *pack;
912         dpackfile_t *info;
913
914 #if _MSC_VER >= 1400
915         _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
916 #else
917         packhandle = open (packfile, O_RDONLY | O_BINARY);
918 #endif
919         if (packhandle < 0)
920                 return NULL;
921         if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
922         {
923                 Con_Printf ("%s is not a packfile\n", packfile);
924                 close(packhandle);
925                 return NULL;
926         }
927         if (memcmp(header.id, "PACK", 4))
928         {
929                 Con_Printf ("%s is not a packfile\n", packfile);
930                 close(packhandle);
931                 return NULL;
932         }
933         header.dirofs = LittleLong (header.dirofs);
934         header.dirlen = LittleLong (header.dirlen);
935
936         if (header.dirlen % sizeof(dpackfile_t))
937         {
938                 Con_Printf ("%s has an invalid directory size\n", packfile);
939                 close(packhandle);
940                 return NULL;
941         }
942
943         numpackfiles = header.dirlen / sizeof(dpackfile_t);
944
945         if (numpackfiles > MAX_FILES_IN_PACK)
946         {
947                 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
948                 close(packhandle);
949                 return NULL;
950         }
951
952         info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
953         lseek (packhandle, header.dirofs, SEEK_SET);
954         if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
955         {
956                 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
957                 Mem_Free(info);
958                 close(packhandle);
959                 return NULL;
960         }
961
962         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
963         pack->ignorecase = false; // PAK is case sensitive
964         strlcpy (pack->filename, packfile, sizeof (pack->filename));
965         pack->handle = packhandle;
966         pack->numfiles = 0;
967         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
968
969         // parse the directory
970         for (i = 0;i < numpackfiles;i++)
971         {
972                 fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
973                 fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
974
975                 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
976         }
977
978         Mem_Free(info);
979
980         Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
981         return pack;
982 }
983
984 /*
985 ====================
986 FS_LoadPackVirtual
987
988 Create a package entry associated with a directory file
989 ====================
990 */
991 pack_t *FS_LoadPackVirtual (const char *dirname)
992 {
993         pack_t *pack;
994         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
995         pack->vpack = true;
996         pack->ignorecase = false;
997         strlcpy (pack->filename, dirname, sizeof(pack->filename));
998         pack->handle = -1;
999         pack->numfiles = -1;
1000         pack->files = NULL;
1001         Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
1002         return pack;
1003 }
1004
1005 /*
1006 ================
1007 FS_AddPack_Fullpath
1008 ================
1009 */
1010 /*! Adds the given pack to the search path.
1011  * The pack type is autodetected by the file extension.
1012  *
1013  * Returns true if the file was successfully added to the
1014  * search path or if it was already included.
1015  *
1016  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1017  * plain directories.
1018  *
1019  */
1020 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
1021 {
1022         searchpath_t *search;
1023         pack_t *pak = NULL;
1024         const char *ext = FS_FileExtension(pakfile);
1025         size_t l;
1026
1027         for(search = fs_searchpaths; search; search = search->next)
1028         {
1029                 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1030                 {
1031                         if(already_loaded)
1032                                 *already_loaded = true;
1033                         return true; // already loaded
1034                 }
1035         }
1036
1037         if(already_loaded)
1038                 *already_loaded = false;
1039
1040         if(!strcasecmp(ext, "pk3dir"))
1041                 pak = FS_LoadPackVirtual (pakfile);
1042         else if(!strcasecmp(ext, "pak"))
1043                 pak = FS_LoadPackPAK (pakfile);
1044         else if(!strcasecmp(ext, "pk3"))
1045                 pak = FS_LoadPackPK3 (pakfile);
1046         else
1047                 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1048
1049         if(pak)
1050         {
1051                 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1052
1053                 //Con_DPrintf("  Registered pack with short name %s\n", shortname);
1054                 if(keep_plain_dirs)
1055                 {
1056                         // find the first item whose next one is a pack or NULL
1057                         searchpath_t *insertion_point = 0;
1058                         if(fs_searchpaths && !fs_searchpaths->pack)
1059                         {
1060                                 insertion_point = fs_searchpaths;
1061                                 for(;;)
1062                                 {
1063                                         if(!insertion_point->next)
1064                                                 break;
1065                                         if(insertion_point->next->pack)
1066                                                 break;
1067                                         insertion_point = insertion_point->next;
1068                                 }
1069                         }
1070                         // If insertion_point is NULL, this means that either there is no
1071                         // item in the list yet, or that the very first item is a pack. In
1072                         // that case, we want to insert at the beginning...
1073                         if(!insertion_point)
1074                         {
1075                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1076                                 search->next = fs_searchpaths;
1077                                 fs_searchpaths = search;
1078                         }
1079                         else
1080                         // otherwise we want to append directly after insertion_point.
1081                         {
1082                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1083                                 search->next = insertion_point->next;
1084                                 insertion_point->next = search;
1085                         }
1086                 }
1087                 else
1088                 {
1089                         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1090                         search->next = fs_searchpaths;
1091                         fs_searchpaths = search;
1092                 }
1093                 search->pack = pak;
1094                 if(pak->vpack)
1095                 {
1096                         dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1097                         // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
1098                         // same goes for the name inside the pack structure
1099                         l = strlen(pak->shortname);
1100                         if(l >= 7)
1101                                 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
1102                                         pak->shortname[l - 3] = 0;
1103                         l = strlen(pak->filename);
1104                         if(l >= 7)
1105                                 if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
1106                                         pak->filename[l - 3] = 0;
1107                 }
1108                 return true;
1109         }
1110         else
1111         {
1112                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1113                 return false;
1114         }
1115 }
1116
1117
1118 /*
1119 ================
1120 FS_AddPack
1121 ================
1122 */
1123 /*! Adds the given pack to the search path and searches for it in the game path.
1124  * The pack type is autodetected by the file extension.
1125  *
1126  * Returns true if the file was successfully added to the
1127  * search path or if it was already included.
1128  *
1129  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1130  * plain directories.
1131  */
1132 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1133 {
1134         char fullpath[MAX_OSPATH];
1135         int index;
1136         searchpath_t *search;
1137
1138         if(already_loaded)
1139                 *already_loaded = false;
1140
1141         // then find the real name...
1142         search = FS_FindFile(pakfile, &index, true);
1143         if(!search || search->pack)
1144         {
1145                 Con_Printf("could not find pak \"%s\"\n", pakfile);
1146                 return false;
1147         }
1148
1149         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1150
1151         return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1152 }
1153
1154
1155 /*
1156 ================
1157 FS_AddGameDirectory
1158
1159 Sets fs_gamedir, adds the directory to the head of the path,
1160 then loads and adds pak1.pak pak2.pak ...
1161 ================
1162 */
1163 void FS_AddGameDirectory (const char *dir)
1164 {
1165         int i;
1166         stringlist_t list;
1167         searchpath_t *search;
1168
1169         strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1170
1171         stringlistinit(&list);
1172         listdirectory(&list, "", dir);
1173         stringlistsort(&list);
1174
1175         // add any PAK package in the directory
1176         for (i = 0;i < list.numstrings;i++)
1177         {
1178                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1179                 {
1180                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1181                 }
1182         }
1183
1184         // add any PK3 package in the directory
1185         for (i = 0;i < list.numstrings;i++)
1186         {
1187                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
1188                 {
1189                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1190                 }
1191         }
1192
1193         stringlistfreecontents(&list);
1194
1195         // Add the directory to the search path
1196         // (unpacked files have the priority over packed files)
1197         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1198         strlcpy (search->filename, dir, sizeof (search->filename));
1199         search->next = fs_searchpaths;
1200         fs_searchpaths = search;
1201 }
1202
1203
1204 /*
1205 ================
1206 FS_AddGameHierarchy
1207 ================
1208 */
1209 void FS_AddGameHierarchy (const char *dir)
1210 {
1211         // Add the common game directory
1212         FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1213
1214         if (*fs_userdir)
1215                 FS_AddGameDirectory(va("%s%s/", fs_userdir, dir));
1216 }
1217
1218
1219 /*
1220 ============
1221 FS_FileExtension
1222 ============
1223 */
1224 const char *FS_FileExtension (const char *in)
1225 {
1226         const char *separator, *backslash, *colon, *dot;
1227
1228         separator = strrchr(in, '/');
1229         backslash = strrchr(in, '\\');
1230         if (!separator || separator < backslash)
1231                 separator = backslash;
1232         colon = strrchr(in, ':');
1233         if (!separator || separator < colon)
1234                 separator = colon;
1235
1236         dot = strrchr(in, '.');
1237         if (dot == NULL || (separator && (dot < separator)))
1238                 return "";
1239
1240         return dot + 1;
1241 }
1242
1243
1244 /*
1245 ============
1246 FS_FileWithoutPath
1247 ============
1248 */
1249 const char *FS_FileWithoutPath (const char *in)
1250 {
1251         const char *separator, *backslash, *colon;
1252
1253         separator = strrchr(in, '/');
1254         backslash = strrchr(in, '\\');
1255         if (!separator || separator < backslash)
1256                 separator = backslash;
1257         colon = strrchr(in, ':');
1258         if (!separator || separator < colon)
1259                 separator = colon;
1260         return separator ? separator + 1 : in;
1261 }
1262
1263
1264 /*
1265 ================
1266 FS_ClearSearchPath
1267 ================
1268 */
1269 void FS_ClearSearchPath (void)
1270 {
1271         // unload all packs and directory information, close all pack files
1272         // (if a qfile is still reading a pack it won't be harmed because it used
1273         //  dup() to get its own handle already)
1274         while (fs_searchpaths)
1275         {
1276                 searchpath_t *search = fs_searchpaths;
1277                 fs_searchpaths = search->next;
1278                 if (search->pack && search->pack != fs_selfpack)
1279                 {
1280                         if(!search->pack->vpack)
1281                         {
1282                                 // close the file
1283                                 close(search->pack->handle);
1284                                 // free any memory associated with it
1285                                 if (search->pack->files)
1286                                         Mem_Free(search->pack->files);
1287                         }
1288                         Mem_Free(search->pack);
1289                 }
1290                 Mem_Free(search);
1291         }
1292 }
1293
1294 static void FS_AddSelfPack(void)
1295 {
1296         if(fs_selfpack)
1297         {
1298                 searchpath_t *search;
1299                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1300                 search->next = fs_searchpaths;
1301                 search->pack = fs_selfpack;
1302                 fs_searchpaths = search;
1303         }
1304 }
1305
1306
1307 /*
1308 ================
1309 FS_Rescan
1310 ================
1311 */
1312 void FS_Rescan (void)
1313 {
1314         int i;
1315         qboolean fs_modified = false;
1316         qboolean reset = false;
1317         char gamedirbuf[MAX_INPUTLINE];
1318
1319         if (fs_searchpaths)
1320                 reset = true;
1321         FS_ClearSearchPath();
1322
1323         // automatically activate gamemode for the gamedirs specified
1324         if (reset)
1325                 COM_ChangeGameTypeForGameDirs();
1326
1327         // add the game-specific paths
1328         // gamedirname1 (typically id1)
1329         FS_AddGameHierarchy (gamedirname1);
1330         // update the com_modname (used for server info)
1331         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1332
1333         // add the game-specific path, if any
1334         // (only used for mission packs and the like, which should set fs_modified)
1335         if (gamedirname2)
1336         {
1337                 fs_modified = true;
1338                 FS_AddGameHierarchy (gamedirname2);
1339         }
1340
1341         // -game <gamedir>
1342         // Adds basedir/gamedir as an override game
1343         // LordHavoc: now supports multiple -game directories
1344         // set the com_modname (reported in server info)
1345         *gamedirbuf = 0;
1346         for (i = 0;i < fs_numgamedirs;i++)
1347         {
1348                 fs_modified = true;
1349                 FS_AddGameHierarchy (fs_gamedirs[i]);
1350                 // update the com_modname (used server info)
1351                 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1352                 if(i)
1353                         strlcat(gamedirbuf, va(" %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1354                 else
1355                         strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1356         }
1357         Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1358
1359         // add back the selfpack as new first item
1360         FS_AddSelfPack();
1361
1362         // set the default screenshot name to either the mod name or the
1363         // gamemode screenshot name
1364         if (strcmp(com_modname, gamedirname1))
1365                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1366         else
1367                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1368         
1369         if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1370                 strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1371
1372         // If "-condebug" is in the command line, remove the previous log file
1373         if (COM_CheckParm ("-condebug") != 0)
1374                 unlink (va("%s/qconsole.log", fs_gamedir));
1375
1376         // look for the pop.lmp file and set registered to true if it is found
1377         if (FS_FileExists("gfx/pop.lmp"))
1378                 Cvar_Set ("registered", "1");
1379         switch(gamemode)
1380         {
1381         case GAME_NORMAL:
1382         case GAME_HIPNOTIC:
1383         case GAME_ROGUE:
1384                 if (!registered.integer)
1385                 {
1386                         if (fs_modified)
1387                                 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1388                         else
1389                                 Con_Print("Playing shareware version.\n");
1390                 }
1391                 else
1392                         Con_Print("Playing registered version.\n");
1393                 break;
1394         case GAME_STEELSTORM:
1395                 if (registered.integer)
1396                         Con_Print("Playing registered version.\n");
1397                 else
1398                         Con_Print("Playing shareware version.\n");
1399                 break;
1400         default:
1401                 break;
1402         }
1403
1404         // unload all wads so that future queries will return the new data
1405         W_UnloadAll();
1406 }
1407
1408 void FS_Rescan_f(void)
1409 {
1410         FS_Rescan();
1411 }
1412
1413 /*
1414 ================
1415 FS_ChangeGameDirs
1416 ================
1417 */
1418 extern void Host_SaveConfig (void);
1419 extern void Host_LoadConfig_f (void);
1420 extern qboolean vid_opened;
1421 extern void VID_Stop(void);
1422 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1423 {
1424         int i;
1425         const char *p;
1426
1427         if (fs_numgamedirs == numgamedirs)
1428         {
1429                 for (i = 0;i < numgamedirs;i++)
1430                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1431                                 break;
1432                 if (i == numgamedirs)
1433                         return true; // already using this set of gamedirs, do nothing
1434         }
1435
1436         if (numgamedirs > MAX_GAMEDIRS)
1437         {
1438                 if (complain)
1439                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1440                 return false; // too many gamedirs
1441         }
1442
1443         for (i = 0;i < numgamedirs;i++)
1444         {
1445                 // if string is nasty, reject it
1446                 p = FS_CheckGameDir(gamedirs[i]);
1447                 if(!p)
1448                 {
1449                         if (complain)
1450                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1451                         return false; // nasty gamedirs
1452                 }
1453                 if(p == fs_checkgamedir_missing && failmissing)
1454                 {
1455                         if (complain)
1456                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1457                         return false; // missing gamedirs
1458                 }
1459         }
1460
1461         Host_SaveConfig();
1462
1463         fs_numgamedirs = numgamedirs;
1464         for (i = 0;i < fs_numgamedirs;i++)
1465                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1466
1467         // reinitialize filesystem to detect the new paks
1468         FS_Rescan();
1469
1470         if (cls.demoplayback)
1471         {
1472                 CL_Disconnect_f();
1473                 cls.demonum = 0;
1474         }
1475
1476         // unload all sounds so they will be reloaded from the new files as needed
1477         S_UnloadAllSounds_f();
1478
1479         // close down the video subsystem, it will start up again when the config finishes...
1480         VID_Stop();
1481         vid_opened = false;
1482
1483         // restart the video subsystem after the config is executed
1484         Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
1485
1486         return true;
1487 }
1488
1489 /*
1490 ================
1491 FS_GameDir_f
1492 ================
1493 */
1494 void FS_GameDir_f (void)
1495 {
1496         int i;
1497         int numgamedirs;
1498         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1499
1500         if (Cmd_Argc() < 2)
1501         {
1502                 Con_Printf("gamedirs active:");
1503                 for (i = 0;i < fs_numgamedirs;i++)
1504                         Con_Printf(" %s", fs_gamedirs[i]);
1505                 Con_Printf("\n");
1506                 return;
1507         }
1508
1509         numgamedirs = Cmd_Argc() - 1;
1510         if (numgamedirs > MAX_GAMEDIRS)
1511         {
1512                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1513                 return;
1514         }
1515
1516         for (i = 0;i < numgamedirs;i++)
1517                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1518
1519         if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1520         {
1521                 // actually, changing during game would work fine, but would be stupid
1522                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1523                 return;
1524         }
1525
1526         // halt demo playback to close the file
1527         CL_Disconnect();
1528
1529         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1530 }
1531
1532 static const char *FS_SysCheckGameDir(const char *gamedir)
1533 {
1534         static char buf[8192];
1535         qboolean success;
1536         qfile_t *f;
1537         stringlist_t list;
1538         fs_offset_t n;
1539
1540         stringlistinit(&list);
1541         listdirectory(&list, gamedir, "");
1542         success = list.numstrings > 0;
1543         stringlistfreecontents(&list);
1544
1545         if(success)
1546         {
1547                 f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false);
1548                 if(f)
1549                 {
1550                         n = FS_Read (f, buf, sizeof(buf) - 1);
1551                         if(n >= 0)
1552                                 buf[n] = 0;
1553                         else
1554                                 *buf = 0;
1555                         FS_Close(f);
1556                 }
1557                 else
1558                         *buf = 0;
1559                 return buf;
1560         }
1561
1562         return NULL;
1563 }
1564
1565 /*
1566 ================
1567 FS_CheckGameDir
1568 ================
1569 */
1570 const char *FS_CheckGameDir(const char *gamedir)
1571 {
1572         const char *ret;
1573
1574         if (FS_CheckNastyPath(gamedir, true))
1575                 return NULL;
1576
1577         ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir));
1578         if(ret)
1579         {
1580                 if(!*ret)
1581                 {
1582                         // get description from basedir
1583                         ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1584                         if(ret)
1585                                 return ret;
1586                         return "";
1587                 }
1588                 return ret;
1589         }
1590
1591         ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1592         if(ret)
1593                 return ret;
1594         
1595         return fs_checkgamedir_missing;
1596 }
1597
1598 static void FS_ListGameDirs(void)
1599 {
1600         stringlist_t list, list2;
1601         int i, j;
1602         const char *info;
1603
1604         fs_all_gamedirs_count = 0;
1605         if(fs_all_gamedirs)
1606                 Mem_Free(fs_all_gamedirs);
1607
1608         stringlistinit(&list);
1609         listdirectory(&list, va("%s/", fs_basedir), "");
1610         listdirectory(&list, va("%s/", fs_userdir), "");
1611         stringlistsort(&list);
1612
1613         stringlistinit(&list2);
1614         for(i = 0; i < list.numstrings; ++i)
1615         {
1616                 if(i)
1617                         if(!strcmp(list.strings[i-1], list.strings[i]))
1618                                 continue;
1619                 info = FS_CheckGameDir(list.strings[i]);
1620                 if(!info)
1621                         continue;
1622                 if(info == fs_checkgamedir_missing)
1623                         continue;
1624                 if(!*info)
1625                         continue;
1626                 stringlistappend(&list2, list.strings[i]); 
1627         }
1628         stringlistfreecontents(&list);
1629
1630         fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1631         for(i = 0; i < list2.numstrings; ++i)
1632         {
1633                 info = FS_CheckGameDir(list2.strings[i]);
1634                 // all this cannot happen any more, but better be safe than sorry
1635                 if(!info)
1636                         continue;
1637                 if(info == fs_checkgamedir_missing)
1638                         continue;
1639                 if(!*info)
1640                         continue;
1641                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name));
1642                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description));
1643                 ++fs_all_gamedirs_count;
1644         }
1645 }
1646
1647 /*
1648 ================
1649 FS_Init_SelfPack
1650 ================
1651 */
1652 void FS_Init_SelfPack (void)
1653 {
1654         PK3_OpenLibrary ();
1655         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1656         if(com_selffd >= 0)
1657         {
1658                 fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
1659                 if(fs_selfpack)
1660                 {
1661                         char *buf, *q;
1662                         const char *p;
1663                         FS_AddSelfPack();
1664                         buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
1665                         if(buf)
1666                         {
1667                                 const char **new_argv;
1668                                 int i = 0;
1669                                 int args_left = 256;
1670                                 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
1671                                 if(com_argc == 0)
1672                                 {
1673                                         new_argv[0] = "dummy";
1674                                         com_argc = 1;
1675                                 }
1676                                 else
1677                                 {
1678                                         memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc);
1679                                 }
1680                                 p = buf;
1681                                 while(COM_ParseToken_Console(&p))
1682                                 {
1683                                         if(i >= args_left)
1684                                                 break;
1685                                         q = (char *)Mem_Alloc(fs_mempool, strlen(com_token) + 1);
1686                                         strlcpy(q, com_token, strlen(com_token) + 1);
1687                                         new_argv[com_argc + i] = q;
1688                                         ++i;
1689                                 }
1690                                 new_argv[i+com_argc] = NULL;
1691                                 com_argv = new_argv;
1692                                 com_argc = com_argc + i;
1693                         }
1694                         Mem_Free(buf);
1695                 }
1696         }
1697 }
1698
1699 /*
1700 ================
1701 FS_Init
1702 ================
1703 */
1704 void FS_Init (void)
1705 {
1706         const char *p;
1707         int i;
1708 #ifdef WIN32
1709         TCHAR mydocsdir[MAX_PATH + 1];
1710 #if _MSC_VER >= 1400
1711         size_t homedirlen;
1712 #endif
1713 #endif
1714 #ifndef __IPHONEOS__
1715         char *homedir;
1716 #endif
1717
1718 #ifdef WIN32
1719         const char* dllnames [] =
1720         {
1721                 "shfolder.dll",  // IE 4, or Win NT and higher
1722                 NULL
1723         };
1724         Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1725         // don't care for the result; if it fails, %USERPROFILE% will be used instead
1726 #endif
1727
1728         *fs_basedir = 0;
1729         *fs_userdir = 0;
1730         *fs_gamedir = 0;
1731
1732 #ifdef __IPHONEOS__
1733         // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1734         // fs_userdir stores configurations to the Documents folder of the app
1735         strlcpy(fs_userdir, "../Documents/", sizeof(fs_userdir));
1736 #else
1737         // Add the personal game directory
1738         if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1739         {
1740                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
1741         }
1742         else if(COM_CheckParm("-nohome"))
1743         {
1744                 *fs_userdir = 0;
1745         }
1746         else
1747         {
1748 #ifdef WIN32
1749                 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1750                 {
1751                         dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1752                         Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", fs_userdir);
1753                 }
1754                 else
1755                 {
1756                         // use the environment
1757 #if _MSC_VER >= 1400
1758                         _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1759 #else
1760                         homedir = getenv("USERPROFILE");
1761 #endif
1762
1763                         if(homedir)
1764                         {
1765                                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1766 #if _MSC_VER >= 1400
1767                                 free(homedir);
1768 #endif
1769                                 Con_DPrintf("Obtained personal directory %s from environment\n", fs_userdir);
1770                         }
1771                 }
1772
1773                 if(!*fs_userdir)
1774                         Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1775 #else
1776                 homedir = getenv ("HOME");
1777                 if(homedir)
1778                         dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/.%s/", homedir, gameuserdirname);
1779
1780                 if(!*fs_userdir)
1781                         Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1782 #endif
1783
1784 #ifdef WIN32
1785                 if(!COM_CheckParm("-mygames"))
1786                 {
1787 #if _MSC_VER >= 1400
1788                         int fd;
1789                         _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
1790 #else
1791                         int fd = open (va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1792 #endif
1793                         if(fd >= 0)
1794                         {
1795                                 close(fd);
1796                                 *fs_userdir = 0; // we have write access to the game dir, so let's use it
1797                         }
1798                 }
1799 #endif
1800         }
1801
1802         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1803
1804 // If the base directory is explicitly defined by the compilation process
1805 #ifdef DP_FS_BASEDIR
1806         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1807 #else
1808         *fs_basedir = 0;
1809
1810 #ifdef MACOSX
1811         // FIXME: is there a better way to find the directory outside the .app?
1812         if (strstr(com_argv[0], ".app/"))
1813         {
1814                 char *split;
1815
1816                 split = strstr(com_argv[0], ".app/");
1817                 while (split > com_argv[0] && *split != '/')
1818                         split--;
1819                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1820                 fs_basedir[split - com_argv[0]] = 0;
1821         }
1822 #endif
1823 #endif
1824 #endif
1825
1826         // -basedir <path>
1827         // Overrides the system supplied base directory (under GAMENAME)
1828 // COMMANDLINEOPTION: Filesystem: -basedir <path> chooses what base directory the game data is in, inside this there should be a data directory for the game (for example id1)
1829         i = COM_CheckParm ("-basedir");
1830         if (i && i < com_argc-1)
1831         {
1832                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1833                 i = (int)strlen (fs_basedir);
1834                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1835                         fs_basedir[i-1] = 0;
1836         }
1837
1838         // add a path separator to the end of the basedir if it lacks one
1839         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1840                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1841
1842         FS_ListGameDirs();
1843
1844         p = FS_CheckGameDir(gamedirname1);
1845         if(!p || p == fs_checkgamedir_missing)
1846                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1847
1848         if(gamedirname2)
1849         {
1850                 p = FS_CheckGameDir(gamedirname2);
1851                 if(!p || p == fs_checkgamedir_missing)
1852                         Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1853         }
1854
1855         // -game <gamedir>
1856         // Adds basedir/gamedir as an override game
1857         // LordHavoc: now supports multiple -game directories
1858         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1859         {
1860                 if (!com_argv[i])
1861                         continue;
1862                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1863                 {
1864                         i++;
1865                         p = FS_CheckGameDir(com_argv[i]);
1866                         if(!p)
1867                                 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
1868                         if(p == fs_checkgamedir_missing)
1869                                 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1870                         // add the gamedir to the list of active gamedirs
1871                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1872                         fs_numgamedirs++;
1873                 }
1874         }
1875
1876         // generate the searchpath
1877         FS_Rescan();
1878 }
1879
1880 void FS_Init_Commands(void)
1881 {
1882         Cvar_RegisterVariable (&scr_screenshot_name);
1883         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1884         Cvar_RegisterVariable (&cvar_fs_gamedir);
1885
1886         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1887         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1888         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1889         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1890         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1891         Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
1892 }
1893
1894 /*
1895 ================
1896 FS_Shutdown
1897 ================
1898 */
1899 void FS_Shutdown (void)
1900 {
1901         // close all pack files and such
1902         // (hopefully there aren't any other open files, but they'll be cleaned up
1903         //  by the OS anyway)
1904         FS_ClearSearchPath();
1905         Mem_FreePool (&fs_mempool);
1906
1907 #ifdef WIN32
1908         Sys_UnloadLibrary (&shfolder_dll);
1909 #endif
1910 }
1911
1912 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
1913 {
1914         int handle;
1915         int mod, opt;
1916         unsigned int ind;
1917
1918         // Parse the mode string
1919         switch (mode[0])
1920         {
1921                 case 'r':
1922                         mod = O_RDONLY;
1923                         opt = 0;
1924                         break;
1925                 case 'w':
1926                         mod = O_WRONLY;
1927                         opt = O_CREAT | O_TRUNC;
1928                         break;
1929                 case 'a':
1930                         mod = O_WRONLY;
1931                         opt = O_CREAT | O_APPEND;
1932                         break;
1933                 default:
1934                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1935                         return -1;
1936         }
1937         for (ind = 1; mode[ind] != '\0'; ind++)
1938         {
1939                 switch (mode[ind])
1940                 {
1941                         case '+':
1942                                 mod = O_RDWR;
1943                                 break;
1944                         case 'b':
1945                                 opt |= O_BINARY;
1946                                 break;
1947                         default:
1948                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1949                                                         filepath, mode, mode[ind]);
1950                 }
1951         }
1952
1953         if (nonblocking)
1954                 opt |= O_NONBLOCK;
1955
1956 #if _MSC_VER >= 1400
1957         _sopen_s(&handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1958 #else
1959         handle = open (filepath, mod | opt, 0666);
1960 #endif
1961         return handle;
1962 }
1963
1964 /*
1965 ====================
1966 FS_SysOpen
1967
1968 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1969 ====================
1970 */
1971 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1972 {
1973         qfile_t* file;
1974
1975         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1976         file->ungetc = EOF;
1977         file->handle = FS_SysOpenFD(filepath, mode, nonblocking);
1978         if (file->handle < 0)
1979         {
1980                 Mem_Free (file);
1981                 return NULL;
1982         }
1983
1984         file->filename = Mem_strdup(fs_mempool, filepath);
1985
1986         file->real_length = lseek (file->handle, 0, SEEK_END);
1987
1988         // For files opened in append mode, we start at the end of the file
1989         if (mode[0] == 'a')
1990                 file->position = file->real_length;
1991         else
1992                 lseek (file->handle, 0, SEEK_SET);
1993
1994         return file;
1995 }
1996
1997
1998 /*
1999 ===========
2000 FS_OpenPackedFile
2001
2002 Open a packed file using its package file descriptor
2003 ===========
2004 */
2005 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2006 {
2007         packfile_t *pfile;
2008         int dup_handle;
2009         qfile_t* file;
2010
2011         pfile = &pack->files[pack_ind];
2012
2013         // If we don't have the true offset, get it now
2014         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2015                 if (!PK3_GetTrueFileOffset (pfile, pack))
2016                         return NULL;
2017
2018 #ifndef LINK_TO_ZLIB
2019         // No Zlib DLL = no compressed files
2020         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2021         {
2022                 Con_Printf("WARNING: can't open the compressed file %s\n"
2023                                         "You need the Zlib DLL to use compressed files\n",
2024                                         pfile->name);
2025                 return NULL;
2026         }
2027 #endif
2028
2029         // LordHavoc: lseek affects all duplicates of a handle so we do it before
2030         // the dup() call to avoid having to close the dup_handle on error here
2031         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
2032         {
2033                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2034                                         pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2035                 return NULL;
2036         }
2037
2038         dup_handle = dup (pack->handle);
2039         if (dup_handle < 0)
2040         {
2041                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2042                 return NULL;
2043         }
2044
2045         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2046         memset (file, 0, sizeof (*file));
2047         file->handle = dup_handle;
2048         file->flags = QFILE_FLAG_PACKED;
2049         file->real_length = pfile->realsize;
2050         file->offset = pfile->offset;
2051         file->position = 0;
2052         file->ungetc = EOF;
2053
2054         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2055         {
2056                 ztoolkit_t *ztk;
2057
2058                 file->flags |= QFILE_FLAG_DEFLATED;
2059
2060                 // We need some more variables
2061                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2062
2063                 ztk->comp_length = pfile->packsize;
2064
2065                 // Initialize zlib stream
2066                 ztk->zstream.next_in = ztk->input;
2067                 ztk->zstream.avail_in = 0;
2068
2069                 /* From Zlib's "unzip.c":
2070                  *
2071                  * windowBits is passed < 0 to tell that there is no zlib header.
2072                  * Note that in this case inflate *requires* an extra "dummy" byte
2073                  * after the compressed stream in order to complete decompression and
2074                  * return Z_STREAM_END.
2075                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2076                  * size of both compressed and uncompressed data
2077                  */
2078                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2079                 {
2080                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2081                         close(dup_handle);
2082                         Mem_Free(file);
2083                         return NULL;
2084                 }
2085
2086                 ztk->zstream.next_out = file->buff;
2087                 ztk->zstream.avail_out = sizeof (file->buff);
2088
2089                 file->ztk = ztk;
2090         }
2091
2092         return file;
2093 }
2094
2095 /*
2096 ====================
2097 FS_CheckNastyPath
2098
2099 Return true if the path should be rejected due to one of the following:
2100 1: path elements that are non-portable
2101 2: path elements that would allow access to files outside the game directory,
2102    or are just not a good idea for a mod to be using.
2103 ====================
2104 */
2105 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2106 {
2107         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2108         if (!path[0])
2109                 return 2;
2110
2111         // Windows: don't allow \ in filenames (windows-only), period.
2112         // (on Windows \ is a directory separator, but / is also supported)
2113         if (strstr(path, "\\"))
2114                 return 1; // non-portable
2115
2116         // Mac: don't allow Mac-only filenames - : is a directory separator
2117         // instead of /, but we rely on / working already, so there's no reason to
2118         // support a Mac-only path
2119         // Amiga and Windows: : tries to go to root of drive
2120         if (strstr(path, ":"))
2121                 return 1; // non-portable attempt to go to root of drive
2122
2123         // Amiga: // is parent directory
2124         if (strstr(path, "//"))
2125                 return 1; // non-portable attempt to go to parent directory
2126
2127         // all: don't allow going to parent directory (../ or /../)
2128         if (strstr(path, ".."))
2129                 return 2; // attempt to go outside the game directory
2130
2131         // Windows and UNIXes: don't allow absolute paths
2132         if (path[0] == '/')
2133                 return 2; // attempt to go outside the game directory
2134
2135         // all: don't allow . characters before the last slash (it should only be used in filenames, not path elements), this catches all imaginable cases of ./, ../, .../, etc
2136         if (strchr(path, '.'))
2137         {
2138                 if (isgamedir)
2139                 {
2140                         // gamedir is entirely path elements, so simply forbid . entirely
2141                         return 2;
2142                 }
2143                 if (strchr(path, '.') < strrchr(path, '/'))
2144                         return 2; // possible attempt to go outside the game directory
2145         }
2146
2147         // all: forbid trailing slash on gamedir
2148         if (isgamedir && path[strlen(path)-1] == '/')
2149                 return 2;
2150
2151         // all: forbid leading dot on any filename for any reason
2152         if (strstr(path, "/."))
2153                 return 2; // attempt to go outside the game directory
2154
2155         // after all these checks we're pretty sure it's a / separated filename
2156         // and won't do much if any harm
2157         return false;
2158 }
2159
2160
2161 /*
2162 ====================
2163 FS_FindFile
2164
2165 Look for a file in the packages and in the filesystem
2166
2167 Return the searchpath where the file was found (or NULL)
2168 and the file index in the package if relevant
2169 ====================
2170 */
2171 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2172 {
2173         searchpath_t *search;
2174         pack_t *pak;
2175
2176         // search through the path, one element at a time
2177         for (search = fs_searchpaths;search;search = search->next)
2178         {
2179                 // is the element a pak file?
2180                 if (search->pack && !search->pack->vpack)
2181                 {
2182                         int (*strcmp_funct) (const char* str1, const char* str2);
2183                         int left, right, middle;
2184
2185                         pak = search->pack;
2186                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2187
2188                         // Look for the file (binary search)
2189                         left = 0;
2190                         right = pak->numfiles - 1;
2191                         while (left <= right)
2192                         {
2193                                 int diff;
2194
2195                                 middle = (left + right) / 2;
2196                                 diff = strcmp_funct (pak->files[middle].name, name);
2197
2198                                 // Found it
2199                                 if (!diff)
2200                                 {
2201                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2202                                         {
2203                                                 // yes, but the first one is empty so we treat it as not being there
2204                                                 if (!quiet && developer_extra.integer)
2205                                                         Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2206
2207                                                 if (index != NULL)
2208                                                         *index = -1;
2209                                                 return NULL;
2210                                         }
2211
2212                                         if (!quiet && developer_extra.integer)
2213                                                 Con_DPrintf("FS_FindFile: %s in %s\n",
2214                                                                         pak->files[middle].name, pak->filename);
2215
2216                                         if (index != NULL)
2217                                                 *index = middle;
2218                                         return search;
2219                                 }
2220
2221                                 // If we're too far in the list
2222                                 if (diff > 0)
2223                                         right = middle - 1;
2224                                 else
2225                                         left = middle + 1;
2226                         }
2227                 }
2228                 else
2229                 {
2230                         char netpath[MAX_OSPATH];
2231                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2232                         if (FS_SysFileExists (netpath))
2233                         {
2234                                 if (!quiet && developer_extra.integer)
2235                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
2236
2237                                 if (index != NULL)
2238                                         *index = -1;
2239                                 return search;
2240                         }
2241                 }
2242         }
2243
2244         if (!quiet && developer_extra.integer)
2245                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2246
2247         if (index != NULL)
2248                 *index = -1;
2249         return NULL;
2250 }
2251
2252
2253 /*
2254 ===========
2255 FS_OpenReadFile
2256
2257 Look for a file in the search paths and open it in read-only mode
2258 ===========
2259 */
2260 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2261 {
2262         searchpath_t *search;
2263         int pack_ind;
2264
2265         search = FS_FindFile (filename, &pack_ind, quiet);
2266
2267         // Not found?
2268         if (search == NULL)
2269                 return NULL;
2270
2271         // Found in the filesystem?
2272         if (pack_ind < 0)
2273         {
2274                 // this works with vpacks, so we are fine
2275                 char path [MAX_OSPATH];
2276                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2277                 return FS_SysOpen (path, "rb", nonblocking);
2278         }
2279
2280         // So, we found it in a package...
2281
2282         // Is it a PK3 symlink?
2283         // TODO also handle directory symlinks by parsing the whole structure...
2284         // but heck, file symlinks are good enough for now
2285         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2286         {
2287                 if(symlinkLevels <= 0)
2288                 {
2289                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2290                         return NULL;
2291                 }
2292                 else
2293                 {
2294                         char linkbuf[MAX_QPATH];
2295                         fs_offset_t count;
2296                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2297                         const char *mergeslash;
2298                         char *mergestart;
2299
2300                         if(!linkfile)
2301                                 return NULL;
2302                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2303                         FS_Close(linkfile);
2304                         if(count < 0)
2305                                 return NULL;
2306                         linkbuf[count] = 0;
2307                         
2308                         // Now combine the paths...
2309                         mergeslash = strrchr(filename, '/');
2310                         mergestart = linkbuf;
2311                         if(!mergeslash)
2312                                 mergeslash = filename;
2313                         while(!strncmp(mergestart, "../", 3))
2314                         {
2315                                 mergestart += 3;
2316                                 while(mergeslash > filename)
2317                                 {
2318                                         --mergeslash;
2319                                         if(*mergeslash == '/')
2320                                                 break;
2321                                 }
2322                         }
2323                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2324                         if(mergeslash == filename)
2325                         {
2326                                 // Either mergeslash == filename, then we just replace the name (done below)
2327                         }
2328                         else
2329                         {
2330                                 // Or, we append the name after mergeslash;
2331                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2332                                 int spaceNeeded = mergeslash - filename + 1;
2333                                 int spaceRemoved = mergestart - linkbuf;
2334                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2335                                 {
2336                                         Con_DPrintf("symlink: too long path rejected\n");
2337                                         return NULL;
2338                                 }
2339                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2340                                 memcpy(linkbuf, filename, spaceNeeded);
2341                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2342                                 mergestart = linkbuf;
2343                         }
2344                         if (!quiet && developer_loading.integer)
2345                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2346                         if(FS_CheckNastyPath (mergestart, false))
2347                         {
2348                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2349                                 return NULL;
2350                         }
2351                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2352                 }
2353         }
2354
2355         return FS_OpenPackedFile (search->pack, pack_ind);
2356 }
2357
2358
2359 /*
2360 =============================================================================
2361
2362 MAIN PUBLIC FUNCTIONS
2363
2364 =============================================================================
2365 */
2366
2367 /*
2368 ====================
2369 FS_OpenRealFile
2370
2371 Open a file in the userpath. The syntax is the same as fopen
2372 Used for savegame scanning in menu, and all file writing.
2373 ====================
2374 */
2375 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2376 {
2377         char real_path [MAX_OSPATH];
2378
2379         if (FS_CheckNastyPath(filepath, false))
2380         {
2381                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2382                 return NULL;
2383         }
2384
2385         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2386
2387         // If the file is opened in "write", "append", or "read/write" mode,
2388         // create directories up to the file.
2389         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2390                 FS_CreatePath (real_path);
2391         return FS_SysOpen (real_path, mode, false);
2392 }
2393
2394
2395 /*
2396 ====================
2397 FS_OpenVirtualFile
2398
2399 Open a file. The syntax is the same as fopen
2400 ====================
2401 */
2402 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2403 {
2404         if (FS_CheckNastyPath(filepath, false))
2405         {
2406                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2407                 return NULL;
2408         }
2409
2410         return FS_OpenReadFile (filepath, quiet, false, 16);
2411 }
2412
2413
2414 /*
2415 ====================
2416 FS_FileFromData
2417
2418 Open a file. The syntax is the same as fopen
2419 ====================
2420 */
2421 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2422 {
2423         qfile_t* file;
2424         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2425         memset (file, 0, sizeof (*file));
2426         file->flags = QFILE_FLAG_DATA;
2427         file->ungetc = EOF;
2428         file->real_length = size;
2429         file->data = data;
2430         return file;
2431 }
2432
2433 /*
2434 ====================
2435 FS_Close
2436
2437 Close a file
2438 ====================
2439 */
2440 int FS_Close (qfile_t* file)
2441 {
2442         if(file->flags & QFILE_FLAG_DATA)
2443         {
2444                 Mem_Free(file);
2445                 return 0;
2446         }
2447
2448         if (close (file->handle))
2449                 return EOF;
2450
2451         if (file->filename)
2452         {
2453                 if (file->flags & QFILE_FLAG_REMOVE)
2454                         remove(file->filename);
2455
2456                 Mem_Free((void *) file->filename);
2457         }
2458
2459         if (file->ztk)
2460         {
2461                 qz_inflateEnd (&file->ztk->zstream);
2462                 Mem_Free (file->ztk);
2463         }
2464
2465         Mem_Free (file);
2466         return 0;
2467 }
2468
2469 void FS_RemoveOnClose(qfile_t* file)
2470 {
2471         file->flags |= QFILE_FLAG_REMOVE;
2472 }
2473
2474 /*
2475 ====================
2476 FS_Write
2477
2478 Write "datasize" bytes into a file
2479 ====================
2480 */
2481 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2482 {
2483         fs_offset_t result;
2484
2485         // If necessary, seek to the exact file position we're supposed to be
2486         if (file->buff_ind != file->buff_len)
2487                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2488
2489         // Purge cached data
2490         FS_Purge (file);
2491
2492         // Write the buffer and update the position
2493         result = write (file->handle, data, (fs_offset_t)datasize);
2494         file->position = lseek (file->handle, 0, SEEK_CUR);
2495         if (file->real_length < file->position)
2496                 file->real_length = file->position;
2497
2498         if (result < 0)
2499                 return 0;
2500
2501         return result;
2502 }
2503
2504
2505 /*
2506 ====================
2507 FS_Read
2508
2509 Read up to "buffersize" bytes from a file
2510 ====================
2511 */
2512 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2513 {
2514         fs_offset_t count, done;
2515
2516         if (buffersize == 0)
2517                 return 0;
2518
2519         // Get rid of the ungetc character
2520         if (file->ungetc != EOF)
2521         {
2522                 ((char*)buffer)[0] = file->ungetc;
2523                 buffersize--;
2524                 file->ungetc = EOF;
2525                 done = 1;
2526         }
2527         else
2528                 done = 0;
2529
2530         if(file->flags & QFILE_FLAG_DATA)
2531         {
2532                 size_t left = file->real_length - file->position;
2533                 if(buffersize > left)
2534                         buffersize = left;
2535                 memcpy(buffer, file->data + file->position, buffersize);
2536                 file->position += buffersize;
2537                 return buffersize;
2538         }
2539
2540         // First, we copy as many bytes as we can from "buff"
2541         if (file->buff_ind < file->buff_len)
2542         {
2543                 count = file->buff_len - file->buff_ind;
2544                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2545                 done += count;
2546                 memcpy (buffer, &file->buff[file->buff_ind], count);
2547                 file->buff_ind += count;
2548
2549                 buffersize -= count;
2550                 if (buffersize == 0)
2551                         return done;
2552         }
2553
2554         // NOTE: at this point, the read buffer is always empty
2555
2556         // If the file isn't compressed
2557         if (! (file->flags & QFILE_FLAG_DEFLATED))
2558         {
2559                 fs_offset_t nb;
2560
2561                 // We must take care to not read after the end of the file
2562                 count = file->real_length - file->position;
2563
2564                 // If we have a lot of data to get, put them directly into "buffer"
2565                 if (buffersize > sizeof (file->buff) / 2)
2566                 {
2567                         if (count > (fs_offset_t)buffersize)
2568                                 count = (fs_offset_t)buffersize;
2569                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2570                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2571                         if (nb > 0)
2572                         {
2573                                 done += nb;
2574                                 file->position += nb;
2575
2576                                 // Purge cached data
2577                                 FS_Purge (file);
2578                         }
2579                 }
2580                 else
2581                 {
2582                         if (count > (fs_offset_t)sizeof (file->buff))
2583                                 count = (fs_offset_t)sizeof (file->buff);
2584                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2585                         nb = read (file->handle, file->buff, count);
2586                         if (nb > 0)
2587                         {
2588                                 file->buff_len = nb;
2589                                 file->position += nb;
2590
2591                                 // Copy the requested data in "buffer" (as much as we can)
2592                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2593                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2594                                 file->buff_ind = count;
2595                                 done += count;
2596                         }
2597                 }
2598
2599                 return done;
2600         }
2601
2602         // If the file is compressed, it's more complicated...
2603         // We cycle through a few operations until we have read enough data
2604         while (buffersize > 0)
2605         {
2606                 ztoolkit_t *ztk = file->ztk;
2607                 int error;
2608
2609                 // NOTE: at this point, the read buffer is always empty
2610
2611                 // If "input" is also empty, we need to refill it
2612                 if (ztk->in_ind == ztk->in_len)
2613                 {
2614                         // If we are at the end of the file
2615                         if (file->position == file->real_length)
2616                                 return done;
2617
2618                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2619                         if (count > (fs_offset_t)sizeof (ztk->input))
2620                                 count = (fs_offset_t)sizeof (ztk->input);
2621                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2622                         if (read (file->handle, ztk->input, count) != count)
2623                         {
2624                                 Con_Printf ("FS_Read: unexpected end of file\n");
2625                                 break;
2626                         }
2627
2628                         ztk->in_ind = 0;
2629                         ztk->in_len = count;
2630                         ztk->in_position += count;
2631                 }
2632
2633                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2634                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2635
2636                 // Now that we are sure we have compressed data available, we need to determine
2637                 // if it's better to inflate it in "file->buff" or directly in "buffer"
2638
2639                 // Inflate the data in "file->buff"
2640                 if (buffersize < sizeof (file->buff) / 2)
2641                 {
2642                         ztk->zstream.next_out = file->buff;
2643                         ztk->zstream.avail_out = sizeof (file->buff);
2644                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2645                         if (error != Z_OK && error != Z_STREAM_END)
2646                         {
2647                                 Con_Printf ("FS_Read: Can't inflate file\n");
2648                                 break;
2649                         }
2650                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2651
2652                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2653                         file->position += file->buff_len;
2654
2655                         // Copy the requested data in "buffer" (as much as we can)
2656                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2657                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2658                         file->buff_ind = count;
2659                 }
2660
2661                 // Else, we inflate directly in "buffer"
2662                 else
2663                 {
2664                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2665                         ztk->zstream.avail_out = (unsigned int)buffersize;
2666                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2667                         if (error != Z_OK && error != Z_STREAM_END)
2668                         {
2669                                 Con_Printf ("FS_Read: Can't inflate file\n");
2670                                 break;
2671                         }
2672                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2673
2674                         // How much data did it inflate?
2675                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2676                         file->position += count;
2677
2678                         // Purge cached data
2679                         FS_Purge (file);
2680                 }
2681
2682                 done += count;
2683                 buffersize -= count;
2684         }
2685
2686         return done;
2687 }
2688
2689
2690 /*
2691 ====================
2692 FS_Print
2693
2694 Print a string into a file
2695 ====================
2696 */
2697 int FS_Print (qfile_t* file, const char *msg)
2698 {
2699         return (int)FS_Write (file, msg, strlen (msg));
2700 }
2701
2702 /*
2703 ====================
2704 FS_Printf
2705
2706 Print a string into a file
2707 ====================
2708 */
2709 int FS_Printf(qfile_t* file, const char* format, ...)
2710 {
2711         int result;
2712         va_list args;
2713
2714         va_start (args, format);
2715         result = FS_VPrintf (file, format, args);
2716         va_end (args);
2717
2718         return result;
2719 }
2720
2721
2722 /*
2723 ====================
2724 FS_VPrintf
2725
2726 Print a string into a file
2727 ====================
2728 */
2729 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2730 {
2731         int len;
2732         fs_offset_t buff_size = MAX_INPUTLINE;
2733         char *tempbuff;
2734
2735         for (;;)
2736         {
2737                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2738                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2739                 if (len >= 0 && len < buff_size)
2740                         break;
2741                 Mem_Free (tempbuff);
2742                 buff_size *= 2;
2743         }
2744
2745         len = write (file->handle, tempbuff, len);
2746         Mem_Free (tempbuff);
2747
2748         return len;
2749 }
2750
2751
2752 /*
2753 ====================
2754 FS_Getc
2755
2756 Get the next character of a file
2757 ====================
2758 */
2759 int FS_Getc (qfile_t* file)
2760 {
2761         unsigned char c;
2762
2763         if (FS_Read (file, &c, 1) != 1)
2764                 return EOF;
2765
2766         return c;
2767 }
2768
2769
2770 /*
2771 ====================
2772 FS_UnGetc
2773
2774 Put a character back into the read buffer (only supports one character!)
2775 ====================
2776 */
2777 int FS_UnGetc (qfile_t* file, unsigned char c)
2778 {
2779         // If there's already a character waiting to be read
2780         if (file->ungetc != EOF)
2781                 return EOF;
2782
2783         file->ungetc = c;
2784         return c;
2785 }
2786
2787
2788 /*
2789 ====================
2790 FS_Seek
2791
2792 Move the position index in a file
2793 ====================
2794 */
2795 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2796 {
2797         ztoolkit_t *ztk;
2798         unsigned char* buffer;
2799         fs_offset_t buffersize;
2800
2801         // Compute the file offset
2802         switch (whence)
2803         {
2804                 case SEEK_CUR:
2805                         offset += file->position - file->buff_len + file->buff_ind;
2806                         break;
2807
2808                 case SEEK_SET:
2809                         break;
2810
2811                 case SEEK_END:
2812                         offset += file->real_length;
2813                         break;
2814
2815                 default:
2816                         return -1;
2817         }
2818         if (offset < 0 || offset > file->real_length)
2819                 return -1;
2820
2821         if(file->flags & QFILE_FLAG_DATA)
2822         {
2823                 file->position = offset;
2824                 return 0;
2825         }
2826
2827         // If we have the data in our read buffer, we don't need to actually seek
2828         if (file->position - file->buff_len <= offset && offset <= file->position)
2829         {
2830                 file->buff_ind = offset + file->buff_len - file->position;
2831                 return 0;
2832         }
2833
2834         // Purge cached data
2835         FS_Purge (file);
2836
2837         // Unpacked or uncompressed files can seek directly
2838         if (! (file->flags & QFILE_FLAG_DEFLATED))
2839         {
2840                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2841                         return -1;
2842                 file->position = offset;
2843                 return 0;
2844         }
2845
2846         // Seeking in compressed files is more a hack than anything else,
2847         // but we need to support it, so here we go.
2848         ztk = file->ztk;
2849
2850         // If we have to go back in the file, we need to restart from the beginning
2851         if (offset <= file->position)
2852         {
2853                 ztk->in_ind = 0;
2854                 ztk->in_len = 0;
2855                 ztk->in_position = 0;
2856                 file->position = 0;
2857                 lseek (file->handle, file->offset, SEEK_SET);
2858
2859                 // Reset the Zlib stream
2860                 ztk->zstream.next_in = ztk->input;
2861                 ztk->zstream.avail_in = 0;
2862                 qz_inflateReset (&ztk->zstream);
2863         }
2864
2865         // We need a big buffer to force inflating into it directly
2866         buffersize = 2 * sizeof (file->buff);
2867         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2868
2869         // Skip all data until we reach the requested offset
2870         while (offset > file->position)
2871         {
2872                 fs_offset_t diff = offset - file->position;
2873                 fs_offset_t count, len;
2874
2875                 count = (diff > buffersize) ? buffersize : diff;
2876                 len = FS_Read (file, buffer, count);
2877                 if (len != count)
2878                 {
2879                         Mem_Free (buffer);
2880                         return -1;
2881                 }
2882         }
2883
2884         Mem_Free (buffer);
2885         return 0;
2886 }
2887
2888
2889 /*
2890 ====================
2891 FS_Tell
2892
2893 Give the current position in a file
2894 ====================
2895 */
2896 fs_offset_t FS_Tell (qfile_t* file)
2897 {
2898         return file->position - file->buff_len + file->buff_ind;
2899 }
2900
2901
2902 /*
2903 ====================
2904 FS_FileSize
2905
2906 Give the total size of a file
2907 ====================
2908 */
2909 fs_offset_t FS_FileSize (qfile_t* file)
2910 {
2911         return file->real_length;
2912 }
2913
2914
2915 /*
2916 ====================
2917 FS_Purge
2918
2919 Erases any buffered input or output data
2920 ====================
2921 */
2922 void FS_Purge (qfile_t* file)
2923 {
2924         file->buff_len = 0;
2925         file->buff_ind = 0;
2926         file->ungetc = EOF;
2927 }
2928
2929
2930 /*
2931 ============
2932 FS_LoadFile
2933
2934 Filename are relative to the quake directory.
2935 Always appends a 0 byte.
2936 ============
2937 */
2938 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2939 {
2940         qfile_t *file;
2941         unsigned char *buf = NULL;
2942         fs_offset_t filesize = 0;
2943
2944         file = FS_OpenVirtualFile(path, quiet);
2945         if (file)
2946         {
2947                 filesize = file->real_length;
2948                 if(filesize < 0)
2949                 {
2950                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
2951                         FS_Close(file);
2952                         return NULL;
2953                 }
2954
2955                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2956                 buf[filesize] = '\0';
2957                 FS_Read (file, buf, filesize);
2958                 FS_Close (file);
2959                 if (developer_loadfile.integer)
2960                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2961         }
2962
2963         if (filesizepointer)
2964                 *filesizepointer = filesize;
2965         return buf;
2966 }
2967
2968
2969 /*
2970 ============
2971 FS_WriteFile
2972
2973 The filename will be prefixed by the current game directory
2974 ============
2975 */
2976 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
2977 {
2978         qfile_t *file;
2979         size_t i;
2980         fs_offset_t lentotal;
2981
2982         file = FS_OpenRealFile(filename, "wb", false);
2983         if (!file)
2984         {
2985                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2986                 return false;
2987         }
2988
2989         lentotal = 0;
2990         for(i = 0; i < count; ++i)
2991                 lentotal += len[i];
2992         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
2993         for(i = 0; i < count; ++i)
2994                 FS_Write (file, data[i], len[i]);
2995         FS_Close (file);
2996         return true;
2997 }
2998
2999 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3000 {
3001         return FS_WriteFileInBlocks(filename, &data, &len, 1);
3002 }
3003
3004
3005 /*
3006 =============================================================================
3007
3008 OTHERS PUBLIC FUNCTIONS
3009
3010 =============================================================================
3011 */
3012
3013 /*
3014 ============
3015 FS_StripExtension
3016 ============
3017 */
3018 void FS_StripExtension (const char *in, char *out, size_t size_out)
3019 {
3020         char *last = NULL;
3021         char currentchar;
3022
3023         if (size_out == 0)
3024                 return;
3025
3026         while ((currentchar = *in) && size_out > 1)
3027         {
3028                 if (currentchar == '.')
3029                         last = out;
3030                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3031                         last = NULL;
3032                 *out++ = currentchar;
3033                 in++;
3034                 size_out--;
3035         }
3036         if (last)
3037                 *last = 0;
3038         else
3039                 *out = 0;
3040 }
3041
3042
3043 /*
3044 ==================
3045 FS_DefaultExtension
3046 ==================
3047 */
3048 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3049 {
3050         const char *src;
3051
3052         // if path doesn't have a .EXT, append extension
3053         // (extension should include the .)
3054         src = path + strlen(path) - 1;
3055
3056         while (*src != '/' && src != path)
3057         {
3058                 if (*src == '.')
3059                         return;                 // it has an extension
3060                 src--;
3061         }
3062
3063         strlcat (path, extension, size_path);
3064 }
3065
3066
3067 /*
3068 ==================
3069 FS_FileType
3070
3071 Look for a file in the packages and in the filesystem
3072 ==================
3073 */
3074 int FS_FileType (const char *filename)
3075 {
3076         searchpath_t *search;
3077         char fullpath[MAX_OSPATH];
3078
3079         search = FS_FindFile (filename, NULL, true);
3080         if(!search)
3081                 return FS_FILETYPE_NONE;
3082
3083         if(search->pack && !search->pack->vpack)
3084                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3085
3086         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3087         return FS_SysFileType(fullpath);
3088 }
3089
3090
3091 /*
3092 ==================
3093 FS_FileExists
3094
3095 Look for a file in the packages and in the filesystem
3096 ==================
3097 */
3098 qboolean FS_FileExists (const char *filename)
3099 {
3100         return (FS_FindFile (filename, NULL, true) != NULL);
3101 }
3102
3103
3104 /*
3105 ==================
3106 FS_SysFileExists
3107
3108 Look for a file in the filesystem only
3109 ==================
3110 */
3111 int FS_SysFileType (const char *path)
3112 {
3113 #if WIN32
3114 // Sajt - some older sdks are missing this define
3115 # ifndef INVALID_FILE_ATTRIBUTES
3116 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3117 # endif
3118
3119         DWORD result = GetFileAttributes(path);
3120
3121         if(result == INVALID_FILE_ATTRIBUTES)
3122                 return FS_FILETYPE_NONE;
3123
3124         if(result & FILE_ATTRIBUTE_DIRECTORY)
3125                 return FS_FILETYPE_DIRECTORY;
3126
3127         return FS_FILETYPE_FILE;
3128 #else
3129         struct stat buf;
3130
3131         if (stat (path,&buf) == -1)
3132                 return FS_FILETYPE_NONE;
3133
3134 #ifndef S_ISDIR
3135 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3136 #endif
3137         if(S_ISDIR(buf.st_mode))
3138                 return FS_FILETYPE_DIRECTORY;
3139
3140         return FS_FILETYPE_FILE;
3141 #endif
3142 }
3143
3144 qboolean FS_SysFileExists (const char *path)
3145 {
3146         return FS_SysFileType (path) != FS_FILETYPE_NONE;
3147 }
3148
3149 void FS_mkdir (const char *path)
3150 {
3151 #if WIN32
3152         _mkdir (path);
3153 #else
3154         mkdir (path, 0777);
3155 #endif
3156 }
3157
3158 /*
3159 ===========
3160 FS_Search
3161
3162 Allocate and fill a search structure with information on matching filenames.
3163 ===========
3164 */
3165 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3166 {
3167         fssearch_t *search;
3168         searchpath_t *searchpath;
3169         pack_t *pak;
3170         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3171         stringlist_t resultlist;
3172         stringlist_t dirlist;
3173         const char *slash, *backslash, *colon, *separator;
3174         char *basepath;
3175         char temp[MAX_OSPATH];
3176
3177         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3178                 ;
3179
3180         if (i > 0)
3181         {
3182                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3183                 return NULL;
3184         }
3185
3186         stringlistinit(&resultlist);
3187         stringlistinit(&dirlist);
3188         search = NULL;
3189         slash = strrchr(pattern, '/');
3190         backslash = strrchr(pattern, '\\');
3191         colon = strrchr(pattern, ':');
3192         separator = max(slash, backslash);
3193         separator = max(separator, colon);
3194         basepathlength = separator ? (separator + 1 - pattern) : 0;
3195         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3196         if (basepathlength)
3197                 memcpy(basepath, pattern, basepathlength);
3198         basepath[basepathlength] = 0;
3199
3200         // search through the path, one element at a time
3201         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3202         {
3203                 // is the element a pak file?
3204                 if (searchpath->pack && !searchpath->pack->vpack)
3205                 {
3206                         // look through all the pak file elements
3207                         pak = searchpath->pack;
3208                         for (i = 0;i < pak->numfiles;i++)
3209                         {
3210                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
3211                                 while (temp[0])
3212                                 {
3213                                         if (matchpattern(temp, (char *)pattern, true))
3214                                         {
3215                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3216                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
3217                                                                 break;
3218                                                 if (resultlistindex == resultlist.numstrings)
3219                                                 {
3220                                                         stringlistappend(&resultlist, temp);
3221                                                         if (!quiet && developer_loading.integer)
3222                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3223                                                 }
3224                                         }
3225                                         // strip off one path element at a time until empty
3226                                         // this way directories are added to the listing if they match the pattern
3227                                         slash = strrchr(temp, '/');
3228                                         backslash = strrchr(temp, '\\');
3229                                         colon = strrchr(temp, ':');
3230                                         separator = temp;
3231                                         if (separator < slash)
3232                                                 separator = slash;
3233                                         if (separator < backslash)
3234                                                 separator = backslash;
3235                                         if (separator < colon)
3236                                                 separator = colon;
3237                                         *((char *)separator) = 0;
3238                                 }
3239                         }
3240                 }
3241                 else
3242                 {
3243                         stringlist_t matchedSet, foundSet;
3244                         const char *start = pattern;
3245
3246                         stringlistinit(&matchedSet);
3247                         stringlistinit(&foundSet);
3248                         // add a first entry to the set
3249                         stringlistappend(&matchedSet, "");
3250                         // iterate through pattern's path
3251                         while (*start)
3252                         {
3253                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3254                                 char subpath[MAX_OSPATH];
3255                                 char subpattern[MAX_OSPATH];
3256
3257                                 // find the next wildcard
3258                                 wildcard = strchr(start, '?');
3259                                 asterisk = strchr(start, '*');
3260                                 if (asterisk && (!wildcard || asterisk < wildcard))
3261                                 {
3262                                         wildcard = asterisk;
3263                                 }
3264
3265                                 if (wildcard)
3266                                 {
3267                                         nextseparator = strchr( wildcard, '/' );
3268                                 }
3269                                 else
3270                                 {
3271                                         nextseparator = NULL;
3272                                 }
3273
3274                                 if( !nextseparator ) {
3275                                         nextseparator = start + strlen( start );
3276                                 }
3277
3278                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3279                                 // copy everything up except nextseperator
3280                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3281                                 // find the last '/' before the wildcard
3282                                 prevseparator = strrchr( subpattern, '/' );
3283                                 if (!prevseparator)
3284                                         prevseparator = subpattern;
3285                                 else
3286                                         prevseparator++;
3287                                 // copy everything from start to the previous including the '/' (before the wildcard)
3288                                 // everything up to start is already included in the path of matchedSet's entries
3289                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3290
3291                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3292                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3293                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3294                                         strlcat( temp, subpath, sizeof(temp) );
3295                                         listdirectory( &foundSet, searchpath->filename, temp );
3296                                 }
3297                                 if( dirlistindex == 0 ) {
3298                                         break;
3299                                 }
3300                                 // reset the current result set
3301                                 stringlistfreecontents( &matchedSet );
3302                                 // match against the pattern
3303                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3304                                         const char *direntry = foundSet.strings[ dirlistindex ];
3305                                         if (matchpattern(direntry, subpattern, true)) {
3306                                                 stringlistappend( &matchedSet, direntry );
3307                                         }
3308                                 }
3309                                 stringlistfreecontents( &foundSet );
3310
3311                                 start = nextseparator;
3312                         }
3313
3314                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3315                         {
3316                                 const char *temp = matchedSet.strings[dirlistindex];
3317                                 if (matchpattern(temp, (char *)pattern, true))
3318                                 {
3319                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3320                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
3321                                                         break;
3322                                         if (resultlistindex == resultlist.numstrings)
3323                                         {
3324                                                 stringlistappend(&resultlist, temp);
3325                                                 if (!quiet && developer_loading.integer)
3326                                                         Con_Printf("SearchDirFile: %s\n", temp);
3327                                         }
3328                                 }
3329                         }
3330                         stringlistfreecontents( &matchedSet );
3331                 }
3332         }
3333
3334         if (resultlist.numstrings)
3335         {
3336                 stringlistsort(&resultlist);
3337                 numfiles = resultlist.numstrings;
3338                 numchars = 0;
3339                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3340                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3341                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3342                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3343                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3344                 search->numfilenames = (int)numfiles;
3345                 numfiles = 0;
3346                 numchars = 0;
3347                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3348                 {
3349                         size_t textlen;
3350                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
3351                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3352                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3353                         numfiles++;
3354                         numchars += (int)textlen;
3355                 }
3356         }
3357         stringlistfreecontents(&resultlist);
3358
3359         Mem_Free(basepath);
3360         return search;
3361 }
3362
3363 void FS_FreeSearch(fssearch_t *search)
3364 {
3365         Z_Free(search);
3366 }
3367
3368 extern int con_linewidth;
3369 int FS_ListDirectory(const char *pattern, int oneperline)
3370 {
3371         int numfiles;
3372         int numcolumns;
3373         int numlines;
3374         int columnwidth;
3375         int linebufpos;
3376         int i, j, k, l;
3377         const char *name;
3378         char linebuf[MAX_INPUTLINE];
3379         fssearch_t *search;
3380         search = FS_Search(pattern, true, true);
3381         if (!search)
3382                 return 0;
3383         numfiles = search->numfilenames;
3384         if (!oneperline)
3385         {
3386                 // FIXME: the names could be added to one column list and then
3387                 // gradually shifted into the next column if they fit, and then the
3388                 // next to make a compact variable width listing but it's a lot more
3389                 // complicated...
3390                 // find width for columns
3391                 columnwidth = 0;
3392                 for (i = 0;i < numfiles;i++)
3393                 {
3394                         l = (int)strlen(search->filenames[i]);
3395                         if (columnwidth < l)
3396                                 columnwidth = l;
3397                 }
3398                 // count the spacing character
3399                 columnwidth++;
3400                 // calculate number of columns
3401                 numcolumns = con_linewidth / columnwidth;
3402                 // don't bother with the column printing if it's only one column
3403                 if (numcolumns >= 2)
3404                 {
3405                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3406                         for (i = 0;i < numlines;i++)
3407                         {
3408                                 linebufpos = 0;
3409                                 for (k = 0;k < numcolumns;k++)
3410                                 {
3411                                         l = i * numcolumns + k;
3412                                         if (l < numfiles)
3413                                         {
3414                                                 name = search->filenames[l];
3415                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3416                                                         linebuf[linebufpos++] = name[j];
3417                                                 // space out name unless it's the last on the line
3418                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
3419                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3420                                                                 linebuf[linebufpos++] = ' ';
3421                                         }
3422                                 }
3423                                 linebuf[linebufpos] = 0;
3424                                 Con_Printf("%s\n", linebuf);
3425                         }
3426                 }
3427                 else
3428                         oneperline = true;
3429         }
3430         if (oneperline)
3431                 for (i = 0;i < numfiles;i++)
3432                         Con_Printf("%s\n", search->filenames[i]);
3433         FS_FreeSearch(search);
3434         return (int)numfiles;
3435 }
3436
3437 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3438 {
3439         const char *pattern;
3440         if (Cmd_Argc() > 3)
3441         {
3442                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3443                 return;
3444         }
3445         if (Cmd_Argc() == 2)
3446                 pattern = Cmd_Argv(1);
3447         else
3448                 pattern = "*";
3449         if (!FS_ListDirectory(pattern, oneperline))
3450                 Con_Print("No files found.\n");
3451 }
3452
3453 void FS_Dir_f(void)
3454 {
3455         FS_ListDirectoryCmd("dir", true);
3456 }
3457
3458 void FS_Ls_f(void)
3459 {
3460         FS_ListDirectoryCmd("ls", false);
3461 }
3462
3463 void FS_Which_f(void)
3464 {
3465         const char *filename;
3466         int index;
3467         searchpath_t *sp;
3468         if (Cmd_Argc() != 2)
3469         {
3470                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3471                 return;
3472         }  
3473         filename = Cmd_Argv(1);
3474         sp = FS_FindFile(filename, &index, true);
3475         if (!sp) {
3476                 Con_Printf("%s isn't anywhere\n", filename);
3477                 return;
3478         }
3479         if (sp->pack)
3480         {
3481                 if(sp->pack->vpack)
3482                         Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3483                 else
3484                         Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3485         }
3486         else
3487                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3488 }
3489
3490
3491 const char *FS_WhichPack(const char *filename)
3492 {
3493         int index;
3494         searchpath_t *sp = FS_FindFile(filename, &index, true);
3495         if(sp && sp->pack)
3496       &n