4 Copyright (C) 2003-2006 Mathieu Olivier
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.
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.
15 See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to:
20 Free Software Foundation, Inc.
21 59 Temple Place - Suite 330
22 Boston, MA 02111-1307, USA
36 # include <sys/stat.h>
43 // Win32 requires us to add O_BINARY, but the other OSes don't have it
48 // In case the system doesn't support the O_NONBLOCK flag
53 // largefile support for Win32
55 # define lseek _lseeki64
59 // suppress deprecated warnings
60 # include <sys/stat.h>
65 # define unlink _unlink
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.
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.
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.
91 =============================================================================
95 =============================================================================
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"
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
109 // Zlib constants (from zlib.h)
110 #define Z_SYNC_FLUSH 2
113 #define Z_STREAM_END 1
114 #define ZLIB_VERSION "1.2.3"
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
122 =============================================================================
126 =============================================================================
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
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
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
142 char *msg; // last error message, NULL if no error
143 void *state; // not visible by applications
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
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
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)
160 #define FILE_BUFF_SIZE 2048
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];
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
180 fs_offset_t buff_ind, buff_len; // buffer current index and length
181 unsigned char buff [FILE_BUFF_SIZE];
188 // ------ PK3 files on disk ------ //
190 // You can get the complete ZIP format description from PKWARE website
192 typedef struct pk3_endOfCentralDir_s
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;
205 // ------ PAK files on disk ------ //
206 typedef struct dpackfile_s
209 int filepos, filelen;
212 typedef struct dpackheader_s
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 // file is a symbolic link
226 #define PACKFILE_FLAG_SYMLINK (1 << 2)
228 typedef struct packfile_s
230 char name [MAX_QPATH];
233 fs_offset_t packsize; // size in the package
234 fs_offset_t realsize; // real file size (uncompressed)
237 typedef struct pack_s
239 char filename [MAX_OSPATH];
240 char shortname [MAX_QPATH];
242 int ignorecase; // PK3 ignores case
248 // Search paths for files (including packages)
249 typedef struct searchpath_s
251 // only one of filename / pack will be used
252 char filename[MAX_OSPATH];
254 struct searchpath_s *next;
259 =============================================================================
263 =============================================================================
269 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
270 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
271 fs_offset_t offset, fs_offset_t packsize,
272 fs_offset_t realsize, int flags);
276 =============================================================================
280 =============================================================================
283 mempool_t *fs_mempool;
285 searchpath_t *fs_searchpaths = NULL;
287 #define MAX_FILES_IN_PACK 65536
289 char fs_gamedir[MAX_OSPATH];
290 char fs_basedir[MAX_OSPATH];
292 // list of active game directories (empty if not running a mod)
293 int fs_numgamedirs = 0;
294 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
296 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)"};
297 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"};
301 =============================================================================
303 PRIVATE FUNCTIONS - PK3 HANDLING
305 =============================================================================
308 // Functions exported from zlib
309 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
310 # define ZEXPORT WINAPI
315 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
316 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
317 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
318 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
320 #define qz_inflateInit2(strm, windowBits) \
321 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
323 static dllfunction_t zlibfuncs[] =
325 {"inflate", (void **) &qz_inflate},
326 {"inflateEnd", (void **) &qz_inflateEnd},
327 {"inflateInit2_", (void **) &qz_inflateInit2_},
328 {"inflateReset", (void **) &qz_inflateReset},
332 // Handle for Zlib DLL
333 static dllhandle_t zlib_dll = NULL;
336 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
337 static dllfunction_t shfolderfuncs[] =
339 {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
342 static dllhandle_t shfolder_dll = NULL;
352 void PK3_CloseLibrary (void)
354 Sys_UnloadLibrary (&zlib_dll);
362 Try to load the Zlib DLL
365 qboolean PK3_OpenLibrary (void)
367 const char* dllnames [] =
372 # ifdef ZLIB_USES_WINAPI
378 #elif defined(MACOSX)
392 return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
398 PK3_GetEndOfCentralDir
400 Extract the end of the central directory from a PK3 package
403 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
405 fs_offset_t filesize, maxsize;
406 unsigned char *buffer, *ptr;
409 // Get the package size
410 filesize = lseek (packhandle, 0, SEEK_END);
411 if (filesize < ZIP_END_CDIR_SIZE)
414 // Load the end of the file in memory
415 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
418 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
419 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
420 lseek (packhandle, filesize - maxsize, SEEK_SET);
421 if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
427 // Look for the end of central dir signature around the end of the file
428 maxsize -= ZIP_END_CDIR_SIZE;
429 ptr = &buffer[maxsize];
431 while (BuffBigLong (ptr) != ZIP_END_HEADER)
443 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
444 eocd->signature = LittleLong (eocd->signature);
445 eocd->disknum = LittleShort (eocd->disknum);
446 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
447 eocd->localentries = LittleShort (eocd->localentries);
448 eocd->nbentries = LittleShort (eocd->nbentries);
449 eocd->cdir_size = LittleLong (eocd->cdir_size);
450 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
451 eocd->comment_size = LittleShort (eocd->comment_size);
463 Extract the file list from a PK3 file
466 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
468 unsigned char *central_dir, *ptr;
470 fs_offset_t remaining;
472 // Load the central directory in memory
473 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
474 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
475 read (pack->handle, central_dir, eocd->cdir_size);
477 // Extract the files properties
478 // The parsing is done "by hand" because some fields have variable sizes and
479 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
480 remaining = eocd->cdir_size;
483 for (ind = 0; ind < eocd->nbentries; ind++)
485 fs_offset_t namesize, count;
487 // Checking the remaining size
488 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
490 Mem_Free (central_dir);
493 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
496 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
498 Mem_Free (central_dir);
502 namesize = BuffLittleShort (&ptr[28]); // filename length
504 // Check encryption, compression, and attributes
505 // 1st uint8 : general purpose bit flag
506 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
508 // LordHavoc: bit 3 would be a problem if we were scanning the archive
509 // but is not a problem in the central directory where the values are
512 // bit 3 seems to always be set by the standard Mac OSX zip maker
514 // 2nd uint8 : external file attributes
515 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
516 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
518 // Still enough bytes for the name?
519 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
521 Mem_Free (central_dir);
525 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
526 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
528 char filename [sizeof (pack->files[0].name)];
529 fs_offset_t offset, packsize, realsize;
532 // Extract the name (strip it if necessary)
533 namesize = min(namesize, (int)sizeof (filename) - 1);
534 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
535 filename[namesize] = '\0';
537 if (BuffLittleShort (&ptr[10]))
538 flags = PACKFILE_FLAG_DEFLATED;
541 offset = BuffLittleLong (&ptr[42]);
542 packsize = BuffLittleLong (&ptr[20]);
543 realsize = BuffLittleLong (&ptr[24]);
545 switch(ptr[5]) // C_VERSION_MADE_BY_1
550 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
551 // can't use S_ISLNK here, as this has to compile on non-UNIX too
552 flags |= PACKFILE_FLAG_SYMLINK;
556 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
560 // Skip the name, additionnal field, and comment
561 // 1er uint16 : extra field length
562 // 2eme uint16 : file comment length
563 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
564 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
568 // If the package is empty, central_dir is NULL here
569 if (central_dir != NULL)
570 Mem_Free (central_dir);
571 return pack->numfiles;
579 Create a package entry associated with a PK3 file
582 pack_t *FS_LoadPackPK3 (const char *packfile)
585 pk3_endOfCentralDir_t eocd;
590 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
592 packhandle = open (packfile, O_RDONLY | O_BINARY);
597 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
599 Con_Printf ("%s is not a PK3 file\n", packfile);
604 // Multi-volume ZIP archives are NOT allowed
605 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
607 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
612 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
613 // since eocd.nbentries is an unsigned 16 bits integer
614 #if MAX_FILES_IN_PACK < 65535
615 if (eocd.nbentries > MAX_FILES_IN_PACK)
617 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
623 // Create a package structure in memory
624 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
625 pack->ignorecase = true; // PK3 ignores case
626 strlcpy (pack->filename, packfile, sizeof (pack->filename));
627 pack->handle = packhandle;
628 pack->numfiles = eocd.nbentries;
629 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
631 real_nb_files = PK3_BuildFileList (pack, &eocd);
632 if (real_nb_files < 0)
634 Con_Printf ("%s is not a valid PK3 file\n", packfile);
640 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
647 PK3_GetTrueFileOffset
649 Find where the true file data offset is
652 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
654 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
658 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
661 // Load the local file description
662 lseek (pack->handle, pfile->offset, SEEK_SET);
663 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
664 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
666 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
670 // Skip name and extra field
671 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
673 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
679 =============================================================================
681 OTHER PRIVATE FUNCTIONS
683 =============================================================================
691 Add a file to the list of files contained into a package
694 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
695 fs_offset_t offset, fs_offset_t packsize,
696 fs_offset_t realsize, int flags)
698 int (*strcmp_funct) (const char* str1, const char* str2);
699 int left, right, middle;
702 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
704 // Look for the slot we should put that file into (binary search)
706 right = pack->numfiles - 1;
707 while (left <= right)
711 middle = (left + right) / 2;
712 diff = strcmp_funct (pack->files[middle].name, name);
714 // If we found the file, there's a problem
716 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
718 // If we're too far in the list
725 // We have to move the right of the list by one slot to free the one we need
726 pfile = &pack->files[left];
727 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
730 strlcpy (pfile->name, name, sizeof (pfile->name));
731 pfile->offset = offset;
732 pfile->packsize = packsize;
733 pfile->realsize = realsize;
734 pfile->flags = flags;
744 Only used for FS_OpenRealFile.
747 void FS_CreatePath (char *path)
751 for (ofs = path+1 ; *ofs ; ofs++)
753 if (*ofs == '/' || *ofs == '\\')
755 // create the directory
771 void FS_Path_f (void)
775 Con_Print("Current search path:\n");
776 for (s=fs_searchpaths ; s ; s=s->next)
779 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
781 Con_Printf("%s\n", s->filename);
790 Takes an explicit (not game tree related) path to a pak file.
792 Loads the header and directory, adding the files at the beginning
793 of the list so they override previous pack files.
796 pack_t *FS_LoadPackPAK (const char *packfile)
798 dpackheader_t header;
805 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
807 packhandle = open (packfile, O_RDONLY | O_BINARY);
811 read (packhandle, (void *)&header, sizeof(header));
812 if (memcmp(header.id, "PACK", 4))
814 Con_Printf ("%s is not a packfile\n", packfile);
818 header.dirofs = LittleLong (header.dirofs);
819 header.dirlen = LittleLong (header.dirlen);
821 if (header.dirlen % sizeof(dpackfile_t))
823 Con_Printf ("%s has an invalid directory size\n", packfile);
828 numpackfiles = header.dirlen / sizeof(dpackfile_t);
830 if (numpackfiles > MAX_FILES_IN_PACK)
832 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
837 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
838 lseek (packhandle, header.dirofs, SEEK_SET);
839 if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
841 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
847 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
848 pack->ignorecase = false; // PAK is case sensitive
849 strlcpy (pack->filename, packfile, sizeof (pack->filename));
850 pack->handle = packhandle;
852 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
854 // parse the directory
855 for (i = 0;i < numpackfiles;i++)
857 fs_offset_t offset = LittleLong (info[i].filepos);
858 fs_offset_t size = LittleLong (info[i].filelen);
860 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
865 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
873 Adds the given pack to the search path.
874 The pack type is autodetected by the file extension.
876 Returns true if the file was successfully added to the
877 search path or if it was already included.
879 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
883 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
885 searchpath_t *search;
887 const char *ext = FS_FileExtension(pakfile);
889 for(search = fs_searchpaths; search; search = search->next)
891 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
894 *already_loaded = true;
895 return true; // already loaded
900 *already_loaded = false;
902 if(!strcasecmp(ext, "pak"))
903 pak = FS_LoadPackPAK (pakfile);
904 else if(!strcasecmp(ext, "pk3"))
905 pak = FS_LoadPackPK3 (pakfile);
907 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
911 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
912 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
915 // find the first item whose next one is a pack or NULL
916 searchpath_t *insertion_point = 0;
917 if(fs_searchpaths && !fs_searchpaths->pack)
919 insertion_point = fs_searchpaths;
922 if(!insertion_point->next)
924 if(insertion_point->next->pack)
926 insertion_point = insertion_point->next;
929 // If insertion_point is NULL, this means that either there is no
930 // item in the list yet, or that the very first item is a pack. In
931 // that case, we want to insert at the beginning...
934 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
936 search->next = fs_searchpaths;
937 fs_searchpaths = search;
940 // otherwise we want to append directly after insertion_point.
942 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
944 search->next = insertion_point->next;
945 insertion_point->next = search;
950 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
952 search->next = fs_searchpaths;
953 fs_searchpaths = search;
959 Con_Printf("unable to load pak \"%s\"\n", pakfile);
969 Adds the given pack to the search path and searches for it in the game path.
970 The pack type is autodetected by the file extension.
972 Returns true if the file was successfully added to the
973 search path or if it was already included.
975 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
979 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
981 char fullpath[MAX_QPATH];
983 searchpath_t *search;
986 *already_loaded = false;
988 // then find the real name...
989 search = FS_FindFile(pakfile, &index, true);
990 if(!search || search->pack)
992 Con_Printf("could not find pak \"%s\"\n", pakfile);
996 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
998 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1006 Sets fs_gamedir, adds the directory to the head of the path,
1007 then loads and adds pak1.pak pak2.pak ...
1010 void FS_AddGameDirectory (const char *dir)
1014 searchpath_t *search;
1016 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1018 stringlistinit(&list);
1019 listdirectory(&list, "", dir);
1020 stringlistsort(&list);
1022 // add any PAK package in the directory
1023 for (i = 0;i < list.numstrings;i++)
1025 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1027 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1031 // add any PK3 package in the directory
1032 for (i = 0;i < list.numstrings;i++)
1034 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
1036 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1040 stringlistfreecontents(&list);
1042 // Add the directory to the search path
1043 // (unpacked files have the priority over packed files)
1044 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1045 strlcpy (search->filename, dir, sizeof (search->filename));
1046 search->next = fs_searchpaths;
1047 fs_searchpaths = search;
1056 void FS_AddGameHierarchy (const char *dir)
1059 char userdir[MAX_QPATH];
1061 TCHAR mydocsdir[MAX_PATH + 1];
1062 #if _MSC_VER >= 1400
1068 // Add the common game directory
1069 FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1073 // Add the personal game directory
1075 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1077 dpsnprintf(userdir, sizeof(userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1078 Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", userdir);
1082 // use the environment
1083 #if _MSC_VER >= 1400
1084 _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1086 homedir = getenv("USERPROFILE");
1091 dpsnprintf(userdir, sizeof(userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1092 #if _MSC_VER >= 1400
1095 Con_DPrintf("Obtained personal directory %s from environment\n", userdir);
1098 *userdir = 0; // just to make sure it hasn't been written to by SHGetFolderPath returning failure
1102 Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1104 homedir = getenv ("HOME");
1106 dpsnprintf(userdir, sizeof(userdir), "%s/.%s/", homedir, gameuserdirname);
1109 Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1114 if(!COM_CheckParm("-mygames"))
1116 #if _MSC_VER >= 1400
1118 _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!
1120 int fd = open (va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1125 *userdir = 0; // we have write access to the game dir, so let's use it
1130 if(COM_CheckParm("-nohome"))
1133 if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1134 dpsnprintf(userdir, sizeof(userdir), "%s/", com_argv[i+1]);
1137 FS_AddGameDirectory(va("%s%s/", userdir, dir));
1146 const char *FS_FileExtension (const char *in)
1148 const char *separator, *backslash, *colon, *dot;
1150 separator = strrchr(in, '/');
1151 backslash = strrchr(in, '\\');
1152 if (!separator || separator < backslash)
1153 separator = backslash;
1154 colon = strrchr(in, ':');
1155 if (!separator || separator < colon)
1158 dot = strrchr(in, '.');
1159 if (dot == NULL || (separator && (dot < separator)))
1171 const char *FS_FileWithoutPath (const char *in)
1173 const char *separator, *backslash, *colon;
1175 separator = strrchr(in, '/');
1176 backslash = strrchr(in, '\\');
1177 if (!separator || separator < backslash)
1178 separator = backslash;
1179 colon = strrchr(in, ':');
1180 if (!separator || separator < colon)
1182 return separator ? separator + 1 : in;
1191 void FS_ClearSearchPath (void)
1193 // unload all packs and directory information, close all pack files
1194 // (if a qfile is still reading a pack it won't be harmed because it used
1195 // dup() to get its own handle already)
1196 while (fs_searchpaths)
1198 searchpath_t *search = fs_searchpaths;
1199 fs_searchpaths = search->next;
1203 close(search->pack->handle);
1204 // free any memory associated with it
1205 if (search->pack->files)
1206 Mem_Free(search->pack->files);
1207 Mem_Free(search->pack);
1219 void FS_Rescan (void)
1222 qboolean fs_modified = false;
1224 FS_ClearSearchPath();
1226 // add the game-specific paths
1227 // gamedirname1 (typically id1)
1228 FS_AddGameHierarchy (gamedirname1);
1229 // update the com_modname (used for server info)
1230 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1232 // add the game-specific path, if any
1233 // (only used for mission packs and the like, which should set fs_modified)
1237 FS_AddGameHierarchy (gamedirname2);
1241 // Adds basedir/gamedir as an override game
1242 // LordHavoc: now supports multiple -game directories
1243 // set the com_modname (reported in server info)
1244 for (i = 0;i < fs_numgamedirs;i++)
1247 FS_AddGameHierarchy (fs_gamedirs[i]);
1248 // update the com_modname (used server info)
1249 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1252 // set the default screenshot name to either the mod name or the
1253 // gamemode screenshot name
1254 if (strcmp(com_modname, gamedirname1))
1255 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1257 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1259 // If "-condebug" is in the command line, remove the previous log file
1260 if (COM_CheckParm ("-condebug") != 0)
1261 unlink (va("%s/qconsole.log", fs_gamedir));
1263 // look for the pop.lmp file and set registered to true if it is found
1264 if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1267 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1269 Con_Print("Playing shareware version.\n");
1273 Cvar_Set ("registered", "1");
1274 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1275 Con_Print("Playing registered version.\n");
1278 // unload all wads so that future queries will return the new data
1282 void FS_Rescan_f(void)
1292 extern void Host_SaveConfig (void);
1293 extern void Host_LoadConfig_f (void);
1294 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1298 if (fs_numgamedirs == numgamedirs)
1300 for (i = 0;i < numgamedirs;i++)
1301 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1303 if (i == numgamedirs)
1304 return true; // already using this set of gamedirs, do nothing
1307 if (numgamedirs > MAX_GAMEDIRS)
1310 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1311 return false; // too many gamedirs
1314 for (i = 0;i < numgamedirs;i++)
1316 // if string is nasty, reject it
1317 if(FS_CheckNastyPath(gamedirs[i], true))
1320 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1321 return false; // nasty gamedirs
1325 for (i = 0;i < numgamedirs;i++)
1327 if (!FS_CheckGameDir(gamedirs[i]) && failmissing)
1330 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1331 return false; // missing gamedirs
1337 fs_numgamedirs = numgamedirs;
1338 for (i = 0;i < fs_numgamedirs;i++)
1339 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1341 // reinitialize filesystem to detect the new paks
1344 // exec the new config
1345 Host_LoadConfig_f();
1347 // unload all sounds so they will be reloaded from the new files as needed
1348 S_UnloadAllSounds_f();
1350 // reinitialize renderer (this reloads hud/console background/etc)
1351 R_Modules_Restart();
1361 void FS_GameDir_f (void)
1365 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1369 Con_Printf("gamedirs active:");
1370 for (i = 0;i < fs_numgamedirs;i++)
1371 Con_Printf(" %s", fs_gamedirs[i]);
1376 numgamedirs = Cmd_Argc() - 1;
1377 if (numgamedirs > MAX_GAMEDIRS)
1379 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1383 for (i = 0;i < numgamedirs;i++)
1384 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1386 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1388 // actually, changing during game would work fine, but would be stupid
1389 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1393 // halt demo playback to close the file
1396 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1405 qboolean FS_CheckGameDir(const char *gamedir)
1409 stringlistinit(&list);
1410 listdirectory(&list, va("%s%s/", fs_basedir, gamedir), "");
1411 success = list.numstrings > 0;
1412 stringlistfreecontents(&list);
1427 const char* dllnames [] =
1429 "shfolder.dll", // IE 4, or Win NT and higher
1432 Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1433 // don't care for the result; if it fails, %USERPROFILE% will be used instead
1436 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1438 strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1440 // If the base directory is explicitly defined by the compilation process
1441 #ifdef DP_FS_BASEDIR
1442 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1444 strlcpy(fs_basedir, "", sizeof(fs_basedir));
1447 // FIXME: is there a better way to find the directory outside the .app?
1448 if (strstr(com_argv[0], ".app/"))
1452 split = strstr(com_argv[0], ".app/");
1453 while (split > com_argv[0] && *split != '/')
1455 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1456 fs_basedir[split - com_argv[0]] = 0;
1464 // Overrides the system supplied base directory (under GAMENAME)
1465 // 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)
1466 i = COM_CheckParm ("-basedir");
1467 if (i && i < com_argc-1)
1469 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1470 i = (int)strlen (fs_basedir);
1471 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1472 fs_basedir[i-1] = 0;
1475 // add a path separator to the end of the basedir if it lacks one
1476 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1477 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1479 if (!FS_CheckGameDir(gamedirname1))
1480 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1482 if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
1483 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1486 // Adds basedir/gamedir as an override game
1487 // LordHavoc: now supports multiple -game directories
1488 for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1492 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1495 if (FS_CheckNastyPath(com_argv[i], true))
1496 Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
1497 if (!FS_CheckGameDir(com_argv[i]))
1498 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1499 // add the gamedir to the list of active gamedirs
1500 strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1505 // generate the searchpath
1509 void FS_Init_Commands(void)
1511 Cvar_RegisterVariable (&scr_screenshot_name);
1512 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1514 Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1515 Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1516 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1517 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1518 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1526 void FS_Shutdown (void)
1528 // close all pack files and such
1529 // (hopefully there aren't any other open files, but they'll be cleaned up
1530 // by the OS anyway)
1531 FS_ClearSearchPath();
1532 Mem_FreePool (&fs_mempool);
1535 Sys_UnloadLibrary (&shfolder_dll);
1540 ====================
1543 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1544 ====================
1546 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1552 // Parse the mode string
1561 opt = O_CREAT | O_TRUNC;
1565 opt = O_CREAT | O_APPEND;
1568 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1571 for (ind = 1; mode[ind] != '\0'; ind++)
1582 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1583 filepath, mode, mode[ind]);
1590 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1591 memset (file, 0, sizeof (*file));
1594 #if _MSC_VER >= 1400
1595 _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1597 file->handle = open (filepath, mod | opt, 0666);
1599 if (file->handle < 0)
1605 file->real_length = lseek (file->handle, 0, SEEK_END);
1607 // For files opened in append mode, we start at the end of the file
1609 file->position = file->real_length;
1611 lseek (file->handle, 0, SEEK_SET);
1621 Open a packed file using its package file descriptor
1624 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1630 pfile = &pack->files[pack_ind];
1632 // If we don't have the true offset, get it now
1633 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1634 if (!PK3_GetTrueFileOffset (pfile, pack))
1637 // No Zlib DLL = no compressed files
1638 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1640 Con_Printf("WARNING: can't open the compressed file %s\n"
1641 "You need the Zlib DLL to use compressed files\n",
1646 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1647 // the dup() call to avoid having to close the dup_handle on error here
1648 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1650 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1651 pfile->name, pack->filename, (int) pfile->offset);
1655 dup_handle = dup (pack->handle);
1658 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1662 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1663 memset (file, 0, sizeof (*file));
1664 file->handle = dup_handle;
1665 file->flags = QFILE_FLAG_PACKED;
1666 file->real_length = pfile->realsize;
1667 file->offset = pfile->offset;
1671 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1675 file->flags |= QFILE_FLAG_DEFLATED;
1677 // We need some more variables
1678 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1680 ztk->comp_length = pfile->packsize;
1682 // Initialize zlib stream
1683 ztk->zstream.next_in = ztk->input;
1684 ztk->zstream.avail_in = 0;
1686 /* From Zlib's "unzip.c":
1688 * windowBits is passed < 0 to tell that there is no zlib header.
1689 * Note that in this case inflate *requires* an extra "dummy" byte
1690 * after the compressed stream in order to complete decompression and
1691 * return Z_STREAM_END.
1692 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1693 * size of both compressed and uncompressed data
1695 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1697 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1703 ztk->zstream.next_out = file->buff;
1704 ztk->zstream.avail_out = sizeof (file->buff);
1713 ====================
1716 Return true if the path should be rejected due to one of the following:
1717 1: path elements that are non-portable
1718 2: path elements that would allow access to files outside the game directory,
1719 or are just not a good idea for a mod to be using.
1720 ====================
1722 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1724 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1728 // Windows: don't allow \ in filenames (windows-only), period.
1729 // (on Windows \ is a directory separator, but / is also supported)
1730 if (strstr(path, "\\"))
1731 return 1; // non-portable
1733 // Mac: don't allow Mac-only filenames - : is a directory separator
1734 // instead of /, but we rely on / working already, so there's no reason to
1735 // support a Mac-only path
1736 // Amiga and Windows: : tries to go to root of drive
1737 if (strstr(path, ":"))
1738 return 1; // non-portable attempt to go to root of drive
1740 // Amiga: // is parent directory
1741 if (strstr(path, "//"))
1742 return 1; // non-portable attempt to go to parent directory
1744 // all: don't allow going to parent directory (../ or /../)
1745 if (strstr(path, ".."))
1746 return 2; // attempt to go outside the game directory
1748 // Windows and UNIXes: don't allow absolute paths
1750 return 2; // attempt to go outside the game directory
1752 // 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
1753 if (strchr(path, '.'))
1757 // gamedir is entirely path elements, so simply forbid . entirely
1760 if (strchr(path, '.') < strrchr(path, '/'))
1761 return 2; // possible attempt to go outside the game directory
1764 // all: forbid trailing slash on gamedir
1765 if (isgamedir && path[strlen(path)-1] == '/')
1768 // all: forbid leading dot on any filename for any reason
1769 if (strstr(path, "/."))
1770 return 2; // attempt to go outside the game directory
1772 // after all these checks we're pretty sure it's a / separated filename
1773 // and won't do much if any harm
1779 ====================
1782 Look for a file in the packages and in the filesystem
1784 Return the searchpath where the file was found (or NULL)
1785 and the file index in the package if relevant
1786 ====================
1788 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1790 searchpath_t *search;
1793 // search through the path, one element at a time
1794 for (search = fs_searchpaths;search;search = search->next)
1796 // is the element a pak file?
1799 int (*strcmp_funct) (const char* str1, const char* str2);
1800 int left, right, middle;
1803 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1805 // Look for the file (binary search)
1807 right = pak->numfiles - 1;
1808 while (left <= right)
1812 middle = (left + right) / 2;
1813 diff = strcmp_funct (pak->files[middle].name, name);
1818 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1820 // yes, but the first one is empty so we treat it as not being there
1821 if (!quiet && developer.integer >= 10)
1822 Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1829 if (!quiet && developer.integer >= 10)
1830 Con_Printf("FS_FindFile: %s in %s\n",
1831 pak->files[middle].name, pak->filename);
1838 // If we're too far in the list
1847 char netpath[MAX_OSPATH];
1848 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1849 if (FS_SysFileExists (netpath))
1851 if (!quiet && developer.integer >= 10)
1852 Con_Printf("FS_FindFile: %s\n", netpath);
1861 if (!quiet && developer.integer >= 10)
1862 Con_Printf("FS_FindFile: can't find %s\n", name);
1874 Look for a file in the search paths and open it in read-only mode
1877 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
1879 searchpath_t *search;
1882 search = FS_FindFile (filename, &pack_ind, quiet);
1888 // Found in the filesystem?
1891 char path [MAX_OSPATH];
1892 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1893 return FS_SysOpen (path, "rb", nonblocking);
1896 // So, we found it in a package...
1898 // Is it a PK3 symlink?
1899 // TODO also handle directory symlinks by parsing the whole structure...
1900 // but heck, file symlinks are good enough for now
1901 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
1903 if(symlinkLevels <= 0)
1905 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
1910 char linkbuf[MAX_QPATH];
1912 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
1913 const char *mergeslash;
1918 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
1924 // Now combine the paths...
1925 mergeslash = strrchr(filename, '/');
1926 mergestart = linkbuf;
1928 mergeslash = filename;
1929 while(!strncmp(mergestart, "../", 3))
1932 while(mergeslash > filename)
1935 if(*mergeslash == '/')
1939 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
1940 if(mergeslash == filename)
1942 // Either mergeslash == filename, then we just replace the name (done below)
1946 // Or, we append the name after mergeslash;
1947 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
1948 int spaceNeeded = mergeslash - filename + 1;
1949 int spaceRemoved = mergestart - linkbuf;
1950 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
1952 Con_DPrintf("symlink: too long path rejected\n");
1955 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
1956 memcpy(linkbuf, filename, spaceNeeded);
1957 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
1958 mergestart = linkbuf;
1960 if (!quiet && developer_loading.integer)
1961 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
1962 if(FS_CheckNastyPath (mergestart, false))
1964 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
1967 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
1971 return FS_OpenPackedFile (search->pack, pack_ind);
1976 =============================================================================
1978 MAIN PUBLIC FUNCTIONS
1980 =============================================================================
1984 ====================
1987 Open a file in the userpath. The syntax is the same as fopen
1988 Used for savegame scanning in menu, and all file writing.
1989 ====================
1991 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
1993 char real_path [MAX_OSPATH];
1995 if (FS_CheckNastyPath(filepath, false))
1997 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2001 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
2003 // If the file is opened in "write", "append", or "read/write" mode,
2004 // create directories up to the file.
2005 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2006 FS_CreatePath (real_path);
2007 return FS_SysOpen (real_path, mode, false);
2012 ====================
2015 Open a file. The syntax is the same as fopen
2016 ====================
2018 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2020 if (FS_CheckNastyPath(filepath, false))
2022 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2026 return FS_OpenReadFile (filepath, quiet, false, 16);
2031 ====================
2035 ====================
2037 int FS_Close (qfile_t* file)
2039 if (close (file->handle))
2044 qz_inflateEnd (&file->ztk->zstream);
2045 Mem_Free (file->ztk);
2054 ====================
2057 Write "datasize" bytes into a file
2058 ====================
2060 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2064 // If necessary, seek to the exact file position we're supposed to be
2065 if (file->buff_ind != file->buff_len)
2066 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2068 // Purge cached data
2071 // Write the buffer and update the position
2072 result = write (file->handle, data, (fs_offset_t)datasize);
2073 file->position = lseek (file->handle, 0, SEEK_CUR);
2074 if (file->real_length < file->position)
2075 file->real_length = file->position;
2085 ====================
2088 Read up to "buffersize" bytes from a file
2089 ====================
2091 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2093 fs_offset_t count, done;
2095 if (buffersize == 0)
2098 // Get rid of the ungetc character
2099 if (file->ungetc != EOF)
2101 ((char*)buffer)[0] = file->ungetc;
2109 // First, we copy as many bytes as we can from "buff"
2110 if (file->buff_ind < file->buff_len)
2112 count = file->buff_len - file->buff_ind;
2113 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2115 memcpy (buffer, &file->buff[file->buff_ind], count);
2116 file->buff_ind += count;
2118 buffersize -= count;
2119 if (buffersize == 0)
2123 // NOTE: at this point, the read buffer is always empty
2125 // If the file isn't compressed
2126 if (! (file->flags & QFILE_FLAG_DEFLATED))
2130 // We must take care to not read after the end of the file
2131 count = file->real_length - file->position;
2133 // If we have a lot of data to get, put them directly into "buffer"
2134 if (buffersize > sizeof (file->buff) / 2)
2136 if (count > (fs_offset_t)buffersize)
2137 count = (fs_offset_t)buffersize;
2138 lseek (file->handle, file->offset + file->position, SEEK_SET);
2139 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2143 file->position += nb;
2145 // Purge cached data
2151 if (count > (fs_offset_t)sizeof (file->buff))
2152 count = (fs_offset_t)sizeof (file->buff);
2153 lseek (file->handle, file->offset + file->position, SEEK_SET);
2154 nb = read (file->handle, file->buff, count);
2157 file->buff_len = nb;
2158 file->position += nb;
2160 // Copy the requested data in "buffer" (as much as we can)
2161 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2162 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2163 file->buff_ind = count;
2171 // If the file is compressed, it's more complicated...
2172 // We cycle through a few operations until we have read enough data
2173 while (buffersize > 0)
2175 ztoolkit_t *ztk = file->ztk;
2178 // NOTE: at this point, the read buffer is always empty
2180 // If "input" is also empty, we need to refill it
2181 if (ztk->in_ind == ztk->in_len)
2183 // If we are at the end of the file
2184 if (file->position == file->real_length)
2187 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2188 if (count > (fs_offset_t)sizeof (ztk->input))
2189 count = (fs_offset_t)sizeof (ztk->input);
2190 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2191 if (read (file->handle, ztk->input, count) != count)
2193 Con_Printf ("FS_Read: unexpected end of file\n");
2198 ztk->in_len = count;
2199 ztk->in_position += count;
2202 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2203 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2205 // Now that we are sure we have compressed data available, we need to determine
2206 // if it's better to inflate it in "file->buff" or directly in "buffer"
2208 // Inflate the data in "file->buff"
2209 if (buffersize < sizeof (file->buff) / 2)
2211 ztk->zstream.next_out = file->buff;
2212 ztk->zstream.avail_out = sizeof (file->buff);
2213 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2214 if (error != Z_OK && error != Z_STREAM_END)
2216 Con_Printf ("FS_Read: Can't inflate file\n");
2219 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2221 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2222 file->position += file->buff_len;
2224 // Copy the requested data in "buffer" (as much as we can)
2225 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2226 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2227 file->buff_ind = count;
2230 // Else, we inflate directly in "buffer"
2233 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2234 ztk->zstream.avail_out = (unsigned int)buffersize;
2235 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2236 if (error != Z_OK && error != Z_STREAM_END)
2238 Con_Printf ("FS_Read: Can't inflate file\n");
2241 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2243 // How much data did it inflate?
2244 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2245 file->position += count;
2247 // Purge cached data
2252 buffersize -= count;
2260 ====================
2263 Print a string into a file
2264 ====================
2266 int FS_Print (qfile_t* file, const char *msg)
2268 return (int)FS_Write (file, msg, strlen (msg));
2272 ====================
2275 Print a string into a file
2276 ====================
2278 int FS_Printf(qfile_t* file, const char* format, ...)
2283 va_start (args, format);
2284 result = FS_VPrintf (file, format, args);
2292 ====================
2295 Print a string into a file
2296 ====================
2298 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2301 fs_offset_t buff_size = MAX_INPUTLINE;
2306 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2307 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2308 if (len >= 0 && len < buff_size)
2310 Mem_Free (tempbuff);
2314 len = write (file->handle, tempbuff, len);
2315 Mem_Free (tempbuff);
2322 ====================
2325 Get the next character of a file
2326 ====================
2328 int FS_Getc (qfile_t* file)
2332 if (FS_Read (file, &c, 1) != 1)
2340 ====================
2343 Put a character back into the read buffer (only supports one character!)
2344 ====================
2346 int FS_UnGetc (qfile_t* file, unsigned char c)
2348 // If there's already a character waiting to be read
2349 if (file->ungetc != EOF)
2358 ====================
2361 Move the position index in a file
2362 ====================
2364 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2367 unsigned char* buffer;
2368 fs_offset_t buffersize;
2370 // Compute the file offset
2374 offset += file->position - file->buff_len + file->buff_ind;
2381 offset += file->real_length;
2387 if (offset < 0 || offset > file->real_length)
2390 // If we have the data in our read buffer, we don't need to actually seek
2391 if (file->position - file->buff_len <= offset && offset <= file->position)
2393 file->buff_ind = offset + file->buff_len - file->position;
2397 // Purge cached data
2400 // Unpacked or uncompressed files can seek directly
2401 if (! (file->flags & QFILE_FLAG_DEFLATED))
2403 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2405 file->position = offset;
2409 // Seeking in compressed files is more a hack than anything else,
2410 // but we need to support it, so here we go.
2413 // If we have to go back in the file, we need to restart from the beginning
2414 if (offset <= file->position)
2418 ztk->in_position = 0;
2420 lseek (file->handle, file->offset, SEEK_SET);
2422 // Reset the Zlib stream
2423 ztk->zstream.next_in = ztk->input;
2424 ztk->zstream.avail_in = 0;
2425 qz_inflateReset (&ztk->zstream);
2428 // We need a big buffer to force inflating into it directly
2429 buffersize = 2 * sizeof (file->buff);
2430 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2432 // Skip all data until we reach the requested offset
2433 while (offset > file->position)
2435 fs_offset_t diff = offset - file->position;
2436 fs_offset_t count, len;
2438 count = (diff > buffersize) ? buffersize : diff;
2439 len = FS_Read (file, buffer, count);
2453 ====================
2456 Give the current position in a file
2457 ====================
2459 fs_offset_t FS_Tell (qfile_t* file)
2461 return file->position - file->buff_len + file->buff_ind;
2466 ====================
2469 Give the total size of a file
2470 ====================
2472 fs_offset_t FS_FileSize (qfile_t* file)
2474 return file->real_length;
2479 ====================
2482 Erases any buffered input or output data
2483 ====================
2485 void FS_Purge (qfile_t* file)
2497 Filename are relative to the quake directory.
2498 Always appends a 0 byte.
2501 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2504 unsigned char *buf = NULL;
2505 fs_offset_t filesize = 0;
2507 file = FS_OpenVirtualFile(path, quiet);
2510 filesize = file->real_length;
2511 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2512 buf[filesize] = '\0';
2513 FS_Read (file, buf, filesize);
2515 if (developer_loadfile.integer)
2516 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2519 if (filesizepointer)
2520 *filesizepointer = filesize;
2529 The filename will be prefixed by the current game directory
2532 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2536 file = FS_OpenRealFile(filename, "wb", false);
2539 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2543 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
2544 FS_Write (file, data, len);
2551 =============================================================================
2553 OTHERS PUBLIC FUNCTIONS
2555 =============================================================================
2563 void FS_StripExtension (const char *in, char *out, size_t size_out)
2571 while ((currentchar = *in) && size_out > 1)
2573 if (currentchar == '.')
2575 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2577 *out++ = currentchar;
2593 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2597 // if path doesn't have a .EXT, append extension
2598 // (extension should include the .)
2599 src = path + strlen(path) - 1;
2601 while (*src != '/' && src != path)
2604 return; // it has an extension
2608 strlcat (path, extension, size_path);
2616 Look for a file in the packages and in the filesystem
2619 int FS_FileType (const char *filename)
2621 searchpath_t *search;
2622 char fullpath[MAX_QPATH];
2624 search = FS_FindFile (filename, NULL, true);
2626 return FS_FILETYPE_NONE;
2629 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
2631 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
2632 return FS_SysFileType(fullpath);
2640 Look for a file in the packages and in the filesystem
2643 qboolean FS_FileExists (const char *filename)
2645 return (FS_FindFile (filename, NULL, true) != NULL);
2653 Look for a file in the filesystem only
2656 int FS_SysFileType (const char *path)
2659 // Sajt - some older sdks are missing this define
2660 # ifndef INVALID_FILE_ATTRIBUTES
2661 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
2664 DWORD result = GetFileAttributes(path);
2666 if(result == INVALID_FILE_ATTRIBUTES)
2667 return FS_FILETYPE_NONE;
2669 if(result & FILE_ATTRIBUTE_DIRECTORY)
2670 return FS_FILETYPE_DIRECTORY;
2672 return FS_FILETYPE_FILE;
2676 if (stat (path,&buf) == -1)
2677 return FS_FILETYPE_NONE;
2679 if(S_ISDIR(buf.st_mode))
2680 return FS_FILETYPE_DIRECTORY;
2682 return FS_FILETYPE_FILE;
2686 qboolean FS_SysFileExists (const char *path)
2688 return FS_SysFileType (path) != FS_FILETYPE_NONE;
2691 void FS_mkdir (const char *path)
2704 Allocate and fill a search structure with information on matching filenames.
2707 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2710 searchpath_t *searchpath;
2712 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2713 stringlist_t resultlist;
2714 stringlist_t dirlist;
2715 const char *slash, *backslash, *colon, *separator;
2717 char temp[MAX_OSPATH];
2719 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2724 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2728 stringlistinit(&resultlist);
2729 stringlistinit(&dirlist);
2731 slash = strrchr(pattern, '/');
2732 backslash = strrchr(pattern, '\\');
2733 colon = strrchr(pattern, ':');
2734 separator = max(slash, backslash);
2735 separator = max(separator, colon);
2736 basepathlength = separator ? (separator + 1 - pattern) : 0;
2737 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2739 memcpy(basepath, pattern, basepathlength);
2740 basepath[basepathlength] = 0;
2742 // search through the path, one element at a time
2743 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2745 // is the element a pak file?
2746 if (searchpath->pack)
2748 // look through all the pak file elements
2749 pak = searchpath->pack;
2750 for (i = 0;i < pak->numfiles;i++)
2752 strlcpy(temp, pak->files[i].name, sizeof(temp));
2755 if (matchpattern(temp, (char *)pattern, true))
2757 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2758 if (!strcmp(resultlist.strings[resultlistindex], temp))
2760 if (resultlistindex == resultlist.numstrings)
2762 stringlistappend(&resultlist, temp);
2763 if (!quiet && developer_loading.integer)
2764 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2767 // strip off one path element at a time until empty
2768 // this way directories are added to the listing if they match the pattern
2769 slash = strrchr(temp, '/');
2770 backslash = strrchr(temp, '\\');
2771 colon = strrchr(temp, ':');
2773 if (separator < slash)
2775 if (separator < backslash)
2776 separator = backslash;
2777 if (separator < colon)
2779 *((char *)separator) = 0;
2785 stringlist_t matchedSet, foundSet;
2786 const char *start = pattern;
2788 stringlistinit(&matchedSet);
2789 stringlistinit(&foundSet);
2790 // add a first entry to the set
2791 stringlistappend(&matchedSet, "");
2792 // iterate through pattern's path
2795 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
2796 char subpath[MAX_OSPATH];
2797 char subpattern[MAX_OSPATH];
2799 // find the next wildcard
2800 wildcard = strchr(start, '?');
2801 asterisk = strchr(start, '*');
2802 if (asterisk && (!wildcard || asterisk < wildcard))
2804 wildcard = asterisk;
2809 nextseparator = strchr( wildcard, '/' );
2813 nextseparator = NULL;
2816 if( !nextseparator ) {
2817 nextseparator = start + strlen( start );
2820 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
2821 // copy everything up except nextseperator
2822 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
2823 // find the last '/' before the wildcard
2824 prevseparator = strrchr( subpattern, '/' );
2826 prevseparator = subpattern;
2829 // copy everything from start to the previous including the '/' (before the wildcard)
2830 // everything up to start is already included in the path of matchedSet's entries
2831 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
2833 // for each entry in matchedSet try to open the subdirectories specified in subpath
2834 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
2835 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
2836 strlcat( temp, subpath, sizeof(temp) );
2837 listdirectory( &foundSet, searchpath->filename, temp );
2839 if( dirlistindex == 0 ) {
2842 // reset the current result set
2843 stringlistfreecontents( &matchedSet );
2844 // match against the pattern
2845 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
2846 const char *direntry = foundSet.strings[ dirlistindex ];
2847 if (matchpattern(direntry, subpattern, true)) {
2848 stringlistappend( &matchedSet, direntry );
2851 stringlistfreecontents( &foundSet );
2853 start = nextseparator;
2856 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
2858 const char *temp = matchedSet.strings[dirlistindex];
2859 if (matchpattern(temp, (char *)pattern, true))
2861 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2862 if (!strcmp(resultlist.strings[resultlistindex], temp))
2864 if (resultlistindex == resultlist.numstrings)
2866 stringlistappend(&resultlist, temp);
2867 if (!quiet && developer_loading.integer)
2868 Con_Printf("SearchDirFile: %s\n", temp);
2872 stringlistfreecontents( &matchedSet );
2876 if (resultlist.numstrings)
2878 stringlistsort(&resultlist);
2879 numfiles = resultlist.numstrings;
2881 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2882 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
2883 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2884 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2885 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2886 search->numfilenames = (int)numfiles;
2889 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2892 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2893 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
2894 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
2896 numchars += (int)textlen;
2899 stringlistfreecontents(&resultlist);
2905 void FS_FreeSearch(fssearch_t *search)
2910 extern int con_linewidth;
2911 int FS_ListDirectory(const char *pattern, int oneperline)
2920 char linebuf[MAX_INPUTLINE];
2922 search = FS_Search(pattern, true, true);
2925 numfiles = search->numfilenames;
2928 // FIXME: the names could be added to one column list and then
2929 // gradually shifted into the next column if they fit, and then the
2930 // next to make a compact variable width listing but it's a lot more
2932 // find width for columns
2934 for (i = 0;i < numfiles;i++)
2936 l = (int)strlen(search->filenames[i]);
2937 if (columnwidth < l)
2940 // count the spacing character
2942 // calculate number of columns
2943 numcolumns = con_linewidth / columnwidth;
2944 // don't bother with the column printing if it's only one column
2945 if (numcolumns >= 2)
2947 numlines = (numfiles + numcolumns - 1) / numcolumns;
2948 for (i = 0;i < numlines;i++)
2951 for (k = 0;k < numcolumns;k++)
2953 l = i * numcolumns + k;
2956 name = search->filenames[l];
2957 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2958 linebuf[linebufpos++] = name[j];
2959 // space out name unless it's the last on the line
2960 if (k + 1 < numcolumns && l + 1 < numfiles)
2961 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2962 linebuf[linebufpos++] = ' ';
2965 linebuf[linebufpos] = 0;
2966 Con_Printf("%s\n", linebuf);
2973 for (i = 0;i < numfiles;i++)
2974 Con_Printf("%s\n", search->filenames[i]);
2975 FS_FreeSearch(search);
2976 return (int)numfiles;
2979 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2981 const char *pattern;
2984 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2987 if (Cmd_Argc() == 2)
2988 pattern = Cmd_Argv(1);
2991 if (!FS_ListDirectory(pattern, oneperline))
2992 Con_Print("No files found.\n");
2997 FS_ListDirectoryCmd("dir", true);
3002 FS_ListDirectoryCmd("ls", false);
3005 const char *FS_WhichPack(const char *filename)
3008 searchpath_t *sp = FS_FindFile(filename, &index, true);
3010 return sp->pack->shortname;
3016 ====================
3017 FS_IsRegisteredQuakePack
3019 Look for a proof of purchase file file in the requested package
3021 If it is found, this file should NOT be downloaded.
3022 ====================
3024 qboolean FS_IsRegisteredQuakePack(const char *name)
3026 searchpath_t *search;
3029 // search through the path, one element at a time
3030 for (search = fs_searchpaths;search;search = search->next)
3032 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3034 int (*strcmp_funct) (const char* str1, const char* str2);
3035 int left, right, middle;
3038 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3040 // Look for the file (binary search)
3042 right = pak->numfiles - 1;
3043 while (left <= right)
3047 middle = (left + right) / 2;
3048 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3054 // If we're too far in the list
3061 // we found the requested pack but it is not registered quake
3069 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3072 unsigned char *filedata;
3073 fs_offset_t filesize;
3074 if (filesizepointer)
3075 *filesizepointer = 0;
3076 if (!filename || !*filename)
3078 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3081 if (filesizepointer)
3082 *filesizepointer = filesize;
3083 crc = CRC_Block(filedata, filesize);