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