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