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