74329aaf779185b2c82ce6e165fa21144e357592
[divverent/darkplaces.git] / fs.c
1 /*
2         DarkPlaces file system
3
4         Copyright (C) 2003-2006 Mathieu Olivier
5
6         This program is free software; you can redistribute it and/or
7         modify it under the terms of the GNU General Public License
8         as published by the Free Software Foundation; either version 2
9         of the License, or (at your option) any later version.
10
11         This program is distributed in the hope that it will be useful,
12         but WITHOUT ANY WARRANTY; without even the implied warranty of
13         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14
15         See the GNU General Public License for more details.
16
17         You should have received a copy of the GNU General Public License
18         along with this program; if not, write to:
19
20                 Free Software Foundation, Inc.
21                 59 Temple Place - Suite 330
22                 Boston, MA  02111-1307, USA
23 */
24
25 // on UNIX platforms we need to define this so that video saving does not cause a SIGFSZ (file size) signal when a video clip exceeds 2GB
26 #define _FILE_OFFSET_BITS 64
27
28 #include "quakedef.h"
29
30 #include <limits.h>
31 #include <fcntl.h>
32
33 #ifdef WIN32
34 # include <direct.h>
35 # include <io.h>
36 #else
37 # include <pwd.h>
38 # include <sys/stat.h>
39 # include <unistd.h>
40 #endif
41
42 #include "fs.h"
43 #include "wad.h"
44
45 // Win32 requires us to add O_BINARY, but the other OSes don't have it
46 #ifndef O_BINARY
47 # define O_BINARY 0
48 #endif
49
50 // In case the system doesn't support the O_NONBLOCK flag
51 #ifndef O_NONBLOCK
52 # define O_NONBLOCK 0
53 #endif
54
55
56 /*
57
58 All of Quake's data access is through a hierchal file system, but the contents
59 of the file system can be transparently merged from several sources.
60
61 The "base directory" is the path to the directory holding the quake.exe and
62 all game directories.  The sys_* files pass this to host_init in
63 quakeparms_t->basedir.  This can be overridden with the "-basedir" command
64 line parm to allow code debugging in a different directory.  The base
65 directory is only used during filesystem initialization.
66
67 The "game directory" is the first tree on the search path and directory that
68 all generated files (savegames, screenshots, demos, config files) will be
69 saved to.  This can be overridden with the "-game" command line parameter.
70 The game directory can never be changed while quake is executing.  This is a
71 precaution against having a malicious server instruct clients to write files
72 over areas they shouldn't.
73
74 */
75
76
77 /*
78 =============================================================================
79
80 CONSTANTS
81
82 =============================================================================
83 */
84
85 // Magic numbers of a ZIP file (big-endian format)
86 #define ZIP_DATA_HEADER 0x504B0304  // "PK\3\4"
87 #define ZIP_CDIR_HEADER 0x504B0102  // "PK\1\2"
88 #define ZIP_END_HEADER  0x504B0506  // "PK\5\6"
89
90 // Other constants for ZIP files
91 #define ZIP_MAX_COMMENTS_SIZE           ((unsigned short)0xFFFF)
92 #define ZIP_END_CDIR_SIZE                       22
93 #define ZIP_CDIR_CHUNK_BASE_SIZE        46
94 #define ZIP_LOCAL_CHUNK_BASE_SIZE       30
95
96 // Zlib constants (from zlib.h)
97 #define Z_SYNC_FLUSH    2
98 #define MAX_WBITS               15
99 #define Z_OK                    0
100 #define Z_STREAM_END    1
101 #define ZLIB_VERSION    "1.2.3"
102
103 // Uncomment the following line if the zlib DLL you have still uses
104 // the 1.1.x series calling convention on Win32 (WINAPI)
105 //#define ZLIB_USES_WINAPI
106
107
108 /*
109 =============================================================================
110
111 TYPES
112
113 =============================================================================
114 */
115
116 // Zlib stream (from zlib.h)
117 // Warning: some pointers we don't use directly have
118 // been cast to "void*" for a matter of simplicity
119 typedef struct
120 {
121         unsigned char                   *next_in;       // next input byte
122         unsigned int    avail_in;       // number of bytes available at next_in
123         unsigned long   total_in;       // total nb of input bytes read so far
124
125         unsigned char                   *next_out;      // next output byte should be put there
126         unsigned int    avail_out;      // remaining free space at next_out
127         unsigned long   total_out;      // total nb of bytes output so far
128
129         char                    *msg;           // last error message, NULL if no error
130         void                    *state;         // not visible by applications
131
132         void                    *zalloc;        // used to allocate the internal state
133         void                    *zfree;         // used to free the internal state
134         void                    *opaque;        // private data object passed to zalloc and zfree
135
136         int                             data_type;      // best guess about the data type: ascii or binary
137         unsigned long   adler;          // adler32 value of the uncompressed data
138         unsigned long   reserved;       // reserved for future use
139 } z_stream;
140
141
142 // inside a package (PAK or PK3)
143 #define QFILE_FLAG_PACKED (1 << 0)
144 // file is compressed using the deflate algorithm (PK3 only)
145 #define QFILE_FLAG_DEFLATED (1 << 1)
146
147 #define FILE_BUFF_SIZE 2048
148 typedef struct
149 {
150         z_stream        zstream;
151         size_t          comp_length;                    // length of the compressed file
152         size_t          in_ind, in_len;                 // input buffer current index and length
153         size_t          in_position;                    // position in the compressed file
154         unsigned char           input [FILE_BUFF_SIZE];
155 } ztoolkit_t;
156
157 struct qfile_s
158 {
159         int                             flags;
160         int                             handle;                                 // file descriptor
161         fs_offset_t             real_length;                    // uncompressed file size (for files opened in "read" mode)
162         fs_offset_t             position;                               // current position in the file
163         fs_offset_t             offset;                                 // offset into the package (0 if external file)
164         int                             ungetc;                                 // single stored character from ungetc, cleared to EOF when read
165
166         // Contents buffer
167         fs_offset_t             buff_ind, buff_len;             // buffer current index and length
168         unsigned char                   buff [FILE_BUFF_SIZE];
169
170         // For zipped files
171         ztoolkit_t*             ztk;
172 };
173
174
175 // ------ PK3 files on disk ------ //
176
177 // You can get the complete ZIP format description from PKWARE website
178
179 typedef struct pk3_endOfCentralDir_s
180 {
181         unsigned int signature;
182         unsigned short disknum;
183         unsigned short cdir_disknum;    // number of the disk with the start of the central directory
184         unsigned short localentries;    // number of entries in the central directory on this disk
185         unsigned short nbentries;               // total number of entries in the central directory on this disk
186         unsigned int cdir_size;                 // size of the central directory
187         unsigned int cdir_offset;               // with respect to the starting disk number
188         unsigned short comment_size;
189 } pk3_endOfCentralDir_t;
190
191
192 // ------ PAK files on disk ------ //
193 typedef struct dpackfile_s
194 {
195         char name[56];
196         int filepos, filelen;
197 } dpackfile_t;
198
199 typedef struct dpackheader_s
200 {
201         char id[4];
202         int dirofs;
203         int dirlen;
204 } dpackheader_t;
205
206
207 // Packages in memory
208 // the offset in packfile_t is the true contents offset
209 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
210 // file compressed using the deflate algorithm
211 #define PACKFILE_FLAG_DEFLATED (1 << 1)
212
213 typedef struct packfile_s
214 {
215         char name [MAX_QPATH];
216         int flags;
217         fs_offset_t offset;
218         fs_offset_t packsize;   // size in the package
219         fs_offset_t realsize;   // real file size (uncompressed)
220 } packfile_t;
221
222 typedef struct pack_s
223 {
224         char filename [MAX_OSPATH];
225         int handle;
226         int ignorecase;  // PK3 ignores case
227         int numfiles;
228         packfile_t *files;
229 } pack_t;
230
231
232 // Search paths for files (including packages)
233 typedef struct searchpath_s
234 {
235         // only one of filename / pack will be used
236         char filename[MAX_OSPATH];
237         pack_t *pack;
238         struct searchpath_s *next;
239 } searchpath_t;
240
241
242 /*
243 =============================================================================
244
245 FUNCTION PROTOTYPES
246
247 =============================================================================
248 */
249
250 void FS_Dir_f(void);
251 void FS_Ls_f(void);
252
253 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
254 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
255                                                                         fs_offset_t offset, fs_offset_t packsize,
256                                                                         fs_offset_t realsize, int flags);
257
258
259 /*
260 =============================================================================
261
262 VARIABLES
263
264 =============================================================================
265 */
266
267 mempool_t *fs_mempool;
268
269 searchpath_t *fs_searchpaths = NULL;
270
271 #define MAX_FILES_IN_PACK       65536
272
273 char fs_gamedir[MAX_OSPATH];
274 char fs_basedir[MAX_OSPATH];
275
276 // list of active game directories (empty if not running a mod)
277 #define MAX_GAMEDIRS 16
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         while (fs_searchpaths)
1084         {
1085                 searchpath_t *search = fs_searchpaths;
1086                 fs_searchpaths = search->next;
1087                 if (search->pack)
1088                 {
1089                         if (search->pack->files)
1090                                 Mem_Free(search->pack->files);
1091                         Mem_Free(search->pack);
1092                 }
1093                 Mem_Free(search);
1094         }
1095 }
1096
1097
1098 /*
1099 ================
1100 FS_Rescan
1101 ================
1102 */
1103 void FS_Rescan (void)
1104 {
1105         int i;
1106         qboolean fs_modified = false;
1107
1108         FS_ClearSearchPath();
1109
1110         // add the game-specific paths
1111         // gamedirname1 (typically id1)
1112         FS_AddGameHierarchy (gamedirname1);
1113         // update the com_modname (used for server info)
1114         strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1115
1116         // add the game-specific path, if any
1117         // (only used for mission packs and the like, which should set fs_modified)
1118         if (gamedirname2)
1119         {
1120                 fs_modified = true;
1121                 FS_AddGameHierarchy (gamedirname2);
1122         }
1123
1124         // -game <gamedir>
1125         // Adds basedir/gamedir as an override game
1126         // LordHavoc: now supports multiple -game directories
1127         // set the com_modname (reported in server info)
1128         for (i = 0;i < fs_numgamedirs;i++)
1129         {
1130                 fs_modified = true;
1131                 FS_AddGameHierarchy (fs_gamedirs[i]);
1132                 // update the com_modname (used server info)
1133                 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1134         }
1135
1136         // set the default screenshot name to either the mod name or the
1137         // gamemode screenshot name
1138         if (strcmp(com_modname, gamedirname1))
1139                 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1140         else
1141                 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1142
1143         // If "-condebug" is in the command line, remove the previous log file
1144         if (COM_CheckParm ("-condebug") != 0)
1145                 unlink (va("%s/qconsole.log", fs_gamedir));
1146
1147         // look for the pop.lmp file and set registered to true if it is found
1148         if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1149         {
1150                 if (fs_modified)
1151                         Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1152                 else
1153                         Con_Print("Playing shareware version.\n");
1154         }
1155         else
1156         {
1157                 Cvar_Set ("registered", "1");
1158                 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1159                         Con_Print("Playing registered version.\n");
1160         }
1161
1162         // unload all wads so that future queries will return the new data
1163         W_UnloadAll();
1164 }
1165
1166 void FS_Rescan_f(void)
1167 {
1168         FS_Rescan();
1169 }
1170
1171 /*
1172 ================
1173 FS_ChangeGameDirs
1174 ================
1175 */
1176 extern void Host_SaveConfig_f (void);
1177 extern void Host_LoadConfig_f (void);
1178 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1179 {
1180         int i;
1181
1182         if (fs_numgamedirs == numgamedirs)
1183         {
1184                 for (i = 0;i < numgamedirs;i++)
1185                         if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1186                                 break;
1187                 if (i == numgamedirs)
1188                         return true; // already using this set of gamedirs, do nothing
1189         }
1190
1191         if (numgamedirs > MAX_GAMEDIRS)
1192         {
1193                 if (complain)
1194                         Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1195                 return false; // too many gamedirs
1196         }
1197
1198         for (i = 0;i < numgamedirs;i++)
1199         {
1200                 // if string is nasty, reject it
1201                 if(FS_CheckNastyPath(gamedirs[i], true))
1202                 {
1203                         if (complain)
1204                                 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1205                         return false; // nasty gamedirs
1206                 }
1207         }
1208
1209         for (i = 0;i < numgamedirs;i++)
1210         {
1211                 if (!FS_CheckGameDir(gamedirs[i]) && failmissing)
1212                 {
1213                         if (complain)
1214                                 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1215                         return false; // missing gamedirs
1216                 }
1217         }
1218
1219         Host_SaveConfig_f();
1220
1221         fs_numgamedirs = numgamedirs;
1222         for (i = 0;i < fs_numgamedirs;i++)
1223                 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1224
1225         // reinitialize filesystem to detect the new paks
1226         FS_Rescan();
1227
1228         // exec the new config
1229         Host_LoadConfig_f();
1230
1231         // reinitialize the loaded sounds
1232         S_Reload_f();
1233
1234         // reinitialize renderer (this reloads hud/console background/etc)
1235         R_Modules_Restart();
1236
1237         return true;
1238 }
1239
1240 /*
1241 ================
1242 FS_GameDir_f
1243 ================
1244 */
1245 void FS_GameDir_f (void)
1246 {
1247         int i;
1248         int numgamedirs;
1249         char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1250
1251         if (Cmd_Argc() < 2)
1252         {
1253                 Con_Printf("gamedirs active:");
1254                 for (i = 0;i < fs_numgamedirs;i++)
1255                         Con_Printf(" %s", fs_gamedirs[i]);
1256                 Con_Printf("\n");
1257                 return;
1258         }
1259
1260         numgamedirs = Cmd_Argc() - 1;
1261         if (numgamedirs > MAX_GAMEDIRS)
1262         {
1263                 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1264                 return;
1265         }
1266
1267         for (i = 0;i < numgamedirs;i++)
1268                 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1269
1270         // allow gamedir change during demo loop
1271         if (cls.demoplayback)
1272                 CL_Disconnect();
1273
1274         if (cls.state == ca_connected || sv.active)
1275         {
1276                 // actually, changing during game would work fine, but would be stupid
1277                 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1278                 return;
1279         }
1280
1281         FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1282 }
1283
1284
1285 /*
1286 ================
1287 FS_CheckGameDir
1288 ================
1289 */
1290 qboolean FS_CheckGameDir(const char *gamedir)
1291 {
1292         qboolean success;
1293         stringlist_t list;
1294         stringlistinit(&list);
1295         listdirectory(&list, va("%s%s/", fs_basedir, gamedir));
1296         success = list.numstrings > 0;
1297         stringlistfreecontents(&list);
1298         return success;
1299 }
1300
1301
1302 /*
1303 ================
1304 FS_Init
1305 ================
1306 */
1307 void FS_Init (void)
1308 {
1309         int i;
1310
1311         fs_mempool = Mem_AllocPool("file management", 0, NULL);
1312
1313         strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1314
1315 // If the base directory is explicitly defined by the compilation process
1316 #ifdef DP_FS_BASEDIR
1317         strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1318 #else
1319         strlcpy(fs_basedir, "", sizeof(fs_basedir));
1320
1321 #ifdef MACOSX
1322         // FIXME: is there a better way to find the directory outside the .app?
1323         if (strstr(com_argv[0], ".app/"))
1324         {
1325                 char *split;
1326
1327                 split = strstr(com_argv[0], ".app/");
1328                 while (split > com_argv[0] && *split != '/')
1329                         split--;
1330                 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1331                 fs_basedir[split - com_argv[0]] = 0;
1332         }
1333 #endif
1334 #endif
1335
1336         PK3_OpenLibrary ();
1337
1338         // -basedir <path>
1339         // Overrides the system supplied base directory (under GAMENAME)
1340 // 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)
1341         i = COM_CheckParm ("-basedir");
1342         if (i && i < com_argc-1)
1343         {
1344                 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1345                 i = (int)strlen (fs_basedir);
1346                 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1347                         fs_basedir[i-1] = 0;
1348         }
1349
1350         // add a path separator to the end of the basedir if it lacks one
1351         if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1352                 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1353
1354         if (!FS_CheckGameDir(gamedirname1))
1355                 Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1356
1357         if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
1358                 Sys_Error("base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1359
1360         // -game <gamedir>
1361         // Adds basedir/gamedir as an override game
1362         // LordHavoc: now supports multiple -game directories
1363         for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1364         {
1365                 if (!com_argv[i])
1366                         continue;
1367                 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1368                 {
1369                         i++;
1370                         if (FS_CheckNastyPath(com_argv[i], true))
1371                                 Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
1372                         if (!FS_CheckGameDir(com_argv[i]))
1373                                 Sys_Error("-game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1374                         // add the gamedir to the list of active gamedirs
1375                         strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1376                         fs_numgamedirs++;
1377                 }
1378         }
1379
1380         // generate the searchpath
1381         FS_Rescan();
1382 }
1383
1384 void FS_Init_Commands(void)
1385 {
1386         Cvar_RegisterVariable (&scr_screenshot_name);
1387         Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1388
1389         Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1390         Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1391         Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1392         Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1393         Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1394 }
1395
1396 /*
1397 ================
1398 FS_Shutdown
1399 ================
1400 */
1401 void FS_Shutdown (void)
1402 {
1403         Mem_FreePool (&fs_mempool);
1404 }
1405
1406 /*
1407 ====================
1408 FS_SysOpen
1409
1410 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1411 ====================
1412 */
1413 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1414 {
1415         qfile_t* file;
1416         int mod, opt;
1417         unsigned int ind;
1418
1419         // Parse the mode string
1420         switch (mode[0])
1421         {
1422                 case 'r':
1423                         mod = O_RDONLY;
1424                         opt = 0;
1425                         break;
1426                 case 'w':
1427                         mod = O_WRONLY;
1428                         opt = O_CREAT | O_TRUNC;
1429                         break;
1430                 case 'a':
1431                         mod = O_WRONLY;
1432                         opt = O_CREAT | O_APPEND;
1433                         break;
1434                 default:
1435                         Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1436                         return NULL;
1437         }
1438         for (ind = 1; mode[ind] != '\0'; ind++)
1439         {
1440                 switch (mode[ind])
1441                 {
1442                         case '+':
1443                                 mod = O_RDWR;
1444                                 break;
1445                         case 'b':
1446                                 opt |= O_BINARY;
1447                                 break;
1448                         default:
1449                                 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1450                                                         filepath, mode, mode[ind]);
1451                 }
1452         }
1453
1454         if (nonblocking)
1455                 opt |= O_NONBLOCK;
1456
1457         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1458         memset (file, 0, sizeof (*file));
1459         file->ungetc = EOF;
1460
1461         file->handle = open (filepath, mod | opt, 0666);
1462         if (file->handle < 0)
1463         {
1464                 Mem_Free (file);
1465                 return NULL;
1466         }
1467
1468         file->real_length = lseek (file->handle, 0, SEEK_END);
1469
1470         // For files opened in append mode, we start at the end of the file
1471         if (mod & O_APPEND)
1472                 file->position = file->real_length;
1473         else
1474                 lseek (file->handle, 0, SEEK_SET);
1475
1476         return file;
1477 }
1478
1479
1480 /*
1481 ===========
1482 FS_OpenPackedFile
1483
1484 Open a packed file using its package file descriptor
1485 ===========
1486 */
1487 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1488 {
1489         packfile_t *pfile;
1490         int dup_handle;
1491         qfile_t* file;
1492
1493         pfile = &pack->files[pack_ind];
1494
1495         // If we don't have the true offset, get it now
1496         if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1497                 if (!PK3_GetTrueFileOffset (pfile, pack))
1498                         return NULL;
1499
1500         // No Zlib DLL = no compressed files
1501         if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1502         {
1503                 Con_Printf("WARNING: can't open the compressed file %s\n"
1504                                         "You need the Zlib DLL to use compressed files\n",
1505                                         pfile->name);
1506                 return NULL;
1507         }
1508
1509         // LordHavoc: lseek affects all duplicates of a handle so we do it before
1510         // the dup() call to avoid having to close the dup_handle on error here
1511         if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1512         {
1513                 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1514                                         pfile->name, pack->filename, (int) pfile->offset);
1515                 return NULL;
1516         }
1517
1518         dup_handle = dup (pack->handle);
1519         if (dup_handle < 0)
1520         {
1521                 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1522                 return NULL;
1523         }
1524
1525         file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1526         memset (file, 0, sizeof (*file));
1527         file->handle = dup_handle;
1528         file->flags = QFILE_FLAG_PACKED;
1529         file->real_length = pfile->realsize;
1530         file->offset = pfile->offset;
1531         file->position = 0;
1532         file->ungetc = EOF;
1533
1534         if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1535         {
1536                 ztoolkit_t *ztk;
1537
1538                 file->flags |= QFILE_FLAG_DEFLATED;
1539
1540                 // We need some more variables
1541                 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1542
1543                 ztk->comp_length = pfile->packsize;
1544
1545                 // Initialize zlib stream
1546                 ztk->zstream.next_in = ztk->input;
1547                 ztk->zstream.avail_in = 0;
1548
1549                 /* From Zlib's "unzip.c":
1550                  *
1551                  * windowBits is passed < 0 to tell that there is no zlib header.
1552                  * Note that in this case inflate *requires* an extra "dummy" byte
1553                  * after the compressed stream in order to complete decompression and
1554                  * return Z_STREAM_END.
1555                  * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1556                  * size of both compressed and uncompressed data
1557                  */
1558                 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1559                 {
1560                         Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1561                         close(dup_handle);
1562                         Mem_Free(file);
1563                         return NULL;
1564                 }
1565
1566                 ztk->zstream.next_out = file->buff;
1567                 ztk->zstream.avail_out = sizeof (file->buff);
1568
1569                 file->ztk = ztk;
1570         }
1571
1572         return file;
1573 }
1574
1575 /*
1576 ====================
1577 FS_CheckNastyPath
1578
1579 Return true if the path should be rejected due to one of the following:
1580 1: path elements that are non-portable
1581 2: path elements that would allow access to files outside the game directory,
1582    or are just not a good idea for a mod to be using.
1583 ====================
1584 */
1585 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1586 {
1587         // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1588         if (!path[0])
1589                 return 2;
1590
1591         // Windows: don't allow \ in filenames (windows-only), period.
1592         // (on Windows \ is a directory separator, but / is also supported)
1593         if (strstr(path, "\\"))
1594                 return 1; // non-portable
1595
1596         // Mac: don't allow Mac-only filenames - : is a directory separator
1597         // instead of /, but we rely on / working already, so there's no reason to
1598         // support a Mac-only path
1599         // Amiga and Windows: : tries to go to root of drive
1600         if (strstr(path, ":"))
1601                 return 1; // non-portable attempt to go to root of drive
1602
1603         // Amiga: // is parent directory
1604         if (strstr(path, "//"))
1605                 return 1; // non-portable attempt to go to parent directory
1606
1607         // all: don't allow going to parent directory (../ or /../)
1608         if (strstr(path, ".."))
1609                 return 2; // attempt to go outside the game directory
1610
1611         // Windows and UNIXes: don't allow absolute paths
1612         if (path[0] == '/')
1613                 return 2; // attempt to go outside the game directory
1614
1615         // 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
1616         if (strchr(path, '.'))
1617         {
1618                 if (isgamedir)
1619                 {
1620                         // gamedir is entirely path elements, so simply forbid . entirely
1621                         return 2;
1622                 }
1623                 if (strchr(path, '.') < strrchr(path, '/'))
1624                         return 2; // possible attempt to go outside the game directory
1625         }
1626
1627         // all: forbid trailing slash on gamedir
1628         if (isgamedir && path[strlen(path)-1] == '/')
1629                 return 2;
1630
1631         // all: forbid leading dot on any filename for any reason
1632         if (strstr(path, "/."))
1633                 return 2; // attempt to go outside the game directory
1634
1635         // after all these checks we're pretty sure it's a / separated filename
1636         // and won't do much if any harm
1637         return false;
1638 }
1639
1640
1641 /*
1642 ====================
1643 FS_FindFile
1644
1645 Look for a file in the packages and in the filesystem
1646
1647 Return the searchpath where the file was found (or NULL)
1648 and the file index in the package if relevant
1649 ====================
1650 */
1651 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1652 {
1653         searchpath_t *search;
1654         pack_t *pak;
1655
1656         // search through the path, one element at a time
1657         for (search = fs_searchpaths;search;search = search->next)
1658         {
1659                 // is the element a pak file?
1660                 if (search->pack)
1661                 {
1662                         int (*strcmp_funct) (const char* str1, const char* str2);
1663                         int left, right, middle;
1664
1665                         pak = search->pack;
1666                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1667
1668                         // Look for the file (binary search)
1669                         left = 0;
1670                         right = pak->numfiles - 1;
1671                         while (left <= right)
1672                         {
1673                                 int diff;
1674
1675                                 middle = (left + right) / 2;
1676                                 diff = strcmp_funct (pak->files[middle].name, name);
1677
1678                                 // Found it
1679                                 if (!diff)
1680                                 {
1681                                         if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1682                                         {
1683                                                 // yes, but the first one is empty so we treat it as not being there
1684                                                 if (!quiet && developer.integer >= 10)
1685                                                         Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1686
1687                                                 if (index != NULL)
1688                                                         *index = -1;
1689                                                 return NULL;
1690                                         }
1691
1692                                         if (!quiet && developer.integer >= 10)
1693                                                 Con_Printf("FS_FindFile: %s in %s\n",
1694                                                                         pak->files[middle].name, pak->filename);
1695
1696                                         if (index != NULL)
1697                                                 *index = middle;
1698                                         return search;
1699                                 }
1700
1701                                 // If we're too far in the list
1702                                 if (diff > 0)
1703                                         right = middle - 1;
1704                                 else
1705                                         left = middle + 1;
1706                         }
1707                 }
1708                 else
1709                 {
1710                         char netpath[MAX_OSPATH];
1711                         dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1712                         if (FS_SysFileExists (netpath))
1713                         {
1714                                 if (!quiet && developer.integer >= 10)
1715                                         Con_Printf("FS_FindFile: %s\n", netpath);
1716
1717                                 if (index != NULL)
1718                                         *index = -1;
1719                                 return search;
1720                         }
1721                 }
1722         }
1723
1724         if (!quiet && developer.integer >= 10)
1725                 Con_Printf("FS_FindFile: can't find %s\n", name);
1726
1727         if (index != NULL)
1728                 *index = -1;
1729         return NULL;
1730 }
1731
1732
1733 /*
1734 ===========
1735 FS_OpenReadFile
1736
1737 Look for a file in the search paths and open it in read-only mode
1738 ===========
1739 */
1740 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking)
1741 {
1742         searchpath_t *search;
1743         int pack_ind;
1744
1745         search = FS_FindFile (filename, &pack_ind, quiet);
1746
1747         // Not found?
1748         if (search == NULL)
1749                 return NULL;
1750
1751         // Found in the filesystem?
1752         if (pack_ind < 0)
1753         {
1754                 char path [MAX_OSPATH];
1755                 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1756                 return FS_SysOpen (path, "rb", nonblocking);
1757         }
1758
1759         // So, we found it in a package...
1760         return FS_OpenPackedFile (search->pack, pack_ind);
1761 }
1762
1763
1764 /*
1765 =============================================================================
1766
1767 MAIN PUBLIC FUNCTIONS
1768
1769 =============================================================================
1770 */
1771
1772 /*
1773 ====================
1774 FS_Open
1775
1776 Open a file. The syntax is the same as fopen
1777 ====================
1778 */
1779 qfile_t* FS_Open (const char* filepath, const char* mode, qboolean quiet, qboolean nonblocking)
1780 {
1781         if (FS_CheckNastyPath(filepath, false))
1782         {
1783                 Con_Printf("FS_Open(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
1784                 return NULL;
1785         }
1786
1787         // If the file is opened in "write", "append", or "read/write" mode
1788         if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
1789         {
1790                 char real_path [MAX_OSPATH];
1791
1792                 // Open the file on disk directly
1793                 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
1794
1795                 // Create directories up to the file
1796                 FS_CreatePath (real_path);
1797
1798                 return FS_SysOpen (real_path, mode, nonblocking);
1799         }
1800         // Else, we look at the various search paths and open the file in read-only mode
1801         else
1802                 return FS_OpenReadFile (filepath, quiet, nonblocking);
1803 }
1804
1805
1806 /*
1807 ====================
1808 FS_Close
1809
1810 Close a file
1811 ====================
1812 */
1813 int FS_Close (qfile_t* file)
1814 {
1815         if (close (file->handle))
1816                 return EOF;
1817
1818         if (file->ztk)
1819         {
1820                 qz_inflateEnd (&file->ztk->zstream);
1821                 Mem_Free (file->ztk);
1822         }
1823
1824         Mem_Free (file);
1825         return 0;
1826 }
1827
1828
1829 /*
1830 ====================
1831 FS_Write
1832
1833 Write "datasize" bytes into a file
1834 ====================
1835 */
1836 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
1837 {
1838         fs_offset_t result;
1839
1840         // If necessary, seek to the exact file position we're supposed to be
1841         if (file->buff_ind != file->buff_len)
1842                 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
1843
1844         // Purge cached data
1845         FS_Purge (file);
1846
1847         // Write the buffer and update the position
1848         result = write (file->handle, data, (fs_offset_t)datasize);
1849         file->position = lseek (file->handle, 0, SEEK_CUR);
1850         if (file->real_length < file->position)
1851                 file->real_length = file->position;
1852
1853         if (result < 0)
1854                 return 0;
1855
1856         return result;
1857 }
1858
1859
1860 /*
1861 ====================
1862 FS_Read
1863
1864 Read up to "buffersize" bytes from a file
1865 ====================
1866 */
1867 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
1868 {
1869         fs_offset_t count, done;
1870
1871         if (buffersize == 0)
1872                 return 0;
1873
1874         // Get rid of the ungetc character
1875         if (file->ungetc != EOF)
1876         {
1877                 ((char*)buffer)[0] = file->ungetc;
1878                 buffersize--;
1879                 file->ungetc = EOF;
1880                 done = 1;
1881         }
1882         else
1883                 done = 0;
1884
1885         // First, we copy as many bytes as we can from "buff"
1886         if (file->buff_ind < file->buff_len)
1887         {
1888                 count = file->buff_len - file->buff_ind;
1889
1890                 done += ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
1891                 memcpy (buffer, &file->buff[file->buff_ind], done);
1892                 file->buff_ind += done;
1893
1894                 buffersize -= done;
1895                 if (buffersize == 0)
1896                         return done;
1897         }
1898
1899         // NOTE: at this point, the read buffer is always empty
1900
1901         // If the file isn't compressed
1902         if (! (file->flags & QFILE_FLAG_DEFLATED))
1903         {
1904                 fs_offset_t nb;
1905
1906                 // We must take care to not read after the end of the file
1907                 count = file->real_length - file->position;
1908
1909                 // If we have a lot of data to get, put them directly into "buffer"
1910                 if (buffersize > sizeof (file->buff) / 2)
1911                 {
1912                         if (count > (fs_offset_t)buffersize)
1913                                 count = (fs_offset_t)buffersize;
1914                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1915                         nb = read (file->handle, &((unsigned char*)buffer)[done], count);
1916                         if (nb > 0)
1917                         {
1918                                 done += nb;
1919                                 file->position += nb;
1920
1921                                 // Purge cached data
1922                                 FS_Purge (file);
1923                         }
1924                 }
1925                 else
1926                 {
1927                         if (count > (fs_offset_t)sizeof (file->buff))
1928                                 count = (fs_offset_t)sizeof (file->buff);
1929                         lseek (file->handle, file->offset + file->position, SEEK_SET);
1930                         nb = read (file->handle, file->buff, count);
1931                         if (nb > 0)
1932                         {
1933                                 file->buff_len = nb;
1934                                 file->position += nb;
1935
1936                                 // Copy the requested data in "buffer" (as much as we can)
1937                                 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
1938                                 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
1939                                 file->buff_ind = count;
1940                                 done += count;
1941                         }
1942                 }
1943
1944                 return done;
1945         }
1946
1947         // If the file is compressed, it's more complicated...
1948         // We cycle through a few operations until we have read enough data
1949         while (buffersize > 0)
1950         {
1951                 ztoolkit_t *ztk = file->ztk;
1952                 int error;
1953
1954                 // NOTE: at this point, the read buffer is always empty
1955
1956                 // If "input" is also empty, we need to refill it
1957                 if (ztk->in_ind == ztk->in_len)
1958                 {
1959                         // If we are at the end of the file
1960                         if (file->position == file->real_length)
1961                                 return done;
1962
1963                         count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
1964                         if (count > (fs_offset_t)sizeof (ztk->input))
1965                                 count = (fs_offset_t)sizeof (ztk->input);
1966                         lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
1967                         if (read (file->handle, ztk->input, count) != count)
1968                         {
1969                                 Con_Printf ("FS_Read: unexpected end of file\n");
1970                                 break;
1971                         }
1972
1973                         ztk->in_ind = 0;
1974                         ztk->in_len = count;
1975                         ztk->in_position += count;
1976                 }
1977
1978                 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
1979                 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
1980
1981                 // Now that we are sure we have compressed data available, we need to determine
1982                 // if it's better to inflate it in "file->buff" or directly in "buffer"
1983
1984                 // Inflate the data in "file->buff"
1985                 if (buffersize < sizeof (file->buff) / 2)
1986                 {
1987                         ztk->zstream.next_out = file->buff;
1988                         ztk->zstream.avail_out = sizeof (file->buff);
1989                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
1990                         if (error != Z_OK && error != Z_STREAM_END)
1991                         {
1992                                 Con_Printf ("FS_Read: Can't inflate file\n");
1993                                 break;
1994                         }
1995                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
1996
1997                         file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
1998                         file->position += file->buff_len;
1999
2000                         // Copy the requested data in "buffer" (as much as we can)
2001                         count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2002                         memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2003                         file->buff_ind = count;
2004                 }
2005
2006                 // Else, we inflate directly in "buffer"
2007                 else
2008                 {
2009                         ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2010                         ztk->zstream.avail_out = (unsigned int)buffersize;
2011                         error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2012                         if (error != Z_OK && error != Z_STREAM_END)
2013                         {
2014                                 Con_Printf ("FS_Read: Can't inflate file\n");
2015                                 break;
2016                         }
2017                         ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2018
2019                         // How much data did it inflate?
2020                         count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2021                         file->position += count;
2022
2023                         // Purge cached data
2024                         FS_Purge (file);
2025                 }
2026
2027                 done += count;
2028                 buffersize -= count;
2029         }
2030
2031         return done;
2032 }
2033
2034
2035 /*
2036 ====================
2037 FS_Print
2038
2039 Print a string into a file
2040 ====================
2041 */
2042 int FS_Print (qfile_t* file, const char *msg)
2043 {
2044         return (int)FS_Write (file, msg, strlen (msg));
2045 }
2046
2047 /*
2048 ====================
2049 FS_Printf
2050
2051 Print a string into a file
2052 ====================
2053 */
2054 int FS_Printf(qfile_t* file, const char* format, ...)
2055 {
2056         int result;
2057         va_list args;
2058
2059         va_start (args, format);
2060         result = FS_VPrintf (file, format, args);
2061         va_end (args);
2062
2063         return result;
2064 }
2065
2066
2067 /*
2068 ====================
2069 FS_VPrintf
2070
2071 Print a string into a file
2072 ====================
2073 */
2074 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2075 {
2076         int len;
2077         fs_offset_t buff_size = MAX_INPUTLINE;
2078         char *tempbuff;
2079
2080         for (;;)
2081         {
2082                 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2083                 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2084                 if (len >= 0 && len < buff_size)
2085                         break;
2086                 Mem_Free (tempbuff);
2087                 buff_size *= 2;
2088         }
2089
2090         len = write (file->handle, tempbuff, len);
2091         Mem_Free (tempbuff);
2092
2093         return len;
2094 }
2095
2096
2097 /*
2098 ====================
2099 FS_Getc
2100
2101 Get the next character of a file
2102 ====================
2103 */
2104 int FS_Getc (qfile_t* file)
2105 {
2106         char c;
2107
2108         if (FS_Read (file, &c, 1) != 1)
2109                 return EOF;
2110
2111         return c;
2112 }
2113
2114
2115 /*
2116 ====================
2117 FS_UnGetc
2118
2119 Put a character back into the read buffer (only supports one character!)
2120 ====================
2121 */
2122 int FS_UnGetc (qfile_t* file, unsigned char c)
2123 {
2124         // If there's already a character waiting to be read
2125         if (file->ungetc != EOF)
2126                 return EOF;
2127
2128         file->ungetc = c;
2129         return c;
2130 }
2131
2132
2133 /*
2134 ====================
2135 FS_Seek
2136
2137 Move the position index in a file
2138 ====================
2139 */
2140 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2141 {
2142         ztoolkit_t *ztk;
2143         unsigned char* buffer;
2144         fs_offset_t buffersize;
2145
2146         // Compute the file offset
2147         switch (whence)
2148         {
2149                 case SEEK_CUR:
2150                         offset += file->position - file->buff_len + file->buff_ind;
2151                         break;
2152
2153                 case SEEK_SET:
2154                         break;
2155
2156                 case SEEK_END:
2157                         offset += file->real_length;
2158                         break;
2159
2160                 default:
2161                         return -1;
2162         }
2163         if (offset < 0 || offset > (long) file->real_length)
2164                 return -1;
2165
2166         // If we have the data in our read buffer, we don't need to actually seek
2167         if (file->position - file->buff_len <= offset && offset <= file->position)
2168         {
2169                 file->buff_ind = offset + file->buff_len - file->position;
2170                 return 0;
2171         }
2172
2173         // Purge cached data
2174         FS_Purge (file);
2175
2176         // Unpacked or uncompressed files can seek directly
2177         if (! (file->flags & QFILE_FLAG_DEFLATED))
2178         {
2179                 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2180                         return -1;
2181                 file->position = offset;
2182                 return 0;
2183         }
2184
2185         // Seeking in compressed files is more a hack than anything else,
2186         // but we need to support it, so here we go.
2187         ztk = file->ztk;
2188
2189         // If we have to go back in the file, we need to restart from the beginning
2190         if (offset <= file->position)
2191         {
2192                 ztk->in_ind = 0;
2193                 ztk->in_len = 0;
2194                 ztk->in_position = 0;
2195                 file->position = 0;
2196                 lseek (file->handle, file->offset, SEEK_SET);
2197
2198                 // Reset the Zlib stream
2199                 ztk->zstream.next_in = ztk->input;
2200                 ztk->zstream.avail_in = 0;
2201                 qz_inflateReset (&ztk->zstream);
2202         }
2203
2204         // We need a big buffer to force inflating into it directly
2205         buffersize = 2 * sizeof (file->buff);
2206         buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2207
2208         // Skip all data until we reach the requested offset
2209         while (offset > file->position)
2210         {
2211                 fs_offset_t diff = offset - file->position;
2212                 fs_offset_t count, len;
2213
2214                 count = (diff > buffersize) ? buffersize : diff;
2215                 len = FS_Read (file, buffer, count);
2216                 if (len != count)
2217                 {
2218                         Mem_Free (buffer);
2219                         return -1;
2220                 }
2221         }
2222
2223         Mem_Free (buffer);
2224         return 0;
2225 }
2226
2227
2228 /*
2229 ====================
2230 FS_Tell
2231
2232 Give the current position in a file
2233 ====================
2234 */
2235 fs_offset_t FS_Tell (qfile_t* file)
2236 {
2237         return file->position - file->buff_len + file->buff_ind;
2238 }
2239
2240
2241 /*
2242 ====================
2243 FS_FileSize
2244
2245 Give the total size of a file
2246 ====================
2247 */
2248 fs_offset_t FS_FileSize (qfile_t* file)
2249 {
2250         return file->real_length;
2251 }
2252
2253
2254 /*
2255 ====================
2256 FS_Purge
2257
2258 Erases any buffered input or output data
2259 ====================
2260 */
2261 void FS_Purge (qfile_t* file)
2262 {
2263         file->buff_len = 0;
2264         file->buff_ind = 0;
2265         file->ungetc = EOF;
2266 }
2267
2268
2269 /*
2270 ============
2271 FS_LoadFile
2272
2273 Filename are relative to the quake directory.
2274 Always appends a 0 byte.
2275 ============
2276 */
2277 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2278 {
2279         qfile_t *file;
2280         unsigned char *buf = NULL;
2281         fs_offset_t filesize = 0;
2282
2283         file = FS_Open (path, "rb", quiet, false);
2284         if (file)
2285         {
2286                 filesize = file->real_length;
2287                 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2288                 buf[filesize] = '\0';
2289                 FS_Read (file, buf, filesize);
2290                 FS_Close (file);
2291         }
2292
2293         if (filesizepointer)
2294                 *filesizepointer = filesize;
2295         return buf;
2296 }
2297
2298
2299 /*
2300 ============
2301 FS_WriteFile
2302
2303 The filename will be prefixed by the current game directory
2304 ============
2305 */
2306 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2307 {
2308         qfile_t *file;
2309
2310         file = FS_Open (filename, "wb", false, false);
2311         if (!file)
2312         {
2313                 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2314                 return false;
2315         }
2316
2317         Con_DPrintf("FS_WriteFile: %s\n", filename);
2318         FS_Write (file, data, len);
2319         FS_Close (file);
2320         return true;
2321 }
2322
2323
2324 /*
2325 =============================================================================
2326
2327 OTHERS PUBLIC FUNCTIONS
2328
2329 =============================================================================
2330 */
2331
2332 /*
2333 ============
2334 FS_StripExtension
2335 ============
2336 */
2337 void FS_StripExtension (const char *in, char *out, size_t size_out)
2338 {
2339         char *last = NULL;
2340         char currentchar;
2341
2342         if (size_out == 0)
2343                 return;
2344
2345         while ((currentchar = *in) && size_out > 1)
2346         {
2347                 if (currentchar == '.')
2348                         last = out;
2349                 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2350                         last = NULL;
2351                 *out++ = currentchar;
2352                 in++;
2353                 size_out--;
2354         }
2355         if (last)
2356                 *last = 0;
2357         else
2358                 *out = 0;
2359 }
2360
2361
2362 /*
2363 ==================
2364 FS_DefaultExtension
2365 ==================
2366 */
2367 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2368 {
2369         const char *src;
2370
2371         // if path doesn't have a .EXT, append extension
2372         // (extension should include the .)
2373         src = path + strlen(path) - 1;
2374
2375         while (*src != '/' && src != path)
2376         {
2377                 if (*src == '.')
2378                         return;                 // it has an extension
2379                 src--;
2380         }
2381
2382         strlcat (path, extension, size_path);
2383 }
2384
2385
2386 /*
2387 ==================
2388 FS_FileExists
2389
2390 Look for a file in the packages and in the filesystem
2391 ==================
2392 */
2393 qboolean FS_FileExists (const char *filename)
2394 {
2395         return (FS_FindFile (filename, NULL, true) != NULL);
2396 }
2397
2398
2399 /*
2400 ==================
2401 FS_SysFileExists
2402
2403 Look for a file in the filesystem only
2404 ==================
2405 */
2406 qboolean FS_SysFileExists (const char *path)
2407 {
2408 #if WIN32
2409         int desc;
2410
2411         // TODO: use another function instead, to avoid opening the file
2412         desc = open (path, O_RDONLY | O_BINARY);
2413         if (desc < 0)
2414                 return false;
2415
2416         close (desc);
2417         return true;
2418 #else
2419         struct stat buf;
2420
2421         if (stat (path,&buf) == -1)
2422                 return false;
2423
2424         return true;
2425 #endif
2426 }
2427
2428 void FS_mkdir (const char *path)
2429 {
2430 #if WIN32
2431         _mkdir (path);
2432 #else
2433         mkdir (path, 0777);
2434 #endif
2435 }
2436
2437 /*
2438 ===========
2439 FS_Search
2440
2441 Allocate and fill a search structure with information on matching filenames.
2442 ===========
2443 */
2444 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2445 {
2446         fssearch_t *search;
2447         searchpath_t *searchpath;
2448         pack_t *pak;
2449         int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2450         stringlist_t resultlist;
2451         stringlist_t dirlist;
2452         const char *slash, *backslash, *colon, *separator;
2453         char *basepath;
2454         char netpath[MAX_OSPATH];
2455         char temp[MAX_OSPATH];
2456
2457         for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2458                 ;
2459
2460         if (i > 0)
2461         {
2462                 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2463                 return NULL;
2464         }
2465
2466         stringlistinit(&resultlist);
2467         stringlistinit(&dirlist);
2468         search = NULL;
2469         slash = strrchr(pattern, '/');
2470         backslash = strrchr(pattern, '\\');
2471         colon = strrchr(pattern, ':');
2472         separator = max(slash, backslash);
2473         separator = max(separator, colon);
2474         basepathlength = separator ? (separator + 1 - pattern) : 0;
2475         basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2476         if (basepathlength)
2477                 memcpy(basepath, pattern, basepathlength);
2478         basepath[basepathlength] = 0;
2479
2480         // search through the path, one element at a time
2481         for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2482         {
2483                 // is the element a pak file?
2484                 if (searchpath->pack)
2485                 {
2486                         // look through all the pak file elements
2487                         pak = searchpath->pack;
2488                         for (i = 0;i < pak->numfiles;i++)
2489                         {
2490                                 strlcpy(temp, pak->files[i].name, sizeof(temp));
2491                                 while (temp[0])
2492                                 {
2493                                         if (matchpattern(temp, (char *)pattern, true))
2494                                         {
2495                                                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2496                                                         if (!strcmp(resultlist.strings[resultlistindex], temp))
2497                                                                 break;
2498                                                 if (resultlistindex == resultlist.numstrings)
2499                                                 {
2500                                                         stringlistappend(&resultlist, temp);
2501                                                         if (!quiet)
2502                                                                 Con_DPrintf("SearchPackFile: %s : %s\n", pak->filename, temp);
2503                                                 }
2504                                         }
2505                                         // strip off one path element at a time until empty
2506                                         // this way directories are added to the listing if they match the pattern
2507                                         slash = strrchr(temp, '/');
2508                                         backslash = strrchr(temp, '\\');
2509                                         colon = strrchr(temp, ':');
2510                                         separator = temp;
2511                                         if (separator < slash)
2512                                                 separator = slash;
2513                                         if (separator < backslash)
2514                                                 separator = backslash;
2515                                         if (separator < colon)
2516                                                 separator = colon;
2517                                         *((char *)separator) = 0;
2518                                 }
2519                         }
2520                 }
2521                 else
2522                 {
2523                         // get a directory listing and look at each name
2524                         dpsnprintf(netpath, sizeof (netpath), "%s%s", searchpath->filename, basepath);
2525                         stringlistinit(&dirlist);
2526                         listdirectory(&dirlist, netpath);
2527                         for (dirlistindex = 0;dirlistindex < dirlist.numstrings;dirlistindex++)
2528                         {
2529                                 dpsnprintf(temp, sizeof(temp), "%s%s", basepath, dirlist.strings[dirlistindex]);
2530                                 if (matchpattern(temp, (char *)pattern, true))
2531                                 {
2532                                         for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2533                                                 if (!strcmp(resultlist.strings[resultlistindex], temp))
2534                                                         break;
2535                                         if (resultlistindex == resultlist.numstrings)
2536                                         {
2537                                                 stringlistappend(&resultlist, temp);
2538                                                 if (!quiet)
2539                                                         Con_DPrintf("SearchDirFile: %s\n", temp);
2540                                         }
2541                                 }
2542                         }
2543                         stringlistfreecontents(&dirlist);
2544                 }
2545         }
2546
2547         if (resultlist.numstrings)
2548         {
2549                 stringlistsort(&resultlist);
2550                 numfiles = resultlist.numstrings;
2551                 numchars = 0;
2552                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2553                         numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
2554                 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2555                 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2556                 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2557                 search->numfilenames = (int)numfiles;
2558                 numfiles = 0;
2559                 numchars = 0;
2560                 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2561                 {
2562                         size_t textlen;
2563                         search->filenames[numfiles] = search->filenamesbuffer + numchars;
2564                         textlen = strlen(resultlist.strings[resultlistindex]) + 1;
2565                         memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
2566                         numfiles++;
2567                         numchars += (int)textlen;
2568                 }
2569         }
2570         stringlistfreecontents(&resultlist);
2571
2572         Mem_Free(basepath);
2573         return search;
2574 }
2575
2576 void FS_FreeSearch(fssearch_t *search)
2577 {
2578         Z_Free(search);
2579 }
2580
2581 extern int con_linewidth;
2582 int FS_ListDirectory(const char *pattern, int oneperline)
2583 {
2584         int numfiles;
2585         int numcolumns;
2586         int numlines;
2587         int columnwidth;
2588         int linebufpos;
2589         int i, j, k, l;
2590         const char *name;
2591         char linebuf[MAX_INPUTLINE];
2592         fssearch_t *search;
2593         search = FS_Search(pattern, true, true);
2594         if (!search)
2595                 return 0;
2596         numfiles = search->numfilenames;
2597         if (!oneperline)
2598         {
2599                 // FIXME: the names could be added to one column list and then
2600                 // gradually shifted into the next column if they fit, and then the
2601                 // next to make a compact variable width listing but it's a lot more
2602                 // complicated...
2603                 // find width for columns
2604                 columnwidth = 0;
2605                 for (i = 0;i < numfiles;i++)
2606                 {
2607                         l = (int)strlen(search->filenames[i]);
2608                         if (columnwidth < l)
2609                                 columnwidth = l;
2610                 }
2611                 // count the spacing character
2612                 columnwidth++;
2613                 // calculate number of columns
2614                 numcolumns = con_linewidth / columnwidth;
2615                 // don't bother with the column printing if it's only one column
2616                 if (numcolumns >= 2)
2617                 {
2618                         numlines = (numfiles + numcolumns - 1) / numcolumns;
2619                         for (i = 0;i < numlines;i++)
2620                         {
2621                                 linebufpos = 0;
2622                                 for (k = 0;k < numcolumns;k++)
2623                                 {
2624                                         l = i * numcolumns + k;
2625                                         if (l < numfiles)
2626                                         {
2627                                                 name = search->filenames[l];
2628                                                 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2629                                                         linebuf[linebufpos++] = name[j];
2630                                                 // space out name unless it's the last on the line
2631                                                 if (k + 1 < numcolumns && l + 1 < numfiles)
2632                                                         for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2633                                                                 linebuf[linebufpos++] = ' ';
2634                                         }
2635                                 }
2636                                 linebuf[linebufpos] = 0;
2637                                 Con_Printf("%s\n", linebuf);
2638                         }
2639                 }
2640                 else
2641                         oneperline = true;
2642         }
2643         if (oneperline)
2644                 for (i = 0;i < numfiles;i++)
2645                         Con_Printf("%s\n", search->filenames[i]);
2646         FS_FreeSearch(search);
2647         return (int)numfiles;
2648 }
2649
2650 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2651 {
2652         const char *pattern;
2653         if (Cmd_Argc() > 3)
2654         {
2655                 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2656                 return;
2657         }
2658         if (Cmd_Argc() == 2)
2659                 pattern = Cmd_Argv(1);
2660         else
2661                 pattern = "*";
2662         if (!FS_ListDirectory(pattern, oneperline))
2663                 Con_Print("No files found.\n");
2664 }
2665
2666 void FS_Dir_f(void)
2667 {
2668         FS_ListDirectoryCmd("dir", true);
2669 }
2670
2671 void FS_Ls_f(void)
2672 {
2673         FS_ListDirectoryCmd("ls", false);
2674 }
2675
2676 const char *FS_WhichPack(const char *filename)
2677 {
2678         int index;
2679         searchpath_t *sp = FS_FindFile(filename, &index, true);
2680         if(sp && sp->pack)
2681                 return sp->pack->filename;
2682         else
2683                 return 0;
2684 }
2685
2686 /*
2687 ====================
2688 FS_IsRegisteredQuakePack
2689
2690 Look for a proof of purchase file file in the requested package
2691
2692 If it is found, this file should NOT be downloaded.
2693 ====================
2694 */
2695 qboolean FS_IsRegisteredQuakePack(const char *name)
2696 {
2697         searchpath_t *search;
2698         pack_t *pak;
2699
2700         // search through the path, one element at a time
2701         for (search = fs_searchpaths;search;search = search->next)
2702         {
2703                 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
2704                 {
2705                         int (*strcmp_funct) (const char* str1, const char* str2);
2706                         int left, right, middle;
2707
2708                         pak = search->pack;
2709                         strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2710
2711                         // Look for the file (binary search)
2712                         left = 0;
2713                         right = pak->numfiles - 1;
2714                         while (left <= right)
2715                         {
2716                                 int diff;
2717
2718                                 middle = (left + right) / 2;
2719                                 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
2720
2721                                 // Found it
2722                                 if (!diff)
2723                                         return true;
2724
2725                                 // If we're too far in the list
2726                                 if (diff > 0)
2727                                         right = middle - 1;
2728                                 else
2729                                         left = middle + 1;
2730                         }
2731
2732                         // we found the requested pack but it is not registered quake
2733                         return false;
2734                 }
2735         }
2736
2737         return false;
2738 }
2739
2740 int FS_CRCFile(const char *filename, size_t *filesizepointer)
2741 {
2742         int crc = -1;
2743         unsigned char *filedata;
2744         fs_offset_t filesize;
2745         if (filesizepointer)
2746                 *filesizepointer = 0;
2747         if (!filename || !*filename)
2748                 return crc;
2749         filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
2750         if (filedata)
2751         {
2752                 if (filesizepointer)
2753                         *filesizepointer = filesize;
2754                 crc = CRC_Block(filedata, filesize);
2755                 Mem_Free(filedata);
2756         }
2757         return crc;
2758 }
2759