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