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