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
69 /** \page fs File System
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 Z_STREAM_ERROR (-2)
115 #define Z_DATA_ERROR (-3)
116 #define Z_MEM_ERROR (-4)
117 #define Z_BUF_ERROR (-5)
118 #define ZLIB_VERSION "1.2.3"
122 #define Z_MEMLEVEL_DEFAULT 8
125 #define Z_DEFAULT_COMPRESSION (-1)
127 #define Z_SYNC_FLUSH 2
128 #define Z_FULL_FLUSH 3
131 // Uncomment the following line if the zlib DLL you have still uses
132 // the 1.1.x series calling convention on Win32 (WINAPI)
133 //#define ZLIB_USES_WINAPI
137 =============================================================================
141 =============================================================================
144 /*! Zlib stream (from zlib.h)
145 * \warning: some pointers we don't use directly have
146 * been cast to "void*" for a matter of simplicity
150 unsigned char *next_in; ///< next input byte
151 unsigned int avail_in; ///< number of bytes available at next_in
152 unsigned long total_in; ///< total nb of input bytes read so far
154 unsigned char *next_out; ///< next output byte should be put there
155 unsigned int avail_out; ///< remaining free space at next_out
156 unsigned long total_out; ///< total nb of bytes output so far
158 char *msg; ///< last error message, NULL if no error
159 void *state; ///< not visible by applications
161 void *zalloc; ///< used to allocate the internal state
162 void *zfree; ///< used to free the internal state
163 void *opaque; ///< private data object passed to zalloc and zfree
165 int data_type; ///< best guess about the data type: ascii or binary
166 unsigned long adler; ///< adler32 value of the uncompressed data
167 unsigned long reserved; ///< reserved for future use
171 /// inside a package (PAK or PK3)
172 #define QFILE_FLAG_PACKED (1 << 0)
173 /// file is compressed using the deflate algorithm (PK3 only)
174 #define QFILE_FLAG_DEFLATED (1 << 1)
175 /// file is actually already loaded data
176 #define QFILE_FLAG_DATA (1 << 2)
178 #define FILE_BUFF_SIZE 2048
182 size_t comp_length; ///< length of the compressed file
183 size_t in_ind, in_len; ///< input buffer current index and length
184 size_t in_position; ///< position in the compressed file
185 unsigned char input [FILE_BUFF_SIZE];
191 int handle; ///< file descriptor
192 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
193 fs_offset_t position; ///< current position in the file
194 fs_offset_t offset; ///< offset into the package (0 if external file)
195 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
198 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
199 unsigned char buff [FILE_BUFF_SIZE];
201 ztoolkit_t* ztk; ///< For zipped files.
203 const unsigned char *data; ///< For data files.
207 // ------ PK3 files on disk ------ //
209 // You can get the complete ZIP format description from PKWARE website
211 typedef struct pk3_endOfCentralDir_s
213 unsigned int signature;
214 unsigned short disknum;
215 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
216 unsigned short localentries; ///< number of entries in the central directory on this disk
217 unsigned short nbentries; ///< total number of entries in the central directory on this disk
218 unsigned int cdir_size; ///< size of the central directory
219 unsigned int cdir_offset; ///< with respect to the starting disk number
220 unsigned short comment_size;
221 } pk3_endOfCentralDir_t;
224 // ------ PAK files on disk ------ //
225 typedef struct dpackfile_s
228 int filepos, filelen;
231 typedef struct dpackheader_s
239 /*! \name Packages in memory
242 /// the offset in packfile_t is the true contents offset
243 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
244 /// file compressed using the deflate algorithm
245 #define PACKFILE_FLAG_DEFLATED (1 << 1)
246 /// file is a symbolic link
247 #define PACKFILE_FLAG_SYMLINK (1 << 2)
249 typedef struct packfile_s
251 char name [MAX_QPATH];
254 fs_offset_t packsize; ///< size in the package
255 fs_offset_t realsize; ///< real file size (uncompressed)
258 typedef struct pack_s
260 char filename [MAX_OSPATH];
261 char shortname [MAX_QPATH];
263 int ignorecase; ///< PK3 ignores case
269 /// Search paths for files (including packages)
270 typedef struct searchpath_s
272 // only one of filename / pack will be used
273 char filename[MAX_OSPATH];
275 struct searchpath_s *next;
280 =============================================================================
284 =============================================================================
289 void FS_Which_f(void);
291 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
292 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
293 fs_offset_t offset, fs_offset_t packsize,
294 fs_offset_t realsize, int flags);
298 =============================================================================
302 =============================================================================
305 mempool_t *fs_mempool;
307 searchpath_t *fs_searchpaths = NULL;
308 const char *const fs_checkgamedir_missing = "missing";
310 #define MAX_FILES_IN_PACK 65536
312 char fs_userdir[MAX_OSPATH];
313 char fs_gamedir[MAX_OSPATH];
314 char fs_basedir[MAX_OSPATH];
316 // list of active game directories (empty if not running a mod)
317 int fs_numgamedirs = 0;
318 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
320 // list of all gamedirs with modinfo.txt
321 gamedir_t *fs_all_gamedirs = NULL;
322 int fs_all_gamedirs_count = 0;
324 cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
325 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"};
326 cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
330 =============================================================================
332 PRIVATE FUNCTIONS - PK3 HANDLING
334 =============================================================================
337 // Functions exported from zlib
338 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
339 # define ZEXPORT WINAPI
344 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
345 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
346 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
347 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
348 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
349 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
350 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
352 #define qz_inflateInit2(strm, windowBits) \
353 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
354 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
355 qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
357 // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
359 static dllfunction_t zlibfuncs[] =
361 {"inflate", (void **) &qz_inflate},
362 {"inflateEnd", (void **) &qz_inflateEnd},
363 {"inflateInit2_", (void **) &qz_inflateInit2_},
364 {"inflateReset", (void **) &qz_inflateReset},
365 {"deflateInit2_", (void **) &qz_deflateInit2_},
366 {"deflateEnd", (void **) &qz_deflateEnd},
367 {"deflate", (void **) &qz_deflate},
371 /// Handle for Zlib DLL
372 static dllhandle_t zlib_dll = NULL;
375 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
376 static dllfunction_t shfolderfuncs[] =
378 {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
381 static dllhandle_t shfolder_dll = NULL;
391 void PK3_CloseLibrary (void)
393 Sys_UnloadLibrary (&zlib_dll);
401 Try to load the Zlib DLL
404 qboolean PK3_OpenLibrary (void)
406 const char* dllnames [] =
411 # ifdef ZLIB_USES_WINAPI
417 #elif defined(MACOSX)
431 return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
438 See if zlib is available
441 qboolean FS_HasZlib(void)
443 PK3_OpenLibrary(); // to be safe
444 return (zlib_dll != 0);
449 PK3_GetEndOfCentralDir
451 Extract the end of the central directory from a PK3 package
454 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
456 fs_offset_t filesize, maxsize;
457 unsigned char *buffer, *ptr;
460 // Get the package size
461 filesize = lseek (packhandle, 0, SEEK_END);
462 if (filesize < ZIP_END_CDIR_SIZE)
465 // Load the end of the file in memory
466 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
469 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
470 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
471 lseek (packhandle, filesize - maxsize, SEEK_SET);
472 if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
478 // Look for the end of central dir signature around the end of the file
479 maxsize -= ZIP_END_CDIR_SIZE;
480 ptr = &buffer[maxsize];
482 while (BuffBigLong (ptr) != ZIP_END_HEADER)
494 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
495 eocd->signature = LittleLong (eocd->signature);
496 eocd->disknum = LittleShort (eocd->disknum);
497 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
498 eocd->localentries = LittleShort (eocd->localentries);
499 eocd->nbentries = LittleShort (eocd->nbentries);
500 eocd->cdir_size = LittleLong (eocd->cdir_size);
501 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
502 eocd->comment_size = LittleShort (eocd->comment_size);
514 Extract the file list from a PK3 file
517 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
519 unsigned char *central_dir, *ptr;
521 fs_offset_t remaining;
523 // Load the central directory in memory
524 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
525 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
526 if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
528 Mem_Free (central_dir);
532 // Extract the files properties
533 // The parsing is done "by hand" because some fields have variable sizes and
534 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
535 remaining = eocd->cdir_size;
538 for (ind = 0; ind < eocd->nbentries; ind++)
540 fs_offset_t namesize, count;
542 // Checking the remaining size
543 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
545 Mem_Free (central_dir);
548 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
551 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
553 Mem_Free (central_dir);
557 namesize = BuffLittleShort (&ptr[28]); // filename length
559 // Check encryption, compression, and attributes
560 // 1st uint8 : general purpose bit flag
561 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
563 // LordHavoc: bit 3 would be a problem if we were scanning the archive
564 // but is not a problem in the central directory where the values are
567 // bit 3 seems to always be set by the standard Mac OSX zip maker
569 // 2nd uint8 : external file attributes
570 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
571 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
573 // Still enough bytes for the name?
574 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
576 Mem_Free (central_dir);
580 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
581 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
583 char filename [sizeof (pack->files[0].name)];
584 fs_offset_t offset, packsize, realsize;
587 // Extract the name (strip it if necessary)
588 namesize = min(namesize, (int)sizeof (filename) - 1);
589 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
590 filename[namesize] = '\0';
592 if (BuffLittleShort (&ptr[10]))
593 flags = PACKFILE_FLAG_DEFLATED;
596 offset = BuffLittleLong (&ptr[42]);
597 packsize = BuffLittleLong (&ptr[20]);
598 realsize = BuffLittleLong (&ptr[24]);
600 switch(ptr[5]) // C_VERSION_MADE_BY_1
605 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
606 // can't use S_ISLNK here, as this has to compile on non-UNIX too
607 flags |= PACKFILE_FLAG_SYMLINK;
611 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
615 // Skip the name, additionnal field, and comment
616 // 1er uint16 : extra field length
617 // 2eme uint16 : file comment length
618 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
619 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
623 // If the package is empty, central_dir is NULL here
624 if (central_dir != NULL)
625 Mem_Free (central_dir);
626 return pack->numfiles;
634 Create a package entry associated with a PK3 file
637 pack_t *FS_LoadPackPK3 (const char *packfile)
640 pk3_endOfCentralDir_t eocd;
645 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
647 packhandle = open (packfile, O_RDONLY | O_BINARY);
652 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
654 Con_Printf ("%s is not a PK3 file\n", packfile);
659 // Multi-volume ZIP archives are NOT allowed
660 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
662 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
667 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
668 // since eocd.nbentries is an unsigned 16 bits integer
669 #if MAX_FILES_IN_PACK < 65535
670 if (eocd.nbentries > MAX_FILES_IN_PACK)
672 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
678 // Create a package structure in memory
679 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
680 pack->ignorecase = true; // PK3 ignores case
681 strlcpy (pack->filename, packfile, sizeof (pack->filename));
682 pack->handle = packhandle;
683 pack->numfiles = eocd.nbentries;
684 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
686 real_nb_files = PK3_BuildFileList (pack, &eocd);
687 if (real_nb_files < 0)
689 Con_Printf ("%s is not a valid PK3 file\n", packfile);
695 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
702 PK3_GetTrueFileOffset
704 Find where the true file data offset is
707 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
709 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
713 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
716 // Load the local file description
717 lseek (pack->handle, pfile->offset, SEEK_SET);
718 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
719 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
721 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
725 // Skip name and extra field
726 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
728 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
734 =============================================================================
736 OTHER PRIVATE FUNCTIONS
738 =============================================================================
746 Add a file to the list of files contained into a package
749 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
750 fs_offset_t offset, fs_offset_t packsize,
751 fs_offset_t realsize, int flags)
753 int (*strcmp_funct) (const char* str1, const char* str2);
754 int left, right, middle;
757 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
759 // Look for the slot we should put that file into (binary search)
761 right = pack->numfiles - 1;
762 while (left <= right)
766 middle = (left + right) / 2;
767 diff = strcmp_funct (pack->files[middle].name, name);
769 // If we found the file, there's a problem
771 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
773 // If we're too far in the list
780 // We have to move the right of the list by one slot to free the one we need
781 pfile = &pack->files[left];
782 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
785 strlcpy (pfile->name, name, sizeof (pfile->name));
786 pfile->offset = offset;
787 pfile->packsize = packsize;
788 pfile->realsize = realsize;
789 pfile->flags = flags;
799 Only used for FS_OpenRealFile.
802 void FS_CreatePath (char *path)
806 for (ofs = path+1 ; *ofs ; ofs++)
808 if (*ofs == '/' || *ofs == '\\')
810 // create the directory
826 void FS_Path_f (void)
830 Con_Print("Current search path:\n");
831 for (s=fs_searchpaths ; s ; s=s->next)
834 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
836 Con_Printf("%s\n", s->filename);
846 /*! Takes an explicit (not game tree related) path to a pak file.
847 *Loads the header and directory, adding the files at the beginning
848 *of the list so they override previous pack files.
850 pack_t *FS_LoadPackPAK (const char *packfile)
852 dpackheader_t header;
859 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
861 packhandle = open (packfile, O_RDONLY | O_BINARY);
865 if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
867 Con_Printf ("%s is not a packfile\n", packfile);
871 if (memcmp(header.id, "PACK", 4))
873 Con_Printf ("%s is not a packfile\n", packfile);
877 header.dirofs = LittleLong (header.dirofs);
878 header.dirlen = LittleLong (header.dirlen);
880 if (header.dirlen % sizeof(dpackfile_t))
882 Con_Printf ("%s has an invalid directory size\n", packfile);
887 numpackfiles = header.dirlen / sizeof(dpackfile_t);
889 if (numpackfiles > MAX_FILES_IN_PACK)
891 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
896 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
897 lseek (packhandle, header.dirofs, SEEK_SET);
898 if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
900 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
906 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
907 pack->ignorecase = false; // PAK is case sensitive
908 strlcpy (pack->filename, packfile, sizeof (pack->filename));
909 pack->handle = packhandle;
911 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
913 // parse the directory
914 for (i = 0;i < numpackfiles;i++)
916 fs_offset_t offset = LittleLong (info[i].filepos);
917 fs_offset_t size = LittleLong (info[i].filelen);
919 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
924 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
933 /*! Adds the given pack to the search path.
934 * The pack type is autodetected by the file extension.
936 * Returns true if the file was successfully added to the
937 * search path or if it was already included.
939 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
943 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
945 searchpath_t *search;
947 const char *ext = FS_FileExtension(pakfile);
949 for(search = fs_searchpaths; search; search = search->next)
951 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
954 *already_loaded = true;
955 return true; // already loaded
960 *already_loaded = false;
962 if(!strcasecmp(ext, "pak"))
963 pak = FS_LoadPackPAK (pakfile);
964 else if(!strcasecmp(ext, "pk3"))
965 pak = FS_LoadPackPK3 (pakfile);
967 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
971 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
972 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
975 // find the first item whose next one is a pack or NULL
976 searchpath_t *insertion_point = 0;
977 if(fs_searchpaths && !fs_searchpaths->pack)
979 insertion_point = fs_searchpaths;
982 if(!insertion_point->next)
984 if(insertion_point->next->pack)
986 insertion_point = insertion_point->next;
989 // If insertion_point is NULL, this means that either there is no
990 // item in the list yet, or that the very first item is a pack. In
991 // that case, we want to insert at the beginning...
994 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
996 search->next = fs_searchpaths;
997 fs_searchpaths = search;
1000 // otherwise we want to append directly after insertion_point.
1002 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1004 search->next = insertion_point->next;
1005 insertion_point->next = search;
1010 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1012 search->next = fs_searchpaths;
1013 fs_searchpaths = search;
1019 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1030 /*! Adds the given pack to the search path and searches for it in the game path.
1031 * The pack type is autodetected by the file extension.
1033 * Returns true if the file was successfully added to the
1034 * search path or if it was already included.
1036 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1037 * plain directories.
1039 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1041 char fullpath[MAX_OSPATH];
1043 searchpath_t *search;
1046 *already_loaded = false;
1048 // then find the real name...
1049 search = FS_FindFile(pakfile, &index, true);
1050 if(!search || search->pack)
1052 Con_Printf("could not find pak \"%s\"\n", pakfile);
1056 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1058 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1066 Sets fs_gamedir, adds the directory to the head of the path,
1067 then loads and adds pak1.pak pak2.pak ...
1070 void FS_AddGameDirectory (const char *dir)
1074 searchpath_t *search;
1076 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1078 stringlistinit(&list);
1079 listdirectory(&list, "", dir);
1080 stringlistsort(&list);
1082 // add any PAK package in the directory
1083 for (i = 0;i < list.numstrings;i++)
1085 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1087 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1091 // add any PK3 package in the directory
1092 for (i = 0;i < list.numstrings;i++)
1094 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
1096 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1100 stringlistfreecontents(&list);
1102 // Add the directory to the search path
1103 // (unpacked files have the priority over packed files)
1104 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1105 strlcpy (search->filename, dir, sizeof (search->filename));
1106 search->next = fs_searchpaths;
1107 fs_searchpaths = search;
1116 void FS_AddGameHierarchy (const char *dir)
1118 // Add the common game directory
1119 FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1122 FS_AddGameDirectory(va("%s%s/", fs_userdir, dir));
1131 const char *FS_FileExtension (const char *in)
1133 const char *separator, *backslash, *colon, *dot;
1135 separator = strrchr(in, '/');
1136 backslash = strrchr(in, '\\');
1137 if (!separator || separator < backslash)
1138 separator = backslash;
1139 colon = strrchr(in, ':');
1140 if (!separator || separator < colon)
1143 dot = strrchr(in, '.');
1144 if (dot == NULL || (separator && (dot < separator)))
1156 const char *FS_FileWithoutPath (const char *in)
1158 const char *separator, *backslash, *colon;
1160 separator = strrchr(in, '/');
1161 backslash = strrchr(in, '\\');
1162 if (!separator || separator < backslash)
1163 separator = backslash;
1164 colon = strrchr(in, ':');
1165 if (!separator || separator < colon)
1167 return separator ? separator + 1 : in;
1176 void FS_ClearSearchPath (void)
1178 // unload all packs and directory information, close all pack files
1179 // (if a qfile is still reading a pack it won't be harmed because it used
1180 // dup() to get its own handle already)
1181 while (fs_searchpaths)
1183 searchpath_t *search = fs_searchpaths;
1184 fs_searchpaths = search->next;
1188 close(search->pack->handle);
1189 // free any memory associated with it
1190 if (search->pack->files)
1191 Mem_Free(search->pack->files);
1192 Mem_Free(search->pack);
1204 void FS_Rescan (void)
1207 qboolean fs_modified = false;
1208 char gamedirbuf[MAX_INPUTLINE];
1210 FS_ClearSearchPath();
1212 // add the game-specific paths
1213 // gamedirname1 (typically id1)
1214 FS_AddGameHierarchy (gamedirname1);
1215 // update the com_modname (used for server info)
1216 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1218 // add the game-specific path, if any
1219 // (only used for mission packs and the like, which should set fs_modified)
1223 FS_AddGameHierarchy (gamedirname2);
1227 // Adds basedir/gamedir as an override game
1228 // LordHavoc: now supports multiple -game directories
1229 // set the com_modname (reported in server info)
1231 for (i = 0;i < fs_numgamedirs;i++)
1234 FS_AddGameHierarchy (fs_gamedirs[i]);
1235 // update the com_modname (used server info)
1236 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1238 strlcat(gamedirbuf, va(" %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1240 strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1242 Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1244 // set the default screenshot name to either the mod name or the
1245 // gamemode screenshot name
1246 if (strcmp(com_modname, gamedirname1))
1247 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1249 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1251 if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1252 strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1254 // If "-condebug" is in the command line, remove the previous log file
1255 if (COM_CheckParm ("-condebug") != 0)
1256 unlink (va("%s/qconsole.log", fs_gamedir));
1258 // look for the pop.lmp file and set registered to true if it is found
1259 if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1262 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1264 Con_Print("Playing shareware version.\n");
1268 Cvar_Set ("registered", "1");
1269 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1270 Con_Print("Playing registered version.\n");
1273 // unload all wads so that future queries will return the new data
1277 void FS_Rescan_f(void)
1287 extern void Host_SaveConfig (void);
1288 extern void Host_LoadConfig_f (void);
1289 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1294 if (fs_numgamedirs == numgamedirs)
1296 for (i = 0;i < numgamedirs;i++)
1297 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1299 if (i == numgamedirs)
1300 return true; // already using this set of gamedirs, do nothing
1303 if (numgamedirs > MAX_GAMEDIRS)
1306 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1307 return false; // too many gamedirs
1310 for (i = 0;i < numgamedirs;i++)
1312 // if string is nasty, reject it
1313 p = FS_CheckGameDir(gamedirs[i]);
1317 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1318 return false; // nasty gamedirs
1320 if(p == fs_checkgamedir_missing && failmissing)
1323 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1324 return false; // missing gamedirs
1330 fs_numgamedirs = numgamedirs;
1331 for (i = 0;i < fs_numgamedirs;i++)
1332 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1334 // reinitialize filesystem to detect the new paks
1337 // exec the new config
1338 Host_LoadConfig_f();
1340 // unload all sounds so they will be reloaded from the new files as needed
1341 S_UnloadAllSounds_f();
1343 // reinitialize renderer (this reloads hud/console background/etc)
1344 R_Modules_Restart();
1354 void FS_GameDir_f (void)
1358 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1362 Con_Printf("gamedirs active:");
1363 for (i = 0;i < fs_numgamedirs;i++)
1364 Con_Printf(" %s", fs_gamedirs[i]);
1369 numgamedirs = Cmd_Argc() - 1;
1370 if (numgamedirs > MAX_GAMEDIRS)
1372 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1376 for (i = 0;i < numgamedirs;i++)
1377 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1379 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1381 // actually, changing during game would work fine, but would be stupid
1382 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1386 // halt demo playback to close the file
1389 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1392 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking);
1393 static const char *FS_SysCheckGameDir(const char *gamedir)
1395 static char buf[8192];
1401 stringlistinit(&list);
1402 listdirectory(&list, gamedir, "");
1403 success = list.numstrings > 0;
1404 stringlistfreecontents(&list);
1408 f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false);
1411 n = FS_Read (f, buf, sizeof(buf) - 1);
1431 const char *FS_CheckGameDir(const char *gamedir)
1435 if (FS_CheckNastyPath(gamedir, true))
1438 ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir));
1443 // get description from basedir
1444 ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1452 ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1456 return fs_checkgamedir_missing;
1459 static void FS_ListGameDirs(void)
1461 stringlist_t list, list2;
1465 fs_all_gamedirs_count = 0;
1467 Mem_Free(fs_all_gamedirs);
1469 stringlistinit(&list);
1470 listdirectory(&list, va("%s/", fs_basedir), "");
1471 listdirectory(&list, va("%s/", fs_userdir), "");
1472 stringlistsort(&list);
1474 stringlistinit(&list2);
1475 for(i = 0; i < list.numstrings; ++i)
1478 if(!strcmp(list.strings[i-1], list.strings[i]))
1480 info = FS_CheckGameDir(list.strings[i]);
1483 if(info == fs_checkgamedir_missing)
1487 stringlistappend(&list2, list.strings[i]);
1489 stringlistfreecontents(&list);
1491 fs_all_gamedirs = Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1492 for(i = 0; i < list2.numstrings; ++i)
1494 info = FS_CheckGameDir(list2.strings[i]);
1495 // all this cannot happen any more, but better be safe than sorry
1498 if(info == fs_checkgamedir_missing)
1502 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name));
1503 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description));
1504 ++fs_all_gamedirs_count;
1518 TCHAR mydocsdir[MAX_PATH + 1];
1519 #if _MSC_VER >= 1400
1526 const char* dllnames [] =
1528 "shfolder.dll", // IE 4, or Win NT and higher
1531 Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1532 // don't care for the result; if it fails, %USERPROFILE% will be used instead
1535 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1537 // Add the personal game directory
1538 if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1540 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
1542 else if(COM_CheckParm("-nohome"))
1549 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1551 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1552 Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", fs_userdir);
1556 // use the environment
1557 #if _MSC_VER >= 1400
1558 _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1560 homedir = getenv("USERPROFILE");
1565 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1566 #if _MSC_VER >= 1400
1569 Con_DPrintf("Obtained personal directory %s from environment\n", fs_userdir);
1574 Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1576 homedir = getenv ("HOME");
1578 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/.%s/", homedir, gameuserdirname);
1581 Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1585 if(!COM_CheckParm("-mygames"))
1587 #if _MSC_VER >= 1400
1589 _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
1591 int fd = open (va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1596 *fs_userdir = 0; // we have write access to the game dir, so let's use it
1602 strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1604 // If the base directory is explicitly defined by the compilation process
1605 #ifdef DP_FS_BASEDIR
1606 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1611 // FIXME: is there a better way to find the directory outside the .app?
1612 if (strstr(com_argv[0], ".app/"))
1616 split = strstr(com_argv[0], ".app/");
1617 while (split > com_argv[0] && *split != '/')
1619 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1620 fs_basedir[split - com_argv[0]] = 0;
1628 // Overrides the system supplied base directory (under GAMENAME)
1629 // 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)
1630 i = COM_CheckParm ("-basedir");
1631 if (i && i < com_argc-1)
1633 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1634 i = (int)strlen (fs_basedir);
1635 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1636 fs_basedir[i-1] = 0;
1639 // add a path separator to the end of the basedir if it lacks one
1640 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1641 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1645 p = FS_CheckGameDir(gamedirname1);
1646 if(!p || p == fs_checkgamedir_missing)
1647 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1651 p = FS_CheckGameDir(gamedirname2);
1652 if(!p || p == fs_checkgamedir_missing)
1653 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1657 // Adds basedir/gamedir as an override game
1658 // LordHavoc: now supports multiple -game directories
1659 for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1663 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1666 p = FS_CheckGameDir(com_argv[i]);
1668 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
1669 if(p == fs_checkgamedir_missing)
1670 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1671 // add the gamedir to the list of active gamedirs
1672 strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1677 // generate the searchpath
1681 void FS_Init_Commands(void)
1683 Cvar_RegisterVariable (&scr_screenshot_name);
1684 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1685 Cvar_RegisterVariable (&cvar_fs_gamedir);
1687 Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1688 Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1689 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1690 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1691 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1692 Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
1700 void FS_Shutdown (void)
1702 // close all pack files and such
1703 // (hopefully there aren't any other open files, but they'll be cleaned up
1704 // by the OS anyway)
1705 FS_ClearSearchPath();
1706 Mem_FreePool (&fs_mempool);
1709 Sys_UnloadLibrary (&shfolder_dll);
1714 ====================
1717 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1718 ====================
1720 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1726 // Parse the mode string
1735 opt = O_CREAT | O_TRUNC;
1739 opt = O_CREAT | O_APPEND;
1742 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1745 for (ind = 1; mode[ind] != '\0'; ind++)
1756 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1757 filepath, mode, mode[ind]);
1764 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1765 memset (file, 0, sizeof (*file));
1768 #if _MSC_VER >= 1400
1769 _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1771 file->handle = open (filepath, mod | opt, 0666);
1773 if (file->handle < 0)
1779 file->real_length = lseek (file->handle, 0, SEEK_END);
1781 // For files opened in append mode, we start at the end of the file
1783 file->position = file->real_length;
1785 lseek (file->handle, 0, SEEK_SET);
1795 Open a packed file using its package file descriptor
1798 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1804 pfile = &pack->files[pack_ind];
1806 // If we don't have the true offset, get it now
1807 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1808 if (!PK3_GetTrueFileOffset (pfile, pack))
1811 // No Zlib DLL = no compressed files
1812 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1814 Con_Printf("WARNING: can't open the compressed file %s\n"
1815 "You need the Zlib DLL to use compressed files\n",
1820 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1821 // the dup() call to avoid having to close the dup_handle on error here
1822 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1824 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1825 pfile->name, pack->filename, (int) pfile->offset);
1829 dup_handle = dup (pack->handle);
1832 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1836 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1837 memset (file, 0, sizeof (*file));
1838 file->handle = dup_handle;
1839 file->flags = QFILE_FLAG_PACKED;
1840 file->real_length = pfile->realsize;
1841 file->offset = pfile->offset;
1845 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1849 file->flags |= QFILE_FLAG_DEFLATED;
1851 // We need some more variables
1852 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1854 ztk->comp_length = pfile->packsize;
1856 // Initialize zlib stream
1857 ztk->zstream.next_in = ztk->input;
1858 ztk->zstream.avail_in = 0;
1860 /* From Zlib's "unzip.c":
1862 * windowBits is passed < 0 to tell that there is no zlib header.
1863 * Note that in this case inflate *requires* an extra "dummy" byte
1864 * after the compressed stream in order to complete decompression and
1865 * return Z_STREAM_END.
1866 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1867 * size of both compressed and uncompressed data
1869 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1871 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1877 ztk->zstream.next_out = file->buff;
1878 ztk->zstream.avail_out = sizeof (file->buff);
1887 ====================
1890 Return true if the path should be rejected due to one of the following:
1891 1: path elements that are non-portable
1892 2: path elements that would allow access to files outside the game directory,
1893 or are just not a good idea for a mod to be using.
1894 ====================
1896 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1898 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1902 // Windows: don't allow \ in filenames (windows-only), period.
1903 // (on Windows \ is a directory separator, but / is also supported)
1904 if (strstr(path, "\\"))
1905 return 1; // non-portable
1907 // Mac: don't allow Mac-only filenames - : is a directory separator
1908 // instead of /, but we rely on / working already, so there's no reason to
1909 // support a Mac-only path
1910 // Amiga and Windows: : tries to go to root of drive
1911 if (strstr(path, ":"))
1912 return 1; // non-portable attempt to go to root of drive
1914 // Amiga: // is parent directory
1915 if (strstr(path, "//"))
1916 return 1; // non-portable attempt to go to parent directory
1918 // all: don't allow going to parent directory (../ or /../)
1919 if (strstr(path, ".."))
1920 return 2; // attempt to go outside the game directory
1922 // Windows and UNIXes: don't allow absolute paths
1924 return 2; // attempt to go outside the game directory
1926 // 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
1927 if (strchr(path, '.'))
1931 // gamedir is entirely path elements, so simply forbid . entirely
1934 if (strchr(path, '.') < strrchr(path, '/'))
1935 return 2; // possible attempt to go outside the game directory
1938 // all: forbid trailing slash on gamedir
1939 if (isgamedir && path[strlen(path)-1] == '/')
1942 // all: forbid leading dot on any filename for any reason
1943 if (strstr(path, "/."))
1944 return 2; // attempt to go outside the game directory
1946 // after all these checks we're pretty sure it's a / separated filename
1947 // and won't do much if any harm
1953 ====================
1956 Look for a file in the packages and in the filesystem
1958 Return the searchpath where the file was found (or NULL)
1959 and the file index in the package if relevant
1960 ====================
1962 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1964 searchpath_t *search;
1967 // search through the path, one element at a time
1968 for (search = fs_searchpaths;search;search = search->next)
1970 // is the element a pak file?
1973 int (*strcmp_funct) (const char* str1, const char* str2);
1974 int left, right, middle;
1977 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1979 // Look for the file (binary search)
1981 right = pak->numfiles - 1;
1982 while (left <= right)
1986 middle = (left + right) / 2;
1987 diff = strcmp_funct (pak->files[middle].name, name);
1992 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1994 // yes, but the first one is empty so we treat it as not being there
1995 if (!quiet && developer.integer >= 10)
1996 Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
2003 if (!quiet && developer.integer >= 10)
2004 Con_Printf("FS_FindFile: %s in %s\n",
2005 pak->files[middle].name, pak->filename);
2012 // If we're too far in the list
2021 char netpath[MAX_OSPATH];
2022 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2023 if (FS_SysFileExists (netpath))
2025 if (!quiet && developer.integer >= 10)
2026 Con_Printf("FS_FindFile: %s\n", netpath);
2035 if (!quiet && developer.integer >= 10)
2036 Con_Printf("FS_FindFile: can't find %s\n", name);
2048 Look for a file in the search paths and open it in read-only mode
2051 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2053 searchpath_t *search;
2056 search = FS_FindFile (filename, &pack_ind, quiet);
2062 // Found in the filesystem?
2065 char path [MAX_OSPATH];
2066 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2067 return FS_SysOpen (path, "rb", nonblocking);
2070 // So, we found it in a package...
2072 // Is it a PK3 symlink?
2073 // TODO also handle directory symlinks by parsing the whole structure...
2074 // but heck, file symlinks are good enough for now
2075 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2077 if(symlinkLevels <= 0)
2079 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2084 char linkbuf[MAX_QPATH];
2086 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2087 const char *mergeslash;
2092 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2098 // Now combine the paths...
2099 mergeslash = strrchr(filename, '/');
2100 mergestart = linkbuf;
2102 mergeslash = filename;
2103 while(!strncmp(mergestart, "../", 3))
2106 while(mergeslash > filename)
2109 if(*mergeslash == '/')
2113 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2114 if(mergeslash == filename)
2116 // Either mergeslash == filename, then we just replace the name (done below)
2120 // Or, we append the name after mergeslash;
2121 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2122 int spaceNeeded = mergeslash - filename + 1;
2123 int spaceRemoved = mergestart - linkbuf;
2124 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2126 Con_DPrintf("symlink: too long path rejected\n");
2129 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2130 memcpy(linkbuf, filename, spaceNeeded);
2131 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2132 mergestart = linkbuf;
2134 if (!quiet && developer_loading.integer)
2135 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2136 if(FS_CheckNastyPath (mergestart, false))
2138 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2141 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2145 return FS_OpenPackedFile (search->pack, pack_ind);
2150 =============================================================================
2152 MAIN PUBLIC FUNCTIONS
2154 =============================================================================
2158 ====================
2161 Open a file in the userpath. The syntax is the same as fopen
2162 Used for savegame scanning in menu, and all file writing.
2163 ====================
2165 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2167 char real_path [MAX_OSPATH];
2169 if (FS_CheckNastyPath(filepath, false))
2171 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2175 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
2177 // If the file is opened in "write", "append", or "read/write" mode,
2178 // create directories up to the file.
2179 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2180 FS_CreatePath (real_path);
2181 return FS_SysOpen (real_path, mode, false);
2186 ====================
2189 Open a file. The syntax is the same as fopen
2190 ====================
2192 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2194 if (FS_CheckNastyPath(filepath, false))
2196 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2200 return FS_OpenReadFile (filepath, quiet, false, 16);
2205 ====================
2208 Open a file. The syntax is the same as fopen
2209 ====================
2211 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2214 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2215 memset (file, 0, sizeof (*file));
2216 file->flags = QFILE_FLAG_DATA;
2218 file->real_length = size;
2224 ====================
2228 ====================
2230 int FS_Close (qfile_t* file)
2232 if(file->flags & QFILE_FLAG_DATA)
2238 if (close (file->handle))
2243 qz_inflateEnd (&file->ztk->zstream);
2244 Mem_Free (file->ztk);
2253 ====================
2256 Write "datasize" bytes into a file
2257 ====================
2259 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2263 // If necessary, seek to the exact file position we're supposed to be
2264 if (file->buff_ind != file->buff_len)
2265 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2267 // Purge cached data
2270 // Write the buffer and update the position
2271 result = write (file->handle, data, (fs_offset_t)datasize);
2272 file->position = lseek (file->handle, 0, SEEK_CUR);
2273 if (file->real_length < file->position)
2274 file->real_length = file->position;
2284 ====================
2287 Read up to "buffersize" bytes from a file
2288 ====================
2290 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2292 fs_offset_t count, done;
2294 if (buffersize == 0)
2297 // Get rid of the ungetc character
2298 if (file->ungetc != EOF)
2300 ((char*)buffer)[0] = file->ungetc;
2308 if(file->flags & QFILE_FLAG_DATA)
2310 size_t left = file->real_length - file->position;
2311 if(buffersize > left)
2313 memcpy(buffer, file->data + file->position, buffersize);
2314 file->position += buffersize;
2318 // First, we copy as many bytes as we can from "buff"
2319 if (file->buff_ind < file->buff_len)
2321 count = file->buff_len - file->buff_ind;
2322 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2324 memcpy (buffer, &file->buff[file->buff_ind], count);
2325 file->buff_ind += count;
2327 buffersize -= count;
2328 if (buffersize == 0)
2332 // NOTE: at this point, the read buffer is always empty
2334 // If the file isn't compressed
2335 if (! (file->flags & QFILE_FLAG_DEFLATED))
2339 // We must take care to not read after the end of the file
2340 count = file->real_length - file->position;
2342 // If we have a lot of data to get, put them directly into "buffer"
2343 if (buffersize > sizeof (file->buff) / 2)
2345 if (count > (fs_offset_t)buffersize)
2346 count = (fs_offset_t)buffersize;
2347 lseek (file->handle, file->offset + file->position, SEEK_SET);
2348 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2352 file->position += nb;
2354 // Purge cached data
2360 if (count > (fs_offset_t)sizeof (file->buff))
2361 count = (fs_offset_t)sizeof (file->buff);
2362 lseek (file->handle, file->offset + file->position, SEEK_SET);
2363 nb = read (file->handle, file->buff, count);
2366 file->buff_len = nb;
2367 file->position += nb;
2369 // Copy the requested data in "buffer" (as much as we can)
2370 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2371 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2372 file->buff_ind = count;
2380 // If the file is compressed, it's more complicated...
2381 // We cycle through a few operations until we have read enough data
2382 while (buffersize > 0)
2384 ztoolkit_t *ztk = file->ztk;
2387 // NOTE: at this point, the read buffer is always empty
2389 // If "input" is also empty, we need to refill it
2390 if (ztk->in_ind == ztk->in_len)
2392 // If we are at the end of the file
2393 if (file->position == file->real_length)
2396 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2397 if (count > (fs_offset_t)sizeof (ztk->input))
2398 count = (fs_offset_t)sizeof (ztk->input);
2399 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2400 if (read (file->handle, ztk->input, count) != count)
2402 Con_Printf ("FS_Read: unexpected end of file\n");
2407 ztk->in_len = count;
2408 ztk->in_position += count;
2411 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2412 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2414 // Now that we are sure we have compressed data available, we need to determine
2415 // if it's better to inflate it in "file->buff" or directly in "buffer"
2417 // Inflate the data in "file->buff"
2418 if (buffersize < sizeof (file->buff) / 2)
2420 ztk->zstream.next_out = file->buff;
2421 ztk->zstream.avail_out = sizeof (file->buff);
2422 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2423 if (error != Z_OK && error != Z_STREAM_END)
2425 Con_Printf ("FS_Read: Can't inflate file\n");
2428 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2430 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2431 file->position += file->buff_len;
2433 // Copy the requested data in "buffer" (as much as we can)
2434 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2435 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2436 file->buff_ind = count;
2439 // Else, we inflate directly in "buffer"
2442 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2443 ztk->zstream.avail_out = (unsigned int)buffersize;
2444 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2445 if (error != Z_OK && error != Z_STREAM_END)
2447 Con_Printf ("FS_Read: Can't inflate file\n");
2450 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2452 // How much data did it inflate?
2453 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2454 file->position += count;
2456 // Purge cached data
2461 buffersize -= count;
2469 ====================
2472 Print a string into a file
2473 ====================
2475 int FS_Print (qfile_t* file, const char *msg)
2477 return (int)FS_Write (file, msg, strlen (msg));
2481 ====================
2484 Print a string into a file
2485 ====================
2487 int FS_Printf(qfile_t* file, const char* format, ...)
2492 va_start (args, format);
2493 result = FS_VPrintf (file, format, args);
2501 ====================
2504 Print a string into a file
2505 ====================
2507 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2510 fs_offset_t buff_size = MAX_INPUTLINE;
2515 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2516 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2517 if (len >= 0 && len < buff_size)
2519 Mem_Free (tempbuff);
2523 len = write (file->handle, tempbuff, len);
2524 Mem_Free (tempbuff);
2531 ====================
2534 Get the next character of a file
2535 ====================
2537 int FS_Getc (qfile_t* file)
2541 if (FS_Read (file, &c, 1) != 1)
2549 ====================
2552 Put a character back into the read buffer (only supports one character!)
2553 ====================
2555 int FS_UnGetc (qfile_t* file, unsigned char c)
2557 // If there's already a character waiting to be read
2558 if (file->ungetc != EOF)
2567 ====================
2570 Move the position index in a file
2571 ====================
2573 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2576 unsigned char* buffer;
2577 fs_offset_t buffersize;
2579 // Compute the file offset
2583 offset += file->position - file->buff_len + file->buff_ind;
2590 offset += file->real_length;
2596 if (offset < 0 || offset > file->real_length)
2599 if(file->flags & QFILE_FLAG_DATA)
2601 file->position = offset;
2605 // If we have the data in our read buffer, we don't need to actually seek
2606 if (file->position - file->buff_len <= offset && offset <= file->position)
2608 file->buff_ind = offset + file->buff_len - file->position;
2612 // Purge cached data
2615 // Unpacked or uncompressed files can seek directly
2616 if (! (file->flags & QFILE_FLAG_DEFLATED))
2618 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2620 file->position = offset;
2624 // Seeking in compressed files is more a hack than anything else,
2625 // but we need to support it, so here we go.
2628 // If we have to go back in the file, we need to restart from the beginning
2629 if (offset <= file->position)
2633 ztk->in_position = 0;
2635 lseek (file->handle, file->offset, SEEK_SET);
2637 // Reset the Zlib stream
2638 ztk->zstream.next_in = ztk->input;
2639 ztk->zstream.avail_in = 0;
2640 qz_inflateReset (&ztk->zstream);
2643 // We need a big buffer to force inflating into it directly
2644 buffersize = 2 * sizeof (file->buff);
2645 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2647 // Skip all data until we reach the requested offset
2648 while (offset > file->position)
2650 fs_offset_t diff = offset - file->position;
2651 fs_offset_t count, len;
2653 count = (diff > buffersize) ? buffersize : diff;
2654 len = FS_Read (file, buffer, count);
2668 ====================
2671 Give the current position in a file
2672 ====================
2674 fs_offset_t FS_Tell (qfile_t* file)
2676 return file->position - file->buff_len + file->buff_ind;
2681 ====================
2684 Give the total size of a file
2685 ====================
2687 fs_offset_t FS_FileSize (qfile_t* file)
2689 return file->real_length;
2694 ====================
2697 Erases any buffered input or output data
2698 ====================
2700 void FS_Purge (qfile_t* file)
2712 Filename are relative to the quake directory.
2713 Always appends a 0 byte.
2716 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2719 unsigned char *buf = NULL;
2720 fs_offset_t filesize = 0;
2722 file = FS_OpenVirtualFile(path, quiet);
2725 filesize = file->real_length;
2728 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
2733 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2734 buf[filesize] = '\0';
2735 FS_Read (file, buf, filesize);
2737 if (developer_loadfile.integer)
2738 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2741 if (filesizepointer)
2742 *filesizepointer = filesize;
2751 The filename will be prefixed by the current game directory
2754 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2758 file = FS_OpenRealFile(filename, "wb", false);
2761 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2765 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
2766 FS_Write (file, data, len);
2773 =============================================================================
2775 OTHERS PUBLIC FUNCTIONS
2777 =============================================================================
2785 void FS_StripExtension (const char *in, char *out, size_t size_out)
2793 while ((currentchar = *in) && size_out > 1)
2795 if (currentchar == '.')
2797 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2799 *out++ = currentchar;
2815 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2819 // if path doesn't have a .EXT, append extension
2820 // (extension should include the .)
2821 src = path + strlen(path) - 1;
2823 while (*src != '/' && src != path)
2826 return; // it has an extension
2830 strlcat (path, extension, size_path);
2838 Look for a file in the packages and in the filesystem
2841 int FS_FileType (const char *filename)
2843 searchpath_t *search;
2844 char fullpath[MAX_OSPATH];
2846 search = FS_FindFile (filename, NULL, true);
2848 return FS_FILETYPE_NONE;
2851 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
2853 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
2854 return FS_SysFileType(fullpath);
2862 Look for a file in the packages and in the filesystem
2865 qboolean FS_FileExists (const char *filename)
2867 return (FS_FindFile (filename, NULL, true) != NULL);
2875 Look for a file in the filesystem only
2878 int FS_SysFileType (const char *path)
2881 // Sajt - some older sdks are missing this define
2882 # ifndef INVALID_FILE_ATTRIBUTES
2883 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
2886 DWORD result = GetFileAttributes(path);
2888 if(result == INVALID_FILE_ATTRIBUTES)
2889 return FS_FILETYPE_NONE;
2891 if(result & FILE_ATTRIBUTE_DIRECTORY)
2892 return FS_FILETYPE_DIRECTORY;
2894 return FS_FILETYPE_FILE;
2898 if (stat (path,&buf) == -1)
2899 return FS_FILETYPE_NONE;
2901 if(S_ISDIR(buf.st_mode))
2902 return FS_FILETYPE_DIRECTORY;
2904 return FS_FILETYPE_FILE;
2908 qboolean FS_SysFileExists (const char *path)
2910 return FS_SysFileType (path) != FS_FILETYPE_NONE;
2913 void FS_mkdir (const char *path)
2926 Allocate and fill a search structure with information on matching filenames.
2929 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2932 searchpath_t *searchpath;
2934 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2935 stringlist_t resultlist;
2936 stringlist_t dirlist;
2937 const char *slash, *backslash, *colon, *separator;
2939 char temp[MAX_OSPATH];
2941 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2946 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2950 stringlistinit(&resultlist);
2951 stringlistinit(&dirlist);
2953 slash = strrchr(pattern, '/');
2954 backslash = strrchr(pattern, '\\');
2955 colon = strrchr(pattern, ':');
2956 separator = max(slash, backslash);
2957 separator = max(separator, colon);
2958 basepathlength = separator ? (separator + 1 - pattern) : 0;
2959 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2961 memcpy(basepath, pattern, basepathlength);
2962 basepath[basepathlength] = 0;
2964 // search through the path, one element at a time
2965 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2967 // is the element a pak file?
2968 if (searchpath->pack)
2970 // look through all the pak file elements
2971 pak = searchpath->pack;
2972 for (i = 0;i < pak->numfiles;i++)
2974 strlcpy(temp, pak->files[i].name, sizeof(temp));
2977 if (matchpattern(temp, (char *)pattern, true))
2979 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2980 if (!strcmp(resultlist.strings[resultlistindex], temp))
2982 if (resultlistindex == resultlist.numstrings)
2984 stringlistappend(&resultlist, temp);
2985 if (!quiet && developer_loading.integer)
2986 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2989 // strip off one path element at a time until empty
2990 // this way directories are added to the listing if they match the pattern
2991 slash = strrchr(temp, '/');
2992 backslash = strrchr(temp, '\\');
2993 colon = strrchr(temp, ':');
2995 if (separator < slash)
2997 if (separator < backslash)
2998 separator = backslash;
2999 if (separator < colon)
3001 *((char *)separator) = 0;
3007 stringlist_t matchedSet, foundSet;
3008 const char *start = pattern;
3010 stringlistinit(&matchedSet);
3011 stringlistinit(&foundSet);
3012 // add a first entry to the set
3013 stringlistappend(&matchedSet, "");
3014 // iterate through pattern's path
3017 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3018 char subpath[MAX_OSPATH];
3019 char subpattern[MAX_OSPATH];
3021 // find the next wildcard
3022 wildcard = strchr(start, '?');
3023 asterisk = strchr(start, '*');
3024 if (asterisk && (!wildcard || asterisk < wildcard))
3026 wildcard = asterisk;
3031 nextseparator = strchr( wildcard, '/' );
3035 nextseparator = NULL;
3038 if( !nextseparator ) {
3039 nextseparator = start + strlen( start );
3042 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3043 // copy everything up except nextseperator
3044 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3045 // find the last '/' before the wildcard
3046 prevseparator = strrchr( subpattern, '/' );
3048 prevseparator = subpattern;
3051 // copy everything from start to the previous including the '/' (before the wildcard)
3052 // everything up to start is already included in the path of matchedSet's entries
3053 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3055 // for each entry in matchedSet try to open the subdirectories specified in subpath
3056 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3057 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3058 strlcat( temp, subpath, sizeof(temp) );
3059 listdirectory( &foundSet, searchpath->filename, temp );
3061 if( dirlistindex == 0 ) {
3064 // reset the current result set
3065 stringlistfreecontents( &matchedSet );
3066 // match against the pattern
3067 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3068 const char *direntry = foundSet.strings[ dirlistindex ];
3069 if (matchpattern(direntry, subpattern, true)) {
3070 stringlistappend( &matchedSet, direntry );
3073 stringlistfreecontents( &foundSet );
3075 start = nextseparator;
3078 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3080 const char *temp = matchedSet.strings[dirlistindex];
3081 if (matchpattern(temp, (char *)pattern, true))
3083 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3084 if (!strcmp(resultlist.strings[resultlistindex], temp))
3086 if (resultlistindex == resultlist.numstrings)
3088 stringlistappend(&resultlist, temp);
3089 if (!quiet && developer_loading.integer)
3090 Con_Printf("SearchDirFile: %s\n", temp);
3094 stringlistfreecontents( &matchedSet );
3098 if (resultlist.numstrings)
3100 stringlistsort(&resultlist);
3101 numfiles = resultlist.numstrings;
3103 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3104 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3105 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3106 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3107 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3108 search->numfilenames = (int)numfiles;
3111 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3114 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3115 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3116 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3118 numchars += (int)textlen;
3121 stringlistfreecontents(&resultlist);
3127 void FS_FreeSearch(fssearch_t *search)
3132 extern int con_linewidth;
3133 int FS_ListDirectory(const char *pattern, int oneperline)
3142 char linebuf[MAX_INPUTLINE];
3144 search = FS_Search(pattern, true, true);
3147 numfiles = search->numfilenames;
3150 // FIXME: the names could be added to one column list and then
3151 // gradually shifted into the next column if they fit, and then the
3152 // next to make a compact variable width listing but it's a lot more
3154 // find width for columns
3156 for (i = 0;i < numfiles;i++)
3158 l = (int)strlen(search->filenames[i]);
3159 if (columnwidth < l)
3162 // count the spacing character
3164 // calculate number of columns
3165 numcolumns = con_linewidth / columnwidth;
3166 // don't bother with the column printing if it's only one column
3167 if (numcolumns >= 2)
3169 numlines = (numfiles + numcolumns - 1) / numcolumns;
3170 for (i = 0;i < numlines;i++)
3173 for (k = 0;k < numcolumns;k++)
3175 l = i * numcolumns + k;
3178 name = search->filenames[l];
3179 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3180 linebuf[linebufpos++] = name[j];
3181 // space out name unless it's the last on the line
3182 if (k + 1 < numcolumns && l + 1 < numfiles)
3183 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3184 linebuf[linebufpos++] = ' ';
3187 linebuf[linebufpos] = 0;
3188 Con_Printf("%s\n", linebuf);
3195 for (i = 0;i < numfiles;i++)
3196 Con_Printf("%s\n", search->filenames[i]);
3197 FS_FreeSearch(search);
3198 return (int)numfiles;
3201 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3203 const char *pattern;
3206 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3209 if (Cmd_Argc() == 2)
3210 pattern = Cmd_Argv(1);
3213 if (!FS_ListDirectory(pattern, oneperline))
3214 Con_Print("No files found.\n");
3219 FS_ListDirectoryCmd("dir", true);
3224 FS_ListDirectoryCmd("ls", false);
3227 void FS_Which_f(void)
3229 const char *filename;
3232 if (Cmd_Argc() != 2)
3234 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3237 filename = Cmd_Argv(1);
3238 sp = FS_FindFile(filename, &index, true);
3240 Con_Printf("%s isn't anywhere\n", filename);
3244 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3246 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3250 const char *FS_WhichPack(const char *filename)
3253 searchpath_t *sp = FS_FindFile(filename, &index, true);
3255 return sp->pack->shortname;
3261 ====================
3262 FS_IsRegisteredQuakePack
3264 Look for a proof of purchase file file in the requested package
3266 If it is found, this file should NOT be downloaded.
3267 ====================
3269 qboolean FS_IsRegisteredQuakePack(const char *name)
3271 searchpath_t *search;
3274 // search through the path, one element at a time
3275 for (search = fs_searchpaths;search;search = search->next)
3277 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3279 int (*strcmp_funct) (const char* str1, const char* str2);
3280 int left, right, middle;
3283 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3285 // Look for the file (binary search)
3287 right = pak->numfiles - 1;
3288 while (left <= right)
3292 middle = (left + right) / 2;
3293 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3299 // If we're too far in the list
3306 // we found the requested pack but it is not registered quake
3314 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3317 unsigned char *filedata;
3318 fs_offset_t filesize;
3319 if (filesizepointer)
3320 *filesizepointer = 0;
3321 if (!filename || !*filename)
3323 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3326 if (filesizepointer)
3327 *filesizepointer = filesize;
3328 crc = CRC_Block(filedata, filesize);
3334 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3337 unsigned char *out = NULL;
3344 memset(&strm, 0, sizeof(strm));
3345 strm.zalloc = Z_NULL;
3346 strm.zfree = Z_NULL;
3347 strm.opaque = Z_NULL;
3350 level = Z_DEFAULT_COMPRESSION;
3352 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3354 Con_Printf("FS_Deflate: deflate init error!\n");
3358 strm.next_in = (unsigned char*)data;
3359 strm.avail_in = size;
3361 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3364 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
3365 qz_deflateEnd(&strm);
3369 strm.next_out = tmp;
3370 strm.avail_out = size;
3372 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
3374 Con_Printf("FS_Deflate: deflate failed!\n");
3375 qz_deflateEnd(&strm);
3380 if(qz_deflateEnd(&strm) != Z_OK)
3382 Con_Printf("FS_Deflate: deflateEnd failed\n");
3387 if(strm.total_out >= size)
3389 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
3394 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
3397 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
3403 *deflated_size = (size_t)strm.total_out;
3405 memcpy(out, tmp, strm.total_out);
3411 static void AssertBufsize(sizebuf_t *buf, int length)
3413 if(buf->cursize + length > buf->maxsize)
3415 int oldsize = buf->maxsize;
3416 unsigned char *olddata;
3417 olddata = buf->data;
3418 buf->maxsize += length;
3419 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
3422 memcpy(buf->data, olddata, oldsize);
3428 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
3432 unsigned char *out = NULL;
3433 unsigned char tmp[2048];
3441 memset(&outbuf, 0, sizeof(outbuf));
3442 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
3443 outbuf.maxsize = sizeof(tmp);
3445 memset(&strm, 0, sizeof(strm));
3446 strm.zalloc = Z_NULL;
3447 strm.zfree = Z_NULL;
3448 strm.opaque = Z_NULL;
3450 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
3452 Con_Printf("FS_Inflate: inflate init error!\n");
3453 Mem_Free(outbuf.data);
3457 strm.next_in = (unsigned char*)data;
3458 strm.avail_in = size;
3462 strm.next_out = tmp;
3463 strm.avail_out = sizeof(tmp);
3464 ret = qz_inflate(&strm, Z_NO_FLUSH);
3465 // it either returns Z_OK on progress, Z_STREAM_END on end
3473 case Z_STREAM_ERROR:
3474 Con_Print("FS_Inflate: stream error!\n");
3477 Con_Print("FS_Inflate: data error!\n");
3480 Con_Print("FS_Inflate: mem error!\n");
3483 Con_Print("FS_Inflate: buf error!\n");
3486 Con_Print("FS_Inflate: unknown error!\n");
3490 if(ret != Z_OK && ret != Z_STREAM_END)
3492 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
3493 Mem_Free(outbuf.data);
3494 qz_inflateEnd(&strm);
3497 have = sizeof(tmp) - strm.avail_out;
3498 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
3499 SZ_Write(&outbuf, tmp, have);
3500 } while(ret != Z_STREAM_END);
3502 qz_inflateEnd(&strm);
3504 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
3507 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
3508 Mem_Free(outbuf.data);
3512 memcpy(out, outbuf.data, outbuf.cursize);
3513 Mem_Free(outbuf.data);
3516 *inflated_size = (size_t)outbuf.cursize;