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