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