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