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