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