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