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