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) {
113 /* This code seems to have originally been written with the assumption that
114 * read(..., n) returns n on success. This is not the case (refer to
115 * <https://pubs.opengroup.org/onlinepubs/9699919799/functions/read.html>).
123 Read exactly length bytes from fd into buf. If end of file is reached,
124 the number of bytes read is returned. If an error occurred, that error
125 is returned. Note that if an error is returned, any previously read
129 static fs_offset_t ReadAll(const filedesc_t fd, void *const buf, const size_t length)
131 char *const p = (char *)buf;
135 const fs_offset_t result = FILEDESC_READ(fd, p + cursor, length - cursor);
136 if (result < 0) // Error
138 if (result == 0) // EOF
141 } while (cursor < length);
149 Write exactly length bytes to fd from buf.
150 If an error occurred, that error is returned.
153 static fs_offset_t WriteAll(const filedesc_t fd, const void *const buf, const size_t length)
155 const char *const p = (const char *)buf;
159 const fs_offset_t result = FILEDESC_WRITE(fd, p + cursor, length - cursor);
160 if (result < 0) // Error
163 } while (cursor < length);
168 #define FILEDESC_READ ReadAll
169 #undef FILEDESC_WRITE
170 #define FILEDESC_WRITE WriteAll
172 /** \page fs File System
174 All of Quake's data access is through a hierarchical file system, the contents
175 of the file system can be transparently merged from several sources.
177 The "base directory" is the path to the directory holding the quake.exe and
178 all game directories. The sys_* files pass this to host_init in
179 quakeparms_t->basedir. This can be overridden with the "-basedir" command
180 line parm to allow code debugging in a different directory. The base
181 directory is only used during filesystem initialization.
183 The "game directory" is the first tree on the search path and directory that
184 all generated files (savegames, screenshots, demos, config files) will be
185 saved to. This can be overridden with the "-game" command line parameter.
186 If multiple "-game <gamedir>" args are passed the last one is the "primary"
187 and files will be saved there, the rest are read-only.
193 =============================================================================
197 =============================================================================
200 // Magic numbers of a ZIP file (big-endian format)
201 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
202 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
203 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
205 // Other constants for ZIP files
206 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
207 #define ZIP_END_CDIR_SIZE 22
208 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
209 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
214 #define qz_inflate inflate
215 #define qz_inflateEnd inflateEnd
216 #define qz_inflateInit2_ inflateInit2_
217 #define qz_inflateReset inflateReset
218 #define qz_deflateInit2_ deflateInit2_
219 #define qz_deflateEnd deflateEnd
220 #define qz_deflate deflate
221 #define Z_MEMLEVEL_DEFAULT 8
224 // Zlib constants (from zlib.h)
225 #define Z_SYNC_FLUSH 2
228 #define Z_STREAM_END 1
229 #define Z_STREAM_ERROR (-2)
230 #define Z_DATA_ERROR (-3)
231 #define Z_MEM_ERROR (-4)
232 #define Z_BUF_ERROR (-5)
233 #define ZLIB_VERSION "1.2.3"
237 #define Z_MEMLEVEL_DEFAULT 8
240 #define Z_DEFAULT_COMPRESSION (-1)
242 #define Z_SYNC_FLUSH 2
243 #define Z_FULL_FLUSH 3
246 // Uncomment the following line if the zlib DLL you have still uses
247 // the 1.1.x series calling convention on Win32 (WINAPI)
248 //#define ZLIB_USES_WINAPI
252 =============================================================================
256 =============================================================================
259 /*! Zlib stream (from zlib.h)
260 * \warning: some pointers we don't use directly have
261 * been cast to "void*" for a matter of simplicity
265 unsigned char *next_in; ///< next input byte
266 unsigned int avail_in; ///< number of bytes available at next_in
267 unsigned long total_in; ///< total nb of input bytes read so far
269 unsigned char *next_out; ///< next output byte should be put there
270 unsigned int avail_out; ///< remaining free space at next_out
271 unsigned long total_out; ///< total nb of bytes output so far
273 char *msg; ///< last error message, NULL if no error
274 void *state; ///< not visible by applications
276 void *zalloc; ///< used to allocate the internal state
277 void *zfree; ///< used to free the internal state
278 void *opaque; ///< private data object passed to zalloc and zfree
280 int data_type; ///< best guess about the data type: ascii or binary
281 unsigned long adler; ///< adler32 value of the uncompressed data
282 unsigned long reserved; ///< reserved for future use
287 /// inside a package (PAK or PK3)
288 #define QFILE_FLAG_PACKED (1 << 0)
289 /// file is compressed using the deflate algorithm (PK3 only)
290 #define QFILE_FLAG_DEFLATED (1 << 1)
291 /// file is actually already loaded data
292 #define QFILE_FLAG_DATA (1 << 2)
293 /// real file will be removed on close
294 #define QFILE_FLAG_REMOVE (1 << 3)
296 #define FILE_BUFF_SIZE 2048
300 size_t comp_length; ///< length of the compressed file
301 size_t in_ind, in_len; ///< input buffer current index and length
302 size_t in_position; ///< position in the compressed file
303 unsigned char input [FILE_BUFF_SIZE];
309 filedesc_t handle; ///< file descriptor
310 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
311 fs_offset_t position; ///< current position in the file
312 fs_offset_t offset; ///< offset into the package (0 if external file)
313 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
316 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
317 unsigned char buff [FILE_BUFF_SIZE];
319 ztoolkit_t* ztk; ///< For zipped files.
321 const unsigned char *data; ///< For data files.
323 const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
327 // ------ PK3 files on disk ------ //
329 // You can get the complete ZIP format description from PKWARE website
331 typedef struct pk3_endOfCentralDir_s
333 unsigned int signature;
334 unsigned short disknum;
335 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
336 unsigned short localentries; ///< number of entries in the central directory on this disk
337 unsigned short nbentries; ///< total number of entries in the central directory on this disk
338 unsigned int cdir_size; ///< size of the central directory
339 unsigned int cdir_offset; ///< with respect to the starting disk number
340 unsigned short comment_size;
341 fs_offset_t prepended_garbage;
342 } pk3_endOfCentralDir_t;
345 // ------ PAK files on disk ------ //
346 typedef struct dpackfile_s
349 int filepos, filelen;
352 typedef struct dpackheader_s
360 /*! \name Packages in memory
363 /// the offset in packfile_t is the true contents offset
364 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
365 /// file compressed using the deflate algorithm
366 #define PACKFILE_FLAG_DEFLATED (1 << 1)
367 /// file is a symbolic link
368 #define PACKFILE_FLAG_SYMLINK (1 << 2)
370 typedef struct packfile_s
372 char name [MAX_QPATH];
375 fs_offset_t packsize; ///< size in the package
376 fs_offset_t realsize; ///< real file size (uncompressed)
379 typedef struct pack_s
381 char filename [MAX_OSPATH];
382 char shortname [MAX_QPATH];
384 qbool ignorecase; ///< PK3 ignores case
392 /// Search paths for files (including packages)
393 typedef struct searchpath_s
395 // only one of filename / pack will be used
396 char filename[MAX_OSPATH];
398 struct searchpath_s *next;
403 =============================================================================
407 =============================================================================
410 void FS_Dir_f(cmd_state_t *cmd);
411 void FS_Ls_f(cmd_state_t *cmd);
412 void FS_Which_f(cmd_state_t *cmd);
414 static searchpath_t *FS_FindFile (const char *name, int *index, const char **canonicalname, qbool quiet);
415 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
416 fs_offset_t offset, fs_offset_t packsize,
417 fs_offset_t realsize, int flags);
421 =============================================================================
425 =============================================================================
428 mempool_t *fs_mempool;
429 void *fs_mutex = NULL;
431 searchpath_t *fs_searchpaths = NULL;
432 const char *const fs_checkgamedir_missing = "missing";
434 #define MAX_FILES_IN_PACK 65536
436 char fs_userdir[MAX_OSPATH];
437 char fs_gamedir[MAX_OSPATH];
438 char fs_basedir[MAX_OSPATH];
439 static pack_t *fs_selfpack = NULL;
441 // list of active game directories
442 int fs_numgamedirs = 0;
443 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
445 // list of all gamedirs with modinfo.txt
446 gamedir_t *fs_all_gamedirs = NULL;
447 int fs_all_gamedirs_count = 0;
449 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)"};
450 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"};
451 cvar_t fs_unload_dlcache = {CF_CLIENT, "fs_unload_dlcache", "1", "if enabled, unload dlcache's loaded pak/pk3 files when changing server and/or map WARNING: disabling unloading can cause servers to override assets of other servers, \"memory leaking\" by dlcache assets never unloading and many more issues"};
452 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)"};
456 =============================================================================
458 PRIVATE FUNCTIONS - PK3 HANDLING
460 =============================================================================
464 // Functions exported from zlib
465 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
466 # define ZEXPORT WINAPI
471 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
472 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
473 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
474 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
475 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
476 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
477 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
480 #define qz_inflateInit2(strm, windowBits) \
481 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
482 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
483 qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
486 // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
488 static dllfunction_t zlibfuncs[] =
490 {"inflate", (void **) &qz_inflate},
491 {"inflateEnd", (void **) &qz_inflateEnd},
492 {"inflateInit2_", (void **) &qz_inflateInit2_},
493 {"inflateReset", (void **) &qz_inflateReset},
494 {"deflateInit2_", (void **) &qz_deflateInit2_},
495 {"deflateEnd", (void **) &qz_deflateEnd},
496 {"deflate", (void **) &qz_deflate},
500 /// Handle for Zlib DLL
501 static dllhandle_t zlib_dll = NULL;
505 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPWSTR pszPath);
506 static dllfunction_t shfolderfuncs[] =
508 {"SHGetFolderPathW", (void **) &qSHGetFolderPath},
511 static const char* shfolderdllnames [] =
513 "shfolder.dll", // IE 4, or Win NT and higher
516 static dllhandle_t shfolder_dll = NULL;
518 const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}};
519 #define qREFKNOWNFOLDERID const GUID *
520 #define qKF_FLAG_CREATE 0x8000
521 #define qKF_FLAG_NO_ALIAS 0x1000
522 static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
523 static dllfunction_t shell32funcs[] =
525 {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath},
528 static const char* shell32dllnames [] =
530 "shell32.dll", // Vista and higher
533 static dllhandle_t shell32_dll = NULL;
535 static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit);
536 static void (WINAPI *qCoUninitialize)(void);
537 static void (WINAPI *qCoTaskMemFree)(LPVOID pv);
538 static dllfunction_t ole32funcs[] =
540 {"CoInitializeEx", (void **) &qCoInitializeEx},
541 {"CoUninitialize", (void **) &qCoUninitialize},
542 {"CoTaskMemFree", (void **) &qCoTaskMemFree},
545 static const char* ole32dllnames [] =
547 "ole32.dll", // 2000 and higher
550 static dllhandle_t ole32_dll = NULL;
560 static void PK3_CloseLibrary (void)
563 Sys_FreeLibrary (&zlib_dll);
572 Try to load the Zlib DLL
575 static qbool PK3_OpenLibrary (void)
580 const char* dllnames [] =
583 # ifdef ZLIB_USES_WINAPI
589 #elif defined(MACOSX)
603 return Sys_LoadDependency (dllnames, &zlib_dll, zlibfuncs);
611 See if zlib is available
614 qbool FS_HasZlib(void)
619 PK3_OpenLibrary(); // to be safe
620 return (zlib_dll != 0);
626 PK3_GetEndOfCentralDir
628 Extract the end of the central directory from a PK3 package
631 static qbool PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd)
633 fs_offset_t filesize, maxsize;
634 unsigned char *buffer, *ptr;
637 // Get the package size
638 filesize = FILEDESC_SEEK (packhandle, 0, SEEK_END);
639 if (filesize < ZIP_END_CDIR_SIZE)
642 // Load the end of the file in memory
643 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
646 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
647 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
648 FILEDESC_SEEK (packhandle, filesize - maxsize, SEEK_SET);
649 if (FILEDESC_READ (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
655 // Look for the end of central dir signature around the end of the file
656 maxsize -= ZIP_END_CDIR_SIZE;
657 ptr = &buffer[maxsize];
659 while (BuffBigLong (ptr) != ZIP_END_HEADER)
671 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
672 eocd->signature = LittleLong (eocd->signature);
673 eocd->disknum = LittleShort (eocd->disknum);
674 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
675 eocd->localentries = LittleShort (eocd->localentries);
676 eocd->nbentries = LittleShort (eocd->nbentries);
677 eocd->cdir_size = LittleLong (eocd->cdir_size);
678 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
679 eocd->comment_size = LittleShort (eocd->comment_size);
680 eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
681 eocd->cdir_offset += eocd->prepended_garbage;
686 eocd->cdir_size > filesize ||
687 eocd->cdir_offset >= filesize ||
688 eocd->cdir_offset + eocd->cdir_size > filesize
691 // Obviously invalid central directory.
703 Extract the file list from a PK3 file
706 static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
708 unsigned char *central_dir, *ptr;
710 fs_offset_t remaining;
712 // Load the central directory in memory
713 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
714 if (FILEDESC_SEEK (pack->handle, eocd->cdir_offset, SEEK_SET) == -1)
716 Mem_Free (central_dir);
719 if(FILEDESC_READ (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
721 Mem_Free (central_dir);
725 // Extract the files properties
726 // The parsing is done "by hand" because some fields have variable sizes and
727 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
728 remaining = eocd->cdir_size;
731 for (ind = 0; ind < eocd->nbentries; ind++)
733 fs_offset_t namesize, count;
735 // Checking the remaining size
736 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
738 Mem_Free (central_dir);
741 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
744 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
746 Mem_Free (central_dir);
750 namesize = (unsigned short)BuffLittleShort (&ptr[28]); // filename length
752 // Check encryption, compression, and attributes
753 // 1st uint8 : general purpose bit flag
754 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
756 // LadyHavoc: bit 3 would be a problem if we were scanning the archive
757 // but is not a problem in the central directory where the values are
760 // bit 3 seems to always be set by the standard Mac OSX zip maker
762 // 2nd uint8 : external file attributes
763 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
764 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
766 // Still enough bytes for the name?
767 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
769 Mem_Free (central_dir);
773 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
774 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
776 char filename [sizeof (pack->files[0].name)];
777 fs_offset_t offset, packsize, realsize;
780 // Extract the name (strip it if necessary)
781 namesize = min(namesize, (int)sizeof (filename) - 1);
782 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
783 filename[namesize] = '\0';
785 if (BuffLittleShort (&ptr[10]))
786 flags = PACKFILE_FLAG_DEFLATED;
789 offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
790 packsize = (unsigned int)BuffLittleLong (&ptr[20]);
791 realsize = (unsigned int)BuffLittleLong (&ptr[24]);
793 switch(ptr[5]) // C_VERSION_MADE_BY_1
798 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
799 // can't use S_ISLNK here, as this has to compile on non-UNIX too
800 flags |= PACKFILE_FLAG_SYMLINK;
804 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
808 // Skip the name, additionnal field, and comment
809 // 1er uint16 : extra field length
810 // 2eme uint16 : file comment length
811 count = namesize + (unsigned short)BuffLittleShort (&ptr[30]) + (unsigned short)BuffLittleShort (&ptr[32]);
812 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
816 // If the package is empty, central_dir is NULL here
817 if (central_dir != NULL)
818 Mem_Free (central_dir);
819 return pack->numfiles;
827 Create a package entry associated with a PK3 file
830 static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qbool silent)
832 pk3_endOfCentralDir_t eocd;
836 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
839 Con_Printf ("%s is not a PK3 file\n", packfile);
840 FILEDESC_CLOSE(packhandle);
844 // Multi-volume ZIP archives are NOT allowed
845 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
847 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
848 FILEDESC_CLOSE(packhandle);
852 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
853 // since eocd.nbentries is an unsigned 16 bits integer
854 #if MAX_FILES_IN_PACK < 65535
855 if (eocd.nbentries > MAX_FILES_IN_PACK)
857 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
858 FILEDESC_CLOSE(packhandle);
863 // Create a package structure in memory
864 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
865 pack->ignorecase = true; // PK3 ignores case
866 dp_strlcpy (pack->filename, packfile, sizeof (pack->filename));
867 pack->handle = packhandle;
868 pack->numfiles = eocd.nbentries;
869 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
871 real_nb_files = PK3_BuildFileList (pack, &eocd);
872 if (real_nb_files < 0)
874 Con_Printf ("%s is not a valid PK3 file\n", packfile);
875 FILEDESC_CLOSE(pack->handle);
880 Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
884 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking);
885 static pack_t *FS_LoadPackPK3 (const char *packfile)
887 filedesc_t packhandle;
888 packhandle = FS_SysOpenFiledesc (packfile, "rb", false);
889 if (!FILEDESC_ISVALID(packhandle))
891 return FS_LoadPackPK3FromFD(packfile, packhandle, false);
897 PK3_GetTrueFileOffset
899 Find where the true file data offset is
902 static qbool PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
904 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
908 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
911 // Load the local file description
912 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
914 Con_Printf ("Can't seek in package %s\n", pack->filename);
917 count = FILEDESC_READ (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
918 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
920 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
924 // Skip name and extra field
925 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
927 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
933 =============================================================================
935 OTHER PRIVATE FUNCTIONS
937 =============================================================================
945 Add a file to the list of files contained into a package
948 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
949 fs_offset_t offset, fs_offset_t packsize,
950 fs_offset_t realsize, int flags)
952 int (*strcmp_funct) (const char* str1, const char* str2);
953 int left, right, middle;
956 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
958 // Look for the slot we should put that file into (binary search)
960 right = pack->numfiles - 1;
961 while (left <= right)
965 middle = (left + right) / 2;
966 diff = strcmp_funct (pack->files[middle].name, name);
968 // If we found the file, there's a problem
970 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
972 // If we're too far in the list
979 // We have to move the right of the list by one slot to free the one we need
980 pfile = &pack->files[left];
981 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
984 dp_strlcpy (pfile->name, name, sizeof (pfile->name));
985 pfile->offset = offset;
986 pfile->packsize = packsize;
987 pfile->realsize = realsize;
988 pfile->flags = flags;
995 static inline int wstrlen(wchar *wstr)
998 while (wstr[len] != 0 && len < WSTRBUF)
1002 #define widen(str, wstr) fromwtf8(str, strlen(str), wstr, WSTRBUF)
1003 #define narrow(wstr, str) towtf8(wstr, wstrlen(wstr), str, WSTRBUF)
1006 static void FS_mkdir (const char *path)
1009 wchar pathw[WSTRBUF] = {0};
1011 if(Sys_CheckParm("-readonly"))
1016 if (_wmkdir (pathw) == -1)
1018 if (mkdir (path, 0777) == -1)
1021 // No logging for this. The only caller is FS_CreatePath (which
1022 // calls it in ways that will intentionally produce EEXIST),
1023 // and its own callers always use the directory afterwards and
1024 // thus will detect failure that way.
1032 Only used for FS_OpenRealFile.
1035 void FS_CreatePath (char *path)
1039 for (ofs = path+1 ; *ofs ; ofs++)
1041 if (*ofs == '/' || *ofs == '\\')
1043 // create the directory
1059 static void FS_Path_f(cmd_state_t *cmd)
1063 Con_Print("Current search path:\n");
1064 for (s=fs_searchpaths ; s ; s=s->next)
1069 Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
1071 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
1074 Con_Printf("%s\n", s->filename);
1084 /*! Takes an explicit (not game tree related) path to a pak file.
1085 *Loads the header and directory, adding the files at the beginning
1086 *of the list so they override previous pack files.
1088 static pack_t *FS_LoadPackPAK (const char *packfile)
1090 dpackheader_t header;
1091 int i, numpackfiles;
1092 filedesc_t packhandle;
1096 packhandle = FS_SysOpenFiledesc(packfile, "rb", false);
1097 if (!FILEDESC_ISVALID(packhandle))
1099 if(FILEDESC_READ (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
1101 Con_Printf ("%s is not a packfile\n", packfile);
1102 FILEDESC_CLOSE(packhandle);
1105 if (memcmp(header.id, "PACK", 4))
1107 Con_Printf ("%s is not a packfile\n", packfile);
1108 FILEDESC_CLOSE(packhandle);
1111 header.dirofs = LittleLong (header.dirofs);
1112 header.dirlen = LittleLong (header.dirlen);
1114 if (header.dirlen % sizeof(dpackfile_t))
1116 Con_Printf ("%s has an invalid directory size\n", packfile);
1117 FILEDESC_CLOSE(packhandle);
1121 numpackfiles = header.dirlen / sizeof(dpackfile_t);
1123 if (numpackfiles < 0 || numpackfiles > MAX_FILES_IN_PACK)
1125 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
1126 FILEDESC_CLOSE(packhandle);
1130 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
1131 FILEDESC_SEEK (packhandle, header.dirofs, SEEK_SET);
1132 if(header.dirlen != FILEDESC_READ (packhandle, (void *)info, header.dirlen))
1134 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
1136 FILEDESC_CLOSE(packhandle);
1140 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1141 pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2
1142 dp_strlcpy (pack->filename, packfile, sizeof (pack->filename));
1143 pack->handle = packhandle;
1145 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
1147 // parse the directory
1148 for (i = 0;i < numpackfiles;i++)
1150 fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
1151 fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
1153 // Ensure a zero terminated file name (required by format).
1154 info[i].name[sizeof(info[i].name) - 1] = 0;
1156 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
1161 Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
1166 ====================
1169 Create a package entry associated with a directory file
1170 ====================
1172 static pack_t *FS_LoadPackVirtual (const char *dirname)
1175 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1177 pack->ignorecase = false;
1178 dp_strlcpy (pack->filename, dirname, sizeof(pack->filename));
1179 pack->handle = FILEDESC_INVALID;
1180 pack->numfiles = -1;
1182 Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
1191 /*! Adds the given pack to the search path.
1192 * The pack type is autodetected by the file extension.
1194 * Returns true if the file was successfully added to the
1195 * search path or if it was already included.
1197 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1198 * plain directories.
1201 static qbool FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
1203 searchpath_t *search;
1205 const char *ext = FS_FileExtension(pakfile);
1208 for(search = fs_searchpaths; search; search = search->next)
1210 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1213 *already_loaded = true;
1214 return true; // already loaded
1219 *already_loaded = false;
1221 if(!strcasecmp(ext, "pk3dir") || !strcasecmp(ext, "dpkdir"))
1222 pak = FS_LoadPackVirtual (pakfile);
1223 else if(!strcasecmp(ext, "pak"))
1224 pak = FS_LoadPackPAK (pakfile);
1225 else if(!strcasecmp(ext, "pk3") || !strcasecmp(ext, "dpk"))
1226 pak = FS_LoadPackPK3 (pakfile);
1227 else if(!strcasecmp(ext, "obb")) // android apk expansion
1228 pak = FS_LoadPackPK3 (pakfile);
1230 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1234 dp_strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1236 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
1239 // find the first item whose next one is a pack or NULL
1240 searchpath_t *insertion_point = 0;
1241 if(fs_searchpaths && !fs_searchpaths->pack)
1243 insertion_point = fs_searchpaths;
1246 if(!insertion_point->next)
1248 if(insertion_point->next->pack)
1250 insertion_point = insertion_point->next;
1253 // If insertion_point is NULL, this means that either there is no
1254 // item in the list yet, or that the very first item is a pack. In
1255 // that case, we want to insert at the beginning...
1256 if(!insertion_point)
1258 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1259 search->next = fs_searchpaths;
1260 fs_searchpaths = search;
1263 // otherwise we want to append directly after insertion_point.
1265 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1266 search->next = insertion_point->next;
1267 insertion_point->next = search;
1272 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1273 search->next = fs_searchpaths;
1274 fs_searchpaths = search;
1277 search->pack->dlcache = dlcache;
1280 dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1281 // if shortname ends with "pk3dir" or "dpkdir", strip that suffix to make it just "pk3" or "dpk"
1282 // same goes for the name inside the pack structure
1283 l = strlen(pak->shortname);
1285 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir") || !strcasecmp(pak->shortname + l - 7, ".dpkdir"))
1286 pak->shortname[l - 3] = 0;
1287 l = strlen(pak->filename);
1289 if(!strcasecmp(pak->filename + l - 7, ".pk3dir") || !strcasecmp(pak->filename + l - 7, ".dpkdir"))
1290 pak->filename[l - 3] = 0;
1296 Con_Printf(CON_ERROR "unable to load pak \"%s\"\n", pakfile);
1307 /*! Adds the given pack to the search path and searches for it in the game path.
1308 * The pack type is autodetected by the file extension.
1310 * Returns true if the file was successfully added to the
1311 * search path or if it was already included.
1313 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1314 * plain directories.
1316 qbool FS_AddPack(const char *pakfile, qbool *already_loaded, qbool keep_plain_dirs, qbool dlcache)
1318 char fullpath[MAX_OSPATH];
1320 searchpath_t *search;
1323 *already_loaded = false;
1325 // then find the real name...
1326 search = FS_FindFile(pakfile, &index, NULL, true);
1327 if(!search || search->pack)
1329 Con_Printf("could not find pak \"%s\"\n", pakfile);
1333 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1335 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs, dlcache);
1343 Sets fs_gamedir, adds the directory to the head of the path,
1344 then loads and adds pak1.pak pak2.pak ...
1347 static void FS_AddGameDirectory (const char *dir)
1351 searchpath_t *search;
1353 dp_strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1355 stringlistinit(&list);
1356 listdirectory(&list, "", dir);
1357 stringlistsort(&list, false);
1359 // add any PAK package in the directory
1360 for (i = 0;i < list.numstrings;i++)
1362 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1364 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
1368 // add any PK3 package in the directory
1369 for (i = 0;i < list.numstrings;i++)
1371 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir")
1372 || !strcasecmp(FS_FileExtension(list.strings[i]), "dpk") || !strcasecmp(FS_FileExtension(list.strings[i]), "dpkdir"))
1374 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false, false);
1378 stringlistfreecontents(&list);
1380 // Add the directory to the search path
1381 // (unpacked files have the priority over packed files)
1382 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1383 dp_strlcpy (search->filename, dir, sizeof (search->filename));
1384 search->next = fs_searchpaths;
1385 fs_searchpaths = search;
1394 static void FS_AddGameHierarchy (const char *dir)
1397 // Add the common game directory
1398 FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
1401 FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
1410 const char *FS_FileExtension (const char *in)
1412 const char *separator, *backslash, *colon, *dot;
1414 dot = strrchr(in, '.');
1418 separator = strrchr(in, '/');
1419 backslash = strrchr(in, '\\');
1420 if (!separator || separator < backslash)
1421 separator = backslash;
1422 colon = strrchr(in, ':');
1423 if (!separator || separator < colon)
1426 if (separator && (dot < separator))
1438 const char *FS_FileWithoutPath (const char *in)
1440 const char *separator, *backslash, *colon;
1442 separator = strrchr(in, '/');
1443 backslash = strrchr(in, '\\');
1444 if (!separator || separator < backslash)
1445 separator = backslash;
1446 colon = strrchr(in, ':');
1447 if (!separator || separator < colon)
1449 return separator ? separator + 1 : in;
1458 static void FS_ClearSearchPath (void)
1460 // unload all packs and directory information, close all pack files
1461 // (if a qfile is still reading a pack it won't be harmed because it used
1462 // dup() to get its own handle already)
1463 while (fs_searchpaths)
1465 searchpath_t *search = fs_searchpaths;
1466 fs_searchpaths = search->next;
1467 if (search->pack && search->pack != fs_selfpack)
1469 if(!search->pack->vpack)
1472 FILEDESC_CLOSE(search->pack->handle);
1473 // free any memory associated with it
1474 if (search->pack->files)
1475 Mem_Free(search->pack->files);
1477 Mem_Free(search->pack);
1485 FS_UnloadPacks_dlcache
1487 Like FS_ClearSearchPath() but unloads only the packs loaded from dlcache
1488 so we don't need to use a full FS_Rescan() to prevent
1489 content from the previous server and/or map from interfering with the next
1492 void FS_UnloadPacks_dlcache(void)
1494 searchpath_t *search = fs_searchpaths, *searchprev = fs_searchpaths, *searchnext;
1496 if (!fs_unload_dlcache.integer)
1501 searchnext = search->next;
1502 if (search->pack && search->pack->dlcache)
1504 Con_DPrintf("Unloading pack: %s\n", search->pack->shortname);
1506 // remove it from the search path list
1507 if (search == fs_searchpaths)
1508 fs_searchpaths = search->next;
1510 searchprev->next = search->next;
1513 FILEDESC_CLOSE(search->pack->handle);
1514 // free any memory associated with it
1515 if (search->pack->files)
1516 Mem_Free(search->pack->files);
1517 Mem_Free(search->pack);
1521 searchprev = search;
1522 search = searchnext;
1526 static void FS_AddSelfPack(void)
1530 searchpath_t *search;
1531 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1532 search->next = fs_searchpaths;
1533 search->pack = fs_selfpack;
1534 fs_searchpaths = search;
1544 static void FS_ListGameDirs(void);
1545 void FS_Rescan (void)
1548 char gamedirbuf[MAX_INPUTLINE];
1553 FS_ClearSearchPath();
1555 // update the com_modname (used for server info)
1556 if (gamedirname2 && gamedirname2[0])
1557 dp_strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1559 dp_strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1562 // Adds basedir/gamedir as an override game
1563 // LadyHavoc: now supports multiple -game directories
1564 // set the com_modname (reported in server info)
1566 for (i = 0;i < fs_numgamedirs;i++)
1568 FS_AddGameHierarchy (fs_gamedirs[i]);
1569 // update the com_modname (used server info)
1570 dp_strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1572 dp_strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1574 dp_strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1576 Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1578 // add back the selfpack as new first item
1581 if (cls.state != ca_dedicated)
1583 // set the default screenshot name to either the mod name or the
1584 // gamemode screenshot name
1585 if (strcmp(com_modname, gamedirname1))
1586 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1588 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1591 if((i = Sys_CheckParm("-modname")) && i < sys.argc - 1)
1592 dp_strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
1594 // If "-condebug" is in the command line, remove the previous log file
1595 if (Sys_CheckParm ("-condebug") != 0)
1596 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1598 // look for the pop.lmp file and set registered to true if it is found
1599 if (FS_FileExists("gfx/pop.lmp"))
1600 Cvar_SetValueQuick(®istered, 1);
1606 if (!registered.integer)
1608 if (fs_numgamedirs > 1)
1609 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1611 Con_Print("Playing shareware version.\n");
1614 Con_Print("Playing registered version.\n");
1616 case GAME_STEELSTORM:
1617 if (registered.integer)
1618 Con_Print("Playing registered version.\n");
1620 Con_Print("Playing shareware version.\n");
1626 // unload all wads so that future queries will return the new data
1630 static void FS_Rescan_f(cmd_state_t *cmd)
1640 addgamedirs_t FS_SetGameDirs(int numgamedirs, const char *gamedirs[], qbool failmissing, qbool abortonfail)
1644 const char *gamedirs_ok[MAX_GAMEDIRS + 2];
1647 // prepend the game-specific gamedirs (the primary and search order can be overriden)
1648 gamedirs_ok[0] = gamedirname1;
1650 if (gamedirname2 && gamedirname2[0])
1652 gamedirs_ok[1] = gamedirname2;
1656 // check the game-specific gamedirs
1657 for (i = 0; i < numgamedirs_ok; ++i)
1659 p = FS_CheckGameDir(gamedirs_ok[i]);
1661 Sys_Error("BUG: nasty gamedir name \"%s\" in gamemode_info", gamedirs_ok[i]);
1662 if(p == fs_checkgamedir_missing && failmissing)
1664 Con_Printf(abortonfail ? CON_ERROR : CON_WARN "Base gamedir \"%s\" empty or not found!\n", gamedirs_ok[i]);
1666 return GAMEDIRS_FAILURE; // missing gamedirs
1670 // copy and check the user-specified gamedirs
1671 for (i = 0; i < numgamedirs && (size_t)numgamedirs_ok < sizeof(gamedirs_ok) / sizeof(gamedirs_ok[0]); ++i)
1673 // remove any previously-added duplicate (last one wins)
1674 for (j = 0; j < numgamedirs_ok; ++j)
1675 if (!strcasecmp(gamedirs_ok[j], gamedirs[i]))
1678 for (k = j; k < numgamedirs_ok; ++k)
1679 gamedirs_ok[k] = gamedirs_ok[k + 1];
1682 // if string is nasty, reject it
1683 p = FS_CheckGameDir(gamedirs[i]);
1686 Con_Printf(abortonfail ? CON_ERROR : CON_WARN "Nasty gamedir name \"%s\" rejected\n", gamedirs[i]);
1688 return GAMEDIRS_FAILURE; // nasty gamedirs
1692 if(p == fs_checkgamedir_missing && failmissing)
1694 Con_Printf(abortonfail ? CON_ERROR : CON_WARN "Gamedir \"%s\" empty or not found!\n", gamedirs[i]);
1696 return GAMEDIRS_FAILURE; // missing gamedirs
1701 gamedirs_ok[numgamedirs_ok++] = gamedirs[i];
1704 if (fs_numgamedirs == numgamedirs_ok)
1706 for (i = 0;i < numgamedirs_ok;i++)
1707 if (strcasecmp(fs_gamedirs[i], gamedirs_ok[i]))
1709 if (i == numgamedirs_ok)
1710 return GAMEDIRS_ALLGOOD; // already using this set of gamedirs, do nothing
1713 if (numgamedirs_ok > MAX_GAMEDIRS)
1715 Con_Printf(abortonfail ? CON_ERROR : CON_WARN "That is too many gamedirs (%i > %i)\n", numgamedirs_ok, MAX_GAMEDIRS);
1717 return GAMEDIRS_FAILURE; // too many gamedirs
1720 for (i = 0, fs_numgamedirs = 0; i < numgamedirs_ok && fs_numgamedirs < MAX_GAMEDIRS; ++i)
1721 dp_strlcpy(fs_gamedirs[fs_numgamedirs++], gamedirs_ok[i], sizeof(fs_gamedirs[0]));
1723 return GAMEDIRS_SUCCESS;
1726 qbool FS_ChangeGameDirs(int numgamedirs, const char *gamedirs[], qbool failmissing)
1728 addgamedirs_t addresult = COM_ChangeGameTypeForGameDirs(numgamedirs, gamedirs, failmissing, false);
1730 if (addresult == GAMEDIRS_ALLGOOD)
1731 return true; // already using this set of gamedirs, do nothing
1732 else if (addresult == GAMEDIRS_FAILURE)
1735 Host_SaveConfig(CONFIGFILENAME);
1737 if (cls.demoplayback)
1739 cls.demonum = 0; // make sure startdemos will work if the mod uses it
1741 // unload all sounds so they will be reloaded from the new files as needed
1742 S_UnloadAllSounds_f(cmd_local);
1744 // reinitialize filesystem to detect the new paks
1747 // reload assets after the config is executed
1748 Cbuf_InsertText(cmd_local, "\nloadconfig\nr_restart\n");
1758 static void FS_GameDir_f(cmd_state_t *cmd)
1762 const char *gamedirs[MAX_GAMEDIRS];
1764 if (Cmd_Argc(cmd) < 2)
1766 Con_Printf("gamedirs active:");
1767 for (i = 0;i < fs_numgamedirs;i++)
1768 Con_Printf(" %s%s", (strcasecmp(fs_gamedirs[i], gamedirname1) && (!gamedirname2 || strcasecmp(fs_gamedirs[i], gamedirname2))) ? "^7" : "^9", fs_gamedirs[i]);
1773 numgamedirs = Cmd_Argc(cmd) - 1;
1774 if (numgamedirs > MAX_GAMEDIRS)
1776 Con_Printf(CON_ERROR "Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1780 for (i = 0;i < numgamedirs;i++)
1781 gamedirs[i] = Cmd_Argv(cmd, i+1);
1783 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1785 // actually, changing during game would work fine, but would be stupid
1786 Con_Printf(CON_ERROR "Can not change gamedir while client is connected or server is running!\n");
1790 FS_ChangeGameDirs(numgamedirs, gamedirs, true);
1793 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1801 stringlistinit(&list);
1802 listdirectory(&list, gamedir, "");
1803 success = list.numstrings > 0;
1804 stringlistfreecontents(&list);
1808 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1811 n = FS_Read (f, buf, buflength - 1);
1831 const char *FS_CheckGameDir(const char *gamedir)
1834 static char buf[8192];
1837 if (FS_CheckNastyPath(gamedir, true))
1840 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1845 // get description from basedir
1846 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1854 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1858 return fs_checkgamedir_missing;
1861 static void FS_ListGameDirs(void)
1863 stringlist_t list, list2;
1868 fs_all_gamedirs_count = 0;
1870 Mem_Free(fs_all_gamedirs);
1872 stringlistinit(&list);
1873 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1874 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1875 stringlistsort(&list, false);
1877 stringlistinit(&list2);
1878 for(i = 0; i < list.numstrings; ++i)
1881 if(!strcmp(list.strings[i-1], list.strings[i]))
1883 info = FS_CheckGameDir(list.strings[i]);
1886 if(info == fs_checkgamedir_missing)
1890 stringlistappend(&list2, list.strings[i]);
1892 stringlistfreecontents(&list);
1894 fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1895 for(i = 0; i < list2.numstrings; ++i)
1897 info = FS_CheckGameDir(list2.strings[i]);
1898 // all this cannot happen any more, but better be safe than sorry
1901 if(info == fs_checkgamedir_missing)
1905 dp_strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1906 dp_strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1907 ++fs_all_gamedirs_count;
1913 #pragma comment(lib, "shell32.lib")
1918 static void COM_InsertFlags(const char *buf) {
1921 const char **new_argv;
1923 int args_left = 256;
1924 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*sys.argv) * (sys.argc + args_left + 2));
1926 new_argv[0] = "dummy"; // Can't really happen.
1928 new_argv[0] = sys.argv[0];
1931 while(COM_ParseToken_Console(&p))
1933 size_t sz = strlen(com_token) + 1; // shut up clang
1936 q = (char *)Mem_Alloc(fs_mempool, sz);
1937 dp_strlcpy(q, com_token, sz);
1941 // Now: i <= args_left + 1.
1944 memcpy((char *)(&new_argv[i]), &sys.argv[1], sizeof(*sys.argv) * (sys.argc - 1));
1947 // Now: i <= args_left + (sys.argc || 1).
1949 sys.argv = new_argv;
1953 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1955 #if defined(__IPHONEOS__)
1956 if (userdirmode == USERDIRMODE_HOME)
1958 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1959 // fs_userdir stores configurations to the Documents folder of the app
1960 dp_strlcpy(userdir, "../Documents/", MAX_OSPATH);
1965 #elif defined(WIN32)
1966 char homedir[WSTRBUF];
1968 #if _MSC_VER >= 1400
1971 wchar_t mydocsdirw[WSTRBUF];
1972 char mydocsdir[WSTRBUF];
1973 wchar_t *savedgamesdirw;
1974 char savedgamesdir[WSTRBUF] = {0};
1983 case USERDIRMODE_NOHOME:
1984 dp_strlcpy(userdir, fs_basedir, userdirsize);
1986 case USERDIRMODE_MYGAMES:
1988 Sys_LoadDependency(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1990 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdirw) == S_OK)
1992 narrow(mydocsdirw, mydocsdir);
1993 dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1996 #if _MSC_VER >= 1400
1997 _wdupenv_s(&homedirw, &homedirwlen, L"USERPROFILE");
1998 narrow(homedirw, homedir);
2001 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
2006 homedirw = _wgetenv(L"USERPROFILE");
2007 narrow(homedirw, homedir);
2010 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
2015 case USERDIRMODE_SAVEDGAMES:
2017 Sys_LoadDependency(shell32dllnames, &shell32_dll, shell32funcs);
2019 Sys_LoadDependency(ole32dllnames, &ole32_dll, ole32funcs);
2020 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
2022 savedgamesdir[0] = 0;
2023 qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
2026 if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2028 if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2031 if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2033 narrow(savedgamesdirw, savedgamesdir);
2034 qCoTaskMemFree(savedgamesdirw);
2037 if (savedgamesdir[0])
2039 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
2054 case USERDIRMODE_NOHOME:
2055 dp_strlcpy(userdir, fs_basedir, userdirsize);
2057 case USERDIRMODE_HOME:
2058 homedir = getenv("HOME");
2061 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
2065 case USERDIRMODE_SAVEDGAMES:
2066 homedir = getenv("HOME");
2070 dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
2072 // the XDG say some files would need to go in:
2073 // XDG_CONFIG_HOME (or ~/.config/%s/)
2074 // XDG_DATA_HOME (or ~/.local/share/%s/)
2075 // XDG_CACHE_HOME (or ~/.cache/%s/)
2076 // and also search the following global locations if defined:
2077 // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
2078 // XDG_DATA_DIRS (normally /usr/share/%s/)
2079 // this would be too complicated...
2089 #if !defined(__IPHONEOS__)
2092 // historical behavior...
2093 if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
2094 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
2097 // see if we can write to this path (note: won't create path)
2099 // no access() here, we must try to open the file for appending
2100 fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
2104 // on Unix, we don't need to ACTUALLY attempt to open the file
2105 if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
2112 return 1; // good choice - the path exists and is writable
2116 if (userdirmode == USERDIRMODE_NOHOME)
2117 return -1; // path usually already exists, we lack permissions
2119 return 0; // probably good - failed to write but maybe we need to create path
2124 void FS_Init_Commands(void)
2126 Cvar_RegisterVariable (&scr_screenshot_name);
2127 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2128 Cvar_RegisterVariable (&fs_unload_dlcache);
2129 Cvar_RegisterVariable (&cvar_fs_gamedir);
2131 Cmd_AddCommand(CF_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list, can take multiple arguments which shouldn't include the base directory, the last gamedir is the \"primary\" and files will be saved there (example usage: gamedir ctf id1)");
2132 Cmd_AddCommand(CF_SHARED, "fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2133 Cmd_AddCommand(CF_SHARED, "path", FS_Path_f, "print searchpath (game directories and archives)");
2134 Cmd_AddCommand(CF_SHARED, "dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2135 Cmd_AddCommand(CF_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2136 Cmd_AddCommand(CF_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2139 static void FS_Init_Dir (void)
2143 const char *cmdline_gamedirs[MAX_GAMEDIRS];
2150 // Overrides the system supplied base directory (under GAMENAME)
2151 // 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)
2152 i = Sys_CheckParm ("-basedir");
2153 if (i && i < sys.argc-1)
2155 dp_strlcpy (fs_basedir, sys.argv[i+1], sizeof (fs_basedir));
2156 i = (int)strlen (fs_basedir);
2157 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2158 fs_basedir[i-1] = 0;
2162 // If the base directory is explicitly defined by the compilation process
2163 #ifdef DP_FS_BASEDIR
2164 dp_strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2165 #elif defined(__ANDROID__)
2166 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2167 #elif defined(MACOSX)
2168 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2169 if (strstr(sys.argv[0], ".app/"))
2172 dp_strlcpy(fs_basedir, sys.argv[0], sizeof(fs_basedir));
2173 split = strstr(fs_basedir, ".app/");
2176 struct stat statresult;
2178 // truncate to just after the .app/
2180 // see if gamedir exists in Resources
2181 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2183 // found gamedir inside Resources, use it
2184 dp_strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2188 // no gamedir found in Resources, gamedir is probably
2189 // outside the .app, remove .app part of path
2190 while (split > fs_basedir && *split != '/')
2197 // use the working directory
2198 getcwd(fs_basedir, sizeof(fs_basedir));
2202 // make sure the appending of a path separator won't create an unterminated string
2203 memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2204 // add a path separator to the end of the basedir if it lacks one
2205 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2206 dp_strlcat(fs_basedir, "/", sizeof(fs_basedir));
2208 // Add the personal game directory
2209 if((i = Sys_CheckParm("-userdir")) && i < sys.argc - 1)
2210 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", sys.argv[i+1]);
2211 else if (Sys_CheckParm("-nohome"))
2212 *fs_userdir = 0; // user wants roaming installation, no userdir
2215 #ifdef DP_FS_USERDIR
2216 dp_strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
2219 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2220 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2221 int userdirstatus[USERDIRMODE_COUNT];
2223 // historical behavior...
2224 if (!strcmp(gamedirname1, "id1"))
2225 preferreduserdirmode = USERDIRMODE_NOHOME;
2227 // check what limitations the user wants to impose
2228 if (Sys_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2229 if (Sys_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2230 if (Sys_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2231 // gather the status of the possible userdirs
2232 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2234 userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2235 if (userdirstatus[dirmode] == 1)
2236 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2237 else if (userdirstatus[dirmode] == 0)
2238 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2240 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2242 // some games may prefer writing to basedir, but if write fails we
2243 // have to search for a real userdir...
2244 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2245 preferreduserdirmode = highestuserdirmode;
2246 // check for an existing userdir and continue using it if possible...
2247 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2248 if (userdirstatus[dirmode] == 1)
2250 // if no existing userdir found, make a new one...
2251 if (dirmode == 0 && preferreduserdirmode > 0)
2252 for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2253 if (userdirstatus[dirmode] >= 0)
2255 // and finally, we picked one...
2256 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2257 Con_DPrintf("userdir %i is the winner\n", dirmode);
2261 // if userdir equal to basedir, clear it to avoid confusion later
2262 if (!strcmp(fs_basedir, fs_userdir))
2266 // Adds basedir/gamedir as an override game
2267 // LadyHavoc: now supports multiple -game directories
2268 // the last one is the primary (where files are saved) and is used to identify mods
2269 for (i = 1, numgamedirs = 0; i < sys.argc && numgamedirs < MAX_GAMEDIRS; i++)
2273 if (!strcmp (sys.argv[i], "-game") && i < sys.argc-1)
2276 cmdline_gamedirs[numgamedirs++] = sys.argv[i];
2279 COM_ChangeGameTypeForGameDirs(numgamedirs, cmdline_gamedirs, true, true);
2281 // generate the searchpath
2284 if (Thread_HasThreads())
2285 fs_mutex = Thread_CreateMutex();
2293 void FS_Init_SelfPack (void)
2297 // Load darkplaces.opt from the FS.
2298 if (!Sys_CheckParm("-noopt"))
2300 buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
2303 COM_InsertFlags(buf);
2309 // Provide the SelfPack.
2310 if (!Sys_CheckParm("-noselfpack") && sys.selffd >= 0)
2312 fs_selfpack = FS_LoadPackPK3FromFD(sys.argv[0], sys.selffd, true);
2316 if (!Sys_CheckParm("-noopt"))
2318 buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
2321 COM_InsertFlags(buf);
2338 fs_mempool = Mem_AllocPool("file management", 0, NULL);
2344 // initialize the self-pack (must be before COM_InitGameType as it may add command line options)
2347 // detect gamemode from commandline options or executable name
2358 void FS_Shutdown (void)
2360 // close all pack files and such
2361 // (hopefully there aren't any other open files, but they'll be cleaned up
2362 // by the OS anyway)
2363 FS_ClearSearchPath();
2364 Mem_FreePool (&fs_mempool);
2365 PK3_CloseLibrary ();
2368 Sys_FreeLibrary (&shfolder_dll);
2369 Sys_FreeLibrary (&shell32_dll);
2370 Sys_FreeLibrary (&ole32_dll);
2374 Thread_DestroyMutex(fs_mutex);
2377 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking)
2379 filedesc_t handle = FILEDESC_INVALID;
2382 qbool dolock = false;
2384 wchar filepathw[WSTRBUF] = {0};
2387 // Parse the mode string
2396 opt = O_CREAT | O_TRUNC;
2400 opt = O_CREAT | O_APPEND;
2403 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2404 return FILEDESC_INVALID;
2406 for (ind = 1; mode[ind] != '\0'; ind++)
2420 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2421 filepath, mode, mode[ind]);
2428 if(Sys_CheckParm("-readonly") && mod != O_RDONLY)
2429 return FILEDESC_INVALID;
2433 return FILEDESC_INVALID;
2434 handle = SDL_RWFromFile(filepath, mode);
2437 widen(filepath, filepathw);
2438 # if _MSC_VER >= 1400
2439 _wsopen_s(&handle, filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2441 handle = _wsopen (filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2444 handle = open (filepath, mod | opt, 0666);
2445 if(handle >= 0 && dolock)
2448 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2449 l.l_whence = SEEK_SET;
2452 if(fcntl(handle, F_SETLK, &l) == -1)
2454 FILEDESC_CLOSE(handle);
2464 int FS_SysOpenFD(const char *filepath, const char *mode, qbool nonblocking)
2469 return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2474 ====================
2477 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2478 ====================
2480 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qbool nonblocking)
2484 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2486 file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2487 if (!FILEDESC_ISVALID(file->handle))
2493 file->filename = Mem_strdup(fs_mempool, filepath);
2495 file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2497 // For files opened in append mode, we start at the end of the file
2499 file->position = file->real_length;
2501 FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2511 Open a packed file using its package file descriptor
2514 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2517 filedesc_t dup_handle;
2520 pfile = &pack->files[pack_ind];
2522 // If we don't have the true offset, get it now
2523 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2524 if (!PK3_GetTrueFileOffset (pfile, pack))
2527 #ifndef LINK_TO_ZLIB
2528 // No Zlib DLL = no compressed files
2529 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2531 Con_Printf(CON_WARN "WARNING: can't open the compressed file %s\n"
2532 "You need the Zlib DLL to use compressed files\n",
2538 // LadyHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2539 // the dup() call to avoid having to close the dup_handle on error here
2540 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2542 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2543 pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2547 dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2548 if (!FILEDESC_ISVALID(dup_handle))
2550 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2554 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2555 memset (file, 0, sizeof (*file));
2556 file->handle = dup_handle;
2557 file->flags = QFILE_FLAG_PACKED;
2558 file->real_length = pfile->realsize;
2559 file->offset = pfile->offset;
2563 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2567 file->flags |= QFILE_FLAG_DEFLATED;
2569 // We need some more variables
2570 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2572 ztk->comp_length = pfile->packsize;
2574 // Initialize zlib stream
2575 ztk->zstream.next_in = ztk->input;
2576 ztk->zstream.avail_in = 0;
2578 /* From Zlib's "unzip.c":
2580 * windowBits is passed < 0 to tell that there is no zlib header.
2581 * Note that in this case inflate *requires* an extra "dummy" byte
2582 * after the compressed stream in order to complete decompression and
2583 * return Z_STREAM_END.
2584 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2585 * size of both compressed and uncompressed data
2587 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2589 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2590 FILEDESC_CLOSE(dup_handle);
2595 ztk->zstream.next_out = file->buff;
2596 ztk->zstream.avail_out = sizeof (file->buff);
2605 ====================
2608 Return true if the path should be rejected due to one of the following:
2609 1: path elements that are non-portable
2610 2: path elements that would allow access to files outside the game directory,
2611 or are just not a good idea for a mod to be using.
2612 ====================
2614 int FS_CheckNastyPath (const char *path, qbool isgamedir)
2616 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2620 // Windows: don't allow \ in filenames (windows-only), period.
2621 // (on Windows \ is a directory separator, but / is also supported)
2622 if (strstr(path, "\\"))
2623 return 1; // non-portable
2625 // Mac: don't allow Mac-only filenames - : is a directory separator
2626 // instead of /, but we rely on / working already, so there's no reason to
2627 // support a Mac-only path
2628 // Amiga and Windows: : tries to go to root of drive
2629 if (strstr(path, ":"))
2630 return 1; // non-portable attempt to go to root of drive
2632 // Amiga: // is parent directory
2633 if (strstr(path, "//"))
2634 return 1; // non-portable attempt to go to parent directory
2636 // all: don't allow going to parent directory (../ or /../)
2637 if (strstr(path, ".."))
2638 return 2; // attempt to go outside the game directory
2640 // Windows and UNIXes: don't allow absolute paths
2642 return 2; // attempt to go outside the game directory
2644 // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2645 if (strstr(path, "./"))
2646 return 2; // possible attempt to go outside the game directory
2648 // all: forbid trailing slash on gamedir
2649 if (isgamedir && path[strlen(path)-1] == '/')
2652 // all: forbid leading dot on any filename for any reason
2653 if (strstr(path, "/."))
2654 return 2; // attempt to go outside the game directory
2656 // after all these checks we're pretty sure it's a / separated filename
2657 // and won't do much if any harm
2662 ====================
2665 Sanitize path (replace non-portable characters
2666 with portable ones in-place, etc)
2667 ====================
2669 void FS_SanitizePath(char *path)
2671 for (; *path; path++)
2677 ====================
2680 Look for a file in the packages and in the filesystem
2682 Return the searchpath where the file was found (or NULL)
2683 and the file index in the package if relevant
2684 ====================
2686 static searchpath_t *FS_FindFile (const char *name, int *index, const char **canonicalname, qbool quiet)
2688 searchpath_t *search;
2691 // search through the path, one element at a time
2692 for (search = fs_searchpaths;search;search = search->next)
2694 // is the element a pak file?
2695 if (search->pack && !search->pack->vpack)
2697 int (*strcmp_funct) (const char* str1, const char* str2);
2698 int left, right, middle;
2701 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2703 // Look for the file (binary search)
2705 right = pak->numfiles - 1;
2706 while (left <= right)
2710 middle = (left + right) / 2;
2711 diff = strcmp_funct (pak->files[middle].name, name);
2716 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2718 // yes, but the first one is empty so we treat it as not being there
2719 if (!quiet && developer_extra.integer)
2720 Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2725 *canonicalname = NULL;
2729 if (!quiet && developer_extra.integer)
2730 Con_DPrintf("FS_FindFile: %s in %s\n", pak->files[middle].name, pak->filename);
2735 *canonicalname = pak->files[middle].name;
2739 // If we're too far in the list
2748 char netpath[MAX_OSPATH];
2749 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2750 if (FS_SysFileExists (netpath))
2752 if (!quiet && developer_extra.integer)
2753 Con_DPrintf("FS_FindFile: %s\n", netpath);
2758 *canonicalname = name;
2764 if (!quiet && developer_extra.integer)
2765 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2770 *canonicalname = NULL;
2779 Look for a file in the search paths and open it in read-only mode
2782 static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblocking, int symlinkLevels)
2784 searchpath_t *search;
2787 search = FS_FindFile (filename, &pack_ind, NULL, quiet);
2793 // Found in the filesystem?
2796 // this works with vpacks, so we are fine
2797 char path [MAX_OSPATH];
2798 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2799 return FS_SysOpen (path, "rb", nonblocking);
2802 // So, we found it in a package...
2804 // Is it a PK3 symlink?
2805 // TODO also handle directory symlinks by parsing the whole structure...
2806 // but heck, file symlinks are good enough for now
2807 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2809 if(symlinkLevels <= 0)
2811 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2816 char linkbuf[MAX_QPATH];
2818 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2819 const char *mergeslash;
2824 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2830 // Now combine the paths...
2831 mergeslash = strrchr(filename, '/');
2832 mergestart = linkbuf;
2834 mergeslash = filename;
2835 while(!strncmp(mergestart, "../", 3))
2838 while(mergeslash > filename)
2841 if(*mergeslash == '/')
2845 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2846 if(mergeslash == filename)
2848 // Either mergeslash == filename, then we just replace the name (done below)
2852 // Or, we append the name after mergeslash;
2853 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2854 int spaceNeeded = mergeslash - filename + 1;
2855 int spaceRemoved = mergestart - linkbuf;
2856 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2858 Con_DPrintf("symlink: too long path rejected\n");
2861 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2862 memcpy(linkbuf, filename, spaceNeeded);
2863 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2864 mergestart = linkbuf;
2866 if (!quiet && developer_loading.integer)
2867 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2868 if(FS_CheckNastyPath (mergestart, false))
2870 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2873 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2877 return FS_OpenPackedFile (search->pack, pack_ind);
2882 =============================================================================
2884 MAIN PUBLIC FUNCTIONS
2886 =============================================================================
2890 ====================
2893 Open a file in the userpath. The syntax is the same as fopen
2894 Used for savegame scanning in menu, and all file writing.
2895 ====================
2897 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qbool quiet)
2899 char real_path [MAX_OSPATH];
2901 if (FS_CheckNastyPath(filepath, false))
2903 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2907 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2909 // If the file is opened in "write", "append", or "read/write" mode,
2910 // create directories up to the file.
2911 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2912 FS_CreatePath (real_path);
2913 return FS_SysOpen (real_path, mode, false);
2918 ====================
2921 Open a file. The syntax is the same as fopen
2922 ====================
2924 qfile_t* FS_OpenVirtualFile (const char* filepath, qbool quiet)
2926 qfile_t *result = NULL;
2927 if (FS_CheckNastyPath(filepath, false))
2929 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2933 if (fs_mutex) Thread_LockMutex(fs_mutex);
2934 result = FS_OpenReadFile (filepath, quiet, false, 16);
2935 if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2941 ====================
2944 Open a file. The syntax is the same as fopen
2945 ====================
2947 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qbool quiet)
2950 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2951 memset (file, 0, sizeof (*file));
2952 file->flags = QFILE_FLAG_DATA;
2954 file->real_length = size;
2960 ====================
2964 ====================
2966 int FS_Close (qfile_t* file)
2968 if(file->flags & QFILE_FLAG_DATA)
2974 if (FILEDESC_CLOSE (file->handle))
2979 if (file->flags & QFILE_FLAG_REMOVE)
2981 if (remove(file->filename) == -1)
2983 // No need to report this. If removing a just
2984 // written file failed, this most likely means
2985 // someone else deleted it first - which we
2990 Mem_Free((void *) file->filename);
2995 qz_inflateEnd (&file->ztk->zstream);
2996 Mem_Free (file->ztk);
3003 void FS_RemoveOnClose(qfile_t* file)
3005 file->flags |= QFILE_FLAG_REMOVE;
3009 ====================
3012 Write "datasize" bytes into a file
3013 ====================
3015 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
3017 fs_offset_t written = 0;
3019 // If necessary, seek to the exact file position we're supposed to be
3020 if (file->buff_ind != file->buff_len)
3022 if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
3024 Con_Printf(CON_WARN "WARNING: could not seek in %s.\n", file->filename);
3028 // Purge cached data
3031 // Write the buffer and update the position
3032 // 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)
3033 while (written < (fs_offset_t)datasize)
3035 // figure out how much to write in one chunk
3036 fs_offset_t maxchunk = 1<<30; // 1 GiB
3037 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
3038 int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
3039 // if at least some was written, add it to our accumulator
3042 // if the result is not what we expected, consider the write to be incomplete
3043 if (result != chunk)
3046 file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
3047 if (file->real_length < file->position)
3048 file->real_length = file->position;
3050 // note that this will never be less than 0 even if the write failed
3056 ====================
3059 Read up to "buffersize" bytes from a file
3060 ====================
3062 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
3064 fs_offset_t count, done;
3066 if (buffersize == 0 || !buffer)
3069 // Get rid of the ungetc character
3070 if (file->ungetc != EOF)
3072 ((char*)buffer)[0] = file->ungetc;
3080 if(file->flags & QFILE_FLAG_DATA)
3082 size_t left = file->real_length - file->position;
3083 if(buffersize > left)
3085 memcpy(buffer, file->data + file->position, buffersize);
3086 file->position += buffersize;
3090 // First, we copy as many bytes as we can from "buff"
3091 if (file->buff_ind < file->buff_len)
3093 count = file->buff_len - file->buff_ind;
3094 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
3096 memcpy (buffer, &file->buff[file->buff_ind], count);
3097 file->buff_ind += count;
3099 buffersize -= count;
3100 if (buffersize == 0)
3104 // NOTE: at this point, the read buffer is always empty
3106 // If the file isn't compressed
3107 if (! (file->flags & QFILE_FLAG_DEFLATED))
3111 // We must take care to not read after the end of the file
3112 count = file->real_length - file->position;
3114 // If we have a lot of data to get, put them directly into "buffer"
3115 if (buffersize > sizeof (file->buff) / 2)
3117 if (count > (fs_offset_t)buffersize)
3118 count = (fs_offset_t)buffersize;
3119 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3121 // Seek failed. When reading from a pipe, and
3122 // the caller never called FS_Seek, this still
3123 // works fine. So no reporting this error.
3125 nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
3129 file->position += nb;
3131 // Purge cached data
3137 if (count > (fs_offset_t)sizeof (file->buff))
3138 count = (fs_offset_t)sizeof (file->buff);
3139 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3141 // Seek failed. When reading from a pipe, and
3142 // the caller never called FS_Seek, this still
3143 // works fine. So no reporting this error.
3145 nb = FILEDESC_READ (file->handle, file->buff, count);
3148 file->buff_len = nb;
3149 file->position += nb;
3151 // Copy the requested data in "buffer" (as much as we can)
3152 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3153 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3154 file->buff_ind = count;
3162 // If the file is compressed, it's more complicated...
3163 // We cycle through a few operations until we have read enough data
3164 while (buffersize > 0)
3166 ztoolkit_t *ztk = file->ztk;
3169 // NOTE: at this point, the read buffer is always empty
3171 // If "input" is also empty, we need to refill it
3172 if (ztk->in_ind == ztk->in_len)
3174 // If we are at the end of the file
3175 if (file->position == file->real_length)
3178 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
3179 if (count > (fs_offset_t)sizeof (ztk->input))
3180 count = (fs_offset_t)sizeof (ztk->input);
3181 FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
3182 if (FILEDESC_READ (file->handle, ztk->input, count) != count)
3184 Con_Printf ("FS_Read: unexpected end of file\n");
3189 ztk->in_len = count;
3190 ztk->in_position += count;
3193 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3194 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3196 // Now that we are sure we have compressed data available, we need to determine
3197 // if it's better to inflate it in "file->buff" or directly in "buffer"
3199 // Inflate the data in "file->buff"
3200 if (buffersize < sizeof (file->buff) / 2)
3202 ztk->zstream.next_out = file->buff;
3203 ztk->zstream.avail_out = sizeof (file->buff);
3204 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3205 if (error != Z_OK && error != Z_STREAM_END)
3207 Con_Printf ("FS_Read: Can't inflate file\n");
3210 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3212 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3213 file->position += file->buff_len;
3215 // Copy the requested data in "buffer" (as much as we can)
3216 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3217 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3218 file->buff_ind = count;
3221 // Else, we inflate directly in "buffer"
3224 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3225 ztk->zstream.avail_out = (unsigned int)buffersize;
3226 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3227 if (error != Z_OK && error != Z_STREAM_END)
3229 Con_Printf ("FS_Read: Can't inflate file\n");
3232 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3234 // How much data did it inflate?
3235 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3236 file->position += count;
3238 // Purge cached data
3243 buffersize -= count;
3251 ====================
3254 Print a string into a file
3255 ====================
3257 int FS_Print (qfile_t* file, const char *msg)
3259 return (int)FS_Write (file, msg, strlen (msg));
3263 ====================
3266 Print a string into a file
3267 ====================
3269 int FS_Printf(qfile_t* file, const char* format, ...)
3274 va_start (args, format);
3275 result = FS_VPrintf (file, format, args);
3283 ====================
3286 Print a string into a file
3287 ====================
3289 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3292 fs_offset_t buff_size = MAX_INPUTLINE;
3297 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3298 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3299 if (len >= 0 && len < buff_size)
3301 Mem_Free (tempbuff);
3305 len = FILEDESC_WRITE (file->handle, tempbuff, len);
3306 Mem_Free (tempbuff);
3313 ====================
3316 Get the next character of a file
3317 ====================
3319 int FS_Getc (qfile_t* file)
3323 if (FS_Read (file, &c, 1) != 1)
3331 ====================
3334 Put a character back into the read buffer (only supports one character!)
3335 ====================
3337 int FS_UnGetc (qfile_t* file, unsigned char c)
3339 // If there's already a character waiting to be read
3340 if (file->ungetc != EOF)
3349 ====================
3352 Move the position index in a file
3353 ====================
3355 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3358 unsigned char* buffer;
3359 fs_offset_t buffersize;
3361 // Compute the file offset
3365 offset += file->position - file->buff_len + file->buff_ind;
3372 offset += file->real_length;
3378 if (offset < 0 || offset > file->real_length)
3381 if(file->flags & QFILE_FLAG_DATA)
3383 file->position = offset;
3387 // If we have the data in our read buffer, we don't need to actually seek
3388 if (file->position - file->buff_len <= offset && offset <= file->position)
3390 file->buff_ind = offset + file->buff_len - file->position;
3394 // Purge cached data
3397 // Unpacked or uncompressed files can seek directly
3398 if (! (file->flags & QFILE_FLAG_DEFLATED))
3400 if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3402 file->position = offset;
3406 // Seeking in compressed files is more a hack than anything else,
3407 // but we need to support it, so here we go.
3410 // If we have to go back in the file, we need to restart from the beginning
3411 if (offset <= file->position)
3415 ztk->in_position = 0;
3417 if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3418 Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3420 // Reset the Zlib stream
3421 ztk->zstream.next_in = ztk->input;
3422 ztk->zstream.avail_in = 0;
3423 qz_inflateReset (&ztk->zstream);
3426 // We need a big buffer to force inflating into it directly
3427 buffersize = 2 * sizeof (file->buff);
3428 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3430 // Skip all data until we reach the requested offset
3431 while (offset > (file->position - file->buff_len + file->buff_ind))
3433 fs_offset_t diff = offset - (file->position - file->buff_len + file->buff_ind);
3434 fs_offset_t count, len;
3436 count = (diff > buffersize) ? buffersize : diff;
3437 len = FS_Read (file, buffer, count);
3451 ====================
3454 Give the current position in a file
3455 ====================
3457 fs_offset_t FS_Tell (qfile_t* file)
3459 return file->position - file->buff_len + file->buff_ind;
3464 ====================
3467 Give the total size of a file
3468 ====================
3470 fs_offset_t FS_FileSize (qfile_t* file)
3472 return file->real_length;
3477 ====================
3480 Erases any buffered input or output data
3481 ====================
3483 void FS_Purge (qfile_t* file)
3493 FS_LoadAndCloseQFile
3495 Loads full content of a qfile_t and closes it.
3496 Always appends a 0 byte.
3499 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3501 unsigned char *buf = NULL;
3502 fs_offset_t filesize = 0;
3506 filesize = file->real_length;
3509 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3514 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3515 buf[filesize] = '\0';
3516 FS_Read (file, buf, filesize);
3518 if (developer_loadfile.integer)
3519 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3522 if (filesizepointer)
3523 *filesizepointer = filesize;
3532 Filename are relative to the quake directory.
3533 Always appends a 0 byte.
3536 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3538 qfile_t *file = FS_OpenVirtualFile(path, quiet);
3539 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3547 Filename are OS paths.
3548 Always appends a 0 byte.
3551 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3553 qfile_t *file = FS_SysOpen(path, "rb", false);
3554 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3562 The filename will be prefixed by the current game directory
3565 qbool FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3569 fs_offset_t lentotal;
3571 file = FS_OpenRealFile(filename, "wb", false);
3574 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3579 for(i = 0; i < count; ++i)
3581 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3582 for(i = 0; i < count; ++i)
3583 FS_Write (file, data[i], len[i]);
3588 qbool FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3590 return FS_WriteFileInBlocks(filename, &data, &len, 1);
3595 =============================================================================
3597 OTHERS PUBLIC FUNCTIONS
3599 =============================================================================
3607 void FS_StripExtension (const char *in, char *out, size_t size_out)
3615 while ((currentchar = *in) && size_out > 1)
3617 if (currentchar == '.')
3619 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3621 *out++ = currentchar;
3637 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3641 // if path doesn't have a .EXT, append extension
3642 // (extension should include the .)
3643 src = path + strlen(path);
3645 while (*src != '/' && src != path)
3648 return; // it has an extension
3652 dp_strlcat (path, extension, size_path);
3660 Look for a file in the packages and in the filesystem
3663 int FS_FileType (const char *filename)
3665 searchpath_t *search;
3666 char fullpath[MAX_OSPATH];
3668 search = FS_FindFile (filename, NULL, NULL, true);
3670 return FS_FILETYPE_NONE;
3672 if(search->pack && !search->pack->vpack)
3673 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3675 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3676 return FS_SysFileType(fullpath);
3684 Look for a file in the packages and in the filesystem
3685 Returns its canonical name (VFS path with correct capitalisation) if found, else NULL.
3686 If the file is found outside a pak, this will be the same pointer as passed in.
3689 const char *FS_FileExists (const char *filename)
3691 const char *canonicalname;
3693 return FS_FindFile(filename, NULL, &canonicalname, true) ? canonicalname : NULL;
3701 Look for a file in the filesystem only
3704 int FS_SysFileType (const char *path)
3707 // Sajt - some older sdks are missing this define
3708 # ifndef INVALID_FILE_ATTRIBUTES
3709 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3711 wchar pathw[WSTRBUF] = {0};
3714 result = GetFileAttributesW(pathw);
3716 if(result == INVALID_FILE_ATTRIBUTES)
3717 return FS_FILETYPE_NONE;
3719 if(result & FILE_ATTRIBUTE_DIRECTORY)
3720 return FS_FILETYPE_DIRECTORY;
3722 return FS_FILETYPE_FILE;
3726 if (stat (path,&buf) == -1)
3727 return FS_FILETYPE_NONE;
3730 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3732 if(S_ISDIR(buf.st_mode))
3733 return FS_FILETYPE_DIRECTORY;
3735 return FS_FILETYPE_FILE;
3739 qbool FS_SysFileExists (const char *path)
3741 return FS_SysFileType (path) != FS_FILETYPE_NONE;
3748 Allocate and fill a search structure with information on matching filenames.
3751 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const char *packfile)
3754 searchpath_t *searchpath;
3756 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3757 stringlist_t resultlist;
3758 stringlist_t dirlist;
3759 stringlist_t matchedSet, foundSet;
3760 const char *start, *slash, *backslash, *colon, *separator;
3763 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3768 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3772 stringlistinit(&resultlist);
3773 stringlistinit(&dirlist);
3775 slash = strrchr(pattern, '/');
3776 backslash = strrchr(pattern, '\\');
3777 colon = strrchr(pattern, ':');
3778 separator = max(slash, backslash);
3779 separator = max(separator, colon);
3780 basepathlength = separator ? (separator + 1 - pattern) : 0;
3781 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3783 memcpy(basepath, pattern, basepathlength);
3784 basepath[basepathlength] = 0;
3786 // search through the path, one element at a time
3787 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3789 // is the element a pak file?
3790 if (searchpath->pack && !searchpath->pack->vpack)
3792 // look through all the pak file elements
3793 pak = searchpath->pack;
3796 if(strcmp(packfile, pak->shortname))
3799 for (i = 0;i < pak->numfiles;i++)
3801 char temp[MAX_OSPATH];
3802 dp_strlcpy(temp, pak->files[i].name, sizeof(temp));
3805 if (matchpattern(temp, (char *)pattern, true))
3807 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3808 if (!strcmp(resultlist.strings[resultlistindex], temp))
3810 if (resultlistindex == resultlist.numstrings)
3812 stringlistappend(&resultlist, temp);
3813 if (!quiet && developer_loading.integer)
3814 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3817 // strip off one path element at a time until empty
3818 // this way directories are added to the listing if they match the pattern
3819 slash = strrchr(temp, '/');
3820 backslash = strrchr(temp, '\\');
3821 colon = strrchr(temp, ':');
3823 if (separator < slash)
3825 if (separator < backslash)
3826 separator = backslash;
3827 if (separator < colon)
3829 *((char *)separator) = 0;
3840 stringlistinit(&matchedSet);
3841 stringlistinit(&foundSet);
3842 // add a first entry to the set
3843 stringlistappend(&matchedSet, "");
3844 // iterate through pattern's path
3847 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3848 char subpath[MAX_OSPATH];
3849 char subpattern[MAX_OSPATH];
3851 // find the next wildcard
3852 wildcard = strchr(start, '?');
3853 asterisk = strchr(start, '*');
3854 if (asterisk && (!wildcard || asterisk < wildcard))
3856 wildcard = asterisk;
3861 nextseparator = strchr( wildcard, '/' );
3865 nextseparator = NULL;
3868 if( !nextseparator ) {
3869 nextseparator = start + strlen( start );
3872 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3873 // copy everything up except nextseperator
3874 dp_ustr2stp(subpattern, sizeof(subpattern), pattern, nextseparator - pattern);
3875 // find the last '/' before the wildcard
3876 prevseparator = strrchr( subpattern, '/' );
3878 prevseparator = subpattern;
3881 // copy everything from start to the previous including the '/' (before the wildcard)
3882 // everything up to start is already included in the path of matchedSet's entries
3883 dp_ustr2stp(subpath, sizeof(subpath), start, (prevseparator - subpattern) - (start - pattern));
3885 // for each entry in matchedSet try to open the subdirectories specified in subpath
3886 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3887 char temp[MAX_OSPATH];
3888 dp_strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3889 dp_strlcat( temp, subpath, sizeof(temp) );
3890 listdirectory( &foundSet, searchpath->filename, temp );
3892 if( dirlistindex == 0 ) {
3895 // reset the current result set
3896 stringlistfreecontents( &matchedSet );
3897 // match against the pattern
3898 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3899 const char *direntry = foundSet.strings[ dirlistindex ];
3900 if (matchpattern(direntry, subpattern, true)) {
3901 stringlistappend( &matchedSet, direntry );
3904 stringlistfreecontents( &foundSet );
3906 start = nextseparator;
3909 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3911 const char *matchtemp = matchedSet.strings[dirlistindex];
3912 if (matchpattern(matchtemp, (char *)pattern, true))
3914 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3915 if (!strcmp(resultlist.strings[resultlistindex], matchtemp))
3917 if (resultlistindex == resultlist.numstrings)
3919 stringlistappend(&resultlist, matchtemp);
3920 if (!quiet && developer_loading.integer)
3921 Con_Printf("SearchDirFile: %s\n", matchtemp);
3925 stringlistfreecontents( &matchedSet );
3929 if (resultlist.numstrings)
3931 stringlistsort(&resultlist, true);
3932 numfiles = resultlist.numstrings;
3934 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3935 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3936 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3937 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3938 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3939 search->numfilenames = (int)numfiles;
3942 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3945 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3946 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3947 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3949 numchars += (int)textlen;
3952 stringlistfreecontents(&resultlist);
3958 void FS_FreeSearch(fssearch_t *search)
3963 extern int con_linewidth;
3964 static int FS_ListDirectory(const char *pattern, int oneperline)
3973 char linebuf[MAX_INPUTLINE];
3975 search = FS_Search(pattern, true, true, NULL);
3978 numfiles = search->numfilenames;
3981 // FIXME: the names could be added to one column list and then
3982 // gradually shifted into the next column if they fit, and then the
3983 // next to make a compact variable width listing but it's a lot more
3985 // find width for columns
3987 for (i = 0;i < numfiles;i++)
3989 l = (int)strlen(search->filenames[i]);
3990 if (columnwidth < l)
3993 // count the spacing character
3995 // calculate number of columns
3996 numcolumns = con_linewidth / columnwidth;
3997 // don't bother with the column printing if it's only one column
3998 if (numcolumns >= 2)
4000 numlines = (numfiles + numcolumns - 1) / numcolumns;
4001 for (i = 0;i < numlines;i++)
4004 for (k = 0;k < numcolumns;k++)
4006 l = i * numcolumns + k;
4009 name = search->filenames[l];
4010 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
4011 linebuf[linebufpos++] = name[j];
4012 // space out name unless it's the last on the line
4013 if (k + 1 < numcolumns && l + 1 < numfiles)
4014 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
4015 linebuf[linebufpos++] = ' ';
4018 linebuf[linebufpos] = 0;
4019 Con_Printf("%s\n", linebuf);
4026 for (i = 0;i < numfiles;i++)
4027 Con_Printf("%s\n", search->filenames[i]);
4028 FS_FreeSearch(search);
4029 return (int)numfiles;
4032 static void FS_ListDirectoryCmd (cmd_state_t *cmd, const char* cmdname, int oneperline)
4034 const char *pattern;
4035 if (Cmd_Argc(cmd) >= 3)
4037 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
4040 if (Cmd_Argc(cmd) == 2)
4041 pattern = Cmd_Argv(cmd, 1);
4044 if (!FS_ListDirectory(pattern, oneperline))
4045 Con_Print("No files found.\n");
4048 void FS_Dir_f(cmd_state_t *cmd)
4050 FS_ListDirectoryCmd(cmd, "dir", true);
4053 void FS_Ls_f(cmd_state_t *cmd)
4055 FS_ListDirectoryCmd(cmd, "ls", false);
4058 void FS_Which_f(cmd_state_t *cmd)
4060 const char *filename;
4063 if (Cmd_Argc(cmd) != 2)
4065 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(cmd, 0));
4068 filename = Cmd_Argv(cmd, 1);
4069 sp = FS_FindFile(filename, &index, NULL, true);
4071 Con_Printf("%s isn't anywhere\n", filename);
4077 Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
4079 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
4082 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
4086 const char *FS_WhichPack(const char *filename)
4089 searchpath_t *sp = FS_FindFile(filename, &index, NULL, true);
4091 return sp->pack->shortname;
4099 ====================
4100 FS_IsRegisteredQuakePack
4102 Look for a proof of purchase file file in the requested package
4104 If it is found, this file should NOT be downloaded.
4105 ====================
4107 qbool FS_IsRegisteredQuakePack(const char *name)
4109 searchpath_t *search;
4112 // search through the path, one element at a time
4113 for (search = fs_searchpaths;search;search = search->next)
4115 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
4116 // TODO do we want to support vpacks in here too?
4118 int (*strcmp_funct) (const char* str1, const char* str2);
4119 int left, right, middle;
4122 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
4124 // Look for the file (binary search)
4126 right = pak->numfiles - 1;
4127 while (left <= right)
4131 middle = (left + right) / 2;
4132 diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
4138 // If we're too far in the list
4145 // we found the requested pack but it is not registered quake
4153 int FS_CRCFile(const char *filename, size_t *filesizepointer)
4156 unsigned char *filedata;
4157 fs_offset_t filesize;
4158 if (filesizepointer)
4159 *filesizepointer = 0;
4160 if (!filename || !*filename)
4162 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
4165 if (filesizepointer)
4166 *filesizepointer = filesize;
4167 crc = CRC_Block(filedata, filesize);
4173 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
4176 unsigned char *out = NULL;
4180 #ifndef LINK_TO_ZLIB
4185 memset(&strm, 0, sizeof(strm));
4186 strm.zalloc = Z_NULL;
4187 strm.zfree = Z_NULL;
4188 strm.opaque = Z_NULL;
4191 level = Z_DEFAULT_COMPRESSION;
4193 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
4195 Con_Printf("FS_Deflate: deflate init error!\n");
4199 strm.next_in = (unsigned char*)data;
4200 strm.avail_in = (unsigned int)size;
4202 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
4205 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4206 qz_deflateEnd(&strm);
4210 strm.next_out = tmp;
4211 strm.avail_out = (unsigned int)size;
4213 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4215 Con_Printf("FS_Deflate: deflate failed!\n");
4216 qz_deflateEnd(&strm);
4221 if(qz_deflateEnd(&strm) != Z_OK)
4223 Con_Printf("FS_Deflate: deflateEnd failed\n");
4228 if(strm.total_out >= size)
4230 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4235 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4238 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4243 *deflated_size = (size_t)strm.total_out;
4245 memcpy(out, tmp, strm.total_out);
4251 static void AssertBufsize(sizebuf_t *buf, int length)
4253 if(buf->cursize + length > buf->maxsize)
4255 int oldsize = buf->maxsize;
4256 unsigned char *olddata;
4257 olddata = buf->data;
4258 buf->maxsize += length;
4259 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4262 memcpy(buf->data, olddata, oldsize);
4268 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4272 unsigned char *out = NULL;
4273 unsigned char tmp[2048];
4278 #ifndef LINK_TO_ZLIB
4283 memset(&outbuf, 0, sizeof(outbuf));
4284 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4285 outbuf.maxsize = sizeof(tmp);
4287 memset(&strm, 0, sizeof(strm));
4288 strm.zalloc = Z_NULL;
4289 strm.zfree = Z_NULL;
4290 strm.opaque = Z_NULL;
4292 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4294 Con_Printf("FS_Inflate: inflate init error!\n");
4295 Mem_Free(outbuf.data);
4299 strm.next_in = (unsigned char*)data;
4300 strm.avail_in = (unsigned int)size;
4304 strm.next_out = tmp;
4305 strm.avail_out = sizeof(tmp);
4306 ret = qz_inflate(&strm, Z_NO_FLUSH);
4307 // it either returns Z_OK on progress, Z_STREAM_END on end
4315 case Z_STREAM_ERROR:
4316 Con_Print("FS_Inflate: stream error!\n");
4319 Con_Print("FS_Inflate: data error!\n");
4322 Con_Print("FS_Inflate: mem error!\n");
4325 Con_Print("FS_Inflate: buf error!\n");
4328 Con_Print("FS_Inflate: unknown error!\n");
4332 if(ret != Z_OK && ret != Z_STREAM_END)
4334 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4335 Mem_Free(outbuf.data);
4336 qz_inflateEnd(&strm);
4339 have = sizeof(tmp) - strm.avail_out;
4340 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4341 SZ_Write(&outbuf, tmp, have);
4342 } while(ret != Z_STREAM_END);
4344 qz_inflateEnd(&strm);
4346 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4349 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4350 Mem_Free(outbuf.data);
4354 memcpy(out, outbuf.data, outbuf.cursize);
4355 Mem_Free(outbuf.data);
4357 *inflated_size = (size_t)outbuf.cursize;