]> git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
File system: Android assets support.
[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 #define USE_RWOPS 1
78 #if USE_RWOPS
79 # include <SDL.h>
80 typedef SDL_RWops *filedesc_t;
81 # define FILEDESC_INVALID NULL
82 # define FILEDESC_ISVALID(fd) ((fd) != NULL)
83 # define FILEDESC_READ(fd,buf,count) ((fs_offset_t)SDL_RWread(fd, buf, 1, count))
84 # define FILEDESC_WRITE(fd,buf,count) ((fs_offset_t)SDL_RWwrite(fd, buf, 1, count))
85 # define FILEDESC_CLOSE SDL_RWclose
86 # define FILEDESC_SEEK SDL_RWseek
87 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
88         filedesc_t new_fd = SDL_RWFromFile(filename, "rb");
89         if (SDL_RWseek(new_fd, SDL_RWseek(fd, 0, RW_SEEK_CUR), RW_SEEK_SET) < 0) {
90                 SDL_RWclose(new_fd);
91                 return NULL;
92         }
93         return new_fd;
94 }
95 # define unlink(name) Con_DPrintf("Sorry, no unlink support when trying to unlink %s.\n", (name))
96 #else
97 typedef int filedesc_t;
98 # define FILEDESC_INVALID -1
99 # define FILEDESC_ISVALID(fd) ((fd) != -1)
100 # define FILEDESC_READ read
101 # define FILEDESC_WRITE write
102 # define FILEDESC_CLOSE close
103 # define FILEDESC_SEEK lseek
104 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
105         return dup(fd);
106 }
107 #endif
108
109 /** \page fs File System
110
111 All of Quake's data access is through a hierchal file system, but the contents
112 of the file system can be transparently merged from several sources.
113
114 The "base directory" is the path to the directory holding the quake.exe and
115 all game directories.  The sys_* files pass this to host_init in
116 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
117 line parm to allow code debugging in a different directory.  The base
118 directory is only used during filesystem initialization.
119
120 The "game directory" is the first tree on the search path and directory that
121 all generated files (savegames, screenshots, demos, config files) will be
122 saved to.  This can be overridden with the "-game" command line parameter.
123 The game directory can never be changed while quake is executing.  This is a
124 precaution against having a malicious server instruct clients to write files
125 over areas they shouldn't.
126
127 */
128
129
130 /*
131 =============================================================================
132
133 CONSTANTS
134
135 =============================================================================
136 */
137
138 // Magic numbers of a ZIP file (big-endian format)
139 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
140 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
141 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
142
143 // Other constants for ZIP files
144 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
145 #define ZIP_END_CDIR_SIZE                       22
146 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
147 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
148
149 #ifdef LINK_TO_ZLIB
150 #include <zlib.h>
151
152 #define qz_inflate inflate
153 #define qz_inflateEnd inflateEnd
154 #define qz_inflateInit2_ inflateInit2_
155 #define qz_inflateReset inflateReset
156 #define qz_deflateInit2_ deflateInit2_
157 #define qz_deflateEnd deflateEnd
158 #define qz_deflate deflate
159 #define Z_MEMLEVEL_DEFAULT 8
160 #else
161
162 // Zlib constants (from zlib.h)
163 #define Z_SYNC_FLUSH    2
164 #define MAX_WBITS               15
165 #define Z_OK                    0
166 #define Z_STREAM_END    1
167 #define Z_STREAM_ERROR  (-2)
168 #define Z_DATA_ERROR    (-3)
169 #define Z_MEM_ERROR     (-4)
170 #define Z_BUF_ERROR     (-5)
171 #define ZLIB_VERSION    "1.2.3"
172
173 #define Z_BINARY 0
174 #define Z_DEFLATED 8
175 #define Z_MEMLEVEL_DEFAULT 8
176
177 #define Z_NULL 0
178 #define Z_DEFAULT_COMPRESSION (-1)
179 #define Z_NO_FLUSH 0
180 #define Z_SYNC_FLUSH 2
181 #define Z_FULL_FLUSH 3
182 #define Z_FINISH 4
183
184 // Uncomment the following line if the zlib DLL you have still uses
185 // the 1.1.x series calling convention on Win32 (WINAPI)
186 //#define ZLIB_USES_WINAPI
187
188
189 /*
190 =============================================================================
191
192 TYPES
193
194 =============================================================================
195 */
196
197 /*! Zlib stream (from zlib.h)
198  * \warning: some pointers we don't use directly have
199  * been cast to "void*" for a matter of simplicity
200  */
201 typedef struct
202 {
203         unsigned char                   *next_in;       ///< next input byte
204         unsigned int    avail_in;       ///< number of bytes available at next_in
205         unsigned long   total_in;       ///< total nb of input bytes read so far
206
207         unsigned char                   *next_out;      ///< next output byte should be put there
208         unsigned int    avail_out;      ///< remaining free space at next_out
209         unsigned long   total_out;      ///< total nb of bytes output so far
210
211         char                    *msg;           ///< last error message, NULL if no error
212         void                    *state;         ///< not visible by applications
213
214         void                    *zalloc;        ///< used to allocate the internal state
215         void                    *zfree;         ///< used to free the internal state
216         void                    *opaque;        ///< private data object passed to zalloc and zfree
217
218         int                             data_type;      ///< best guess about the data type: ascii or binary
219         unsigned long   adler;          ///< adler32 value of the uncompressed data
220         unsigned long   reserved;       ///< reserved for future use
221 } z_stream;
222 #endif
223
224
225 /// inside a package (PAK or PK3)
226 #define QFILE_FLAG_PACKED (1 << 0)
227 /// file is compressed using the deflate algorithm (PK3 only)
228 #define QFILE_FLAG_DEFLATED (1 << 1)
229 /// file is actually already loaded data
230 #define QFILE_FLAG_DATA (1 << 2)
231 /// real file will be removed on close
232 #define QFILE_FLAG_REMOVE (1 << 3)
233
234 #define FILE_BUFF_SIZE 2048
235 typedef struct
236 {
237         z_stream        zstream;
238         size_t          comp_length;                    ///< length of the compressed file
239         size_t          in_ind, in_len;                 ///< input buffer current index and length
240         size_t          in_position;                    ///< position in the compressed file
241         unsigned char           input [FILE_BUFF_SIZE];
242 } ztoolkit_t;
243
244 struct qfile_s
245 {
246         int                             flags;
247         filedesc_t                      handle;                                 ///< file descriptor
248         fs_offset_t             real_length;                    ///< uncompressed file size (for files opened in "read" mode)
249         fs_offset_t             position;                               ///< current position in the file
250         fs_offset_t             offset;                                 ///< offset into the package (0 if external file)
251         int                             ungetc;                                 ///< single stored character from ungetc, cleared to EOF when read
252
253         // Contents buffer
254         fs_offset_t             buff_ind, buff_len;             ///< buffer current index and length
255         unsigned char                   buff [FILE_BUFF_SIZE];
256
257         ztoolkit_t*             ztk;    ///< For zipped files.
258
259         const unsigned char *data;      ///< For data files.
260
261         const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
262 };
263
264
265 // ------ PK3 files on disk ------ //
266
267 // You can get the complete ZIP format description from PKWARE website
268
269 typedef struct pk3_endOfCentralDir_s
270 {
271         unsigned int signature;
272         unsigned short disknum;
273         unsigned short cdir_disknum;    ///< number of the disk with the start of the central directory
274         unsigned short localentries;    ///< number of entries in the central directory on this disk
275         unsigned short nbentries;               ///< total number of entries in the central directory on this disk
276         unsigned int cdir_size;                 ///< size of the central directory
277         unsigned int cdir_offset;               ///< with respect to the starting disk number
278         unsigned short comment_size;
279         fs_offset_t prepended_garbage;
280 } pk3_endOfCentralDir_t;
281
282
283 // ------ PAK files on disk ------ //
284 typedef struct dpackfile_s
285 {
286         char name[56];
287         int filepos, filelen;
288 } dpackfile_t;
289
290 typedef struct dpackheader_s
291 {
292         char id[4];
293         int dirofs;
294         int dirlen;
295 } dpackheader_t;
296
297
298 /*! \name Packages in memory
299  * @{
300  */
301 /// the offset in packfile_t is the true contents offset
302 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
303 /// file compressed using the deflate algorithm
304 #define PACKFILE_FLAG_DEFLATED (1 << 1)
305 /// file is a symbolic link
306 #define PACKFILE_FLAG_SYMLINK (1 << 2)
307
308 typedef struct packfile_s
309 {
310         char name [MAX_QPATH];
311         int flags;
312         fs_offset_t offset;
313         fs_offset_t packsize;   ///< size in the package
314         fs_offset_t realsize;   ///< real file size (uncompressed)
315 } packfile_t;
316
317 typedef struct pack_s
318 {
319         char filename [MAX_OSPATH];
320         char shortname [MAX_QPATH];
321         filedesc_t handle;
322         int ignorecase;  ///< PK3 ignores case
323         int numfiles;
324         qboolean vpack;
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(void);
348 void FS_Ls_f(void);
349 void FS_Which_f(void);
350
351 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean 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 = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
387 cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
388 cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "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_UnloadLibrary (&zlib_dll);
500 #endif
501 }
502
503
504 /*
505 ====================
506 PK3_OpenLibrary
507
508 Try to load the Zlib DLL
509 ====================
510 */
511 static qboolean 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_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
540 #endif
541 }
542
543 /*
544 ====================
545 FS_HasZlib
546
547 See if zlib is available
548 ====================
549 */
550 qboolean 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 qboolean 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                 // LordHavoc: 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, qboolean 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, qboolean 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 qboolean 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(COM_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 (void)
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 qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
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"))
1143                 pak = FS_LoadPackVirtual (pakfile);
1144         else if(!strcasecmp(ext, "pak"))
1145                 pak = FS_LoadPackPAK (pakfile);
1146         else if(!strcasecmp(ext, "pk3"))
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                 if(pak->vpack)
1199                 {
1200                         dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1201                         // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
1202                         // same goes for the name inside the pack structure
1203                         l = strlen(pak->shortname);
1204                         if(l >= 7)
1205                                 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
1206                                         pak->shortname[l - 3] = 0;
1207                         l = strlen(pak->filename);
1208                         if(l >= 7)
1209                                 if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
1210                                         pak->filename[l - 3] = 0;
1211                 }
1212                 return true;
1213         }
1214         else
1215         {
1216                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1217                 return false;
1218         }
1219 }
1220
1221
1222 /*
1223 ================
1224 FS_AddPack
1225 ================
1226 */
1227 /*! Adds the given pack to the search path and searches for it in the game path.
1228  * The pack type is autodetected by the file extension.
1229  *
1230  * Returns true if the file was successfully added to the
1231  * search path or if it was already included.
1232  *
1233  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1234  * plain directories.
1235  */
1236 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1237 {
1238         char fullpath[MAX_OSPATH];
1239         int index;
1240         searchpath_t *search;
1241
1242         if(already_loaded)
1243                 *already_loaded = false;
1244
1245         // then find the real name...
1246         search = FS_FindFile(pakfile, &index, true);
1247         if(!search || search->pack)
1248         {
1249                 Con_Printf("could not find pak \"%s\"\n", pakfile);
1250                 return false;
1251         }
1252
1253         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1254
1255         return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1256 }
1257
1258
1259 /*
1260 ================
1261 FS_AddGameDirectory
1262
1263 Sets fs_gamedir, adds the directory to the head of the path,
1264 then loads and adds pak1.pak pak2.pak ...
1265 ================
1266 */
1267 static void FS_AddGameDirectory (const char *dir)
1268 {
1269         int i;
1270         stringlist_t list;
1271         searchpath_t *search;
1272
1273         strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1274
1275         stringlistinit(&list);
1276         listdirectory(&list, "", dir);
1277         stringlistsort(&list, false);
1278
1279         // add any PAK package in the directory
1280         for (i = 0;i < list.numstrings;i++)
1281         {
1282                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1283                 {
1284                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1285                 }
1286         }
1287
1288         // add any PK3 package in the directory
1289         for (i = 0;i < list.numstrings;i++)
1290         {
1291                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
1292                 {
1293                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1294                 }
1295         }
1296
1297         stringlistfreecontents(&list);
1298
1299         // Add the directory to the search path
1300         // (unpacked files have the priority over packed files)
1301         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1302         strlcpy (search->filename, dir, sizeof (search->filename));
1303         search->next = fs_searchpaths;
1304         fs_searchpaths = search;
1305 }
1306
1307
1308 /*
1309 ================
1310 FS_AddGameHierarchy
1311 ================
1312 */
1313 static void FS_AddGameHierarchy (const char *dir)
1314 {
1315         char vabuf[1024];
1316         // Add the common game directory
1317         FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
1318
1319         if (*fs_userdir)
1320                 FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
1321 }
1322
1323
1324 /*
1325 ============
1326 FS_FileExtension
1327 ============
1328 */
1329 const char *FS_FileExtension (const char *in)
1330 {
1331         const char *separator, *backslash, *colon, *dot;
1332
1333         separator = strrchr(in, '/');
1334         backslash = strrchr(in, '\\');
1335         if (!separator || separator < backslash)
1336                 separator = backslash;
1337         colon = strrchr(in, ':');
1338         if (!separator || separator < colon)
1339                 separator = colon;
1340
1341         dot = strrchr(in, '.');
1342         if (dot == NULL || (separator && (dot < separator)))
1343                 return "";
1344
1345         return dot + 1;
1346 }
1347
1348
1349 /*
1350 ============
1351 FS_FileWithoutPath
1352 ============
1353 */
1354 const char *FS_FileWithoutPath (const char *in)
1355 {
1356         const char *separator, *backslash, *colon;
1357
1358         separator = strrchr(in, '/');
1359         backslash = strrchr(in, '\\');
1360         if (!separator || separator < backslash)
1361                 separator = backslash;
1362         colon = strrchr(in, ':');
1363         if (!separator || separator < colon)
1364                 separator = colon;
1365         return separator ? separator + 1 : in;
1366 }
1367
1368
1369 /*
1370 ================
1371 FS_ClearSearchPath
1372 ================
1373 */
1374 static void FS_ClearSearchPath (void)
1375 {
1376         // unload all packs and directory information, close all pack files
1377         // (if a qfile is still reading a pack it won't be harmed because it used
1378         //  dup() to get its own handle already)
1379         while (fs_searchpaths)
1380         {
1381                 searchpath_t *search = fs_searchpaths;
1382                 fs_searchpaths = search->next;
1383                 if (search->pack && search->pack != fs_selfpack)
1384                 {
1385                         if(!search->pack->vpack)
1386                         {
1387                                 // close the file
1388                                 FILEDESC_CLOSE(search->pack->handle);
1389                                 // free any memory associated with it
1390                                 if (search->pack->files)
1391                                         Mem_Free(search->pack->files);
1392                         }
1393                         Mem_Free(search->pack);
1394                 }
1395                 Mem_Free(search);
1396         }
1397 }
1398
1399 static void FS_AddSelfPack(void)
1400 {
1401         if(fs_selfpack)
1402         {
1403                 searchpath_t *search;
1404                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1405                 search->next = fs_searchpaths;
1406                 search->pack = fs_selfpack;
1407                 fs_searchpaths = search;
1408         }
1409 }
1410
1411
1412 /*
1413 ================
1414 FS_Rescan
1415 ================
1416 */
1417 void FS_Rescan (void)
1418 {
1419         int i;
1420         qboolean fs_modified = false;
1421         qboolean reset = false;
1422         char gamedirbuf[MAX_INPUTLINE];
1423         char vabuf[1024];
1424
1425         if (fs_searchpaths)
1426                 reset = true;
1427         FS_ClearSearchPath();
1428
1429         // automatically activate gamemode for the gamedirs specified
1430         if (reset)
1431                 COM_ChangeGameTypeForGameDirs();
1432
1433         // add the game-specific paths
1434         // gamedirname1 (typically id1)
1435         FS_AddGameHierarchy (gamedirname1);
1436         // update the com_modname (used for server info)
1437         if (gamedirname2 && gamedirname2[0])
1438                 strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1439         else
1440                 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1441
1442         // add the game-specific path, if any
1443         // (only used for mission packs and the like, which should set fs_modified)
1444         if (gamedirname2 && gamedirname2[0])
1445         {
1446                 fs_modified = true;
1447                 FS_AddGameHierarchy (gamedirname2);
1448         }
1449
1450         // -game <gamedir>
1451         // Adds basedir/gamedir as an override game
1452         // LordHavoc: now supports multiple -game directories
1453         // set the com_modname (reported in server info)
1454         *gamedirbuf = 0;
1455         for (i = 0;i < fs_numgamedirs;i++)
1456         {
1457                 fs_modified = true;
1458                 FS_AddGameHierarchy (fs_gamedirs[i]);
1459                 // update the com_modname (used server info)
1460                 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1461                 if(i)
1462                         strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1463                 else
1464                         strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1465         }
1466         Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1467
1468         // add back the selfpack as new first item
1469         FS_AddSelfPack();
1470
1471         // set the default screenshot name to either the mod name or the
1472         // gamemode screenshot name
1473         if (strcmp(com_modname, gamedirname1))
1474                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1475         else
1476                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1477         
1478         if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1479                 strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1480
1481         // If "-condebug" is in the command line, remove the previous log file
1482         if (COM_CheckParm ("-condebug") != 0)
1483                 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1484
1485         // look for the pop.lmp file and set registered to true if it is found
1486         if (FS_FileExists("gfx/pop.lmp"))
1487                 Cvar_Set ("registered", "1");
1488         switch(gamemode)
1489         {
1490         case GAME_NORMAL:
1491         case GAME_HIPNOTIC:
1492         case GAME_ROGUE:
1493                 if (!registered.integer)
1494                 {
1495                         if (fs_modified)
1496                                 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1497                         else
1498                                 Con_Print("Playing shareware version.\n");
1499                 }
1500                 else
1501                         Con_Print("Playing registered version.\n");
1502                 break;
1503         case GAME_STEELSTORM:
1504                 if (registered.integer)
1505                         Con_Print("Playing registered version.\n");
1506                 else
1507                         Con_Print("Playing shareware version.\n");
1508                 break;
1509         default:
1510                 break;
1511         }
1512
1513         // unload all wads so that future queries will return the new data
1514         W_UnloadAll();
1515 }
1516
1517 static void FS_Rescan_f(void)
1518 {
1519         FS_Rescan();
1520 }
1521
1522 /*
1523 ================
1524 FS_ChangeGameDirs
1525 ================
1526 */
1527 extern qboolean vid_opened;
1528 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1529 {
1530         int i;
1531         const char *p;
1532
1533         if (fs_numgamedirs == numgamedirs)
1534         {
1535                 for (i = 0;i < numgamedirs;i++)
1536                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1537                                 break;
1538                 if (i == numgamedirs)
1539                         return true; // already using this set of gamedirs, do nothing
1540         }
1541
1542         if (numgamedirs > MAX_GAMEDIRS)
1543         {
1544                 if (complain)
1545                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1546                 return false; // too many gamedirs
1547         }
1548
1549         for (i = 0;i < numgamedirs;i++)
1550         {
1551                 // if string is nasty, reject it
1552                 p = FS_CheckGameDir(gamedirs[i]);
1553                 if(!p)
1554                 {
1555                         if (complain)
1556                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1557                         return false; // nasty gamedirs
1558                 }
1559                 if(p == fs_checkgamedir_missing && failmissing)
1560                 {
1561                         if (complain)
1562                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1563                         return false; // missing gamedirs
1564                 }
1565         }
1566
1567         Host_SaveConfig();
1568
1569         fs_numgamedirs = numgamedirs;
1570         for (i = 0;i < fs_numgamedirs;i++)
1571                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1572
1573         // reinitialize filesystem to detect the new paks
1574         FS_Rescan();
1575
1576         if (cls.demoplayback)
1577         {
1578                 CL_Disconnect_f();
1579                 cls.demonum = 0;
1580         }
1581
1582         // unload all sounds so they will be reloaded from the new files as needed
1583         S_UnloadAllSounds_f();
1584
1585         // close down the video subsystem, it will start up again when the config finishes...
1586         VID_Stop();
1587         vid_opened = false;
1588
1589         // restart the video subsystem after the config is executed
1590         Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
1591
1592         return true;
1593 }
1594
1595 /*
1596 ================
1597 FS_GameDir_f
1598 ================
1599 */
1600 static void FS_GameDir_f (void)
1601 {
1602         int i;
1603         int numgamedirs;
1604         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1605
1606         if (Cmd_Argc() < 2)
1607         {
1608                 Con_Printf("gamedirs active:");
1609                 for (i = 0;i < fs_numgamedirs;i++)
1610                         Con_Printf(" %s", fs_gamedirs[i]);
1611                 Con_Printf("\n");
1612                 return;
1613         }
1614
1615         numgamedirs = Cmd_Argc() - 1;
1616         if (numgamedirs > MAX_GAMEDIRS)
1617         {
1618                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1619                 return;
1620         }
1621
1622         for (i = 0;i < numgamedirs;i++)
1623                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1624
1625         if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1626         {
1627                 // actually, changing during game would work fine, but would be stupid
1628                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1629                 return;
1630         }
1631
1632         // halt demo playback to close the file
1633         CL_Disconnect();
1634
1635         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1636 }
1637
1638 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1639 {
1640         qboolean success;
1641         qfile_t *f;
1642         stringlist_t list;
1643         fs_offset_t n;
1644         char vabuf[1024];
1645
1646         stringlistinit(&list);
1647         listdirectory(&list, gamedir, "");
1648         success = list.numstrings > 0;
1649         stringlistfreecontents(&list);
1650
1651         if(success)
1652         {
1653                 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1654                 if(f)
1655                 {
1656                         n = FS_Read (f, buf, buflength - 1);
1657                         if(n >= 0)
1658                                 buf[n] = 0;
1659                         else
1660                                 *buf = 0;
1661                         FS_Close(f);
1662                 }
1663                 else
1664                         *buf = 0;
1665                 return buf;
1666         }
1667
1668         return NULL;
1669 }
1670
1671 /*
1672 ================
1673 FS_CheckGameDir
1674 ================
1675 */
1676 const char *FS_CheckGameDir(const char *gamedir)
1677 {
1678         const char *ret;
1679         static char buf[8192];
1680         char vabuf[1024];
1681
1682         if (FS_CheckNastyPath(gamedir, true))
1683                 return NULL;
1684
1685         ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1686         if(ret)
1687         {
1688                 if(!*ret)
1689                 {
1690                         // get description from basedir
1691                         ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1692                         if(ret)
1693                                 return ret;
1694                         return "";
1695                 }
1696                 return ret;
1697         }
1698
1699         ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1700         if(ret)
1701                 return ret;
1702         
1703         return fs_checkgamedir_missing;
1704 }
1705
1706 static void FS_ListGameDirs(void)
1707 {
1708         stringlist_t list, list2;
1709         int i;
1710         const char *info;
1711         char vabuf[1024];
1712
1713         fs_all_gamedirs_count = 0;
1714         if(fs_all_gamedirs)
1715                 Mem_Free(fs_all_gamedirs);
1716
1717         stringlistinit(&list);
1718         listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1719         listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1720         stringlistsort(&list, false);
1721
1722         stringlistinit(&list2);
1723         for(i = 0; i < list.numstrings; ++i)
1724         {
1725                 if(i)
1726                         if(!strcmp(list.strings[i-1], list.strings[i]))
1727                                 continue;
1728                 info = FS_CheckGameDir(list.strings[i]);
1729                 if(!info)
1730                         continue;
1731                 if(info == fs_checkgamedir_missing)
1732                         continue;
1733                 if(!*info)
1734                         continue;
1735                 stringlistappend(&list2, list.strings[i]); 
1736         }
1737         stringlistfreecontents(&list);
1738
1739         fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1740         for(i = 0; i < list2.numstrings; ++i)
1741         {
1742                 info = FS_CheckGameDir(list2.strings[i]);
1743                 // all this cannot happen any more, but better be safe than sorry
1744                 if(!info)
1745                         continue;
1746                 if(info == fs_checkgamedir_missing)
1747                         continue;
1748                 if(!*info)
1749                         continue;
1750                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1751                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1752                 ++fs_all_gamedirs_count;
1753         }
1754 }
1755
1756 /*
1757 #ifdef WIN32
1758 #pragma comment(lib, "shell32.lib")
1759 #include <ShlObj.h>
1760 #endif
1761 */
1762
1763 static void COM_InsertFlags(const char *buf) {
1764         const char *p;
1765         char *q;
1766         const char **new_argv;
1767         int i = 0;
1768         int args_left = 256;
1769         new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
1770         if(com_argc == 0)
1771                 new_argv[0] = "dummy";  // Can't really happen.
1772         else
1773                 new_argv[0] = com_argv[0];
1774         ++i;
1775         p = buf;
1776         while(COM_ParseToken_Console(&p))
1777         {
1778                 size_t sz = strlen(com_token) + 1; // shut up clang
1779                 if(i > args_left)
1780                         break;
1781                 q = (char *)Mem_Alloc(fs_mempool, sz);
1782                 strlcpy(q, com_token, sz);
1783                 new_argv[i] = q;
1784                 ++i;
1785         }
1786         // Now: i <= args_left + 1.
1787         if (com_argc >= 1)
1788         {
1789                 memcpy((char *)(&new_argv[i]), &com_argv[1], sizeof(*com_argv) * (com_argc - 1));
1790                 i += com_argc - 1;
1791         }
1792         // Now: i <= args_left + (com_argc || 1).
1793         new_argv[i] = NULL;
1794         com_argv = new_argv;
1795         com_argc = i;
1796 }
1797
1798 /*
1799 ================
1800 FS_Init_SelfPack
1801 ================
1802 */
1803 void FS_Init_SelfPack (void)
1804 {
1805         PK3_OpenLibrary ();
1806         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1807
1808         // Load darkplaces.opt from the FS.
1809         if (!COM_CheckParm("-noopt"))
1810         {
1811                 char *buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
1812                 if(buf)
1813                         COM_InsertFlags(buf);
1814                 Mem_Free(buf);
1815         }
1816
1817 #ifndef USE_RWOPS
1818         // Provide the SelfPack.
1819         if (!COM_CheckParm("-noselfpack"))
1820         {
1821                 if (com_selffd >= 0)
1822                 {
1823                         fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
1824                         if(fs_selfpack)
1825                         {
1826                                 FS_AddSelfPack();
1827                                 if (!COM_CheckParm("-noopt"))
1828                                 {
1829                                         char *buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
1830                                         if(buf)
1831                                                 COM_InsertFlags(buf);
1832                                         Mem_Free(buf);
1833                                 }
1834                         }
1835                 }
1836         }
1837 #endif
1838 }
1839
1840 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1841 {
1842 #if defined(__IPHONEOS__)
1843         if (userdirmode == USERDIRMODE_HOME)
1844         {
1845                 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1846                 // fs_userdir stores configurations to the Documents folder of the app
1847                 strlcpy(userdir, "../Documents/", MAX_OSPATH);
1848                 return 1;
1849         }
1850         return -1;
1851
1852 #elif defined(WIN32)
1853         char *homedir;
1854 #if _MSC_VER >= 1400
1855         size_t homedirlen;
1856 #endif
1857         TCHAR mydocsdir[MAX_PATH + 1];
1858         wchar_t *savedgamesdirw;
1859         char savedgamesdir[MAX_OSPATH];
1860         int fd;
1861         char vabuf[1024];
1862
1863         userdir[0] = 0;
1864         switch(userdirmode)
1865         {
1866         default:
1867                 return -1;
1868         case USERDIRMODE_NOHOME:
1869                 strlcpy(userdir, fs_basedir, userdirsize);
1870                 break;
1871         case USERDIRMODE_MYGAMES:
1872                 if (!shfolder_dll)
1873                         Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1874                 mydocsdir[0] = 0;
1875                 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
1876                 {
1877                         dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1878                         break;
1879                 }
1880 #if _MSC_VER >= 1400
1881                 _dupenv_s(&homedir, &homedirlen, "USERPROFILE");
1882                 if(homedir)
1883                 {
1884                         dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1885                         free(homedir);
1886                         break;
1887                 }
1888 #else
1889                 homedir = getenv("USERPROFILE");
1890                 if(homedir)
1891                 {
1892                         dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1893                         break;
1894                 }
1895 #endif
1896                 return -1;
1897         case USERDIRMODE_SAVEDGAMES:
1898                 if (!shell32_dll)
1899                         Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
1900                 if (!ole32_dll)
1901                         Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
1902                 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1903                 {
1904                         savedgamesdir[0] = 0;
1905                         qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1906 /*
1907 #ifdef __cplusplus
1908                         if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1909 #else
1910                         if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1911 #endif
1912 */
1913                         if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1914                         {
1915                                 memset(savedgamesdir, 0, sizeof(savedgamesdir));
1916 #if _MSC_VER >= 1400
1917                                 wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
1918 #else
1919                                 wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
1920 #endif
1921                                 qCoTaskMemFree(savedgamesdirw);
1922                         }
1923                         qCoUninitialize();
1924                         if (savedgamesdir[0])
1925                         {
1926                                 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
1927                                 break;
1928                         }
1929                 }
1930                 return -1;
1931         }
1932 #else
1933         int fd;
1934         char *homedir;
1935         char vabuf[1024];
1936         userdir[0] = 0;
1937         switch(userdirmode)
1938         {
1939         default:
1940                 return -1;
1941         case USERDIRMODE_NOHOME:
1942                 strlcpy(userdir, fs_basedir, userdirsize);
1943                 break;
1944         case USERDIRMODE_HOME:
1945                 homedir = getenv("HOME");
1946                 if(homedir)
1947                 {
1948                         dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1949                         break;
1950                 }
1951                 return -1;
1952         case USERDIRMODE_SAVEDGAMES:
1953                 homedir = getenv("HOME");
1954                 if(homedir)
1955                 {
1956 #ifdef MACOSX
1957                         dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
1958 #else
1959                         // the XDG say some files would need to go in:
1960                         // XDG_CONFIG_HOME (or ~/.config/%s/)
1961                         // XDG_DATA_HOME (or ~/.local/share/%s/)
1962                         // XDG_CACHE_HOME (or ~/.cache/%s/)
1963                         // and also search the following global locations if defined:
1964                         // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
1965                         // XDG_DATA_DIRS (normally /usr/share/%s/)
1966                         // this would be too complicated...
1967                         return -1;
1968 #endif
1969                         break;
1970                 }
1971                 return -1;
1972         }
1973 #endif
1974
1975
1976 #if !defined(__IPHONEOS__)
1977
1978 #ifdef WIN32
1979         // historical behavior...
1980         if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
1981                 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
1982 #endif
1983
1984         // see if we can write to this path (note: won't create path)
1985 #ifdef WIN32
1986         // no access() here, we must try to open the file for appending
1987         fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
1988         if(fd >= 0)
1989                 FILEDESC_CLOSE(fd);
1990 #else
1991         // on Unix, we don't need to ACTUALLY attempt to open the file
1992         if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
1993                 fd = 1;
1994         else
1995                 fd = -1;
1996 #endif
1997         if(fd >= 0)
1998         {
1999                 return 1; // good choice - the path exists and is writable
2000         }
2001         else
2002         {
2003                 if (userdirmode == USERDIRMODE_NOHOME)
2004                         return -1; // path usually already exists, we lack permissions
2005                 else
2006                         return 0; // probably good - failed to write but maybe we need to create path
2007         }
2008 #endif
2009 }
2010
2011 /*
2012 ================
2013 FS_Init
2014 ================
2015 */
2016 void FS_Init (void)
2017 {
2018         const char *p;
2019         int i;
2020
2021         *fs_basedir = 0;
2022         *fs_userdir = 0;
2023         *fs_gamedir = 0;
2024
2025         // -basedir <path>
2026         // Overrides the system supplied base directory (under GAMENAME)
2027 // 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)
2028         i = COM_CheckParm ("-basedir");
2029         if (i && i < com_argc-1)
2030         {
2031                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
2032                 i = (int)strlen (fs_basedir);
2033                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2034                         fs_basedir[i-1] = 0;
2035         }
2036         else
2037         {
2038 // If the base directory is explicitly defined by the compilation process
2039 #ifdef DP_FS_BASEDIR
2040                 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2041 #elif defined(__ANDROID__)
2042                 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2043 #elif defined(MACOSX)
2044                 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2045                 if (strstr(com_argv[0], ".app/"))
2046                 {
2047                         char *split;
2048                         strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
2049                         split = strstr(fs_basedir, ".app/");
2050                         if (split)
2051                         {
2052                                 struct stat statresult;
2053                                 char vabuf[1024];
2054                                 // truncate to just after the .app/
2055                                 split[5] = 0;
2056                                 // see if gamedir exists in Resources
2057                                 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2058                                 {
2059                                         // found gamedir inside Resources, use it
2060                                         strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2061                                 }
2062                                 else
2063                                 {
2064                                         // no gamedir found in Resources, gamedir is probably
2065                                         // outside the .app, remove .app part of path
2066                                         while (split > fs_basedir && *split != '/')
2067                                                 split--;
2068                                         *split = 0;
2069                                 }
2070                         }
2071                 }
2072 #endif
2073         }
2074
2075         // make sure the appending of a path separator won't create an unterminated string
2076         memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2077         // add a path separator to the end of the basedir if it lacks one
2078         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2079                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
2080
2081         // Add the personal game directory
2082         if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
2083                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
2084         else if (COM_CheckParm("-nohome"))
2085                 *fs_userdir = 0; // user wants roaming installation, no userdir
2086         else
2087         {
2088                 int dirmode;
2089                 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2090                 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2091                 int userdirstatus[USERDIRMODE_COUNT];
2092 #ifdef WIN32
2093                 // historical behavior...
2094                 if (!strcmp(gamedirname1, "id1"))
2095                         preferreduserdirmode = USERDIRMODE_NOHOME;
2096 #endif
2097                 // check what limitations the user wants to impose
2098                 if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2099                 if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2100                 if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2101                 // gather the status of the possible userdirs
2102                 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2103                 {
2104                         userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2105                         if (userdirstatus[dirmode] == 1)
2106                                 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2107                         else if (userdirstatus[dirmode] == 0)
2108                                 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2109                         else
2110                                 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2111                 }
2112                 // some games may prefer writing to basedir, but if write fails we
2113                 // have to search for a real userdir...
2114                 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2115                         preferreduserdirmode = highestuserdirmode;
2116                 // check for an existing userdir and continue using it if possible...
2117                 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2118                         if (userdirstatus[dirmode] == 1)
2119                                 break;
2120                 // if no existing userdir found, make a new one...
2121                 if (dirmode == 0 && preferreduserdirmode > 0)
2122                         for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2123                                 if (userdirstatus[dirmode] >= 0)
2124                                         break;
2125                 // and finally, we picked one...
2126                 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2127                 Con_DPrintf("userdir %i is the winner\n", dirmode);
2128         }
2129
2130         // if userdir equal to basedir, clear it to avoid confusion later
2131         if (!strcmp(fs_basedir, fs_userdir))
2132                 fs_userdir[0] = 0;
2133
2134         FS_ListGameDirs();
2135
2136         p = FS_CheckGameDir(gamedirname1);
2137         if(!p || p == fs_checkgamedir_missing)
2138                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2139
2140         if(gamedirname2)
2141         {
2142                 p = FS_CheckGameDir(gamedirname2);
2143                 if(!p || p == fs_checkgamedir_missing)
2144                         Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2145         }
2146
2147         // -game <gamedir>
2148         // Adds basedir/gamedir as an override game
2149         // LordHavoc: now supports multiple -game directories
2150         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2151         {
2152                 if (!com_argv[i])
2153                         continue;
2154                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
2155                 {
2156                         i++;
2157                         p = FS_CheckGameDir(com_argv[i]);
2158                         if(!p)
2159                                 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
2160                         if(p == fs_checkgamedir_missing)
2161                                 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
2162                         // add the gamedir to the list of active gamedirs
2163                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2164                         fs_numgamedirs++;
2165                 }
2166         }
2167
2168         // generate the searchpath
2169         FS_Rescan();
2170
2171         if (Thread_HasThreads())
2172                 fs_mutex = Thread_CreateMutex();
2173 }
2174
2175 void FS_Init_Commands(void)
2176 {
2177         Cvar_RegisterVariable (&scr_screenshot_name);
2178         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2179         Cvar_RegisterVariable (&cvar_fs_gamedir);
2180
2181         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2182         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2183         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
2184         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2185         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2186         Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2187 }
2188
2189 /*
2190 ================
2191 FS_Shutdown
2192 ================
2193 */
2194 void FS_Shutdown (void)
2195 {
2196         // close all pack files and such
2197         // (hopefully there aren't any other open files, but they'll be cleaned up
2198         //  by the OS anyway)
2199         FS_ClearSearchPath();
2200         Mem_FreePool (&fs_mempool);
2201         PK3_CloseLibrary ();
2202
2203 #ifdef WIN32
2204         Sys_UnloadLibrary (&shfolder_dll);
2205         Sys_UnloadLibrary (&shell32_dll);
2206         Sys_UnloadLibrary (&ole32_dll);
2207 #endif
2208
2209         if (fs_mutex)
2210                 Thread_DestroyMutex(fs_mutex);
2211 }
2212
2213 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking)
2214 {
2215         filedesc_t handle = FILEDESC_INVALID;
2216         int mod, opt;
2217         unsigned int ind;
2218         qboolean dolock = false;
2219
2220         // Parse the mode string
2221         switch (mode[0])
2222         {
2223                 case 'r':
2224                         mod = O_RDONLY;
2225                         opt = 0;
2226                         break;
2227                 case 'w':
2228                         mod = O_WRONLY;
2229                         opt = O_CREAT | O_TRUNC;
2230                         break;
2231                 case 'a':
2232                         mod = O_WRONLY;
2233                         opt = O_CREAT | O_APPEND;
2234                         break;
2235                 default:
2236                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2237                         return FILEDESC_INVALID;
2238         }
2239         for (ind = 1; mode[ind] != '\0'; ind++)
2240         {
2241                 switch (mode[ind])
2242                 {
2243                         case '+':
2244                                 mod = O_RDWR;
2245                                 break;
2246                         case 'b':
2247                                 opt |= O_BINARY;
2248                                 break;
2249                         case 'l':
2250                                 dolock = true;
2251                                 break;
2252                         default:
2253                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2254                                                         filepath, mode, mode[ind]);
2255                 }
2256         }
2257
2258         if (nonblocking)
2259                 opt |= O_NONBLOCK;
2260
2261         if(COM_CheckParm("-readonly") && mod != O_RDONLY)
2262                 return FILEDESC_INVALID;
2263
2264 #if USE_RWOPS
2265         if (dolock)
2266                 return FILEDESC_INVALID;
2267         handle = SDL_RWFromFile(filepath, mode);
2268 #else
2269 # ifdef WIN32
2270 #  if _MSC_VER >= 1400
2271         _sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2272 #  else
2273         handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2274 #  endif
2275 # else
2276         handle = open (filepath, mod | opt, 0666);
2277         if(handle >= 0 && dolock)
2278         {
2279                 struct flock l;
2280                 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2281                 l.l_whence = SEEK_SET;
2282                 l.l_start = 0;
2283                 l.l_len = 0;
2284                 if(fcntl(handle, F_SETLK, &l) == -1)
2285                 {
2286                         FILEDESC_CLOSE(handle);
2287                         handle = -1;
2288                 }
2289         }
2290 # endif
2291 #endif
2292
2293         return handle;
2294 }
2295
2296 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
2297 {
2298 #ifdef USE_RWOPS
2299         return -1;
2300 #else
2301         return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2302 #endif
2303 }
2304
2305 /*
2306 ====================
2307 FS_SysOpen
2308
2309 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2310 ====================
2311 */
2312 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
2313 {
2314         qfile_t* file;
2315
2316         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2317         file->ungetc = EOF;
2318         file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2319         if (!FILEDESC_ISVALID(file->handle))
2320         {
2321                 Mem_Free (file);
2322                 return NULL;
2323         }
2324
2325         file->filename = Mem_strdup(fs_mempool, filepath);
2326
2327         file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2328
2329         // For files opened in append mode, we start at the end of the file
2330         if (mode[0] == 'a')
2331                 file->position = file->real_length;
2332         else
2333                 FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2334
2335         return file;
2336 }
2337
2338
2339 /*
2340 ===========
2341 FS_OpenPackedFile
2342
2343 Open a packed file using its package file descriptor
2344 ===========
2345 */
2346 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2347 {
2348         packfile_t *pfile;
2349         filedesc_t dup_handle;
2350         qfile_t* file;
2351
2352         pfile = &pack->files[pack_ind];
2353
2354         // If we don't have the true offset, get it now
2355         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2356                 if (!PK3_GetTrueFileOffset (pfile, pack))
2357                         return NULL;
2358
2359 #ifndef LINK_TO_ZLIB
2360         // No Zlib DLL = no compressed files
2361         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2362         {
2363                 Con_Printf("WARNING: can't open the compressed file %s\n"
2364                                         "You need the Zlib DLL to use compressed files\n",
2365                                         pfile->name);
2366                 return NULL;
2367         }
2368 #endif
2369
2370         // LordHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2371         // the dup() call to avoid having to close the dup_handle on error here
2372         if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2373         {
2374                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2375                                         pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2376                 return NULL;
2377         }
2378
2379         dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2380         if (!FILEDESC_ISVALID(dup_handle))
2381         {
2382                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2383                 return NULL;
2384         }
2385
2386         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2387         memset (file, 0, sizeof (*file));
2388         file->handle = dup_handle;
2389         file->flags = QFILE_FLAG_PACKED;
2390         file->real_length = pfile->realsize;
2391         file->offset = pfile->offset;
2392         file->position = 0;
2393         file->ungetc = EOF;
2394
2395         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2396         {
2397                 ztoolkit_t *ztk;
2398
2399                 file->flags |= QFILE_FLAG_DEFLATED;
2400
2401                 // We need some more variables
2402                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2403
2404                 ztk->comp_length = pfile->packsize;
2405
2406                 // Initialize zlib stream
2407                 ztk->zstream.next_in = ztk->input;
2408                 ztk->zstream.avail_in = 0;
2409
2410                 /* From Zlib's "unzip.c":
2411                  *
2412                  * windowBits is passed < 0 to tell that there is no zlib header.
2413                  * Note that in this case inflate *requires* an extra "dummy" byte
2414                  * after the compressed stream in order to complete decompression and
2415                  * return Z_STREAM_END.
2416                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2417                  * size of both compressed and uncompressed data
2418                  */
2419                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2420                 {
2421                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2422                         FILEDESC_CLOSE(dup_handle);
2423                         Mem_Free(file);
2424                         return NULL;
2425                 }
2426
2427                 ztk->zstream.next_out = file->buff;
2428                 ztk->zstream.avail_out = sizeof (file->buff);
2429
2430                 file->ztk = ztk;
2431         }
2432
2433         return file;
2434 }
2435
2436 /*
2437 ====================
2438 FS_CheckNastyPath
2439
2440 Return true if the path should be rejected due to one of the following:
2441 1: path elements that are non-portable
2442 2: path elements that would allow access to files outside the game directory,
2443    or are just not a good idea for a mod to be using.
2444 ====================
2445 */
2446 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2447 {
2448         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2449         if (!path[0])
2450                 return 2;
2451
2452         // Windows: don't allow \ in filenames (windows-only), period.
2453         // (on Windows \ is a directory separator, but / is also supported)
2454         if (strstr(path, "\\"))
2455                 return 1; // non-portable
2456
2457         // Mac: don't allow Mac-only filenames - : is a directory separator
2458         // instead of /, but we rely on / working already, so there's no reason to
2459         // support a Mac-only path
2460         // Amiga and Windows: : tries to go to root of drive
2461         if (strstr(path, ":"))
2462                 return 1; // non-portable attempt to go to root of drive
2463
2464         // Amiga: // is parent directory
2465         if (strstr(path, "//"))
2466                 return 1; // non-portable attempt to go to parent directory
2467
2468         // all: don't allow going to parent directory (../ or /../)
2469         if (strstr(path, ".."))
2470                 return 2; // attempt to go outside the game directory
2471
2472         // Windows and UNIXes: don't allow absolute paths
2473         if (path[0] == '/')
2474                 return 2; // attempt to go outside the game directory
2475
2476         // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2477         if (strstr(path, "./"))
2478                 return 2; // possible attempt to go outside the game directory
2479
2480         // all: forbid trailing slash on gamedir
2481         if (isgamedir && path[strlen(path)-1] == '/')
2482                 return 2;
2483
2484         // all: forbid leading dot on any filename for any reason
2485         if (strstr(path, "/."))
2486                 return 2; // attempt to go outside the game directory
2487
2488         // after all these checks we're pretty sure it's a / separated filename
2489         // and won't do much if any harm
2490         return false;
2491 }
2492
2493
2494 /*
2495 ====================
2496 FS_FindFile
2497
2498 Look for a file in the packages and in the filesystem
2499
2500 Return the searchpath where the file was found (or NULL)
2501 and the file index in the package if relevant
2502 ====================
2503 */
2504 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2505 {
2506         searchpath_t *search;
2507         pack_t *pak;
2508
2509         // search through the path, one element at a time
2510         for (search = fs_searchpaths;search;search = search->next)
2511         {
2512                 // is the element a pak file?
2513                 if (search->pack && !search->pack->vpack)
2514                 {
2515                         int (*strcmp_funct) (const char* str1, const char* str2);
2516                         int left, right, middle;
2517
2518                         pak = search->pack;
2519                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2520
2521                         // Look for the file (binary search)
2522                         left = 0;
2523                         right = pak->numfiles - 1;
2524                         while (left <= right)
2525                         {
2526                                 int diff;
2527
2528                                 middle = (left + right) / 2;
2529                                 diff = strcmp_funct (pak->files[middle].name, name);
2530
2531                                 // Found it
2532                                 if (!diff)
2533                                 {
2534                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2535                                         {
2536                                                 // yes, but the first one is empty so we treat it as not being there
2537                                                 if (!quiet && developer_extra.integer)
2538                                                         Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2539
2540                                                 if (index != NULL)
2541                                                         *index = -1;
2542                                                 return NULL;
2543                                         }
2544
2545                                         if (!quiet && developer_extra.integer)
2546                                                 Con_DPrintf("FS_FindFile: %s in %s\n",
2547                                                                         pak->files[middle].name, pak->filename);
2548
2549                                         if (index != NULL)
2550                                                 *index = middle;
2551                                         return search;
2552                                 }
2553
2554                                 // If we're too far in the list
2555                                 if (diff > 0)
2556                                         right = middle - 1;
2557                                 else
2558                                         left = middle + 1;
2559                         }
2560                 }
2561                 else
2562                 {
2563                         char netpath[MAX_OSPATH];
2564                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2565                         if (FS_SysFileExists (netpath))
2566                         {
2567                                 if (!quiet && developer_extra.integer)
2568                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
2569
2570                                 if (index != NULL)
2571                                         *index = -1;
2572                                 return search;
2573                         }
2574                 }
2575         }
2576
2577         if (!quiet && developer_extra.integer)
2578                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2579
2580         if (index != NULL)
2581                 *index = -1;
2582         return NULL;
2583 }
2584
2585
2586 /*
2587 ===========
2588 FS_OpenReadFile
2589
2590 Look for a file in the search paths and open it in read-only mode
2591 ===========
2592 */
2593 static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2594 {
2595         searchpath_t *search;
2596         int pack_ind;
2597
2598         search = FS_FindFile (filename, &pack_ind, quiet);
2599
2600         // Not found?
2601         if (search == NULL)
2602                 return NULL;
2603
2604         // Found in the filesystem?
2605         if (pack_ind < 0)
2606         {
2607                 // this works with vpacks, so we are fine
2608                 char path [MAX_OSPATH];
2609                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2610                 return FS_SysOpen (path, "rb", nonblocking);
2611         }
2612
2613         // So, we found it in a package...
2614
2615         // Is it a PK3 symlink?
2616         // TODO also handle directory symlinks by parsing the whole structure...
2617         // but heck, file symlinks are good enough for now
2618         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2619         {
2620                 if(symlinkLevels <= 0)
2621                 {
2622                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2623                         return NULL;
2624                 }
2625                 else
2626                 {
2627                         char linkbuf[MAX_QPATH];
2628                         fs_offset_t count;
2629                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2630                         const char *mergeslash;
2631                         char *mergestart;
2632
2633                         if(!linkfile)
2634                                 return NULL;
2635                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2636                         FS_Close(linkfile);
2637                         if(count < 0)
2638                                 return NULL;
2639                         linkbuf[count] = 0;
2640                         
2641                         // Now combine the paths...
2642                         mergeslash = strrchr(filename, '/');
2643                         mergestart = linkbuf;
2644                         if(!mergeslash)
2645                                 mergeslash = filename;
2646                         while(!strncmp(mergestart, "../", 3))
2647                         {
2648                                 mergestart += 3;
2649                                 while(mergeslash > filename)
2650                                 {
2651                                         --mergeslash;
2652                                         if(*mergeslash == '/')
2653                                                 break;
2654                                 }
2655                         }
2656                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2657                         if(mergeslash == filename)
2658                         {
2659                                 // Either mergeslash == filename, then we just replace the name (done below)
2660                         }
2661                         else
2662                         {
2663                                 // Or, we append the name after mergeslash;
2664                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2665                                 int spaceNeeded = mergeslash - filename + 1;
2666                                 int spaceRemoved = mergestart - linkbuf;
2667                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2668                                 {
2669                                         Con_DPrintf("symlink: too long path rejected\n");
2670                                         return NULL;
2671                                 }
2672                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2673                                 memcpy(linkbuf, filename, spaceNeeded);
2674                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2675                                 mergestart = linkbuf;
2676                         }
2677                         if (!quiet && developer_loading.integer)
2678                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2679                         if(FS_CheckNastyPath (mergestart, false))
2680                         {
2681                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2682                                 return NULL;
2683                         }
2684                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2685                 }
2686         }
2687
2688         return FS_OpenPackedFile (search->pack, pack_ind);
2689 }
2690
2691
2692 /*
2693 =============================================================================
2694
2695 MAIN PUBLIC FUNCTIONS
2696
2697 =============================================================================
2698 */
2699
2700 /*
2701 ====================
2702 FS_OpenRealFile
2703
2704 Open a file in the userpath. The syntax is the same as fopen
2705 Used for savegame scanning in menu, and all file writing.
2706 ====================
2707 */
2708 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2709 {
2710         char real_path [MAX_OSPATH];
2711
2712         if (FS_CheckNastyPath(filepath, false))
2713         {
2714                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2715                 return NULL;
2716         }
2717
2718         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2719
2720         // If the file is opened in "write", "append", or "read/write" mode,
2721         // create directories up to the file.
2722         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2723                 FS_CreatePath (real_path);
2724         return FS_SysOpen (real_path, mode, false);
2725 }
2726
2727
2728 /*
2729 ====================
2730 FS_OpenVirtualFile
2731
2732 Open a file. The syntax is the same as fopen
2733 ====================
2734 */
2735 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2736 {
2737         qfile_t *result = NULL;
2738         if (FS_CheckNastyPath(filepath, false))
2739         {
2740                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2741                 return NULL;
2742         }
2743
2744         if (fs_mutex) Thread_LockMutex(fs_mutex);
2745         result = FS_OpenReadFile (filepath, quiet, false, 16);
2746         if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2747         return result;
2748 }
2749
2750
2751 /*
2752 ====================
2753 FS_FileFromData
2754
2755 Open a file. The syntax is the same as fopen
2756 ====================
2757 */
2758 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2759 {
2760         qfile_t* file;
2761         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2762         memset (file, 0, sizeof (*file));
2763         file->flags = QFILE_FLAG_DATA;
2764         file->ungetc = EOF;
2765         file->real_length = size;
2766         file->data = data;
2767         return file;
2768 }
2769
2770 /*
2771 ====================
2772 FS_Close
2773
2774 Close a file
2775 ====================
2776 */
2777 int FS_Close (qfile_t* file)
2778 {
2779         if(file->flags & QFILE_FLAG_DATA)
2780         {
2781                 Mem_Free(file);
2782                 return 0;
2783         }
2784
2785         if (FILEDESC_CLOSE (file->handle))
2786                 return EOF;
2787
2788         if (file->filename)
2789         {
2790                 if (file->flags & QFILE_FLAG_REMOVE)
2791                 {
2792                         if (remove(file->filename) == -1)
2793                         {
2794                                 // No need to report this. If removing a just
2795                                 // written file failed, this most likely means
2796                                 // someone else deleted it first - which we
2797                                 // like.
2798                         }
2799                 }
2800
2801                 Mem_Free((void *) file->filename);
2802         }
2803
2804         if (file->ztk)
2805         {
2806                 qz_inflateEnd (&file->ztk->zstream);
2807                 Mem_Free (file->ztk);
2808         }
2809
2810         Mem_Free (file);
2811         return 0;
2812 }
2813
2814 void FS_RemoveOnClose(qfile_t* file)
2815 {
2816         file->flags |= QFILE_FLAG_REMOVE;
2817 }
2818
2819 /*
2820 ====================
2821 FS_Write
2822
2823 Write "datasize" bytes into a file
2824 ====================
2825 */
2826 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2827 {
2828         fs_offset_t written = 0;
2829
2830         // If necessary, seek to the exact file position we're supposed to be
2831         if (file->buff_ind != file->buff_len)
2832         {
2833                 if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
2834                 {
2835                         Con_Printf("WARNING: could not seek in %s.\n", file->filename);
2836                 }
2837         }
2838
2839         // Purge cached data
2840         FS_Purge (file);
2841
2842         // Write the buffer and update the position
2843         // LordHavoc: to hush a warning about passing size_t to an unsigned int parameter on Win64 we do this as multiple writes if the size would be too big for an integer (we never write that big in one go, but it's a theory)
2844         while (written < (fs_offset_t)datasize)
2845         {
2846                 // figure out how much to write in one chunk
2847                 fs_offset_t maxchunk = 1<<30; // 1 GiB
2848                 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
2849                 int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
2850                 // if at least some was written, add it to our accumulator
2851                 if (result > 0)
2852                         written += result;
2853                 // if the result is not what we expected, consider the write to be incomplete
2854                 if (result != chunk)
2855                         break;
2856         }
2857         file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
2858         if (file->real_length < file->position)
2859                 file->real_length = file->position;
2860
2861         // note that this will never be less than 0 even if the write failed
2862         return written;
2863 }
2864
2865
2866 /*
2867 ====================
2868 FS_Read
2869
2870 Read up to "buffersize" bytes from a file
2871 ====================
2872 */
2873 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2874 {
2875         fs_offset_t count, done;
2876
2877         if (buffersize == 0)
2878                 return 0;
2879
2880         // Get rid of the ungetc character
2881         if (file->ungetc != EOF)
2882         {
2883                 ((char*)buffer)[0] = file->ungetc;
2884                 buffersize--;
2885                 file->ungetc = EOF;
2886                 done = 1;
2887         }
2888         else
2889                 done = 0;
2890
2891         if(file->flags & QFILE_FLAG_DATA)
2892         {
2893                 size_t left = file->real_length - file->position;
2894                 if(buffersize > left)
2895                         buffersize = left;
2896                 memcpy(buffer, file->data + file->position, buffersize);
2897                 file->position += buffersize;
2898                 return buffersize;
2899         }
2900
2901         // First, we copy as many bytes as we can from "buff"
2902         if (file->buff_ind < file->buff_len)
2903         {
2904                 count = file->buff_len - file->buff_ind;
2905                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2906                 done += count;
2907                 memcpy (buffer, &file->buff[file->buff_ind], count);
2908                 file->buff_ind += count;
2909
2910                 buffersize -= count;
2911                 if (buffersize == 0)
2912                         return done;
2913         }
2914
2915         // NOTE: at this point, the read buffer is always empty
2916
2917         // If the file isn't compressed
2918         if (! (file->flags & QFILE_FLAG_DEFLATED))
2919         {
2920                 fs_offset_t nb;
2921
2922                 // We must take care to not read after the end of the file
2923                 count = file->real_length - file->position;
2924
2925                 // If we have a lot of data to get, put them directly into "buffer"
2926                 if (buffersize > sizeof (file->buff) / 2)
2927                 {
2928                         if (count > (fs_offset_t)buffersize)
2929                                 count = (fs_offset_t)buffersize;
2930                         if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
2931                         {
2932                                 // Seek failed. When reading from a pipe, and
2933                                 // the caller never called FS_Seek, this still
2934                                 // works fine.  So no reporting this error.
2935                         }
2936                         nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
2937                         if (nb > 0)
2938                         {
2939                                 done += nb;
2940                                 file->position += nb;
2941
2942                                 // Purge cached data
2943                                 FS_Purge (file);
2944                         }
2945                 }
2946                 else
2947                 {
2948                         if (count > (fs_offset_t)sizeof (file->buff))
2949                                 count = (fs_offset_t)sizeof (file->buff);
2950                         if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
2951                         {
2952                                 // Seek failed. When reading from a pipe, and
2953                                 // the caller never called FS_Seek, this still
2954                                 // works fine.  So no reporting this error.
2955                         }
2956                         nb = FILEDESC_READ (file->handle, file->buff, count);
2957                         if (nb > 0)
2958                         {
2959                                 file->buff_len = nb;
2960                                 file->position += nb;
2961
2962                                 // Copy the requested data in "buffer" (as much as we can)
2963                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2964                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2965                                 file->buff_ind = count;
2966                                 done += count;
2967                         }
2968                 }
2969
2970                 return done;
2971         }
2972
2973         // If the file is compressed, it's more complicated...
2974         // We cycle through a few operations until we have read enough data
2975         while (buffersize > 0)
2976         {
2977                 ztoolkit_t *ztk = file->ztk;
2978                 int error;
2979
2980                 // NOTE: at this point, the read buffer is always empty
2981
2982                 // If "input" is also empty, we need to refill it
2983                 if (ztk->in_ind == ztk->in_len)
2984                 {
2985                         // If we are at the end of the file
2986                         if (file->position == file->real_length)
2987                                 return done;
2988
2989                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2990                         if (count > (fs_offset_t)sizeof (ztk->input))
2991                                 count = (fs_offset_t)sizeof (ztk->input);
2992                         FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2993                         if (FILEDESC_READ (file->handle, ztk->input, count) != count)
2994                         {
2995                                 Con_Printf ("FS_Read: unexpected end of file\n");
2996                                 break;
2997                         }
2998
2999                         ztk->in_ind = 0;
3000                         ztk->in_len = count;
3001                         ztk->in_position += count;
3002                 }
3003
3004                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3005                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3006
3007                 // Now that we are sure we have compressed data available, we need to determine
3008                 // if it's better to inflate it in "file->buff" or directly in "buffer"
3009
3010                 // Inflate the data in "file->buff"
3011                 if (buffersize < sizeof (file->buff) / 2)
3012                 {
3013                         ztk->zstream.next_out = file->buff;
3014                         ztk->zstream.avail_out = sizeof (file->buff);
3015                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3016                         if (error != Z_OK && error != Z_STREAM_END)
3017                         {
3018                                 Con_Printf ("FS_Read: Can't inflate file\n");
3019                                 break;
3020                         }
3021                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3022
3023                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3024                         file->position += file->buff_len;
3025
3026                         // Copy the requested data in "buffer" (as much as we can)
3027                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3028                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3029                         file->buff_ind = count;
3030                 }
3031
3032                 // Else, we inflate directly in "buffer"
3033                 else
3034                 {
3035                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3036                         ztk->zstream.avail_out = (unsigned int)buffersize;
3037                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3038                         if (error != Z_OK && error != Z_STREAM_END)
3039                         {
3040                                 Con_Printf ("FS_Read: Can't inflate file\n");
3041                                 break;
3042                         }
3043                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3044
3045                         // How much data did it inflate?
3046                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3047                         file->position += count;
3048
3049                         // Purge cached data
3050                         FS_Purge (file);
3051                 }
3052
3053                 done += count;
3054                 buffersize -= count;
3055         }
3056
3057         return done;
3058 }
3059
3060
3061 /*
3062 ====================
3063 FS_Print
3064
3065 Print a string into a file
3066 ====================
3067 */
3068 int FS_Print (qfile_t* file, const char *msg)
3069 {
3070         return (int)FS_Write (file, msg, strlen (msg));
3071 }
3072
3073 /*
3074 ====================
3075 FS_Printf
3076
3077 Print a string into a file
3078 ====================
3079 */
3080 int FS_Printf(qfile_t* file, const char* format, ...)
3081 {
3082         int result;
3083         va_list args;
3084
3085         va_start (args, format);
3086         result = FS_VPrintf (file, format, args);
3087         va_end (args);
3088
3089         return result;
3090 }
3091
3092
3093 /*
3094 ====================
3095 FS_VPrintf
3096
3097 Print a string into a file
3098 ====================
3099 */
3100 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3101 {
3102         int len;
3103         fs_offset_t buff_size = MAX_INPUTLINE;
3104         char *tempbuff;
3105
3106         for (;;)
3107         {
3108                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3109                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3110                 if (len >= 0 && len < buff_size)
3111                         break;
3112                 Mem_Free (tempbuff);
3113                 buff_size *= 2;
3114         }
3115
3116         len = FILEDESC_WRITE (file->handle, tempbuff, len);
3117         Mem_Free (tempbuff);
3118
3119         return len;
3120 }
3121
3122
3123 /*
3124 ====================
3125 FS_Getc
3126
3127 Get the next character of a file
3128 ====================
3129 */
3130 int FS_Getc (qfile_t* file)
3131 {
3132         unsigned char c;
3133
3134         if (FS_Read (file, &c, 1) != 1)
3135                 return EOF;
3136
3137         return c;
3138 }
3139
3140
3141 /*
3142 ====================
3143 FS_UnGetc
3144
3145 Put a character back into the read buffer (only supports one character!)
3146 ====================
3147 */
3148 int FS_UnGetc (qfile_t* file, unsigned char c)
3149 {
3150         // If there's already a character waiting to be read
3151         if (file->ungetc != EOF)
3152                 return EOF;
3153
3154         file->ungetc = c;
3155         return c;
3156 }
3157
3158
3159 /*
3160 ====================
3161 FS_Seek
3162
3163 Move the position index in a file
3164 ====================
3165 */
3166 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3167 {
3168         ztoolkit_t *ztk;
3169         unsigned char* buffer;
3170         fs_offset_t buffersize;
3171
3172         // Compute the file offset
3173         switch (whence)
3174         {
3175                 case SEEK_CUR:
3176                         offset += file->position - file->buff_len + file->buff_ind;
3177                         break;
3178
3179                 case SEEK_SET:
3180                         break;
3181
3182                 case SEEK_END:
3183                         offset += file->real_length;
3184                         break;
3185
3186                 default:
3187                         return -1;
3188         }
3189         if (offset < 0 || offset > file->real_length)
3190                 return -1;
3191
3192         if(file->flags & QFILE_FLAG_DATA)
3193         {
3194                 file->position = offset;
3195                 return 0;
3196         }
3197
3198         // If we have the data in our read buffer, we don't need to actually seek
3199         if (file->position - file->buff_len <= offset && offset <= file->position)
3200         {
3201                 file->buff_ind = offset + file->buff_len - file->position;
3202                 return 0;
3203         }
3204
3205         // Purge cached data
3206         FS_Purge (file);
3207
3208         // Unpacked or uncompressed files can seek directly
3209         if (! (file->flags & QFILE_FLAG_DEFLATED))
3210         {
3211                 if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3212                         return -1;
3213                 file->position = offset;
3214                 return 0;
3215         }
3216
3217         // Seeking in compressed files is more a hack than anything else,
3218         // but we need to support it, so here we go.
3219         ztk = file->ztk;
3220
3221         // If we have to go back in the file, we need to restart from the beginning
3222         if (offset <= file->position)
3223         {
3224                 ztk->in_ind = 0;
3225                 ztk->in_len = 0;
3226                 ztk->in_position = 0;
3227                 file->position = 0;
3228                 if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3229                         Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3230
3231                 // Reset the Zlib stream
3232                 ztk->zstream.next_in = ztk->input;
3233                 ztk->zstream.avail_in = 0;
3234                 qz_inflateReset (&ztk->zstream);
3235         }
3236
3237         // We need a big buffer to force inflating into it directly
3238         buffersize = 2 * sizeof (file->buff);
3239         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3240
3241         // Skip all data until we reach the requested offset
3242         while (offset > file->position)
3243         {
3244                 fs_offset_t diff = offset - file->position;
3245                 fs_offset_t count, len;
3246
3247                 count = (diff > buffersize) ? buffersize : diff;
3248                 len = FS_Read (file, buffer, count);
3249                 if (len != count)
3250                 {
3251                         Mem_Free (buffer);
3252                         return -1;
3253                 }
3254         }
3255
3256         Mem_Free (buffer);
3257         return 0;
3258 }
3259
3260
3261 /*
3262 ====================
3263 FS_Tell
3264
3265 Give the current position in a file
3266 ====================
3267 */
3268 fs_offset_t FS_Tell (qfile_t* file)
3269 {
3270         return file->position - file->buff_len + file->buff_ind;
3271 }
3272
3273
3274 /*
3275 ====================
3276 FS_FileSize
3277
3278 Give the total size of a file
3279 ====================
3280 */
3281 fs_offset_t FS_FileSize (qfile_t* file)
3282 {
3283         return file->real_length;
3284 }
3285
3286
3287 /*
3288 ====================
3289 FS_Purge
3290
3291 Erases any buffered input or output data
3292 ====================
3293 */
3294 void FS_Purge (qfile_t* file)
3295 {
3296         file->buff_len = 0;
3297         file->buff_ind = 0;
3298         file->ungetc = EOF;
3299 }
3300
3301
3302 /*
3303 ============
3304 FS_LoadAndCloseQFile
3305
3306 Loads full content of a qfile_t and closes it.
3307 Always appends a 0 byte.
3308 ============
3309 */
3310 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3311 {
3312         unsigned char *buf = NULL;
3313         fs_offset_t filesize = 0;
3314
3315         if (file)
3316         {
3317                 filesize = file->real_length;
3318                 if(filesize < 0)
3319                 {
3320                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3321                         FS_Close(file);
3322                         return NULL;
3323                 }
3324
3325                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3326                 buf[filesize] = '\0';
3327                 FS_Read (file, buf, filesize);
3328                 FS_Close (file);
3329                 if (developer_loadfile.integer)
3330                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3331         }
3332
3333         if (filesizepointer)
3334                 *filesizepointer = filesize;
3335         return buf;
3336 }
3337
3338
3339 /*
3340 ============
3341 FS_LoadFile
3342
3343 Filename are relative to the quake directory.
3344 Always appends a 0 byte.
3345 ============
3346 */
3347 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3348 {
3349         qfile_t *file = FS_OpenVirtualFile(path, quiet);
3350         return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3351 }
3352
3353
3354 /*
3355 ============
3356 FS_SysLoadFile
3357
3358 Filename are OS paths.
3359 Always appends a 0 byte.
3360 ============
3361 */
3362 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3363 {
3364         qfile_t *file = FS_SysOpen(path, "rb", false);
3365         return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3366 }
3367
3368
3369 /*
3370 ============
3371 FS_WriteFile
3372
3373 The filename will be prefixed by the current game directory
3374 ============
3375 */
3376 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3377 {
3378         qfile_t *file;
3379         size_t i;
3380         fs_offset_t lentotal;
3381
3382         file = FS_OpenRealFile(filename, "wb", false);
3383         if (!file)
3384         {
3385                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3386                 return false;
3387         }
3388
3389         lentotal = 0;
3390         for(i = 0; i < count; ++i)
3391                 lentotal += len[i];
3392         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3393         for(i = 0; i < count; ++i)
3394                 FS_Write (file, data[i], len[i]);
3395         FS_Close (file);
3396         return true;
3397 }
3398
3399 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3400 {
3401         return FS_WriteFileInBlocks(filename, &data, &len, 1);
3402 }
3403
3404
3405 /*
3406 =============================================================================
3407
3408 OTHERS PUBLIC FUNCTIONS
3409
3410 =============================================================================
3411 */
3412
3413 /*
3414 ============
3415 FS_StripExtension
3416 ============
3417 */
3418 void FS_StripExtension (const char *in, char *out, size_t size_out)
3419 {
3420         char *last = NULL;
3421         char currentchar;
3422
3423         if (size_out == 0)
3424                 return;
3425
3426         while ((currentchar = *in) && size_out > 1)
3427         {
3428                 if (currentchar == '.')
3429                         last = out;
3430                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3431                         last = NULL;
3432                 *out++ = currentchar;
3433                 in++;
3434                 size_out--;
3435         }
3436         if (last)
3437                 *last = 0;
3438         else
3439                 *out = 0;
3440 }
3441
3442
3443 /*
3444 ==================
3445 FS_DefaultExtension
3446 ==================
3447 */
3448 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3449 {
3450         const char *src;
3451
3452         // if path doesn't have a .EXT, append extension
3453         // (extension should include the .)
3454         src = path + strlen(path);
3455
3456         while (*src != '/' && src != path)
3457         {
3458                 if (*src == '.')
3459                         return;                 // it has an extension
3460                 src--;
3461         }
3462
3463         strlcat (path, extension, size_path);
3464 }
3465
3466
3467 /*
3468 ==================
3469 FS_FileType
3470
3471 Look for a file in the packages and in the filesystem
3472 ==================
3473 */
3474 int FS_FileType (const char *filename)
3475 {
3476         searchpath_t *search;
3477         char fullpath[MAX_OSPATH];
3478
3479         search = FS_FindFile (filename, NULL, true);
3480         if(!search)
3481                 return FS_FILETYPE_NONE;
3482
3483         if(search->pack && !search->pack->vpack)
3484                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3485
3486         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3487         return FS_SysFileType(fullpath);
3488 }
3489
3490
3491 /*
3492 ==================
3493 FS_FileExists
3494
3495 Look for a file in the packages and in the filesystem
3496 ==================
3497 */
3498 qboolean FS_FileExists (const char *filename)
3499 {
3500         return (FS_FindFile (filename, NULL, true) != NULL);
3501 }
3502
3503
3504 /*
3505 ==================
3506 FS_SysFileExists
3507
3508 Look for a file in the filesystem only
3509 ==================
3510 */
3511 int FS_SysFileType (const char *path)
3512 {
3513 #if WIN32
3514 // Sajt - some older sdks are missing this define
3515 # ifndef INVALID_FILE_ATTRIBUTES
3516 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3517 # endif
3518
3519         DWORD result = GetFileAttributes(path);
3520
3521         if(result == INVALID_FILE_ATTRIBUTES)
3522                 return FS_FILETYPE_NONE;
3523
3524         if(result & FILE_ATTRIBUTE_DIRECTORY)
3525                 return FS_FILETYPE_DIRECTORY;
3526
3527         return FS_FILETYPE_FILE;
3528 #else
3529         struct stat buf;
3530
3531         if (stat (path,&buf) == -1)
3532                 return FS_FILETYPE_NONE;
3533
3534 #ifndef S_ISDIR
3535 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3536 #endif
3537         if(S_ISDIR(buf.st_mode))
3538                 return FS_FILETYPE_DIRECTORY;
3539
3540         return FS_FILETYPE_FILE;
3541 #endif
3542 }
3543
3544 qboolean FS_SysFileExists (const char *path)
3545 {
3546         return FS_SysFileType (path) != FS_FILETYPE_NONE;
3547 }
3548
3549 /*
3550 ===========
3551 FS_Search
3552
3553 Allocate and fill a search structure with information on matching filenames.
3554 ===========
3555 */
3556 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3557 {
3558         fssearch_t *search;
3559         searchpath_t *searchpath;
3560         pack_t *pak;
3561         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3562         stringlist_t resultlist;
3563         stringlist_t dirlist;
3564         const char *slash, *backslash, *colon, *separator;
3565         char *basepath;
3566         char temp[MAX_OSPATH];
3567
3568         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3569                 ;
3570
3571         if (i > 0)
3572         {
3573                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3574                 return NULL;
3575         }
3576
3577         stringlistinit(&resultlist);
3578         stringlistinit(&dirlist);
3579         search = NULL;
3580         slash = strrchr(pattern, '/');
3581         backslash = strrchr(pattern, '\\');
3582         colon = strrchr(pattern, ':');
3583         separator = max(slash, backslash);
3584         separator = max(separator, colon);
3585         basepathlength = separator ? (separator + 1 - pattern) : 0;
3586         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3587         if (basepathlength)
3588                 memcpy(basepath, pattern, basepathlength);
3589         basepath[basepathlength] = 0;
3590
3591         // search through the path, one element at a time
3592         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3593         {
3594                 // is the element a pak file?
3595                 if (searchpath->pack && !searchpath->pack->vpack)
3596                 {
3597                         // look through all the pak file elements
3598                         pak = searchpath->pack;
3599                         for (i = 0;i < pak->numfiles;i++)
3600                         {
3601                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
3602                                 while (temp[0])
3603                                 {
3604                                         if (matchpattern(temp, (char *)pattern, true))
3605                                         {
3606                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3607                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
3608                                                                 break;
3609                                                 if (resultlistindex == resultlist.numstrings)
3610                                                 {
3611                                                         stringlistappend(&resultlist, temp);
3612                                                         if (!quiet && developer_loading.integer)
3613                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3614                                                 }
3615                                         }
3616                                         // strip off one path element at a time until empty
3617                                         // this way directories are added to the listing if they match the pattern
3618                                         slash = strrchr(temp, '/');
3619                                         backslash = strrchr(temp, '\\');
3620                                         colon = strrchr(temp, ':');
3621                                         separator = temp;
3622                                         if (separator < slash)
3623                                                 separator = slash;
3624                                         if (separator < backslash)
3625                                                 separator = backslash;
3626                                         if (separator < colon)
3627                                                 separator = colon;
3628                                         *((char *)separator) = 0;
3629                                 }
3630                         }
3631                 }
3632                 else
3633                 {
3634                         stringlist_t matchedSet, foundSet;
3635                         const char *start = pattern;
3636
3637                         stringlistinit(&matchedSet);
3638                         stringlistinit(&foundSet);
3639                         // add a first entry to the set
3640                         stringlistappend(&matchedSet, "");
3641                         // iterate through pattern's path
3642                         while (*start)
3643                         {
3644                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3645                                 char subpath[MAX_OSPATH];
3646                                 char subpattern[MAX_OSPATH];
3647
3648                                 // find the next wildcard
3649                                 wildcard = strchr(start, '?');
3650                                 asterisk = strchr(start, '*');
3651                                 if (asterisk && (!wildcard || asterisk < wildcard))
3652                                 {
3653                                         wildcard = asterisk;
3654                                 }
3655
3656                                 if (wildcard)
3657                                 {
3658                                         nextseparator = strchr( wildcard, '/' );
3659                                 }
3660                                 else
3661                                 {
3662                                         nextseparator = NULL;
3663                                 }
3664
3665                                 if( !nextseparator ) {
3666                                         nextseparator = start + strlen( start );
3667                                 }
3668
3669                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3670                                 // copy everything up except nextseperator
3671                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3672                                 // find the last '/' before the wildcard
3673                                 prevseparator = strrchr( subpattern, '/' );
3674                                 if (!prevseparator)
3675                                         prevseparator = subpattern;
3676                                 else
3677                                         prevseparator++;
3678                                 // copy everything from start to the previous including the '/' (before the wildcard)
3679                                 // everything up to start is already included in the path of matchedSet's entries
3680                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3681
3682                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3683                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3684                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3685                                         strlcat( temp, subpath, sizeof(temp) );
3686                                         listdirectory( &foundSet, searchpath->filename, temp );
3687                                 }
3688                                 if( dirlistindex == 0 ) {
3689                                         break;
3690                                 }
3691                                 // reset the current result set
3692                                 stringlistfreecontents( &matchedSet );
3693                                 // match against the pattern
3694                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3695                                         const char *direntry = foundSet.strings[ dirlistindex ];
3696                                         if (matchpattern(direntry, subpattern, true)) {
3697                                                 stringlistappend( &matchedSet, direntry );
3698                                         }
3699                                 }
3700                                 stringlistfreecontents( &foundSet );
3701
3702                                 start = nextseparator;
3703                         }
3704
3705                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3706                         {
3707                                 const char *temp = matchedSet.strings[dirlistindex];
3708                                 if (matchpattern(temp, (char *)pattern, true))
3709                                 {
3710                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3711                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
3712                                                         break;
3713                                         if (resultlistindex == resultlist.numstrings)
3714                                         {
3715                                                 stringlistappend(&resultlist, temp);
3716                                                 if (!quiet && developer_loading.integer)
3717                                                         Con_Printf("SearchDirFile: %s\n", temp);
3718                                         }
3719                                 }
3720                         }
3721                         stringlistfreecontents( &matchedSet );
3722                 }
3723         }
3724
3725         if (resultlist.numstrings)
3726         {
3727                 stringlistsort(&resultlist, true);
3728                 numfiles = resultlist.numstrings;
3729                 numchars = 0;
3730                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3731                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3732                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3733                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3734                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3735                 search->numfilenames = (int)numfiles;
3736                 numfiles = 0;
3737                 numchars = 0;
3738                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3739                 {
3740                         size_t textlen;
3741                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
3742                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3743                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3744                         numfiles++;
3745                         numchars += (int)textlen;
3746                 }
3747         }
3748         stringlistfreecontents(&resultlist);
3749
3750         Mem_Free(basepath);
3751         return search;
3752 }
3753
3754 void FS_FreeSearch(fssearch_t *search)
3755 {
3756         Z_Free(search);
3757 }
3758
3759 extern int con_linewidth;
3760 static int FS_ListDirectory(const char *pattern, int oneperline)
3761 {
3762         int numfiles;
3763         int numcolumns;
3764         int numlines;
3765         int columnwidth;
3766         int linebufpos;
3767         int i, j, k, l;
3768         const char *name;
3769         char linebuf[MAX_INPUTLINE];
3770         fssearch_t *search;
3771         search = FS_Search(pattern, true, true);
3772         if (!search)
3773                 return 0;
3774         numfiles = search->numfilenames;
3775         if (!oneperline)
3776         {
3777                 // FIXME: the names could be added to one column list and then
3778                 // gradually shifted into the next column if they fit, and then the
3779                 // next to make a compact variable width listing but it's a lot more
3780                 // complicated...
3781                 // find width for columns
3782                 columnwidth = 0;
3783                 for (i = 0;i < numfiles;i++)
3784                 {
3785                         l = (int)strlen(search->filenames[i]);
3786                         if (columnwidth < l)
3787                                 columnwidth = l;
3788                 }
3789                 // count the spacing character
3790                 columnwidth++;
3791                 // calculate number of columns
3792                 numcolumns = con_linewidth / columnwidth;
3793                 // don't bother with the column printing if it's only one column
3794                 if (numcolumns >= 2)
3795                 {
3796                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3797                         for (i = 0;i < numlines;i++)
3798                         {
3799                                 linebufpos = 0;
3800                                 for (k = 0;k < numcolumns;k++)
3801                                 {
3802                                         l = i * numcolumns + k;
3803                                         if (l < numfiles)
3804                                         {
3805                                                 name = search->filenames[l];
3806                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3807                                                         linebuf[linebufpos++] = name[j];
3808                                                 // space out name unless it's the last on the line
3809                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
3810                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3811                                                                 linebuf[linebufpos++] = ' ';
3812                                         }
3813                                 }
3814                                 linebuf[linebufpos] = 0;
3815                                 Con_Printf("%s\n", linebuf);
3816                         }
3817                 }
3818                 else
3819                         oneperline = true;
3820         }
3821         if (oneperline)
3822                 for (i = 0;i < numfiles;i++)
3823                         Con_Printf("%s\n", search->filenames[i]);
3824         FS_FreeSearch(search);
3825         return (int)numfiles;
3826 }
3827
3828 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3829 {
3830         const char *pattern;
3831         if (Cmd_Argc() >= 3)
3832         {
3833                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3834                 return;
3835         }
3836         if (Cmd_Argc() == 2)
3837                 pattern = Cmd_Argv(1);
3838         else
3839                 pattern = "*";
3840         if (!FS_ListDirectory(pattern, oneperline))
3841                 Con_Print("No files found.\n");
3842 }
3843
3844 void FS_Dir_f(void)
3845 {
3846         FS_ListDirectoryCmd("dir", true);
3847 }
3848
3849 void FS_Ls_f(void)
3850 {
3851         FS_ListDirectoryCmd("ls", false);
3852 }
3853
3854 void FS_Which_f(void)
3855 {
3856         const char *filename;
3857         int index;
3858         searchpath_t *sp;
3859         if (Cmd_Argc() != 2)
3860         {
3861                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3862                 return;
3863         }  
3864         filename = Cmd_Argv(1);
3865         sp = FS_FindFile(filename, &index, true);
3866         if (!sp) {
3867                 Con_Printf("%s isn't anywhere\n", filename);
3868                 return;
3869         }
3870         if (sp->pack)
3871         {
3872                 if(sp->pack->vpack)
3873                         Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3874                 else
3875                         Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3876         }
3877         else
3878                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3879 }
3880
3881
3882 const char *FS_WhichPack(const char *filename)
3883 {
3884         int index;
3885         searchpath_t *sp = FS_FindFile(filename, &index, true);
3886         if(sp && sp->pack)
3887                 return sp->pack->shortname;
3888         else if(sp)
3889                 return "";
3890         else
3891                 return 0;
3892 }
3893
3894 /*
3895 ====================
3896 FS_IsRegisteredQuakePack
3897
3898 Look for a proof of purchase file file in the requested package
3899
3900 If it is found, this file should NOT be downloaded.
3901 ====================
3902 */
3903 qboolean FS_IsRegisteredQuakePack(const char *name)
3904 {
3905         searchpath_t *search;
3906         pack_t *pak;
3907
3908         // search through the path, one element at a time
3909         for (search = fs_searchpaths;search;search = search->next)
3910         {
3911                 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3912                         // TODO do we want to support vpacks in here too?
3913                 {
3914                         int (*strcmp_funct) (const char* str1, const char* str2);
3915                         int left, right, middle;
3916
3917                         pak = search->pack;
3918                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3919
3920                         // Look for the file (binary search)
3921                         left = 0;
3922                         right = pak->numfiles - 1;
3923                         while (left <= right)
3924                         {
3925                                 int diff;
3926
3927                                 middle = (left + right) / 2;
3928                                 diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3929
3930                                 // Found it
3931                                 if (!diff)
3932                                         return true;
3933
3934                                 // If we're too far in the list
3935                                 if (diff > 0)
3936                                         right = middle - 1;
3937                                 else
3938                                         left = middle + 1;
3939                         }
3940
3941                         // we found the requested pack but it is not registered quake
3942                         return false;
3943                 }
3944         }
3945
3946         return false;
3947 }
3948
3949 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3950 {
3951         int crc = -1;
3952         unsigned char *filedata;
3953         fs_offset_t filesize;
3954         if (filesizepointer)
3955                 *filesizepointer = 0;
3956         if (!filename || !*filename)
3957                 return crc;
3958         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3959         if (filedata)
3960         {
3961                 if (filesizepointer)
3962                         *filesizepointer = filesize;
3963                 crc = CRC_Block(filedata, filesize);
3964                 Mem_Free(filedata);
3965         }
3966         return crc;
3967 }
3968
3969 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3970 {
3971         z_stream strm;
3972         unsigned char *out = NULL;
3973         unsigned char *tmp;
3974
3975         *deflated_size = 0;
3976 #ifndef LINK_TO_ZLIB
3977         if(!zlib_dll)
3978                 return NULL;
3979 #endif
3980
3981         memset(&strm, 0, sizeof(strm));
3982         strm.zalloc = Z_NULL;
3983         strm.zfree = Z_NULL;
3984         strm.opaque = Z_NULL;
3985
3986         if(level < 0)
3987                 level = Z_DEFAULT_COMPRESSION;
3988
3989         if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3990         {
3991                 Con_Printf("FS_Deflate: deflate init error!\n");
3992                 return NULL;
3993         }
3994
3995         strm.next_in = (unsigned char*)data;
3996         strm.avail_in = (unsigned int)size;
3997
3998         tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3999         if(!tmp)
4000         {
4001                 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4002                 qz_deflateEnd(&strm);
4003                 return NULL;
4004         }
4005
4006         strm.next_out = tmp;
4007         strm.avail_out = (unsigned int)size;
4008
4009         if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4010         {
4011                 Con_Printf("FS_Deflate: deflate failed!\n");
4012                 qz_deflateEnd(&strm);
4013                 Mem_Free(tmp);
4014                 return NULL;
4015         }
4016         
4017         if(qz_deflateEnd(&strm) != Z_OK)
4018         {
4019                 Con_Printf("FS_Deflate: deflateEnd failed\n");
4020                 Mem_Free(tmp);
4021                 return NULL;
4022         }
4023
4024         if(strm.total_out >= size)
4025         {
4026                 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4027                 Mem_Free(tmp);
4028                 return NULL;
4029         }
4030
4031         out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4032         if(!out)
4033         {
4034                 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4035                 Mem_Free(tmp);
4036                 return NULL;
4037         }
4038
4039         *deflated_size = (size_t)strm.total_out;
4040
4041         memcpy(out, tmp, strm.total_out);
4042         Mem_Free(tmp);
4043         
4044         return out;
4045 }
4046
4047 static void AssertBufsize(sizebuf_t *buf, int length)
4048 {
4049         if(buf->cursize + length > buf->maxsize)
4050         {
4051                 int oldsize = buf->maxsize;
4052                 unsigned char *olddata;
4053                 olddata = buf->data;
4054                 buf->maxsize += length;
4055                 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4056                 if(olddata)
4057                 {
4058                         memcpy(buf->data, olddata, oldsize);
4059                         Mem_Free(olddata);
4060                 }
4061         }
4062 }
4063
4064 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4065 {
4066         int ret;
4067         z_stream strm;
4068         unsigned char *out = NULL;
4069         unsigned char tmp[2048];
4070         unsigned int have;
4071         sizebuf_t outbuf;
4072
4073         *inflated_size = 0;
4074 #ifndef LINK_TO_ZLIB
4075         if(!zlib_dll)
4076                 return NULL;
4077 #endif
4078
4079         memset(&outbuf, 0, sizeof(outbuf));
4080         outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4081         outbuf.maxsize = sizeof(tmp);
4082
4083         memset(&strm, 0, sizeof(strm));
4084         strm.zalloc = Z_NULL;
4085         strm.zfree = Z_NULL;
4086         strm.opaque = Z_NULL;
4087
4088         if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4089         {
4090                 Con_Printf("FS_Inflate: inflate init error!\n");
4091                 Mem_Free(outbuf.data);
4092                 return NULL;
4093         }
4094
4095         strm.next_in = (unsigned char*)data;
4096         strm.avail_in = (unsigned int)size;
4097
4098         do
4099         {
4100                 strm.next_out = tmp;
4101                 strm.avail_out = sizeof(tmp);
4102                 ret = qz_inflate(&strm, Z_NO_FLUSH);
4103                 // it either returns Z_OK on progress, Z_STREAM_END on end
4104                 // or an error code
4105                 switch(ret)
4106                 {
4107                         case Z_STREAM_END:
4108                         case Z_OK:
4109                                 break;
4110                                 
4111                         case Z_STREAM_ERROR:
4112                                 Con_Print("FS_Inflate: stream error!\n");
4113                                 break;
4114                         case Z_DATA_ERROR:
4115                                 Con_Print("FS_Inflate: data error!\n");
4116                                 break;
4117                         case Z_MEM_ERROR:
4118                                 Con_Print("FS_Inflate: mem error!\n");
4119                                 break;
4120                         case Z_BUF_ERROR:
4121                                 Con_Print("FS_Inflate: buf error!\n");
4122                                 break;
4123                         default:
4124                                 Con_Print("FS_Inflate: unknown error!\n");
4125                                 break;
4126                                 
4127                 }
4128                 if(ret != Z_OK && ret != Z_STREAM_END)
4129                 {
4130                         Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4131                         Mem_Free(outbuf.data);
4132                         qz_inflateEnd(&strm);
4133                         return NULL;
4134                 }
4135                 have = sizeof(tmp) - strm.avail_out;
4136                 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4137                 SZ_Write(&outbuf, tmp, have);
4138         } while(ret != Z_STREAM_END);
4139
4140         qz_inflateEnd(&strm);
4141
4142         out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4143         if(!out)
4144         {
4145                 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4146                 Mem_Free(outbuf.data);
4147                 return NULL;
4148         }
4149
4150         memcpy(out, outbuf.data, outbuf.cursize);
4151         Mem_Free(outbuf.data);
4152
4153         *inflated_size = (size_t)outbuf.cursize;
4154         
4155         return out;
4156 }