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