fix typo
[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)
673 {
674         pk3_endOfCentralDir_t eocd;
675         pack_t *pack;
676         int real_nb_files;
677
678         if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
679         {
680                 Con_Printf ("%s is not a PK3 file\n", packfile);
681                 close(packhandle);
682                 return NULL;
683         }
684
685         // Multi-volume ZIP archives are NOT allowed
686         if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
687         {
688                 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
689                 close(packhandle);
690                 return NULL;
691         }
692
693         // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
694         // since eocd.nbentries is an unsigned 16 bits integer
695 #if MAX_FILES_IN_PACK < 65535
696         if (eocd.nbentries > MAX_FILES_IN_PACK)
697         {
698                 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
699                 close(packhandle);
700                 return NULL;
701         }
702 #endif
703
704         // Create a package structure in memory
705         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
706         pack->ignorecase = true; // PK3 ignores case
707         strlcpy (pack->filename, packfile, sizeof (pack->filename));
708         pack->handle = packhandle;
709         pack->numfiles = eocd.nbentries;
710         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
711
712         real_nb_files = PK3_BuildFileList (pack, &eocd);
713         if (real_nb_files < 0)
714         {
715                 Con_Printf ("%s is not a valid PK3 file\n", packfile);
716                 close(pack->handle);
717                 Mem_Free(pack);
718                 return NULL;
719         }
720
721         Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
722         return pack;
723 }
724 pack_t *FS_LoadPackPK3 (const char *packfile)
725 {
726         int packhandle;
727 #if _MSC_VER >= 1400
728         _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
729 #else
730         packhandle = open (packfile, O_RDONLY | O_BINARY);
731 #endif
732         if (packhandle < 0)
733                 return NULL;
734         return FS_LoadPackPK3FromFD(packfile, packhandle);
735 }
736
737
738 /*
739 ====================
740 PK3_GetTrueFileOffset
741
742 Find where the true file data offset is
743 ====================
744 */
745 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
746 {
747         unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
748         fs_offset_t count;
749
750         // Already found?
751         if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
752                 return true;
753
754         // Load the local file description
755         lseek (pack->handle, pfile->offset, SEEK_SET);
756         count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
757         if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
758         {
759                 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
760                 return false;
761         }
762
763         // Skip name and extra field
764         pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
765
766         pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
767         return true;
768 }
769
770
771 /*
772 =============================================================================
773
774 OTHER PRIVATE FUNCTIONS
775
776 =============================================================================
777 */
778
779
780 /*
781 ====================
782 FS_AddFileToPack
783
784 Add a file to the list of files contained into a package
785 ====================
786 */
787 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
788                                                                          fs_offset_t offset, fs_offset_t packsize,
789                                                                          fs_offset_t realsize, int flags)
790 {
791         int (*strcmp_funct) (const char* str1, const char* str2);
792         int left, right, middle;
793         packfile_t *pfile;
794
795         strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
796
797         // Look for the slot we should put that file into (binary search)
798         left = 0;
799         right = pack->numfiles - 1;
800         while (left <= right)
801         {
802                 int diff;
803
804                 middle = (left + right) / 2;
805                 diff = strcmp_funct (pack->files[middle].name, name);
806
807                 // If we found the file, there's a problem
808                 if (!diff)
809                         Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
810
811                 // If we're too far in the list
812                 if (diff > 0)
813                         right = middle - 1;
814                 else
815                         left = middle + 1;
816         }
817
818         // We have to move the right of the list by one slot to free the one we need
819         pfile = &pack->files[left];
820         memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
821         pack->numfiles++;
822
823         strlcpy (pfile->name, name, sizeof (pfile->name));
824         pfile->offset = offset;
825         pfile->packsize = packsize;
826         pfile->realsize = realsize;
827         pfile->flags = flags;
828
829         return pfile;
830 }
831
832
833 /*
834 ============
835 FS_CreatePath
836
837 Only used for FS_OpenRealFile.
838 ============
839 */
840 void FS_CreatePath (char *path)
841 {
842         char *ofs, save;
843
844         for (ofs = path+1 ; *ofs ; ofs++)
845         {
846                 if (*ofs == '/' || *ofs == '\\')
847                 {
848                         // create the directory
849                         save = *ofs;
850                         *ofs = 0;
851                         FS_mkdir (path);
852                         *ofs = save;
853                 }
854         }
855 }
856
857
858 /*
859 ============
860 FS_Path_f
861
862 ============
863 */
864 void FS_Path_f (void)
865 {
866         searchpath_t *s;
867
868         Con_Print("Current search path:\n");
869         for (s=fs_searchpaths ; s ; s=s->next)
870         {
871                 if (s->pack)
872                 {
873                         if(s->pack->vpack)
874                                 Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
875                         else
876                                 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
877                 }
878                 else
879                         Con_Printf("%s\n", s->filename);
880         }
881 }
882
883
884 /*
885 =================
886 FS_LoadPackPAK
887 =================
888 */
889 /*! Takes an explicit (not game tree related) path to a pak file.
890  *Loads the header and directory, adding the files at the beginning
891  *of the list so they override previous pack files.
892  */
893 pack_t *FS_LoadPackPAK (const char *packfile)
894 {
895         dpackheader_t header;
896         int i, numpackfiles;
897         int packhandle;
898         pack_t *pack;
899         dpackfile_t *info;
900
901 #if _MSC_VER >= 1400
902         _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
903 #else
904         packhandle = open (packfile, O_RDONLY | O_BINARY);
905 #endif
906         if (packhandle < 0)
907                 return NULL;
908         if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
909         {
910                 Con_Printf ("%s is not a packfile\n", packfile);
911                 close(packhandle);
912                 return NULL;
913         }
914         if (memcmp(header.id, "PACK", 4))
915         {
916                 Con_Printf ("%s is not a packfile\n", packfile);
917                 close(packhandle);
918                 return NULL;
919         }
920         header.dirofs = LittleLong (header.dirofs);
921         header.dirlen = LittleLong (header.dirlen);
922
923         if (header.dirlen % sizeof(dpackfile_t))
924         {
925                 Con_Printf ("%s has an invalid directory size\n", packfile);
926                 close(packhandle);
927                 return NULL;
928         }
929
930         numpackfiles = header.dirlen / sizeof(dpackfile_t);
931
932         if (numpackfiles > MAX_FILES_IN_PACK)
933         {
934                 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
935                 close(packhandle);
936                 return NULL;
937         }
938
939         info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
940         lseek (packhandle, header.dirofs, SEEK_SET);
941         if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
942         {
943                 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
944                 Mem_Free(info);
945                 close(packhandle);
946                 return NULL;
947         }
948
949         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
950         pack->ignorecase = false; // PAK is case sensitive
951         strlcpy (pack->filename, packfile, sizeof (pack->filename));
952         pack->handle = packhandle;
953         pack->numfiles = 0;
954         pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
955
956         // parse the directory
957         for (i = 0;i < numpackfiles;i++)
958         {
959                 fs_offset_t offset = LittleLong (info[i].filepos);
960                 fs_offset_t size = LittleLong (info[i].filelen);
961
962                 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
963         }
964
965         Mem_Free(info);
966
967         Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
968         return pack;
969 }
970
971 /*
972 ====================
973 FS_LoadPackVirtual
974
975 Create a package entry associated with a directory file
976 ====================
977 */
978 pack_t *FS_LoadPackVirtual (const char *dirname)
979 {
980         pack_t *pack;
981         pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
982         pack->vpack = true;
983         pack->ignorecase = false;
984         strlcpy (pack->filename, dirname, sizeof(pack->filename));
985         pack->handle = -1;
986         pack->numfiles = -1;
987         pack->files = NULL;
988         Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
989         return pack;
990 }
991
992 /*
993 ================
994 FS_AddPack_Fullpath
995 ================
996 */
997 /*! Adds the given pack to the search path.
998  * The pack type is autodetected by the file extension.
999  *
1000  * Returns true if the file was successfully added to the
1001  * search path or if it was already included.
1002  *
1003  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1004  * plain directories.
1005  *
1006  */
1007 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
1008 {
1009         searchpath_t *search;
1010         pack_t *pak = NULL;
1011         const char *ext = FS_FileExtension(pakfile);
1012         size_t l;
1013
1014         for(search = fs_searchpaths; search; search = search->next)
1015         {
1016                 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1017                 {
1018                         if(already_loaded)
1019                                 *already_loaded = true;
1020                         return true; // already loaded
1021                 }
1022         }
1023
1024         if(already_loaded)
1025                 *already_loaded = false;
1026
1027         if(!strcasecmp(ext, "pk3dir"))
1028                 pak = FS_LoadPackVirtual (pakfile);
1029         else if(!strcasecmp(ext, "pak"))
1030                 pak = FS_LoadPackPAK (pakfile);
1031         else if(!strcasecmp(ext, "pk3"))
1032                 pak = FS_LoadPackPK3 (pakfile);
1033         else
1034                 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1035
1036         if(pak)
1037         {
1038                 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1039
1040                 //Con_DPrintf("  Registered pack with short name %s\n", shortname);
1041                 if(keep_plain_dirs)
1042                 {
1043                         // find the first item whose next one is a pack or NULL
1044                         searchpath_t *insertion_point = 0;
1045                         if(fs_searchpaths && !fs_searchpaths->pack)
1046                         {
1047                                 insertion_point = fs_searchpaths;
1048                                 for(;;)
1049                                 {
1050                                         if(!insertion_point->next)
1051                                                 break;
1052                                         if(insertion_point->next->pack)
1053                                                 break;
1054                                         insertion_point = insertion_point->next;
1055                                 }
1056                         }
1057                         // If insertion_point is NULL, this means that either there is no
1058                         // item in the list yet, or that the very first item is a pack. In
1059                         // that case, we want to insert at the beginning...
1060                         if(!insertion_point)
1061                         {
1062                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1063                                 search->next = fs_searchpaths;
1064                                 fs_searchpaths = search;
1065                         }
1066                         else
1067                         // otherwise we want to append directly after insertion_point.
1068                         {
1069                                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1070                                 search->next = insertion_point->next;
1071                                 insertion_point->next = search;
1072                         }
1073                 }
1074                 else
1075                 {
1076                         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1077                         search->next = fs_searchpaths;
1078                         fs_searchpaths = search;
1079                 }
1080                 search->pack = pak;
1081                 if(pak->vpack)
1082                 {
1083                         dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1084                         // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
1085                         // same goes for the name inside the pack structure
1086                         l = strlen(pak->shortname);
1087                         if(l >= 7)
1088                                 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
1089                                         pak->shortname[l - 3] = 0;
1090                         l = strlen(pak->filename);
1091                         if(l >= 7)
1092                                 if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
1093                                         pak->filename[l - 3] = 0;
1094                 }
1095                 return true;
1096         }
1097         else
1098         {
1099                 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1100                 return false;
1101         }
1102 }
1103
1104
1105 /*
1106 ================
1107 FS_AddPack
1108 ================
1109 */
1110 /*! Adds the given pack to the search path and searches for it in the game path.
1111  * The pack type is autodetected by the file extension.
1112  *
1113  * Returns true if the file was successfully added to the
1114  * search path or if it was already included.
1115  *
1116  * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1117  * plain directories.
1118  */
1119 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1120 {
1121         char fullpath[MAX_OSPATH];
1122         int index;
1123         searchpath_t *search;
1124
1125         if(already_loaded)
1126                 *already_loaded = false;
1127
1128         // then find the real name...
1129         search = FS_FindFile(pakfile, &index, true);
1130         if(!search || search->pack)
1131         {
1132                 Con_Printf("could not find pak \"%s\"\n", pakfile);
1133                 return false;
1134         }
1135
1136         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1137
1138         return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1139 }
1140
1141
1142 /*
1143 ================
1144 FS_AddGameDirectory
1145
1146 Sets fs_gamedir, adds the directory to the head of the path,
1147 then loads and adds pak1.pak pak2.pak ...
1148 ================
1149 */
1150 void FS_AddGameDirectory (const char *dir)
1151 {
1152         int i;
1153         stringlist_t list;
1154         searchpath_t *search;
1155
1156         strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1157
1158         stringlistinit(&list);
1159         listdirectory(&list, "", dir);
1160         stringlistsort(&list);
1161
1162         // add any PAK package in the directory
1163         for (i = 0;i < list.numstrings;i++)
1164         {
1165                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1166                 {
1167                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1168                 }
1169         }
1170
1171         // add any PK3 package in the directory
1172         for (i = 0;i < list.numstrings;i++)
1173         {
1174                 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
1175                 {
1176                         FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1177                 }
1178         }
1179
1180         stringlistfreecontents(&list);
1181
1182         // Add the directory to the search path
1183         // (unpacked files have the priority over packed files)
1184         search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1185         strlcpy (search->filename, dir, sizeof (search->filename));
1186         search->next = fs_searchpaths;
1187         fs_searchpaths = search;
1188 }
1189
1190
1191 /*
1192 ================
1193 FS_AddGameHierarchy
1194 ================
1195 */
1196 void FS_AddGameHierarchy (const char *dir)
1197 {
1198         // Add the common game directory
1199         FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1200
1201         if (*fs_userdir)
1202                 FS_AddGameDirectory(va("%s%s/", fs_userdir, dir));
1203 }
1204
1205
1206 /*
1207 ============
1208 FS_FileExtension
1209 ============
1210 */
1211 const char *FS_FileExtension (const char *in)
1212 {
1213         const char *separator, *backslash, *colon, *dot;
1214
1215         separator = strrchr(in, '/');
1216         backslash = strrchr(in, '\\');
1217         if (!separator || separator < backslash)
1218                 separator = backslash;
1219         colon = strrchr(in, ':');
1220         if (!separator || separator < colon)
1221                 separator = colon;
1222
1223         dot = strrchr(in, '.');
1224         if (dot == NULL || (separator && (dot < separator)))
1225                 return "";
1226
1227         return dot + 1;
1228 }
1229
1230
1231 /*
1232 ============
1233 FS_FileWithoutPath
1234 ============
1235 */
1236 const char *FS_FileWithoutPath (const char *in)
1237 {
1238         const char *separator, *backslash, *colon;
1239
1240         separator = strrchr(in, '/');
1241         backslash = strrchr(in, '\\');
1242         if (!separator || separator < backslash)
1243                 separator = backslash;
1244         colon = strrchr(in, ':');
1245         if (!separator || separator < colon)
1246                 separator = colon;
1247         return separator ? separator + 1 : in;
1248 }
1249
1250
1251 /*
1252 ================
1253 FS_ClearSearchPath
1254 ================
1255 */
1256 void FS_ClearSearchPath (void)
1257 {
1258         // unload all packs and directory information, close all pack files
1259         // (if a qfile is still reading a pack it won't be harmed because it used
1260         //  dup() to get its own handle already)
1261         while (fs_searchpaths)
1262         {
1263                 searchpath_t *search = fs_searchpaths;
1264                 fs_searchpaths = search->next;
1265                 if (search->pack && search->pack != fs_selfpack)
1266                 {
1267                         if(!search->pack->vpack)
1268                         {
1269                                 // close the file
1270                                 close(search->pack->handle);
1271                                 // free any memory associated with it
1272                                 if (search->pack->files)
1273                                         Mem_Free(search->pack->files);
1274                         }
1275                         Mem_Free(search->pack);
1276                 }
1277                 Mem_Free(search);
1278         }
1279 }
1280
1281 static void FS_AddSelfPack(void)
1282 {
1283         if(fs_selfpack)
1284         {
1285                 searchpath_t *search;
1286                 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1287                 search->next = fs_searchpaths;
1288                 search->pack = fs_selfpack;
1289                 fs_searchpaths = search;
1290         }
1291 }
1292
1293
1294 /*
1295 ================
1296 FS_Rescan
1297 ================
1298 */
1299 void FS_Rescan (void)
1300 {
1301         int i;
1302         qboolean fs_modified = false;
1303         char gamedirbuf[MAX_INPUTLINE];
1304
1305         FS_ClearSearchPath();
1306
1307         // add the game-specific paths
1308         // gamedirname1 (typically id1)
1309         FS_AddGameHierarchy (gamedirname1);
1310         // update the com_modname (used for server info)
1311         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1312
1313         // add the game-specific path, if any
1314         // (only used for mission packs and the like, which should set fs_modified)
1315         if (gamedirname2)
1316         {
1317                 fs_modified = true;
1318                 FS_AddGameHierarchy (gamedirname2);
1319         }
1320
1321         // -game <gamedir>
1322         // Adds basedir/gamedir as an override game
1323         // LordHavoc: now supports multiple -game directories
1324         // set the com_modname (reported in server info)
1325         *gamedirbuf = 0;
1326         for (i = 0;i < fs_numgamedirs;i++)
1327         {
1328                 fs_modified = true;
1329                 FS_AddGameHierarchy (fs_gamedirs[i]);
1330                 // update the com_modname (used server info)
1331                 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1332                 if(i)
1333                         strlcat(gamedirbuf, va(" %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1334                 else
1335                         strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1336         }
1337         Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1338
1339         // add back the selfpack as new first item
1340         FS_AddSelfPack();
1341
1342         // set the default screenshot name to either the mod name or the
1343         // gamemode screenshot name
1344         if (strcmp(com_modname, gamedirname1))
1345                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1346         else
1347                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1348         
1349         if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1350                 strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1351
1352         // If "-condebug" is in the command line, remove the previous log file
1353         if (COM_CheckParm ("-condebug") != 0)
1354                 unlink (va("%s/qconsole.log", fs_gamedir));
1355
1356         // look for the pop.lmp file and set registered to true if it is found
1357         if (FS_FileExists("gfx/pop.lmp"))
1358                 Cvar_Set ("registered", "1");
1359         switch(gamemode)
1360         {
1361         case GAME_NORMAL:
1362         case GAME_HIPNOTIC:
1363         case GAME_ROGUE:
1364                 if (!registered.integer)
1365                 {
1366                         if (fs_modified)
1367                                 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1368                         else
1369                                 Con_Print("Playing shareware version.\n");
1370                 }
1371                 else
1372                         Con_Print("Playing registered version.\n");
1373                 break;
1374         case GAME_STEELSTORM:
1375                 if (registered.integer)
1376                         Con_Print("Playing registered version.\n");
1377                 else
1378                         Con_Print("Playing shareware version.\n");
1379                 break;
1380         default:
1381                 break;
1382         }
1383
1384         // unload all wads so that future queries will return the new data
1385         W_UnloadAll();
1386 }
1387
1388 void FS_Rescan_f(void)
1389 {
1390         FS_Rescan();
1391 }
1392
1393 /*
1394 ================
1395 FS_ChangeGameDirs
1396 ================
1397 */
1398 extern void Host_SaveConfig (void);
1399 extern void Host_LoadConfig_f (void);
1400 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1401 {
1402         int i;
1403         const char *p;
1404
1405         if (fs_numgamedirs == numgamedirs)
1406         {
1407                 for (i = 0;i < numgamedirs;i++)
1408                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1409                                 break;
1410                 if (i == numgamedirs)
1411                         return true; // already using this set of gamedirs, do nothing
1412         }
1413
1414         if (numgamedirs > MAX_GAMEDIRS)
1415         {
1416                 if (complain)
1417                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1418                 return false; // too many gamedirs
1419         }
1420
1421         for (i = 0;i < numgamedirs;i++)
1422         {
1423                 // if string is nasty, reject it
1424                 p = FS_CheckGameDir(gamedirs[i]);
1425                 if(!p)
1426                 {
1427                         if (complain)
1428                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1429                         return false; // nasty gamedirs
1430                 }
1431                 if(p == fs_checkgamedir_missing && failmissing)
1432                 {
1433                         if (complain)
1434                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1435                         return false; // missing gamedirs
1436                 }
1437         }
1438
1439         Host_SaveConfig();
1440
1441         fs_numgamedirs = numgamedirs;
1442         for (i = 0;i < fs_numgamedirs;i++)
1443                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1444
1445         // reinitialize filesystem to detect the new paks
1446         FS_Rescan();
1447
1448         // exec the new config
1449         Host_LoadConfig_f();
1450
1451         // unload all sounds so they will be reloaded from the new files as needed
1452         S_UnloadAllSounds_f();
1453
1454         // reinitialize renderer (this reloads hud/console background/etc)
1455         R_Modules_Restart();
1456
1457         return true;
1458 }
1459
1460 /*
1461 ================
1462 FS_GameDir_f
1463 ================
1464 */
1465 void FS_GameDir_f (void)
1466 {
1467         int i;
1468         int numgamedirs;
1469         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1470
1471         if (Cmd_Argc() < 2)
1472         {
1473                 Con_Printf("gamedirs active:");
1474                 for (i = 0;i < fs_numgamedirs;i++)
1475                         Con_Printf(" %s", fs_gamedirs[i]);
1476                 Con_Printf("\n");
1477                 return;
1478         }
1479
1480         numgamedirs = Cmd_Argc() - 1;
1481         if (numgamedirs > MAX_GAMEDIRS)
1482         {
1483                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1484                 return;
1485         }
1486
1487         for (i = 0;i < numgamedirs;i++)
1488                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1489
1490         if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1491         {
1492                 // actually, changing during game would work fine, but would be stupid
1493                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1494                 return;
1495         }
1496
1497         // halt demo playback to close the file
1498         CL_Disconnect();
1499
1500         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1501 }
1502
1503 static const char *FS_SysCheckGameDir(const char *gamedir)
1504 {
1505         static char buf[8192];
1506         qboolean success;
1507         qfile_t *f;
1508         stringlist_t list;
1509         fs_offset_t n;
1510
1511         stringlistinit(&list);
1512         listdirectory(&list, gamedir, "");
1513         success = list.numstrings > 0;
1514         stringlistfreecontents(&list);
1515
1516         if(success)
1517         {
1518                 f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false);
1519                 if(f)
1520                 {
1521                         n = FS_Read (f, buf, sizeof(buf) - 1);
1522                         if(n >= 0)
1523                                 buf[n] = 0;
1524                         else
1525                                 *buf = 0;
1526                         FS_Close(f);
1527                 }
1528                 else
1529                         *buf = 0;
1530                 return buf;
1531         }
1532
1533         return NULL;
1534 }
1535
1536 /*
1537 ================
1538 FS_CheckGameDir
1539 ================
1540 */
1541 const char *FS_CheckGameDir(const char *gamedir)
1542 {
1543         const char *ret;
1544
1545         if (FS_CheckNastyPath(gamedir, true))
1546                 return NULL;
1547
1548         ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir));
1549         if(ret)
1550         {
1551                 if(!*ret)
1552                 {
1553                         // get description from basedir
1554                         ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1555                         if(ret)
1556                                 return ret;
1557                         return "";
1558                 }
1559                 return ret;
1560         }
1561
1562         ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1563         if(ret)
1564                 return ret;
1565         
1566         return fs_checkgamedir_missing;
1567 }
1568
1569 static void FS_ListGameDirs(void)
1570 {
1571         stringlist_t list, list2;
1572         int i, j;
1573         const char *info;
1574
1575         fs_all_gamedirs_count = 0;
1576         if(fs_all_gamedirs)
1577                 Mem_Free(fs_all_gamedirs);
1578
1579         stringlistinit(&list);
1580         listdirectory(&list, va("%s/", fs_basedir), "");
1581         listdirectory(&list, va("%s/", fs_userdir), "");
1582         stringlistsort(&list);
1583
1584         stringlistinit(&list2);
1585         for(i = 0; i < list.numstrings; ++i)
1586         {
1587                 if(i)
1588                         if(!strcmp(list.strings[i-1], list.strings[i]))
1589                                 continue;
1590                 info = FS_CheckGameDir(list.strings[i]);
1591                 if(!info)
1592                         continue;
1593                 if(info == fs_checkgamedir_missing)
1594                         continue;
1595                 if(!*info)
1596                         continue;
1597                 stringlistappend(&list2, list.strings[i]); 
1598         }
1599         stringlistfreecontents(&list);
1600
1601         fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1602         for(i = 0; i < list2.numstrings; ++i)
1603         {
1604                 info = FS_CheckGameDir(list2.strings[i]);
1605                 // all this cannot happen any more, but better be safe than sorry
1606                 if(!info)
1607                         continue;
1608                 if(info == fs_checkgamedir_missing)
1609                         continue;
1610                 if(!*info)
1611                         continue;
1612                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name));
1613                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description));
1614                 ++fs_all_gamedirs_count;
1615         }
1616 }
1617
1618 /*
1619 ================
1620 FS_Init_SelfPack
1621 ================
1622 */
1623 void FS_Init_SelfPack (void)
1624 {
1625         PK3_OpenLibrary ();
1626         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1627         if(com_selffd >= 0)
1628         {
1629                 fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd);
1630                 if(fs_selfpack)
1631                 {
1632                         char *buf, *q;
1633                         const char *p;
1634                         FS_AddSelfPack();
1635                         buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
1636                         if(buf)
1637                         {
1638                                 const char **new_argv;
1639                                 int i = 0;
1640                                 int args_left = 256;
1641                                 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
1642                                 if(com_argc == 0)
1643                                 {
1644                                         new_argv[0] = "dummy";
1645                                         com_argc = 1;
1646                                 }
1647                                 else
1648                                 {
1649                                         memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc);
1650                                 }
1651                                 p = buf;
1652                                 while(COM_ParseToken_Console(&p))
1653                                 {
1654                                         if(i >= args_left)
1655                                                 break;
1656                                         q = (char *)Mem_Alloc(fs_mempool, strlen(com_token) + 1);
1657                                         strlcpy(q, com_token, strlen(com_token) + 1);
1658                                         new_argv[com_argc + i] = q;
1659                                         ++i;
1660                                 }
1661                                 new_argv[i+com_argc] = NULL;
1662                                 com_argv = new_argv;
1663                                 com_argc = com_argc + i;
1664                         }
1665                         Mem_Free(buf);
1666                 }
1667         }
1668 }
1669
1670 /*
1671 ================
1672 FS_Init
1673 ================
1674 */
1675 void FS_Init (void)
1676 {
1677         const char *p;
1678         int i;
1679 #ifdef WIN32
1680         TCHAR mydocsdir[MAX_PATH + 1];
1681 #if _MSC_VER >= 1400
1682         size_t homedirlen;
1683 #endif
1684 #endif
1685         char *homedir;
1686
1687 #ifdef WIN32
1688         const char* dllnames [] =
1689         {
1690                 "shfolder.dll",  // IE 4, or Win NT and higher
1691                 NULL
1692         };
1693         Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1694         // don't care for the result; if it fails, %USERPROFILE% will be used instead
1695 #endif
1696
1697         // Add the personal game directory
1698         if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1699         {
1700                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
1701         }
1702         else if(COM_CheckParm("-nohome"))
1703         {
1704                 *fs_userdir = 0;
1705         }
1706         else
1707         {
1708 #ifdef WIN32
1709                 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1710                 {
1711                         dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1712                         Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", fs_userdir);
1713                 }
1714                 else
1715                 {
1716                         // use the environment
1717 #if _MSC_VER >= 1400
1718                         _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1719 #else
1720                         homedir = getenv("USERPROFILE");
1721 #endif
1722
1723                         if(homedir)
1724                         {
1725                                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1726 #if _MSC_VER >= 1400
1727                                 free(homedir);
1728 #endif
1729                                 Con_DPrintf("Obtained personal directory %s from environment\n", fs_userdir);
1730                         }
1731                 }
1732
1733                 if(!*fs_userdir)
1734                         Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1735 #else
1736                 homedir = getenv ("HOME");
1737                 if(homedir)
1738                         dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/.%s/", homedir, gameuserdirname);
1739
1740                 if(!*fs_userdir)
1741                         Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1742 #endif
1743
1744 #ifdef WIN32
1745                 if(!COM_CheckParm("-mygames"))
1746                 {
1747 #if _MSC_VER >= 1400
1748                         int fd;
1749                         _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!
1750 #else
1751                         int fd = open (va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1752 #endif
1753                         if(fd >= 0)
1754                         {
1755                                 close(fd);
1756                                 *fs_userdir = 0; // we have write access to the game dir, so let's use it
1757                         }
1758                 }
1759 #endif
1760         }
1761
1762         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1763
1764 // If the base directory is explicitly defined by the compilation process
1765 #ifdef DP_FS_BASEDIR
1766         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1767 #else
1768         *fs_basedir = 0;
1769
1770 #ifdef MACOSX
1771         // FIXME: is there a better way to find the directory outside the .app?
1772         if (strstr(com_argv[0], ".app/"))
1773         {
1774                 char *split;
1775
1776                 split = strstr(com_argv[0], ".app/");
1777                 while (split > com_argv[0] && *split != '/')
1778                         split--;
1779                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1780                 fs_basedir[split - com_argv[0]] = 0;
1781         }
1782 #endif
1783 #endif
1784
1785         // -basedir <path>
1786         // Overrides the system supplied base directory (under GAMENAME)
1787 // 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)
1788         i = COM_CheckParm ("-basedir");
1789         if (i && i < com_argc-1)
1790         {
1791                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1792                 i = (int)strlen (fs_basedir);
1793                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1794                         fs_basedir[i-1] = 0;
1795         }
1796
1797         // add a path separator to the end of the basedir if it lacks one
1798         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1799                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1800
1801         FS_ListGameDirs();
1802
1803         p = FS_CheckGameDir(gamedirname1);
1804         if(!p || p == fs_checkgamedir_missing)
1805                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1806
1807         if(gamedirname2)
1808         {
1809                 p = FS_CheckGameDir(gamedirname2);
1810                 if(!p || p == fs_checkgamedir_missing)
1811                         Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1812         }
1813
1814         // -game <gamedir>
1815         // Adds basedir/gamedir as an override game
1816         // LordHavoc: now supports multiple -game directories
1817         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1818         {
1819                 if (!com_argv[i])
1820                         continue;
1821                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1822                 {
1823                         i++;
1824                         p = FS_CheckGameDir(com_argv[i]);
1825                         if(!p)
1826                                 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
1827                         if(p == fs_checkgamedir_missing)
1828                                 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1829                         // add the gamedir to the list of active gamedirs
1830                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1831                         fs_numgamedirs++;
1832                 }
1833         }
1834
1835         // generate the searchpath
1836         FS_Rescan();
1837 }
1838
1839 void FS_Init_Commands(void)
1840 {
1841         Cvar_RegisterVariable (&scr_screenshot_name);
1842         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1843         Cvar_RegisterVariable (&cvar_fs_gamedir);
1844
1845         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1846         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1847         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1848         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1849         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1850         Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
1851 }
1852
1853 /*
1854 ================
1855 FS_Shutdown
1856 ================
1857 */
1858 void FS_Shutdown (void)
1859 {
1860         // close all pack files and such
1861         // (hopefully there aren't any other open files, but they'll be cleaned up
1862         //  by the OS anyway)
1863         FS_ClearSearchPath();
1864         Mem_FreePool (&fs_mempool);
1865
1866 #ifdef WIN32
1867         Sys_UnloadLibrary (&shfolder_dll);
1868 #endif
1869 }
1870
1871 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
1872 {
1873         int handle;
1874         int mod, opt;
1875         unsigned int ind;
1876
1877         // Parse the mode string
1878         switch (mode[0])
1879         {
1880                 case 'r':
1881                         mod = O_RDONLY;
1882                         opt = 0;
1883                         break;
1884                 case 'w':
1885                         mod = O_WRONLY;
1886                         opt = O_CREAT | O_TRUNC;
1887                         break;
1888                 case 'a':
1889                         mod = O_WRONLY;
1890                         opt = O_CREAT | O_APPEND;
1891                         break;
1892                 default:
1893                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1894                         return -1;
1895         }
1896         for (ind = 1; mode[ind] != '\0'; ind++)
1897         {
1898                 switch (mode[ind])
1899                 {
1900                         case '+':
1901                                 mod = O_RDWR;
1902                                 break;
1903                         case 'b':
1904                                 opt |= O_BINARY;
1905                                 break;
1906                         default:
1907                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1908                                                         filepath, mode, mode[ind]);
1909                 }
1910         }
1911
1912         if (nonblocking)
1913                 opt |= O_NONBLOCK;
1914
1915 #if _MSC_VER >= 1400
1916         _sopen_s(&handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1917 #else
1918         handle = open (filepath, mod | opt, 0666);
1919 #endif
1920         return handle;
1921 }
1922
1923 /*
1924 ====================
1925 FS_SysOpen
1926
1927 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1928 ====================
1929 */
1930 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1931 {
1932         qfile_t* file;
1933
1934         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1935         file->ungetc = EOF;
1936         file->handle = FS_SysOpenFD(filepath, mode, nonblocking);
1937         if (file->handle < 0)
1938         {
1939                 Mem_Free (file);
1940                 return NULL;
1941         }
1942
1943         file->filename = Mem_strdup(fs_mempool, filepath);
1944
1945         file->real_length = lseek (file->handle, 0, SEEK_END);
1946
1947         // For files opened in append mode, we start at the end of the file
1948         if (mode[0] == 'a')
1949                 file->position = file->real_length;
1950         else
1951                 lseek (file->handle, 0, SEEK_SET);
1952
1953         return file;
1954 }
1955
1956
1957 /*
1958 ===========
1959 FS_OpenPackedFile
1960
1961 Open a packed file using its package file descriptor
1962 ===========
1963 */
1964 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1965 {
1966         packfile_t *pfile;
1967         int dup_handle;
1968         qfile_t* file;
1969
1970         pfile = &pack->files[pack_ind];
1971
1972         // If we don't have the true offset, get it now
1973         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1974                 if (!PK3_GetTrueFileOffset (pfile, pack))
1975                         return NULL;
1976
1977 #ifndef LINK_TO_ZLIB
1978         // No Zlib DLL = no compressed files
1979         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1980         {
1981                 Con_Printf("WARNING: can't open the compressed file %s\n"
1982                                         "You need the Zlib DLL to use compressed files\n",
1983                                         pfile->name);
1984                 return NULL;
1985         }
1986 #endif
1987
1988         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1989         // the dup() call to avoid having to close the dup_handle on error here
1990         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1991         {
1992                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1993                                         pfile->name, pack->filename, (int) pfile->offset);
1994                 return NULL;
1995         }
1996
1997         dup_handle = dup (pack->handle);
1998         if (dup_handle < 0)
1999         {
2000                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2001                 return NULL;
2002         }
2003
2004         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2005         memset (file, 0, sizeof (*file));
2006         file->handle = dup_handle;
2007         file->flags = QFILE_FLAG_PACKED;
2008         file->real_length = pfile->realsize;
2009         file->offset = pfile->offset;
2010         file->position = 0;
2011         file->ungetc = EOF;
2012
2013         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2014         {
2015                 ztoolkit_t *ztk;
2016
2017                 file->flags |= QFILE_FLAG_DEFLATED;
2018
2019                 // We need some more variables
2020                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2021
2022                 ztk->comp_length = pfile->packsize;
2023
2024                 // Initialize zlib stream
2025                 ztk->zstream.next_in = ztk->input;
2026                 ztk->zstream.avail_in = 0;
2027
2028                 /* From Zlib's "unzip.c":
2029                  *
2030                  * windowBits is passed < 0 to tell that there is no zlib header.
2031                  * Note that in this case inflate *requires* an extra "dummy" byte
2032                  * after the compressed stream in order to complete decompression and
2033                  * return Z_STREAM_END.
2034                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2035                  * size of both compressed and uncompressed data
2036                  */
2037                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2038                 {
2039                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2040                         close(dup_handle);
2041                         Mem_Free(file);
2042                         return NULL;
2043                 }
2044
2045                 ztk->zstream.next_out = file->buff;
2046                 ztk->zstream.avail_out = sizeof (file->buff);
2047
2048                 file->ztk = ztk;
2049         }
2050
2051         return file;
2052 }
2053
2054 /*
2055 ====================
2056 FS_CheckNastyPath
2057
2058 Return true if the path should be rejected due to one of the following:
2059 1: path elements that are non-portable
2060 2: path elements that would allow access to files outside the game directory,
2061    or are just not a good idea for a mod to be using.
2062 ====================
2063 */
2064 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2065 {
2066         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2067         if (!path[0])
2068                 return 2;
2069
2070         // Windows: don't allow \ in filenames (windows-only), period.
2071         // (on Windows \ is a directory separator, but / is also supported)
2072         if (strstr(path, "\\"))
2073                 return 1; // non-portable
2074
2075         // Mac: don't allow Mac-only filenames - : is a directory separator
2076         // instead of /, but we rely on / working already, so there's no reason to
2077         // support a Mac-only path
2078         // Amiga and Windows: : tries to go to root of drive
2079         if (strstr(path, ":"))
2080                 return 1; // non-portable attempt to go to root of drive
2081
2082         // Amiga: // is parent directory
2083         if (strstr(path, "//"))
2084                 return 1; // non-portable attempt to go to parent directory
2085
2086         // all: don't allow going to parent directory (../ or /../)
2087         if (strstr(path, ".."))
2088                 return 2; // attempt to go outside the game directory
2089
2090         // Windows and UNIXes: don't allow absolute paths
2091         if (path[0] == '/')
2092                 return 2; // attempt to go outside the game directory
2093
2094         // 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
2095         if (strchr(path, '.'))
2096         {
2097                 if (isgamedir)
2098                 {
2099                         // gamedir is entirely path elements, so simply forbid . entirely
2100                         return 2;
2101                 }
2102                 if (strchr(path, '.') < strrchr(path, '/'))
2103                         return 2; // possible attempt to go outside the game directory
2104         }
2105
2106         // all: forbid trailing slash on gamedir
2107         if (isgamedir && path[strlen(path)-1] == '/')
2108                 return 2;
2109
2110         // all: forbid leading dot on any filename for any reason
2111         if (strstr(path, "/."))
2112                 return 2; // attempt to go outside the game directory
2113
2114         // after all these checks we're pretty sure it's a / separated filename
2115         // and won't do much if any harm
2116         return false;
2117 }
2118
2119
2120 /*
2121 ====================
2122 FS_FindFile
2123
2124 Look for a file in the packages and in the filesystem
2125
2126 Return the searchpath where the file was found (or NULL)
2127 and the file index in the package if relevant
2128 ====================
2129 */
2130 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2131 {
2132         searchpath_t *search;
2133         pack_t *pak;
2134
2135         // search through the path, one element at a time
2136         for (search = fs_searchpaths;search;search = search->next)
2137         {
2138                 // is the element a pak file?
2139                 if (search->pack && !search->pack->vpack)
2140                 {
2141                         int (*strcmp_funct) (const char* str1, const char* str2);
2142                         int left, right, middle;
2143
2144                         pak = search->pack;
2145                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2146
2147                         // Look for the file (binary search)
2148                         left = 0;
2149                         right = pak->numfiles - 1;
2150                         while (left <= right)
2151                         {
2152                                 int diff;
2153
2154                                 middle = (left + right) / 2;
2155                                 diff = strcmp_funct (pak->files[middle].name, name);
2156
2157                                 // Found it
2158                                 if (!diff)
2159                                 {
2160                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2161                                         {
2162                                                 // yes, but the first one is empty so we treat it as not being there
2163                                                 if (!quiet && developer_extra.integer)
2164                                                         Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2165
2166                                                 if (index != NULL)
2167                                                         *index = -1;
2168                                                 return NULL;
2169                                         }
2170
2171                                         if (!quiet && developer_extra.integer)
2172                                                 Con_DPrintf("FS_FindFile: %s in %s\n",
2173                                                                         pak->files[middle].name, pak->filename);
2174
2175                                         if (index != NULL)
2176                                                 *index = middle;
2177                                         return search;
2178                                 }
2179
2180                                 // If we're too far in the list
2181                                 if (diff > 0)
2182                                         right = middle - 1;
2183                                 else
2184                                         left = middle + 1;
2185                         }
2186                 }
2187                 else
2188                 {
2189                         char netpath[MAX_OSPATH];
2190                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2191                         if (FS_SysFileExists (netpath))
2192                         {
2193                                 if (!quiet && developer_extra.integer)
2194                                         Con_DPrintf("FS_FindFile: %s\n", netpath);
2195
2196                                 if (index != NULL)
2197                                         *index = -1;
2198                                 return search;
2199                         }
2200                 }
2201         }
2202
2203         if (!quiet && developer_extra.integer)
2204                 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2205
2206         if (index != NULL)
2207                 *index = -1;
2208         return NULL;
2209 }
2210
2211
2212 /*
2213 ===========
2214 FS_OpenReadFile
2215
2216 Look for a file in the search paths and open it in read-only mode
2217 ===========
2218 */
2219 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2220 {
2221         searchpath_t *search;
2222         int pack_ind;
2223
2224         search = FS_FindFile (filename, &pack_ind, quiet);
2225
2226         // Not found?
2227         if (search == NULL)
2228                 return NULL;
2229
2230         // Found in the filesystem?
2231         if (pack_ind < 0)
2232         {
2233                 // this works with vpacks, so we are fine
2234                 char path [MAX_OSPATH];
2235                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2236                 return FS_SysOpen (path, "rb", nonblocking);
2237         }
2238
2239         // So, we found it in a package...
2240
2241         // Is it a PK3 symlink?
2242         // TODO also handle directory symlinks by parsing the whole structure...
2243         // but heck, file symlinks are good enough for now
2244         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2245         {
2246                 if(symlinkLevels <= 0)
2247                 {
2248                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2249                         return NULL;
2250                 }
2251                 else
2252                 {
2253                         char linkbuf[MAX_QPATH];
2254                         fs_offset_t count;
2255                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2256                         const char *mergeslash;
2257                         char *mergestart;
2258
2259                         if(!linkfile)
2260                                 return NULL;
2261                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2262                         FS_Close(linkfile);
2263                         if(count < 0)
2264                                 return NULL;
2265                         linkbuf[count] = 0;
2266                         
2267                         // Now combine the paths...
2268                         mergeslash = strrchr(filename, '/');
2269                         mergestart = linkbuf;
2270                         if(!mergeslash)
2271                                 mergeslash = filename;
2272                         while(!strncmp(mergestart, "../", 3))
2273                         {
2274                                 mergestart += 3;
2275                                 while(mergeslash > filename)
2276                                 {
2277                                         --mergeslash;
2278                                         if(*mergeslash == '/')
2279                                                 break;
2280                                 }
2281                         }
2282                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2283                         if(mergeslash == filename)
2284                         {
2285                                 // Either mergeslash == filename, then we just replace the name (done below)
2286                         }
2287                         else
2288                         {
2289                                 // Or, we append the name after mergeslash;
2290                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2291                                 int spaceNeeded = mergeslash - filename + 1;
2292                                 int spaceRemoved = mergestart - linkbuf;
2293                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2294                                 {
2295                                         Con_DPrintf("symlink: too long path rejected\n");
2296                                         return NULL;
2297                                 }
2298                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2299                                 memcpy(linkbuf, filename, spaceNeeded);
2300                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2301                                 mergestart = linkbuf;
2302                         }
2303                         if (!quiet && developer_loading.integer)
2304                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2305                         if(FS_CheckNastyPath (mergestart, false))
2306                         {
2307                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2308                                 return NULL;
2309                         }
2310                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2311                 }
2312         }
2313
2314         return FS_OpenPackedFile (search->pack, pack_ind);
2315 }
2316
2317
2318 /*
2319 =============================================================================
2320
2321 MAIN PUBLIC FUNCTIONS
2322
2323 =============================================================================
2324 */
2325
2326 /*
2327 ====================
2328 FS_OpenRealFile
2329
2330 Open a file in the userpath. The syntax is the same as fopen
2331 Used for savegame scanning in menu, and all file writing.
2332 ====================
2333 */
2334 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2335 {
2336         char real_path [MAX_OSPATH];
2337
2338         if (FS_CheckNastyPath(filepath, false))
2339         {
2340                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2341                 return NULL;
2342         }
2343
2344         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2345
2346         // If the file is opened in "write", "append", or "read/write" mode,
2347         // create directories up to the file.
2348         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2349                 FS_CreatePath (real_path);
2350         return FS_SysOpen (real_path, mode, false);
2351 }
2352
2353
2354 /*
2355 ====================
2356 FS_OpenVirtualFile
2357
2358 Open a file. The syntax is the same as fopen
2359 ====================
2360 */
2361 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2362 {
2363         if (FS_CheckNastyPath(filepath, false))
2364         {
2365                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2366                 return NULL;
2367         }
2368
2369         return FS_OpenReadFile (filepath, quiet, false, 16);
2370 }
2371
2372
2373 /*
2374 ====================
2375 FS_FileFromData
2376
2377 Open a file. The syntax is the same as fopen
2378 ====================
2379 */
2380 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2381 {
2382         qfile_t* file;
2383         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2384         memset (file, 0, sizeof (*file));
2385         file->flags = QFILE_FLAG_DATA;
2386         file->ungetc = EOF;
2387         file->real_length = size;
2388         file->data = data;
2389         return file;
2390 }
2391
2392 /*
2393 ====================
2394 FS_Close
2395
2396 Close a file
2397 ====================
2398 */
2399 int FS_Close (qfile_t* file)
2400 {
2401         if(file->flags & QFILE_FLAG_DATA)
2402         {
2403                 Mem_Free(file);
2404                 return 0;
2405         }
2406
2407         if (close (file->handle))
2408                 return EOF;
2409
2410         if (file->filename)
2411         {
2412                 if (file->flags & QFILE_FLAG_REMOVE)
2413                         remove(file->filename);
2414
2415                 Mem_Free((void *) file->filename);
2416         }
2417
2418         if (file->ztk)
2419         {
2420                 qz_inflateEnd (&file->ztk->zstream);
2421                 Mem_Free (file->ztk);
2422         }
2423
2424         Mem_Free (file);
2425         return 0;
2426 }
2427
2428 void FS_RemoveOnClose(qfile_t* file)
2429 {
2430         file->flags |= QFILE_FLAG_REMOVE;
2431 }
2432
2433 /*
2434 ====================
2435 FS_Write
2436
2437 Write "datasize" bytes into a file
2438 ====================
2439 */
2440 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2441 {
2442         fs_offset_t result;
2443
2444         // If necessary, seek to the exact file position we're supposed to be
2445         if (file->buff_ind != file->buff_len)
2446                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2447
2448         // Purge cached data
2449         FS_Purge (file);
2450
2451         // Write the buffer and update the position
2452         result = write (file->handle, data, (fs_offset_t)datasize);
2453         file->position = lseek (file->handle, 0, SEEK_CUR);
2454         if (file->real_length < file->position)
2455                 file->real_length = file->position;
2456
2457         if (result < 0)
2458                 return 0;
2459
2460         return result;
2461 }
2462
2463
2464 /*
2465 ====================
2466 FS_Read
2467
2468 Read up to "buffersize" bytes from a file
2469 ====================
2470 */
2471 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2472 {
2473         fs_offset_t count, done;
2474
2475         if (buffersize == 0)
2476                 return 0;
2477
2478         // Get rid of the ungetc character
2479         if (file->ungetc != EOF)
2480         {
2481                 ((char*)buffer)[0] = file->ungetc;
2482                 buffersize--;
2483                 file->ungetc = EOF;
2484                 done = 1;
2485         }
2486         else
2487                 done = 0;
2488
2489         if(file->flags & QFILE_FLAG_DATA)
2490         {
2491                 size_t left = file->real_length - file->position;
2492                 if(buffersize > left)
2493                         buffersize = left;
2494                 memcpy(buffer, file->data + file->position, buffersize);
2495                 file->position += buffersize;
2496                 return buffersize;
2497         }
2498
2499         // First, we copy as many bytes as we can from "buff"
2500         if (file->buff_ind < file->buff_len)
2501         {
2502                 count = file->buff_len - file->buff_ind;
2503                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2504                 done += count;
2505                 memcpy (buffer, &file->buff[file->buff_ind], count);
2506                 file->buff_ind += count;
2507
2508                 buffersize -= count;
2509                 if (buffersize == 0)
2510                         return done;
2511         }
2512
2513         // NOTE: at this point, the read buffer is always empty
2514
2515         // If the file isn't compressed
2516         if (! (file->flags & QFILE_FLAG_DEFLATED))
2517         {
2518                 fs_offset_t nb;
2519
2520                 // We must take care to not read after the end of the file
2521                 count = file->real_length - file->position;
2522
2523                 // If we have a lot of data to get, put them directly into "buffer"
2524                 if (buffersize > sizeof (file->buff) / 2)
2525                 {
2526                         if (count > (fs_offset_t)buffersize)
2527                                 count = (fs_offset_t)buffersize;
2528                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2529                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2530                         if (nb > 0)
2531                         {
2532                                 done += nb;
2533                                 file->position += nb;
2534
2535                                 // Purge cached data
2536                                 FS_Purge (file);
2537                         }
2538                 }
2539                 else
2540                 {
2541                         if (count > (fs_offset_t)sizeof (file->buff))
2542                                 count = (fs_offset_t)sizeof (file->buff);
2543                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2544                         nb = read (file->handle, file->buff, count);
2545                         if (nb > 0)
2546                         {
2547                                 file->buff_len = nb;
2548                                 file->position += nb;
2549
2550                                 // Copy the requested data in "buffer" (as much as we can)
2551                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2552                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2553                                 file->buff_ind = count;
2554                                 done += count;
2555                         }
2556                 }
2557
2558                 return done;
2559         }
2560
2561         // If the file is compressed, it's more complicated...
2562         // We cycle through a few operations until we have read enough data
2563         while (buffersize > 0)
2564         {
2565                 ztoolkit_t *ztk = file->ztk;
2566                 int error;
2567
2568                 // NOTE: at this point, the read buffer is always empty
2569
2570                 // If "input" is also empty, we need to refill it
2571                 if (ztk->in_ind == ztk->in_len)
2572                 {
2573                         // If we are at the end of the file
2574                         if (file->position == file->real_length)
2575                                 return done;
2576
2577                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2578                         if (count > (fs_offset_t)sizeof (ztk->input))
2579                                 count = (fs_offset_t)sizeof (ztk->input);
2580                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2581                         if (read (file->handle, ztk->input, count) != count)
2582                         {
2583                                 Con_Printf ("FS_Read: unexpected end of file\n");
2584                                 break;
2585                         }
2586
2587                         ztk->in_ind = 0;
2588                         ztk->in_len = count;
2589                         ztk->in_position += count;
2590                 }
2591
2592                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2593                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2594
2595                 // Now that we are sure we have compressed data available, we need to determine
2596                 // if it's better to inflate it in "file->buff" or directly in "buffer"
2597
2598                 // Inflate the data in "file->buff"
2599                 if (buffersize < sizeof (file->buff) / 2)
2600                 {
2601                         ztk->zstream.next_out = file->buff;
2602                         ztk->zstream.avail_out = sizeof (file->buff);
2603                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2604                         if (error != Z_OK && error != Z_STREAM_END)
2605                         {
2606                                 Con_Printf ("FS_Read: Can't inflate file\n");
2607                                 break;
2608                         }
2609                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2610
2611                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2612                         file->position += file->buff_len;
2613
2614                         // Copy the requested data in "buffer" (as much as we can)
2615                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2616                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2617                         file->buff_ind = count;
2618                 }
2619
2620                 // Else, we inflate directly in "buffer"
2621                 else
2622                 {
2623                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2624                         ztk->zstream.avail_out = (unsigned int)buffersize;
2625                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2626                         if (error != Z_OK && error != Z_STREAM_END)
2627                         {
2628                                 Con_Printf ("FS_Read: Can't inflate file\n");
2629                                 break;
2630                         }
2631                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2632
2633                         // How much data did it inflate?
2634                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2635                         file->position += count;
2636
2637                         // Purge cached data
2638                         FS_Purge (file);
2639                 }
2640
2641                 done += count;
2642                 buffersize -= count;
2643         }
2644
2645         return done;
2646 }
2647
2648
2649 /*
2650 ====================
2651 FS_Print
2652
2653 Print a string into a file
2654 ====================
2655 */
2656 int FS_Print (qfile_t* file, const char *msg)
2657 {
2658         return (int)FS_Write (file, msg, strlen (msg));
2659 }
2660
2661 /*
2662 ====================
2663 FS_Printf
2664
2665 Print a string into a file
2666 ====================
2667 */
2668 int FS_Printf(qfile_t* file, const char* format, ...)
2669 {
2670         int result;
2671         va_list args;
2672
2673         va_start (args, format);
2674         result = FS_VPrintf (file, format, args);
2675         va_end (args);
2676
2677         return result;
2678 }
2679
2680
2681 /*
2682 ====================
2683 FS_VPrintf
2684
2685 Print a string into a file
2686 ====================
2687 */
2688 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2689 {
2690         int len;
2691         fs_offset_t buff_size = MAX_INPUTLINE;
2692         char *tempbuff;
2693
2694         for (;;)
2695         {
2696                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2697                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2698                 if (len >= 0 && len < buff_size)
2699                         break;
2700                 Mem_Free (tempbuff);
2701                 buff_size *= 2;
2702         }
2703
2704         len = write (file->handle, tempbuff, len);
2705         Mem_Free (tempbuff);
2706
2707         return len;
2708 }
2709
2710
2711 /*
2712 ====================
2713 FS_Getc
2714
2715 Get the next character of a file
2716 ====================
2717 */
2718 int FS_Getc (qfile_t* file)
2719 {
2720         unsigned char c;
2721
2722         if (FS_Read (file, &c, 1) != 1)
2723                 return EOF;
2724
2725         return c;
2726 }
2727
2728
2729 /*
2730 ====================
2731 FS_UnGetc
2732
2733 Put a character back into the read buffer (only supports one character!)
2734 ====================
2735 */
2736 int FS_UnGetc (qfile_t* file, unsigned char c)
2737 {
2738         // If there's already a character waiting to be read
2739         if (file->ungetc != EOF)
2740                 return EOF;
2741
2742         file->ungetc = c;
2743         return c;
2744 }
2745
2746
2747 /*
2748 ====================
2749 FS_Seek
2750
2751 Move the position index in a file
2752 ====================
2753 */
2754 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2755 {
2756         ztoolkit_t *ztk;
2757         unsigned char* buffer;
2758         fs_offset_t buffersize;
2759
2760         // Compute the file offset
2761         switch (whence)
2762         {
2763                 case SEEK_CUR:
2764                         offset += file->position - file->buff_len + file->buff_ind;
2765                         break;
2766
2767                 case SEEK_SET:
2768                         break;
2769
2770                 case SEEK_END:
2771                         offset += file->real_length;
2772                         break;
2773
2774                 default:
2775                         return -1;
2776         }
2777         if (offset < 0 || offset > file->real_length)
2778                 return -1;
2779
2780         if(file->flags & QFILE_FLAG_DATA)
2781         {
2782                 file->position = offset;
2783                 return 0;
2784         }
2785
2786         // If we have the data in our read buffer, we don't need to actually seek
2787         if (file->position - file->buff_len <= offset && offset <= file->position)
2788         {
2789                 file->buff_ind = offset + file->buff_len - file->position;
2790                 return 0;
2791         }
2792
2793         // Purge cached data
2794         FS_Purge (file);
2795
2796         // Unpacked or uncompressed files can seek directly
2797         if (! (file->flags & QFILE_FLAG_DEFLATED))
2798         {
2799                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2800                         return -1;
2801                 file->position = offset;
2802                 return 0;
2803         }
2804
2805         // Seeking in compressed files is more a hack than anything else,
2806         // but we need to support it, so here we go.
2807         ztk = file->ztk;
2808
2809         // If we have to go back in the file, we need to restart from the beginning
2810         if (offset <= file->position)
2811         {
2812                 ztk->in_ind = 0;
2813                 ztk->in_len = 0;
2814                 ztk->in_position = 0;
2815                 file->position = 0;
2816                 lseek (file->handle, file->offset, SEEK_SET);
2817
2818                 // Reset the Zlib stream
2819                 ztk->zstream.next_in = ztk->input;
2820                 ztk->zstream.avail_in = 0;
2821                 qz_inflateReset (&ztk->zstream);
2822         }
2823
2824         // We need a big buffer to force inflating into it directly
2825         buffersize = 2 * sizeof (file->buff);
2826         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2827
2828         // Skip all data until we reach the requested offset
2829         while (offset > file->position)
2830         {
2831                 fs_offset_t diff = offset - file->position;
2832                 fs_offset_t count, len;
2833
2834                 count = (diff > buffersize) ? buffersize : diff;
2835                 len = FS_Read (file, buffer, count);
2836                 if (len != count)
2837                 {
2838                         Mem_Free (buffer);
2839                         return -1;
2840                 }
2841         }
2842
2843         Mem_Free (buffer);
2844         return 0;
2845 }
2846
2847
2848 /*
2849 ====================
2850 FS_Tell
2851
2852 Give the current position in a file
2853 ====================
2854 */
2855 fs_offset_t FS_Tell (qfile_t* file)
2856 {
2857         return file->position - file->buff_len + file->buff_ind;
2858 }
2859
2860
2861 /*
2862 ====================
2863 FS_FileSize
2864
2865 Give the total size of a file
2866 ====================
2867 */
2868 fs_offset_t FS_FileSize (qfile_t* file)
2869 {
2870         return file->real_length;
2871 }
2872
2873
2874 /*
2875 ====================
2876 FS_Purge
2877
2878 Erases any buffered input or output data
2879 ====================
2880 */
2881 void FS_Purge (qfile_t* file)
2882 {
2883         file->buff_len = 0;
2884         file->buff_ind = 0;
2885         file->ungetc = EOF;
2886 }
2887
2888
2889 /*
2890 ============
2891 FS_LoadFile
2892
2893 Filename are relative to the quake directory.
2894 Always appends a 0 byte.
2895 ============
2896 */
2897 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2898 {
2899         qfile_t *file;
2900         unsigned char *buf = NULL;
2901         fs_offset_t filesize = 0;
2902
2903         file = FS_OpenVirtualFile(path, quiet);
2904         if (file)
2905         {
2906                 filesize = file->real_length;
2907                 if(filesize < 0)
2908                 {
2909                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
2910                         FS_Close(file);
2911                         return NULL;
2912                 }
2913
2914                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2915                 buf[filesize] = '\0';
2916                 FS_Read (file, buf, filesize);
2917                 FS_Close (file);
2918                 if (developer_loadfile.integer)
2919                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2920         }
2921
2922         if (filesizepointer)
2923                 *filesizepointer = filesize;
2924         return buf;
2925 }
2926
2927
2928 /*
2929 ============
2930 FS_WriteFile
2931
2932 The filename will be prefixed by the current game directory
2933 ============
2934 */
2935 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
2936 {
2937         qfile_t *file;
2938         size_t i;
2939         fs_offset_t lentotal;
2940
2941         file = FS_OpenRealFile(filename, "wb", false);
2942         if (!file)
2943         {
2944                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2945                 return false;
2946         }
2947
2948         lentotal = 0;
2949         for(i = 0; i < count; ++i)
2950                 lentotal += len[i];
2951         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
2952         for(i = 0; i < count; ++i)
2953                 FS_Write (file, data[i], len[i]);
2954         FS_Close (file);
2955         return true;
2956 }
2957
2958 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
2959 {
2960         return FS_WriteFileInBlocks(filename, &data, &len, 1);
2961 }
2962
2963
2964 /*
2965 =============================================================================
2966
2967 OTHERS PUBLIC FUNCTIONS
2968
2969 =============================================================================
2970 */
2971
2972 /*
2973 ============
2974 FS_StripExtension
2975 ============
2976 */
2977 void FS_StripExtension (const char *in, char *out, size_t size_out)
2978 {
2979         char *last = NULL;
2980         char currentchar;
2981
2982         if (size_out == 0)
2983                 return;
2984
2985         while ((currentchar = *in) && size_out > 1)
2986         {
2987                 if (currentchar == '.')
2988                         last = out;
2989                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2990                         last = NULL;
2991                 *out++ = currentchar;
2992                 in++;
2993                 size_out--;
2994         }
2995         if (last)
2996                 *last = 0;
2997         else
2998                 *out = 0;
2999 }
3000
3001
3002 /*
3003 ==================
3004 FS_DefaultExtension
3005 ==================
3006 */
3007 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3008 {
3009         const char *src;
3010
3011         // if path doesn't have a .EXT, append extension
3012         // (extension should include the .)
3013         src = path + strlen(path) - 1;
3014
3015         while (*src != '/' && src != path)
3016         {
3017                 if (*src == '.')
3018                         return;                 // it has an extension
3019                 src--;
3020         }
3021
3022         strlcat (path, extension, size_path);
3023 }
3024
3025
3026 /*
3027 ==================
3028 FS_FileType
3029
3030 Look for a file in the packages and in the filesystem
3031 ==================
3032 */
3033 int FS_FileType (const char *filename)
3034 {
3035         searchpath_t *search;
3036         char fullpath[MAX_OSPATH];
3037
3038         search = FS_FindFile (filename, NULL, true);
3039         if(!search)
3040                 return FS_FILETYPE_NONE;
3041
3042         if(search->pack && !search->pack->vpack)
3043                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3044
3045         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3046         return FS_SysFileType(fullpath);
3047 }
3048
3049
3050 /*
3051 ==================
3052 FS_FileExists
3053
3054 Look for a file in the packages and in the filesystem
3055 ==================
3056 */
3057 qboolean FS_FileExists (const char *filename)
3058 {
3059         return (FS_FindFile (filename, NULL, true) != NULL);
3060 }
3061
3062
3063 /*
3064 ==================
3065 FS_SysFileExists
3066
3067 Look for a file in the filesystem only
3068 ==================
3069 */
3070 int FS_SysFileType (const char *path)
3071 {
3072 #if WIN32
3073 // Sajt - some older sdks are missing this define
3074 # ifndef INVALID_FILE_ATTRIBUTES
3075 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3076 # endif
3077
3078         DWORD result = GetFileAttributes(path);
3079
3080         if(result == INVALID_FILE_ATTRIBUTES)
3081                 return FS_FILETYPE_NONE;
3082
3083         if(result & FILE_ATTRIBUTE_DIRECTORY)
3084                 return FS_FILETYPE_DIRECTORY;
3085
3086         return FS_FILETYPE_FILE;
3087 #else
3088         struct stat buf;
3089
3090         if (stat (path,&buf) == -1)
3091                 return FS_FILETYPE_NONE;
3092
3093 #ifndef S_ISDIR
3094 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3095 #endif
3096         if(S_ISDIR(buf.st_mode))
3097                 return FS_FILETYPE_DIRECTORY;
3098
3099         return FS_FILETYPE_FILE;
3100 #endif
3101 }
3102
3103 qboolean FS_SysFileExists (const char *path)
3104 {
3105         return FS_SysFileType (path) != FS_FILETYPE_NONE;
3106 }
3107
3108 void FS_mkdir (const char *path)
3109 {
3110 #if WIN32
3111         _mkdir (path);
3112 #else
3113         mkdir (path, 0777);
3114 #endif
3115 }
3116
3117 /*
3118 ===========
3119 FS_Search
3120
3121 Allocate and fill a search structure with information on matching filenames.
3122 ===========
3123 */
3124 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3125 {
3126         fssearch_t *search;
3127         searchpath_t *searchpath;
3128         pack_t *pak;
3129         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3130         stringlist_t resultlist;
3131         stringlist_t dirlist;
3132         const char *slash, *backslash, *colon, *separator;
3133         char *basepath;
3134         char temp[MAX_OSPATH];
3135
3136         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3137                 ;
3138
3139         if (i > 0)
3140         {
3141                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3142                 return NULL;
3143         }
3144
3145         stringlistinit(&resultlist);
3146         stringlistinit(&dirlist);
3147         search = NULL;
3148         slash = strrchr(pattern, '/');
3149         backslash = strrchr(pattern, '\\');
3150         colon = strrchr(pattern, ':');
3151         separator = max(slash, backslash);
3152         separator = max(separator, colon);
3153         basepathlength = separator ? (separator + 1 - pattern) : 0;
3154         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3155         if (basepathlength)
3156                 memcpy(basepath, pattern, basepathlength);
3157         basepath[basepathlength] = 0;
3158
3159         // search through the path, one element at a time
3160         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3161         {
3162                 // is the element a pak file?
3163                 if (searchpath->pack && !searchpath->pack->vpack)
3164                 {
3165                         // look through all the pak file elements
3166                         pak = searchpath->pack;
3167                         for (i = 0;i < pak->numfiles;i++)
3168                         {
3169                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
3170                                 while (temp[0])
3171                                 {
3172                                         if (matchpattern(temp, (char *)pattern, true))
3173                                         {
3174                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3175                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
3176                                                                 break;
3177                                                 if (resultlistindex == resultlist.numstrings)
3178                                                 {
3179                                                         stringlistappend(&resultlist, temp);
3180                                                         if (!quiet && developer_loading.integer)
3181                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3182                                                 }
3183                                         }
3184                                         // strip off one path element at a time until empty
3185                                         // this way directories are added to the listing if they match the pattern
3186                                         slash = strrchr(temp, '/');
3187                                         backslash = strrchr(temp, '\\');
3188                                         colon = strrchr(temp, ':');
3189                                         separator = temp;
3190                                         if (separator < slash)
3191                                                 separator = slash;
3192                                         if (separator < backslash)
3193                                                 separator = backslash;
3194                                         if (separator < colon)
3195                                                 separator = colon;
3196                                         *((char *)separator) = 0;
3197                                 }
3198                         }
3199                 }
3200                 else
3201                 {
3202                         stringlist_t matchedSet, foundSet;
3203                         const char *start = pattern;
3204
3205                         stringlistinit(&matchedSet);
3206                         stringlistinit(&foundSet);
3207                         // add a first entry to the set
3208                         stringlistappend(&matchedSet, "");
3209                         // iterate through pattern's path
3210                         while (*start)
3211                         {
3212                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3213                                 char subpath[MAX_OSPATH];
3214                                 char subpattern[MAX_OSPATH];
3215
3216                                 // find the next wildcard
3217                                 wildcard = strchr(start, '?');
3218                                 asterisk = strchr(start, '*');
3219                                 if (asterisk && (!wildcard || asterisk < wildcard))
3220                                 {
3221                                         wildcard = asterisk;
3222                                 }
3223
3224                                 if (wildcard)
3225                                 {
3226                                         nextseparator = strchr( wildcard, '/' );
3227                                 }
3228                                 else
3229                                 {
3230                                         nextseparator = NULL;
3231                                 }
3232
3233                                 if( !nextseparator ) {
3234                                         nextseparator = start + strlen( start );
3235                                 }
3236
3237                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3238                                 // copy everything up except nextseperator
3239                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3240                                 // find the last '/' before the wildcard
3241                                 prevseparator = strrchr( subpattern, '/' );
3242                                 if (!prevseparator)
3243                                         prevseparator = subpattern;
3244                                 else
3245                                         prevseparator++;
3246                                 // copy everything from start to the previous including the '/' (before the wildcard)
3247                                 // everything up to start is already included in the path of matchedSet's entries
3248                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3249
3250                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3251                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3252                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3253                                         strlcat( temp, subpath, sizeof(temp) );
3254                                         listdirectory( &foundSet, searchpath->filename, temp );
3255                                 }
3256                                 if( dirlistindex == 0 ) {
3257                                         break;
3258                                 }
3259                                 // reset the current result set
3260                                 stringlistfreecontents( &matchedSet );
3261                                 // match against the pattern
3262                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3263                                         const char *direntry = foundSet.strings[ dirlistindex ];
3264                                         if (matchpattern(direntry, subpattern, true)) {
3265                                                 stringlistappend( &matchedSet, direntry );
3266                                         }
3267                                 }
3268                                 stringlistfreecontents( &foundSet );
3269
3270                                 start = nextseparator;
3271                         }
3272
3273                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3274                         {
3275                                 const char *temp = matchedSet.strings[dirlistindex];
3276                                 if (matchpattern(temp, (char *)pattern, true))
3277                                 {
3278                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3279                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
3280                                                         break;
3281                                         if (resultlistindex == resultlist.numstrings)
3282                                         {
3283                                                 stringlistappend(&resultlist, temp);
3284                                                 if (!quiet && developer_loading.integer)
3285                                                         Con_Printf("SearchDirFile: %s\n", temp);
3286                                         }
3287                                 }
3288                         }
3289                         stringlistfreecontents( &matchedSet );
3290                 }
3291         }
3292
3293         if (resultlist.numstrings)
3294         {
3295                 stringlistsort(&resultlist);
3296                 numfiles = resultlist.numstrings;
3297                 numchars = 0;
3298                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3299                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3300                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3301                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3302                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3303                 search->numfilenames = (int)numfiles;
3304                 numfiles = 0;
3305                 numchars = 0;
3306                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3307                 {
3308                         size_t textlen;
3309                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
3310                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3311                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3312                         numfiles++;
3313                         numchars += (int)textlen;
3314                 }
3315         }
3316         stringlistfreecontents(&resultlist);
3317
3318         Mem_Free(basepath);
3319         return search;
3320 }
3321
3322 void FS_FreeSearch(fssearch_t *search)
3323 {
3324         Z_Free(search);
3325 }
3326
3327 extern int con_linewidth;
3328 int FS_ListDirectory(const char *pattern, int oneperline)
3329 {
3330         int numfiles;
3331         int numcolumns;
3332         int numlines;
3333         int columnwidth;
3334         int linebufpos;
3335         int i, j, k, l;
3336         const char *name;
3337         char linebuf[MAX_INPUTLINE];
3338         fssearch_t *search;
3339         search = FS_Search(pattern, true, true);
3340         if (!search)
3341                 return 0;
3342         numfiles = search->numfilenames;
3343         if (!oneperline)
3344         {
3345                 // FIXME: the names could be added to one column list and then
3346                 // gradually shifted into the next column if they fit, and then the
3347                 // next to make a compact variable width listing but it's a lot more
3348                 // complicated...
3349                 // find width for columns
3350                 columnwidth = 0;
3351                 for (i = 0;i < numfiles;i++)
3352                 {
3353                         l = (int)strlen(search->filenames[i]);
3354                         if (columnwidth < l)
3355                                 columnwidth = l;
3356                 }
3357                 // count the spacing character
3358                 columnwidth++;
3359                 // calculate number of columns
3360                 numcolumns = con_linewidth / columnwidth;
3361                 // don't bother with the column printing if it's only one column
3362                 if (numcolumns >= 2)
3363                 {
3364                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3365                         for (i = 0;i < numlines;i++)
3366                         {
3367                                 linebufpos = 0;
3368                                 for (k = 0;k < numcolumns;k++)
3369                                 {
3370                                         l = i * numcolumns + k;
3371                                         if (l < numfiles)
3372                                         {
3373                                                 name = search->filenames[l];
3374                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3375                                                         linebuf[linebufpos++] = name[j];
3376                                                 // space out name unless it's the last on the line
3377                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
3378                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3379                                                                 linebuf[linebufpos++] = ' ';
3380                                         }
3381                                 }
3382                                 linebuf[linebufpos] = 0;
3383                                 Con_Printf("%s\n", linebuf);
3384                         }
3385                 }
3386                 else
3387                         oneperline = true;
3388         }
3389         if (oneperline)
3390                 for (i = 0;i < numfiles;i++)
3391                         Con_Printf("%s\n", search->filenames[i]);
3392         FS_FreeSearch(search);
3393         return (int)numfiles;
3394 }
3395
3396 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3397 {
3398         const char *pattern;
3399         if (Cmd_Argc() > 3)
3400         {
3401                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3402                 return;
3403         }
3404         if (Cmd_Argc() == 2)
3405                 pattern = Cmd_Argv(1);
3406         else
3407                 pattern = "*";
3408         if (!FS_ListDirectory(pattern, oneperline))
3409                 Con_Print("No files found.\n");
3410 }
3411
3412 void FS_Dir_f(void)
3413 {
3414         FS_ListDirectoryCmd("dir", true);
3415 }
3416
3417 void FS_Ls_f(void)
3418 {
3419         FS_ListDirectoryCmd("ls", false);
3420 }
3421
3422 void FS_Which_f(void)
3423 {
3424         const char *filename;
3425         int index;
3426         searchpath_t *sp;
3427         if (Cmd_Argc() != 2)
3428         {
3429                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3430                 return;
3431         }  
3432         filename = Cmd_Argv(1);
3433         sp = FS_FindFile(filename, &index, true);
3434         if (!sp) {
3435                 Con_Printf("%s isn't anywhere\n", filename);
3436                 return;
3437         }
3438         if (sp->pack)
3439         {
3440                 if(sp->pack->vpack)
3441                         Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3442                 else
3443                         Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3444         }
3445         else
3446                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3447 }
3448
3449
3450 const char *FS_WhichPack(const char *filename)
3451 {
3452         int index;
3453         searchpath_t *sp = FS_FindFile(filename, &index, true);
3454         if(sp && sp->pack)
3455                 return sp->pack->shortname;
3456         else
3457                 return 0;
3458 }
3459
3460 /*
3461 ====================
3462 FS_IsRegisteredQuakePack
3463
3464 Look for a proof of purchase file file in the requested package
3465
3466 If it is found, this file should NOT be downloaded.
3467 ====================
3468 */
3469 qboolean FS_IsRegisteredQuakePack(const char *name)