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