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