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