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