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