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