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