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