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