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