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