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