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