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