]> de.git.xonotic.org Git - xonotic/darkplaces.git/blob - fs.c
41f279cc8ab725d4ae56740e95deb510748a8802
[xonotic/darkplaces.git] / fs.c
1 /*
2         DarkPlaces file system
3
4         Copyright (C) 2003-2006 Mathieu Olivier
5
6         This program is free software; you can redistribute it and/or
7         modify it under the terms of the GNU General Public License
8         as published by the Free Software Foundation; either version 2
9         of the License, or (at your option) any later version.
10
11         This program is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15         See the GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to:
19
20                 Free Software Foundation, Inc.
21                 59 Temple Place - Suite 330
22                 Boston, MA  02111-1307, USA
23 */
24
25 #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_QPATH];
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 "-condebug" is in the command line, remove the previous log file
1252         if (COM_CheckParm ("-condebug") != 0)
1253                 unlink (va("%s/qconsole.log", fs_gamedir));
1254
1255         // look for the pop.lmp file and set registered to true if it is found
1256         if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1257         {
1258                 if (fs_modified)
1259                         Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1260                 else
1261                         Con_Print("Playing shareware version.\n");
1262         }
1263         else
1264         {
1265                 Cvar_Set ("registered", "1");
1266                 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1267                         Con_Print("Playing registered version.\n");
1268         }
1269
1270         // unload all wads so that future queries will return the new data
1271         W_UnloadAll();
1272 }
1273
1274 void FS_Rescan_f(void)
1275 {
1276         FS_Rescan();
1277 }
1278
1279 /*
1280 ================
1281 FS_ChangeGameDirs
1282 ================
1283 */
1284 extern void Host_SaveConfig (void);
1285 extern void Host_LoadConfig_f (void);
1286 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1287 {
1288         int i;
1289         const char *p;
1290
1291         if (fs_numgamedirs == numgamedirs)
1292         {
1293                 for (i = 0;i < numgamedirs;i++)
1294                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1295                                 break;
1296                 if (i == numgamedirs)
1297                         return true; // already using this set of gamedirs, do nothing
1298         }
1299
1300         if (numgamedirs > MAX_GAMEDIRS)
1301         {
1302                 if (complain)
1303                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1304                 return false; // too many gamedirs
1305         }
1306
1307         for (i = 0;i < numgamedirs;i++)
1308         {
1309                 // if string is nasty, reject it
1310                 p = FS_CheckGameDir(gamedirs[i]);
1311                 if(!p)
1312                 {
1313                         if (complain)
1314                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1315                         return false; // nasty gamedirs
1316                 }
1317                 if(p == fs_checkgamedir_missing && failmissing)
1318                 {
1319                         if (complain)
1320                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1321                         return false; // missing gamedirs
1322                 }
1323         }
1324
1325         Host_SaveConfig();
1326
1327         fs_numgamedirs = numgamedirs;
1328         for (i = 0;i < fs_numgamedirs;i++)
1329                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1330
1331         // reinitialize filesystem to detect the new paks
1332         FS_Rescan();
1333
1334         // exec the new config
1335         Host_LoadConfig_f();
1336
1337         // unload all sounds so they will be reloaded from the new files as needed
1338         S_UnloadAllSounds_f();
1339
1340         // reinitialize renderer (this reloads hud/console background/etc)
1341         R_Modules_Restart();
1342
1343         return true;
1344 }
1345
1346 /*
1347 ================
1348 FS_GameDir_f
1349 ================
1350 */
1351 void FS_GameDir_f (void)
1352 {
1353         int i;
1354         int numgamedirs;
1355         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1356
1357         if (Cmd_Argc() < 2)
1358         {
1359                 Con_Printf("gamedirs active:");
1360                 for (i = 0;i < fs_numgamedirs;i++)
1361                         Con_Printf(" %s", fs_gamedirs[i]);
1362                 Con_Printf("\n");
1363                 return;
1364         }
1365
1366         numgamedirs = Cmd_Argc() - 1;
1367         if (numgamedirs > MAX_GAMEDIRS)
1368         {
1369                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1370                 return;
1371         }
1372
1373         for (i = 0;i < numgamedirs;i++)
1374                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1375
1376         if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1377         {
1378                 // actually, changing during game would work fine, but would be stupid
1379                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1380                 return;
1381         }
1382
1383         // halt demo playback to close the file
1384         CL_Disconnect();
1385
1386         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1387 }
1388
1389 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking);
1390 static const char *FS_SysCheckGameDir(const char *gamedir)
1391 {
1392         static char buf[8192];
1393         qboolean success;
1394         qfile_t *f;
1395         stringlist_t list;
1396         fs_offset_t n;
1397
1398         stringlistinit(&list);
1399         listdirectory(&list, gamedir, "");
1400         success = list.numstrings > 0;
1401         stringlistfreecontents(&list);
1402
1403         if(success)
1404         {
1405                 f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false);
1406                 if(f)
1407                 {
1408                         n = FS_Read (f, buf, sizeof(buf) - 1);
1409                         if(n >= 0)
1410                                 buf[n] = 0;
1411                         else
1412                                 *buf = 0;
1413                         FS_Close(f);
1414                 }
1415                 else
1416                         *buf = 0;
1417                 return buf;
1418         }
1419
1420         return NULL;
1421 }
1422
1423 /*
1424 ================
1425 FS_CheckGameDir
1426 ================
1427 */
1428 const char *FS_CheckGameDir(const char *gamedir)
1429 {
1430         const char *ret;
1431
1432         if (FS_CheckNastyPath(gamedir, true))
1433                 return NULL;
1434
1435         ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir));
1436         if(ret)
1437         {
1438                 if(!*ret)
1439                 {
1440                         // get description from basedir
1441                         ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1442                         if(ret)
1443                                 return ret;
1444                         return "";
1445                 }
1446                 return ret;
1447         }
1448
1449         ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1450         if(ret)
1451                 return ret;
1452         
1453         return fs_checkgamedir_missing;
1454 }
1455
1456 static void FS_ListGameDirs()
1457 {
1458         stringlist_t list, list2;
1459         int i, j;
1460         const char *info;
1461
1462         fs_all_gamedirs_count = 0;
1463         if(fs_all_gamedirs)
1464                 Mem_Free(fs_all_gamedirs);
1465
1466         stringlistinit(&list);
1467         listdirectory(&list, va("%s/", fs_basedir), "");
1468         listdirectory(&list, va("%s/", fs_userdir), "");
1469         stringlistsort(&list);
1470
1471         stringlistinit(&list2);
1472         for(i = 0; i < list.numstrings; ++i)
1473         {
1474                 if(i)
1475                         if(!strcmp(list.strings[i-1], list.strings[i]))
1476                                 continue;
1477                 info = FS_CheckGameDir(list.strings[i]);
1478                 if(!info)
1479                         continue;
1480                 if(info == fs_checkgamedir_missing)
1481                         continue;
1482                 if(!*info)
1483                         continue;
1484                 stringlistappend(&list2, list.strings[i]); 
1485         }
1486         stringlistfreecontents(&list);
1487
1488         fs_all_gamedirs = Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1489         for(i = 0; i < list2.numstrings; ++i)
1490         {
1491                 info = FS_CheckGameDir(list2.strings[i]);
1492                 // all this cannot happen any more, but better be safe than sorry
1493                 if(!info)
1494                         continue;
1495                 if(info == fs_checkgamedir_missing)
1496                         continue;
1497                 if(!*info)
1498                         continue;
1499                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name));
1500                 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description));
1501                 ++fs_all_gamedirs_count;
1502         }
1503 }
1504
1505 /*
1506 ================
1507 FS_Init
1508 ================
1509 */
1510 void FS_Init (void)
1511 {
1512         const char *p;
1513         int i;
1514 #ifdef WIN32
1515         TCHAR mydocsdir[MAX_PATH + 1];
1516 #if _MSC_VER >= 1400
1517         size_t homedirlen;
1518 #endif
1519 #endif
1520         char *homedir;
1521
1522 #ifdef WIN32
1523         const char* dllnames [] =
1524         {
1525                 "shfolder.dll",  // IE 4, or Win NT and higher
1526                 NULL
1527         };
1528         Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1529         // don't care for the result; if it fails, %USERPROFILE% will be used instead
1530 #endif
1531
1532         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1533
1534         // Add the personal game directory
1535         if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1536         {
1537                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
1538         }
1539         else if(COM_CheckParm("-nohome"))
1540         {
1541                 *fs_userdir = 0;
1542         }
1543         else
1544         {
1545 #ifdef WIN32
1546                 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1547                 {
1548                         dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1549                         Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", fs_userdir);
1550                 }
1551                 else
1552                 {
1553                         // use the environment
1554 #if _MSC_VER >= 1400
1555                         _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1556 #else
1557                         homedir = getenv("USERPROFILE");
1558 #endif
1559
1560                         if(homedir)
1561                         {
1562                                 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1563 #if _MSC_VER >= 1400
1564                                 free(homedir);
1565 #endif
1566                                 Con_DPrintf("Obtained personal directory %s from environment\n", fs_userdir);
1567                         }
1568                 }
1569
1570                 if(!*fs_userdir)
1571                         Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1572 #else
1573                 homedir = getenv ("HOME");
1574                 if(homedir)
1575                         dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/.%s/", homedir, gameuserdirname);
1576
1577                 if(!*fs_userdir)
1578                         Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1579 #endif
1580
1581 #ifdef WIN32
1582                 if(!COM_CheckParm("-mygames"))
1583                 {
1584 #if _MSC_VER >= 1400
1585                         int fd;
1586                         _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!
1587 #else
1588                         int fd = open (va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1589 #endif
1590                         if(fd >= 0)
1591                         {
1592                                 close(fd);
1593                                 *fs_userdir = 0; // we have write access to the game dir, so let's use it
1594                         }
1595                 }
1596 #endif
1597         }
1598
1599         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1600
1601 // If the base directory is explicitly defined by the compilation process
1602 #ifdef DP_FS_BASEDIR
1603         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1604 #else
1605         *fs_basedir = 0;
1606
1607 #ifdef MACOSX
1608         // FIXME: is there a better way to find the directory outside the .app?
1609         if (strstr(com_argv[0], ".app/"))
1610         {
1611                 char *split;
1612
1613                 split = strstr(com_argv[0], ".app/");
1614                 while (split > com_argv[0] && *split != '/')
1615                         split--;
1616                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1617                 fs_basedir[split - com_argv[0]] = 0;
1618         }
1619 #endif
1620 #endif
1621
1622         PK3_OpenLibrary ();
1623
1624         // -basedir <path>
1625         // Overrides the system supplied base directory (under GAMENAME)
1626 // 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)
1627         i = COM_CheckParm ("-basedir");
1628         if (i && i < com_argc-1)
1629         {
1630                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1631                 i = (int)strlen (fs_basedir);
1632                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1633                         fs_basedir[i-1] = 0;
1634         }
1635
1636         // add a path separator to the end of the basedir if it lacks one
1637         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1638                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1639
1640         FS_ListGameDirs();
1641
1642         p = FS_CheckGameDir(gamedirname1);
1643         if(!p || p == fs_checkgamedir_missing)
1644                 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1645
1646         if(gamedirname2)
1647         {
1648                 p = FS_CheckGameDir(gamedirname2);
1649                 if(!p || p == fs_checkgamedir_missing)
1650                         Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1651         }
1652
1653         // -game <gamedir>
1654         // Adds basedir/gamedir as an override game
1655         // LordHavoc: now supports multiple -game directories
1656         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1657         {
1658                 if (!com_argv[i])
1659                         continue;
1660                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1661                 {
1662                         i++;
1663                         p = FS_CheckGameDir(com_argv[i]);
1664                         if(!p)
1665                                 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
1666                         if(p == fs_checkgamedir_missing)
1667                                 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1668                         // add the gamedir to the list of active gamedirs
1669                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1670                         fs_numgamedirs++;
1671                 }
1672         }
1673
1674         // generate the searchpath
1675         FS_Rescan();
1676 }
1677
1678 void FS_Init_Commands(void)
1679 {
1680         Cvar_RegisterVariable (&scr_screenshot_name);
1681         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1682         Cvar_RegisterVariable (&cvar_fs_gamedir);
1683
1684         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1685         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1686         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1687         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1688         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1689         Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
1690 }
1691
1692 /*
1693 ================
1694 FS_Shutdown
1695 ================
1696 */
1697 void FS_Shutdown (void)
1698 {
1699         // close all pack files and such
1700         // (hopefully there aren't any other open files, but they'll be cleaned up
1701         //  by the OS anyway)
1702         FS_ClearSearchPath();
1703         Mem_FreePool (&fs_mempool);
1704
1705 #ifdef WIN32
1706         Sys_UnloadLibrary (&shfolder_dll);
1707 #endif
1708 }
1709
1710 /*
1711 ====================
1712 FS_SysOpen
1713
1714 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1715 ====================
1716 */
1717 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1718 {
1719         qfile_t* file;
1720         int mod, opt;
1721         unsigned int ind;
1722
1723         // Parse the mode string
1724         switch (mode[0])
1725         {
1726                 case 'r':
1727                         mod = O_RDONLY;
1728                         opt = 0;
1729                         break;
1730                 case 'w':
1731                         mod = O_WRONLY;
1732                         opt = O_CREAT | O_TRUNC;
1733                         break;
1734                 case 'a':
1735                         mod = O_WRONLY;
1736                         opt = O_CREAT | O_APPEND;
1737                         break;
1738                 default:
1739                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1740                         return NULL;
1741         }
1742         for (ind = 1; mode[ind] != '\0'; ind++)
1743         {
1744                 switch (mode[ind])
1745                 {
1746                         case '+':
1747                                 mod = O_RDWR;
1748                                 break;
1749                         case 'b':
1750                                 opt |= O_BINARY;
1751                                 break;
1752                         default:
1753                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1754                                                         filepath, mode, mode[ind]);
1755                 }
1756         }
1757
1758         if (nonblocking)
1759                 opt |= O_NONBLOCK;
1760
1761         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1762         memset (file, 0, sizeof (*file));
1763         file->ungetc = EOF;
1764
1765 #if _MSC_VER >= 1400
1766         _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1767 #else
1768         file->handle = open (filepath, mod | opt, 0666);
1769 #endif
1770         if (file->handle < 0)
1771         {
1772                 Mem_Free (file);
1773                 return NULL;
1774         }
1775
1776         file->real_length = lseek (file->handle, 0, SEEK_END);
1777
1778         // For files opened in append mode, we start at the end of the file
1779         if (mod & O_APPEND)
1780                 file->position = file->real_length;
1781         else
1782                 lseek (file->handle, 0, SEEK_SET);
1783
1784         return file;
1785 }
1786
1787
1788 /*
1789 ===========
1790 FS_OpenPackedFile
1791
1792 Open a packed file using its package file descriptor
1793 ===========
1794 */
1795 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1796 {
1797         packfile_t *pfile;
1798         int dup_handle;
1799         qfile_t* file;
1800
1801         pfile = &pack->files[pack_ind];
1802
1803         // If we don't have the true offset, get it now
1804         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1805                 if (!PK3_GetTrueFileOffset (pfile, pack))
1806                         return NULL;
1807
1808         // No Zlib DLL = no compressed files
1809         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1810         {
1811                 Con_Printf("WARNING: can't open the compressed file %s\n"
1812                                         "You need the Zlib DLL to use compressed files\n",
1813                                         pfile->name);
1814                 return NULL;
1815         }
1816
1817         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1818         // the dup() call to avoid having to close the dup_handle on error here
1819         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1820         {
1821                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1822                                         pfile->name, pack->filename, (int) pfile->offset);
1823                 return NULL;
1824         }
1825
1826         dup_handle = dup (pack->handle);
1827         if (dup_handle < 0)
1828         {
1829                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1830                 return NULL;
1831         }
1832
1833         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1834         memset (file, 0, sizeof (*file));
1835         file->handle = dup_handle;
1836         file->flags = QFILE_FLAG_PACKED;
1837         file->real_length = pfile->realsize;
1838         file->offset = pfile->offset;
1839         file->position = 0;
1840         file->ungetc = EOF;
1841
1842         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1843         {
1844                 ztoolkit_t *ztk;
1845
1846                 file->flags |= QFILE_FLAG_DEFLATED;
1847
1848                 // We need some more variables
1849                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1850
1851                 ztk->comp_length = pfile->packsize;
1852
1853                 // Initialize zlib stream
1854                 ztk->zstream.next_in = ztk->input;
1855                 ztk->zstream.avail_in = 0;
1856
1857                 /* From Zlib's "unzip.c":
1858                  *
1859                  * windowBits is passed < 0 to tell that there is no zlib header.
1860                  * Note that in this case inflate *requires* an extra "dummy" byte
1861                  * after the compressed stream in order to complete decompression and
1862                  * return Z_STREAM_END.
1863                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1864                  * size of both compressed and uncompressed data
1865                  */
1866                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1867                 {
1868                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1869                         close(dup_handle);
1870                         Mem_Free(file);
1871                         return NULL;
1872                 }
1873
1874                 ztk->zstream.next_out = file->buff;
1875                 ztk->zstream.avail_out = sizeof (file->buff);
1876
1877                 file->ztk = ztk;
1878         }
1879
1880         return file;
1881 }
1882
1883 /*
1884 ====================
1885 FS_CheckNastyPath
1886
1887 Return true if the path should be rejected due to one of the following:
1888 1: path elements that are non-portable
1889 2: path elements that would allow access to files outside the game directory,
1890    or are just not a good idea for a mod to be using.
1891 ====================
1892 */
1893 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1894 {
1895         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1896         if (!path[0])
1897                 return 2;
1898
1899         // Windows: don't allow \ in filenames (windows-only), period.
1900         // (on Windows \ is a directory separator, but / is also supported)
1901         if (strstr(path, "\\"))
1902                 return 1; // non-portable
1903
1904         // Mac: don't allow Mac-only filenames - : is a directory separator
1905         // instead of /, but we rely on / working already, so there's no reason to
1906         // support a Mac-only path
1907         // Amiga and Windows: : tries to go to root of drive
1908         if (strstr(path, ":"))
1909                 return 1; // non-portable attempt to go to root of drive
1910
1911         // Amiga: // is parent directory
1912         if (strstr(path, "//"))
1913                 return 1; // non-portable attempt to go to parent directory
1914
1915         // all: don't allow going to parent directory (../ or /../)
1916         if (strstr(path, ".."))
1917                 return 2; // attempt to go outside the game directory
1918
1919         // Windows and UNIXes: don't allow absolute paths
1920         if (path[0] == '/')
1921                 return 2; // attempt to go outside the game directory
1922
1923         // 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
1924         if (strchr(path, '.'))
1925         {
1926                 if (isgamedir)
1927                 {
1928                         // gamedir is entirely path elements, so simply forbid . entirely
1929                         return 2;
1930                 }
1931                 if (strchr(path, '.') < strrchr(path, '/'))
1932                         return 2; // possible attempt to go outside the game directory
1933         }
1934
1935         // all: forbid trailing slash on gamedir
1936         if (isgamedir && path[strlen(path)-1] == '/')
1937                 return 2;
1938
1939         // all: forbid leading dot on any filename for any reason
1940         if (strstr(path, "/."))
1941                 return 2; // attempt to go outside the game directory
1942
1943         // after all these checks we're pretty sure it's a / separated filename
1944         // and won't do much if any harm
1945         return false;
1946 }
1947
1948
1949 /*
1950 ====================
1951 FS_FindFile
1952
1953 Look for a file in the packages and in the filesystem
1954
1955 Return the searchpath where the file was found (or NULL)
1956 and the file index in the package if relevant
1957 ====================
1958 */
1959 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1960 {
1961         searchpath_t *search;
1962         pack_t *pak;
1963
1964         // search through the path, one element at a time
1965         for (search = fs_searchpaths;search;search = search->next)
1966         {
1967                 // is the element a pak file?
1968                 if (search->pack)
1969                 {
1970                         int (*strcmp_funct) (const char* str1, const char* str2);
1971                         int left, right, middle;
1972
1973                         pak = search->pack;
1974                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1975
1976                         // Look for the file (binary search)
1977                         left = 0;
1978                         right = pak->numfiles - 1;
1979                         while (left <= right)
1980                         {
1981                                 int diff;
1982
1983                                 middle = (left + right) / 2;
1984                                 diff = strcmp_funct (pak->files[middle].name, name);
1985
1986                                 // Found it
1987                                 if (!diff)
1988                                 {
1989                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1990                                         {
1991                                                 // yes, but the first one is empty so we treat it as not being there
1992                                                 if (!quiet && developer.integer >= 10)
1993                                                         Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1994
1995                                                 if (index != NULL)
1996                                                         *index = -1;
1997                                                 return NULL;
1998                                         }
1999
2000                                         if (!quiet && developer.integer >= 10)
2001                                                 Con_Printf("FS_FindFile: %s in %s\n",
2002                                                                         pak->files[middle].name, pak->filename);
2003
2004                                         if (index != NULL)
2005                                                 *index = middle;
2006                                         return search;
2007                                 }
2008
2009                                 // If we're too far in the list
2010                                 if (diff > 0)
2011                                         right = middle - 1;
2012                                 else
2013                                         left = middle + 1;
2014                         }
2015                 }
2016                 else
2017                 {
2018                         char netpath[MAX_OSPATH];
2019                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2020                         if (FS_SysFileExists (netpath))
2021                         {
2022                                 if (!quiet && developer.integer >= 10)
2023                                         Con_Printf("FS_FindFile: %s\n", netpath);
2024
2025                                 if (index != NULL)
2026                                         *index = -1;
2027                                 return search;
2028                         }
2029                 }
2030         }
2031
2032         if (!quiet && developer.integer >= 10)
2033                 Con_Printf("FS_FindFile: can't find %s\n", name);
2034
2035         if (index != NULL)
2036                 *index = -1;
2037         return NULL;
2038 }
2039
2040
2041 /*
2042 ===========
2043 FS_OpenReadFile
2044
2045 Look for a file in the search paths and open it in read-only mode
2046 ===========
2047 */
2048 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2049 {
2050         searchpath_t *search;
2051         int pack_ind;
2052
2053         search = FS_FindFile (filename, &pack_ind, quiet);
2054
2055         // Not found?
2056         if (search == NULL)
2057                 return NULL;
2058
2059         // Found in the filesystem?
2060         if (pack_ind < 0)
2061         {
2062                 char path [MAX_OSPATH];
2063                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2064                 return FS_SysOpen (path, "rb", nonblocking);
2065         }
2066
2067         // So, we found it in a package...
2068
2069         // Is it a PK3 symlink?
2070         // TODO also handle directory symlinks by parsing the whole structure...
2071         // but heck, file symlinks are good enough for now
2072         if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2073         {
2074                 if(symlinkLevels <= 0)
2075                 {
2076                         Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2077                         return NULL;
2078                 }
2079                 else
2080                 {
2081                         char linkbuf[MAX_QPATH];
2082                         fs_offset_t count;
2083                         qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2084                         const char *mergeslash;
2085                         char *mergestart;
2086
2087                         if(!linkfile)
2088                                 return NULL;
2089                         count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2090                         FS_Close(linkfile);
2091                         if(count < 0)
2092                                 return NULL;
2093                         linkbuf[count] = 0;
2094                         
2095                         // Now combine the paths...
2096                         mergeslash = strrchr(filename, '/');
2097                         mergestart = linkbuf;
2098                         if(!mergeslash)
2099                                 mergeslash = filename;
2100                         while(!strncmp(mergestart, "../", 3))
2101                         {
2102                                 mergestart += 3;
2103                                 while(mergeslash > filename)
2104                                 {
2105                                         --mergeslash;
2106                                         if(*mergeslash == '/')
2107                                                 break;
2108                                 }
2109                         }
2110                         // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2111                         if(mergeslash == filename)
2112                         {
2113                                 // Either mergeslash == filename, then we just replace the name (done below)
2114                         }
2115                         else
2116                         {
2117                                 // Or, we append the name after mergeslash;
2118                                 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2119                                 int spaceNeeded = mergeslash - filename + 1;
2120                                 int spaceRemoved = mergestart - linkbuf;
2121                                 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2122                                 {
2123                                         Con_DPrintf("symlink: too long path rejected\n");
2124                                         return NULL;
2125                                 }
2126                                 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2127                                 memcpy(linkbuf, filename, spaceNeeded);
2128                                 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2129                                 mergestart = linkbuf;
2130                         }
2131                         if (!quiet && developer_loading.integer)
2132                                 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2133                         if(FS_CheckNastyPath (mergestart, false))
2134                         {
2135                                 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2136                                 return NULL;
2137                         }
2138                         return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2139                 }
2140         }
2141
2142         return FS_OpenPackedFile (search->pack, pack_ind);
2143 }
2144
2145
2146 /*
2147 =============================================================================
2148
2149 MAIN PUBLIC FUNCTIONS
2150
2151 =============================================================================
2152 */
2153
2154 /*
2155 ====================
2156 FS_OpenRealFile
2157
2158 Open a file in the userpath. The syntax is the same as fopen
2159 Used for savegame scanning in menu, and all file writing.
2160 ====================
2161 */
2162 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2163 {
2164         char real_path [MAX_OSPATH];
2165
2166         if (FS_CheckNastyPath(filepath, false))
2167         {
2168                 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2169                 return NULL;
2170         }
2171
2172         dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
2173
2174         // If the file is opened in "write", "append", or "read/write" mode,
2175         // create directories up to the file.
2176         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2177                 FS_CreatePath (real_path);
2178         return FS_SysOpen (real_path, mode, false);
2179 }
2180
2181
2182 /*
2183 ====================
2184 FS_OpenVirtualFile
2185
2186 Open a file. The syntax is the same as fopen
2187 ====================
2188 */
2189 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2190 {
2191         if (FS_CheckNastyPath(filepath, false))
2192         {
2193                 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2194                 return NULL;
2195         }
2196
2197         return FS_OpenReadFile (filepath, quiet, false, 16);
2198 }
2199
2200
2201 /*
2202 ====================
2203 FS_FileFromData
2204
2205 Open a file. The syntax is the same as fopen
2206 ====================
2207 */
2208 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2209 {
2210         qfile_t* file;
2211         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2212         memset (file, 0, sizeof (*file));
2213         file->flags = QFILE_FLAG_DATA;
2214         file->ungetc = EOF;
2215         file->real_length = size;
2216         file->data = data;
2217         return file;
2218 }
2219
2220 /*
2221 ====================
2222 FS_Close
2223
2224 Close a file
2225 ====================
2226 */
2227 int FS_Close (qfile_t* file)
2228 {
2229         if(file->flags & QFILE_FLAG_DATA)
2230         {
2231                 Mem_Free(file);
2232                 return 0;
2233         }
2234
2235         if (close (file->handle))
2236                 return EOF;
2237
2238         if (file->ztk)
2239         {
2240                 qz_inflateEnd (&file->ztk->zstream);
2241                 Mem_Free (file->ztk);
2242         }
2243
2244         Mem_Free (file);
2245         return 0;
2246 }
2247
2248
2249 /*
2250 ====================
2251 FS_Write
2252
2253 Write "datasize" bytes into a file
2254 ====================
2255 */
2256 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2257 {
2258         fs_offset_t result;
2259
2260         // If necessary, seek to the exact file position we're supposed to be
2261         if (file->buff_ind != file->buff_len)
2262                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2263
2264         // Purge cached data
2265         FS_Purge (file);
2266
2267         // Write the buffer and update the position
2268         result = write (file->handle, data, (fs_offset_t)datasize);
2269         file->position = lseek (file->handle, 0, SEEK_CUR);
2270         if (file->real_length < file->position)
2271                 file->real_length = file->position;
2272
2273         if (result < 0)
2274                 return 0;
2275
2276         return result;
2277 }
2278
2279
2280 /*
2281 ====================
2282 FS_Read
2283
2284 Read up to "buffersize" bytes from a file
2285 ====================
2286 */
2287 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2288 {
2289         fs_offset_t count, done;
2290
2291         if (buffersize == 0)
2292                 return 0;
2293
2294         // Get rid of the ungetc character
2295         if (file->ungetc != EOF)
2296         {
2297                 ((char*)buffer)[0] = file->ungetc;
2298                 buffersize--;
2299                 file->ungetc = EOF;
2300                 done = 1;
2301         }
2302         else
2303                 done = 0;
2304
2305         if(file->flags & QFILE_FLAG_DATA)
2306         {
2307                 size_t left = file->real_length - file->position;
2308                 if(buffersize > left)
2309                         buffersize = left;
2310                 memcpy(buffer, file->data + file->position, buffersize);
2311                 file->position += buffersize;
2312                 return buffersize;
2313         }
2314
2315         // First, we copy as many bytes as we can from "buff"
2316         if (file->buff_ind < file->buff_len)
2317         {
2318                 count = file->buff_len - file->buff_ind;
2319                 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2320                 done += count;
2321                 memcpy (buffer, &file->buff[file->buff_ind], count);
2322                 file->buff_ind += count;
2323
2324                 buffersize -= count;
2325                 if (buffersize == 0)
2326                         return done;
2327         }
2328
2329         // NOTE: at this point, the read buffer is always empty
2330
2331         // If the file isn't compressed
2332         if (! (file->flags & QFILE_FLAG_DEFLATED))
2333         {
2334                 fs_offset_t nb;
2335
2336                 // We must take care to not read after the end of the file
2337                 count = file->real_length - file->position;
2338
2339                 // If we have a lot of data to get, put them directly into "buffer"
2340                 if (buffersize > sizeof (file->buff) / 2)
2341                 {
2342                         if (count > (fs_offset_t)buffersize)
2343                                 count = (fs_offset_t)buffersize;
2344                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2345                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2346                         if (nb > 0)
2347                         {
2348                                 done += nb;
2349                                 file->position += nb;
2350
2351                                 // Purge cached data
2352                                 FS_Purge (file);
2353                         }
2354                 }
2355                 else
2356                 {
2357                         if (count > (fs_offset_t)sizeof (file->buff))
2358                                 count = (fs_offset_t)sizeof (file->buff);
2359                         lseek (file->handle, file->offset + file->position, SEEK_SET);
2360                         nb = read (file->handle, file->buff, count);
2361                         if (nb > 0)
2362                         {
2363                                 file->buff_len = nb;
2364                                 file->position += nb;
2365
2366                                 // Copy the requested data in "buffer" (as much as we can)
2367                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2368                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2369                                 file->buff_ind = count;
2370                                 done += count;
2371                         }
2372                 }
2373
2374                 return done;
2375         }
2376
2377         // If the file is compressed, it's more complicated...
2378         // We cycle through a few operations until we have read enough data
2379         while (buffersize > 0)
2380         {
2381                 ztoolkit_t *ztk = file->ztk;
2382                 int error;
2383
2384                 // NOTE: at this point, the read buffer is always empty
2385
2386                 // If "input" is also empty, we need to refill it
2387                 if (ztk->in_ind == ztk->in_len)
2388                 {
2389                         // If we are at the end of the file
2390                         if (file->position == file->real_length)
2391                                 return done;
2392
2393                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2394                         if (count > (fs_offset_t)sizeof (ztk->input))
2395                                 count = (fs_offset_t)sizeof (ztk->input);
2396                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2397                         if (read (file->handle, ztk->input, count) != count)
2398                         {
2399                                 Con_Printf ("FS_Read: unexpected end of file\n");
2400                                 break;
2401                         }
2402
2403                         ztk->in_ind = 0;
2404                         ztk->in_len = count;
2405                         ztk->in_position += count;
2406                 }
2407
2408                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2409                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2410
2411                 // Now that we are sure we have compressed data available, we need to determine
2412                 // if it's better to inflate it in "file->buff" or directly in "buffer"
2413
2414                 // Inflate the data in "file->buff"
2415                 if (buffersize < sizeof (file->buff) / 2)
2416                 {
2417                         ztk->zstream.next_out = file->buff;
2418                         ztk->zstream.avail_out = sizeof (file->buff);
2419                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2420                         if (error != Z_OK && error != Z_STREAM_END)
2421                         {
2422                                 Con_Printf ("FS_Read: Can't inflate file\n");
2423                                 break;
2424                         }
2425                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2426
2427                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2428                         file->position += file->buff_len;
2429
2430                         // Copy the requested data in "buffer" (as much as we can)
2431                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2432                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2433                         file->buff_ind = count;
2434                 }
2435
2436                 // Else, we inflate directly in "buffer"
2437                 else
2438                 {
2439                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2440                         ztk->zstream.avail_out = (unsigned int)buffersize;
2441                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2442                         if (error != Z_OK && error != Z_STREAM_END)
2443                         {
2444                                 Con_Printf ("FS_Read: Can't inflate file\n");
2445                                 break;
2446                         }
2447                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2448
2449                         // How much data did it inflate?
2450                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2451                         file->position += count;
2452
2453                         // Purge cached data
2454                         FS_Purge (file);
2455                 }
2456
2457                 done += count;
2458                 buffersize -= count;
2459         }
2460
2461         return done;
2462 }
2463
2464
2465 /*
2466 ====================
2467 FS_Print
2468
2469 Print a string into a file
2470 ====================
2471 */
2472 int FS_Print (qfile_t* file, const char *msg)
2473 {
2474         return (int)FS_Write (file, msg, strlen (msg));
2475 }
2476
2477 /*
2478 ====================
2479 FS_Printf
2480
2481 Print a string into a file
2482 ====================
2483 */
2484 int FS_Printf(qfile_t* file, const char* format, ...)
2485 {
2486         int result;
2487         va_list args;
2488
2489         va_start (args, format);
2490         result = FS_VPrintf (file, format, args);
2491         va_end (args);
2492
2493         return result;
2494 }
2495
2496
2497 /*
2498 ====================
2499 FS_VPrintf
2500
2501 Print a string into a file
2502 ====================
2503 */
2504 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2505 {
2506         int len;
2507         fs_offset_t buff_size = MAX_INPUTLINE;
2508         char *tempbuff;
2509
2510         for (;;)
2511         {
2512                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2513                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2514                 if (len >= 0 && len < buff_size)
2515                         break;
2516                 Mem_Free (tempbuff);
2517                 buff_size *= 2;
2518         }
2519
2520         len = write (file->handle, tempbuff, len);
2521         Mem_Free (tempbuff);
2522
2523         return len;
2524 }
2525
2526
2527 /*
2528 ====================
2529 FS_Getc
2530
2531 Get the next character of a file
2532 ====================
2533 */
2534 int FS_Getc (qfile_t* file)
2535 {
2536         unsigned char c;
2537
2538         if (FS_Read (file, &c, 1) != 1)
2539                 return EOF;
2540
2541         return c;
2542 }
2543
2544
2545 /*
2546 ====================
2547 FS_UnGetc
2548
2549 Put a character back into the read buffer (only supports one character!)
2550 ====================
2551 */
2552 int FS_UnGetc (qfile_t* file, unsigned char c)
2553 {
2554         // If there's already a character waiting to be read
2555         if (file->ungetc != EOF)
2556                 return EOF;
2557
2558         file->ungetc = c;
2559         return c;
2560 }
2561
2562
2563 /*
2564 ====================
2565 FS_Seek
2566
2567 Move the position index in a file
2568 ====================
2569 */
2570 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2571 {
2572         ztoolkit_t *ztk;
2573         unsigned char* buffer;
2574         fs_offset_t buffersize;
2575
2576         // Compute the file offset
2577         switch (whence)
2578         {
2579                 case SEEK_CUR:
2580                         offset += file->position - file->buff_len + file->buff_ind;
2581                         break;
2582
2583                 case SEEK_SET:
2584                         break;
2585
2586                 case SEEK_END:
2587                         offset += file->real_length;
2588                         break;
2589
2590                 default:
2591                         return -1;
2592         }
2593         if (offset < 0 || offset > file->real_length)
2594                 return -1;
2595
2596         if(file->flags & QFILE_FLAG_DATA)
2597         {
2598                 file->position = offset;
2599                 return 0;
2600         }
2601
2602         // If we have the data in our read buffer, we don't need to actually seek
2603         if (file->position - file->buff_len <= offset && offset <= file->position)
2604         {
2605                 file->buff_ind = offset + file->buff_len - file->position;
2606                 return 0;
2607         }
2608
2609         // Purge cached data
2610         FS_Purge (file);
2611
2612         // Unpacked or uncompressed files can seek directly
2613         if (! (file->flags & QFILE_FLAG_DEFLATED))
2614         {
2615                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2616                         return -1;
2617                 file->position = offset;
2618                 return 0;
2619         }
2620
2621         // Seeking in compressed files is more a hack than anything else,
2622         // but we need to support it, so here we go.
2623         ztk = file->ztk;
2624
2625         // If we have to go back in the file, we need to restart from the beginning
2626         if (offset <= file->position)
2627         {
2628                 ztk->in_ind = 0;
2629                 ztk->in_len = 0;
2630                 ztk->in_position = 0;
2631                 file->position = 0;
2632                 lseek (file->handle, file->offset, SEEK_SET);
2633
2634                 // Reset the Zlib stream
2635                 ztk->zstream.next_in = ztk->input;
2636                 ztk->zstream.avail_in = 0;
2637                 qz_inflateReset (&ztk->zstream);
2638         }
2639
2640         // We need a big buffer to force inflating into it directly
2641         buffersize = 2 * sizeof (file->buff);
2642         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2643
2644         // Skip all data until we reach the requested offset
2645         while (offset > file->position)
2646         {
2647                 fs_offset_t diff = offset - file->position;
2648                 fs_offset_t count, len;
2649
2650                 count = (diff > buffersize) ? buffersize : diff;
2651                 len = FS_Read (file, buffer, count);
2652                 if (len != count)
2653                 {
2654                         Mem_Free (buffer);
2655                         return -1;
2656                 }
2657         }
2658
2659         Mem_Free (buffer);
2660         return 0;
2661 }
2662
2663
2664 /*
2665 ====================
2666 FS_Tell
2667
2668 Give the current position in a file
2669 ====================
2670 */
2671 fs_offset_t FS_Tell (qfile_t* file)
2672 {
2673         return file->position - file->buff_len + file->buff_ind;
2674 }
2675
2676
2677 /*
2678 ====================
2679 FS_FileSize
2680
2681 Give the total size of a file
2682 ====================
2683 */
2684 fs_offset_t FS_FileSize (qfile_t* file)
2685 {
2686         return file->real_length;
2687 }
2688
2689
2690 /*
2691 ====================
2692 FS_Purge
2693
2694 Erases any buffered input or output data
2695 ====================
2696 */
2697 void FS_Purge (qfile_t* file)
2698 {
2699         file->buff_len = 0;
2700         file->buff_ind = 0;
2701         file->ungetc = EOF;
2702 }
2703
2704
2705 /*
2706 ============
2707 FS_LoadFile
2708
2709 Filename are relative to the quake directory.
2710 Always appends a 0 byte.
2711 ============
2712 */
2713 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2714 {
2715         qfile_t *file;
2716         unsigned char *buf = NULL;
2717         fs_offset_t filesize = 0;
2718
2719         file = FS_OpenVirtualFile(path, quiet);
2720         if (file)
2721         {
2722                 filesize = file->real_length;
2723                 if(filesize < 0)
2724                 {
2725                         Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
2726                         FS_Close(file);
2727                         return NULL;
2728                 }
2729
2730                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2731                 buf[filesize] = '\0';
2732                 FS_Read (file, buf, filesize);
2733                 FS_Close (file);
2734                 if (developer_loadfile.integer)
2735                         Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2736         }
2737
2738         if (filesizepointer)
2739                 *filesizepointer = filesize;
2740         return buf;
2741 }
2742
2743
2744 /*
2745 ============
2746 FS_WriteFile
2747
2748 The filename will be prefixed by the current game directory
2749 ============
2750 */
2751 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2752 {
2753         qfile_t *file;
2754
2755         file = FS_OpenRealFile(filename, "wb", false);
2756         if (!file)
2757         {
2758                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2759                 return false;
2760         }
2761
2762         Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
2763         FS_Write (file, data, len);
2764         FS_Close (file);
2765         return true;
2766 }
2767
2768
2769 /*
2770 =============================================================================
2771
2772 OTHERS PUBLIC FUNCTIONS
2773
2774 =============================================================================
2775 */
2776
2777 /*
2778 ============
2779 FS_StripExtension
2780 ============
2781 */
2782 void FS_StripExtension (const char *in, char *out, size_t size_out)
2783 {
2784         char *last = NULL;
2785         char currentchar;
2786
2787         if (size_out == 0)
2788                 return;
2789
2790         while ((currentchar = *in) && size_out > 1)
2791         {
2792                 if (currentchar == '.')
2793                         last = out;
2794                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2795                         last = NULL;
2796                 *out++ = currentchar;
2797                 in++;
2798                 size_out--;
2799         }
2800         if (last)
2801                 *last = 0;
2802         else
2803                 *out = 0;
2804 }
2805
2806
2807 /*
2808 ==================
2809 FS_DefaultExtension
2810 ==================
2811 */
2812 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2813 {
2814         const char *src;
2815
2816         // if path doesn't have a .EXT, append extension
2817         // (extension should include the .)
2818         src = path + strlen(path) - 1;
2819
2820         while (*src != '/' && src != path)
2821         {
2822                 if (*src == '.')
2823                         return;                 // it has an extension
2824                 src--;
2825         }
2826
2827         strlcat (path, extension, size_path);
2828 }
2829
2830
2831 /*
2832 ==================
2833 FS_FileType
2834
2835 Look for a file in the packages and in the filesystem
2836 ==================
2837 */
2838 int FS_FileType (const char *filename)
2839 {
2840         searchpath_t *search;
2841         char fullpath[MAX_QPATH];
2842
2843         search = FS_FindFile (filename, NULL, true);
2844         if(!search)
2845                 return FS_FILETYPE_NONE;
2846
2847         if(search->pack)
2848                 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
2849
2850         dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
2851         return FS_SysFileType(fullpath);
2852 }
2853
2854
2855 /*
2856 ==================
2857 FS_FileExists
2858
2859 Look for a file in the packages and in the filesystem
2860 ==================
2861 */
2862 qboolean FS_FileExists (const char *filename)
2863 {
2864         return (FS_FindFile (filename, NULL, true) != NULL);
2865 }
2866
2867
2868 /*
2869 ==================
2870 FS_SysFileExists
2871
2872 Look for a file in the filesystem only
2873 ==================
2874 */
2875 int FS_SysFileType (const char *path)
2876 {
2877 #if WIN32
2878 // Sajt - some older sdks are missing this define
2879 # ifndef INVALID_FILE_ATTRIBUTES
2880 #  define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
2881 # endif
2882
2883         DWORD result = GetFileAttributes(path);
2884
2885         if(result == INVALID_FILE_ATTRIBUTES)
2886                 return FS_FILETYPE_NONE;
2887
2888         if(result & FILE_ATTRIBUTE_DIRECTORY)
2889                 return FS_FILETYPE_DIRECTORY;
2890
2891         return FS_FILETYPE_FILE;
2892 #else
2893         struct stat buf;
2894
2895         if (stat (path,&buf) == -1)
2896                 return FS_FILETYPE_NONE;
2897
2898         if(S_ISDIR(buf.st_mode))
2899                 return FS_FILETYPE_DIRECTORY;
2900
2901         return FS_FILETYPE_FILE;
2902 #endif
2903 }
2904
2905 qboolean FS_SysFileExists (const char *path)
2906 {
2907         return FS_SysFileType (path) != FS_FILETYPE_NONE;
2908 }
2909
2910 void FS_mkdir (const char *path)
2911 {
2912 #if WIN32
2913         _mkdir (path);
2914 #else
2915         mkdir (path, 0777);
2916 #endif
2917 }
2918
2919 /*
2920 ===========
2921 FS_Search
2922
2923 Allocate and fill a search structure with information on matching filenames.
2924 ===========
2925 */
2926 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2927 {
2928         fssearch_t *search;
2929         searchpath_t *searchpath;
2930         pack_t *pak;
2931         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2932         stringlist_t resultlist;
2933         stringlist_t dirlist;
2934         const char *slash, *backslash, *colon, *separator;
2935         char *basepath;
2936         char temp[MAX_OSPATH];
2937
2938         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2939                 ;
2940
2941         if (i > 0)
2942         {
2943                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2944                 return NULL;
2945         }
2946
2947         stringlistinit(&resultlist);
2948         stringlistinit(&dirlist);
2949         search = NULL;
2950         slash = strrchr(pattern, '/');
2951         backslash = strrchr(pattern, '\\');
2952         colon = strrchr(pattern, ':');
2953         separator = max(slash, backslash);
2954         separator = max(separator, colon);
2955         basepathlength = separator ? (separator + 1 - pattern) : 0;
2956         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2957         if (basepathlength)
2958                 memcpy(basepath, pattern, basepathlength);
2959         basepath[basepathlength] = 0;
2960
2961         // search through the path, one element at a time
2962         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2963         {
2964                 // is the element a pak file?
2965                 if (searchpath->pack)
2966                 {
2967                         // look through all the pak file elements
2968                         pak = searchpath->pack;
2969                         for (i = 0;i < pak->numfiles;i++)
2970                         {
2971                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
2972                                 while (temp[0])
2973                                 {
2974                                         if (matchpattern(temp, (char *)pattern, true))
2975                                         {
2976                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2977                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
2978                                                                 break;
2979                                                 if (resultlistindex == resultlist.numstrings)
2980                                                 {
2981                                                         stringlistappend(&resultlist, temp);
2982                                                         if (!quiet && developer_loading.integer)
2983                                                                 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2984                                                 }
2985                                         }
2986                                         // strip off one path element at a time until empty
2987                                         // this way directories are added to the listing if they match the pattern
2988                                         slash = strrchr(temp, '/');
2989                                         backslash = strrchr(temp, '\\');
2990                                         colon = strrchr(temp, ':');
2991                                         separator = temp;
2992                                         if (separator < slash)
2993                                                 separator = slash;
2994                                         if (separator < backslash)
2995                                                 separator = backslash;
2996                                         if (separator < colon)
2997                                                 separator = colon;
2998                                         *((char *)separator) = 0;
2999                                 }
3000                         }
3001                 }
3002                 else
3003                 {
3004                         stringlist_t matchedSet, foundSet;
3005                         const char *start = pattern;
3006
3007                         stringlistinit(&matchedSet);
3008                         stringlistinit(&foundSet);
3009                         // add a first entry to the set
3010                         stringlistappend(&matchedSet, "");
3011                         // iterate through pattern's path
3012                         while (*start)
3013                         {
3014                                 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3015                                 char subpath[MAX_OSPATH];
3016                                 char subpattern[MAX_OSPATH];
3017
3018                                 // find the next wildcard
3019                                 wildcard = strchr(start, '?');
3020                                 asterisk = strchr(start, '*');
3021                                 if (asterisk && (!wildcard || asterisk < wildcard))
3022                                 {
3023                                         wildcard = asterisk;
3024                                 }
3025
3026                                 if (wildcard)
3027                                 {
3028                                         nextseparator = strchr( wildcard, '/' );
3029                                 }
3030                                 else
3031                                 {
3032                                         nextseparator = NULL;
3033                                 }
3034
3035                                 if( !nextseparator ) {
3036                                         nextseparator = start + strlen( start );
3037                                 }
3038
3039                                 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3040                                 // copy everything up except nextseperator
3041                                 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3042                                 // find the last '/' before the wildcard
3043                                 prevseparator = strrchr( subpattern, '/' );
3044                                 if (!prevseparator)
3045                                         prevseparator = subpattern;
3046                                 else
3047                                         prevseparator++;
3048                                 // copy everything from start to the previous including the '/' (before the wildcard)
3049                                 // everything up to start is already included in the path of matchedSet's entries
3050                                 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3051
3052                                 // for each entry in matchedSet try to open the subdirectories specified in subpath
3053                                 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3054                                         strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3055                                         strlcat( temp, subpath, sizeof(temp) );
3056                                         listdirectory( &foundSet, searchpath->filename, temp );
3057                                 }
3058                                 if( dirlistindex == 0 ) {
3059                                         break;
3060                                 }
3061                                 // reset the current result set
3062                                 stringlistfreecontents( &matchedSet );
3063                                 // match against the pattern
3064                                 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3065                                         const char *direntry = foundSet.strings[ dirlistindex ];
3066                                         if (matchpattern(direntry, subpattern, true)) {
3067                                                 stringlistappend( &matchedSet, direntry );
3068                                         }
3069                                 }
3070                                 stringlistfreecontents( &foundSet );
3071
3072                                 start = nextseparator;
3073                         }
3074
3075                         for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3076                         {
3077                                 const char *temp = matchedSet.strings[dirlistindex];
3078                                 if (matchpattern(temp, (char *)pattern, true))
3079                                 {
3080                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3081                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
3082                                                         break;
3083                                         if (resultlistindex == resultlist.numstrings)
3084                                         {
3085                                                 stringlistappend(&resultlist, temp);
3086                                                 if (!quiet && developer_loading.integer)
3087                                                         Con_Printf("SearchDirFile: %s\n", temp);
3088                                         }
3089                                 }
3090                         }
3091                         stringlistfreecontents( &matchedSet );
3092                 }
3093         }
3094
3095         if (resultlist.numstrings)
3096         {
3097                 stringlistsort(&resultlist);
3098                 numfiles = resultlist.numstrings;
3099                 numchars = 0;
3100                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3101                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3102                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3103                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3104                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3105                 search->numfilenames = (int)numfiles;
3106                 numfiles = 0;
3107                 numchars = 0;
3108                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3109                 {
3110                         size_t textlen;
3111                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
3112                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3113                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3114                         numfiles++;
3115                         numchars += (int)textlen;
3116                 }
3117         }
3118         stringlistfreecontents(&resultlist);
3119
3120         Mem_Free(basepath);
3121         return search;
3122 }
3123
3124 void FS_FreeSearch(fssearch_t *search)
3125 {
3126         Z_Free(search);
3127 }
3128
3129 extern int con_linewidth;
3130 int FS_ListDirectory(const char *pattern, int oneperline)
3131 {
3132         int numfiles;
3133         int numcolumns;
3134         int numlines;
3135         int columnwidth;
3136         int linebufpos;
3137         int i, j, k, l;
3138         const char *name;
3139         char linebuf[MAX_INPUTLINE];
3140         fssearch_t *search;
3141         search = FS_Search(pattern, true, true);
3142         if (!search)
3143                 return 0;
3144         numfiles = search->numfilenames;
3145         if (!oneperline)
3146         {
3147                 // FIXME: the names could be added to one column list and then
3148                 // gradually shifted into the next column if they fit, and then the
3149                 // next to make a compact variable width listing but it's a lot more
3150                 // complicated...
3151                 // find width for columns
3152                 columnwidth = 0;
3153                 for (i = 0;i < numfiles;i++)
3154                 {
3155                         l = (int)strlen(search->filenames[i]);
3156                         if (columnwidth < l)
3157                                 columnwidth = l;
3158                 }
3159                 // count the spacing character
3160                 columnwidth++;
3161                 // calculate number of columns
3162                 numcolumns = con_linewidth / columnwidth;
3163                 // don't bother with the column printing if it's only one column
3164                 if (numcolumns >= 2)
3165                 {
3166                         numlines = (numfiles + numcolumns - 1) / numcolumns;
3167                         for (i = 0;i < numlines;i++)
3168                         {
3169                                 linebufpos = 0;
3170                                 for (k = 0;k < numcolumns;k++)
3171                                 {
3172                                         l = i * numcolumns + k;
3173                                         if (l < numfiles)
3174                                         {
3175                                                 name = search->filenames[l];
3176                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3177                                                         linebuf[linebufpos++] = name[j];
3178                                                 // space out name unless it's the last on the line
3179                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
3180                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3181                                                                 linebuf[linebufpos++] = ' ';
3182                                         }
3183                                 }
3184                                 linebuf[linebufpos] = 0;
3185                                 Con_Printf("%s\n", linebuf);
3186                         }
3187                 }
3188                 else
3189                         oneperline = true;
3190         }
3191         if (oneperline)
3192                 for (i = 0;i < numfiles;i++)
3193                         Con_Printf("%s\n", search->filenames[i]);
3194         FS_FreeSearch(search);
3195         return (int)numfiles;
3196 }
3197
3198 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3199 {
3200         const char *pattern;
3201         if (Cmd_Argc() > 3)
3202         {
3203                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3204                 return;
3205         }
3206         if (Cmd_Argc() == 2)
3207                 pattern = Cmd_Argv(1);
3208         else
3209                 pattern = "*";
3210         if (!FS_ListDirectory(pattern, oneperline))
3211                 Con_Print("No files found.\n");
3212 }
3213
3214 void FS_Dir_f(void)
3215 {
3216         FS_ListDirectoryCmd("dir", true);
3217 }
3218
3219 void FS_Ls_f(void)
3220 {
3221         FS_ListDirectoryCmd("ls", false);
3222 }
3223
3224 void FS_Which_f(void)
3225 {
3226         const char *filename;
3227         int index;
3228         searchpath_t *sp;
3229         if (Cmd_Argc() != 2)
3230         {
3231                 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3232                 return;
3233         }  
3234         filename = Cmd_Argv(1);
3235         sp = FS_FindFile(filename, &index, true);
3236         if (!sp) {
3237                 Con_Printf("%s isn't anywhere\n", filename);
3238                 return;
3239         }
3240         if (sp->pack)
3241                 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3242         else
3243                 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3244 }
3245
3246
3247 const char *FS_WhichPack(const char *filename)
3248 {
3249         int index;
3250         searchpath_t *sp = FS_FindFile(filename, &index, true);
3251         if(sp && sp->pack)
3252                 return sp->pack->shortname;
3253         else
3254                 return 0;
3255 }
3256
3257 /*
3258 ====================
3259 FS_IsRegisteredQuakePack
3260
3261 Look for a proof of purchase file file in the requested package
3262
3263 If it is found, this file should NOT be downloaded.
3264 ====================
3265 */
3266 qboolean FS_IsRegisteredQuakePack(const char *name)
3267 {
3268         searchpath_t *search;
3269         pack_t *pak;
3270
3271         // search through the path, one element at a time
3272         for (search = fs_searchpaths;search;search = search->next)
3273         {
3274                 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3275                 {
3276                         int (*strcmp_funct) (const char* str1, const char* str2);
3277                         int left, right, middle;
3278
3279                         pak = search->pack;
3280                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3281
3282                         // Look for the file (binary search)
3283                         left = 0;
3284                         right = pak->numfiles - 1;
3285                         while (left <= right)
3286                         {
3287                                 int diff;
3288
3289                                 middle = (left + right) / 2;
3290                                 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3291
3292                                 // Found it
3293                                 if (!diff)
3294                                         return true;
3295
3296                                 // If we're too far in the list
3297                                 if (diff > 0)
3298                                         right = middle - 1;
3299                                 else
3300                                         left = middle + 1;
3301                         }
3302
3303                         // we found the requested pack but it is not registered quake
3304                         return false;
3305                 }
3306         }
3307
3308         return false;
3309 }
3310
3311 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3312 {
3313         int crc = -1;
3314         unsigned char *filedata;
3315         fs_offset_t filesize;
3316         if (filesizepointer)
3317                 *filesizepointer = 0;
3318         if (!filename || !*filename)
3319                 return crc;
3320         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3321         if (filedata)
3322         {
3323                 if (filesizepointer)
3324                         *filesizepointer = filesize;
3325                 crc = CRC_Block(filedata, filesize);
3326                 Mem_Free(filedata);
3327         }
3328         return crc;
3329 }
3330
3331 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3332 {
3333         z_stream strm;
3334         unsigned char *out = NULL;
3335         unsigned char *tmp;
3336
3337         memset(&strm, 0, sizeof(strm));
3338         strm.zalloc = Z_NULL;
3339         strm.zfree = Z_NULL;
3340         strm.opaque = Z_NULL;
3341
3342         if(level < 0)
3343                 level = Z_DEFAULT_COMPRESSION;
3344
3345         if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3346         {
3347                 Con_Printf("FS_Deflate: deflate init error!\n");
3348                 return NULL;
3349         }
3350
3351         strm.next_in = (unsigned char*)data;
3352         strm.avail_in = size;
3353
3354         tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3355         if(!tmp)
3356         {
3357                 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
3358                 qz_deflateEnd(&strm);
3359                 return NULL;
3360         }
3361
3362         strm.next_out = tmp;
3363         strm.avail_out = size;
3364
3365         if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
3366         {
3367                 Con_Printf("FS_Deflate: deflate failed!\n");
3368                 qz_deflateEnd(&strm);
3369                 Mem_Free(tmp);
3370                 return NULL;
3371         }
3372         
3373         if(qz_deflateEnd(&strm) != Z_OK)
3374         {
3375                 Con_Printf("FS_Deflate: deflateEnd failed\n");
3376                 Mem_Free(tmp);
3377                 return NULL;
3378         }
3379
3380         if(strm.total_out >= size)
3381         {
3382                 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
3383                 Mem_Free(tmp);
3384                 return NULL;
3385         }
3386
3387         out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
3388         if(!out)
3389         {
3390                 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
3391                 Mem_Free(tmp);
3392                 return NULL;
3393         }
3394
3395         if(deflated_size)
3396                 *deflated_size = (size_t)strm.total_out;
3397
3398         memcpy(out, tmp, strm.total_out);
3399         Mem_Free(tmp);
3400         
3401         return out;
3402 }
3403
3404 static void AssertBufsize(sizebuf_t *buf, int length)
3405 {
3406         if(buf->cursize + length > buf->maxsize)
3407         {
3408                 int oldsize = buf->maxsize;
3409                 unsigned char *olddata;
3410                 olddata = buf->data;
3411                 buf->maxsize += length;
3412                 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
3413                 if(olddata)
3414                 {
3415                         memcpy(buf->data, olddata, oldsize);
3416                         Mem_Free(olddata);
3417                 }
3418         }
3419 }
3420
3421 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
3422 {
3423         int ret;
3424         z_stream strm;
3425         unsigned char *out = NULL;
3426         unsigned char tmp[2048];
3427         unsigned int have;
3428         sizebuf_t outbuf;
3429
3430         memset(&outbuf, 0, sizeof(outbuf));
3431         outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
3432         outbuf.maxsize = sizeof(tmp);
3433
3434         memset(&strm, 0, sizeof(strm));
3435         strm.zalloc = Z_NULL;
3436         strm.zfree = Z_NULL;
3437         strm.opaque = Z_NULL;
3438
3439         if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
3440         {
3441                 Con_Printf("FS_Inflate: inflate init error!\n");
3442                 Mem_Free(outbuf.data);
3443                 return NULL;
3444         }
3445
3446         strm.next_in = (unsigned char*)data;
3447         strm.avail_in = size;
3448
3449         do
3450         {
3451                 strm.next_out = tmp;
3452                 strm.avail_out = sizeof(tmp);
3453                 ret = qz_inflate(&strm, Z_NO_FLUSH);
3454                 // it either returns Z_OK on progress, Z_STREAM_END on end
3455                 // or an error code
3456                 switch(ret)
3457                 {
3458                         case Z_STREAM_END:
3459                         case Z_OK:
3460                                 break;
3461                                 
3462                         case Z_STREAM_ERROR:
3463                                 Con_Print("FS_Inflate: stream error!\n");
3464                                 break;
3465                         case Z_DATA_ERROR:
3466                                 Con_Print("FS_Inflate: data error!\n");
3467                                 break;
3468                         case Z_MEM_ERROR:
3469                                 Con_Print("FS_Inflate: mem error!\n");
3470                                 break;
3471                         case Z_BUF_ERROR:
3472                                 Con_Print("FS_Inflate: buf error!\n");
3473                                 break;
3474                         default:
3475                                 Con_Print("FS_Inflate: unknown error!\n");
3476                                 break;
3477                                 
3478                 }
3479                 if(ret != Z_OK && ret != Z_STREAM_END)
3480                 {
3481                         Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
3482                         Mem_Free(outbuf.data);
3483                         qz_inflateEnd(&strm);
3484                         return NULL;
3485                 }
3486                 have = sizeof(tmp) - strm.avail_out;
3487                 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
3488                 SZ_Write(&outbuf, tmp, have);
3489         } while(ret != Z_STREAM_END);
3490
3491         qz_inflateEnd(&strm);
3492
3493         out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
3494         if(!out)
3495         {
3496                 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
3497                 Mem_Free(outbuf.data);
3498                 return NULL;
3499         }
3500
3501         memcpy(out, outbuf.data, outbuf.cursize);
3502         Mem_Free(outbuf.data);
3503
3504         if(inflated_size)
3505                 *inflated_size = (size_t)outbuf.cursize;
3506         
3507         return out;
3508 }