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 hierchal file system, but 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 The game directory can never be changed while quake is executing. This is a
187 precaution against having a malicious server instruct clients to write files
188 over areas they shouldn't.
194 =============================================================================
198 =============================================================================
201 // Magic numbers of a ZIP file (big-endian format)
202 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
203 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
204 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
206 // Other constants for ZIP files
207 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
208 #define ZIP_END_CDIR_SIZE 22
209 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
210 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
215 #define qz_inflate inflate
216 #define qz_inflateEnd inflateEnd
217 #define qz_inflateInit2_ inflateInit2_
218 #define qz_inflateReset inflateReset
219 #define qz_deflateInit2_ deflateInit2_
220 #define qz_deflateEnd deflateEnd
221 #define qz_deflate deflate
222 #define Z_MEMLEVEL_DEFAULT 8
225 // Zlib constants (from zlib.h)
226 #define Z_SYNC_FLUSH 2
229 #define Z_STREAM_END 1
230 #define Z_STREAM_ERROR (-2)
231 #define Z_DATA_ERROR (-3)
232 #define Z_MEM_ERROR (-4)
233 #define Z_BUF_ERROR (-5)
234 #define ZLIB_VERSION "1.2.3"
238 #define Z_MEMLEVEL_DEFAULT 8
241 #define Z_DEFAULT_COMPRESSION (-1)
243 #define Z_SYNC_FLUSH 2
244 #define Z_FULL_FLUSH 3
247 // Uncomment the following line if the zlib DLL you have still uses
248 // the 1.1.x series calling convention on Win32 (WINAPI)
249 //#define ZLIB_USES_WINAPI
253 =============================================================================
257 =============================================================================
260 /*! Zlib stream (from zlib.h)
261 * \warning: some pointers we don't use directly have
262 * been cast to "void*" for a matter of simplicity
266 unsigned char *next_in; ///< next input byte
267 unsigned int avail_in; ///< number of bytes available at next_in
268 unsigned long total_in; ///< total nb of input bytes read so far
270 unsigned char *next_out; ///< next output byte should be put there
271 unsigned int avail_out; ///< remaining free space at next_out
272 unsigned long total_out; ///< total nb of bytes output so far
274 char *msg; ///< last error message, NULL if no error
275 void *state; ///< not visible by applications
277 void *zalloc; ///< used to allocate the internal state
278 void *zfree; ///< used to free the internal state
279 void *opaque; ///< private data object passed to zalloc and zfree
281 int data_type; ///< best guess about the data type: ascii or binary
282 unsigned long adler; ///< adler32 value of the uncompressed data
283 unsigned long reserved; ///< reserved for future use
288 /// inside a package (PAK or PK3)
289 #define QFILE_FLAG_PACKED (1 << 0)
290 /// file is compressed using the deflate algorithm (PK3 only)
291 #define QFILE_FLAG_DEFLATED (1 << 1)
292 /// file is actually already loaded data
293 #define QFILE_FLAG_DATA (1 << 2)
294 /// real file will be removed on close
295 #define QFILE_FLAG_REMOVE (1 << 3)
297 #define FILE_BUFF_SIZE 2048
301 size_t comp_length; ///< length of the compressed file
302 size_t in_ind, in_len; ///< input buffer current index and length
303 size_t in_position; ///< position in the compressed file
304 unsigned char input [FILE_BUFF_SIZE];
310 filedesc_t handle; ///< file descriptor
311 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
312 fs_offset_t position; ///< current position in the file
313 fs_offset_t offset; ///< offset into the package (0 if external file)
314 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
317 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
318 unsigned char buff [FILE_BUFF_SIZE];
320 ztoolkit_t* ztk; ///< For zipped files.
322 const unsigned char *data; ///< For data files.
324 const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
328 // ------ PK3 files on disk ------ //
330 // You can get the complete ZIP format description from PKWARE website
332 typedef struct pk3_endOfCentralDir_s
334 unsigned int signature;
335 unsigned short disknum;
336 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
337 unsigned short localentries; ///< number of entries in the central directory on this disk
338 unsigned short nbentries; ///< total number of entries in the central directory on this disk
339 unsigned int cdir_size; ///< size of the central directory
340 unsigned int cdir_offset; ///< with respect to the starting disk number
341 unsigned short comment_size;
342 fs_offset_t prepended_garbage;
343 } pk3_endOfCentralDir_t;
346 // ------ PAK files on disk ------ //
347 typedef struct dpackfile_s
350 int filepos, filelen;
353 typedef struct dpackheader_s
361 /*! \name Packages in memory
364 /// the offset in packfile_t is the true contents offset
365 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
366 /// file compressed using the deflate algorithm
367 #define PACKFILE_FLAG_DEFLATED (1 << 1)
368 /// file is a symbolic link
369 #define PACKFILE_FLAG_SYMLINK (1 << 2)
371 typedef struct packfile_s
373 char name [MAX_QPATH];
376 fs_offset_t packsize; ///< size in the package
377 fs_offset_t realsize; ///< real file size (uncompressed)
380 typedef struct pack_s
382 char filename [MAX_OSPATH];
383 char shortname [MAX_QPATH];
385 int ignorecase; ///< PK3 ignores case
393 /// Search paths for files (including packages)
394 typedef struct searchpath_s
396 // only one of filename / pack will be used
397 char filename[MAX_OSPATH];
399 struct searchpath_s *next;
404 =============================================================================
408 =============================================================================
411 void FS_Dir_f(cmd_state_t *cmd);
412 void FS_Ls_f(cmd_state_t *cmd);
413 void FS_Which_f(cmd_state_t *cmd);
415 static searchpath_t *FS_FindFile (const char *name, int* index, qbool quiet);
416 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
417 fs_offset_t offset, fs_offset_t packsize,
418 fs_offset_t realsize, int flags);
422 =============================================================================
426 =============================================================================
429 mempool_t *fs_mempool;
430 void *fs_mutex = NULL;
432 searchpath_t *fs_searchpaths = NULL;
433 const char *const fs_checkgamedir_missing = "missing";
435 #define MAX_FILES_IN_PACK 65536
437 char fs_userdir[MAX_OSPATH];
438 char fs_gamedir[MAX_OSPATH];
439 char fs_basedir[MAX_OSPATH];
440 static pack_t *fs_selfpack = NULL;
442 // list of active game directories (empty if not running a mod)
443 int fs_numgamedirs = 0;
444 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
446 // list of all gamedirs with modinfo.txt
447 gamedir_t *fs_all_gamedirs = NULL;
448 int fs_all_gamedirs_count = 0;
450 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)"};
451 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"};
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 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 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 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 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 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, 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 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 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;
1498 searchnext = search->next;
1499 if (search->pack && search->pack->dlcache)
1501 Con_DPrintf("Unloading pack: %s\n", search->pack->shortname);
1503 // remove it from the search path list
1504 if (search == fs_searchpaths)
1505 fs_searchpaths = search->next;
1507 searchprev->next = search->next;
1510 FILEDESC_CLOSE(search->pack->handle);
1511 // free any memory associated with it
1512 if (search->pack->files)
1513 Mem_Free(search->pack->files);
1514 Mem_Free(search->pack);
1518 searchprev = search;
1519 search = searchnext;
1523 static void FS_AddSelfPack(void)
1527 searchpath_t *search;
1528 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1529 search->next = fs_searchpaths;
1530 search->pack = fs_selfpack;
1531 fs_searchpaths = search;
1541 void FS_Rescan (void)
1544 qbool fs_modified = false;
1545 qbool reset = false;
1546 char gamedirbuf[MAX_INPUTLINE];
1551 FS_ClearSearchPath();
1553 // automatically activate gamemode for the gamedirs specified
1555 COM_ChangeGameTypeForGameDirs();
1557 // add the game-specific paths
1558 // gamedirname1 (typically id1)
1559 FS_AddGameHierarchy (gamedirname1);
1560 // update the com_modname (used for server info)
1561 if (gamedirname2 && gamedirname2[0])
1562 strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1564 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1566 // add the game-specific path, if any
1567 // (only used for mission packs and the like, which should set fs_modified)
1568 if (gamedirname2 && gamedirname2[0])
1571 FS_AddGameHierarchy (gamedirname2);
1575 // Adds basedir/gamedir as an override game
1576 // LadyHavoc: now supports multiple -game directories
1577 // set the com_modname (reported in server info)
1579 for (i = 0;i < fs_numgamedirs;i++)
1582 FS_AddGameHierarchy (fs_gamedirs[i]);
1583 // update the com_modname (used server info)
1584 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1586 strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1588 strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1590 Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1592 // add back the selfpack as new first item
1595 if (cls.state != ca_dedicated)
1597 // set the default screenshot name to either the mod name or the
1598 // gamemode screenshot name
1599 if (strcmp(com_modname, gamedirname1))
1600 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1602 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1605 if((i = Sys_CheckParm("-modname")) && i < sys.argc - 1)
1606 strlcpy(com_modname, sys.argv[i+1], sizeof(com_modname));
1608 // If "-condebug" is in the command line, remove the previous log file
1609 if (Sys_CheckParm ("-condebug") != 0)
1610 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1612 // look for the pop.lmp file and set registered to true if it is found
1613 if (FS_FileExists("gfx/pop.lmp"))
1614 Cvar_SetValueQuick(®istered, 1);
1620 if (!registered.integer)
1623 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1625 Con_Print("Playing shareware version.\n");
1628 Con_Print("Playing registered version.\n");
1630 case GAME_STEELSTORM:
1631 if (registered.integer)
1632 Con_Print("Playing registered version.\n");
1634 Con_Print("Playing shareware version.\n");
1640 // unload all wads so that future queries will return the new data
1644 static void FS_Rescan_f(cmd_state_t *cmd)
1654 extern qbool vid_opened;
1655 qbool FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qbool complain, qbool failmissing)
1660 if (fs_numgamedirs == numgamedirs)
1662 for (i = 0;i < numgamedirs;i++)
1663 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1665 if (i == numgamedirs)
1666 return true; // already using this set of gamedirs, do nothing
1669 if (numgamedirs > MAX_GAMEDIRS)
1672 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1673 return false; // too many gamedirs
1676 for (i = 0;i < numgamedirs;i++)
1678 // if string is nasty, reject it
1679 p = FS_CheckGameDir(gamedirs[i]);
1683 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1684 return false; // nasty gamedirs
1686 if(p == fs_checkgamedir_missing && failmissing)
1689 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1690 return false; // missing gamedirs
1694 Host_SaveConfig(CONFIGFILENAME);
1696 fs_numgamedirs = numgamedirs;
1697 for (i = 0;i < fs_numgamedirs;i++)
1698 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1700 // reinitialize filesystem to detect the new paks
1703 if (cls.demoplayback)
1709 // unload all sounds so they will be reloaded from the new files as needed
1710 S_UnloadAllSounds_f(cmd_local);
1712 // restart the video subsystem after the config is executed
1713 Cbuf_InsertText(cmd_local, "\nloadconfig\nvid_restart\n\n");
1723 static void FS_GameDir_f(cmd_state_t *cmd)
1727 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1729 if (Cmd_Argc(cmd) < 2)
1731 Con_Printf("gamedirs active:");
1732 for (i = 0;i < fs_numgamedirs;i++)
1733 Con_Printf(" %s", fs_gamedirs[i]);
1738 numgamedirs = Cmd_Argc(cmd) - 1;
1739 if (numgamedirs > MAX_GAMEDIRS)
1741 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1745 for (i = 0;i < numgamedirs;i++)
1746 strlcpy(gamedirs[i], Cmd_Argv(cmd, i+1), sizeof(gamedirs[i]));
1748 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1750 // actually, changing during game would work fine, but would be stupid
1751 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1755 // halt demo playback to close the file
1758 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1761 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1769 stringlistinit(&list);
1770 listdirectory(&list, gamedir, "");
1771 success = list.numstrings > 0;
1772 stringlistfreecontents(&list);
1776 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1779 n = FS_Read (f, buf, buflength - 1);
1799 const char *FS_CheckGameDir(const char *gamedir)
1802 static char buf[8192];
1805 if (FS_CheckNastyPath(gamedir, true))
1808 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1813 // get description from basedir
1814 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1822 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1826 return fs_checkgamedir_missing;
1829 static void FS_ListGameDirs(void)
1831 stringlist_t list, list2;
1836 fs_all_gamedirs_count = 0;
1838 Mem_Free(fs_all_gamedirs);
1840 stringlistinit(&list);
1841 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1842 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1843 stringlistsort(&list, false);
1845 stringlistinit(&list2);
1846 for(i = 0; i < list.numstrings; ++i)
1849 if(!strcmp(list.strings[i-1], list.strings[i]))
1851 info = FS_CheckGameDir(list.strings[i]);
1854 if(info == fs_checkgamedir_missing)
1858 stringlistappend(&list2, list.strings[i]);
1860 stringlistfreecontents(&list);
1862 fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1863 for(i = 0; i < list2.numstrings; ++i)
1865 info = FS_CheckGameDir(list2.strings[i]);
1866 // all this cannot happen any more, but better be safe than sorry
1869 if(info == fs_checkgamedir_missing)
1873 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1874 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1875 ++fs_all_gamedirs_count;
1881 #pragma comment(lib, "shell32.lib")
1886 static void COM_InsertFlags(const char *buf) {
1889 const char **new_argv;
1891 int args_left = 256;
1892 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*sys.argv) * (sys.argc + args_left + 2));
1894 new_argv[0] = "dummy"; // Can't really happen.
1896 new_argv[0] = sys.argv[0];
1899 while(COM_ParseToken_Console(&p))
1901 size_t sz = strlen(com_token) + 1; // shut up clang
1904 q = (char *)Mem_Alloc(fs_mempool, sz);
1905 strlcpy(q, com_token, sz);
1909 // Now: i <= args_left + 1.
1912 memcpy((char *)(&new_argv[i]), &sys.argv[1], sizeof(*sys.argv) * (sys.argc - 1));
1915 // Now: i <= args_left + (sys.argc || 1).
1917 sys.argv = new_argv;
1921 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1923 #if defined(__IPHONEOS__)
1924 if (userdirmode == USERDIRMODE_HOME)
1926 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1927 // fs_userdir stores configurations to the Documents folder of the app
1928 strlcpy(userdir, "../Documents/", MAX_OSPATH);
1933 #elif defined(WIN32)
1934 char homedir[WSTRBUF];
1936 #if _MSC_VER >= 1400
1939 wchar_t mydocsdirw[WSTRBUF];
1940 char mydocsdir[WSTRBUF];
1941 wchar_t *savedgamesdirw;
1942 char savedgamesdir[WSTRBUF] = {0};
1951 case USERDIRMODE_NOHOME:
1952 strlcpy(userdir, fs_basedir, userdirsize);
1954 case USERDIRMODE_MYGAMES:
1956 Sys_LoadDependency(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1958 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdirw) == S_OK)
1960 narrow(mydocsdirw, mydocsdir);
1961 dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1964 #if _MSC_VER >= 1400
1965 _wdupenv_s(&homedirw, &homedirwlen, L"USERPROFILE");
1966 narrow(homedirw, homedir);
1969 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1974 homedirw = _wgetenv(L"USERPROFILE");
1975 narrow(homedirw, homedir);
1978 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1983 case USERDIRMODE_SAVEDGAMES:
1985 Sys_LoadDependency(shell32dllnames, &shell32_dll, shell32funcs);
1987 Sys_LoadDependency(ole32dllnames, &ole32_dll, ole32funcs);
1988 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1990 savedgamesdir[0] = 0;
1991 qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1994 if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1996 if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1999 if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
2001 narrow(savedgamesdirw, savedgamesdir);
2002 qCoTaskMemFree(savedgamesdirw);
2005 if (savedgamesdir[0])
2007 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
2022 case USERDIRMODE_NOHOME:
2023 strlcpy(userdir, fs_basedir, userdirsize);
2025 case USERDIRMODE_HOME:
2026 homedir = getenv("HOME");
2029 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
2033 case USERDIRMODE_SAVEDGAMES:
2034 homedir = getenv("HOME");
2038 dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
2040 // the XDG say some files would need to go in:
2041 // XDG_CONFIG_HOME (or ~/.config/%s/)
2042 // XDG_DATA_HOME (or ~/.local/share/%s/)
2043 // XDG_CACHE_HOME (or ~/.cache/%s/)
2044 // and also search the following global locations if defined:
2045 // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
2046 // XDG_DATA_DIRS (normally /usr/share/%s/)
2047 // this would be too complicated...
2057 #if !defined(__IPHONEOS__)
2060 // historical behavior...
2061 if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
2062 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
2065 // see if we can write to this path (note: won't create path)
2067 // no access() here, we must try to open the file for appending
2068 fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
2072 // on Unix, we don't need to ACTUALLY attempt to open the file
2073 if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
2080 return 1; // good choice - the path exists and is writable
2084 if (userdirmode == USERDIRMODE_NOHOME)
2085 return -1; // path usually already exists, we lack permissions
2087 return 0; // probably good - failed to write but maybe we need to create path
2092 void FS_Init_Commands(void)
2094 Cvar_RegisterVariable (&scr_screenshot_name);
2095 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2096 Cvar_RegisterVariable (&cvar_fs_gamedir);
2098 Cmd_AddCommand(CF_SHARED, "gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2099 Cmd_AddCommand(CF_SHARED, "fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2100 Cmd_AddCommand(CF_SHARED, "path", FS_Path_f, "print searchpath (game directories and archives)");
2101 Cmd_AddCommand(CF_SHARED, "dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2102 Cmd_AddCommand(CF_SHARED, "ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2103 Cmd_AddCommand(CF_SHARED, "which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2106 static void FS_Init_Dir (void)
2116 // Overrides the system supplied base directory (under GAMENAME)
2117 // 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)
2118 i = Sys_CheckParm ("-basedir");
2119 if (i && i < sys.argc-1)
2121 strlcpy (fs_basedir, sys.argv[i+1], sizeof (fs_basedir));
2122 i = (int)strlen (fs_basedir);
2123 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2124 fs_basedir[i-1] = 0;
2128 // If the base directory is explicitly defined by the compilation process
2129 #ifdef DP_FS_BASEDIR
2130 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2131 #elif defined(__ANDROID__)
2132 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2133 #elif defined(MACOSX)
2134 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2135 if (strstr(sys.argv[0], ".app/"))
2138 strlcpy(fs_basedir, sys.argv[0], sizeof(fs_basedir));
2139 split = strstr(fs_basedir, ".app/");
2142 struct stat statresult;
2144 // truncate to just after the .app/
2146 // see if gamedir exists in Resources
2147 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2149 // found gamedir inside Resources, use it
2150 strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2154 // no gamedir found in Resources, gamedir is probably
2155 // outside the .app, remove .app part of path
2156 while (split > fs_basedir && *split != '/')
2165 // make sure the appending of a path separator won't create an unterminated string
2166 memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2167 // add a path separator to the end of the basedir if it lacks one
2168 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2169 strlcat(fs_basedir, "/", sizeof(fs_basedir));
2171 // Add the personal game directory
2172 if((i = Sys_CheckParm("-userdir")) && i < sys.argc - 1)
2173 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", sys.argv[i+1]);
2174 else if (Sys_CheckParm("-nohome"))
2175 *fs_userdir = 0; // user wants roaming installation, no userdir
2178 #ifdef DP_FS_USERDIR
2179 strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
2182 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2183 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2184 int userdirstatus[USERDIRMODE_COUNT];
2186 // historical behavior...
2187 if (!strcmp(gamedirname1, "id1"))
2188 preferreduserdirmode = USERDIRMODE_NOHOME;
2190 // check what limitations the user wants to impose
2191 if (Sys_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2192 if (Sys_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2193 if (Sys_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2194 // gather the status of the possible userdirs
2195 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2197 userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2198 if (userdirstatus[dirmode] == 1)
2199 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2200 else if (userdirstatus[dirmode] == 0)
2201 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2203 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2205 // some games may prefer writing to basedir, but if write fails we
2206 // have to search for a real userdir...
2207 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2208 preferreduserdirmode = highestuserdirmode;
2209 // check for an existing userdir and continue using it if possible...
2210 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2211 if (userdirstatus[dirmode] == 1)
2213 // if no existing userdir found, make a new one...
2214 if (dirmode == 0 && preferreduserdirmode > 0)
2215 for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2216 if (userdirstatus[dirmode] >= 0)
2218 // and finally, we picked one...
2219 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2220 Con_DPrintf("userdir %i is the winner\n", dirmode);
2224 // if userdir equal to basedir, clear it to avoid confusion later
2225 if (!strcmp(fs_basedir, fs_userdir))
2230 p = FS_CheckGameDir(gamedirname1);
2231 if(!p || p == fs_checkgamedir_missing)
2232 Con_Printf(CON_WARN "WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2236 p = FS_CheckGameDir(gamedirname2);
2237 if(!p || p == fs_checkgamedir_missing)
2238 Con_Printf(CON_WARN "WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2242 // Adds basedir/gamedir as an override game
2243 // LadyHavoc: now supports multiple -game directories
2244 for (i = 1;i < sys.argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2248 if (!strcmp (sys.argv[i], "-game") && i < sys.argc-1)
2251 p = FS_CheckGameDir(sys.argv[i]);
2253 Con_Printf("WARNING: Nasty -game name rejected: %s\n", sys.argv[i]);
2254 if(p == fs_checkgamedir_missing)
2255 Con_Printf(CON_WARN "WARNING: -game %s%s/ not found!\n", fs_basedir, sys.argv[i]);
2256 // add the gamedir to the list of active gamedirs
2257 strlcpy (fs_gamedirs[fs_numgamedirs], sys.argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2262 // generate the searchpath
2265 if (Thread_HasThreads())
2266 fs_mutex = Thread_CreateMutex();
2274 void FS_Init_SelfPack (void)
2278 // Load darkplaces.opt from the FS.
2279 if (!Sys_CheckParm("-noopt"))
2281 buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
2284 COM_InsertFlags(buf);
2290 // Provide the SelfPack.
2291 if (!Sys_CheckParm("-noselfpack") && sys.selffd >= 0)
2293 fs_selfpack = FS_LoadPackPK3FromFD(sys.argv[0], sys.selffd, true);
2297 if (!Sys_CheckParm("-noopt"))
2299 buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
2302 COM_InsertFlags(buf);
2319 fs_mempool = Mem_AllocPool("file management", 0, NULL);
2325 // initialize the self-pack (must be before COM_InitGameType as it may add command line options)
2328 // detect gamemode from commandline options or executable name
2339 void FS_Shutdown (void)
2341 // close all pack files and such
2342 // (hopefully there aren't any other open files, but they'll be cleaned up
2343 // by the OS anyway)
2344 FS_ClearSearchPath();
2345 Mem_FreePool (&fs_mempool);
2346 PK3_CloseLibrary ();
2349 Sys_FreeLibrary (&shfolder_dll);
2350 Sys_FreeLibrary (&shell32_dll);
2351 Sys_FreeLibrary (&ole32_dll);
2355 Thread_DestroyMutex(fs_mutex);
2358 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qbool nonblocking)
2360 filedesc_t handle = FILEDESC_INVALID;
2363 qbool dolock = false;
2365 wchar filepathw[WSTRBUF] = {0};
2368 // Parse the mode string
2377 opt = O_CREAT | O_TRUNC;
2381 opt = O_CREAT | O_APPEND;
2384 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2385 return FILEDESC_INVALID;
2387 for (ind = 1; mode[ind] != '\0'; ind++)
2401 Con_Printf(CON_ERROR "FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2402 filepath, mode, mode[ind]);
2409 if(Sys_CheckParm("-readonly") && mod != O_RDONLY)
2410 return FILEDESC_INVALID;
2414 return FILEDESC_INVALID;
2415 handle = SDL_RWFromFile(filepath, mode);
2418 widen(filepath, filepathw);
2419 # if _MSC_VER >= 1400
2420 _wsopen_s(&handle, filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2422 handle = _wsopen (filepathw, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2425 handle = open (filepath, mod | opt, 0666);
2426 if(handle >= 0 && dolock)
2429 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2430 l.l_whence = SEEK_SET;
2433 if(fcntl(handle, F_SETLK, &l) == -1)
2435 FILEDESC_CLOSE(handle);
2445 int FS_SysOpenFD(const char *filepath, const char *mode, qbool nonblocking)
2450 return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2455 ====================
2458 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2459 ====================
2461 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qbool nonblocking)
2465 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2467 file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2468 if (!FILEDESC_ISVALID(file->handle))
2474 file->filename = Mem_strdup(fs_mempool, filepath);
2476 file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2478 // For files opened in append mode, we start at the end of the file
2480 file->position = file->real_length;
2482 FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2492 Open a packed file using its package file descriptor
2495 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2498 filedesc_t dup_handle;
2501 pfile = &pack->files[pack_ind];
2503 // If we don't have the true offset, get it now
2504 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2505 if (!PK3_GetTrueFileOffset (pfile, pack))
2508 #ifndef LINK_TO_ZLIB
2509 // No Zlib DLL = no compressed files
2510 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2512 Con_Printf(CON_WARN "WARNING: can't open the compressed file %s\n"
2513 "You need the Zlib DLL to use compressed files\n",
2519 // LadyHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2520 // the dup() call to avoid having to close the dup_handle on error here
2521 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2523 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2524 pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2528 dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2529 if (!FILEDESC_ISVALID(dup_handle))
2531 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2535 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2536 memset (file, 0, sizeof (*file));
2537 file->handle = dup_handle;
2538 file->flags = QFILE_FLAG_PACKED;
2539 file->real_length = pfile->realsize;
2540 file->offset = pfile->offset;
2544 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2548 file->flags |= QFILE_FLAG_DEFLATED;
2550 // We need some more variables
2551 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2553 ztk->comp_length = pfile->packsize;
2555 // Initialize zlib stream
2556 ztk->zstream.next_in = ztk->input;
2557 ztk->zstream.avail_in = 0;
2559 /* From Zlib's "unzip.c":
2561 * windowBits is passed < 0 to tell that there is no zlib header.
2562 * Note that in this case inflate *requires* an extra "dummy" byte
2563 * after the compressed stream in order to complete decompression and
2564 * return Z_STREAM_END.
2565 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2566 * size of both compressed and uncompressed data
2568 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2570 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2571 FILEDESC_CLOSE(dup_handle);
2576 ztk->zstream.next_out = file->buff;
2577 ztk->zstream.avail_out = sizeof (file->buff);
2586 ====================
2589 Return true if the path should be rejected due to one of the following:
2590 1: path elements that are non-portable
2591 2: path elements that would allow access to files outside the game directory,
2592 or are just not a good idea for a mod to be using.
2593 ====================
2595 int FS_CheckNastyPath (const char *path, qbool isgamedir)
2597 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2601 // Windows: don't allow \ in filenames (windows-only), period.
2602 // (on Windows \ is a directory separator, but / is also supported)
2603 if (strstr(path, "\\"))
2604 return 1; // non-portable
2606 // Mac: don't allow Mac-only filenames - : is a directory separator
2607 // instead of /, but we rely on / working already, so there's no reason to
2608 // support a Mac-only path
2609 // Amiga and Windows: : tries to go to root of drive
2610 if (strstr(path, ":"))
2611 return 1; // non-portable attempt to go to root of drive
2613 // Amiga: // is parent directory
2614 if (strstr(path, "//"))
2615 return 1; // non-portable attempt to go to parent directory
2617 // all: don't allow going to parent directory (../ or /../)
2618 if (strstr(path, ".."))
2619 return 2; // attempt to go outside the game directory
2621 // Windows and UNIXes: don't allow absolute paths
2623 return 2; // attempt to go outside the game directory
2625 // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2626 if (strstr(path, "./"))
2627 return 2; // possible attempt to go outside the game directory
2629 // all: forbid trailing slash on gamedir
2630 if (isgamedir && path[strlen(path)-1] == '/')
2633 // all: forbid leading dot on any filename for any reason
2634 if (strstr(path, "/."))
2635 return 2; // attempt to go outside the game directory
2637 // after all these checks we're pretty sure it's a / separated filename
2638 // and won't do much if any harm
2643 ====================
2646 Sanitize path (replace non-portable characters
2647 with portable ones in-place, etc)
2648 ====================
2650 void FS_SanitizePath(char *path)
2652 for (; *path; path++)
2658 ====================
2661 Look for a file in the packages and in the filesystem
2663 Return the searchpath where the file was found (or NULL)
2664 and the file index in the package if relevant
2665 ====================
2667 static searchpath_t *FS_FindFile (const char *name, int* index, qbool quiet)
2669 searchpath_t *search;
2672 // search through the path, one element at a time
2673 for (search = fs_searchpaths;search;search = search->next)
2675 // is the element a pak file?
2676 if (search->pack && !search->pack->vpack)
2678 int (*strcmp_funct) (const char* str1, const char* str2);
2679 int left, right, middle;
2682 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2684 // Look for the file (binary search)
2686 right = pak->numfiles - 1;
2687 while (left <= right)
2691 middle = (left + right) / 2;
2692 diff = strcmp_funct (pak->files[middle].name, name);
2697 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2699 // yes, but the first one is empty so we treat it as not being there
2700 if (!quiet && developer_extra.integer)
2701 Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2708 if (!quiet && developer_extra.integer)
2709 Con_DPrintf("FS_FindFile: %s in %s\n",
2710 pak->files[middle].name, pak->filename);
2717 // If we're too far in the list
2726 char netpath[MAX_OSPATH];
2727 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2728 if (FS_SysFileExists (netpath))
2730 if (!quiet && developer_extra.integer)
2731 Con_DPrintf("FS_FindFile: %s\n", netpath);
2740 if (!quiet && developer_extra.integer)
2741 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2753 Look for a file in the search paths and open it in read-only mode
2756 static qfile_t *FS_OpenReadFile (const char *filename, qbool quiet, qbool nonblocking, int symlinkLevels)
2758 searchpath_t *search;
2761 search = FS_FindFile (filename, &pack_ind, quiet);
2767 // Found in the filesystem?
2770 // this works with vpacks, so we are fine
2771 char path [MAX_OSPATH];
2772 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2773 return FS_SysOpen (path, "rb", nonblocking);
2776 // So, we found it in a package...
2778 // Is it a PK3 symlink?
2779 // TODO also handle directory symlinks by parsing the whole structure...
2780 // but heck, file symlinks are good enough for now
2781 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2783 if(symlinkLevels <= 0)
2785 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2790 char linkbuf[MAX_QPATH];
2792 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2793 const char *mergeslash;
2798 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2804 // Now combine the paths...
2805 mergeslash = strrchr(filename, '/');
2806 mergestart = linkbuf;
2808 mergeslash = filename;
2809 while(!strncmp(mergestart, "../", 3))
2812 while(mergeslash > filename)
2815 if(*mergeslash == '/')
2819 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2820 if(mergeslash == filename)
2822 // Either mergeslash == filename, then we just replace the name (done below)
2826 // Or, we append the name after mergeslash;
2827 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2828 int spaceNeeded = mergeslash - filename + 1;
2829 int spaceRemoved = mergestart - linkbuf;
2830 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2832 Con_DPrintf("symlink: too long path rejected\n");
2835 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2836 memcpy(linkbuf, filename, spaceNeeded);
2837 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2838 mergestart = linkbuf;
2840 if (!quiet && developer_loading.integer)
2841 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2842 if(FS_CheckNastyPath (mergestart, false))
2844 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2847 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2851 return FS_OpenPackedFile (search->pack, pack_ind);
2856 =============================================================================
2858 MAIN PUBLIC FUNCTIONS
2860 =============================================================================
2864 ====================
2867 Open a file in the userpath. The syntax is the same as fopen
2868 Used for savegame scanning in menu, and all file writing.
2869 ====================
2871 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qbool quiet)
2873 char real_path [MAX_OSPATH];
2875 if (FS_CheckNastyPath(filepath, false))
2877 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2881 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2883 // If the file is opened in "write", "append", or "read/write" mode,
2884 // create directories up to the file.
2885 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2886 FS_CreatePath (real_path);
2887 return FS_SysOpen (real_path, mode, false);
2892 ====================
2895 Open a file. The syntax is the same as fopen
2896 ====================
2898 qfile_t* FS_OpenVirtualFile (const char* filepath, qbool quiet)
2900 qfile_t *result = NULL;
2901 if (FS_CheckNastyPath(filepath, false))
2903 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2907 if (fs_mutex) Thread_LockMutex(fs_mutex);
2908 result = FS_OpenReadFile (filepath, quiet, false, 16);
2909 if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2915 ====================
2918 Open a file. The syntax is the same as fopen
2919 ====================
2921 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qbool quiet)
2924 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2925 memset (file, 0, sizeof (*file));
2926 file->flags = QFILE_FLAG_DATA;
2928 file->real_length = size;
2934 ====================
2938 ====================
2940 int FS_Close (qfile_t* file)
2942 if(file->flags & QFILE_FLAG_DATA)
2948 if (FILEDESC_CLOSE (file->handle))
2953 if (file->flags & QFILE_FLAG_REMOVE)
2955 if (remove(file->filename) == -1)
2957 // No need to report this. If removing a just
2958 // written file failed, this most likely means
2959 // someone else deleted it first - which we
2964 Mem_Free((void *) file->filename);
2969 qz_inflateEnd (&file->ztk->zstream);
2970 Mem_Free (file->ztk);
2977 void FS_RemoveOnClose(qfile_t* file)
2979 file->flags |= QFILE_FLAG_REMOVE;
2983 ====================
2986 Write "datasize" bytes into a file
2987 ====================
2989 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2991 fs_offset_t written = 0;
2993 // If necessary, seek to the exact file position we're supposed to be
2994 if (file->buff_ind != file->buff_len)
2996 if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
2998 Con_Printf(CON_WARN "WARNING: could not seek in %s.\n", file->filename);
3002 // Purge cached data
3005 // Write the buffer and update the position
3006 // 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)
3007 while (written < (fs_offset_t)datasize)
3009 // figure out how much to write in one chunk
3010 fs_offset_t maxchunk = 1<<30; // 1 GiB
3011 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
3012 int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
3013 // if at least some was written, add it to our accumulator
3016 // if the result is not what we expected, consider the write to be incomplete
3017 if (result != chunk)
3020 file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
3021 if (file->real_length < file->position)
3022 file->real_length = file->position;
3024 // note that this will never be less than 0 even if the write failed
3030 ====================
3033 Read up to "buffersize" bytes from a file
3034 ====================
3036 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
3038 fs_offset_t count, done;
3040 if (buffersize == 0 || !buffer)
3043 // Get rid of the ungetc character
3044 if (file->ungetc != EOF)
3046 ((char*)buffer)[0] = file->ungetc;
3054 if(file->flags & QFILE_FLAG_DATA)
3056 size_t left = file->real_length - file->position;
3057 if(buffersize > left)
3059 memcpy(buffer, file->data + file->position, buffersize);
3060 file->position += buffersize;
3064 // First, we copy as many bytes as we can from "buff"
3065 if (file->buff_ind < file->buff_len)
3067 count = file->buff_len - file->buff_ind;
3068 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
3070 memcpy (buffer, &file->buff[file->buff_ind], count);
3071 file->buff_ind += count;
3073 buffersize -= count;
3074 if (buffersize == 0)
3078 // NOTE: at this point, the read buffer is always empty
3080 // If the file isn't compressed
3081 if (! (file->flags & QFILE_FLAG_DEFLATED))
3085 // We must take care to not read after the end of the file
3086 count = file->real_length - file->position;
3088 // If we have a lot of data to get, put them directly into "buffer"
3089 if (buffersize > sizeof (file->buff) / 2)
3091 if (count > (fs_offset_t)buffersize)
3092 count = (fs_offset_t)buffersize;
3093 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3095 // Seek failed. When reading from a pipe, and
3096 // the caller never called FS_Seek, this still
3097 // works fine. So no reporting this error.
3099 nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
3103 file->position += nb;
3105 // Purge cached data
3111 if (count > (fs_offset_t)sizeof (file->buff))
3112 count = (fs_offset_t)sizeof (file->buff);
3113 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
3115 // Seek failed. When reading from a pipe, and
3116 // the caller never called FS_Seek, this still
3117 // works fine. So no reporting this error.
3119 nb = FILEDESC_READ (file->handle, file->buff, count);
3122 file->buff_len = nb;
3123 file->position += nb;
3125 // Copy the requested data in "buffer" (as much as we can)
3126 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3127 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3128 file->buff_ind = count;
3136 // If the file is compressed, it's more complicated...
3137 // We cycle through a few operations until we have read enough data
3138 while (buffersize > 0)
3140 ztoolkit_t *ztk = file->ztk;
3143 // NOTE: at this point, the read buffer is always empty
3145 // If "input" is also empty, we need to refill it
3146 if (ztk->in_ind == ztk->in_len)
3148 // If we are at the end of the file
3149 if (file->position == file->real_length)
3152 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
3153 if (count > (fs_offset_t)sizeof (ztk->input))
3154 count = (fs_offset_t)sizeof (ztk->input);
3155 FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
3156 if (FILEDESC_READ (file->handle, ztk->input, count) != count)
3158 Con_Printf ("FS_Read: unexpected end of file\n");
3163 ztk->in_len = count;
3164 ztk->in_position += count;
3167 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3168 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3170 // Now that we are sure we have compressed data available, we need to determine
3171 // if it's better to inflate it in "file->buff" or directly in "buffer"
3173 // Inflate the data in "file->buff"
3174 if (buffersize < sizeof (file->buff) / 2)
3176 ztk->zstream.next_out = file->buff;
3177 ztk->zstream.avail_out = sizeof (file->buff);
3178 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3179 if (error != Z_OK && error != Z_STREAM_END)
3181 Con_Printf ("FS_Read: Can't inflate file\n");
3184 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3186 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3187 file->position += file->buff_len;
3189 // Copy the requested data in "buffer" (as much as we can)
3190 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3191 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3192 file->buff_ind = count;
3195 // Else, we inflate directly in "buffer"
3198 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3199 ztk->zstream.avail_out = (unsigned int)buffersize;
3200 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3201 if (error != Z_OK && error != Z_STREAM_END)
3203 Con_Printf ("FS_Read: Can't inflate file\n");
3206 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3208 // How much data did it inflate?
3209 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3210 file->position += count;
3212 // Purge cached data
3217 buffersize -= count;
3225 ====================
3228 Print a string into a file
3229 ====================
3231 int FS_Print (qfile_t* file, const char *msg)
3233 return (int)FS_Write (file, msg, strlen (msg));
3237 ====================
3240 Print a string into a file
3241 ====================
3243 int FS_Printf(qfile_t* file, const char* format, ...)
3248 va_start (args, format);
3249 result = FS_VPrintf (file, format, args);
3257 ====================
3260 Print a string into a file
3261 ====================
3263 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3266 fs_offset_t buff_size = MAX_INPUTLINE;
3271 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3272 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3273 if (len >= 0 && len < buff_size)
3275 Mem_Free (tempbuff);
3279 len = FILEDESC_WRITE (file->handle, tempbuff, len);
3280 Mem_Free (tempbuff);
3287 ====================
3290 Get the next character of a file
3291 ====================
3293 int FS_Getc (qfile_t* file)
3297 if (FS_Read (file, &c, 1) != 1)
3305 ====================
3308 Put a character back into the read buffer (only supports one character!)
3309 ====================
3311 int FS_UnGetc (qfile_t* file, unsigned char c)
3313 // If there's already a character waiting to be read
3314 if (file->ungetc != EOF)
3323 ====================
3326 Move the position index in a file
3327 ====================
3329 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3332 unsigned char* buffer;
3333 fs_offset_t buffersize;
3335 // Compute the file offset
3339 offset += file->position - file->buff_len + file->buff_ind;
3346 offset += file->real_length;
3352 if (offset < 0 || offset > file->real_length)
3355 if(file->flags & QFILE_FLAG_DATA)
3357 file->position = offset;
3361 // If we have the data in our read buffer, we don't need to actually seek
3362 if (file->position - file->buff_len <= offset && offset <= file->position)
3364 file->buff_ind = offset + file->buff_len - file->position;
3368 // Purge cached data
3371 // Unpacked or uncompressed files can seek directly
3372 if (! (file->flags & QFILE_FLAG_DEFLATED))
3374 if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3376 file->position = offset;
3380 // Seeking in compressed files is more a hack than anything else,
3381 // but we need to support it, so here we go.
3384 // If we have to go back in the file, we need to restart from the beginning
3385 if (offset <= file->position)
3389 ztk->in_position = 0;
3391 if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3392 Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3394 // Reset the Zlib stream
3395 ztk->zstream.next_in = ztk->input;
3396 ztk->zstream.avail_in = 0;
3397 qz_inflateReset (&ztk->zstream);
3400 // We need a big buffer to force inflating into it directly
3401 buffersize = 2 * sizeof (file->buff);
3402 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3404 // Skip all data until we reach the requested offset
3405 while (offset > (file->position - file->buff_len + file->buff_ind))
3407 fs_offset_t diff = offset - (file->position - file->buff_len + file->buff_ind);
3408 fs_offset_t count, len;
3410 count = (diff > buffersize) ? buffersize : diff;
3411 len = FS_Read (file, buffer, count);
3425 ====================
3428 Give the current position in a file
3429 ====================
3431 fs_offset_t FS_Tell (qfile_t* file)
3433 return file->position - file->buff_len + file->buff_ind;
3438 ====================
3441 Give the total size of a file
3442 ====================
3444 fs_offset_t FS_FileSize (qfile_t* file)
3446 return file->real_length;
3451 ====================
3454 Erases any buffered input or output data
3455 ====================
3457 void FS_Purge (qfile_t* file)
3467 FS_LoadAndCloseQFile
3469 Loads full content of a qfile_t and closes it.
3470 Always appends a 0 byte.
3473 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3475 unsigned char *buf = NULL;
3476 fs_offset_t filesize = 0;
3480 filesize = file->real_length;
3483 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3488 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3489 buf[filesize] = '\0';
3490 FS_Read (file, buf, filesize);
3492 if (developer_loadfile.integer)
3493 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3496 if (filesizepointer)
3497 *filesizepointer = filesize;
3506 Filename are relative to the quake directory.
3507 Always appends a 0 byte.
3510 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3512 qfile_t *file = FS_OpenVirtualFile(path, quiet);
3513 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3521 Filename are OS paths.
3522 Always appends a 0 byte.
3525 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qbool quiet, fs_offset_t *filesizepointer)
3527 qfile_t *file = FS_SysOpen(path, "rb", false);
3528 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3536 The filename will be prefixed by the current game directory
3539 qbool FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3543 fs_offset_t lentotal;
3545 file = FS_OpenRealFile(filename, "wb", false);
3548 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3553 for(i = 0; i < count; ++i)
3555 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3556 for(i = 0; i < count; ++i)
3557 FS_Write (file, data[i], len[i]);
3562 qbool FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3564 return FS_WriteFileInBlocks(filename, &data, &len, 1);
3569 =============================================================================
3571 OTHERS PUBLIC FUNCTIONS
3573 =============================================================================
3581 void FS_StripExtension (const char *in, char *out, size_t size_out)
3589 while ((currentchar = *in) && size_out > 1)
3591 if (currentchar == '.')
3593 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3595 *out++ = currentchar;
3611 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3615 // if path doesn't have a .EXT, append extension
3616 // (extension should include the .)
3617 src = path + strlen(path);
3619 while (*src != '/' && src != path)
3622 return; // it has an extension
3626 strlcat (path, extension, size_path);
3634 Look for a file in the packages and in the filesystem
3637 int FS_FileType (const char *filename)
3639 searchpath_t *search;
3640 char fullpath[MAX_OSPATH];
3642 search = FS_FindFile (filename, NULL, true);
3644 return FS_FILETYPE_NONE;
3646 if(search->pack && !search->pack->vpack)
3647 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3649 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3650 return FS_SysFileType(fullpath);
3658 Look for a file in the packages and in the filesystem
3661 qbool FS_FileExists (const char *filename)
3663 return (FS_FindFile (filename, NULL, true) != NULL);
3671 Look for a file in the filesystem only
3674 int FS_SysFileType (const char *path)
3677 // Sajt - some older sdks are missing this define
3678 # ifndef INVALID_FILE_ATTRIBUTES
3679 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3681 wchar pathw[WSTRBUF] = {0};
3684 result = GetFileAttributesW(pathw);
3686 if(result == INVALID_FILE_ATTRIBUTES)
3687 return FS_FILETYPE_NONE;
3689 if(result & FILE_ATTRIBUTE_DIRECTORY)
3690 return FS_FILETYPE_DIRECTORY;
3692 return FS_FILETYPE_FILE;
3696 if (stat (path,&buf) == -1)
3697 return FS_FILETYPE_NONE;
3700 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3702 if(S_ISDIR(buf.st_mode))
3703 return FS_FILETYPE_DIRECTORY;
3705 return FS_FILETYPE_FILE;
3709 qbool FS_SysFileExists (const char *path)
3711 return FS_SysFileType (path) != FS_FILETYPE_NONE;
3718 Allocate and fill a search structure with information on matching filenames.
3721 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet, const char *packfile)
3724 searchpath_t *searchpath;
3726 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3727 stringlist_t resultlist;
3728 stringlist_t dirlist;
3729 stringlist_t matchedSet, foundSet;
3730 const char *start, *slash, *backslash, *colon, *separator;
3733 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3738 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3742 stringlistinit(&resultlist);
3743 stringlistinit(&dirlist);
3745 slash = strrchr(pattern, '/');
3746 backslash = strrchr(pattern, '\\');
3747 colon = strrchr(pattern, ':');
3748 separator = max(slash, backslash);
3749 separator = max(separator, colon);
3750 basepathlength = separator ? (separator + 1 - pattern) : 0;
3751 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3753 memcpy(basepath, pattern, basepathlength);
3754 basepath[basepathlength] = 0;
3756 // search through the path, one element at a time
3757 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3759 // is the element a pak file?
3760 if (searchpath->pack && !searchpath->pack->vpack)
3762 // look through all the pak file elements
3763 pak = searchpath->pack;
3766 if(strcmp(packfile, pak->shortname))
3769 for (i = 0;i < pak->numfiles;i++)
3771 char temp[MAX_OSPATH];
3772 strlcpy(temp, pak->files[i].name, sizeof(temp));
3775 if (matchpattern(temp, (char *)pattern, true))
3777 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3778 if (!strcmp(resultlist.strings[resultlistindex], temp))
3780 if (resultlistindex == resultlist.numstrings)
3782 stringlistappend(&resultlist, temp);
3783 if (!quiet && developer_loading.integer)
3784 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3787 // strip off one path element at a time until empty
3788 // this way directories are added to the listing if they match the pattern
3789 slash = strrchr(temp, '/');
3790 backslash = strrchr(temp, '\\');
3791 colon = strrchr(temp, ':');
3793 if (separator < slash)
3795 if (separator < backslash)
3796 separator = backslash;
3797 if (separator < colon)
3799 *((char *)separator) = 0;
3810 stringlistinit(&matchedSet);
3811 stringlistinit(&foundSet);
3812 // add a first entry to the set
3813 stringlistappend(&matchedSet, "");
3814 // iterate through pattern's path
3817 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3818 char subpath[MAX_OSPATH];
3819 char subpattern[MAX_OSPATH];
3821 // find the next wildcard
3822 wildcard = strchr(start, '?');
3823 asterisk = strchr(start, '*');
3824 if (asterisk && (!wildcard || asterisk < wildcard))
3826 wildcard = asterisk;
3831 nextseparator = strchr( wildcard, '/' );
3835 nextseparator = NULL;
3838 if( !nextseparator ) {
3839 nextseparator = start + strlen( start );
3842 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3843 // copy everything up except nextseperator
3844 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3845 // find the last '/' before the wildcard
3846 prevseparator = strrchr( subpattern, '/' );
3848 prevseparator = subpattern;
3851 // copy everything from start to the previous including the '/' (before the wildcard)
3852 // everything up to start is already included in the path of matchedSet's entries
3853 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3855 // for each entry in matchedSet try to open the subdirectories specified in subpath
3856 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3857 char temp[MAX_OSPATH];
3858 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3859 strlcat( temp, subpath, sizeof(temp) );
3860 listdirectory( &foundSet, searchpath->filename, temp );
3862 if( dirlistindex == 0 ) {
3865 // reset the current result set
3866 stringlistfreecontents( &matchedSet );
3867 // match against the pattern
3868 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3869 const char *direntry = foundSet.strings[ dirlistindex ];
3870 if (matchpattern(direntry, subpattern, true)) {
3871 stringlistappend( &matchedSet, direntry );
3874 stringlistfreecontents( &foundSet );
3876 start = nextseparator;
3879 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3881 const char *matchtemp = matchedSet.strings[dirlistindex];
3882 if (matchpattern(matchtemp, (char *)pattern, true))
3884 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3885 if (!strcmp(resultlist.strings[resultlistindex], matchtemp))
3887 if (resultlistindex == resultlist.numstrings)
3889 stringlistappend(&resultlist, matchtemp);
3890 if (!quiet && developer_loading.integer)
3891 Con_Printf("SearchDirFile: %s\n", matchtemp);
3895 stringlistfreecontents( &matchedSet );
3899 if (resultlist.numstrings)
3901 stringlistsort(&resultlist, true);
3902 numfiles = resultlist.numstrings;
3904 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3905 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3906 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3907 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3908 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3909 search->numfilenames = (int)numfiles;
3912 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3915 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3916 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3917 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3919 numchars += (int)textlen;
3922 stringlistfreecontents(&resultlist);
3928 void FS_FreeSearch(fssearch_t *search)
3933 extern int con_linewidth;
3934 static int FS_ListDirectory(const char *pattern, int oneperline)
3943 char linebuf[MAX_INPUTLINE];
3945 search = FS_Search(pattern, true, true, NULL);
3948 numfiles = search->numfilenames;
3951 // FIXME: the names could be added to one column list and then
3952 // gradually shifted into the next column if they fit, and then the
3953 // next to make a compact variable width listing but it's a lot more
3955 // find width for columns
3957 for (i = 0;i < numfiles;i++)
3959 l = (int)strlen(search->filenames[i]);
3960 if (columnwidth < l)
3963 // count the spacing character
3965 // calculate number of columns
3966 numcolumns = con_linewidth / columnwidth;
3967 // don't bother with the column printing if it's only one column
3968 if (numcolumns >= 2)
3970 numlines = (numfiles + numcolumns - 1) / numcolumns;
3971 for (i = 0;i < numlines;i++)
3974 for (k = 0;k < numcolumns;k++)
3976 l = i * numcolumns + k;
3979 name = search->filenames[l];
3980 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3981 linebuf[linebufpos++] = name[j];
3982 // space out name unless it's the last on the line
3983 if (k + 1 < numcolumns && l + 1 < numfiles)
3984 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3985 linebuf[linebufpos++] = ' ';
3988 linebuf[linebufpos] = 0;
3989 Con_Printf("%s\n", linebuf);
3996 for (i = 0;i < numfiles;i++)
3997 Con_Printf("%s\n", search->filenames[i]);
3998 FS_FreeSearch(search);
3999 return (int)numfiles;
4002 static void FS_ListDirectoryCmd (cmd_state_t *cmd, const char* cmdname, int oneperline)
4004 const char *pattern;
4005 if (Cmd_Argc(cmd) >= 3)
4007 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
4010 if (Cmd_Argc(cmd) == 2)
4011 pattern = Cmd_Argv(cmd, 1);
4014 if (!FS_ListDirectory(pattern, oneperline))
4015 Con_Print("No files found.\n");
4018 void FS_Dir_f(cmd_state_t *cmd)
4020 FS_ListDirectoryCmd(cmd, "dir", true);
4023 void FS_Ls_f(cmd_state_t *cmd)
4025 FS_ListDirectoryCmd(cmd, "ls", false);
4028 void FS_Which_f(cmd_state_t *cmd)
4030 const char *filename;
4033 if (Cmd_Argc(cmd) != 2)
4035 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(cmd, 0));
4038 filename = Cmd_Argv(cmd, 1);
4039 sp = FS_FindFile(filename, &index, true);
4041 Con_Printf("%s isn't anywhere\n", filename);
4047 Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
4049 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
4052 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
4056 const char *FS_WhichPack(const char *filename)
4059 searchpath_t *sp = FS_FindFile(filename, &index, true);
4061 return sp->pack->shortname;
4069 ====================
4070 FS_IsRegisteredQuakePack
4072 Look for a proof of purchase file file in the requested package
4074 If it is found, this file should NOT be downloaded.
4075 ====================
4077 qbool FS_IsRegisteredQuakePack(const char *name)
4079 searchpath_t *search;
4082 // search through the path, one element at a time
4083 for (search = fs_searchpaths;search;search = search->next)
4085 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
4086 // TODO do we want to support vpacks in here too?
4088 int (*strcmp_funct) (const char* str1, const char* str2);
4089 int left, right, middle;
4092 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
4094 // Look for the file (binary search)
4096 right = pak->numfiles - 1;
4097 while (left <= right)
4101 middle = (left + right) / 2;
4102 diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
4108 // If we're too far in the list
4115 // we found the requested pack but it is not registered quake
4123 int FS_CRCFile(const char *filename, size_t *filesizepointer)
4126 unsigned char *filedata;
4127 fs_offset_t filesize;
4128 if (filesizepointer)
4129 *filesizepointer = 0;
4130 if (!filename || !*filename)
4132 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
4135 if (filesizepointer)
4136 *filesizepointer = filesize;
4137 crc = CRC_Block(filedata, filesize);
4143 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
4146 unsigned char *out = NULL;
4150 #ifndef LINK_TO_ZLIB
4155 memset(&strm, 0, sizeof(strm));
4156 strm.zalloc = Z_NULL;
4157 strm.zfree = Z_NULL;
4158 strm.opaque = Z_NULL;
4161 level = Z_DEFAULT_COMPRESSION;
4163 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
4165 Con_Printf("FS_Deflate: deflate init error!\n");
4169 strm.next_in = (unsigned char*)data;
4170 strm.avail_in = (unsigned int)size;
4172 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
4175 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4176 qz_deflateEnd(&strm);
4180 strm.next_out = tmp;
4181 strm.avail_out = (unsigned int)size;
4183 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4185 Con_Printf("FS_Deflate: deflate failed!\n");
4186 qz_deflateEnd(&strm);
4191 if(qz_deflateEnd(&strm) != Z_OK)
4193 Con_Printf("FS_Deflate: deflateEnd failed\n");
4198 if(strm.total_out >= size)
4200 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4205 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4208 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4213 *deflated_size = (size_t)strm.total_out;
4215 memcpy(out, tmp, strm.total_out);
4221 static void AssertBufsize(sizebuf_t *buf, int length)
4223 if(buf->cursize + length > buf->maxsize)
4225 int oldsize = buf->maxsize;
4226 unsigned char *olddata;
4227 olddata = buf->data;
4228 buf->maxsize += length;
4229 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4232 memcpy(buf->data, olddata, oldsize);
4238 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4242 unsigned char *out = NULL;
4243 unsigned char tmp[2048];
4248 #ifndef LINK_TO_ZLIB
4253 memset(&outbuf, 0, sizeof(outbuf));
4254 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4255 outbuf.maxsize = sizeof(tmp);
4257 memset(&strm, 0, sizeof(strm));
4258 strm.zalloc = Z_NULL;
4259 strm.zfree = Z_NULL;
4260 strm.opaque = Z_NULL;
4262 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4264 Con_Printf("FS_Inflate: inflate init error!\n");
4265 Mem_Free(outbuf.data);
4269 strm.next_in = (unsigned char*)data;
4270 strm.avail_in = (unsigned int)size;
4274 strm.next_out = tmp;
4275 strm.avail_out = sizeof(tmp);
4276 ret = qz_inflate(&strm, Z_NO_FLUSH);
4277 // it either returns Z_OK on progress, Z_STREAM_END on end
4285 case Z_STREAM_ERROR:
4286 Con_Print("FS_Inflate: stream error!\n");
4289 Con_Print("FS_Inflate: data error!\n");
4292 Con_Print("FS_Inflate: mem error!\n");
4295 Con_Print("FS_Inflate: buf error!\n");
4298 Con_Print("FS_Inflate: unknown error!\n");
4302 if(ret != Z_OK && ret != Z_STREAM_END)
4304 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4305 Mem_Free(outbuf.data);
4306 qz_inflateEnd(&strm);
4309 have = sizeof(tmp) - strm.avail_out;
4310 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4311 SZ_Write(&outbuf, tmp, have);
4312 } while(ret != Z_STREAM_END);
4314 qz_inflateEnd(&strm);
4316 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4319 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4320 Mem_Free(outbuf.data);
4324 memcpy(out, outbuf.data, outbuf.cursize);
4325 Mem_Free(outbuf.data);
4327 *inflated_size = (size_t)outbuf.cursize;