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
32 # include <sys/stat.h>
36 # include <sys/stat.h>
43 // include SDL for IPHONEOS code
56 // Win32 requires us to add O_BINARY, but the other OSes don't have it
61 // In case the system doesn't support the O_NONBLOCK flag
66 // largefile support for Win32
69 # define lseek _lseeki64
72 // suppress deprecated warnings
77 # define unlink _unlink
83 typedef SDL_RWops *filedesc_t;
84 # define FILEDESC_INVALID NULL
85 # define FILEDESC_ISVALID(fd) ((fd) != NULL)
86 # define FILEDESC_READ(fd,buf,count) ((fs_offset_t)SDL_RWread(fd, buf, 1, count))
87 # define FILEDESC_WRITE(fd,buf,count) ((fs_offset_t)SDL_RWwrite(fd, buf, 1, count))
88 # define FILEDESC_CLOSE SDL_RWclose
89 # define FILEDESC_SEEK SDL_RWseek
90 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
91 filedesc_t new_fd = SDL_RWFromFile(filename, "rb");
92 if (SDL_RWseek(new_fd, SDL_RWseek(fd, 0, RW_SEEK_CUR), RW_SEEK_SET) < 0) {
98 # define unlink(name) Con_DPrintf("Sorry, no unlink support when trying to unlink %s.\n", (name))
100 typedef int filedesc_t;
101 # define FILEDESC_INVALID -1
102 # define FILEDESC_ISVALID(fd) ((fd) != -1)
103 # define FILEDESC_READ read
104 # define FILEDESC_WRITE write
105 # define FILEDESC_CLOSE close
106 # define FILEDESC_SEEK lseek
107 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
112 /** \page fs File System
114 All of Quake's data access is through a hierchal file system, but the contents
115 of the file system can be transparently merged from several sources.
117 The "base directory" is the path to the directory holding the quake.exe and
118 all game directories. The sys_* files pass this to host_init in
119 quakeparms_t->basedir. This can be overridden with the "-basedir" command
120 line parm to allow code debugging in a different directory. The base
121 directory is only used during filesystem initialization.
123 The "game directory" is the first tree on the search path and directory that
124 all generated files (savegames, screenshots, demos, config files) will be
125 saved to. This can be overridden with the "-game" command line parameter.
126 The game directory can never be changed while quake is executing. This is a
127 precaution against having a malicious server instruct clients to write files
128 over areas they shouldn't.
134 =============================================================================
138 =============================================================================
141 // Magic numbers of a ZIP file (big-endian format)
142 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
143 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
144 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
146 // Other constants for ZIP files
147 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
148 #define ZIP_END_CDIR_SIZE 22
149 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
150 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
155 #define qz_inflate inflate
156 #define qz_inflateEnd inflateEnd
157 #define qz_inflateInit2_ inflateInit2_
158 #define qz_inflateReset inflateReset
159 #define qz_deflateInit2_ deflateInit2_
160 #define qz_deflateEnd deflateEnd
161 #define qz_deflate deflate
162 #define Z_MEMLEVEL_DEFAULT 8
165 // Zlib constants (from zlib.h)
166 #define Z_SYNC_FLUSH 2
169 #define Z_STREAM_END 1
170 #define Z_STREAM_ERROR (-2)
171 #define Z_DATA_ERROR (-3)
172 #define Z_MEM_ERROR (-4)
173 #define Z_BUF_ERROR (-5)
174 #define ZLIB_VERSION "1.2.3"
178 #define Z_MEMLEVEL_DEFAULT 8
181 #define Z_DEFAULT_COMPRESSION (-1)
183 #define Z_SYNC_FLUSH 2
184 #define Z_FULL_FLUSH 3
187 // Uncomment the following line if the zlib DLL you have still uses
188 // the 1.1.x series calling convention on Win32 (WINAPI)
189 //#define ZLIB_USES_WINAPI
193 =============================================================================
197 =============================================================================
200 /*! Zlib stream (from zlib.h)
201 * \warning: some pointers we don't use directly have
202 * been cast to "void*" for a matter of simplicity
206 unsigned char *next_in; ///< next input byte
207 unsigned int avail_in; ///< number of bytes available at next_in
208 unsigned long total_in; ///< total nb of input bytes read so far
210 unsigned char *next_out; ///< next output byte should be put there
211 unsigned int avail_out; ///< remaining free space at next_out
212 unsigned long total_out; ///< total nb of bytes output so far
214 char *msg; ///< last error message, NULL if no error
215 void *state; ///< not visible by applications
217 void *zalloc; ///< used to allocate the internal state
218 void *zfree; ///< used to free the internal state
219 void *opaque; ///< private data object passed to zalloc and zfree
221 int data_type; ///< best guess about the data type: ascii or binary
222 unsigned long adler; ///< adler32 value of the uncompressed data
223 unsigned long reserved; ///< reserved for future use
228 /// inside a package (PAK or PK3)
229 #define QFILE_FLAG_PACKED (1 << 0)
230 /// file is compressed using the deflate algorithm (PK3 only)
231 #define QFILE_FLAG_DEFLATED (1 << 1)
232 /// file is actually already loaded data
233 #define QFILE_FLAG_DATA (1 << 2)
234 /// real file will be removed on close
235 #define QFILE_FLAG_REMOVE (1 << 3)
237 #define FILE_BUFF_SIZE 2048
241 size_t comp_length; ///< length of the compressed file
242 size_t in_ind, in_len; ///< input buffer current index and length
243 size_t in_position; ///< position in the compressed file
244 unsigned char input [FILE_BUFF_SIZE];
250 filedesc_t handle; ///< file descriptor
251 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
252 fs_offset_t position; ///< current position in the file
253 fs_offset_t offset; ///< offset into the package (0 if external file)
254 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
257 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
258 unsigned char buff [FILE_BUFF_SIZE];
260 ztoolkit_t* ztk; ///< For zipped files.
262 const unsigned char *data; ///< For data files.
264 const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
268 // ------ PK3 files on disk ------ //
270 // You can get the complete ZIP format description from PKWARE website
272 typedef struct pk3_endOfCentralDir_s
274 unsigned int signature;
275 unsigned short disknum;
276 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
277 unsigned short localentries; ///< number of entries in the central directory on this disk
278 unsigned short nbentries; ///< total number of entries in the central directory on this disk
279 unsigned int cdir_size; ///< size of the central directory
280 unsigned int cdir_offset; ///< with respect to the starting disk number
281 unsigned short comment_size;
282 fs_offset_t prepended_garbage;
283 } pk3_endOfCentralDir_t;
286 // ------ PAK files on disk ------ //
287 typedef struct dpackfile_s
290 int filepos, filelen;
293 typedef struct dpackheader_s
301 /*! \name Packages in memory
304 /// the offset in packfile_t is the true contents offset
305 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
306 /// file compressed using the deflate algorithm
307 #define PACKFILE_FLAG_DEFLATED (1 << 1)
308 /// file is a symbolic link
309 #define PACKFILE_FLAG_SYMLINK (1 << 2)
311 typedef struct packfile_s
313 char name [MAX_QPATH];
316 fs_offset_t packsize; ///< size in the package
317 fs_offset_t realsize; ///< real file size (uncompressed)
320 typedef struct pack_s
322 char filename [MAX_OSPATH];
323 char shortname [MAX_QPATH];
325 int ignorecase; ///< PK3 ignores case
333 /// Search paths for files (including packages)
334 typedef struct searchpath_s
336 // only one of filename / pack will be used
337 char filename[MAX_OSPATH];
339 struct searchpath_s *next;
344 =============================================================================
348 =============================================================================
351 void FS_Dir_f(cmd_state_t *cmd);
352 void FS_Ls_f(cmd_state_t *cmd);
353 void FS_Which_f(cmd_state_t *cmd);
355 static searchpath_t *FS_FindFile (const char *name, int* index, qbool quiet);
356 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
357 fs_offset_t offset, fs_offset_t packsize,
358 fs_offset_t realsize, int flags);
362 =============================================================================
366 =============================================================================
369 mempool_t *fs_mempool;
370 void *fs_mutex = NULL;
372 searchpath_t *fs_searchpaths = NULL;
373 const char *const fs_checkgamedir_missing = "missing";
375 #define MAX_FILES_IN_PACK 65536
377 char fs_userdir[MAX_OSPATH];
378 char fs_gamedir[MAX_OSPATH];
379 char fs_basedir[MAX_OSPATH];
380 static pack_t *fs_selfpack = NULL;
382 // list of active game directories (empty if not running a mod)
383 int fs_numgamedirs = 0;
384 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
386 // list of all gamedirs with modinfo.txt
387 gamedir_t *fs_all_gamedirs = NULL;
388 int fs_all_gamedirs_count = 0;
390 cvar_t scr_screenshot_name = {CF_CLIENT | CF_PERSISTENT, "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)"};
391 cvar_t fs_empty_files_in_pack_mark_deletions = {CF_CLIENT | CF_SERVER, "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"};
392 cvar_t cvar_fs_gamedir = {CF_CLIENT | CF_SERVER | CF_READONLY | CF_PERSISTENT, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
396 =============================================================================
398 PRIVATE FUNCTIONS - PK3 HANDLING
400 =============================================================================
404 // Functions exported from zlib
405 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
406 # define ZEXPORT WINAPI
411 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
412 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
413 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
414 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
415 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
416 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
417 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
420 #define qz_inflateInit2(strm, windowBits) \
421 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
422 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
423 qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
426 // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
428 static dllfunction_t zlibfuncs[] =
430 {"inflate", (void **) &qz_inflate},
431 {"inflateEnd", (void **) &qz_inflateEnd},
432 {"inflateInit2_", (void **) &qz_inflateInit2_},
433 {"inflateReset", (void **) &qz_inflateReset},
434 {"deflateInit2_", (void **) &qz_deflateInit2_},
435 {"deflateEnd", (void **) &qz_deflateEnd},
436 {"deflate", (void **) &qz_deflate},
440 /// Handle for Zlib DLL
441 static dllhandle_t zlib_dll = NULL;
445 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath);
446 static dllfunction_t shfolderfuncs[] =
448 {"SHGetFolderPathW", (void **) &qSHGetFolderPath},
451 static const char* shfolderdllnames [] =
453 "shfolder.dll", // IE 4, or Win NT and higher
456 static dllhandle_t shfolder_dll = NULL;
458 const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}};
459 #define qREFKNOWNFOLDERID const GUID *
460 #define qKF_FLAG_CREATE 0x8000
461 #define qKF_FLAG_NO_ALIAS 0x1000
462 static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
463 static dllfunction_t shell32funcs[] =
465 {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath},
468 static const char* shell32dllnames [] =
470 "shell32.dll", // Vista and higher
473 static dllhandle_t shell32_dll = NULL;
475 static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit);
476 static void (WINAPI *qCoUninitialize)(void);
477 static void (WINAPI *qCoTaskMemFree)(LPVOID pv);
478 static dllfunction_t ole32funcs[] =
480 {"CoInitializeEx", (void **) &qCoInitializeEx},
481 {"CoUninitialize", (void **) &qCoUninitialize},
482 {"CoTaskMemFree", (void **) &qCoTaskMemFree},
485 static const char* ole32dllnames [] =
487 "ole32.dll", // 2000 and higher
490 static dllhandle_t ole32_dll = NULL;
500 static void PK3_CloseLibrary (void)
503 Sys_FreeLibrary (&zlib_dll);
512 Try to load the Zlib DLL
515 static qbool PK3_OpenLibrary (void)
520 const char* dllnames [] =
523 # ifdef ZLIB_USES_WINAPI
529 #elif defined(MACOSX)
543 return Sys_LoadDependency (dllnames, &zlib_dll, zlibfuncs);
551 See if zlib is available
554 qbool FS_HasZlib(void)
559 PK3_OpenLibrary(); // to be safe
560 return (zlib_dll != 0);
566 PK3_GetEndOfCentralDir
568 Extract the end of the central directory from a PK3 package
571 static qbool PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd)
573 fs_offset_t filesize, maxsize;
574 unsigned char *buffer, *ptr;
577 // Get the package size
578 filesize = FILEDESC_SEEK (packhandle, 0, SEEK_END);
579 if (filesize < ZIP_END_CDIR_SIZE)
582 // Load the end of the file in memory
583 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
586 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
587 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
588 FILEDESC_SEEK (packhandle, filesize - maxsize, SEEK_SET);
589 if (FILEDESC_READ (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
595 // Look for the end of central dir signature around the end of the file
596 maxsize -= ZIP_END_CDIR_SIZE;
597 ptr = &buffer[maxsize];
599 while (BuffBigLong (ptr) != ZIP_END_HEADER)
611 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
612 eocd->signature = LittleLong (eocd->signature);
613 eocd->disknum = LittleShort (eocd->disknum);
614 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
615 eocd->localentries = LittleShort (eocd->localentries);
616 eocd->nbentries = LittleShort (eocd->nbentries);
617 eocd->cdir_size = LittleLong (eocd->cdir_size);
618 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
619 eocd->comment_size = LittleShort (eocd->comment_size);
620 eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
621 eocd->cdir_offset += eocd->prepended_garbage;
626 eocd->cdir_size > filesize ||
627 eocd->cdir_offset >= filesize ||
628 eocd->cdir_offset + eocd->cdir_size > filesize
631 // Obviously invalid central directory.
643 Extract the file list from a PK3 file
646 static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
648 unsigned char *central_dir, *ptr;
650 fs_offset_t remaining;
652 // Load the central directory in memory
653 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
654 if (FILEDESC_SEEK (pack->handle, eocd->cdir_offset, SEEK_SET) == -1)
656 Mem_Free (central_dir);
659 if(FILEDESC_READ (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
661 Mem_Free (central_dir);
665 // Extract the files properties
666 // The parsing is done "by hand" because some fields have variable sizes and
667 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
668 remaining = eocd->cdir_size;
671 for (ind = 0; ind < eocd->nbentries; ind++)
673 fs_offset_t namesize, count;
675 // Checking the remaining size
676 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
678 Mem_Free (central_dir);
681 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
684 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
686 Mem_Free (central_dir);
690 namesize = (unsigned short)BuffLittleShort (&ptr[28]); // filename length
692 // Check encryption, compression, and attributes
693 // 1st uint8 : general purpose bit flag
694 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
696 // LadyHavoc: bit 3 would be a problem if we were scanning the archive
697 // but is not a problem in the central directory where the values are
700 // bit 3 seems to always be set by the standard Mac OSX zip maker
702 // 2nd uint8 : external file attributes
703 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
704 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
706 // Still enough bytes for the name?
707 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
709 Mem_Free (central_dir);
713 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
714 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
716 char filename [sizeof (pack->files[0].name)];
717 fs_offset_t offset, packsize, realsize;
720 // Extract the name (strip it if necessary)
721 namesize = min(namesize, (int)sizeof (filename) - 1);
722 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
723 filename[namesize] = '\0';
725 if (BuffLittleShort (&ptr[10]))
726 flags = PACKFILE_FLAG_DEFLATED;
729 offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
730 packsize = (unsigned int)BuffLittleLong (&ptr[20]);
731 realsize = (unsigned int)BuffLittleLong (&ptr[24]);
733 switch(ptr[5]) // C_VERSION_MADE_BY_1
738 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
739 // can't use S_ISLNK here, as this has to compile on non-UNIX too
740 flags |= PACKFILE_FLAG_SYMLINK;
744 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
748 // Skip the name, additionnal field, and comment
749 // 1er uint16 : extra field length
750 // 2eme uint16 : file comment length
751 count = namesize + (unsigned short)BuffLittleShort (&ptr[30]) + (unsigned short)BuffLittleShort (&ptr[32]);
752 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
756 // If the package is empty, central_dir is NULL here
757 if (central_dir != NULL)
758 Mem_Free (central_dir);
759 return pack->numfiles;
767 Create a package entry associated with a PK3 file
770 static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qbool silent)
772 pk3_endOfCentralDir_t eocd;
776 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
779 Con_Printf ("%s is not a PK3 file\n", packfile);
780 FILEDESC_CLOSE(packhandle);
784 // Multi-volume ZIP archives are NOT allowed
785 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
787 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
788 FILEDESC_CLOSE(packhandle);
792 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
793 // since eocd.nbentries is an unsigned 16 bits integer
794 #if MAX_FILES_IN_PACK < 65535
795 if (eocd.nbentries > MAX_FILES_IN_PACK)
797 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
798 FILEDESC_CLOSE(packhandle);
803 // Create a package structure in memory
804 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
805 pack->ignorecase = true; // PK3 ignores case
806 strlcpy (pack->filename, packfile, sizeof (pack->filename));
807 pack->handle = packhandle;
808 pack->numfiles = eocd.nbentries;
809 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
811 real_nb_files = PK3_BuildFileList (pack, &eocd);
812 if (real_nb_files < 0)
814 Con_Printf ("%s is not a valid PK3 file\n", packfile);
815 FILEDESC_CLOSE(pack->handle);
820 Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
824 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking);
825 static pack_t *FS_LoadPackPK3 (const char *packfile)
827 filedesc_t packhandle;
828 packhandle = FS_SysOpenFiledesc (packfile, "rb", false);
829 if (!FILEDESC_ISVALID(packhandle))
831 return FS_LoadPackPK3FromFD(packfile, packhandle, false);
837 PK3_GetTrueFileOffset
839 Find where the true file data offset is
842 static qbool PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
844 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
848 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
851 // Load the local file description
852 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
854 Con_Printf ("Can't seek in package %s\n", pack->filename);
857 count = FILEDESC_READ (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
858 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
860 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
864 // Skip name and extra field
865 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
867 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
873 =============================================================================
875 OTHER PRIVATE FUNCTIONS
877 =============================================================================
885 Add a file to the list of files contained into a package
888 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
889 fs_offset_t offset, fs_offset_t packsize,
890 fs_offset_t realsize, int flags)
892 int (*strcmp_funct) (const char* str1, const char* str2);
893 int left, right, middle;
896 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
898 // Look for the slot we should put that file into (binary search)
900 right = pack->numfiles - 1;
901 while (left <= right)
905 middle = (left + right) / 2;
906 diff = strcmp_funct (pack->files[middle].name, name);
908 // If we found the file, there's a problem
910 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
912 // If we're too far in the list
919 // We have to move the right of the list by one slot to free the one we need
920 pfile = &pack->files[left];
921 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
924 strlcpy (pfile->name, name, sizeof (pfile->name));
925 pfile->offset = offset;
926 pfile->packsize = packsize;
927 pfile->realsize = realsize;
928 pfile->flags = flags;
935 static inline int wstrlen(wchar *wstr)
938 while (wstr[len] != 0 && len < WSTRBUF)
942 #define widen(str, wstr) fromwtf8(str, strlen(str), wstr, WSTRBUF)
943 #define narrow(wstr, str) towtf8(wstr, wstrlen(wstr), str, WSTRBUF)
946 static void FS_mkdir (const char *path)
949 wchar pathw[WSTRBUF] = {0};
951 if(Sys_CheckParm("-readonly"))
956 if (_wmkdir (pathw) == -1)
958 if (mkdir (path, 0777) == -1)
961 // No logging for this. The only caller is FS_CreatePath (which
962 // calls it in ways that will intentionally produce EEXIST),
963 // and its own callers always use the directory afterwards and
964 // thus will detect failure that way.
972 Only used for FS_OpenRealFile.
975 void FS_CreatePath (char *path)
979 for (ofs = path+1 ; *ofs ; ofs++)
981 if (*ofs == '/' || *ofs == '\\')
983 // create the directory
999 static void FS_Path_f(cmd_state_t *cmd)
1003 Con_Print("Current search path:\n");
1004 for (s=fs_searchpaths ; s ; s=s->next)
1009 Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
1011 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
1014 Con_Printf("%s\n", s->filename);
1024 /*! Takes an explicit (not game tree related) path to a pak file.
1025 *Loads the header and directory, adding the files at the beginning
1026 *of the list so they override previous pack files.
1028 static pack_t *FS_LoadPackPAK (const char *packfile)
1030 dpackheader_t header;
1031 int i, numpackfiles;
1032 filedesc_t packhandle;
1036 packhandle = FS_SysOpenFiledesc(packfile, "rb", false);
1037 if (!FILEDESC_ISVALID(packhandle))
1039 if(FILEDESC_READ (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
1041 Con_Printf ("%s is not a packfile\n", packfile);
1042 FILEDESC_CLOSE(packhandle);
1045 if (memcmp(header.id, "PACK", 4))
1047 Con_Printf ("%s is not a packfile\n", packfile);
1048 FILEDESC_CLOSE(packhandle);
1051 header.dirofs = LittleLong (header.dirofs);
1052 header.dirlen = LittleLong (header.dirlen);
1054 if (header.dirlen % sizeof(dpackfile_t))
1056 Con_Printf ("%s has an invalid directory size\n", packfile);
1057 FILEDESC_CLOSE(packhandle);
1061 numpackfiles = header.dirlen / sizeof(dpackfile_t);
1063 if (numpackfiles < 0 || numpackfiles > MAX_FILES_IN_PACK)
1065 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
1066 FILEDESC_CLOSE(packhandle);
1070 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
1071 FILEDESC_SEEK (packhandle, header.dirofs, SEEK_SET);
1072 if(header.dirlen != FILEDESC_READ (packhandle, (void *)info, header.dirlen))
1074 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
1076 FILEDESC_CLOSE(packhandle);
1080 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1081 pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2
1082 strlcpy (pack->filename, packfile, sizeof (pack->filename));
1083 pack->handle = packhandle;
1085 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
1087 // parse the directory
1088 for (i = 0;i < numpackfiles;i++)
1090 fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
1091 fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
1093 // Ensure a zero terminated file name (required by format).
1094 info[i].name[sizeof(info[i].name) - 1] = 0;
1096 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
1101 Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
1106 ====================
1109 Create a package entry associated with a directory file
1110 ====================
1112 static pack_t *FS_LoadPackVirtual (const char *dirname)
1115 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1117 pack->ignorecase = false;
1118 strlcpy (pack->filename, dirname, sizeof(pack->filename));
1119 pack->handle = FILEDESC_INVALID;
1120 pack->numfiles = -1;
1122 Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
1131 /*! Adds the given pack to the search path.
1132 * The pack type is autodetected by the file extension.
1134 * Returns true if the file was successfully added to the
1135 * search path or if it was already included.
1137 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1138 * plain directories.
1141 static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
1143 searchpath_t *search;
1145 const char *ext = FS_FileExtension(pakfile);
1148 for(search = fs_searchpaths; search; search = search->next)
1150 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1153 *already_loaded = true;
1154 return true; // already loaded
1159 *already_loaded = false;
1161 if(!strcasecmp(ext, "pk3dir") || !strcasecmp(ext, "dpkdir"))
1162 pak = FS_LoadPackVirtual (pakfile);
1163 else if(!strcasecmp(ext, "pak"))
1164 pak = FS_LoadPackPAK (pakfile);
1165 else if(!strcasecmp(ext, "pk3") || !strcasecmp(ext, "dpk"))
1166 pak = FS_LoadPackPK3 (pakfile);
1167 else if(!strcasecmp(ext, "obb")) // android apk expansion
1168 pak = FS_LoadPackPK3 (pakfile);
1170 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1174 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1176 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
1179 // find the first item whose next one is a pack or NULL
1180 searchpath_t *insertion_point = 0;
1181 if(fs_searchpaths && !fs_searchpaths->pack)
1183 insertion_point = fs_searchpaths;
1186 if(!insertion_point->next)
1188 if(insertion_point->next->pack)
1190 insertion_point = insertion_point->next;
1193 // If insertion_point is NULL, this means that either there is no
1194 // item in the list yet, or that the very first item is a pack. In
1195 // that case, we want to insert at the beginning...
1196 if(!insertion_point)
1198 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1199 search->next = fs_searchpaths;
1200 fs_searchpaths = search;
1203 // otherwise we want to append directly after insertion_point.
1205 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1206 search->next = insertion_point->next;
1207 insertion_point->next = search;
1212 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1213 search->next = fs_searchpaths;
1214 fs_searchpaths = search;
1217 search->pack->dlcache = dlcache;
1220 dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1221 // if shortname ends with "pk3dir" or "dpkdir", strip that suffix to make it just "pk3" or "dpk"
1222 // same goes for the name inside the pack structure
1223 l = strlen(pak->shortname);
1225 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir") || !strcasecmp(pak->shortname + l - 7, ".dpkdir"))
1226 pak->shortname[l - 3] = 0;
1227 l = strlen(pak->filename);
1229 if(!strcasecmp(pak->filename + l - 7, ".pk3dir") || !strcasecmp(pak->filename + l - 7, ".dpkdir"))
1230 pak->filename[l - 3] = 0;
1236 Con_Printf(CON_ERROR "unable to load pak \"%s\"\n", pakfile);
1247 /*! Adds the given pack to the search path and searches for it in the game path.
1248 * The pack type is autodetected by the file extension.
1250 * Returns true if the file was successfully added to the
1251 * search path or if it was already included.
1253 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1254 * plain directories.
1256 qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
1258 char fullpath[MAX_OSPATH];
1260 searchpath_t *search;
1263 *already_loaded = false;
1265 // then find the real name...
1266 search = FS_FindFile(pakfile, &index, true);
1267 if(!search || search->pack)
1269 Con_Printf("could not find pak \"%s\"\n", pakfile);
1273 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1275 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs, dlcache);
1283 Sets fs_gamedir, adds the directory to the head of the path,
1284 then loads and adds pak1.pak pak2.pak ...
1287 static void FS_AddGameDirectory (const char *dir)
1291 searchpath_t *search;
1293 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1295 stringlistinit(&list);
1296 listdirectory(&list, "", dir);
1297 stringlistsort(&list, false);
1299 // add any PAK package in the directory
1300 for (i = 0;i < list.numstrings;i++)
1302 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1304 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
1308 // add any PK3 package in the directory
1309 for (i = 0;i < list.numstrings;i++)
1311 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")
1312 || !strcasecmp(FS_FileExtension(list.strings[i]), "dpk") || !strcasecmp(FS_FileExtension(list.strings[i]), "dpkdir"))
1314 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
1318 stringlistfreecontents(&list);
1320 // Add the directory to the search path
1321 // (unpacked files have the priority over packed files)
1322 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1323 strlcpy (search->filename, dir, sizeof (search->filename));
1324 search->next = fs_searchpaths;
1325 fs_searchpaths = search;
1334 static void FS_AddGameHierarchy (const char *dir)
1337 // Add the common game directory
1338 FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
1341 FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
1350 const char *FS_FileExtension (const char *in)
1352 const char *separator, *backslash, *colon, *dot;
1354 dot = strrchr(in, '.');
1358 separator = strrchr(in, '/');
1359 backslash = strrchr(in, '\\');
1360 if (!separator || separator < backslash)
1361 separator = backslash;
1362 colon = strrchr(in, ':');
1363 if (!separator || separator < colon)
1366 if (separator && (dot < separator))
1378 const char *FS_FileWithoutPath (const char *in)
1380 const char *separator, *backslash, *colon;
1382 separator = strrchr(in, '/');
1383 backslash = strrchr(in, '\\');
1384 if (!separator || separator < backslash)
1385 separator = backslash;
1386 colon = strrchr(in, ':');
1387 if (!separator || separator < colon)
1389 return separator ? separator + 1 : in;
1398 static void FS_ClearSearchPath (void)
1400 // unload all packs and directory information, close all pack files
1401 // (if a qfile is still reading a pack it won't be harmed because it used
1402 // dup() to get its own handle already)
1403 while (fs_searchpaths)
1405 searchpath_t *search = fs_searchpaths;
1406 fs_searchpaths = search->next;
1407 if (search->pack && search->pack != fs_selfpack)
1409 if(!search->pack->vpack)
1412 FILEDESC_CLOSE(search->pack->handle);
1413 // free any memory associated with it
1414 if (search->pack->files)
1415 Mem_Free(search->pack->files);
1417 Mem_Free(search->pack);
1425 FS_UnloadPacks_dlcache
1427 Like FS_ClearSearchPath() but unloads only the packs loaded from dlcache
1428 so we don't need to use a full FS_Rescan() to prevent
1429 content from the previous server and/or map from interfering with the next
1432 void FS_UnloadPacks_dlcache(void)
1434 searchpath_t *search = fs_searchpaths, *searchprev = fs_searchpaths, *searchnext;
1438 searchnext = search->next;
1439 if (search->pack && search->pack->dlcache)
1441 Con_DPrintf("Unloading pack: %s\n", search->pack->shortname);
1443 // remove it from the search path list
1444 if (search == fs_searchpaths)
1445 fs_searchpaths = search->next;
1447 searchprev->next = search->next;
1450 FILEDESC_CLOSE(search->pack->handle);
1451 // free any memory associated with it
1452 if (search->pack->files)
1453 Mem_Free(search->pack->files);
1454 Mem_Free(search->pack);
1458 searchprev = search;
1459 search = searchnext;
1463 static void FS_AddSelfPack(void)
1467 searchpath_t *search;
1468 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1469 search->next = fs_searchpaths;
1470 search->pack = fs_selfpack;
1471 fs_searchpaths = search;
1481 void FS_Rescan (void)
1484 qbool fs_modified = false;
1485 qbool reset = false;
1486 char gamedirbuf[MAX_INPUTLINE];
1491 FS_ClearSearchPath();
1493 // automatically activate gamemode for the gamedirs specified
1495 COM_ChangeGameTypeForGameDirs();
1497 // add the game-specific paths
1498 // gamedirname1 (typically id1)
1499 FS_AddGameHierarchy (gamedirname1);
1500 // update the com_modname (used for server info)
1501 if (gamedirname2 && gamedirname2[0])
1502 strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1504 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1506 // add the game-specific path, if any
1507 // (only used for mission packs and the like, which should set fs_modified)
1508 if (gamedirname2 && gamedirname2[0])
1511 FS_AddGameHierarchy (gamedirname2);
1515 // Adds basedir/gamedir as an override game
1516 // LadyHavoc: now supports multiple -game directories
1517 // set the com_modname (reported in server info)
1519 for (i = 0;i < fs_numgamedirs;i++)
1522 FS_AddGameHierarchy (fs_gamedirs[i]);
1523 // update the com_modname (used server info)
1524 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1526 strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1528 strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1530 Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1532 // add back the selfpack as new first item
1535 // set the default screenshot name to either the mod name or the
1536 // gamemode screenshot name
1537 if (strcmp(com_modname, gamedirname1))
1538 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1540 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1542 if((i = Sys_CheckParm("-modname")) && i < sys.argc - 1)
1543 strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
1545 // If "-condebug" is in the command line, remove the previous log file
1546 if (Sys_CheckParm ("-condebug") != 0)
1547 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1549 // look for the pop.lmp file and set registered to true if it is found
1550 if (FS_FileExists("gfx/pop.lmp"))
1551 Cvar_SetValueQuick(®istered, 1);
1557 if (!registered.integer)
1560 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1562 Con_Print("Playing shareware version.\n");
1565 Con_Print("Playing registered version.\n");
1567 case GAME_STEELSTORM:
1568 if (registered.integer)
1569 Con_Print("Playing registered version.\n");
1571 Con_Print("Playing shareware version.\n");
1577 // unload all wads so that future queries will return the new data
1581 static void FS_Rescan_f(cmd_state_t *cmd)
1591 extern qbool vid_opened;
1592 qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool complain, qbool failmissing)
1597 if (fs_numgamedirs == numgamedirs)
1599 for (i = 0;i < numgamedirs;i++)
1600 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1602 if (i == numgamedirs)
1603 return true; // already using this set of gamedirs, do nothing
1606 if (numgamedirs > MAX_GAMEDIRS)
1609 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1610 return false; // too many gamedirs
1613 for (i = 0;i < numgamedirs;i++)
1615 // if string is nasty, reject it
1616 p = FS_CheckGameDir(gamedirs[i]);
1620 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1621 return false; // nasty gamedirs
1623 if(p == fs_checkgamedir_missing && failmissing)
1626 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1627 return false; // missing gamedirs
1631 Host_SaveConfig(CONFIGFILENAME);
1633 fs_numgamedirs = numgamedirs;
1634 for (i = 0;i < fs_numgamedirs;i++)
1635 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1637 // reinitialize filesystem to detect the new paks
1640 if (cls.demoplayback)
1646 // unload all sounds so they will be reloaded from the new files as needed
1647 S_UnloadAllSounds_f(cmd_local);
1649 // restart the video subsystem after the config is executed
1650 Cbuf_InsertText(cmd_local, "\nloadconfig\nvid_restart\n\n");
1660 static void FS_GameDir_f(cmd_state_t *cmd)
1664 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1666 if (Cmd_Argc(cmd) < 2)
1668 Con_Printf("gamedirs active:");
1669 for (i = 0;i < fs_numgamedirs;i++)
1670 Con_Printf(" %s", fs_gamedirs[i]);
1675 numgamedirs = Cmd_Argc(cmd) - 1;
1676 if (numgamedirs > MAX_GAMEDIRS)
1678 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1682 for (i = 0;i < numgamedirs;i++)
1683 strlcpy(gamedirs[i], Cmd_Argv(cmd, i+1), sizeof(gamedirs[i]));
1685 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1687 // actually, changing during game would work fine, but would be stupid
1688 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1692 // halt demo playback to close the file
1695 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1698 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1706 stringlistinit(&list);
1707 listdirectory(&list, gamedir, "");
1708 success = list.numstrings > 0;
1709 stringlistfreecontents(&list);
1713 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1716 n = FS_Read (f, buf, buflength - 1);
1736 const char *FS_CheckGameDir(const char *gamedir)
1739 static char buf[8192];
1742 if (FS_CheckNastyPath(gamedir, true))
1745 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1750 // get description from basedir
1751 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1759 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1763 return fs_checkgamedir_missing;
1766 static void FS_ListGameDirs(void)
1768 stringlist_t list, list2;
1773 fs_all_gamedirs_count = 0;
1775 Mem_Free(fs_all_gamedirs);
1777 stringlistinit(&list);
1778 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1779 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1780 stringlistsort(&list, false);
1782 stringlistinit(&list2);
1783 for(i = 0; i < list.numstrings; ++i)
1786 if(!strcmp(list.strings[i-1], list.strings[i]))
1788 info = FS_CheckGameDir(list.strings[i]);
1791 if(info == fs_checkgamedir_missing)
1795 stringlistappend(&list2, list.strings[i]);
1797 stringlistfreecontents(&list);
1799 fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1800 for(i = 0; i < list2.numstrings; ++i)
1802 info = FS_CheckGameDir(list2.strings[i]);
1803 // all this cannot happen any more, but better be safe than sorry
1806 if(info == fs_checkgamedir_missing)
1810 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1811 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1812 ++fs_all_gamedirs_count;
1818 #pragma comment(lib, "shell32.lib")
1823 static void COM_InsertFlags(const char *buf) {
1826 const char **new_argv;
1828 int args_left = 256;
1829 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*sys.argv) * (sys.argc + args_left + 2));
1831 new_argv[0] = "dummy"; // Can't really happen.
1833 new_argv[0] = sys.argv[0];
1836 while(COM_ParseToken_Console(&p))
1838 size_t sz = strlen(com_token) + 1; // shut up clang
1841 q = (char *)Mem_Alloc(fs_mempool, sz);
1842 strlcpy(q, com_token, sz);
1846 // Now: i <= args_left + 1.
1849 memcpy((char *)(&new_argv[i]), &sys.argv[1], sizeof(*sys.argv) * (sys.argc - 1));
1852 // Now: i <= args_left + (sys.argc || 1).
1854 sys.argv = new_argv;
1858 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1860 #if defined(__IPHONEOS__)
1861 if (userdirmode == USERDIRMODE_HOME)
1863 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1864 // fs_userdir stores configurations to the Documents folder of the app
1865 strlcpy(userdir, "../Documents/", MAX_OSPATH);
1870 #elif defined(WIN32)
1871 char homedir[WSTRBUF];
1873 #if _MSC_VER >= 1400
1876 wchar_t mydocsdirw[WSTRBUF];
1877 char mydocsdir[WSTRBUF];
1878 wchar_t *savedgamesdirw;
1879 char savedgamesdir[WSTRBUF] = {0};
1888 case USERDIRMODE_NOHOME:
1889 strlcpy(userdir, fs_basedir, userdirsize);
1891 case USERDIRMODE_MYGAMES:
1893 Sys_LoadDependency(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1895 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdirw) == S_OK)
1897 narrow(mydocsdirw, mydocsdir);
1898 dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1901 #if _MSC_VER >= 1400
1902 _wdupenv_s(&homedirw, &homedirwlen, L"USERPROFILE");
1903 narrow(homedirw, homedir);
1906 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1911 homedirw = _wgetenv(L"USERPROFILE");
1912 narrow(homedirw, homedir);
1915 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1920 case USERDIRMODE_SAVEDGAMES:
1922 Sys_LoadDependency(shell32dllnames, &shell32_dll, shell32funcs);
1924 Sys_LoadDependency(ole32dllnames, &ole32_dll, ole32funcs);
1925 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1927 savedgamesdir[0] = 0;
1928 qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1931 if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1933 if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1936 if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1938 narrow(savedgamesdirw, savedgamesdir);
1939 qCoTaskMemFree(savedgamesdirw);
1942 if (savedgamesdir[0])
1944 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
1959 case USERDIRMODE_NOHOME:
1960 strlcpy(userdir, fs_basedir, userdirsize);
1962 case USERDIRMODE_HOME:
1963 homedir = getenv("HOME");
1966 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1970 case USERDIRMODE_SAVEDGAMES:
1971 homedir = getenv("HOME");
1975 dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
1977 // the XDG say some files would need to go in:
1978 // XDG_CONFIG_HOME (or ~/.config/%s/)
1979 // XDG_DATA_HOME (or ~/.local/share/%s/)
1980 // XDG_CACHE_HOME (or ~/.cache/%s/)
1981 // and also search the following global locations if defined:
1982 // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
1983 // XDG_DATA_DIRS (normally /usr/share/%s/)
1984 // this would be too complicated...
1994 #if !defined(__IPHONEOS__)
1997 // historical behavior...
1998 if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
1999 return 0; // don't bother checking if the basedir folder is writable, it's annoying... unless it is Quake on Windows where NOHOME is the default preferred and we have to check for an error case
2002 // see if we can write to this path (note: won't create path)
2004 // no access() here, we must try to open the file for appending
2005 fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
2009 // on Unix, we don't need to ACTUALLY attempt to open the file
2010 if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
2017 return 1; // good choice - the path exists and is writable
2021 if (userdirmode == USERDIRMODE_NOHOME)
2022 return -1; // path usually already exists, we lack permissions
2024 return 0; // probably good - failed to write but maybe we need to create path
2029 void FS_Init_Commands(void)
2031 Cvar_RegisterVariable (&scr_screenshot_name);
2032 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2033 Cvar_RegisterVariable (&cvar_fs_gamedir);
2035 Cmd_AddCommand(CF_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2036 Cmd_AddCommand(CF_SHARED, "fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2037 Cmd_AddCommand(CF_SHARED, "path", FS_Path_f, "print searchpath (game directories and archives)");
2038 Cmd_AddCommand(CF_SHARED, "dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2039 Cmd_AddCommand(CF_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2040 Cmd_AddCommand(CF_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2043 static void FS_Init_Dir (void)
2053 // Overrides the system supplied base directory (under GAMENAME)
2054 // 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)
2055 i = Sys_CheckParm ("-basedir");
2056 if (i && i < sys.argc-1)
2058 strlcpy (fs_basedir, sys.argv[i+1], sizeof (fs_basedir));
2059 i = (int)strlen (fs_basedir);
2060 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2061 fs_basedir[i-1] = 0;
2065 // If the base directory is explicitly defined by the compilation process
2066 #ifdef DP_FS_BASEDIR
2067 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2068 #elif defined(__ANDROID__)
2069 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2070 #elif defined(MACOSX)
2071 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2072 if (strstr(sys.argv[0], ".app/"))
2075 strlcpy(fs_basedir, sys.argv[0], sizeof(fs_basedir));
2076 split = strstr(fs_basedir, ".app/");
2079 struct stat statresult;
2081 // truncate to just after the .app/
2083 // see if gamedir exists in Resources
2084 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2086 // found gamedir inside Resources, use it
2087 strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2091 // no gamedir found in Resources, gamedir is probably
2092 // outside the .app, remove .app part of path
2093 while (split > fs_basedir && *split != '/')
2102 // make sure the appending of a path separator won't create an unterminated string
2103 memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2104 // add a path separator to the end of the basedir if it lacks one
2105 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2106 strlcat(fs_basedir, "/", sizeof(fs_basedir));
2108 // Add the personal game directory
2109 if((i = Sys_CheckParm("-userdir")) && i < sys.argc - 1)
2110 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", sys.argv[i+1]);
2111 else if (Sys_CheckParm("-nohome"))
2112 *fs_userdir = 0; // user wants roaming installation, no userdir
2115 #ifdef DP_FS_USERDIR
2116 strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
2119 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2120 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2121 int userdirstatus[USERDIRMODE_COUNT];
2123 // historical behavior...
2124 if (!strcmp(gamedirname1, "id1"))
2125 preferreduserdirmode = USERDIRMODE_NOHOME;
2127 // check what limitations the user wants to impose
2128 if (Sys_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2129 if (Sys_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2130 if (Sys_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2131 // gather the status of the possible userdirs
2132 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2134 userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2135 if (userdirstatus[dirmode] == 1)
2136 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2137 else if (userdirstatus[dirmode] == 0)
2138 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2140 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2142 // some games may prefer writing to basedir, but if write fails we
2143 // have to search for a real userdir...
2144 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2145 preferreduserdirmode = highestuserdirmode;
2146 // check for an existing userdir and continue using it if possible...
2147 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2148 if (userdirstatus[dirmode] == 1)
2150 // if no existing userdir found, make a new one...
2151 if (dirmode == 0 && preferreduserdirmode > 0)
2152 for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2153 if (userdirstatus[dirmode] >= 0)
2155 // and finally, we picked one...
2156 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2157 Con_DPrintf("userdir %i is the winner\n", dirmode);
2161 // if userdir equal to basedir, clear it to avoid confusion later
2162 if (!strcmp(fs_basedir, fs_userdir))
2167 p = FS_CheckGameDir(gamedirname1);
2168 if(!p || p == fs_checkgamedir_missing)
2169 Con_Printf(CON_WARN "WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2173 p = FS_CheckGameDir(gamedirname2);
2174 if(!p || p == fs_checkgamedir_missing)
2175 Con_Printf(CON_WARN "WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2179 // Adds basedir/gamedir as an override game
2180 // LadyHavoc: now supports multiple -game directories
2181 for (i = 1;i < sys.argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2185 if (!strcmp (sys.argv[i], "-game") && i < sys.argc-1)
2188 p = FS_CheckGameDir(sys.argv[i]);
2190 Con_Printf("WARNING: Nasty -game name rejected: %s\n", sys.argv[i]);
2191 if(p == fs_checkgamedir_missing)
2192 Con_Printf(CON_WARN "WARNING: -game %s%s/ not found!\n", fs_basedir, sys.argv[i]);
2193 // add the gamedir to the list of active gamedirs
2194 strlcpy (fs_gamedirs[fs_numgamedirs], sys.argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2199 // generate the searchpath
2202 if (Thread_HasThreads())
2203 fs_mutex = Thread_CreateMutex();
2211 void FS_Init_SelfPack (void)
2215 // Load darkplaces.opt from the FS.
2216 if (!Sys_CheckParm("-noopt"))
2218 buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
2221 COM_InsertFlags(buf);
2227 // Provide the SelfPack.
2228 if (!Sys_CheckParm("-noselfpack") && sys.selffd >= 0)
2230 fs_selfpack = FS_LoadPackPK3FromFD(sys.argv[0], sys.selffd, true);
2234 if (!Sys_CheckParm("-noopt"))
2236 buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
2239 COM_InsertFlags(buf);
2256 fs_mempool = Mem_AllocPool("file management", 0, NULL);
2262 // initialize the self-pack (must be before COM_InitGameType as it may add command line options)
2265 // detect gamemode from commandline options or executable name
2276 void FS_Shutdown (void)
2278 // close all pack files and such
2279 // (hopefully there aren't any other open files, but they'll be cleaned up
2280 // by the OS anyway)
2281 FS_ClearSearchPath();
2282 Mem_FreePool (&fs_mempool);
2283 PK3_CloseLibrary ();
2286 Sys_FreeLibrary (&shfolder_dll);
2287 Sys_FreeLibrary (&shell32_dll);
2288 Sys_FreeLibrary (&ole32_dll);
2292 Thread_DestroyMutex(fs_mutex);
2295 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking)
2297 filedesc_t handle = FILEDESC_INVALID;
2300 qbool dolock = false;
2302 wchar filepathw[WSTRBUF] = {0};
2305 // Parse the mode string
2314 opt = O_CREAT | O_TRUNC;
2318 opt = O_CREAT | O_APPEND;
2321 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2322 return FILEDESC_INVALID;
2324 for (ind = 1; mode[ind] != '\0'; ind++)
2338 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2339 filepath, mode, mode[ind]);
2346 if(Sys_CheckParm("-readonly") && mod != O_RDONLY)
2347 return FILEDESC_INVALID;
2351 return FILEDESC_INVALID;
2352 handle = SDL_RWFromFile(filepath, mode);
2355 widen(filepath, filepathw);
2356 # if _MSC_VER >= 1400
2357 _wsopen_s(&handle, filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2359 handle = _wsopen (filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2362 handle = open (filepath, mod | opt, 0666);
2363 if(handle >= 0 && dolock)
2366 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2367 l.l_whence = SEEK_SET;
2370 if(fcntl(handle, F_SETLK, &l) == -1)
2372 FILEDESC_CLOSE(handle);
2382 int FS_SysOpenFD(const char *filepath, const char *mode, qbool nonblocking)
2387 return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2392 ====================
2395 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2396 ====================
2398 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qbool nonblocking)
2402 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2404 file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2405 if (!FILEDESC_ISVALID(file->handle))
2411 file->filename = Mem_strdup(fs_mempool, filepath);
2413 file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2415 // For files opened in append mode, we start at the end of the file
2417 file->position = file->real_length;
2419 FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2429 Open a packed file using its package file descriptor
2432 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2435 filedesc_t dup_handle;
2438 pfile = &pack->files[pack_ind];
2440 // If we don't have the true offset, get it now
2441 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2442 if (!PK3_GetTrueFileOffset (pfile, pack))
2445 #ifndef LINK_TO_ZLIB
2446 // No Zlib DLL = no compressed files
2447 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2449 Con_Printf(CON_WARN "WARNING: can't open the compressed file %s\n"
2450 "You need the Zlib DLL to use compressed files\n",
2456 // LadyHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2457 // the dup() call to avoid having to close the dup_handle on error here
2458 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2460 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2461 pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2465 dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2466 if (!FILEDESC_ISVALID(dup_handle))
2468 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2472 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2473 memset (file, 0, sizeof (*file));
2474 file->handle = dup_handle;
2475 file->flags = QFILE_FLAG_PACKED;
2476 file->real_length = pfile->realsize;
2477 file->offset = pfile->offset;
2481 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2485 file->flags |= QFILE_FLAG_DEFLATED;
2487 // We need some more variables
2488 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2490 ztk->comp_length = pfile->packsize;
2492 // Initialize zlib stream
2493 ztk->zstream.next_in = ztk->input;
2494 ztk->zstream.avail_in = 0;
2496 /* From Zlib's "unzip.c":
2498 * windowBits is passed < 0 to tell that there is no zlib header.
2499 * Note that in this case inflate *requires* an extra "dummy" byte
2500 * after the compressed stream in order to complete decompression and
2501 * return Z_STREAM_END.
2502 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2503 * size of both compressed and uncompressed data
2505 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2507 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2508 FILEDESC_CLOSE(dup_handle);
2513 ztk->zstream.next_out = file->buff;
2514 ztk->zstream.avail_out = sizeof (file->buff);
2523 ====================
2526 Return true if the path should be rejected due to one of the following:
2527 1: path elements that are non-portable
2528 2: path elements that would allow access to files outside the game directory,
2529 or are just not a good idea for a mod to be using.
2530 ====================
2532 int FS_CheckNastyPath (const char *path, qbool isgamedir)
2534 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2538 // Windows: don't allow \ in filenames (windows-only), period.
2539 // (on Windows \ is a directory separator, but / is also supported)
2540 if (strstr(path, "\\"))
2541 return 1; // non-portable
2543 // Mac: don't allow Mac-only filenames - : is a directory separator
2544 // instead of /, but we rely on / working already, so there's no reason to
2545 // support a Mac-only path
2546 // Amiga and Windows: : tries to go to root of drive
2547 if (strstr(path, ":"))
2548 return 1; // non-portable attempt to go to root of drive
2550 // Amiga: // is parent directory
2551 if (strstr(path, "//"))
2552 return 1; // non-portable attempt to go to parent directory
2554 // all: don't allow going to parent directory (../ or /../)
2555 if (strstr(path, ".."))
2556 return 2; // attempt to go outside the game directory
2558 // Windows and UNIXes: don't allow absolute paths
2560 return 2; // attempt to go outside the game directory
2562 // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2563 if (strstr(path, "./"))
2564 return 2; // possible attempt to go outside the game directory
2566 // all: forbid trailing slash on gamedir
2567 if (isgamedir && path[strlen(path)-1] == '/')
2570 // all: forbid leading dot on any filename for any reason
2571 if (strstr(path, "/."))
2572 return 2; // attempt to go outside the game directory
2574 // after all these checks we're pretty sure it's a / separated filename
2575 // and won't do much if any harm
2580 ====================
2583 Sanitize path (replace non-portable characters
2584 with portable ones in-place, etc)
2585 ====================
2587 void FS_SanitizePath(char *path)
2589 for (; *path; path++)
2595 ====================
2598 Look for a file in the packages and in the filesystem
2600 Return the searchpath where the file was found (or NULL)
2601 and the file index in the package if relevant
2602 ====================
2604 static searchpath_t *FS_FindFile (const char *name, int* index, qbool quiet)
2606 searchpath_t *search;
2609 // search through the path, one element at a time
2610 for (search = fs_searchpaths;search;search = search->next)
2612 // is the element a pak file?
2613 if (search->pack && !search->pack->vpack)
2615 int (*strcmp_funct) (const char* str1, const char* str2);
2616 int left, right, middle;
2619 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2621 // Look for the file (binary search)
2623 right = pak->numfiles - 1;
2624 while (left <= right)
2628 middle = (left + right) / 2;
2629 diff = strcmp_funct (pak->files[middle].name, name);
2634 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2636 // yes, but the first one is empty so we treat it as not being there
2637 if (!quiet && developer_extra.integer)
2638 Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2645 if (!quiet && developer_extra.integer)
2646 Con_DPrintf("FS_FindFile: %s in %s\n",
2647 pak->files[middle].name, pak->filename);
2654 // If we're too far in the list
2663 char netpath[MAX_OSPATH];
2664 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2665 if (FS_SysFileExists (netpath))
2667 if (!quiet && developer_extra.integer)
2668 Con_DPrintf("FS_FindFile: %s\n", netpath);
2677 if (!quiet && developer_extra.integer)
2678 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2690 Look for a file in the search paths and open it in read-only mode
2693 static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblocking, int symlinkLevels)
2695 searchpath_t *search;
2698 search = FS_FindFile (filename, &pack_ind, quiet);
2704 // Found in the filesystem?
2707 // this works with vpacks, so we are fine
2708 char path [MAX_OSPATH];
2709 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2710 return FS_SysOpen (path, "rb", nonblocking);
2713 // So, we found it in a package...
2715 // Is it a PK3 symlink?
2716 // TODO also handle directory symlinks by parsing the whole structure...
2717 // but heck, file symlinks are good enough for now
2718 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2720 if(symlinkLevels <= 0)
2722 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2727 char linkbuf[MAX_QPATH];
2729 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2730 const char *mergeslash;
2735 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2741 // Now combine the paths...
2742 mergeslash = strrchr(filename, '/');
2743 mergestart = linkbuf;
2745 mergeslash = filename;
2746 while(!strncmp(mergestart, "../", 3))
2749 while(mergeslash > filename)
2752 if(*mergeslash == '/')
2756 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2757 if(mergeslash == filename)
2759 // Either mergeslash == filename, then we just replace the name (done below)
2763 // Or, we append the name after mergeslash;
2764 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2765 int spaceNeeded = mergeslash - filename + 1;
2766 int spaceRemoved = mergestart - linkbuf;
2767 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2769 Con_DPrintf("symlink: too long path rejected\n");
2772 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2773 memcpy(linkbuf, filename, spaceNeeded);
2774 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2775 mergestart = linkbuf;
2777 if (!quiet && developer_loading.integer)
2778 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2779 if(FS_CheckNastyPath (mergestart, false))
2781 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2784 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2788 return FS_OpenPackedFile (search->pack, pack_ind);
2793 =============================================================================
2795 MAIN PUBLIC FUNCTIONS
2797 =============================================================================
2801 ====================
2804 Open a file in the userpath. The syntax is the same as fopen
2805 Used for savegame scanning in menu, and all file writing.
2806 ====================
2808 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qbool quiet)
2810 char real_path [MAX_OSPATH];
2812 if (FS_CheckNastyPath(filepath, false))
2814 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2818 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2820 // If the file is opened in "write", "append", or "read/write" mode,
2821 // create directories up to the file.
2822 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2823 FS_CreatePath (real_path);
2824 return FS_SysOpen (real_path, mode, false);
2829 ====================
2832 Open a file. The syntax is the same as fopen
2833 ====================
2835 qfile_t* FS_OpenVirtualFile (const char* filepath, qbool quiet)
2837 qfile_t *result = NULL;
2838 if (FS_CheckNastyPath(filepath, false))
2840 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2844 if (fs_mutex) Thread_LockMutex(fs_mutex);
2845 result = FS_OpenReadFile (filepath, quiet, false, 16);
2846 if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2852 ====================
2855 Open a file. The syntax is the same as fopen
2856 ====================
2858 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qbool quiet)
2861 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2862 memset (file, 0, sizeof (*file));
2863 file->flags = QFILE_FLAG_DATA;
2865 file->real_length = size;
2871 ====================
2875 ====================
2877 int FS_Close (qfile_t* file)
2879 if(file->flags & QFILE_FLAG_DATA)
2885 if (FILEDESC_CLOSE (file->handle))
2890 if (file->flags & QFILE_FLAG_REMOVE)
2892 if (remove(file->filename) == -1)
2894 // No need to report this. If removing a just
2895 // written file failed, this most likely means
2896 // someone else deleted it first - which we
2901 Mem_Free((void *) file->filename);
2906 qz_inflateEnd (&file->ztk->zstream);
2907 Mem_Free (file->ztk);
2914 void FS_RemoveOnClose(qfile_t* file)
2916 file->flags |= QFILE_FLAG_REMOVE;
2920 ====================
2923 Write "datasize" bytes into a file
2924 ====================
2926 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2928 fs_offset_t written = 0;
2930 // If necessary, seek to the exact file position we're supposed to be
2931 if (file->buff_ind != file->buff_len)
2933 if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
2935 Con_Printf(CON_WARN "WARNING: could not seek in %s.\n", file->filename);
2939 // Purge cached data
2942 // Write the buffer and update the position
2943 // LadyHavoc: to hush a warning about passing size_t to an unsigned int parameter on Win64 we do this as multiple writes if the size would be too big for an integer (we never write that big in one go, but it's a theory)
2944 while (written < (fs_offset_t)datasize)
2946 // figure out how much to write in one chunk
2947 fs_offset_t maxchunk = 1<<30; // 1 GiB
2948 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
2949 int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
2950 // if at least some was written, add it to our accumulator
2953 // if the result is not what we expected, consider the write to be incomplete
2954 if (result != chunk)
2957 file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
2958 if (file->real_length < file->position)
2959 file->real_length = file->position;
2961 // note that this will never be less than 0 even if the write failed
2967 ====================
2970 Read up to "buffersize" bytes from a file
2971 ====================
2973 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2975 fs_offset_t count, done;
2977 if (buffersize == 0 || !buffer)
2980 // Get rid of the ungetc character
2981 if (file->ungetc != EOF)
2983 ((char*)buffer)[0] = file->ungetc;
2991 if(file->flags & QFILE_FLAG_DATA)
2993 size_t left = file->real_length - file->position;
2994 if(buffersize > left)
2996 memcpy(buffer, file->data + file->position, buffersize);
2997 file->position += buffersize;
3001 // First, we copy as many bytes as we can from "buff"
3002 if (file->buff_ind < file->buff_len)
3004 count = file->buff_len - file->buff_ind;
3005 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
3007 memcpy (buffer, &file->buff[file->buff_ind], count);
3008 file->buff_ind += count;
3010 buffersize -= count;
3011 if (buffersize == 0)
3015 // NOTE: at this point, the read buffer is always empty
3017 // If the file isn't compressed
3018 if (! (file->flags & QFILE_FLAG_DEFLATED))
3022 // We must take care to not read after the end of the file
3023 count = file->real_length - file->position;
3025 // If we have a lot of data to get, put them directly into "buffer"
3026 if (buffersize > sizeof (file->buff) / 2)
3028 if (count > (fs_offset_t)buffersize)
3029 count = (fs_offset_t)buffersize;
3030 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3032 // Seek failed. When reading from a pipe, and
3033 // the caller never called FS_Seek, this still
3034 // works fine. So no reporting this error.
3036 nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
3040 file->position += nb;
3042 // Purge cached data
3048 if (count > (fs_offset_t)sizeof (file->buff))
3049 count = (fs_offset_t)sizeof (file->buff);
3050 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3052 // Seek failed. When reading from a pipe, and
3053 // the caller never called FS_Seek, this still
3054 // works fine. So no reporting this error.
3056 nb = FILEDESC_READ (file->handle, file->buff, count);
3059 file->buff_len = nb;
3060 file->position += nb;
3062 // Copy the requested data in "buffer" (as much as we can)
3063 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3064 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3065 file->buff_ind = count;
3073 // If the file is compressed, it's more complicated...
3074 // We cycle through a few operations until we have read enough data
3075 while (buffersize > 0)
3077 ztoolkit_t *ztk = file->ztk;
3080 // NOTE: at this point, the read buffer is always empty
3082 // If "input" is also empty, we need to refill it
3083 if (ztk->in_ind == ztk->in_len)
3085 // If we are at the end of the file
3086 if (file->position == file->real_length)
3089 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
3090 if (count > (fs_offset_t)sizeof (ztk->input))
3091 count = (fs_offset_t)sizeof (ztk->input);
3092 FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
3093 if (FILEDESC_READ (file->handle, ztk->input, count) != count)
3095 Con_Printf ("FS_Read: unexpected end of file\n");
3100 ztk->in_len = count;
3101 ztk->in_position += count;
3104 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3105 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3107 // Now that we are sure we have compressed data available, we need to determine
3108 // if it's better to inflate it in "file->buff" or directly in "buffer"
3110 // Inflate the data in "file->buff"
3111 if (buffersize < sizeof (file->buff) / 2)
3113 ztk->zstream.next_out = file->buff;
3114 ztk->zstream.avail_out = sizeof (file->buff);
3115 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3116 if (error != Z_OK && error != Z_STREAM_END)
3118 Con_Printf ("FS_Read: Can't inflate file\n");
3121 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3123 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3124 file->position += file->buff_len;
3126 // Copy the requested data in "buffer" (as much as we can)
3127 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3128 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3129 file->buff_ind = count;
3132 // Else, we inflate directly in "buffer"
3135 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3136 ztk->zstream.avail_out = (unsigned int)buffersize;
3137 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3138 if (error != Z_OK && error != Z_STREAM_END)
3140 Con_Printf ("FS_Read: Can't inflate file\n");
3143 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3145 // How much data did it inflate?
3146 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3147 file->position += count;
3149 // Purge cached data
3154 buffersize -= count;
3162 ====================
3165 Print a string into a file
3166 ====================
3168 int FS_Print (qfile_t* file, const char *msg)
3170 return (int)FS_Write (file, msg, strlen (msg));
3174 ====================
3177 Print a string into a file
3178 ====================
3180 int FS_Printf(qfile_t* file, const char* format, ...)
3185 va_start (args, format);
3186 result = FS_VPrintf (file, format, args);
3194 ====================
3197 Print a string into a file
3198 ====================
3200 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3203 fs_offset_t buff_size = MAX_INPUTLINE;
3208 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3209 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3210 if (len >= 0 && len < buff_size)
3212 Mem_Free (tempbuff);
3216 len = FILEDESC_WRITE (file->handle, tempbuff, len);
3217 Mem_Free (tempbuff);
3224 ====================
3227 Get the next character of a file
3228 ====================
3230 int FS_Getc (qfile_t* file)
3234 if (FS_Read (file, &c, 1) != 1)
3242 ====================
3245 Put a character back into the read buffer (only supports one character!)
3246 ====================
3248 int FS_UnGetc (qfile_t* file, unsigned char c)
3250 // If there's already a character waiting to be read
3251 if (file->ungetc != EOF)
3260 ====================
3263 Move the position index in a file
3264 ====================
3266 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3269 unsigned char* buffer;
3270 fs_offset_t buffersize;
3272 // Compute the file offset
3276 offset += file->position - file->buff_len + file->buff_ind;
3283 offset += file->real_length;
3289 if (offset < 0 || offset > file->real_length)
3292 if(file->flags & QFILE_FLAG_DATA)
3294 file->position = offset;
3298 // If we have the data in our read buffer, we don't need to actually seek
3299 if (file->position - file->buff_len <= offset && offset <= file->position)
3301 file->buff_ind = offset + file->buff_len - file->position;
3305 // Purge cached data
3308 // Unpacked or uncompressed files can seek directly
3309 if (! (file->flags & QFILE_FLAG_DEFLATED))
3311 if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3313 file->position = offset;
3317 // Seeking in compressed files is more a hack than anything else,
3318 // but we need to support it, so here we go.
3321 // If we have to go back in the file, we need to restart from the beginning
3322 if (offset <= file->position)
3326 ztk->in_position = 0;
3328 if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3329 Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3331 // Reset the Zlib stream
3332 ztk->zstream.next_in = ztk->input;
3333 ztk->zstream.avail_in = 0;
3334 qz_inflateReset (&ztk->zstream);
3337 // We need a big buffer to force inflating into it directly
3338 buffersize = 2 * sizeof (file->buff);
3339 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3341 // Skip all data until we reach the requested offset
3342 while (offset > (file->position - file->buff_len + file->buff_ind))
3344 fs_offset_t diff = offset - (file->position - file->buff_len + file->buff_ind);
3345 fs_offset_t count, len;
3347 count = (diff > buffersize) ? buffersize : diff;
3348 len = FS_Read (file, buffer, count);
3362 ====================
3365 Give the current position in a file
3366 ====================
3368 fs_offset_t FS_Tell (qfile_t* file)
3370 return file->position - file->buff_len + file->buff_ind;
3375 ====================
3378 Give the total size of a file
3379 ====================
3381 fs_offset_t FS_FileSize (qfile_t* file)
3383 return file->real_length;
3388 ====================
3391 Erases any buffered input or output data
3392 ====================
3394 void FS_Purge (qfile_t* file)
3404 FS_LoadAndCloseQFile
3406 Loads full content of a qfile_t and closes it.
3407 Always appends a 0 byte.
3410 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3412 unsigned char *buf = NULL;
3413 fs_offset_t filesize = 0;
3417 filesize = file->real_length;
3420 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3425 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3426 buf[filesize] = '\0';
3427 FS_Read (file, buf, filesize);
3429 if (developer_loadfile.integer)
3430 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3433 if (filesizepointer)
3434 *filesizepointer = filesize;
3443 Filename are relative to the quake directory.
3444 Always appends a 0 byte.
3447 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3449 qfile_t *file = FS_OpenVirtualFile(path, quiet);
3450 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3458 Filename are OS paths.
3459 Always appends a 0 byte.
3462 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3464 qfile_t *file = FS_SysOpen(path, "rb", false);
3465 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3473 The filename will be prefixed by the current game directory
3476 qbool FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3480 fs_offset_t lentotal;
3482 file = FS_OpenRealFile(filename, "wb", false);
3485 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3490 for(i = 0; i < count; ++i)
3492 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3493 for(i = 0; i < count; ++i)
3494 FS_Write (file, data[i], len[i]);
3499 qbool FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3501 return FS_WriteFileInBlocks(filename, &data, &len, 1);
3506 =============================================================================
3508 OTHERS PUBLIC FUNCTIONS
3510 =============================================================================
3518 void FS_StripExtension (const char *in, char *out, size_t size_out)
3526 while ((currentchar = *in) && size_out > 1)
3528 if (currentchar == '.')
3530 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3532 *out++ = currentchar;
3548 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3552 // if path doesn't have a .EXT, append extension
3553 // (extension should include the .)
3554 src = path + strlen(path);
3556 while (*src != '/' && src != path)
3559 return; // it has an extension
3563 strlcat (path, extension, size_path);
3571 Look for a file in the packages and in the filesystem
3574 int FS_FileType (const char *filename)
3576 searchpath_t *search;
3577 char fullpath[MAX_OSPATH];
3579 search = FS_FindFile (filename, NULL, true);
3581 return FS_FILETYPE_NONE;
3583 if(search->pack && !search->pack->vpack)
3584 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3586 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3587 return FS_SysFileType(fullpath);
3595 Look for a file in the packages and in the filesystem
3598 qbool FS_FileExists (const char *filename)
3600 return (FS_FindFile (filename, NULL, true) != NULL);
3608 Look for a file in the filesystem only
3611 int FS_SysFileType (const char *path)
3614 // Sajt - some older sdks are missing this define
3615 # ifndef INVALID_FILE_ATTRIBUTES
3616 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3618 wchar pathw[WSTRBUF] = {0};
3621 result = GetFileAttributesW(pathw);
3623 if(result == INVALID_FILE_ATTRIBUTES)
3624 return FS_FILETYPE_NONE;
3626 if(result & FILE_ATTRIBUTE_DIRECTORY)
3627 return FS_FILETYPE_DIRECTORY;
3629 return FS_FILETYPE_FILE;
3633 if (stat (path,&buf) == -1)
3634 return FS_FILETYPE_NONE;
3637 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3639 if(S_ISDIR(buf.st_mode))
3640 return FS_FILETYPE_DIRECTORY;
3642 return FS_FILETYPE_FILE;
3646 qbool FS_SysFileExists (const char *path)
3648 return FS_SysFileType (path) != FS_FILETYPE_NONE;
3655 Allocate and fill a search structure with information on matching filenames.
3658 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const char *packfile)
3661 searchpath_t *searchpath;
3663 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3664 stringlist_t resultlist;
3665 stringlist_t dirlist;
3666 stringlist_t matchedSet, foundSet;
3667 const char *start, *slash, *backslash, *colon, *separator;
3670 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3675 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3679 stringlistinit(&resultlist);
3680 stringlistinit(&dirlist);
3682 slash = strrchr(pattern, '/');
3683 backslash = strrchr(pattern, '\\');
3684 colon = strrchr(pattern, ':');
3685 separator = max(slash, backslash);
3686 separator = max(separator, colon);
3687 basepathlength = separator ? (separator + 1 - pattern) : 0;
3688 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3690 memcpy(basepath, pattern, basepathlength);
3691 basepath[basepathlength] = 0;
3693 // search through the path, one element at a time
3694 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3696 // is the element a pak file?
3697 if (searchpath->pack && !searchpath->pack->vpack)
3699 // look through all the pak file elements
3700 pak = searchpath->pack;
3703 if(strcmp(packfile, pak->shortname))
3706 for (i = 0;i < pak->numfiles;i++)
3708 char temp[MAX_OSPATH];
3709 strlcpy(temp, pak->files[i].name, sizeof(temp));
3712 if (matchpattern(temp, (char *)pattern, true))
3714 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3715 if (!strcmp(resultlist.strings[resultlistindex], temp))
3717 if (resultlistindex == resultlist.numstrings)
3719 stringlistappend(&resultlist, temp);
3720 if (!quiet && developer_loading.integer)
3721 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3724 // strip off one path element at a time until empty
3725 // this way directories are added to the listing if they match the pattern
3726 slash = strrchr(temp, '/');
3727 backslash = strrchr(temp, '\\');
3728 colon = strrchr(temp, ':');
3730 if (separator < slash)
3732 if (separator < backslash)
3733 separator = backslash;
3734 if (separator < colon)
3736 *((char *)separator) = 0;
3747 stringlistinit(&matchedSet);
3748 stringlistinit(&foundSet);
3749 // add a first entry to the set
3750 stringlistappend(&matchedSet, "");
3751 // iterate through pattern's path
3754 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3755 char subpath[MAX_OSPATH];
3756 char subpattern[MAX_OSPATH];
3758 // find the next wildcard
3759 wildcard = strchr(start, '?');
3760 asterisk = strchr(start, '*');
3761 if (asterisk && (!wildcard || asterisk < wildcard))
3763 wildcard = asterisk;
3768 nextseparator = strchr( wildcard, '/' );
3772 nextseparator = NULL;
3775 if( !nextseparator ) {
3776 nextseparator = start + strlen( start );
3779 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3780 // copy everything up except nextseperator
3781 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3782 // find the last '/' before the wildcard
3783 prevseparator = strrchr( subpattern, '/' );
3785 prevseparator = subpattern;
3788 // copy everything from start to the previous including the '/' (before the wildcard)
3789 // everything up to start is already included in the path of matchedSet's entries
3790 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3792 // for each entry in matchedSet try to open the subdirectories specified in subpath
3793 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3794 char temp[MAX_OSPATH];
3795 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3796 strlcat( temp, subpath, sizeof(temp) );
3797 listdirectory( &foundSet, searchpath->filename, temp );
3799 if( dirlistindex == 0 ) {
3802 // reset the current result set
3803 stringlistfreecontents( &matchedSet );
3804 // match against the pattern
3805 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3806 const char *direntry = foundSet.strings[ dirlistindex ];
3807 if (matchpattern(direntry, subpattern, true)) {
3808 stringlistappend( &matchedSet, direntry );
3811 stringlistfreecontents( &foundSet );
3813 start = nextseparator;
3816 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3818 const char *matchtemp = matchedSet.strings[dirlistindex];
3819 if (matchpattern(matchtemp, (char *)pattern, true))
3821 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3822 if (!strcmp(resultlist.strings[resultlistindex], matchtemp))
3824 if (resultlistindex == resultlist.numstrings)
3826 stringlistappend(&resultlist, matchtemp);
3827 if (!quiet && developer_loading.integer)
3828 Con_Printf("SearchDirFile: %s\n", matchtemp);
3832 stringlistfreecontents( &matchedSet );
3836 if (resultlist.numstrings)
3838 stringlistsort(&resultlist, true);
3839 numfiles = resultlist.numstrings;
3841 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3842 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3843 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3844 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3845 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3846 search->numfilenames = (int)numfiles;
3849 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3852 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3853 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3854 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3856 numchars += (int)textlen;
3859 stringlistfreecontents(&resultlist);
3865 void FS_FreeSearch(fssearch_t *search)
3870 extern int con_linewidth;
3871 static int FS_ListDirectory(const char *pattern, int oneperline)
3880 char linebuf[MAX_INPUTLINE];
3882 search = FS_Search(pattern, true, true, NULL);
3885 numfiles = search->numfilenames;
3888 // FIXME: the names could be added to one column list and then
3889 // gradually shifted into the next column if they fit, and then the
3890 // next to make a compact variable width listing but it's a lot more
3892 // find width for columns
3894 for (i = 0;i < numfiles;i++)
3896 l = (int)strlen(search->filenames[i]);
3897 if (columnwidth < l)
3900 // count the spacing character
3902 // calculate number of columns
3903 numcolumns = con_linewidth / columnwidth;
3904 // don't bother with the column printing if it's only one column
3905 if (numcolumns >= 2)
3907 numlines = (numfiles + numcolumns - 1) / numcolumns;
3908 for (i = 0;i < numlines;i++)
3911 for (k = 0;k < numcolumns;k++)
3913 l = i * numcolumns + k;
3916 name = search->filenames[l];
3917 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3918 linebuf[linebufpos++] = name[j];
3919 // space out name unless it's the last on the line
3920 if (k + 1 < numcolumns && l + 1 < numfiles)
3921 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3922 linebuf[linebufpos++] = ' ';
3925 linebuf[linebufpos] = 0;
3926 Con_Printf("%s\n", linebuf);
3933 for (i = 0;i < numfiles;i++)
3934 Con_Printf("%s\n", search->filenames[i]);
3935 FS_FreeSearch(search);
3936 return (int)numfiles;
3939 static void FS_ListDirectoryCmd (cmd_state_t *cmd, const char* cmdname, int oneperline)
3941 const char *pattern;
3942 if (Cmd_Argc(cmd) >= 3)
3944 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3947 if (Cmd_Argc(cmd) == 2)
3948 pattern = Cmd_Argv(cmd, 1);
3951 if (!FS_ListDirectory(pattern, oneperline))
3952 Con_Print("No files found.\n");
3955 void FS_Dir_f(cmd_state_t *cmd)
3957 FS_ListDirectoryCmd(cmd, "dir", true);
3960 void FS_Ls_f(cmd_state_t *cmd)
3962 FS_ListDirectoryCmd(cmd, "ls", false);
3965 void FS_Which_f(cmd_state_t *cmd)
3967 const char *filename;
3970 if (Cmd_Argc(cmd) != 2)
3972 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(cmd, 0));
3975 filename = Cmd_Argv(cmd, 1);
3976 sp = FS_FindFile(filename, &index, true);
3978 Con_Printf("%s isn't anywhere\n", filename);
3984 Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3986 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3989 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3993 const char *FS_WhichPack(const char *filename)
3996 searchpath_t *sp = FS_FindFile(filename, &index, true);
3998 return sp->pack->shortname;
4006 ====================
4007 FS_IsRegisteredQuakePack
4009 Look for a proof of purchase file file in the requested package
4011 If it is found, this file should NOT be downloaded.
4012 ====================
4014 qbool FS_IsRegisteredQuakePack(const char *name)
4016 searchpath_t *search;
4019 // search through the path, one element at a time
4020 for (search = fs_searchpaths;search;search = search->next)
4022 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
4023 // TODO do we want to support vpacks in here too?
4025 int (*strcmp_funct) (const char* str1, const char* str2);
4026 int left, right, middle;
4029 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
4031 // Look for the file (binary search)
4033 right = pak->numfiles - 1;
4034 while (left <= right)
4038 middle = (left + right) / 2;
4039 diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
4045 // If we're too far in the list
4052 // we found the requested pack but it is not registered quake
4060 int FS_CRCFile(const char *filename, size_t *filesizepointer)
4063 unsigned char *filedata;
4064 fs_offset_t filesize;
4065 if (filesizepointer)
4066 *filesizepointer = 0;
4067 if (!filename || !*filename)
4069 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
4072 if (filesizepointer)
4073 *filesizepointer = filesize;
4074 crc = CRC_Block(filedata, filesize);
4080 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
4083 unsigned char *out = NULL;
4087 #ifndef LINK_TO_ZLIB
4092 memset(&strm, 0, sizeof(strm));
4093 strm.zalloc = Z_NULL;
4094 strm.zfree = Z_NULL;
4095 strm.opaque = Z_NULL;
4098 level = Z_DEFAULT_COMPRESSION;
4100 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
4102 Con_Printf("FS_Deflate: deflate init error!\n");
4106 strm.next_in = (unsigned char*)data;
4107 strm.avail_in = (unsigned int)size;
4109 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
4112 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4113 qz_deflateEnd(&strm);
4117 strm.next_out = tmp;
4118 strm.avail_out = (unsigned int)size;
4120 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4122 Con_Printf("FS_Deflate: deflate failed!\n");
4123 qz_deflateEnd(&strm);
4128 if(qz_deflateEnd(&strm) != Z_OK)
4130 Con_Printf("FS_Deflate: deflateEnd failed\n");
4135 if(strm.total_out >= size)
4137 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4142 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4145 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4150 *deflated_size = (size_t)strm.total_out;
4152 memcpy(out, tmp, strm.total_out);
4158 static void AssertBufsize(sizebuf_t *buf, int length)
4160 if(buf->cursize + length > buf->maxsize)
4162 int oldsize = buf->maxsize;
4163 unsigned char *olddata;
4164 olddata = buf->data;
4165 buf->maxsize += length;
4166 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4169 memcpy(buf->data, olddata, oldsize);
4175 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4179 unsigned char *out = NULL;
4180 unsigned char tmp[2048];
4185 #ifndef LINK_TO_ZLIB
4190 memset(&outbuf, 0, sizeof(outbuf));
4191 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4192 outbuf.maxsize = sizeof(tmp);
4194 memset(&strm, 0, sizeof(strm));
4195 strm.zalloc = Z_NULL;
4196 strm.zfree = Z_NULL;
4197 strm.opaque = Z_NULL;
4199 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4201 Con_Printf("FS_Inflate: inflate init error!\n");
4202 Mem_Free(outbuf.data);
4206 strm.next_in = (unsigned char*)data;
4207 strm.avail_in = (unsigned int)size;
4211 strm.next_out = tmp;
4212 strm.avail_out = sizeof(tmp);
4213 ret = qz_inflate(&strm, Z_NO_FLUSH);
4214 // it either returns Z_OK on progress, Z_STREAM_END on end
4222 case Z_STREAM_ERROR:
4223 Con_Print("FS_Inflate: stream error!\n");
4226 Con_Print("FS_Inflate: data error!\n");
4229 Con_Print("FS_Inflate: mem error!\n");
4232 Con_Print("FS_Inflate: buf error!\n");
4235 Con_Print("FS_Inflate: unknown error!\n");
4239 if(ret != Z_OK && ret != Z_STREAM_END)
4241 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4242 Mem_Free(outbuf.data);
4243 qz_inflateEnd(&strm);
4246 have = sizeof(tmp) - strm.avail_out;
4247 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4248 SZ_Write(&outbuf, tmp, have);
4249 } while(ret != Z_STREAM_END);
4251 qz_inflateEnd(&strm);
4253 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4256 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4257 Mem_Free(outbuf.data);
4261 memcpy(out, outbuf.data, outbuf.cursize);
4262 Mem_Free(outbuf.data);
4264 *inflated_size = (size_t)outbuf.cursize;