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