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