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