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