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