80916b0f8f323de03f006bac22890b63e4a77cf8
[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         // see if we can write to this path (note: won't create path)
1879 #if _MSC_VER >= 1400
1880         _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!
1881 #else
1882         fd = open (va("%s%s/config.cfg", userdir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1883 #endif
1884         if(fd >= 0)
1885         {
1886                 close(fd);
1887                 return 1; // good choice - the path exists and is writable
1888         }
1889         else
1890                 return 0; // probably good - failed to write but maybe we need to create path
1891 }
1892
1893 /*
1894 ================
1895 FS_Init
1896 ================
1897 */
1898 void FS_Init (void)
1899 {
1900         const char *p;
1901         int i;
1902
1903         *fs_basedir = 0;
1904         *fs_userdir = 0;
1905         *fs_gamedir = 0;
1906
1907         // -basedir <path>
1908         // Overrides the system supplied base directory (under GAMENAME)
1909 // 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)
1910         i = COM_CheckParm ("-basedir");
1911         if (i && i < com_argc-1)
1912         {
1913                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1914                 i = (int)strlen (fs_basedir);
1915                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1916                         fs_basedir[i-1] = 0;
1917         }
1918         else
1919         {
1920 // If the base directory is explicitly defined by the compilation process
1921 #ifdef DP_FS_BASEDIR
1922                 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1923 #elif defined(MACOSX)
1924                 // FIXME: is there a better way to find the directory outside the .app?
1925                 // FIXME: check if game data is inside .app bundle
1926                 if (strstr(com_argv[0], ".app/"))
1927                 {
1928                         char *split;
1929                         strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1930                         split = strstr(fs_basedir, ".app/");
1931                         if (split)
1932                         {
1933                                 while (split > fs_basedir && *split != '/')
1934                                         split--;
1935                                 *split = 0;
1936                         }
1937                 }
1938 #endif
1939         }
1940
1941         // make sure the appending of a path separator won't create an unterminated string
1942         memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
1943         // add a path separator to the end of the basedir if it lacks one
1944         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1945                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1946
1947         // Add the personal game directory
1948         if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1949                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
1950         else if (COM_CheckParm("-nohome"))
1951                 *fs_userdir = 0; // user wants roaming installation, no userdir
1952         else
1953         {
1954                 int dirmode;
1955                 int highestuserdirmode = USERDIRMODE_COUNT - 1;
1956                 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
1957                 int userdirstatus[USERDIRMODE_COUNT];
1958 #ifdef WIN32
1959                 // historical behavior...
1960                 if (!strcmp(gamedirname1, "id1"))
1961                         preferreduserdirmode = USERDIRMODE_NOHOME;
1962 #endif
1963                 // check what limitations the user wants to impose
1964                 if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
1965                 if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
1966                 if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
1967                 // gather the status of the possible userdirs
1968                 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
1969                 {
1970                         userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
1971                         if (userdirstatus[dirmode] == 1)
1972                                 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
1973                         else if (userdirstatus[dirmode] == 0)
1974                                 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
1975                         else
1976                                 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
1977                 }
1978                 // some games may prefer writing to basedir, but if write fails we
1979                 // have to search for a real userdir...
1980                 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
1981                         preferreduserdirmode = highestuserdirmode;
1982                 // check for an existing userdir and continue using it if possible...
1983                 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
1984                         if (userdirstatus[dirmode] == 1)
1985                                 break;
1986                 // if no existing userdir found, make a new one...
1987                 if (dirmode == 0 && preferreduserdirmode > 0)
1988                         for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
1989                                 if (userdirstatus[dirmode] >= 0)
1990                                         break;
1991                 // and finally, we picked one...
1992                 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
1993                 Con_DPrintf("userdir %i is the winner\n", dirmode);
1994         }
1995
1996         // if userdir equal to basedir, clear it to avoid confusion later
1997         if (!strcmp(fs_basedir, fs_userdir))
1998                 fs_userdir[0] = 0;
1999
2000         FS_ListGameDirs();
2001
2002         p = FS_CheckGameDir(gamedirname1);
2003         if(!p || p == fs_checkgamedir_missing)
2004                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2005
2006         if(gamedirname2)
2007         {
2008                 p = FS_CheckGameDir(gamedirname2);
2009                 if(!p || p == fs_checkgamedir_missing)
2010                         Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2011         }
2012
2013         // -game <gamedir>
2014         // Adds basedir/gamedir as an override game
2015         // LordHavoc: now supports multiple -game directories
2016         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2017         {
2018                 if (!com_argv[i])
2019                         continue;
2020                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
2021                 {
2022                         i++;
2023                         p = FS_CheckGameDir(com_argv[i]);
2024                         if(!p)
2025                                 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
2026                         if(p == fs_checkgamedir_missing)
2027                                 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
2028                         // add the gamedir to the list of active gamedirs
2029                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2030                         fs_numgamedirs++;
2031                 }
2032         }
2033
2034         // generate the searchpath
2035         FS_Rescan();
2036 }
2037
2038 void FS_Init_Commands(void)
2039 {
2040         Cvar_RegisterVariable (&scr_screenshot_name);
2041         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2042         Cvar_RegisterVariable (&cvar_fs_gamedir);
2043
2044         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2045         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2046         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
2047         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2048         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2049         Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2050 }
2051
2052 /*
2053 ================
2054 FS_Shutdown
2055 ================
2056 */
2057 void FS_Shutdown (void)
2058 {
2059         // close all pack files and such
2060         // (hopefully there aren't any other open files, but they'll be cleaned up
2061         //  by the OS anyway)
2062         FS_ClearSearchPath();
2063         Mem_FreePool (&fs_mempool);
2064
2065 #ifdef WIN32
2066         Sys_UnloadLibrary (&shfolder_dll);
2067         Sys_UnloadLibrary (&shell32_dll);
2068         Sys_UnloadLibrary (&ole32_dll);
2069 #endif
2070 }
2071
2072 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
2073 {
2074         int handle;
2075         int mod, opt;
2076         unsigned int ind;
2077
2078         // Parse the mode string
2079         switch (mode[0])
2080         {
2081                 case 'r':
2082                         mod = O_RDONLY;
2083                         opt = 0;
2084                         break;
2085                 case 'w':
2086                         mod = O_WRONLY;
2087                         opt = O_CREAT | O_TRUNC;
2088                         break;
2089                 case 'a':
2090                         mod = O_WRONLY;
2091                         opt = O_CREAT | O_APPEND;
2092                         break;
2093                 default:
2094                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2095                         return -1;
2096         }
2097         for (ind = 1; mode[ind] != '\0'; ind++)
2098         {
2099                 switch (mode[ind])
2100                 {
2101                         case '+':
2102                                 mod = O_RDWR;
2103                                 break;
2104                         case 'b':
2105                                 opt |= O_BINARY;
2106                                 break;
2107                         default:
2108                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2109                                                         filepath, mode, mode[ind]);
2110                 }
2111         }
2112
2113         if (nonblocking)
2114                 opt |= O_NONBLOCK;
2115
2116 #if _MSC_VER >= 1400
2117         _sopen_s(&handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
2118 #else
2119         handle = open (filepath, mod | opt, 0666);
2120 #endif
2121         return handle;
2122 }
2123
2124 /*
2125 ====================
2126 FS_SysOpen
2127
2128 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2129 ====================
2130 */
2131 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
2132 {
2133         qfile_t* file;
2134
2135         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2136         file->ungetc = EOF;
2137         file->handle = FS_SysOpenFD(filepath, mode, nonblocking);
2138         if (file->handle < 0)
2139         {
2140                 Mem_Free (file);
2141                 return NULL;
2142         }
2143
2144         file->filename = Mem_strdup(fs_mempool, filepath);
2145
2146         file->real_length = lseek (file->handle, 0, SEEK_END);
2147
2148         // For files opened in append mode, we start at the end of the file
2149         if (mode[0] == 'a')
2150                 file->position = file->real_length;
2151         else
2152                 lseek (file->handle, 0, SEEK_SET);
2153
2154         return file;
2155 }
2156
2157
2158 /*
2159 ===========
2160 FS_OpenPackedFile
2161
2162 Open a packed file using its package file descriptor
2163 ===========
2164 */
2165 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2166 {
2167         packfile_t *pfile;
2168         int dup_handle;
2169         qfile_t* file;
2170
2171         pfile = &pack->files[pack_ind];
2172
2173         // If we don't have the true offset, get it now
2174         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2175                 if (!PK3_GetTrueFileOffset (pfile, pack))
2176                         return NULL;
2177
2178 #ifndef LINK_TO_ZLIB
2179         // No Zlib DLL = no compressed files
2180         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2181         {
2182                 Con_Printf("WARNING: can't open the compressed file %s\n"
2183                                         "You need the Zlib DLL to use compressed files\n",
2184                                         pfile->name);
2185                 return NULL;
2186         }
2187 #endif
2188
2189         // LordHavoc: lseek affects all duplicates of a handle so we do it before
2190         // the dup() call to avoid having to close the dup_handle on error here
2191         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
2192         {
2193                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2194                                         pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2195                 return NULL;
2196         }
2197
2198         dup_handle = dup (pack->handle);
2199         if (dup_handle < 0)
2200         {
2201                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2202                 return NULL;
2203         }
2204
2205         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2206         memset (file, 0, sizeof (*file));
2207         file->handle = dup_handle;
2208         file->flags = QFILE_FLAG_PACKED;
2209         file->real_length = pfile->realsize;
2210         file->offset = pfile->offset;
2211         file->position = 0;
2212         file->ungetc = EOF;
2213
2214         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2215         {
2216                 ztoolkit_t *ztk;
2217
2218                 file->flags |= QFILE_FLAG_DEFLATED;
2219
2220                 // We need some more variables
2221                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2222
2223                 ztk->comp_length = pfile->packsize;
2224
2225                 // Initialize zlib stream
2226                 ztk->zstream.next_in = ztk->input;
2227                 ztk->zstream.avail_in = 0;
2228
2229                 /* From Zlib's "unzip.c":
2230                  *
2231                  * windowBits is passed < 0 to tell that there is no zlib header.
2232                  * Note that in this case inflate *requires* an extra "dummy" byte
2233                  * after the compressed stream in order to complete decompression and
2234                  * return Z_STREAM_END.
2235                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2236                  * size of both compressed and uncompressed data
2237                  */
2238                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2239                 {
2240                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2241                         close(dup_handle);
2242                         Mem_Free(file);
2243                         return NULL;
2244                 }
2245
2246                 ztk->zstream.next_out = file->buff;
2247                 ztk->zstream.avail_out = sizeof (file->buff);
2248
2249                 file->ztk = ztk;
2250         }
2251
2252         return file;
2253 }
2254
2255 /*
2256 ====================
2257 FS_CheckNastyPath
2258
2259 Return true if the path should be rejected due to one of the following:
2260 1: path elements that are non-portable
2261 2: path elements that would allow access to files outside the game directory,
2262    or are just not a good idea for a mod to be using.
2263 ====================
2264 */
2265 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2266 {
2267         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2268         if (!path[0])
2269                 return 2;
2270
2271         // Windows: don't allow \ in filenames (windows-only), period.
2272         // (on Windows \ is a directory separator, but / is also supported)
2273         if (strstr(path, "\\"))
2274                 return 1; // non-portable
2275
2276         // Mac: don't allow Mac-only filenames - : is a directory separator
2277         // instead of /, but we rely on / working already, so there's no reason to
2278         // support a Mac-only path
2279         // Amiga and Windows: : tries to go to root of drive
2280         if (strstr(path, ":"))
2281                 return 1; // non-portable attempt to go to root of drive
2282
2283         // Amiga: // is parent directory
2284         if (strstr(path, "//"))
2285                 return 1; // non-portable attempt to go to parent directory
2286
2287         // all: don't allow going to parent directory (../ or /../)
2288         if (strstr(path, ".."))
2289                 return 2; // attempt to go outside the game directory
2290
2291         // Windows and UNIXes: don't allow absolute paths
2292         if (path[0] == '/')
2293                 return 2; // attempt to go outside the game directory
2294
2295         // 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
2296         if (strchr(path, '.'))
2297         {
2298                 if (isgamedir)
2299                 {
2300                         // gamedir is entirely path elements, so simply forbid . entirely
2301                         return 2;
2302                 }
2303                 if (strchr(path, '.') < strrchr(path, '/'))
2304                         return 2; // possible attempt to go outside the game directory
2305         }
2306
2307         // all: forbid trailing slash on gamedir
2308         if (isgamedir && path[strlen(path)-1] == '/')
2309                 return 2;
2310
2311         // all: forbid leading dot on any filename for any reason
2312         if (strstr(path, "/."))
2313                 return 2; // attempt to go outside the game directory
2314
2315         // after all these checks we're pretty sure it's a / separated filename
2316         // and won't do much if any harm
2317         return false;
2318 }
2319
2320
2321 /*
2322 ====================
2323 FS_FindFile
2324
2325 Look for a file in the packages and in the filesystem
2326
2327 Return the searchpath where the file was found (or NULL)
2328 and the file index in the package if relevant
2329 ====================
2330 */
2331 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2332 {
2333         searchpath_t *search;
2334         pack_t *pak;
2335
2336         // search through the path, one element at a time
2337         for (search = fs_searchpaths;search;search = search->next)
2338         {
2339                 // is the element a pak file?
2340                 if (search->pack && !search->pack->vpack)
2341                 {
2342                         int (*strcmp_funct) (const char* str1, const char* str2);
2343                         int left, right, middle;
2344
2345                         pak = search->pack;
2346                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2347
2348                         // Look for the file (binary search)
2349                         left = 0;
2350                         right = pak->numfiles - 1;
2351                         while (left <= right)
2352                         {
2353                                 int diff;
2354
2355                                 middle = (left + right) / 2;
2356                                 diff = strcmp_funct (pak->files[middle].name, name);
2357
2358                                 // Found it
2359                                 if (!diff)
2360                                 {
2361                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2362                                         {
2363                                                 // yes, but the first one is empty so we treat it as not being there
2364                                                 if (!quiet && developer_extra.integer)
2365                                                         Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2366
2367                                                 if (index != NULL)
2368                                                         *index = -1;
2369                                                 return NULL;
2370                                         }
2371
2372                                         if (!quiet && developer_extra.integer)
2373                                                 Con_DPrintf("FS_FindFile: %s in %s\n",
2374                                                                         pak->files[middle].name, pak->filename);
2375
2376                                         if (index != NULL)
2377                                                 *index = middle;
2378                                         return search;
2379                                 }
2380
2381                                 // If we're too far in the list
2382                                 if (diff > 0)
2383                                         right = middle - 1;
2384                                 else
2385                                         left = middle + 1;
2386                         }
2387                 }
2388                 else
2389                 {
2390                         char netpath[MAX_OSPATH];
2391                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2392                         if (FS_SysFileExists (netpath))
2393                         {
2394                                 if (!quiet && developer_extra.integer)
2395                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
2396
2397                                 if (index != NULL)
2398                                         *index = -1;
2399                                 return search;
2400                         }
2401                 }
2402         }
2403
2404         if (!quiet && developer_extra.integer)
2405                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2406
2407         if (index != NULL)
2408                 *index = -1;
2409         return NULL;
2410 }
2411
2412
2413 /*
2414 ===========
2415 FS_OpenReadFile
2416
2417 Look for a file in the search paths and open it in read-only mode
2418 ===========
2419 */
2420 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2421 {
2422         searchpath_t *search;
2423         int pack_ind;
2424
2425         search = FS_FindFile (filename, &pack_ind, quiet);
2426
2427         // Not found?
2428         if (search == NULL)
2429                 return NULL;
2430
2431         // Found in the filesystem?
2432         if (pack_ind < 0)
2433         {
2434                 // this works with vpacks, so we are fine
2435                 char path [MAX_OSPATH];
2436                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2437                 return FS_SysOpen (path, "rb", nonblocking);
2438         }
2439
2440         // So, we found it in a package...
2441
2442         // Is it a PK3 symlink?
2443         // TODO also handle directory symlinks by parsing the whole structure...
2444         // but heck, file symlinks are good enough for now
2445         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2446         {
2447                 if(symlinkLevels <= 0)
2448                 {
2449                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2450                         return NULL;
2451                 }
2452                 else
2453                 {
2454                         char linkbuf[MAX_QPATH];
2455                         fs_offset_t count;
2456                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2457                         const char *mergeslash;
2458                         char *mergestart;
2459
2460                         if(!linkfile)
2461                                 return NULL;
2462                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2463                         FS_Close(linkfile);
2464                         if(count < 0)
2465                                 return NULL;
2466                         linkbuf[count] = 0;
2467                         
2468                         // Now combine the paths...
2469                         mergeslash = strrchr(filename, '/');
2470                         mergestart = linkbuf;
2471                         if(!mergeslash)
2472                                 mergeslash = filename;
2473                         while(!strncmp(mergestart, "../", 3))
2474                         {
2475                                 mergestart += 3;
2476                                 while(mergeslash > filename)
2477                                 {
2478                                         --mergeslash;
2479                                         if(*mergeslash == '/')
2480                                                 break;
2481                                 }
2482                         }
2483                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2484                         if(mergeslash == filename)
2485                         {
2486                                 // Either mergeslash == filename, then we just replace the name (done below)
2487                         }
2488                         else
2489                         {
2490                                 // Or, we append the name after mergeslash;
2491                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2492                                 int spaceNeeded = mergeslash - filename + 1;
2493                                 int spaceRemoved = mergestart - linkbuf;
2494                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2495                                 {
2496                                         Con_DPrintf("symlink: too long path rejected\n");
2497                                         return NULL;
2498                                 }
2499                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2500                                 memcpy(linkbuf, filename, spaceNeeded);
2501                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2502                                 mergestart = linkbuf;
2503                         }
2504                         if (!quiet && developer_loading.integer)
2505                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2506                         if(FS_CheckNastyPath (mergestart, false))
2507                         {
2508                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2509                                 return NULL;
2510                         }
2511                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2512                 }
2513         }
2514
2515         return FS_OpenPackedFile (search->pack, pack_ind);
2516 }
2517
2518
2519 /*
2520 =============================================================================
2521
2522 MAIN PUBLIC FUNCTIONS
2523
2524 =============================================================================
2525 */
2526
2527 /*
2528 ====================
2529 FS_OpenRealFile
2530
2531 Open a file in the userpath. The syntax is the same as fopen
2532 Used for savegame scanning in menu, and all file writing.
2533 ====================
2534 */
2535 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2536 {
2537         char real_path [MAX_OSPATH];
2538
2539         if (FS_CheckNastyPath(filepath, false))
2540         {
2541                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2542                 return NULL;
2543         }
2544
2545         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2546
2547         // If the file is opened in "write", "append", or "read/write" mode,
2548         // create directories up to the file.
2549         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2550                 FS_CreatePath (real_path);
2551         return FS_SysOpen (real_path, mode, false);
2552 }
2553
2554
2555 /*
2556 ====================
2557 FS_OpenVirtualFile
2558
2559 Open a file. The syntax is the same as fopen
2560 ====================
2561 */
2562 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2563 {
2564         if (FS_CheckNastyPath(filepath, false))
2565         {
2566                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2567                 return NULL;
2568         }
2569
2570         return FS_OpenReadFile (filepath, quiet, false, 16);
2571 }
2572
2573
2574 /*
2575 ====================
2576 FS_FileFromData
2577
2578 Open a file. The syntax is the same as fopen
2579 ====================
2580 */
2581 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2582 {
2583         qfile_t* file;
2584         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2585         memset (file, 0, sizeof (*file));
2586         file->flags = QFILE_FLAG_DATA;
2587         file->ungetc = EOF;
2588         file->real_length = size;
2589         file->data = data;
2590         return file;
2591 }
2592
2593 /*
2594 ====================
2595 FS_Close
2596
2597 Close a file
2598 ====================
2599 */
2600 int FS_Close (qfile_t* file)
2601 {
2602         if(file->flags & QFILE_FLAG_DATA)
2603         {
2604                 Mem_Free(file);
2605                 return 0;
2606         }
2607
2608         if (close (file->handle))
2609                 return EOF;
2610
2611         if (file->filename)
2612         {
2613                 if (file->flags & QFILE_FLAG_REMOVE)
2614                         remove(file->filename);
2615
2616                 Mem_Free((void *) file->filename);
2617         }
2618
2619         if (file->ztk)
2620         {
2621                 qz_inflateEnd (&file->ztk->zstream);
2622                 Mem_Free (file->ztk);
2623         }
2624
2625         Mem_Free (file);
2626         return 0;
2627 }
2628
2629 void FS_RemoveOnClose(qfile_t* file)
2630 {
2631         file->flags |= QFILE_FLAG_REMOVE;
2632 }
2633
2634 /*
2635 ====================
2636 FS_Write
2637
2638 Write "datasize" bytes into a file
2639 ====================
2640 */
2641 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2642 {
2643         fs_offset_t result;
2644
2645         // If necessary, seek to the exact file position we're supposed to be
2646         if (file->buff_ind != file->buff_len)
2647                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2648
2649         // Purge cached data
2650         FS_Purge (file);
2651
2652         // Write the buffer and update the position
2653         result = write (file->handle, data, (fs_offset_t)datasize);
2654         file->position = lseek (file->handle, 0, SEEK_CUR);
2655         if (file->real_length < file->position)
2656                 file->real_length = file->position;
2657
2658         if (result < 0)
2659                 return 0;
2660
2661         return result;
2662 }
2663
2664
2665 /*
2666 ====================
2667 FS_Read
2668
2669 Read up to "buffersize" bytes from a file
2670 ====================
2671 */
2672 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2673 {
2674         fs_offset_t count, done;
2675
2676         if (buffersize == 0)
2677                 return 0;
2678
2679         // Get rid of the ungetc character
2680         if (file->ungetc != EOF)
2681         {
2682                 ((char*)buffer)[0] = file->ungetc;
2683                 buffersize--;
2684                 file->ungetc = EOF;
2685                 done = 1;
2686         }
2687         else
2688                 done = 0;
2689
2690         if(file->flags & QFILE_FLAG_DATA)
2691         {
2692                 size_t left = file->real_length - file->position;
2693                 if(buffersize > left)
2694                         buffersize = left;
2695                 memcpy(buffer, file->data + file->position, buffersize);
2696                 file->position += buffersize;
2697                 return buffersize;
2698         }
2699
2700         // First, we copy as many bytes as we can from "buff"
2701         if (file->buff_ind < file->buff_len)
2702         {
2703                 count = file->buff_len - file->buff_ind;
2704                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2705                 done += count;
2706                 memcpy (buffer, &file->buff[file->buff_ind], count);
2707                 file->buff_ind += count;
2708
2709                 buffersize -= count;
2710                 if (buffersize == 0)
2711                         return done;
2712         }
2713
2714         // NOTE: at this point, the read buffer is always empty
2715
2716         // If the file isn't compressed
2717         if (! (file->flags & QFILE_FLAG_DEFLATED))
2718         {
2719                 fs_offset_t nb;
2720
2721                 // We must take care to not read after the end of the file
2722                 count = file->real_length - file->position;
2723
2724                 // If we have a lot of data to get, put them directly into "buffer"
2725                 if (buffersize > sizeof (file->buff) / 2)
2726                 {
2727                         if (count > (fs_offset_t)buffersize)
2728                                 count = (fs_offset_t)buffersize;
2729                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2730                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2731                         if (nb > 0)
2732                         {
2733                                 done += nb;
2734                                 file->position += nb;
2735
2736                                 // Purge cached data
2737                                 FS_Purge (file);
2738                         }
2739                 }
2740                 else
2741                 {
2742                         if (count > (fs_offset_t)sizeof (file->buff))
2743                                 count = (fs_offset_t)sizeof (file->buff);
2744                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2745                         nb = read (file->handle, file->buff, count);
2746                         if (nb > 0)
2747                         {
2748                                 file->buff_len = nb;
2749                                 file->position += nb;
2750
2751                                 // Copy the requested data in "buffer" (as much as we can)
2752                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2753                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2754                                 file->buff_ind = count;
2755                                 done += count;
2756                         }
2757                 }
2758
2759                 return done;
2760         }
2761
2762         // If the file is compressed, it's more complicated...
2763         // We cycle through a few operations until we have read enough data
2764         while (buffersize > 0)
2765         {
2766                 ztoolkit_t *ztk = file->ztk;
2767                 int error;
2768
2769                 // NOTE: at this point, the read buffer is always empty
2770
2771                 // If "input" is also empty, we need to refill it
2772                 if (ztk->in_ind == ztk->in_len)
2773                 {
2774                         // If we are at the end of the file
2775                         if (file->position == file->real_length)
2776                                 return done;
2777
2778                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2779                         if (count > (fs_offset_t)sizeof (ztk->input))
2780                                 count = (fs_offset_t)sizeof (ztk->input);
2781                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2782                         if (read (file->handle, ztk->input, count) != count)
2783                         {
2784                                 Con_Printf ("FS_Read: unexpected end of file\n");
2785                                 break;
2786                         }
2787
2788                         ztk->in_ind = 0;
2789                         ztk->in_len = count;
2790                         ztk->in_position += count;
2791                 }
2792
2793                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2794                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2795
2796                 // Now that we are sure we have compressed data available, we need to determine
2797                 // if it's better to inflate it in "file->buff" or directly in "buffer"
2798
2799                 // Inflate the data in "file->buff"
2800                 if (buffersize < sizeof (file->buff) / 2)
2801                 {
2802                         ztk->zstream.next_out = file->buff;
2803                         ztk->zstream.avail_out = sizeof (file->buff);
2804                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2805                         if (error != Z_OK && error != Z_STREAM_END)
2806                         {
2807                                 Con_Printf ("FS_Read: Can't inflate file\n");
2808                                 break;
2809                         }
2810                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2811
2812                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2813                         file->position += file->buff_len;
2814
2815                         // Copy the requested data in "buffer" (as much as we can)
2816                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2817                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2818                         file->buff_ind = count;
2819                 }
2820
2821                 // Else, we inflate directly in "buffer"
2822                 else
2823                 {
2824                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2825                         ztk->zstream.avail_out = (unsigned int)buffersize;
2826                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2827                         if (error != Z_OK && error != Z_STREAM_END)
2828                         {
2829                                 Con_Printf ("FS_Read: Can't inflate file\n");
2830                                 break;
2831                         }
2832                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2833
2834                         // How much data did it inflate?
2835                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2836                         file->position += count;
2837
2838                         // Purge cached data
2839                         FS_Purge (file);
2840                 }
2841
2842                 done += count;
2843                 buffersize -= count;
2844         }
2845
2846         return done;
2847 }
2848
2849
2850 /*
2851 ====================
2852 FS_Print
2853
2854 Print a string into a file
2855 ====================
2856 */
2857 int FS_Print (qfile_t* file, const char *msg)
2858 {
2859         return (int)FS_Write (file, msg, strlen (msg));
2860 }
2861
2862 /*
2863 ====================
2864 FS_Printf
2865
2866 Print a string into a file
2867 ====================
2868 */
2869 int FS_Printf(qfile_t* file, const char* format, ...)
2870 {
2871         int result;
2872         va_list args;
2873
2874         va_start (args, format);
2875         result = FS_VPrintf (file, format, args);
2876         va_end (args);
2877
2878         return result;
2879 }
2880
2881
2882 /*
2883 ====================
2884 FS_VPrintf
2885
2886 Print a string into a file
2887 ====================
2888 */
2889 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2890 {
2891         int len;
2892         fs_offset_t buff_size = MAX_INPUTLINE;
2893         char *tempbuff;
2894
2895         for (;;)
2896         {
2897                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2898                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2899                 if (len >= 0 && len < buff_size)
2900                         break;
2901                 Mem_Free (tempbuff);
2902                 buff_size *= 2;
2903         }
2904
2905         len = write (file->handle, tempbuff, len);
2906         Mem_Free (tempbuff);
2907
2908         return len;
2909 }
2910
2911
2912 /*
2913 ====================
2914 FS_Getc
2915
2916 Get the next character of a file
2917 ====================
2918 */
2919 int FS_Getc (qfile_t* file)
2920 {
2921         unsigned char c;
2922
2923         if (FS_Read (file, &c, 1) != 1)
2924                 return EOF;
2925
2926         return c;
2927 }
2928
2929
2930 /*
2931 ====================
2932 FS_UnGetc
2933
2934 Put a character back into the read buffer (only supports one character!)
2935 ====================
2936 */
2937 int FS_UnGetc (qfile_t* file, unsigned char c)
2938 {
2939         // If there's already a character waiting to be read
2940         if (file->ungetc != EOF)
2941                 return EOF;
2942
2943         file->ungetc = c;
2944         return c;
2945 }
2946
2947
2948 /*
2949 ====================
2950 FS_Seek
2951
2952 Move the position index in a file
2953 ====================
2954 */
2955 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2956 {
2957         ztoolkit_t *ztk;
2958         unsigned char* buffer;
2959         fs_offset_t buffersize;
2960
2961         // Compute the file offset
2962         switch (whence)
2963         {
2964                 case SEEK_CUR:
2965                         offset += file->position - file->buff_len + file->buff_ind;
2966                         break;
2967
2968                 case SEEK_SET:
2969                         break;
2970
2971                 case SEEK_END:
2972                         offset += file->real_length;
2973                         break;
2974
2975                 default:
2976                         return -1;
2977         }
2978         if (offset < 0 || offset > file->real_length)
2979                 return -1;
2980
2981         if(file->flags & QFILE_FLAG_DATA)
2982         {
2983                 file->position = offset;
2984                 return 0;
2985         }
2986
2987         // If we have the data in our read buffer, we don't need to actually seek
2988         if (file->position - file->buff_len <= offset && offset <= file->position)
2989         {
2990                 file->buff_ind = offset + file->buff_len - file->position;
2991                 return 0;
2992         }
2993
2994         // Purge cached data
2995         FS_Purge (file);
2996
2997         // Unpacked or uncompressed files can seek directly
2998         if (! (file->flags & QFILE_FLAG_DEFLATED))
2999         {
3000                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
3001                         return -1;
3002                 file->position = offset;
3003                 return 0;
3004         }
3005
3006         // Seeking in compressed files is more a hack than anything else,
3007         // but we need to support it, so here we go.
3008         ztk = file->ztk;
3009
3010         // If we have to go back in the file, we need to restart from the beginning
3011         if (offset <= file->position)
3012         {
3013                 ztk->in_ind = 0;
3014                 ztk->in_len = 0;
3015                 ztk->in_position = 0;
3016                 file->position = 0;
3017                 lseek (file->handle, file->offset, SEEK_SET);
3018
3019                 // Reset the Zlib stream
3020                 ztk->zstream.next_in = ztk->input;
3021                 ztk->zstream.avail_in = 0;
3022                 qz_inflateReset (&ztk->zstream);
3023         }
3024
3025         // We need a big buffer to force inflating into it directly
3026         buffersize = 2 * sizeof (file->buff);
3027         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3028
3029         // Skip all data until we reach the requested offset
3030         while (offset > file->position)
3031         {
3032                 fs_offset_t diff = offset - file->position;
3033                 fs_offset_t count, len;
3034
3035                 count = (diff > buffersize) ? buffersize : diff;
3036                 len = FS_Read (file, buffer, count);
3037                 if (len != count)
3038                 {
3039                         Mem_Free (buffer);
3040                         return -1;
3041                 }
3042         }
3043
3044         Mem_Free (buffer);
3045         return 0;
3046 }
3047
3048
3049 /*
3050 ====================
3051 FS_Tell
3052
3053 Give the current position in a file
3054 ====================
3055 */
3056 fs_offset_t FS_Tell (qfile_t* file)
3057 {
3058         return file->position - file->buff_len + file->buff_ind;
3059 }
3060
3061
3062 /*
3063 ====================
3064 FS_FileSize
3065
3066 Give the total size of a file
3067 ====================
3068 */
3069 fs_offset_t FS_FileSize (qfile_t* file)
3070 {
3071         return file->real_length;
3072 }
3073
3074
3075 /*
3076 ====================
3077 FS_Purge
3078
3079 Erases any buffered input or output data
3080 ====================
3081 */
3082 void FS_Purge (qfile_t* file)
3083 {
3084         file->buff_len = 0;
3085         file->buff_ind = 0;
3086         file->ungetc = EOF;
3087 }
3088
3089
3090 /*
3091 ============
3092 FS_LoadFile
3093
3094 Filename are relative to the quake directory.
3095 Always appends a 0 byte.
3096 ============
3097 */
3098 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3099 {
3100         qfile_t *file;
3101         unsigned char *buf = NULL;
3102         fs_offset_t filesize = 0;
3103
3104         file = FS_OpenVirtualFile(path, quiet);
3105         if (file)
3106         {
3107                 filesize = file->real_length;
3108                 if(filesize < 0)
3109                 {
3110                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3111                         FS_Close(file);
3112                         return NULL;
3113                 }
3114
3115                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3116                 buf[filesize] = '\0';
3117                 FS_Read (file, buf, filesize);
3118                 FS_Close (file);
3119                 if (developer_loadfile.integer)
3120                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3121         }
3122
3123         if (filesizepointer)
3124                 *filesizepointer = filesize;
3125         return buf;
3126 }
3127
3128
3129 /*
3130 ============
3131 FS_WriteFile
3132
3133 The filename will be prefixed by the current game directory
3134 ============
3135 */
3136 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3137 {
3138         qfile_t *file;
3139         size_t i;
3140         fs_offset_t lentotal;
3141
3142         file = FS_OpenRealFile(filename, "wb", false);
3143         if (!file)
3144         {
3145                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3146                 return false;
3147         }
3148
3149         lentotal = 0;
3150         for(i = 0; i < count; ++i)
3151                 lentotal += len[i];
3152         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3153         for(i = 0; i < count; ++i)
3154                 FS_Write (file, data[i], len[i]);
3155         FS_Close (file);
3156         return true;
3157 }
3158
3159 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3160 {
3161         return FS_WriteFileInBlocks(filename, &data, &len, 1);
3162 }
3163
3164
3165 /*
3166 =============================================================================
3167
3168 OTHERS PUBLIC FUNCTIONS
3169
3170 =============================================================================
3171 */
3172
3173 /*
3174 ============
3175 FS_StripExtension
3176 ============
3177 */
3178 void FS_StripExtension (const char *in, char *out, size_t size_out)
3179 {
3180         char *last = NULL;
3181         char currentchar;
3182
3183         if (size_out == 0)
3184                 return;
3185
3186         while ((currentchar = *in) && size_out > 1)
3187         {
3188                 if (currentchar == '.')
3189                         last = out;
3190                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3191                         last = NULL;
3192                 *out++ = currentchar;
3193                 in++;
3194                 size_out--;
3195         }
3196         if (last)
3197                 *last = 0;
3198         else
3199                 *out = 0;
3200 }
3201
3202
3203 /*
3204 ==================
3205 FS_DefaultExtension
3206 ==================
3207 */
3208 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3209 {
3210         const char *src;
3211
3212         // if path doesn't have a .EXT, append extension
3213         // (extension should include the .)
3214         src = path + strlen(path) - 1;
3215
3216         while (*src != '/' && src != path)
3217         {
3218                 if (*src == '.')
3219                         return;                 // it has an extension
3220                 src--;
3221         }
3222
3223         strlcat (path, extension, size_path);
3224 }
3225
3226
3227 /*
3228 ==================
3229 FS_FileType
3230
3231 Look for a file in the packages and in the filesystem
3232 ==================
3233 */
3234 int FS_FileType (const char *filename)
3235 {
3236         searchpath_t *search;
3237         char fullpath[MAX_OSPATH];
3238
3239         search = FS_FindFile (filename, NULL, true);
3240         if(!search)
3241                 return FS_FILETYPE_NONE;
3242
3243         if(search->pack && !search->pack->vpack)
3244                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3245
3246         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3247         return FS_SysFileType(fullpath);
3248 }
3249
3250
3251 /*
3252 ==================
3253 FS_FileExists
3254
3255 Look for a file in the packages and in the filesystem
3256 ==================
3257 */
3258 qboolean FS_FileExists (const char *filename)
3259 {
3260         return (FS_FindFile (filename, NULL, true) != NULL);
3261 }
3262
3263
3264 /*
3265 ==================
3266 FS_SysFileExists
3267
3268 Look for a file in the filesystem only
3269 ==================
3270 */
3271 int FS_SysFileType (const char *path)
3272 {
3273 #if WIN32
3274 // Sajt - some older sdks are missing this define
3275 # ifndef INVALID_FILE_ATTRIBUTES
3276 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3277 # endif
3278
3279         DWORD result = GetFileAttributes(path);
3280
3281         if(result == INVALID_FILE_ATTRIBUTES)
3282                 return FS_FILETYPE_NONE;
3283
3284         if(result & FILE_ATTRIBUTE_DIRECTORY)
3285                 return FS_FILETYPE_DIRECTORY;
3286
3287         return FS_FILETYPE_FILE;
3288 #else
3289         struct stat buf;
3290
3291         if (stat (path,&buf) == -1)
3292                 return FS_FILETYPE_NONE;
3293
3294 #ifndef S_ISDIR
3295 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3296 #endif
3297         if(S_ISDIR(buf.st_mode))
3298                 return FS_FILETYPE_DIRECTORY;
3299
3300         return FS_FILETYPE_FILE;
3301 #endif
3302 }
3303
3304 qboolean FS_SysFileExists (const char *path)
3305 {
3306         return FS_SysFileType (path) != FS_FILETYPE_NONE;
3307 }
3308
3309 void FS_mkdir (const char *path)
3310 {
3311 #if WIN32
3312         _mkdir (path);
3313 #else
3314         mkdir (path, 0777);
3315 #endif
3316 }
3317
3318 /*
3319 ===========
3320 FS_Search
3321
3322 Allocate and fill a search structure with information on matching filenames.
3323 ===========
3324 */
3325 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3326 {
3327         fssearch_t *search;
3328         searchpath_t *searchpath;
3329         pack_t *pak;
3330         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3331         stringlist_t resultlist;
3332         stringlist_t dirlist;
3333         const char *slash, *backslash, *colon, *separator;
3334         char *basepath;
3335         char temp[MAX_OSPATH];
3336
3337         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3338                 ;
3339
3340         if (i > 0)
3341         {
3342                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3343                 return NULL;
3344         }
3345
3346         stringlistinit(&resultlist);
3347         stringlistinit(&dirlist);
3348         search = NULL;
3349         slash = strrchr(pattern, '/');
3350         backslash = strrchr(pattern, '\\');
3351         colon = strrchr(pattern, ':');
3352         separator = max(slash, backslash);
3353         separator = max(separator, colon);
3354         basepathlength = separator ? (separator + 1 - pattern) : 0;
3355         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3356         if (basepathlength)
3357                 memcpy(basepath, pattern, basepathlength);
3358         basepath[basepathlength] = 0;
3359
3360         // search through the path, one element at a time
3361         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3362         {
3363                 // is the element a pak file?
3364                 if (searchpath->pack && !searchpath->pack->vpack)
3365                 {
3366                         // look through all the pak file elements
3367                         pak = searchpath->pack;
3368                         for (i = 0;i < pak->numfiles;i++)
3369                         {
3370                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
3371                                 while (temp[0])
3372                                 {
3373                                         if (matchpattern(temp, (char *)pattern, true))
3374                                         {
3375                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3376                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
3377                                                                 break;
3378                                                 if (resultlistindex == resultlist.numstrings)
3379                                                 {
3380                                                         stringlistappend(&resultlist, temp);
3381                                                         if (!quiet && developer_loading.integer)
3382                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3383                                                 }
3384                                         }
3385                                         // strip off one path element at a time until empty
3386                                         // this way directories are added to the listing if they match the pattern
3387                                         slash = strrchr(temp, '/');
3388                                         backslash = strrchr(temp, '\\');
3389                                         colon = strrchr(temp, ':');
3390                                         separator = temp;
3391                                         if (separator < slash)
3392                                                 separator = slash;
3393                                         if (separator < backslash)
3394                                                 separator = backslash;
3395                                         if (separator < colon)
3396                                                 separator = colon;
3397                                         *((char *)separator) = 0;
3398                                 }
3399                         }
3400                 }
3401                 else
3402                 {
3403                         stringlist_t matchedSet, foundSet;
3404                         const char *start = pattern;
3405
3406                         stringlistinit(&matchedSet);
3407                         stringlistinit(&foundSet);
3408                         // add a first entry to the set
3409                         stringlistappend(&matchedSet, "");
3410                         // iterate through pattern's path
3411                         while (*start)
3412                         {
3413                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3414                                 char subpath[MAX_OSPATH];
3415                                 char subpattern[MAX_OSPATH];
3416
3417                                 // find the next wildcard
3418                                 wildcard = strchr(start, '?');
3419                                 asterisk = strchr(start, '*');
3420                                 if (asterisk && (!wildcard || asterisk < wildcard))
3421                                 {
3422                                         wildcard = asterisk;
3423                                 }
3424
3425                                 if (wildcard)
3426                                 {
3427                                         nextseparator = strchr( wildcard, '/' );
3428                                 }
3429                                 else
3430                                 {
3431                                         nextseparator = NULL;
3432                                 }
3433
3434                                 if( !nextseparator ) {
3435                                         nextseparator = start + strlen( start );
3436                                 }
3437
3438                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3439                                 // copy everything up except nextseperator
3440                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3441                                 // find the last '/' before the wildcard
3442                                 prevseparator = strrchr( subpattern, '/' );
3443                                 if (!prevseparator)
3444                                         prevseparator = subpattern;
3445                                 else
3446                                         prevseparator++;
3447                                 // copy everything from start to the previous including the '/' (before the wildcard)
3448                                 // everything up to start is already included in the path of matchedSet's entries
3449                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3450
3451                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3452                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3453                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3454                                         strlcat( temp, subpath, sizeof(temp) );
3455                                         listdirectory( &foundSet, searchpath->filename, temp );
3456                                 }
3457                                 if( dirlistindex == 0 ) {
3458                                         break;
3459                                 }
3460                                 // reset the current result set
3461                                 stringlistfreecontents( &matchedSet );
3462                                 // match against the pattern
3463                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3464                                         const char *direntry = foundSet.strings[ dirlistindex ];
3465                                         if (matchpattern(direntry, subpattern, true)) {
3466                                                 stringlistappend( &matchedSet, direntry );
3467                                         }
3468                                 }
3469                                 stringlistfreecontents( &foundSet );
3470
3471                                 start = nextseparator;
3472                         }
3473
3474                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3475                         {
3476                                 const char *temp = matchedSet.strings[dirlistindex];
3477                                 if (matchpattern(temp, (char *)pattern, true))
3478                                 {
3479                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3480                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
3481                                                         break;
3482                                         if (resultlistindex == resultlist.numstrings)
3483                                         {
3484                                                 stringlistappend(&resultlist, temp);
3485                                                 if (!quiet && developer_loading.integer)
3486                                                         Con_Printf("SearchDirFile: %s\n", temp);
3487                                         }
3488                                 }
3489                         }
3490                         stringlistfreecontents( &matchedSet );
3491                 }
3492         }
3493
3494         if (resultlist.numstrings)