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 if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
477 Mem_Free (central_dir);
481 // Extract the files properties
482 // The parsing is done "by hand" because some fields have variable sizes and
483 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
484 remaining = eocd->cdir_size;
487 for (ind = 0; ind < eocd->nbentries; ind++)
489 fs_offset_t namesize, count;
491 // Checking the remaining size
492 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
494 Mem_Free (central_dir);
497 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
500 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
502 Mem_Free (central_dir);
506 namesize = BuffLittleShort (&ptr[28]); // filename length
508 // Check encryption, compression, and attributes
509 // 1st uint8 : general purpose bit flag
510 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
512 // LordHavoc: bit 3 would be a problem if we were scanning the archive
513 // but is not a problem in the central directory where the values are
516 // bit 3 seems to always be set by the standard Mac OSX zip maker
518 // 2nd uint8 : external file attributes
519 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
520 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
522 // Still enough bytes for the name?
523 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
525 Mem_Free (central_dir);
529 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
530 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
532 char filename [sizeof (pack->files[0].name)];
533 fs_offset_t offset, packsize, realsize;
536 // Extract the name (strip it if necessary)
537 namesize = min(namesize, (int)sizeof (filename) - 1);
538 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
539 filename[namesize] = '\0';
541 if (BuffLittleShort (&ptr[10]))
542 flags = PACKFILE_FLAG_DEFLATED;
545 offset = BuffLittleLong (&ptr[42]);
546 packsize = BuffLittleLong (&ptr[20]);
547 realsize = BuffLittleLong (&ptr[24]);
549 switch(ptr[5]) // C_VERSION_MADE_BY_1
554 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
555 // can't use S_ISLNK here, as this has to compile on non-UNIX too
556 flags |= PACKFILE_FLAG_SYMLINK;
560 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
564 // Skip the name, additionnal field, and comment
565 // 1er uint16 : extra field length
566 // 2eme uint16 : file comment length
567 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
568 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
572 // If the package is empty, central_dir is NULL here
573 if (central_dir != NULL)
574 Mem_Free (central_dir);
575 return pack->numfiles;
583 Create a package entry associated with a PK3 file
586 pack_t *FS_LoadPackPK3 (const char *packfile)
589 pk3_endOfCentralDir_t eocd;
594 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
596 packhandle = open (packfile, O_RDONLY | O_BINARY);
601 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
603 Con_Printf ("%s is not a PK3 file\n", packfile);
608 // Multi-volume ZIP archives are NOT allowed
609 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
611 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
616 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
617 // since eocd.nbentries is an unsigned 16 bits integer
618 #if MAX_FILES_IN_PACK < 65535
619 if (eocd.nbentries > MAX_FILES_IN_PACK)
621 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
627 // Create a package structure in memory
628 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
629 pack->ignorecase = true; // PK3 ignores case
630 strlcpy (pack->filename, packfile, sizeof (pack->filename));
631 pack->handle = packhandle;
632 pack->numfiles = eocd.nbentries;
633 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
635 real_nb_files = PK3_BuildFileList (pack, &eocd);
636 if (real_nb_files < 0)
638 Con_Printf ("%s is not a valid PK3 file\n", packfile);
644 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
651 PK3_GetTrueFileOffset
653 Find where the true file data offset is
656 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
658 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
662 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
665 // Load the local file description
666 lseek (pack->handle, pfile->offset, SEEK_SET);
667 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
668 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
670 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
674 // Skip name and extra field
675 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
677 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
683 =============================================================================
685 OTHER PRIVATE FUNCTIONS
687 =============================================================================
695 Add a file to the list of files contained into a package
698 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
699 fs_offset_t offset, fs_offset_t packsize,
700 fs_offset_t realsize, int flags)
702 int (*strcmp_funct) (const char* str1, const char* str2);
703 int left, right, middle;
706 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
708 // Look for the slot we should put that file into (binary search)
710 right = pack->numfiles - 1;
711 while (left <= right)
715 middle = (left + right) / 2;
716 diff = strcmp_funct (pack->files[middle].name, name);
718 // If we found the file, there's a problem
720 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
722 // If we're too far in the list
729 // We have to move the right of the list by one slot to free the one we need
730 pfile = &pack->files[left];
731 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
734 strlcpy (pfile->name, name, sizeof (pfile->name));
735 pfile->offset = offset;
736 pfile->packsize = packsize;
737 pfile->realsize = realsize;
738 pfile->flags = flags;
748 Only used for FS_OpenRealFile.
751 void FS_CreatePath (char *path)
755 for (ofs = path+1 ; *ofs ; ofs++)
757 if (*ofs == '/' || *ofs == '\\')
759 // create the directory
775 void FS_Path_f (void)
779 Con_Print("Current search path:\n");
780 for (s=fs_searchpaths ; s ; s=s->next)
783 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
785 Con_Printf("%s\n", s->filename);
794 Takes an explicit (not game tree related) path to a pak file.
796 Loads the header and directory, adding the files at the beginning
797 of the list so they override previous pack files.
800 pack_t *FS_LoadPackPAK (const char *packfile)
802 dpackheader_t header;
809 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
811 packhandle = open (packfile, O_RDONLY | O_BINARY);
815 if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
817 Con_Printf ("%s is not a packfile\n", packfile);
821 if (memcmp(header.id, "PACK", 4))
823 Con_Printf ("%s is not a packfile\n", packfile);
827 header.dirofs = LittleLong (header.dirofs);
828 header.dirlen = LittleLong (header.dirlen);
830 if (header.dirlen % sizeof(dpackfile_t))
832 Con_Printf ("%s has an invalid directory size\n", packfile);
837 numpackfiles = header.dirlen / sizeof(dpackfile_t);
839 if (numpackfiles > MAX_FILES_IN_PACK)
841 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
846 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
847 lseek (packhandle, header.dirofs, SEEK_SET);
848 if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
850 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
856 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
857 pack->ignorecase = false; // PAK is case sensitive
858 strlcpy (pack->filename, packfile, sizeof (pack->filename));
859 pack->handle = packhandle;
861 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
863 // parse the directory
864 for (i = 0;i < numpackfiles;i++)
866 fs_offset_t offset = LittleLong (info[i].filepos);
867 fs_offset_t size = LittleLong (info[i].filelen);
869 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
874 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
882 Adds the given pack to the search path.
883 The pack type is autodetected by the file extension.
885 Returns true if the file was successfully added to the
886 search path or if it was already included.
888 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
892 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
894 searchpath_t *search;
896 const char *ext = FS_FileExtension(pakfile);
898 for(search = fs_searchpaths; search; search = search->next)
900 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
903 *already_loaded = true;
904 return true; // already loaded
909 *already_loaded = false;
911 if(!strcasecmp(ext, "pak"))
912 pak = FS_LoadPackPAK (pakfile);
913 else if(!strcasecmp(ext, "pk3"))
914 pak = FS_LoadPackPK3 (pakfile);
916 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
920 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
921 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
924 // find the first item whose next one is a pack or NULL
925 searchpath_t *insertion_point = 0;
926 if(fs_searchpaths && !fs_searchpaths->pack)
928 insertion_point = fs_searchpaths;
931 if(!insertion_point->next)
933 if(insertion_point->next->pack)
935 insertion_point = insertion_point->next;
938 // If insertion_point is NULL, this means that either there is no
939 // item in the list yet, or that the very first item is a pack. In
940 // that case, we want to insert at the beginning...
943 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
945 search->next = fs_searchpaths;
946 fs_searchpaths = search;
949 // otherwise we want to append directly after insertion_point.
951 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
953 search->next = insertion_point->next;
954 insertion_point->next = search;
959 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
961 search->next = fs_searchpaths;
962 fs_searchpaths = search;
968 Con_Printf("unable to load pak \"%s\"\n", pakfile);
978 Adds the given pack to the search path and searches for it in the game path.
979 The pack type is autodetected by the file extension.
981 Returns true if the file was successfully added to the
982 search path or if it was already included.
984 If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
988 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
990 char fullpath[MAX_QPATH];
992 searchpath_t *search;
995 *already_loaded = false;
997 // then find the real name...
998 search = FS_FindFile(pakfile, &index, true);
999 if(!search || search->pack)
1001 Con_Printf("could not find pak \"%s\"\n", pakfile);
1005 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1007 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1015 Sets fs_gamedir, adds the directory to the head of the path,
1016 then loads and adds pak1.pak pak2.pak ...
1019 void FS_AddGameDirectory (const char *dir)
1023 searchpath_t *search;
1025 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1027 stringlistinit(&list);
1028 listdirectory(&list, "", dir);
1029 stringlistsort(&list);
1031 // add any PAK package in the directory
1032 for (i = 0;i < list.numstrings;i++)
1034 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1036 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1040 // add any PK3 package in the directory
1041 for (i = 0;i < list.numstrings;i++)
1043 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
1045 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1049 stringlistfreecontents(&list);
1051 // Add the directory to the search path
1052 // (unpacked files have the priority over packed files)
1053 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1054 strlcpy (search->filename, dir, sizeof (search->filename));
1055 search->next = fs_searchpaths;
1056 fs_searchpaths = search;
1065 void FS_AddGameHierarchy (const char *dir)
1068 char userdir[MAX_QPATH];
1070 TCHAR mydocsdir[MAX_PATH + 1];
1071 #if _MSC_VER >= 1400
1077 // Add the common game directory
1078 FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1082 // Add the personal game directory
1084 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1086 dpsnprintf(userdir, sizeof(userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1087 Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", userdir);
1091 // use the environment
1092 #if _MSC_VER >= 1400
1093 _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1095 homedir = getenv("USERPROFILE");
1100 dpsnprintf(userdir, sizeof(userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1101 #if _MSC_VER >= 1400
1104 Con_DPrintf("Obtained personal directory %s from environment\n", userdir);
1107 *userdir = 0; // just to make sure it hasn't been written to by SHGetFolderPath returning failure
1111 Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1113 homedir = getenv ("HOME");
1115 dpsnprintf(userdir, sizeof(userdir), "%s/.%s/", homedir, gameuserdirname);
1118 Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1123 if(!COM_CheckParm("-mygames"))
1125 #if _MSC_VER >= 1400
1127 _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!
1129 int fd = open (va("%s%s/config.cfg", fs_basedir, dir), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1134 *userdir = 0; // we have write access to the game dir, so let's use it
1139 if(COM_CheckParm("-nohome"))
1142 if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1143 dpsnprintf(userdir, sizeof(userdir), "%s/", com_argv[i+1]);
1146 FS_AddGameDirectory(va("%s%s/", userdir, dir));
1155 const char *FS_FileExtension (const char *in)
1157 const char *separator, *backslash, *colon, *dot;
1159 separator = strrchr(in, '/');
1160 backslash = strrchr(in, '\\');
1161 if (!separator || separator < backslash)
1162 separator = backslash;
1163 colon = strrchr(in, ':');
1164 if (!separator || separator < colon)
1167 dot = strrchr(in, '.');
1168 if (dot == NULL || (separator && (dot < separator)))
1180 const char *FS_FileWithoutPath (const char *in)
1182 const char *separator, *backslash, *colon;
1184 separator = strrchr(in, '/');
1185 backslash = strrchr(in, '\\');
1186 if (!separator || separator < backslash)
1187 separator = backslash;
1188 colon = strrchr(in, ':');
1189 if (!separator || separator < colon)
1191 return separator ? separator + 1 : in;
1200 void FS_ClearSearchPath (void)
1202 // unload all packs and directory information, close all pack files
1203 // (if a qfile is still reading a pack it won't be harmed because it used
1204 // dup() to get its own handle already)
1205 while (fs_searchpaths)
1207 searchpath_t *search = fs_searchpaths;
1208 fs_searchpaths = search->next;
1212 close(search->pack->handle);
1213 // free any memory associated with it
1214 if (search->pack->files)
1215 Mem_Free(search->pack->files);
1216 Mem_Free(search->pack);
1228 void FS_Rescan (void)
1231 qboolean fs_modified = false;
1233 FS_ClearSearchPath();
1235 // add the game-specific paths
1236 // gamedirname1 (typically id1)
1237 FS_AddGameHierarchy (gamedirname1);
1238 // update the com_modname (used for server info)
1239 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1241 // add the game-specific path, if any
1242 // (only used for mission packs and the like, which should set fs_modified)
1246 FS_AddGameHierarchy (gamedirname2);
1250 // Adds basedir/gamedir as an override game
1251 // LordHavoc: now supports multiple -game directories
1252 // set the com_modname (reported in server info)
1253 for (i = 0;i < fs_numgamedirs;i++)
1256 FS_AddGameHierarchy (fs_gamedirs[i]);
1257 // update the com_modname (used server info)
1258 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1261 // set the default screenshot name to either the mod name or the
1262 // gamemode screenshot name
1263 if (strcmp(com_modname, gamedirname1))
1264 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1266 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1268 // If "-condebug" is in the command line, remove the previous log file
1269 if (COM_CheckParm ("-condebug") != 0)
1270 unlink (va("%s/qconsole.log", fs_gamedir));
1272 // look for the pop.lmp file and set registered to true if it is found
1273 if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1276 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1278 Con_Print("Playing shareware version.\n");
1282 Cvar_Set ("registered", "1");
1283 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1284 Con_Print("Playing registered version.\n");
1287 // unload all wads so that future queries will return the new data
1291 void FS_Rescan_f(void)
1301 extern void Host_SaveConfig (void);
1302 extern void Host_LoadConfig_f (void);
1303 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1307 if (fs_numgamedirs == numgamedirs)
1309 for (i = 0;i < numgamedirs;i++)
1310 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1312 if (i == numgamedirs)
1313 return true; // already using this set of gamedirs, do nothing
1316 if (numgamedirs > MAX_GAMEDIRS)
1319 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1320 return false; // too many gamedirs
1323 for (i = 0;i < numgamedirs;i++)
1325 // if string is nasty, reject it
1326 if(FS_CheckNastyPath(gamedirs[i], true))
1329 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1330 return false; // nasty gamedirs
1334 for (i = 0;i < numgamedirs;i++)
1336 if (!FS_CheckGameDir(gamedirs[i]) && failmissing)
1339 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1340 return false; // missing gamedirs
1346 fs_numgamedirs = numgamedirs;
1347 for (i = 0;i < fs_numgamedirs;i++)
1348 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1350 // reinitialize filesystem to detect the new paks
1353 // exec the new config
1354 Host_LoadConfig_f();
1356 // unload all sounds so they will be reloaded from the new files as needed
1357 S_UnloadAllSounds_f();
1359 // reinitialize renderer (this reloads hud/console background/etc)
1360 R_Modules_Restart();
1370 void FS_GameDir_f (void)
1374 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1378 Con_Printf("gamedirs active:");
1379 for (i = 0;i < fs_numgamedirs;i++)
1380 Con_Printf(" %s", fs_gamedirs[i]);
1385 numgamedirs = Cmd_Argc() - 1;
1386 if (numgamedirs > MAX_GAMEDIRS)
1388 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1392 for (i = 0;i < numgamedirs;i++)
1393 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1395 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1397 // actually, changing during game would work fine, but would be stupid
1398 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1402 // halt demo playback to close the file
1405 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1414 qboolean FS_CheckGameDir(const char *gamedir)
1418 stringlistinit(&list);
1419 listdirectory(&list, va("%s%s/", fs_basedir, gamedir), "");
1420 success = list.numstrings > 0;
1421 stringlistfreecontents(&list);
1436 const char* dllnames [] =
1438 "shfolder.dll", // IE 4, or Win NT and higher
1441 Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1442 // don't care for the result; if it fails, %USERPROFILE% will be used instead
1445 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1447 strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1449 // If the base directory is explicitly defined by the compilation process
1450 #ifdef DP_FS_BASEDIR
1451 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1453 strlcpy(fs_basedir, "", sizeof(fs_basedir));
1456 // FIXME: is there a better way to find the directory outside the .app?
1457 if (strstr(com_argv[0], ".app/"))
1461 split = strstr(com_argv[0], ".app/");
1462 while (split > com_argv[0] && *split != '/')
1464 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1465 fs_basedir[split - com_argv[0]] = 0;
1473 // Overrides the system supplied base directory (under GAMENAME)
1474 // 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)
1475 i = COM_CheckParm ("-basedir");
1476 if (i && i < com_argc-1)
1478 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1479 i = (int)strlen (fs_basedir);
1480 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1481 fs_basedir[i-1] = 0;
1484 // add a path separator to the end of the basedir if it lacks one
1485 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1486 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1488 if (!FS_CheckGameDir(gamedirname1))
1489 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1491 if (gamedirname2 && !FS_CheckGameDir(gamedirname2))
1492 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1495 // Adds basedir/gamedir as an override game
1496 // LordHavoc: now supports multiple -game directories
1497 for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1501 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1504 if (FS_CheckNastyPath(com_argv[i], true))
1505 Sys_Error("-game %s%s/ is a dangerous/non-portable path\n", fs_basedir, com_argv[i]);
1506 if (!FS_CheckGameDir(com_argv[i]))
1507 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1508 // add the gamedir to the list of active gamedirs
1509 strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1514 // generate the searchpath
1518 void FS_Init_Commands(void)
1520 Cvar_RegisterVariable (&scr_screenshot_name);
1521 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1523 Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1524 Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1525 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1526 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1527 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1535 void FS_Shutdown (void)
1537 // close all pack files and such
1538 // (hopefully there aren't any other open files, but they'll be cleaned up
1539 // by the OS anyway)
1540 FS_ClearSearchPath();
1541 Mem_FreePool (&fs_mempool);
1544 Sys_UnloadLibrary (&shfolder_dll);
1549 ====================
1552 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1553 ====================
1555 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1561 // Parse the mode string
1570 opt = O_CREAT | O_TRUNC;
1574 opt = O_CREAT | O_APPEND;
1577 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1580 for (ind = 1; mode[ind] != '\0'; ind++)
1591 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1592 filepath, mode, mode[ind]);
1599 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1600 memset (file, 0, sizeof (*file));
1603 #if _MSC_VER >= 1400
1604 _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1606 file->handle = open (filepath, mod | opt, 0666);
1608 if (file->handle < 0)
1614 file->real_length = lseek (file->handle, 0, SEEK_END);
1616 // For files opened in append mode, we start at the end of the file
1618 file->position = file->real_length;
1620 lseek (file->handle, 0, SEEK_SET);
1630 Open a packed file using its package file descriptor
1633 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1639 pfile = &pack->files[pack_ind];
1641 // If we don't have the true offset, get it now
1642 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1643 if (!PK3_GetTrueFileOffset (pfile, pack))
1646 // No Zlib DLL = no compressed files
1647 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1649 Con_Printf("WARNING: can't open the compressed file %s\n"
1650 "You need the Zlib DLL to use compressed files\n",
1655 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1656 // the dup() call to avoid having to close the dup_handle on error here
1657 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1659 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1660 pfile->name, pack->filename, (int) pfile->offset);
1664 dup_handle = dup (pack->handle);
1667 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1671 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1672 memset (file, 0, sizeof (*file));
1673 file->handle = dup_handle;
1674 file->flags = QFILE_FLAG_PACKED;
1675 file->real_length = pfile->realsize;
1676 file->offset = pfile->offset;
1680 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1684 file->flags |= QFILE_FLAG_DEFLATED;
1686 // We need some more variables
1687 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1689 ztk->comp_length = pfile->packsize;
1691 // Initialize zlib stream
1692 ztk->zstream.next_in = ztk->input;
1693 ztk->zstream.avail_in = 0;
1695 /* From Zlib's "unzip.c":
1697 * windowBits is passed < 0 to tell that there is no zlib header.
1698 * Note that in this case inflate *requires* an extra "dummy" byte
1699 * after the compressed stream in order to complete decompression and
1700 * return Z_STREAM_END.
1701 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1702 * size of both compressed and uncompressed data
1704 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1706 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1712 ztk->zstream.next_out = file->buff;
1713 ztk->zstream.avail_out = sizeof (file->buff);
1722 ====================
1725 Return true if the path should be rejected due to one of the following:
1726 1: path elements that are non-portable
1727 2: path elements that would allow access to files outside the game directory,
1728 or are just not a good idea for a mod to be using.
1729 ====================
1731 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1733 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1737 // Windows: don't allow \ in filenames (windows-only), period.
1738 // (on Windows \ is a directory separator, but / is also supported)
1739 if (strstr(path, "\\"))
1740 return 1; // non-portable
1742 // Mac: don't allow Mac-only filenames - : is a directory separator
1743 // instead of /, but we rely on / working already, so there's no reason to
1744 // support a Mac-only path
1745 // Amiga and Windows: : tries to go to root of drive
1746 if (strstr(path, ":"))
1747 return 1; // non-portable attempt to go to root of drive
1749 // Amiga: // is parent directory
1750 if (strstr(path, "//"))
1751 return 1; // non-portable attempt to go to parent directory
1753 // all: don't allow going to parent directory (../ or /../)
1754 if (strstr(path, ".."))
1755 return 2; // attempt to go outside the game directory
1757 // Windows and UNIXes: don't allow absolute paths
1759 return 2; // attempt to go outside the game directory
1761 // 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
1762 if (strchr(path, '.'))
1766 // gamedir is entirely path elements, so simply forbid . entirely
1769 if (strchr(path, '.') < strrchr(path, '/'))
1770 return 2; // possible attempt to go outside the game directory
1773 // all: forbid trailing slash on gamedir
1774 if (isgamedir && path[strlen(path)-1] == '/')
1777 // all: forbid leading dot on any filename for any reason
1778 if (strstr(path, "/."))
1779 return 2; // attempt to go outside the game directory
1781 // after all these checks we're pretty sure it's a / separated filename
1782 // and won't do much if any harm
1788 ====================
1791 Look for a file in the packages and in the filesystem
1793 Return the searchpath where the file was found (or NULL)
1794 and the file index in the package if relevant
1795 ====================
1797 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1799 searchpath_t *search;
1802 // search through the path, one element at a time
1803 for (search = fs_searchpaths;search;search = search->next)
1805 // is the element a pak file?
1808 int (*strcmp_funct) (const char* str1, const char* str2);
1809 int left, right, middle;
1812 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1814 // Look for the file (binary search)
1816 right = pak->numfiles - 1;
1817 while (left <= right)
1821 middle = (left + right) / 2;
1822 diff = strcmp_funct (pak->files[middle].name, name);
1827 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1829 // yes, but the first one is empty so we treat it as not being there
1830 if (!quiet && developer.integer >= 10)
1831 Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1838 if (!quiet && developer.integer >= 10)
1839 Con_Printf("FS_FindFile: %s in %s\n",
1840 pak->files[middle].name, pak->filename);
1847 // If we're too far in the list
1856 char netpath[MAX_OSPATH];
1857 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
1858 if (FS_SysFileExists (netpath))
1860 if (!quiet && developer.integer >= 10)
1861 Con_Printf("FS_FindFile: %s\n", netpath);
1870 if (!quiet && developer.integer >= 10)
1871 Con_Printf("FS_FindFile: can't find %s\n", name);
1883 Look for a file in the search paths and open it in read-only mode
1886 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
1888 searchpath_t *search;
1891 search = FS_FindFile (filename, &pack_ind, quiet);
1897 // Found in the filesystem?
1900 char path [MAX_OSPATH];
1901 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
1902 return FS_SysOpen (path, "rb", nonblocking);
1905 // So, we found it in a package...
1907 // Is it a PK3 symlink?
1908 // TODO also handle directory symlinks by parsing the whole structure...
1909 // but heck, file symlinks are good enough for now
1910 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
1912 if(symlinkLevels <= 0)
1914 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
1919 char linkbuf[MAX_QPATH];
1921 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
1922 const char *mergeslash;
1927 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
1933 // Now combine the paths...
1934 mergeslash = strrchr(filename, '/');
1935 mergestart = linkbuf;
1937 mergeslash = filename;
1938 while(!strncmp(mergestart, "../", 3))
1941 while(mergeslash > filename)
1944 if(*mergeslash == '/')
1948 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
1949 if(mergeslash == filename)
1951 // Either mergeslash == filename, then we just replace the name (done below)
1955 // Or, we append the name after mergeslash;
1956 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
1957 int spaceNeeded = mergeslash - filename + 1;
1958 int spaceRemoved = mergestart - linkbuf;
1959 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
1961 Con_DPrintf("symlink: too long path rejected\n");
1964 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
1965 memcpy(linkbuf, filename, spaceNeeded);
1966 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
1967 mergestart = linkbuf;
1969 if (!quiet && developer_loading.integer)
1970 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
1971 if(FS_CheckNastyPath (mergestart, false))
1973 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
1976 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
1980 return FS_OpenPackedFile (search->pack, pack_ind);
1985 =============================================================================
1987 MAIN PUBLIC FUNCTIONS
1989 =============================================================================
1993 ====================
1996 Open a file in the userpath. The syntax is the same as fopen
1997 Used for savegame scanning in menu, and all file writing.
1998 ====================
2000 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2002 char real_path [MAX_OSPATH];
2004 if (FS_CheckNastyPath(filepath, false))
2006 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2010 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
2012 // If the file is opened in "write", "append", or "read/write" mode,
2013 // create directories up to the file.
2014 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2015 FS_CreatePath (real_path);
2016 return FS_SysOpen (real_path, mode, false);
2021 ====================
2024 Open a file. The syntax is the same as fopen
2025 ====================
2027 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2029 if (FS_CheckNastyPath(filepath, false))
2031 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2035 return FS_OpenReadFile (filepath, quiet, false, 16);
2040 ====================
2044 ====================
2046 int FS_Close (qfile_t* file)
2048 if (close (file->handle))
2053 qz_inflateEnd (&file->ztk->zstream);
2054 Mem_Free (file->ztk);
2063 ====================
2066 Write "datasize" bytes into a file
2067 ====================
2069 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2073 // If necessary, seek to the exact file position we're supposed to be
2074 if (file->buff_ind != file->buff_len)
2075 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2077 // Purge cached data
2080 // Write the buffer and update the position
2081 result = write (file->handle, data, (fs_offset_t)datasize);
2082 file->position = lseek (file->handle, 0, SEEK_CUR);
2083 if (file->real_length < file->position)
2084 file->real_length = file->position;
2094 ====================
2097 Read up to "buffersize" bytes from a file
2098 ====================
2100 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2102 fs_offset_t count, done;
2104 if (buffersize == 0)
2107 // Get rid of the ungetc character
2108 if (file->ungetc != EOF)
2110 ((char*)buffer)[0] = file->ungetc;
2118 // First, we copy as many bytes as we can from "buff"
2119 if (file->buff_ind < file->buff_len)
2121 count = file->buff_len - file->buff_ind;
2122 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2124 memcpy (buffer, &file->buff[file->buff_ind], count);
2125 file->buff_ind += count;
2127 buffersize -= count;
2128 if (buffersize == 0)
2132 // NOTE: at this point, the read buffer is always empty
2134 // If the file isn't compressed
2135 if (! (file->flags & QFILE_FLAG_DEFLATED))
2139 // We must take care to not read after the end of the file
2140 count = file->real_length - file->position;
2142 // If we have a lot of data to get, put them directly into "buffer"
2143 if (buffersize > sizeof (file->buff) / 2)
2145 if (count > (fs_offset_t)buffersize)
2146 count = (fs_offset_t)buffersize;
2147 lseek (file->handle, file->offset + file->position, SEEK_SET);
2148 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2152 file->position += nb;
2154 // Purge cached data
2160 if (count > (fs_offset_t)sizeof (file->buff))
2161 count = (fs_offset_t)sizeof (file->buff);
2162 lseek (file->handle, file->offset + file->position, SEEK_SET);
2163 nb = read (file->handle, file->buff, count);
2166 file->buff_len = nb;
2167 file->position += nb;
2169 // Copy the requested data in "buffer" (as much as we can)
2170 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2171 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2172 file->buff_ind = count;
2180 // If the file is compressed, it's more complicated...
2181 // We cycle through a few operations until we have read enough data
2182 while (buffersize > 0)
2184 ztoolkit_t *ztk = file->ztk;
2187 // NOTE: at this point, the read buffer is always empty
2189 // If "input" is also empty, we need to refill it
2190 if (ztk->in_ind == ztk->in_len)
2192 // If we are at the end of the file
2193 if (file->position == file->real_length)
2196 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2197 if (count > (fs_offset_t)sizeof (ztk->input))
2198 count = (fs_offset_t)sizeof (ztk->input);
2199 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2200 if (read (file->handle, ztk->input, count) != count)
2202 Con_Printf ("FS_Read: unexpected end of file\n");
2207 ztk->in_len = count;
2208 ztk->in_position += count;
2211 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2212 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2214 // Now that we are sure we have compressed data available, we need to determine
2215 // if it's better to inflate it in "file->buff" or directly in "buffer"
2217 // Inflate the data in "file->buff"
2218 if (buffersize < sizeof (file->buff) / 2)
2220 ztk->zstream.next_out = file->buff;
2221 ztk->zstream.avail_out = sizeof (file->buff);
2222 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2223 if (error != Z_OK && error != Z_STREAM_END)
2225 Con_Printf ("FS_Read: Can't inflate file\n");
2228 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2230 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2231 file->position += file->buff_len;
2233 // Copy the requested data in "buffer" (as much as we can)
2234 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2235 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2236 file->buff_ind = count;
2239 // Else, we inflate directly in "buffer"
2242 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2243 ztk->zstream.avail_out = (unsigned int)buffersize;
2244 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2245 if (error != Z_OK && error != Z_STREAM_END)
2247 Con_Printf ("FS_Read: Can't inflate file\n");
2250 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2252 // How much data did it inflate?
2253 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2254 file->position += count;
2256 // Purge cached data
2261 buffersize -= count;
2269 ====================
2272 Print a string into a file
2273 ====================
2275 int FS_Print (qfile_t* file, const char *msg)
2277 return (int)FS_Write (file, msg, strlen (msg));
2281 ====================
2284 Print a string into a file
2285 ====================
2287 int FS_Printf(qfile_t* file, const char* format, ...)
2292 va_start (args, format);
2293 result = FS_VPrintf (file, format, args);
2301 ====================
2304 Print a string into a file
2305 ====================
2307 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2310 fs_offset_t buff_size = MAX_INPUTLINE;
2315 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2316 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2317 if (len >= 0 && len < buff_size)
2319 Mem_Free (tempbuff);
2323 len = write (file->handle, tempbuff, len);
2324 Mem_Free (tempbuff);
2331 ====================
2334 Get the next character of a file
2335 ====================
2337 int FS_Getc (qfile_t* file)
2341 if (FS_Read (file, &c, 1) != 1)
2349 ====================
2352 Put a character back into the read buffer (only supports one character!)
2353 ====================
2355 int FS_UnGetc (qfile_t* file, unsigned char c)
2357 // If there's already a character waiting to be read
2358 if (file->ungetc != EOF)
2367 ====================
2370 Move the position index in a file
2371 ====================
2373 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2376 unsigned char* buffer;
2377 fs_offset_t buffersize;
2379 // Compute the file offset
2383 offset += file->position - file->buff_len + file->buff_ind;
2390 offset += file->real_length;
2396 if (offset < 0 || offset > file->real_length)
2399 // If we have the data in our read buffer, we don't need to actually seek
2400 if (file->position - file->buff_len <= offset && offset <= file->position)
2402 file->buff_ind = offset + file->buff_len - file->position;
2406 // Purge cached data
2409 // Unpacked or uncompressed files can seek directly
2410 if (! (file->flags & QFILE_FLAG_DEFLATED))
2412 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2414 file->position = offset;
2418 // Seeking in compressed files is more a hack than anything else,
2419 // but we need to support it, so here we go.
2422 // If we have to go back in the file, we need to restart from the beginning
2423 if (offset <= file->position)
2427 ztk->in_position = 0;
2429 lseek (file->handle, file->offset, SEEK_SET);
2431 // Reset the Zlib stream
2432 ztk->zstream.next_in = ztk->input;
2433 ztk->zstream.avail_in = 0;
2434 qz_inflateReset (&ztk->zstream);
2437 // We need a big buffer to force inflating into it directly
2438 buffersize = 2 * sizeof (file->buff);
2439 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2441 // Skip all data until we reach the requested offset
2442 while (offset > file->position)
2444 fs_offset_t diff = offset - file->position;
2445 fs_offset_t count, len;
2447 count = (diff > buffersize) ? buffersize : diff;
2448 len = FS_Read (file, buffer, count);
2462 ====================
2465 Give the current position in a file
2466 ====================
2468 fs_offset_t FS_Tell (qfile_t* file)
2470 return file->position - file->buff_len + file->buff_ind;
2475 ====================
2478 Give the total size of a file
2479 ====================
2481 fs_offset_t FS_FileSize (qfile_t* file)
2483 return file->real_length;
2488 ====================
2491 Erases any buffered input or output data
2492 ====================
2494 void FS_Purge (qfile_t* file)
2506 Filename are relative to the quake directory.
2507 Always appends a 0 byte.
2510 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2513 unsigned char *buf = NULL;
2514 fs_offset_t filesize = 0;
2516 file = FS_OpenVirtualFile(path, quiet);
2519 filesize = file->real_length;
2520 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2521 buf[filesize] = '\0';
2522 FS_Read (file, buf, filesize);
2524 if (developer_loadfile.integer)
2525 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2528 if (filesizepointer)
2529 *filesizepointer = filesize;
2538 The filename will be prefixed by the current game directory
2541 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2545 file = FS_OpenRealFile(filename, "wb", false);
2548 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2552 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
2553 FS_Write (file, data, len);
2560 =============================================================================
2562 OTHERS PUBLIC FUNCTIONS
2564 =============================================================================
2572 void FS_StripExtension (const char *in, char *out, size_t size_out)
2580 while ((currentchar = *in) && size_out > 1)
2582 if (currentchar == '.')
2584 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2586 *out++ = currentchar;
2602 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2606 // if path doesn't have a .EXT, append extension
2607 // (extension should include the .)
2608 src = path + strlen(path) - 1;
2610 while (*src != '/' && src != path)
2613 return; // it has an extension
2617 strlcat (path, extension, size_path);
2625 Look for a file in the packages and in the filesystem
2628 int FS_FileType (const char *filename)
2630 searchpath_t *search;
2631 char fullpath[MAX_QPATH];
2633 search = FS_FindFile (filename, NULL, true);
2635 return FS_FILETYPE_NONE;
2638 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
2640 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
2641 return FS_SysFileType(fullpath);
2649 Look for a file in the packages and in the filesystem
2652 qboolean FS_FileExists (const char *filename)
2654 return (FS_FindFile (filename, NULL, true) != NULL);
2662 Look for a file in the filesystem only
2665 int FS_SysFileType (const char *path)
2668 // Sajt - some older sdks are missing this define
2669 # ifndef INVALID_FILE_ATTRIBUTES
2670 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
2673 DWORD result = GetFileAttributes(path);
2675 if(result == INVALID_FILE_ATTRIBUTES)
2676 return FS_FILETYPE_NONE;
2678 if(result & FILE_ATTRIBUTE_DIRECTORY)
2679 return FS_FILETYPE_DIRECTORY;
2681 return FS_FILETYPE_FILE;
2685 if (stat (path,&buf) == -1)
2686 return FS_FILETYPE_NONE;
2688 if(S_ISDIR(buf.st_mode))
2689 return FS_FILETYPE_DIRECTORY;
2691 return FS_FILETYPE_FILE;
2695 qboolean FS_SysFileExists (const char *path)
2697 return FS_SysFileType (path) != FS_FILETYPE_NONE;
2700 void FS_mkdir (const char *path)
2713 Allocate and fill a search structure with information on matching filenames.
2716 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2719 searchpath_t *searchpath;
2721 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2722 stringlist_t resultlist;
2723 stringlist_t dirlist;
2724 const char *slash, *backslash, *colon, *separator;
2726 char temp[MAX_OSPATH];
2728 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2733 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2737 stringlistinit(&resultlist);
2738 stringlistinit(&dirlist);
2740 slash = strrchr(pattern, '/');
2741 backslash = strrchr(pattern, '\\');
2742 colon = strrchr(pattern, ':');
2743 separator = max(slash, backslash);
2744 separator = max(separator, colon);
2745 basepathlength = separator ? (separator + 1 - pattern) : 0;
2746 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2748 memcpy(basepath, pattern, basepathlength);
2749 basepath[basepathlength] = 0;
2751 // search through the path, one element at a time
2752 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2754 // is the element a pak file?
2755 if (searchpath->pack)
2757 // look through all the pak file elements
2758 pak = searchpath->pack;
2759 for (i = 0;i < pak->numfiles;i++)
2761 strlcpy(temp, pak->files[i].name, sizeof(temp));
2764 if (matchpattern(temp, (char *)pattern, true))
2766 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2767 if (!strcmp(resultlist.strings[resultlistindex], temp))
2769 if (resultlistindex == resultlist.numstrings)
2771 stringlistappend(&resultlist, temp);
2772 if (!quiet && developer_loading.integer)
2773 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2776 // strip off one path element at a time until empty
2777 // this way directories are added to the listing if they match the pattern
2778 slash = strrchr(temp, '/');
2779 backslash = strrchr(temp, '\\');
2780 colon = strrchr(temp, ':');
2782 if (separator < slash)
2784 if (separator < backslash)
2785 separator = backslash;
2786 if (separator < colon)
2788 *((char *)separator) = 0;
2794 stringlist_t matchedSet, foundSet;
2795 const char *start = pattern;
2797 stringlistinit(&matchedSet);
2798 stringlistinit(&foundSet);
2799 // add a first entry to the set
2800 stringlistappend(&matchedSet, "");
2801 // iterate through pattern's path
2804 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
2805 char subpath[MAX_OSPATH];
2806 char subpattern[MAX_OSPATH];
2808 // find the next wildcard
2809 wildcard = strchr(start, '?');
2810 asterisk = strchr(start, '*');
2811 if (asterisk && (!wildcard || asterisk < wildcard))
2813 wildcard = asterisk;
2818 nextseparator = strchr( wildcard, '/' );
2822 nextseparator = NULL;
2825 if( !nextseparator ) {
2826 nextseparator = start + strlen( start );
2829 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
2830 // copy everything up except nextseperator
2831 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
2832 // find the last '/' before the wildcard
2833 prevseparator = strrchr( subpattern, '/' );
2835 prevseparator = subpattern;
2838 // copy everything from start to the previous including the '/' (before the wildcard)
2839 // everything up to start is already included in the path of matchedSet's entries
2840 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
2842 // for each entry in matchedSet try to open the subdirectories specified in subpath
2843 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
2844 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
2845 strlcat( temp, subpath, sizeof(temp) );
2846 listdirectory( &foundSet, searchpath->filename, temp );
2848 if( dirlistindex == 0 ) {
2851 // reset the current result set
2852 stringlistfreecontents( &matchedSet );
2853 // match against the pattern
2854 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
2855 const char *direntry = foundSet.strings[ dirlistindex ];
2856 if (matchpattern(direntry, subpattern, true)) {
2857 stringlistappend( &matchedSet, direntry );
2860 stringlistfreecontents( &foundSet );
2862 start = nextseparator;
2865 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
2867 const char *temp = matchedSet.strings[dirlistindex];
2868 if (matchpattern(temp, (char *)pattern, true))
2870 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2871 if (!strcmp(resultlist.strings[resultlistindex], temp))
2873 if (resultlistindex == resultlist.numstrings)
2875 stringlistappend(&resultlist, temp);
2876 if (!quiet && developer_loading.integer)
2877 Con_Printf("SearchDirFile: %s\n", temp);
2881 stringlistfreecontents( &matchedSet );
2885 if (resultlist.numstrings)
2887 stringlistsort(&resultlist);
2888 numfiles = resultlist.numstrings;
2890 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2891 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
2892 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
2893 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
2894 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
2895 search->numfilenames = (int)numfiles;
2898 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2901 search->filenames[numfiles] = search->filenamesbuffer + numchars;
2902 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
2903 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
2905 numchars += (int)textlen;
2908 stringlistfreecontents(&resultlist);
2914 void FS_FreeSearch(fssearch_t *search)
2919 extern int con_linewidth;
2920 int FS_ListDirectory(const char *pattern, int oneperline)
2929 char linebuf[MAX_INPUTLINE];
2931 search = FS_Search(pattern, true, true);
2934 numfiles = search->numfilenames;
2937 // FIXME: the names could be added to one column list and then
2938 // gradually shifted into the next column if they fit, and then the
2939 // next to make a compact variable width listing but it's a lot more
2941 // find width for columns
2943 for (i = 0;i < numfiles;i++)
2945 l = (int)strlen(search->filenames[i]);
2946 if (columnwidth < l)
2949 // count the spacing character
2951 // calculate number of columns
2952 numcolumns = con_linewidth / columnwidth;
2953 // don't bother with the column printing if it's only one column
2954 if (numcolumns >= 2)
2956 numlines = (numfiles + numcolumns - 1) / numcolumns;
2957 for (i = 0;i < numlines;i++)
2960 for (k = 0;k < numcolumns;k++)
2962 l = i * numcolumns + k;
2965 name = search->filenames[l];
2966 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
2967 linebuf[linebufpos++] = name[j];
2968 // space out name unless it's the last on the line
2969 if (k + 1 < numcolumns && l + 1 < numfiles)
2970 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
2971 linebuf[linebufpos++] = ' ';
2974 linebuf[linebufpos] = 0;
2975 Con_Printf("%s\n", linebuf);
2982 for (i = 0;i < numfiles;i++)
2983 Con_Printf("%s\n", search->filenames[i]);
2984 FS_FreeSearch(search);
2985 return (int)numfiles;
2988 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
2990 const char *pattern;
2993 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
2996 if (Cmd_Argc() == 2)
2997 pattern = Cmd_Argv(1);
3000 if (!FS_ListDirectory(pattern, oneperline))
3001 Con_Print("No files found.\n");
3006 FS_ListDirectoryCmd("dir", true);
3011 FS_ListDirectoryCmd("ls", false);
3014 const char *FS_WhichPack(const char *filename)
3017 searchpath_t *sp = FS_FindFile(filename, &index, true);
3019 return sp->pack->shortname;
3025 ====================
3026 FS_IsRegisteredQuakePack
3028 Look for a proof of purchase file file in the requested package
3030 If it is found, this file should NOT be downloaded.
3031 ====================
3033 qboolean FS_IsRegisteredQuakePack(const char *name)
3035 searchpath_t *search;
3038 // search through the path, one element at a time
3039 for (search = fs_searchpaths;search;search = search->next)
3041 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3043 int (*strcmp_funct) (const char* str1, const char* str2);
3044 int left, right, middle;
3047 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3049 // Look for the file (binary search)
3051 right = pak->numfiles - 1;
3052 while (left <= right)
3056 middle = (left + right) / 2;
3057 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3063 // If we're too far in the list
3070 // we found the requested pack but it is not registered quake
3078 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3081 unsigned char *filedata;
3082 fs_offset_t filesize;
3083 if (filesizepointer)
3084 *filesizepointer = 0;
3085 if (!filename || !*filename)
3087 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3090 if (filesizepointer)
3091 *filesizepointer = filesize;
3092 crc = CRC_Block(filedata, filesize);