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