]> git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
Fix pak loading bugs.
[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 = 0;
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                         remove(file->filename);
2719
2720                 Mem_Free((void *) file->filename);
2721         }
2722
2723         if (file->ztk)
2724         {
2725                 qz_inflateEnd (&file->ztk->zstream);
2726                 Mem_Free (file->ztk);
2727         }
2728
2729         Mem_Free (file);
2730         return 0;
2731 }
2732
2733 void FS_RemoveOnClose(qfile_t* file)
2734 {
2735         file->flags |= QFILE_FLAG_REMOVE;
2736 }
2737
2738 /*
2739 ====================
2740 FS_Write
2741
2742 Write "datasize" bytes into a file
2743 ====================
2744 */
2745 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2746 {
2747         fs_offset_t written = 0;
2748
2749         // If necessary, seek to the exact file position we're supposed to be
2750         if (file->buff_ind != file->buff_len)
2751         {
2752                 if (lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
2753                 {
2754                         Con_Printf("WARNING: could not seek in %s.\n");
2755                 }
2756         }
2757
2758         // Purge cached data
2759         FS_Purge (file);
2760
2761         // Write the buffer and update the position
2762         // 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)
2763         while (written < (fs_offset_t)datasize)
2764         {
2765                 // figure out how much to write in one chunk
2766                 fs_offset_t maxchunk = 1<<30; // 1 GiB
2767                 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
2768                 int result = (int)write (file->handle, (const unsigned char *)data + written, chunk);
2769                 // if at least some was written, add it to our accumulator
2770                 if (result > 0)
2771                         written += result;
2772                 // if the result is not what we expected, consider the write to be incomplete
2773                 if (result != chunk)
2774                         break;
2775         }
2776         file->position = lseek (file->handle, 0, SEEK_CUR);
2777         if (file->real_length < file->position)
2778                 file->real_length = file->position;
2779
2780         // note that this will never be less than 0 even if the write failed
2781         return written;
2782 }
2783
2784
2785 /*
2786 ====================
2787 FS_Read
2788
2789 Read up to "buffersize" bytes from a file
2790 ====================
2791 */
2792 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2793 {
2794         fs_offset_t count, done;
2795
2796         if (buffersize == 0)
2797                 return 0;
2798
2799         // Get rid of the ungetc character
2800         if (file->ungetc != EOF)
2801         {
2802                 ((char*)buffer)[0] = file->ungetc;
2803                 buffersize--;
2804                 file->ungetc = EOF;
2805                 done = 1;
2806         }
2807         else
2808                 done = 0;
2809
2810         if(file->flags & QFILE_FLAG_DATA)
2811         {
2812                 size_t left = file->real_length - file->position;
2813                 if(buffersize > left)
2814                         buffersize = left;
2815                 memcpy(buffer, file->data + file->position, buffersize);
2816                 file->position += buffersize;
2817                 return buffersize;
2818         }
2819
2820         // First, we copy as many bytes as we can from "buff"
2821         if (file->buff_ind < file->buff_len)
2822         {
2823                 count = file->buff_len - file->buff_ind;
2824                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2825                 done += count;
2826                 memcpy (buffer, &file->buff[file->buff_ind], count);
2827                 file->buff_ind += count;
2828
2829                 buffersize -= count;
2830                 if (buffersize == 0)
2831                         return done;
2832         }
2833
2834         // NOTE: at this point, the read buffer is always empty
2835
2836         // If the file isn't compressed
2837         if (! (file->flags & QFILE_FLAG_DEFLATED))
2838         {
2839                 fs_offset_t nb;
2840
2841                 // We must take care to not read after the end of the file
2842                 count = file->real_length - file->position;
2843
2844                 // If we have a lot of data to get, put them directly into "buffer"
2845                 if (buffersize > sizeof (file->buff) / 2)
2846                 {
2847                         if (count > (fs_offset_t)buffersize)
2848                                 count = (fs_offset_t)buffersize;
2849                         if (lseek (file->handle, file->offset + file->position, SEEK_SET) == -1)
2850                         {
2851                                 // Seek failed. When reading from a pipe, and
2852                                 // the caller never called FS_Seek, this still
2853                                 // works fine.  So no reporting this error.
2854                         }
2855                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2856                         if (nb > 0)
2857                         {
2858                                 done += nb;
2859                                 file->position += nb;
2860
2861                                 // Purge cached data
2862                                 FS_Purge (file);
2863                         }
2864                 }
2865                 else
2866                 {
2867                         if (count > (fs_offset_t)sizeof (file->buff))
2868                                 count = (fs_offset_t)sizeof (file->buff);
2869                         if (lseek (file->handle, file->offset + file->position, SEEK_SET) == -1)
2870                         {
2871                                 // Seek failed. When reading from a pipe, and
2872                                 // the caller never called FS_Seek, this still
2873                                 // works fine.  So no reporting this error.
2874                         }
2875                         nb = read (file->handle, file->buff, count);
2876                         if (nb > 0)
2877                         {
2878                                 file->buff_len = nb;
2879                                 file->position += nb;
2880
2881                                 // Copy the requested data in "buffer" (as much as we can)
2882                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2883                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2884                                 file->buff_ind = count;
2885                                 done += count;
2886                         }
2887                 }
2888
2889                 return done;
2890         }
2891
2892         // If the file is compressed, it's more complicated...
2893         // We cycle through a few operations until we have read enough data
2894         while (buffersize > 0)
2895         {
2896                 ztoolkit_t *ztk = file->ztk;
2897                 int error;
2898
2899                 // NOTE: at this point, the read buffer is always empty
2900
2901                 // If "input" is also empty, we need to refill it
2902                 if (ztk->in_ind == ztk->in_len)
2903                 {
2904                         // If we are at the end of the file
2905                         if (file->position == file->real_length)
2906                                 return done;
2907
2908                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2909                         if (count > (fs_offset_t)sizeof (ztk->input))
2910                                 count = (fs_offset_t)sizeof (ztk->input);
2911                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2912                         if (read (file->handle, ztk->input, count) != count)
2913                         {
2914                                 Con_Printf ("FS_Read: unexpected end of file\n");
2915                                 break;
2916                         }
2917
2918                         ztk->in_ind = 0;
2919                         ztk->in_len = count;
2920                         ztk->in_position += count;
2921                 }
2922
2923                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2924                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2925
2926                 // Now that we are sure we have compressed data available, we need to determine
2927                 // if it's better to inflate it in "file->buff" or directly in "buffer"
2928
2929                 // Inflate the data in "file->buff"
2930                 if (buffersize < sizeof (file->buff) / 2)
2931                 {
2932                         ztk->zstream.next_out = file->buff;
2933                         ztk->zstream.avail_out = sizeof (file->buff);
2934                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2935                         if (error != Z_OK && error != Z_STREAM_END)
2936                         {
2937                                 Con_Printf ("FS_Read: Can't inflate file\n");
2938                                 break;
2939                         }
2940                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2941
2942                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2943                         file->position += file->buff_len;
2944
2945                         // Copy the requested data in "buffer" (as much as we can)
2946                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2947                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2948                         file->buff_ind = count;
2949                 }
2950
2951                 // Else, we inflate directly in "buffer"
2952                 else
2953                 {
2954                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2955                         ztk->zstream.avail_out = (unsigned int)buffersize;
2956                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2957                         if (error != Z_OK && error != Z_STREAM_END)
2958                         {
2959                                 Con_Printf ("FS_Read: Can't inflate file\n");
2960                                 break;
2961                         }
2962                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2963
2964                         // How much data did it inflate?
2965                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2966                         file->position += count;
2967
2968                         // Purge cached data
2969                         FS_Purge (file);
2970                 }
2971
2972                 done += count;
2973                 buffersize -= count;
2974         }
2975
2976         return done;
2977 }
2978
2979
2980 /*
2981 ====================
2982 FS_Print
2983
2984 Print a string into a file
2985 ====================
2986 */
2987 int FS_Print (qfile_t* file, const char *msg)
2988 {
2989         return (int)FS_Write (file, msg, strlen (msg));
2990 }
2991
2992 /*
2993 ====================
2994 FS_Printf
2995
2996 Print a string into a file
2997 ====================
2998 */
2999 int FS_Printf(qfile_t* file, const char* format, ...)
3000 {
3001         int result;
3002         va_list args;
3003
3004         va_start (args, format);
3005         result = FS_VPrintf (file, format, args);
3006         va_end (args);
3007
3008         return result;
3009 }
3010
3011
3012 /*
3013 ====================
3014 FS_VPrintf
3015
3016 Print a string into a file
3017 ====================
3018 */
3019 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3020 {
3021         int len;
3022         fs_offset_t buff_size = MAX_INPUTLINE;
3023         char *tempbuff;
3024
3025         for (;;)
3026         {
3027                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3028                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3029                 if (len >= 0 && len < buff_size)
3030                         break;
3031                 Mem_Free (tempbuff);
3032                 buff_size *= 2;
3033         }
3034
3035         len = write (file->handle, tempbuff, len);
3036         Mem_Free (tempbuff);
3037
3038         return len;
3039 }
3040
3041
3042 /*
3043 ====================
3044 FS_Getc
3045
3046 Get the next character of a file
3047 ====================
3048 */
3049 int FS_Getc (qfile_t* file)
3050 {
3051         unsigned char c;
3052
3053         if (FS_Read (file, &c, 1) != 1)
3054                 return EOF;
3055
3056         return c;
3057 }
3058
3059
3060 /*
3061 ====================
3062 FS_UnGetc
3063
3064 Put a character back into the read buffer (only supports one character!)
3065 ====================
3066 */
3067 int FS_UnGetc (qfile_t* file, unsigned char c)
3068 {
3069         // If there's already a character waiting to be read
3070         if (file->ungetc != EOF)
3071                 return EOF;
3072
3073         file->ungetc = c;
3074         return c;
3075 }
3076
3077
3078 /*
3079 ====================
3080 FS_Seek
3081
3082 Move the position index in a file
3083 ====================
3084 */
3085 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3086 {
3087         ztoolkit_t *ztk;
3088         unsigned char* buffer;
3089         fs_offset_t buffersize;
3090
3091         // Compute the file offset
3092         switch (whence)
3093         {
3094                 case SEEK_CUR:
3095                         offset += file->position - file->buff_len + file->buff_ind;
3096                         break;
3097
3098                 case SEEK_SET:
3099                         break;
3100
3101                 case SEEK_END:
3102                         offset += file->real_length;
3103                         break;
3104
3105                 default:
3106                         return -1;
3107         }
3108         if (offset < 0 || offset > file->real_length)
3109                 return -1;
3110
3111         if(file->flags & QFILE_FLAG_DATA)
3112         {
3113                 file->position = offset;
3114                 return 0;
3115         }
3116
3117         // If we have the data in our read buffer, we don't need to actually seek
3118         if (file->position - file->buff_len <= offset && offset <= file->position)
3119         {
3120                 file->buff_ind = offset + file->buff_len - file->position;
3121                 return 0;
3122         }
3123
3124         // Purge cached data
3125         FS_Purge (file);
3126
3127         // Unpacked or uncompressed files can seek directly
3128         if (! (file->flags & QFILE_FLAG_DEFLATED))
3129         {
3130                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
3131                         return -1;
3132                 file->position = offset;
3133                 return 0;
3134         }
3135
3136         // Seeking in compressed files is more a hack than anything else,
3137         // but we need to support it, so here we go.
3138         ztk = file->ztk;
3139
3140         // If we have to go back in the file, we need to restart from the beginning
3141         if (offset <= file->position)
3142         {
3143                 ztk->in_ind = 0;
3144                 ztk->in_len = 0;
3145                 ztk->in_position = 0;
3146                 file->position = 0;
3147                 if (lseek (file->handle, file->offset, SEEK_SET) == -1)
3148                         Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3149
3150                 // Reset the Zlib stream
3151                 ztk->zstream.next_in = ztk->input;
3152                 ztk->zstream.avail_in = 0;
3153                 qz_inflateReset (&ztk->zstream);
3154         }
3155
3156         // We need a big buffer to force inflating into it directly
3157         buffersize = 2 * sizeof (file->buff);
3158         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3159
3160         // Skip all data until we reach the requested offset
3161         while (offset > file->position)
3162         {
3163                 fs_offset_t diff = offset - file->position;
3164                 fs_offset_t count, len;
3165
3166                 count = (diff > buffersize) ? buffersize : diff;
3167                 len = FS_Read (file, buffer, count);
3168                 if (len != count)
3169                 {
3170                         Mem_Free (buffer);
3171                         return -1;
3172                 }
3173         }
3174
3175         Mem_Free (buffer);
3176         return 0;
3177 }
3178
3179
3180 /*
3181 ====================
3182 FS_Tell
3183
3184 Give the current position in a file
3185 ====================
3186 */
3187 fs_offset_t FS_Tell (qfile_t* file)
3188 {
3189         return file->position - file->buff_len + file->buff_ind;
3190 }
3191
3192
3193 /*
3194 ====================
3195 FS_FileSize
3196
3197 Give the total size of a file
3198 ====================
3199 */
3200 fs_offset_t FS_FileSize (qfile_t* file)
3201 {
3202         return file->real_length;
3203 }
3204
3205
3206 /*
3207 ====================
3208 FS_Purge
3209
3210 Erases any buffered input or output data
3211 ====================
3212 */
3213 void FS_Purge (qfile_t* file)
3214 {
3215         file->buff_len = 0;
3216         file->buff_ind = 0;
3217         file->ungetc = EOF;
3218 }
3219
3220
3221 /*
3222 ============
3223 FS_LoadFile
3224
3225 Filename are relative to the quake directory.
3226 Always appends a 0 byte.
3227 ============
3228 */
3229 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3230 {
3231         qfile_t *file;
3232         unsigned char *buf = NULL;
3233         fs_offset_t filesize = 0;
3234
3235         file = FS_OpenVirtualFile(path, quiet);
3236         if (file)
3237         {
3238                 filesize = file->real_length;
3239                 if(filesize < 0)
3240                 {
3241                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3242                         FS_Close(file);
3243                         return NULL;
3244                 }
3245
3246                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3247                 buf[filesize] = '\0';
3248                 FS_Read (file, buf, filesize);
3249                 FS_Close (file);
3250                 if (developer_loadfile.integer)
3251                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3252         }
3253
3254         if (filesizepointer)
3255                 *filesizepointer = filesize;
3256         return buf;
3257 }
3258
3259
3260 /*
3261 ============
3262 FS_WriteFile
3263
3264 The filename will be prefixed by the current game directory
3265 ============
3266 */
3267 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3268 {
3269         qfile_t *file;
3270         size_t i;
3271         fs_offset_t lentotal;
3272
3273         file = FS_OpenRealFile(filename, "wb", false);
3274         if (!file)
3275         {
3276                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3277                 return false;
3278         }
3279
3280         lentotal = 0;
3281         for(i = 0; i < count; ++i)
3282                 lentotal += len[i];
3283         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3284         for(i = 0; i < count; ++i)
3285                 FS_Write (file, data[i], len[i]);
3286         FS_Close (file);
3287         return true;
3288 }
3289
3290 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3291 {
3292         return FS_WriteFileInBlocks(filename, &data, &len, 1);
3293 }
3294
3295
3296 /*
3297 =============================================================================
3298
3299 OTHERS PUBLIC FUNCTIONS
3300
3301 =============================================================================
3302 */
3303
3304 /*
3305 ============
3306 FS_StripExtension
3307 ============
3308 */
3309 void FS_StripExtension (const char *in, char *out, size_t size_out)
3310 {
3311         char *last = NULL;
3312         char currentchar;
3313
3314         if (size_out == 0)
3315                 return;
3316
3317         while ((currentchar = *in) && size_out > 1)
3318         {
3319                 if (currentchar == '.')
3320                         last = out;
3321                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3322                         last = NULL;
3323                 *out++ = currentchar;
3324                 in++;
3325                 size_out--;
3326         }
3327         if (last)
3328                 *last = 0;
3329         else
3330                 *out = 0;
3331 }
3332
3333
3334 /*
3335 ==================
3336 FS_DefaultExtension
3337 ==================
3338 */
3339 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3340 {
3341         const char *src;
3342
3343         // if path doesn't have a .EXT, append extension
3344         // (extension should include the .)
3345         src = path + strlen(path) - 1;
3346
3347         while (*src != '/' && src != path)
3348         {
3349                 if (*src == '.')
3350                         return;                 // it has an extension
3351                 src--;
3352         }
3353
3354         strlcat (path, extension, size_path);
3355 }
3356
3357
3358 /*
3359 ==================
3360 FS_FileType
3361
3362 Look for a file in the packages and in the filesystem
3363 ==================
3364 */
3365 int FS_FileType (const char *filename)
3366 {
3367         searchpath_t *search;
3368         char fullpath[MAX_OSPATH];
3369
3370         search = FS_FindFile (filename, NULL, true);
3371         if(!search)
3372                 return FS_FILETYPE_NONE;
3373
3374         if(search->pack && !search->pack->vpack)
3375                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3376
3377         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3378         return FS_SysFileType(fullpath);
3379 }
3380
3381
3382 /*
3383 ==================
3384 FS_FileExists
3385
3386 Look for a file in the packages and in the filesystem
3387 ==================
3388 */
3389 qboolean FS_FileExists (const char *filename)
3390 {
3391         return (FS_FindFile (filename, NULL, true) != NULL);
3392 }
3393
3394
3395 /*
3396 ==================
3397 FS_SysFileExists
3398
3399 Look for a file in the filesystem only
3400 ==================
3401 */
3402 int FS_SysFileType (const char *path)
3403 {
3404 #if WIN32
3405 // Sajt - some older sdks are missing this define
3406 # ifndef INVALID_FILE_ATTRIBUTES
3407 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3408 # endif
3409
3410         DWORD result = GetFileAttributes(path);
3411
3412         if(result == INVALID_FILE_ATTRIBUTES)
3413                 return FS_FILETYPE_NONE;
3414
3415         if(result & FILE_ATTRIBUTE_DIRECTORY)
3416                 return FS_FILETYPE_DIRECTORY;
3417
3418         return FS_FILETYPE_FILE;
3419 #else
3420         struct stat buf;
3421
3422         if (stat (path,&buf) == -1)
3423                 return FS_FILETYPE_NONE;
3424
3425 #ifndef S_ISDIR
3426 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3427 #endif
3428         if(S_ISDIR(buf.st_mode))
3429                 return FS_FILETYPE_DIRECTORY;
3430
3431         return FS_FILETYPE_FILE;
3432 #endif
3433 }
3434
3435 qboolean FS_SysFileExists (const char *path)
3436 {
3437         return FS_SysFileType (path) != FS_FILETYPE_NONE;
3438 }
3439
3440 /*
3441 ===========
3442 FS_Search
3443
3444 Allocate and fill a search structure with information on matching filenames.
3445 ===========
3446 */
3447 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3448 {
3449         fssearch_t *search;
3450         searchpath_t *searchpath;
3451         pack_t *pak;
3452         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3453         stringlist_t resultlist;
3454         stringlist_t dirlist;
3455         const char *slash, *backslash, *colon, *separator;
3456         char *basepath;
3457         char temp[MAX_OSPATH];
3458
3459         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3460                 ;
3461
3462         if (i > 0)
3463         {
3464                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3465                 return NULL;
3466         }
3467
3468         stringlistinit(&resultlist);
3469         stringlistinit(&dirlist);
3470         search = NULL;
3471         slash = strrchr(pattern, '/');
3472         backslash = strrchr(pattern, '\\');
3473         colon = strrchr(pattern, ':');
3474         separator = max(slash, backslash);
3475         separator = max(separator, colon);
3476         basepathlength = separator ? (separator + 1 - pattern) : 0;
3477         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3478         if (basepathlength)
3479                 memcpy(basepath, pattern, basepathlength);
3480         basepath[basepathlength] = 0;
3481
3482         // search through the path, one element at a time
3483         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3484         {
3485                 // is the element a pak file?
3486                 if (searchpath->pack && !searchpath->pack->vpack)
3487                 {
3488                         // look through all the pak file elements
3489                         pak = searchpath->pack;
3490                         for (i = 0;i < pak->numfiles;i++)
3491                         {
3492                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
3493                                 while (temp[0])
3494                                 {
3495                                         if (matchpattern(temp, (char *)pattern, true))
3496                                         {
3497                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3498                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
3499                                                                 break;
3500                                                 if (resultlistindex == resultlist.numstrings)
3501                                                 {
3502                                                         stringlistappend(&resultlist, temp);
3503                                                         if (!quiet && developer_loading.integer)
3504                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3505                                                 }
3506                                         }
3507                                         // strip off one path element at a time until empty
3508                                         // this way directories are added to the listing if they match the pattern
3509                                         slash = strrchr(temp, '/');
3510                                         backslash = strrchr(temp, '\\');
3511                                         colon = strrchr(temp, ':');
3512                                         separator = temp;
3513                                         if (separator < slash)
3514                                                 separator = slash;
3515                                         if (separator < backslash)
3516                                                 separator = backslash;
3517                                         if (separator < colon)
3518                                                 separator = colon;
3519                                         *((char *)separator) = 0;
3520                                 }
3521                         }
3522                 }
3523                 else
3524                 {
3525                         stringlist_t matchedSet, foundSet;
3526                         const char *start = pattern;
3527
3528                         stringlistinit(&matchedSet);
3529                         stringlistinit(&foundSet);
3530                         // add a first entry to the set
3531                         stringlistappend(&matchedSet, "");
3532                         // iterate through pattern's path
3533                         while (*start)
3534                         {
3535                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3536                                 char subpath[MAX_OSPATH];
3537                                 char subpattern[MAX_OSPATH];
3538
3539                                 // find the next wildcard
3540                                 wildcard = strchr(start, '?');
3541                                 asterisk = strchr(start, '*');
3542                                 if (asterisk && (!wildcard || asterisk < wildcard))
3543                                 {
3544                                         wildcard = asterisk;
3545                                 }
3546
3547                                 if (wildcard)
3548                                 {
3549                                         nextseparator = strchr( wildcard, '/' );
3550                                 }
3551                                 else
3552                                 {
3553                                         nextseparator = NULL;
3554                                 }
3555
3556                                 if( !nextseparator ) {
3557                                         nextseparator = start + strlen( start );
3558                                 }
3559
3560                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3561                                 // copy everything up except nextseperator
3562                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3563                                 // find the last '/' before the wildcard
3564                                 prevseparator = strrchr( subpattern, '/' );
3565                                 if (!prevseparator)
3566                                         prevseparator = subpattern;
3567                                 else
3568                                         prevseparator++;
3569                                 // copy everything from start to the previous including the '/' (before the wildcard)
3570                                 // everything up to start is already included in the path of matchedSet's entries
3571                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3572
3573                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3574                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3575                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3576                                         strlcat( temp, subpath, sizeof(temp) );
3577                                         listdirectory( &foundSet, searchpath->filename, temp );
3578                                 }
3579                                 if( dirlistindex == 0 ) {
3580                                         break;
3581                                 }
3582                                 // reset the current result set
3583                                 stringlistfreecontents( &matchedSet );
3584                                 // match against the pattern
3585                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3586                                         const char *direntry = foundSet.strings[ dirlistindex ];
3587                                         if (matchpattern(direntry, subpattern, true)) {
3588                                                 stringlistappend( &matchedSet, direntry );
3589                                         }
3590                                 }
3591                                 stringlistfreecontents( &foundSet );
3592
3593                                 start = nextseparator;
3594                         }
3595
3596                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3597                         {
3598                                 const char *temp = matchedSet.strings[dirlistindex];
3599                                 if (matchpattern(temp, (char *)pattern, true))
3600                                 {
3601                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3602                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
3603                                                         break;
3604                                         if (resultlistindex == resultlist.numstrings)
3605                                         {
3606                                                 stringlistappend(&resultlist, temp);
3607                                                 if (!quiet && developer_loading.integer)
3608                                                         Con_Printf("SearchDirFile: %s\n", temp);
3609                                         }
3610                                 }
3611                         }
3612                         stringlistfreecontents( &matchedSet );
3613                 }
3614         }
3615
3616         if (resultlist.numstrings)
3617         {
3618                 stringlistsort(&resultlist, true);
3619                 numfiles = resultlist.numstrings;
3620                 numchars = 0;
3621                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3622                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3623                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3624                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3625                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3626                 search->numfilenames = (int)numfiles;
3627                 numfiles = 0;
3628                 numchars = 0;
3629                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3630                 {
3631                         size_t textlen;
3632                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
3633                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3634                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3635                         numfiles++;
3636                         numchars += (int)textlen;
3637                 }
3638         }
3639         stringlistfreecontents(&resultlist);
3640
3641         Mem_Free(basepath);
3642         return search;
3643 }
3644
3645 void FS_FreeSearch(fssearch_t *search)
3646 {
3647         Z_Free(search);
3648 }
3649
3650 extern int con_linewidth;
3651 static int FS_ListDirectory(const char *pattern, int oneperline)
3652 {
3653         int numfiles;
3654         int numcolumns;
3655         int numlines;
3656         int columnwidth;
3657         int linebufpos;
3658         int i, j, k, l;
3659         const char *name;
3660         char linebuf[MAX_INPUTLINE];
3661         fssearch_t *search;
3662         search = FS_Search(pattern, true, true);
3663         if (!search)
3664                 return 0;
3665         numfiles = search->numfilenames;
3666         if (!oneperline)
3667         {
3668                 // FIXME: the names could be added to one column list and then
3669                 // gradually shifted into the next column if they fit, and then the
3670                 // next to make a compact variable width listing but it's a lot more
3671                 // complicated...
3672                 // find width for columns
3673                 columnwidth = 0;
3674                 for (i = 0;i < numfiles;i++)
3675                 {
3676                         l = (int)strlen(search->filenames[i]);
3677                         if (columnwidth < l)
3678                                 columnwidth = l;
3679                 }
3680                 // count the spacing character
3681                 columnwidth++;
3682                 // calculate number of columns
3683                 numcolumns = con_linewidth / columnwidth;
3684                 // don't bother with the column printing if it's only one column
3685                 if (numcolumns >= 2)
3686                 {
3687                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3688                         for (i = 0;i < numlines;i++)
3689                         {
3690                                 linebufpos = 0;
3691                                 for (k = 0;k < numcolumns;k++)
3692                                 {
3693                                         l = i * numcolumns + k;
3694                                         if (l < numfiles)
3695                                         {
3696                                                 name = search->filenames[l];
3697                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3698                                                         linebuf[linebufpos++] = name[j];
3699                                                 // space out name unless it's the last on the line
3700                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
3701                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3702                                                                 linebuf[linebufpos++] = ' ';
3703                                         }
3704                                 }
3705                                 linebuf[linebufpos] = 0;
3706                                 Con_Printf("%s\n", linebuf);
3707                         }
3708                 }
3709                 else
3710                         oneperline = true;
3711         }
3712         if (oneperline)
3713                 for (i = 0;i < numfiles;i++)
3714                         Con_Printf("%s\n", search->filenames[i]);
3715         FS_FreeSearch(search);
3716         return (int)numfiles;
3717 }
3718
3719 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3720 {
3721         const char *pattern;
3722         if (Cmd_Argc() >= 3)
3723         {
3724                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3725                 return;
3726         }
3727         if (Cmd_Argc() == 2)
3728                 pattern = Cmd_Argv(1);
3729         else
3730                 pattern = "*";
3731         if (!FS_ListDirectory(pattern, oneperline))
3732                 Con_Print("No files found.\n");
3733 }
3734
3735 void FS_Dir_f(void)
3736 {
3737         FS_ListDirectoryCmd("dir", true);
3738 }
3739
3740 void FS_Ls_f(void)
3741 {
3742         FS_ListDirectoryCmd("ls", false);
3743 }
3744
3745 void FS_Which_f(void)
3746 {
3747         const char *filename;
3748         int index;
3749         searchpath_t *sp;
3750         if (Cmd_Argc() != 2)
3751         {
3752                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3753                 return;
3754         }  
3755         filename = Cmd_Argv(1);
3756         sp = FS_FindFile(filename, &index, true);
3757         if (!sp) {
3758                 Con_Printf("%s isn't anywhere\n", filename);
3759                 return;
3760         }
3761         if (sp->pack)
3762         {
3763                 if(sp->pack->vpack)
3764                         Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3765                 else
3766                         Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3767         }
3768         else
3769                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3770 }
3771
3772
3773 const char *FS_WhichPack(const char *filename)
3774 {
3775         int index;
3776         searchpath_t *sp = FS_FindFile(filename, &index, true);
3777         if(sp && sp->pack)
3778                 return sp->pack->shortname;
3779         else if(sp)
3780                 return "";
3781         else
3782                 return 0;
3783 }
3784
3785 /*
3786 ====================
3787 FS_IsRegisteredQuakePack
3788
3789 Look for a proof of purchase file file in the requested package
3790
3791 If it is found, this file should NOT be downloaded.
3792 ====================
3793 */
3794 qboolean FS_IsRegisteredQuakePack(const char *name)
3795 {
3796         searchpath_t *search;
3797         pack_t *pak;
3798
3799         // search through the path, one element at a time
3800         for (search = fs_searchpaths;search;search = search->next)
3801         {
3802                 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3803                         // TODO do we want to support vpacks in here too?
3804                 {
3805                         int (*strcmp_funct) (const char* str1, const char* str2);
3806                         int left, right, middle;
3807
3808                         pak = search->pack;
3809                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3810
3811                         // Look for the file (binary search)
3812                         left = 0;
3813                         right = pak->numfiles - 1;
3814                         while (left <= right)
3815                         {
3816                                 int diff;
3817
3818                                 middle = (left + right) / 2;
3819                                 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3820
3821                                 // Found it
3822                                 if (!diff)
3823                                         return true;
3824
3825                                 // If we're too far in the list
3826                                 if (diff > 0)
3827                                         right = middle - 1;
3828                                 else
3829                                         left = middle + 1;
3830                         }
3831
3832                         // we found the requested pack but it is not registered quake
3833                         return false;
3834                 }
3835         }
3836
3837         return false;
3838 }
3839
3840 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3841 {
3842         int crc = -1;
3843         unsigned char *filedata;
3844         fs_offset_t filesize;
3845         if (filesizepointer)
3846                 *filesizepointer = 0;
3847         if (!filename || !*filename)
3848                 return crc;
3849         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3850         if (filedata)
3851         {
3852                 if (filesizepointer)
3853                         *filesizepointer = filesize;
3854                 crc = CRC_Block(filedata, filesize);
3855                 Mem_Free(filedata);
3856         }
3857         return crc;
3858 }
3859
3860 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3861 {
3862         z_stream strm;
3863         unsigned char *out = NULL;
3864         unsigned char *tmp;
3865
3866         *deflated_size = 0;
3867 #ifndef LINK_TO_ZLIB
3868         if(!zlib_dll)
3869                 return NULL;
3870 #endif
3871
3872         memset(&strm, 0, sizeof(strm));
3873         strm.zalloc = Z_NULL;
3874         strm.zfree = Z_NULL;
3875         strm.opaque = Z_NULL;
3876
3877         if(level < 0)
3878                 level = Z_DEFAULT_COMPRESSION;
3879
3880         if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3881         {
3882                 Con_Printf("FS_Deflate: deflate init error!\n");
3883                 return NULL;
3884         }
3885
3886         strm.next_in = (unsigned char*)data;
3887         strm.avail_in = (unsigned int)size;
3888
3889         tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3890         if(!tmp)
3891         {
3892                 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
3893                 qz_deflateEnd(&strm);
3894                 return NULL;
3895         }
3896
3897         strm.next_out = tmp;
3898         strm.avail_out = (unsigned int)size;
3899
3900         if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
3901         {
3902                 Con_Printf("FS_Deflate: deflate failed!\n");
3903                 qz_deflateEnd(&strm);
3904                 Mem_Free(tmp);
3905                 return NULL;
3906         }
3907         
3908         if(qz_deflateEnd(&strm) != Z_OK)
3909         {
3910                 Con_Printf("FS_Deflate: deflateEnd failed\n");
3911                 Mem_Free(tmp);
3912                 return NULL;
3913         }
3914
3915         if(strm.total_out >= size)
3916         {
3917                 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
3918                 Mem_Free(tmp);
3919                 return NULL;
3920         }
3921
3922         out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
3923         if(!out)
3924         {
3925                 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
3926                 Mem_Free(tmp);
3927                 return NULL;
3928         }
3929
3930         if(deflated_size)
3931                 *deflated_size = (size_t)strm.total_out;
3932
3933         memcpy(out, tmp, strm.total_out);
3934         Mem_Free(tmp);
3935         
3936         return out;
3937 }
3938
3939 static void AssertBufsize(sizebuf_t *buf, int length)
3940 {
3941         if(buf->cursize + length > buf->maxsize)
3942         {
3943                 int oldsize = buf->maxsize;
3944                 unsigned char *olddata;
3945                 olddata = buf->data;
3946                 buf->maxsize += length;
3947                 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
3948                 if(olddata)
3949                 {
3950                         memcpy(buf->data, olddata, oldsize);
3951                         Mem_Free(olddata);
3952                 }
3953         }
3954 }
3955
3956 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
3957 {
3958         int ret;
3959         z_stream strm;
3960         unsigned char *out = NULL;
3961         unsigned char tmp[2048];
3962         unsigned int have;
3963         sizebuf_t outbuf;
3964
3965         *inflated_size = 0;
3966 #ifndef LINK_TO_ZLIB
3967         if(!zlib_dll)
3968                 return NULL;
3969 #endif
3970
3971         memset(&outbuf, 0, sizeof(outbuf));
3972         outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
3973         outbuf.maxsize = sizeof(tmp);
3974
3975         memset(&strm, 0, sizeof(strm));
3976         strm.zalloc = Z_NULL;
3977         strm.zfree = Z_NULL;
3978         strm.opaque = Z_NULL;
3979
3980         if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
3981         {
3982                 Con_Printf("FS_Inflate: inflate init error!\n");
3983                 Mem_Free(outbuf.data);
3984                 return NULL;
3985         }
3986
3987         strm.next_in = (unsigned char*)data;
3988         strm.avail_in = (unsigned int)size;
3989
3990         do
3991         {
3992                 strm.next_out = tmp;
3993                 strm.avail_out = sizeof(tmp);
3994                 ret = qz_inflate(&strm, Z_NO_FLUSH);
3995                 // it either returns Z_OK on progress, Z_STREAM_END on end
3996                 // or an error code
3997                 switch(ret)
3998                 {
3999                         case Z_STREAM_END:
4000                         case Z_OK:
4001                                 break;
4002                                 
4003                         case Z_STREAM_ERROR:
4004                                 Con_Print("FS_Inflate: stream error!\n");
4005                                 break;
4006                         case Z_DATA_ERROR:
4007                                 Con_Print("FS_Inflate: data error!\n");
4008                                 break;
4009                         case Z_MEM_ERROR:
4010                                 Con_Print("FS_Inflate: mem error!\n");
4011                                 break;
4012                         case Z_BUF_ERROR:
4013                                 Con_Print("FS_Inflate: buf error!\n");
4014                                 break;
4015                         default:
4016                                 Con_Print("FS_Inflate: unknown error!\n");
4017                                 break;
4018                                 
4019                 }
4020                 if(ret != Z_OK && ret != Z_STREAM_END)
4021                 {
4022                         Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4023                         Mem_Free(outbuf.data);
4024                         qz_inflateEnd(&strm);
4025                         return NULL;
4026                 }
4027                 have = sizeof(tmp) - strm.avail_out;
4028                 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4029                 SZ_Write(&outbuf, tmp, have);
4030         } while(ret != Z_STREAM_END);
4031
4032         qz_inflateEnd(&strm);
4033
4034         out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4035         if(!out)
4036         {
4037                 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4038                 Mem_Free(outbuf.data);
4039                 return NULL;
4040         }
4041
4042         memcpy(out, outbuf.data, outbuf.cursize);
4043         Mem_Free(outbuf.data);
4044
4045         if(inflated_size)
4046                 *inflated_size = (size_t)outbuf.cursize;
4047         
4048         return out;
4049 }