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
52 // Win32 requires us to add O_BINARY, but the other OSes don't have it
57 // In case the system doesn't support the O_NONBLOCK flag
62 // largefile support for Win32
65 # define lseek _lseeki64
68 // suppress deprecated warnings
73 # define unlink _unlink
80 typedef SDL_RWops *filedesc_t;
81 # define FILEDESC_INVALID NULL
82 # define FILEDESC_ISVALID(fd) ((fd) != NULL)
83 # define FILEDESC_READ(fd,buf,count) ((fs_offset_t)SDL_RWread(fd, buf, 1, count))
84 # define FILEDESC_WRITE(fd,buf,count) ((fs_offset_t)SDL_RWwrite(fd, buf, 1, count))
85 # define FILEDESC_CLOSE SDL_RWclose
86 # define FILEDESC_SEEK SDL_RWseek
87 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
88 filedesc_t new_fd = SDL_RWFromFile(filename, "rb");
89 if (SDL_RWseek(new_fd, SDL_RWseek(fd, 0, RW_SEEK_CUR), RW_SEEK_SET) < 0) {
95 # define unlink(name) Con_DPrintf("Sorry, no unlink support when trying to unlink %s.\n", (name))
97 typedef int filedesc_t;
98 # define FILEDESC_INVALID -1
99 # define FILEDESC_ISVALID(fd) ((fd) != -1)
100 # define FILEDESC_READ read
101 # define FILEDESC_WRITE write
102 # define FILEDESC_CLOSE close
103 # define FILEDESC_SEEK lseek
104 static filedesc_t FILEDESC_DUP(const char *filename, filedesc_t fd) {
109 /** \page fs File System
111 All of Quake's data access is through a hierchal file system, but the contents
112 of the file system can be transparently merged from several sources.
114 The "base directory" is the path to the directory holding the quake.exe and
115 all game directories. The sys_* files pass this to host_init in
116 quakeparms_t->basedir. This can be overridden with the "-basedir" command
117 line parm to allow code debugging in a different directory. The base
118 directory is only used during filesystem initialization.
120 The "game directory" is the first tree on the search path and directory that
121 all generated files (savegames, screenshots, demos, config files) will be
122 saved to. This can be overridden with the "-game" command line parameter.
123 The game directory can never be changed while quake is executing. This is a
124 precaution against having a malicious server instruct clients to write files
125 over areas they shouldn't.
131 =============================================================================
135 =============================================================================
138 // Magic numbers of a ZIP file (big-endian format)
139 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
140 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
141 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
143 // Other constants for ZIP files
144 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
145 #define ZIP_END_CDIR_SIZE 22
146 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
147 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
152 #define qz_inflate inflate
153 #define qz_inflateEnd inflateEnd
154 #define qz_inflateInit2_ inflateInit2_
155 #define qz_inflateReset inflateReset
156 #define qz_deflateInit2_ deflateInit2_
157 #define qz_deflateEnd deflateEnd
158 #define qz_deflate deflate
159 #define Z_MEMLEVEL_DEFAULT 8
162 // Zlib constants (from zlib.h)
163 #define Z_SYNC_FLUSH 2
166 #define Z_STREAM_END 1
167 #define Z_STREAM_ERROR (-2)
168 #define Z_DATA_ERROR (-3)
169 #define Z_MEM_ERROR (-4)
170 #define Z_BUF_ERROR (-5)
171 #define ZLIB_VERSION "1.2.3"
175 #define Z_MEMLEVEL_DEFAULT 8
178 #define Z_DEFAULT_COMPRESSION (-1)
180 #define Z_SYNC_FLUSH 2
181 #define Z_FULL_FLUSH 3
184 // Uncomment the following line if the zlib DLL you have still uses
185 // the 1.1.x series calling convention on Win32 (WINAPI)
186 //#define ZLIB_USES_WINAPI
190 =============================================================================
194 =============================================================================
197 /*! Zlib stream (from zlib.h)
198 * \warning: some pointers we don't use directly have
199 * been cast to "void*" for a matter of simplicity
203 unsigned char *next_in; ///< next input byte
204 unsigned int avail_in; ///< number of bytes available at next_in
205 unsigned long total_in; ///< total nb of input bytes read so far
207 unsigned char *next_out; ///< next output byte should be put there
208 unsigned int avail_out; ///< remaining free space at next_out
209 unsigned long total_out; ///< total nb of bytes output so far
211 char *msg; ///< last error message, NULL if no error
212 void *state; ///< not visible by applications
214 void *zalloc; ///< used to allocate the internal state
215 void *zfree; ///< used to free the internal state
216 void *opaque; ///< private data object passed to zalloc and zfree
218 int data_type; ///< best guess about the data type: ascii or binary
219 unsigned long adler; ///< adler32 value of the uncompressed data
220 unsigned long reserved; ///< reserved for future use
225 /// inside a package (PAK or PK3)
226 #define QFILE_FLAG_PACKED (1 << 0)
227 /// file is compressed using the deflate algorithm (PK3 only)
228 #define QFILE_FLAG_DEFLATED (1 << 1)
229 /// file is actually already loaded data
230 #define QFILE_FLAG_DATA (1 << 2)
231 /// real file will be removed on close
232 #define QFILE_FLAG_REMOVE (1 << 3)
234 #define FILE_BUFF_SIZE 2048
238 size_t comp_length; ///< length of the compressed file
239 size_t in_ind, in_len; ///< input buffer current index and length
240 size_t in_position; ///< position in the compressed file
241 unsigned char input [FILE_BUFF_SIZE];
247 filedesc_t handle; ///< file descriptor
248 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
249 fs_offset_t position; ///< current position in the file
250 fs_offset_t offset; ///< offset into the package (0 if external file)
251 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
254 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
255 unsigned char buff [FILE_BUFF_SIZE];
257 ztoolkit_t* ztk; ///< For zipped files.
259 const unsigned char *data; ///< For data files.
261 const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
265 // ------ PK3 files on disk ------ //
267 // You can get the complete ZIP format description from PKWARE website
269 typedef struct pk3_endOfCentralDir_s
271 unsigned int signature;
272 unsigned short disknum;
273 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
274 unsigned short localentries; ///< number of entries in the central directory on this disk
275 unsigned short nbentries; ///< total number of entries in the central directory on this disk
276 unsigned int cdir_size; ///< size of the central directory
277 unsigned int cdir_offset; ///< with respect to the starting disk number
278 unsigned short comment_size;
279 fs_offset_t prepended_garbage;
280 } pk3_endOfCentralDir_t;
283 // ------ PAK files on disk ------ //
284 typedef struct dpackfile_s
287 int filepos, filelen;
290 typedef struct dpackheader_s
298 /*! \name Packages in memory
301 /// the offset in packfile_t is the true contents offset
302 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
303 /// file compressed using the deflate algorithm
304 #define PACKFILE_FLAG_DEFLATED (1 << 1)
305 /// file is a symbolic link
306 #define PACKFILE_FLAG_SYMLINK (1 << 2)
308 typedef struct packfile_s
310 char name [MAX_QPATH];
313 fs_offset_t packsize; ///< size in the package
314 fs_offset_t realsize; ///< real file size (uncompressed)
317 typedef struct pack_s
319 char filename [MAX_OSPATH];
320 char shortname [MAX_QPATH];
322 int ignorecase; ///< PK3 ignores case
329 /// Search paths for files (including packages)
330 typedef struct searchpath_s
332 // only one of filename / pack will be used
333 char filename[MAX_OSPATH];
335 struct searchpath_s *next;
340 =============================================================================
344 =============================================================================
349 void FS_Which_f(void);
351 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
352 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
353 fs_offset_t offset, fs_offset_t packsize,
354 fs_offset_t realsize, int flags);
358 =============================================================================
362 =============================================================================
365 mempool_t *fs_mempool;
366 void *fs_mutex = NULL;
368 searchpath_t *fs_searchpaths = NULL;
369 const char *const fs_checkgamedir_missing = "missing";
371 #define MAX_FILES_IN_PACK 65536
373 char fs_userdir[MAX_OSPATH];
374 char fs_gamedir[MAX_OSPATH];
375 char fs_basedir[MAX_OSPATH];
376 static pack_t *fs_selfpack = NULL;
378 // list of active game directories (empty if not running a mod)
379 int fs_numgamedirs = 0;
380 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
382 // list of all gamedirs with modinfo.txt
383 gamedir_t *fs_all_gamedirs = NULL;
384 int fs_all_gamedirs_count = 0;
386 cvar_t scr_screenshot_name = {CVAR_NORESETTODEFAULTS, "scr_screenshot_name","dp", "prefix name for saved screenshots (changes based on -game commandline, as well as which game mode is running; the date is encoded using strftime escapes)"};
387 cvar_t fs_empty_files_in_pack_mark_deletions = {0, "fs_empty_files_in_pack_mark_deletions", "0", "if enabled, empty files in a pak/pk3 count as not existing but cancel the search in further packs, effectively allowing patch pak/pk3 files to 'delete' files"};
388 cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
392 =============================================================================
394 PRIVATE FUNCTIONS - PK3 HANDLING
396 =============================================================================
400 // Functions exported from zlib
401 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
402 # define ZEXPORT WINAPI
407 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
408 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
409 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
410 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
411 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
412 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
413 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
416 #define qz_inflateInit2(strm, windowBits) \
417 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
418 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
419 qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
422 // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
424 static dllfunction_t zlibfuncs[] =
426 {"inflate", (void **) &qz_inflate},
427 {"inflateEnd", (void **) &qz_inflateEnd},
428 {"inflateInit2_", (void **) &qz_inflateInit2_},
429 {"inflateReset", (void **) &qz_inflateReset},
430 {"deflateInit2_", (void **) &qz_deflateInit2_},
431 {"deflateEnd", (void **) &qz_deflateEnd},
432 {"deflate", (void **) &qz_deflate},
436 /// Handle for Zlib DLL
437 static dllhandle_t zlib_dll = NULL;
441 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
442 static dllfunction_t shfolderfuncs[] =
444 {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
447 static const char* shfolderdllnames [] =
449 "shfolder.dll", // IE 4, or Win NT and higher
452 static dllhandle_t shfolder_dll = NULL;
454 const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}};
455 #define qREFKNOWNFOLDERID const GUID *
456 #define qKF_FLAG_CREATE 0x8000
457 #define qKF_FLAG_NO_ALIAS 0x1000
458 static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
459 static dllfunction_t shell32funcs[] =
461 {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath},
464 static const char* shell32dllnames [] =
466 "shell32.dll", // Vista and higher
469 static dllhandle_t shell32_dll = NULL;
471 static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit);
472 static void (WINAPI *qCoUninitialize)(void);
473 static void (WINAPI *qCoTaskMemFree)(LPVOID pv);
474 static dllfunction_t ole32funcs[] =
476 {"CoInitializeEx", (void **) &qCoInitializeEx},
477 {"CoUninitialize", (void **) &qCoUninitialize},
478 {"CoTaskMemFree", (void **) &qCoTaskMemFree},
481 static const char* ole32dllnames [] =
483 "ole32.dll", // 2000 and higher
486 static dllhandle_t ole32_dll = NULL;
496 static void PK3_CloseLibrary (void)
499 Sys_UnloadLibrary (&zlib_dll);
508 Try to load the Zlib DLL
511 static qboolean PK3_OpenLibrary (void)
516 const char* dllnames [] =
519 # ifdef ZLIB_USES_WINAPI
525 #elif defined(MACOSX)
539 return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
547 See if zlib is available
550 qboolean FS_HasZlib(void)
555 PK3_OpenLibrary(); // to be safe
556 return (zlib_dll != 0);
562 PK3_GetEndOfCentralDir
564 Extract the end of the central directory from a PK3 package
567 static qboolean PK3_GetEndOfCentralDir (const char *packfile, filedesc_t packhandle, pk3_endOfCentralDir_t *eocd)
569 fs_offset_t filesize, maxsize;
570 unsigned char *buffer, *ptr;
573 // Get the package size
574 filesize = FILEDESC_SEEK (packhandle, 0, SEEK_END);
575 if (filesize < ZIP_END_CDIR_SIZE)
578 // Load the end of the file in memory
579 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
582 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
583 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
584 FILEDESC_SEEK (packhandle, filesize - maxsize, SEEK_SET);
585 if (FILEDESC_READ (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
591 // Look for the end of central dir signature around the end of the file
592 maxsize -= ZIP_END_CDIR_SIZE;
593 ptr = &buffer[maxsize];
595 while (BuffBigLong (ptr) != ZIP_END_HEADER)
607 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
608 eocd->signature = LittleLong (eocd->signature);
609 eocd->disknum = LittleShort (eocd->disknum);
610 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
611 eocd->localentries = LittleShort (eocd->localentries);
612 eocd->nbentries = LittleShort (eocd->nbentries);
613 eocd->cdir_size = LittleLong (eocd->cdir_size);
614 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
615 eocd->comment_size = LittleShort (eocd->comment_size);
616 eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
617 eocd->cdir_offset += eocd->prepended_garbage;
622 eocd->cdir_size > filesize ||
623 eocd->cdir_offset >= filesize ||
624 eocd->cdir_offset + eocd->cdir_size > filesize
627 // Obviously invalid central directory.
639 Extract the file list from a PK3 file
642 static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
644 unsigned char *central_dir, *ptr;
646 fs_offset_t remaining;
648 // Load the central directory in memory
649 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
650 if (FILEDESC_SEEK (pack->handle, eocd->cdir_offset, SEEK_SET) == -1)
652 Mem_Free (central_dir);
655 if(FILEDESC_READ (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
657 Mem_Free (central_dir);
661 // Extract the files properties
662 // The parsing is done "by hand" because some fields have variable sizes and
663 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
664 remaining = eocd->cdir_size;
667 for (ind = 0; ind < eocd->nbentries; ind++)
669 fs_offset_t namesize, count;
671 // Checking the remaining size
672 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
674 Mem_Free (central_dir);
677 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
680 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
682 Mem_Free (central_dir);
686 namesize = BuffLittleShort (&ptr[28]); // filename length
688 // Check encryption, compression, and attributes
689 // 1st uint8 : general purpose bit flag
690 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
692 // LordHavoc: bit 3 would be a problem if we were scanning the archive
693 // but is not a problem in the central directory where the values are
696 // bit 3 seems to always be set by the standard Mac OSX zip maker
698 // 2nd uint8 : external file attributes
699 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
700 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
702 // Still enough bytes for the name?
703 if (namesize < 0 || remaining < namesize || namesize >= (int)sizeof (*pack->files))
705 Mem_Free (central_dir);
709 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
710 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
712 char filename [sizeof (pack->files[0].name)];
713 fs_offset_t offset, packsize, realsize;
716 // Extract the name (strip it if necessary)
717 namesize = min(namesize, (int)sizeof (filename) - 1);
718 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
719 filename[namesize] = '\0';
721 if (BuffLittleShort (&ptr[10]))
722 flags = PACKFILE_FLAG_DEFLATED;
725 offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
726 packsize = (unsigned int)BuffLittleLong (&ptr[20]);
727 realsize = (unsigned int)BuffLittleLong (&ptr[24]);
729 switch(ptr[5]) // C_VERSION_MADE_BY_1
734 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
735 // can't use S_ISLNK here, as this has to compile on non-UNIX too
736 flags |= PACKFILE_FLAG_SYMLINK;
740 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
744 // Skip the name, additionnal field, and comment
745 // 1er uint16 : extra field length
746 // 2eme uint16 : file comment length
747 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
748 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
752 // If the package is empty, central_dir is NULL here
753 if (central_dir != NULL)
754 Mem_Free (central_dir);
755 return pack->numfiles;
763 Create a package entry associated with a PK3 file
766 static pack_t *FS_LoadPackPK3FromFD (const char *packfile, filedesc_t packhandle, qboolean silent)
768 pk3_endOfCentralDir_t eocd;
772 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
775 Con_Printf ("%s is not a PK3 file\n", packfile);
776 FILEDESC_CLOSE(packhandle);
780 // Multi-volume ZIP archives are NOT allowed
781 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
783 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
784 FILEDESC_CLOSE(packhandle);
788 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
789 // since eocd.nbentries is an unsigned 16 bits integer
790 #if MAX_FILES_IN_PACK < 65535
791 if (eocd.nbentries > MAX_FILES_IN_PACK)
793 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
794 FILEDESC_CLOSE(packhandle);
799 // Create a package structure in memory
800 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
801 pack->ignorecase = true; // PK3 ignores case
802 strlcpy (pack->filename, packfile, sizeof (pack->filename));
803 pack->handle = packhandle;
804 pack->numfiles = eocd.nbentries;
805 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
807 real_nb_files = PK3_BuildFileList (pack, &eocd);
808 if (real_nb_files < 0)
810 Con_Printf ("%s is not a valid PK3 file\n", packfile);
811 FILEDESC_CLOSE(pack->handle);
816 Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
820 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking);
821 static pack_t *FS_LoadPackPK3 (const char *packfile)
823 filedesc_t packhandle;
824 packhandle = FS_SysOpenFiledesc (packfile, "rb", false);
825 if (!FILEDESC_ISVALID(packhandle))
827 return FS_LoadPackPK3FromFD(packfile, packhandle, false);
833 PK3_GetTrueFileOffset
835 Find where the true file data offset is
838 static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
840 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
844 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
847 // Load the local file description
848 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
850 Con_Printf ("Can't seek in package %s\n", pack->filename);
853 count = FILEDESC_READ (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
854 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
856 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
860 // Skip name and extra field
861 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
863 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
869 =============================================================================
871 OTHER PRIVATE FUNCTIONS
873 =============================================================================
881 Add a file to the list of files contained into a package
884 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
885 fs_offset_t offset, fs_offset_t packsize,
886 fs_offset_t realsize, int flags)
888 int (*strcmp_funct) (const char* str1, const char* str2);
889 int left, right, middle;
892 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
894 // Look for the slot we should put that file into (binary search)
896 right = pack->numfiles - 1;
897 while (left <= right)
901 middle = (left + right) / 2;
902 diff = strcmp_funct (pack->files[middle].name, name);
904 // If we found the file, there's a problem
906 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
908 // If we're too far in the list
915 // We have to move the right of the list by one slot to free the one we need
916 pfile = &pack->files[left];
917 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
920 strlcpy (pfile->name, name, sizeof (pfile->name));
921 pfile->offset = offset;
922 pfile->packsize = packsize;
923 pfile->realsize = realsize;
924 pfile->flags = flags;
930 static void FS_mkdir (const char *path)
932 if(COM_CheckParm("-readonly"))
936 if (_mkdir (path) == -1)
938 if (mkdir (path, 0777) == -1)
941 // No logging for this. The only caller is FS_CreatePath (which
942 // calls it in ways that will intentionally produce EEXIST),
943 // and its own callers always use the directory afterwards and
944 // thus will detect failure that way.
953 Only used for FS_OpenRealFile.
956 void FS_CreatePath (char *path)
960 for (ofs = path+1 ; *ofs ; ofs++)
962 if (*ofs == '/' || *ofs == '\\')
964 // create the directory
980 static void FS_Path_f (void)
984 Con_Print("Current search path:\n");
985 for (s=fs_searchpaths ; s ; s=s->next)
990 Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
992 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
995 Con_Printf("%s\n", s->filename);
1005 /*! Takes an explicit (not game tree related) path to a pak file.
1006 *Loads the header and directory, adding the files at the beginning
1007 *of the list so they override previous pack files.
1009 static pack_t *FS_LoadPackPAK (const char *packfile)
1011 dpackheader_t header;
1012 int i, numpackfiles;
1013 filedesc_t packhandle;
1017 packhandle = FS_SysOpenFiledesc(packfile, "rb", false);
1018 if (!FILEDESC_ISVALID(packhandle))
1020 if(FILEDESC_READ (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
1022 Con_Printf ("%s is not a packfile\n", packfile);
1023 FILEDESC_CLOSE(packhandle);
1026 if (memcmp(header.id, "PACK", 4))
1028 Con_Printf ("%s is not a packfile\n", packfile);
1029 FILEDESC_CLOSE(packhandle);
1032 header.dirofs = LittleLong (header.dirofs);
1033 header.dirlen = LittleLong (header.dirlen);
1035 if (header.dirlen % sizeof(dpackfile_t))
1037 Con_Printf ("%s has an invalid directory size\n", packfile);
1038 FILEDESC_CLOSE(packhandle);
1042 numpackfiles = header.dirlen / sizeof(dpackfile_t);
1044 if (numpackfiles < 0 || numpackfiles > MAX_FILES_IN_PACK)
1046 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
1047 FILEDESC_CLOSE(packhandle);
1051 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
1052 FILEDESC_SEEK (packhandle, header.dirofs, SEEK_SET);
1053 if(header.dirlen != FILEDESC_READ (packhandle, (void *)info, header.dirlen))
1055 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
1057 FILEDESC_CLOSE(packhandle);
1061 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1062 pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2
1063 strlcpy (pack->filename, packfile, sizeof (pack->filename));
1064 pack->handle = packhandle;
1066 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
1068 // parse the directory
1069 for (i = 0;i < numpackfiles;i++)
1071 fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
1072 fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
1074 // Ensure a zero terminated file name (required by format).
1075 info[i].name[sizeof(info[i].name) - 1] = 0;
1077 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
1082 Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
1087 ====================
1090 Create a package entry associated with a directory file
1091 ====================
1093 static pack_t *FS_LoadPackVirtual (const char *dirname)
1096 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1098 pack->ignorecase = false;
1099 strlcpy (pack->filename, dirname, sizeof(pack->filename));
1100 pack->handle = FILEDESC_INVALID;
1101 pack->numfiles = -1;
1103 Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
1112 /*! Adds the given pack to the search path.
1113 * The pack type is autodetected by the file extension.
1115 * Returns true if the file was successfully added to the
1116 * search path or if it was already included.
1118 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1119 * plain directories.
1122 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
1124 searchpath_t *search;
1126 const char *ext = FS_FileExtension(pakfile);
1129 for(search = fs_searchpaths; search; search = search->next)
1131 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1134 *already_loaded = true;
1135 return true; // already loaded
1140 *already_loaded = false;
1142 if(!strcasecmp(ext, "pk3dir"))
1143 pak = FS_LoadPackVirtual (pakfile);
1144 else if(!strcasecmp(ext, "pak"))
1145 pak = FS_LoadPackPAK (pakfile);
1146 else if(!strcasecmp(ext, "pk3"))
1147 pak = FS_LoadPackPK3 (pakfile);
1148 else if(!strcasecmp(ext, "obb")) // android apk expansion
1149 pak = FS_LoadPackPK3 (pakfile);
1151 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1155 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1157 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
1160 // find the first item whose next one is a pack or NULL
1161 searchpath_t *insertion_point = 0;
1162 if(fs_searchpaths && !fs_searchpaths->pack)
1164 insertion_point = fs_searchpaths;
1167 if(!insertion_point->next)
1169 if(insertion_point->next->pack)
1171 insertion_point = insertion_point->next;
1174 // If insertion_point is NULL, this means that either there is no
1175 // item in the list yet, or that the very first item is a pack. In
1176 // that case, we want to insert at the beginning...
1177 if(!insertion_point)
1179 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1180 search->next = fs_searchpaths;
1181 fs_searchpaths = search;
1184 // otherwise we want to append directly after insertion_point.
1186 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1187 search->next = insertion_point->next;
1188 insertion_point->next = search;
1193 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1194 search->next = fs_searchpaths;
1195 fs_searchpaths = search;
1200 dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1201 // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
1202 // same goes for the name inside the pack structure
1203 l = strlen(pak->shortname);
1205 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
1206 pak->shortname[l - 3] = 0;
1207 l = strlen(pak->filename);
1209 if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
1210 pak->filename[l - 3] = 0;
1216 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1227 /*! Adds the given pack to the search path and searches for it in the game path.
1228 * The pack type is autodetected by the file extension.
1230 * Returns true if the file was successfully added to the
1231 * search path or if it was already included.
1233 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1234 * plain directories.
1236 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1238 char fullpath[MAX_OSPATH];
1240 searchpath_t *search;
1243 *already_loaded = false;
1245 // then find the real name...
1246 search = FS_FindFile(pakfile, &index, true);
1247 if(!search || search->pack)
1249 Con_Printf("could not find pak \"%s\"\n", pakfile);
1253 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1255 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1263 Sets fs_gamedir, adds the directory to the head of the path,
1264 then loads and adds pak1.pak pak2.pak ...
1267 static void FS_AddGameDirectory (const char *dir)
1271 searchpath_t *search;
1273 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1275 stringlistinit(&list);
1276 listdirectory(&list, "", dir);
1277 stringlistsort(&list, false);
1279 // add any PAK package in the directory
1280 for (i = 0;i < list.numstrings;i++)
1282 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1284 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1288 // add any PK3 package in the directory
1289 for (i = 0;i < list.numstrings;i++)
1291 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
1293 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1297 stringlistfreecontents(&list);
1299 // Add the directory to the search path
1300 // (unpacked files have the priority over packed files)
1301 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1302 strlcpy (search->filename, dir, sizeof (search->filename));
1303 search->next = fs_searchpaths;
1304 fs_searchpaths = search;
1313 static void FS_AddGameHierarchy (const char *dir)
1316 // Add the common game directory
1317 FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
1320 FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
1329 const char *FS_FileExtension (const char *in)
1331 const char *separator, *backslash, *colon, *dot;
1333 separator = strrchr(in, '/');
1334 backslash = strrchr(in, '\\');
1335 if (!separator || separator < backslash)
1336 separator = backslash;
1337 colon = strrchr(in, ':');
1338 if (!separator || separator < colon)
1341 dot = strrchr(in, '.');
1342 if (dot == NULL || (separator && (dot < separator)))
1354 const char *FS_FileWithoutPath (const char *in)
1356 const char *separator, *backslash, *colon;
1358 separator = strrchr(in, '/');
1359 backslash = strrchr(in, '\\');
1360 if (!separator || separator < backslash)
1361 separator = backslash;
1362 colon = strrchr(in, ':');
1363 if (!separator || separator < colon)
1365 return separator ? separator + 1 : in;
1374 static void FS_ClearSearchPath (void)
1376 // unload all packs and directory information, close all pack files
1377 // (if a qfile is still reading a pack it won't be harmed because it used
1378 // dup() to get its own handle already)
1379 while (fs_searchpaths)
1381 searchpath_t *search = fs_searchpaths;
1382 fs_searchpaths = search->next;
1383 if (search->pack && search->pack != fs_selfpack)
1385 if(!search->pack->vpack)
1388 FILEDESC_CLOSE(search->pack->handle);
1389 // free any memory associated with it
1390 if (search->pack->files)
1391 Mem_Free(search->pack->files);
1393 Mem_Free(search->pack);
1399 static void FS_AddSelfPack(void)
1403 searchpath_t *search;
1404 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1405 search->next = fs_searchpaths;
1406 search->pack = fs_selfpack;
1407 fs_searchpaths = search;
1417 void FS_Rescan (void)
1420 qboolean fs_modified = false;
1421 qboolean reset = false;
1422 char gamedirbuf[MAX_INPUTLINE];
1427 FS_ClearSearchPath();
1429 // automatically activate gamemode for the gamedirs specified
1431 COM_ChangeGameTypeForGameDirs();
1433 // add the game-specific paths
1434 // gamedirname1 (typically id1)
1435 FS_AddGameHierarchy (gamedirname1);
1436 // update the com_modname (used for server info)
1437 if (gamedirname2 && gamedirname2[0])
1438 strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1440 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1442 // add the game-specific path, if any
1443 // (only used for mission packs and the like, which should set fs_modified)
1444 if (gamedirname2 && gamedirname2[0])
1447 FS_AddGameHierarchy (gamedirname2);
1451 // Adds basedir/gamedir as an override game
1452 // LordHavoc: now supports multiple -game directories
1453 // set the com_modname (reported in server info)
1455 for (i = 0;i < fs_numgamedirs;i++)
1458 FS_AddGameHierarchy (fs_gamedirs[i]);
1459 // update the com_modname (used server info)
1460 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1462 strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1464 strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1466 Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1468 // add back the selfpack as new first item
1471 // set the default screenshot name to either the mod name or the
1472 // gamemode screenshot name
1473 if (strcmp(com_modname, gamedirname1))
1474 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1476 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1478 if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1479 strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1481 // If "-condebug" is in the command line, remove the previous log file
1482 if (COM_CheckParm ("-condebug") != 0)
1483 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1485 // look for the pop.lmp file and set registered to true if it is found
1486 if (FS_FileExists("gfx/pop.lmp"))
1487 Cvar_Set ("registered", "1");
1493 if (!registered.integer)
1496 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1498 Con_Print("Playing shareware version.\n");
1501 Con_Print("Playing registered version.\n");
1503 case GAME_STEELSTORM:
1504 if (registered.integer)
1505 Con_Print("Playing registered version.\n");
1507 Con_Print("Playing shareware version.\n");
1513 // unload all wads so that future queries will return the new data
1517 static void FS_Rescan_f(void)
1527 extern qboolean vid_opened;
1528 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1533 if (fs_numgamedirs == numgamedirs)
1535 for (i = 0;i < numgamedirs;i++)
1536 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1538 if (i == numgamedirs)
1539 return true; // already using this set of gamedirs, do nothing
1542 if (numgamedirs > MAX_GAMEDIRS)
1545 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1546 return false; // too many gamedirs
1549 for (i = 0;i < numgamedirs;i++)
1551 // if string is nasty, reject it
1552 p = FS_CheckGameDir(gamedirs[i]);
1556 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1557 return false; // nasty gamedirs
1559 if(p == fs_checkgamedir_missing && failmissing)
1562 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1563 return false; // missing gamedirs
1569 fs_numgamedirs = numgamedirs;
1570 for (i = 0;i < fs_numgamedirs;i++)
1571 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1573 // reinitialize filesystem to detect the new paks
1576 if (cls.demoplayback)
1582 // unload all sounds so they will be reloaded from the new files as needed
1583 S_UnloadAllSounds_f();
1585 // close down the video subsystem, it will start up again when the config finishes...
1589 // restart the video subsystem after the config is executed
1590 Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
1600 static void FS_GameDir_f (void)
1604 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1608 Con_Printf("gamedirs active:");
1609 for (i = 0;i < fs_numgamedirs;i++)
1610 Con_Printf(" %s", fs_gamedirs[i]);
1615 numgamedirs = Cmd_Argc() - 1;
1616 if (numgamedirs > MAX_GAMEDIRS)
1618 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1622 for (i = 0;i < numgamedirs;i++)
1623 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1625 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1627 // actually, changing during game would work fine, but would be stupid
1628 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1632 // halt demo playback to close the file
1635 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1638 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1646 stringlistinit(&list);
1647 listdirectory(&list, gamedir, "");
1648 success = list.numstrings > 0;
1649 stringlistfreecontents(&list);
1653 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1656 n = FS_Read (f, buf, buflength - 1);
1676 const char *FS_CheckGameDir(const char *gamedir)
1679 static char buf[8192];
1682 if (FS_CheckNastyPath(gamedir, true))
1685 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1690 // get description from basedir
1691 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1699 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1703 return fs_checkgamedir_missing;
1706 static void FS_ListGameDirs(void)
1708 stringlist_t list, list2;
1713 fs_all_gamedirs_count = 0;
1715 Mem_Free(fs_all_gamedirs);
1717 stringlistinit(&list);
1718 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1719 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1720 stringlistsort(&list, false);
1722 stringlistinit(&list2);
1723 for(i = 0; i < list.numstrings; ++i)
1726 if(!strcmp(list.strings[i-1], list.strings[i]))
1728 info = FS_CheckGameDir(list.strings[i]);
1731 if(info == fs_checkgamedir_missing)
1735 stringlistappend(&list2, list.strings[i]);
1737 stringlistfreecontents(&list);
1739 fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1740 for(i = 0; i < list2.numstrings; ++i)
1742 info = FS_CheckGameDir(list2.strings[i]);
1743 // all this cannot happen any more, but better be safe than sorry
1746 if(info == fs_checkgamedir_missing)
1750 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1751 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1752 ++fs_all_gamedirs_count;
1758 #pragma comment(lib, "shell32.lib")
1763 static void COM_InsertFlags(const char *buf) {
1766 const char **new_argv;
1768 int args_left = 256;
1769 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
1771 new_argv[0] = "dummy"; // Can't really happen.
1773 new_argv[0] = com_argv[0];
1776 while(COM_ParseToken_Console(&p))
1778 size_t sz = strlen(com_token) + 1; // shut up clang
1781 q = (char *)Mem_Alloc(fs_mempool, sz);
1782 strlcpy(q, com_token, sz);
1786 // Now: i <= args_left + 1.
1789 memcpy((char *)(&new_argv[i]), &com_argv[1], sizeof(*com_argv) * (com_argc - 1));
1792 // Now: i <= args_left + (com_argc || 1).
1794 com_argv = new_argv;
1803 void FS_Init_SelfPack (void)
1806 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1808 // Load darkplaces.opt from the FS.
1809 if (!COM_CheckParm("-noopt"))
1811 char *buf = (char *) FS_SysLoadFile("darkplaces.opt", tempmempool, true, NULL);
1813 COM_InsertFlags(buf);
1818 // Provide the SelfPack.
1819 if (!COM_CheckParm("-noselfpack"))
1821 if (com_selffd >= 0)
1823 fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
1827 if (!COM_CheckParm("-noopt"))
1829 char *buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
1831 COM_InsertFlags(buf);
1840 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1842 #if defined(__IPHONEOS__)
1843 if (userdirmode == USERDIRMODE_HOME)
1845 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1846 // fs_userdir stores configurations to the Documents folder of the app
1847 strlcpy(userdir, "../Documents/", MAX_OSPATH);
1852 #elif defined(WIN32)
1854 #if _MSC_VER >= 1400
1857 TCHAR mydocsdir[MAX_PATH + 1];
1858 wchar_t *savedgamesdirw;
1859 char savedgamesdir[MAX_OSPATH];
1868 case USERDIRMODE_NOHOME:
1869 strlcpy(userdir, fs_basedir, userdirsize);
1871 case USERDIRMODE_MYGAMES:
1873 Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1875 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
1877 dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1880 #if _MSC_VER >= 1400
1881 _dupenv_s(&homedir, &homedirlen, "USERPROFILE");
1884 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1889 homedir = getenv("USERPROFILE");
1892 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1897 case USERDIRMODE_SAVEDGAMES:
1899 Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
1901 Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
1902 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1904 savedgamesdir[0] = 0;
1905 qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1908 if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1910 if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1913 if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1915 memset(savedgamesdir, 0, sizeof(savedgamesdir));
1916 #if _MSC_VER >= 1400
1917 wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
1919 wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
1921 qCoTaskMemFree(savedgamesdirw);
1924 if (savedgamesdir[0])
1926 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
1941 case USERDIRMODE_NOHOME:
1942 strlcpy(userdir, fs_basedir, userdirsize);
1944 case USERDIRMODE_HOME:
1945 homedir = getenv("HOME");
1948 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1952 case USERDIRMODE_SAVEDGAMES:
1953 homedir = getenv("HOME");
1957 dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
1959 // the XDG say some files would need to go in:
1960 // XDG_CONFIG_HOME (or ~/.config/%s/)
1961 // XDG_DATA_HOME (or ~/.local/share/%s/)
1962 // XDG_CACHE_HOME (or ~/.cache/%s/)
1963 // and also search the following global locations if defined:
1964 // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
1965 // XDG_DATA_DIRS (normally /usr/share/%s/)
1966 // this would be too complicated...
1976 #if !defined(__IPHONEOS__)
1979 // historical behavior...
1980 if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
1981 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
1984 // see if we can write to this path (note: won't create path)
1986 // no access() here, we must try to open the file for appending
1987 fd = FS_SysOpenFiledesc(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
1991 // on Unix, we don't need to ACTUALLY attempt to open the file
1992 if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
1999 return 1; // good choice - the path exists and is writable
2003 if (userdirmode == USERDIRMODE_NOHOME)
2004 return -1; // path usually already exists, we lack permissions
2006 return 0; // probably good - failed to write but maybe we need to create path
2026 // Overrides the system supplied base directory (under GAMENAME)
2027 // 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)
2028 i = COM_CheckParm ("-basedir");
2029 if (i && i < com_argc-1)
2031 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
2032 i = (int)strlen (fs_basedir);
2033 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
2034 fs_basedir[i-1] = 0;
2038 // If the base directory is explicitly defined by the compilation process
2039 #ifdef DP_FS_BASEDIR
2040 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
2041 #elif defined(__ANDROID__)
2042 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
2043 #elif defined(MACOSX)
2044 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
2045 if (strstr(com_argv[0], ".app/"))
2048 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
2049 split = strstr(fs_basedir, ".app/");
2052 struct stat statresult;
2054 // truncate to just after the .app/
2056 // see if gamedir exists in Resources
2057 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
2059 // found gamedir inside Resources, use it
2060 strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
2064 // no gamedir found in Resources, gamedir is probably
2065 // outside the .app, remove .app part of path
2066 while (split > fs_basedir && *split != '/')
2075 // make sure the appending of a path separator won't create an unterminated string
2076 memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
2077 // add a path separator to the end of the basedir if it lacks one
2078 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
2079 strlcat(fs_basedir, "/", sizeof(fs_basedir));
2081 // Add the personal game directory
2082 if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
2083 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
2084 else if (COM_CheckParm("-nohome"))
2085 *fs_userdir = 0; // user wants roaming installation, no userdir
2088 #ifdef DP_FS_USERDIR
2089 strlcpy(fs_userdir, DP_FS_USERDIR, sizeof(fs_userdir));
2092 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2093 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2094 int userdirstatus[USERDIRMODE_COUNT];
2096 // historical behavior...
2097 if (!strcmp(gamedirname1, "id1"))
2098 preferreduserdirmode = USERDIRMODE_NOHOME;
2100 // check what limitations the user wants to impose
2101 if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2102 if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2103 if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2104 // gather the status of the possible userdirs
2105 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2107 userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2108 if (userdirstatus[dirmode] == 1)
2109 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2110 else if (userdirstatus[dirmode] == 0)
2111 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2113 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2115 // some games may prefer writing to basedir, but if write fails we
2116 // have to search for a real userdir...
2117 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2118 preferreduserdirmode = highestuserdirmode;
2119 // check for an existing userdir and continue using it if possible...
2120 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2121 if (userdirstatus[dirmode] == 1)
2123 // if no existing userdir found, make a new one...
2124 if (dirmode == 0 && preferreduserdirmode > 0)
2125 for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2126 if (userdirstatus[dirmode] >= 0)
2128 // and finally, we picked one...
2129 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2130 Con_DPrintf("userdir %i is the winner\n", dirmode);
2134 // if userdir equal to basedir, clear it to avoid confusion later
2135 if (!strcmp(fs_basedir, fs_userdir))
2140 p = FS_CheckGameDir(gamedirname1);
2141 if(!p || p == fs_checkgamedir_missing)
2142 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2146 p = FS_CheckGameDir(gamedirname2);
2147 if(!p || p == fs_checkgamedir_missing)
2148 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2152 // Adds basedir/gamedir as an override game
2153 // LordHavoc: now supports multiple -game directories
2154 for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2158 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
2161 p = FS_CheckGameDir(com_argv[i]);
2163 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
2164 if(p == fs_checkgamedir_missing)
2165 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
2166 // add the gamedir to the list of active gamedirs
2167 strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2172 // generate the searchpath
2175 if (Thread_HasThreads())
2176 fs_mutex = Thread_CreateMutex();
2179 void FS_Init_Commands(void)
2181 Cvar_RegisterVariable (&scr_screenshot_name);
2182 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2183 Cvar_RegisterVariable (&cvar_fs_gamedir);
2185 Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2186 Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2187 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
2188 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2189 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2190 Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2198 void FS_Shutdown (void)
2200 // close all pack files and such
2201 // (hopefully there aren't any other open files, but they'll be cleaned up
2202 // by the OS anyway)
2203 FS_ClearSearchPath();
2204 Mem_FreePool (&fs_mempool);
2205 PK3_CloseLibrary ();
2208 Sys_UnloadLibrary (&shfolder_dll);
2209 Sys_UnloadLibrary (&shell32_dll);
2210 Sys_UnloadLibrary (&ole32_dll);
2214 Thread_DestroyMutex(fs_mutex);
2217 static filedesc_t FS_SysOpenFiledesc(const char *filepath, const char *mode, qboolean nonblocking)
2219 filedesc_t handle = FILEDESC_INVALID;
2222 qboolean dolock = false;
2224 // Parse the mode string
2233 opt = O_CREAT | O_TRUNC;
2237 opt = O_CREAT | O_APPEND;
2240 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2241 return FILEDESC_INVALID;
2243 for (ind = 1; mode[ind] != '\0'; ind++)
2257 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2258 filepath, mode, mode[ind]);
2265 if(COM_CheckParm("-readonly") && mod != O_RDONLY)
2266 return FILEDESC_INVALID;
2270 return FILEDESC_INVALID;
2271 handle = SDL_RWFromFile(filepath, mode);
2274 # if _MSC_VER >= 1400
2275 _sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2277 handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2280 handle = open (filepath, mod | opt, 0666);
2281 if(handle >= 0 && dolock)
2284 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2285 l.l_whence = SEEK_SET;
2288 if(fcntl(handle, F_SETLK, &l) == -1)
2290 FILEDESC_CLOSE(handle);
2300 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
2305 return FS_SysOpenFiledesc(filepath, mode, nonblocking);
2310 ====================
2313 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2314 ====================
2316 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
2320 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2322 file->handle = FS_SysOpenFiledesc(filepath, mode, nonblocking);
2323 if (!FILEDESC_ISVALID(file->handle))
2329 file->filename = Mem_strdup(fs_mempool, filepath);
2331 file->real_length = FILEDESC_SEEK (file->handle, 0, SEEK_END);
2333 // For files opened in append mode, we start at the end of the file
2335 file->position = file->real_length;
2337 FILEDESC_SEEK (file->handle, 0, SEEK_SET);
2347 Open a packed file using its package file descriptor
2350 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2353 filedesc_t dup_handle;
2356 pfile = &pack->files[pack_ind];
2358 // If we don't have the true offset, get it now
2359 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2360 if (!PK3_GetTrueFileOffset (pfile, pack))
2363 #ifndef LINK_TO_ZLIB
2364 // No Zlib DLL = no compressed files
2365 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2367 Con_Printf("WARNING: can't open the compressed file %s\n"
2368 "You need the Zlib DLL to use compressed files\n",
2374 // LordHavoc: FILEDESC_SEEK affects all duplicates of a handle so we do it before
2375 // the dup() call to avoid having to close the dup_handle on error here
2376 if (FILEDESC_SEEK (pack->handle, pfile->offset, SEEK_SET) == -1)
2378 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2379 pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2383 dup_handle = FILEDESC_DUP (pack->filename, pack->handle);
2384 if (!FILEDESC_ISVALID(dup_handle))
2386 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2390 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2391 memset (file, 0, sizeof (*file));
2392 file->handle = dup_handle;
2393 file->flags = QFILE_FLAG_PACKED;
2394 file->real_length = pfile->realsize;
2395 file->offset = pfile->offset;
2399 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2403 file->flags |= QFILE_FLAG_DEFLATED;
2405 // We need some more variables
2406 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2408 ztk->comp_length = pfile->packsize;
2410 // Initialize zlib stream
2411 ztk->zstream.next_in = ztk->input;
2412 ztk->zstream.avail_in = 0;
2414 /* From Zlib's "unzip.c":
2416 * windowBits is passed < 0 to tell that there is no zlib header.
2417 * Note that in this case inflate *requires* an extra "dummy" byte
2418 * after the compressed stream in order to complete decompression and
2419 * return Z_STREAM_END.
2420 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2421 * size of both compressed and uncompressed data
2423 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2425 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2426 FILEDESC_CLOSE(dup_handle);
2431 ztk->zstream.next_out = file->buff;
2432 ztk->zstream.avail_out = sizeof (file->buff);
2441 ====================
2444 Return true if the path should be rejected due to one of the following:
2445 1: path elements that are non-portable
2446 2: path elements that would allow access to files outside the game directory,
2447 or are just not a good idea for a mod to be using.
2448 ====================
2450 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2452 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2456 // Windows: don't allow \ in filenames (windows-only), period.
2457 // (on Windows \ is a directory separator, but / is also supported)
2458 if (strstr(path, "\\"))
2459 return 1; // non-portable
2461 // Mac: don't allow Mac-only filenames - : is a directory separator
2462 // instead of /, but we rely on / working already, so there's no reason to
2463 // support a Mac-only path
2464 // Amiga and Windows: : tries to go to root of drive
2465 if (strstr(path, ":"))
2466 return 1; // non-portable attempt to go to root of drive
2468 // Amiga: // is parent directory
2469 if (strstr(path, "//"))
2470 return 1; // non-portable attempt to go to parent directory
2472 // all: don't allow going to parent directory (../ or /../)
2473 if (strstr(path, ".."))
2474 return 2; // attempt to go outside the game directory
2476 // Windows and UNIXes: don't allow absolute paths
2478 return 2; // attempt to go outside the game directory
2480 // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2481 if (strstr(path, "./"))
2482 return 2; // possible attempt to go outside the game directory
2484 // all: forbid trailing slash on gamedir
2485 if (isgamedir && path[strlen(path)-1] == '/')
2488 // all: forbid leading dot on any filename for any reason
2489 if (strstr(path, "/."))
2490 return 2; // attempt to go outside the game directory
2492 // after all these checks we're pretty sure it's a / separated filename
2493 // and won't do much if any harm
2499 ====================
2502 Look for a file in the packages and in the filesystem
2504 Return the searchpath where the file was found (or NULL)
2505 and the file index in the package if relevant
2506 ====================
2508 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2510 searchpath_t *search;
2513 // search through the path, one element at a time
2514 for (search = fs_searchpaths;search;search = search->next)
2516 // is the element a pak file?
2517 if (search->pack && !search->pack->vpack)
2519 int (*strcmp_funct) (const char* str1, const char* str2);
2520 int left, right, middle;
2523 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2525 // Look for the file (binary search)
2527 right = pak->numfiles - 1;
2528 while (left <= right)
2532 middle = (left + right) / 2;
2533 diff = strcmp_funct (pak->files[middle].name, name);
2538 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2540 // yes, but the first one is empty so we treat it as not being there
2541 if (!quiet && developer_extra.integer)
2542 Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2549 if (!quiet && developer_extra.integer)
2550 Con_DPrintf("FS_FindFile: %s in %s\n",
2551 pak->files[middle].name, pak->filename);
2558 // If we're too far in the list
2567 char netpath[MAX_OSPATH];
2568 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2569 if (FS_SysFileExists (netpath))
2571 if (!quiet && developer_extra.integer)
2572 Con_DPrintf("FS_FindFile: %s\n", netpath);
2581 if (!quiet && developer_extra.integer)
2582 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2594 Look for a file in the search paths and open it in read-only mode
2597 static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2599 searchpath_t *search;
2602 search = FS_FindFile (filename, &pack_ind, quiet);
2608 // Found in the filesystem?
2611 // this works with vpacks, so we are fine
2612 char path [MAX_OSPATH];
2613 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2614 return FS_SysOpen (path, "rb", nonblocking);
2617 // So, we found it in a package...
2619 // Is it a PK3 symlink?
2620 // TODO also handle directory symlinks by parsing the whole structure...
2621 // but heck, file symlinks are good enough for now
2622 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2624 if(symlinkLevels <= 0)
2626 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2631 char linkbuf[MAX_QPATH];
2633 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2634 const char *mergeslash;
2639 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2645 // Now combine the paths...
2646 mergeslash = strrchr(filename, '/');
2647 mergestart = linkbuf;
2649 mergeslash = filename;
2650 while(!strncmp(mergestart, "../", 3))
2653 while(mergeslash > filename)
2656 if(*mergeslash == '/')
2660 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2661 if(mergeslash == filename)
2663 // Either mergeslash == filename, then we just replace the name (done below)
2667 // Or, we append the name after mergeslash;
2668 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2669 int spaceNeeded = mergeslash - filename + 1;
2670 int spaceRemoved = mergestart - linkbuf;
2671 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2673 Con_DPrintf("symlink: too long path rejected\n");
2676 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2677 memcpy(linkbuf, filename, spaceNeeded);
2678 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2679 mergestart = linkbuf;
2681 if (!quiet && developer_loading.integer)
2682 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2683 if(FS_CheckNastyPath (mergestart, false))
2685 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2688 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2692 return FS_OpenPackedFile (search->pack, pack_ind);
2697 =============================================================================
2699 MAIN PUBLIC FUNCTIONS
2701 =============================================================================
2705 ====================
2708 Open a file in the userpath. The syntax is the same as fopen
2709 Used for savegame scanning in menu, and all file writing.
2710 ====================
2712 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2714 char real_path [MAX_OSPATH];
2716 if (FS_CheckNastyPath(filepath, false))
2718 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2722 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2724 // If the file is opened in "write", "append", or "read/write" mode,
2725 // create directories up to the file.
2726 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2727 FS_CreatePath (real_path);
2728 return FS_SysOpen (real_path, mode, false);
2733 ====================
2736 Open a file. The syntax is the same as fopen
2737 ====================
2739 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2741 qfile_t *result = NULL;
2742 if (FS_CheckNastyPath(filepath, false))
2744 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2748 if (fs_mutex) Thread_LockMutex(fs_mutex);
2749 result = FS_OpenReadFile (filepath, quiet, false, 16);
2750 if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2756 ====================
2759 Open a file. The syntax is the same as fopen
2760 ====================
2762 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2765 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2766 memset (file, 0, sizeof (*file));
2767 file->flags = QFILE_FLAG_DATA;
2769 file->real_length = size;
2775 ====================
2779 ====================
2781 int FS_Close (qfile_t* file)
2783 if(file->flags & QFILE_FLAG_DATA)
2789 if (FILEDESC_CLOSE (file->handle))
2794 if (file->flags & QFILE_FLAG_REMOVE)
2796 if (remove(file->filename) == -1)
2798 // No need to report this. If removing a just
2799 // written file failed, this most likely means
2800 // someone else deleted it first - which we
2805 Mem_Free((void *) file->filename);
2810 qz_inflateEnd (&file->ztk->zstream);
2811 Mem_Free (file->ztk);
2818 void FS_RemoveOnClose(qfile_t* file)
2820 file->flags |= QFILE_FLAG_REMOVE;
2824 ====================
2827 Write "datasize" bytes into a file
2828 ====================
2830 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2832 fs_offset_t written = 0;
2834 // If necessary, seek to the exact file position we're supposed to be
2835 if (file->buff_ind != file->buff_len)
2837 if (FILEDESC_SEEK (file->handle, file->buff_ind - file->buff_len, SEEK_CUR) == -1)
2839 Con_Printf("WARNING: could not seek in %s.\n", file->filename);
2843 // Purge cached data
2846 // Write the buffer and update the position
2847 // LordHavoc: 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)
2848 while (written < (fs_offset_t)datasize)
2850 // figure out how much to write in one chunk
2851 fs_offset_t maxchunk = 1<<30; // 1 GiB
2852 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
2853 int result = (int)FILEDESC_WRITE (file->handle, (const unsigned char *)data + written, chunk);
2854 // if at least some was written, add it to our accumulator
2857 // if the result is not what we expected, consider the write to be incomplete
2858 if (result != chunk)
2861 file->position = FILEDESC_SEEK (file->handle, 0, SEEK_CUR);
2862 if (file->real_length < file->position)
2863 file->real_length = file->position;
2865 // note that this will never be less than 0 even if the write failed
2871 ====================
2874 Read up to "buffersize" bytes from a file
2875 ====================
2877 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2879 fs_offset_t count, done;
2881 if (buffersize == 0)
2884 // Get rid of the ungetc character
2885 if (file->ungetc != EOF)
2887 ((char*)buffer)[0] = file->ungetc;
2895 if(file->flags & QFILE_FLAG_DATA)
2897 size_t left = file->real_length - file->position;
2898 if(buffersize > left)
2900 memcpy(buffer, file->data + file->position, buffersize);
2901 file->position += buffersize;
2905 // First, we copy as many bytes as we can from "buff"
2906 if (file->buff_ind < file->buff_len)
2908 count = file->buff_len - file->buff_ind;
2909 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2911 memcpy (buffer, &file->buff[file->buff_ind], count);
2912 file->buff_ind += count;
2914 buffersize -= count;
2915 if (buffersize == 0)
2919 // NOTE: at this point, the read buffer is always empty
2921 // If the file isn't compressed
2922 if (! (file->flags & QFILE_FLAG_DEFLATED))
2926 // We must take care to not read after the end of the file
2927 count = file->real_length - file->position;
2929 // If we have a lot of data to get, put them directly into "buffer"
2930 if (buffersize > sizeof (file->buff) / 2)
2932 if (count > (fs_offset_t)buffersize)
2933 count = (fs_offset_t)buffersize;
2934 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
2936 // Seek failed. When reading from a pipe, and
2937 // the caller never called FS_Seek, this still
2938 // works fine. So no reporting this error.
2940 nb = FILEDESC_READ (file->handle, &((unsigned char*)buffer)[done], count);
2944 file->position += nb;
2946 // Purge cached data
2952 if (count > (fs_offset_t)sizeof (file->buff))
2953 count = (fs_offset_t)sizeof (file->buff);
2954 if (FILEDESC_SEEK (file->handle, file->offset + file->position, SEEK_SET) == -1)
2956 // Seek failed. When reading from a pipe, and
2957 // the caller never called FS_Seek, this still
2958 // works fine. So no reporting this error.
2960 nb = FILEDESC_READ (file->handle, file->buff, count);
2963 file->buff_len = nb;
2964 file->position += nb;
2966 // Copy the requested data in "buffer" (as much as we can)
2967 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2968 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2969 file->buff_ind = count;
2977 // If the file is compressed, it's more complicated...
2978 // We cycle through a few operations until we have read enough data
2979 while (buffersize > 0)
2981 ztoolkit_t *ztk = file->ztk;
2984 // NOTE: at this point, the read buffer is always empty
2986 // If "input" is also empty, we need to refill it
2987 if (ztk->in_ind == ztk->in_len)
2989 // If we are at the end of the file
2990 if (file->position == file->real_length)
2993 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2994 if (count > (fs_offset_t)sizeof (ztk->input))
2995 count = (fs_offset_t)sizeof (ztk->input);
2996 FILEDESC_SEEK (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2997 if (FILEDESC_READ (file->handle, ztk->input, count) != count)
2999 Con_Printf ("FS_Read: unexpected end of file\n");
3004 ztk->in_len = count;
3005 ztk->in_position += count;
3008 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
3009 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
3011 // Now that we are sure we have compressed data available, we need to determine
3012 // if it's better to inflate it in "file->buff" or directly in "buffer"
3014 // Inflate the data in "file->buff"
3015 if (buffersize < sizeof (file->buff) / 2)
3017 ztk->zstream.next_out = file->buff;
3018 ztk->zstream.avail_out = sizeof (file->buff);
3019 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3020 if (error != Z_OK && error != Z_STREAM_END)
3022 Con_Printf ("FS_Read: Can't inflate file\n");
3025 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3027 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
3028 file->position += file->buff_len;
3030 // Copy the requested data in "buffer" (as much as we can)
3031 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
3032 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
3033 file->buff_ind = count;
3036 // Else, we inflate directly in "buffer"
3039 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
3040 ztk->zstream.avail_out = (unsigned int)buffersize;
3041 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
3042 if (error != Z_OK && error != Z_STREAM_END)
3044 Con_Printf ("FS_Read: Can't inflate file\n");
3047 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
3049 // How much data did it inflate?
3050 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
3051 file->position += count;
3053 // Purge cached data
3058 buffersize -= count;
3066 ====================
3069 Print a string into a file
3070 ====================
3072 int FS_Print (qfile_t* file, const char *msg)
3074 return (int)FS_Write (file, msg, strlen (msg));
3078 ====================
3081 Print a string into a file
3082 ====================
3084 int FS_Printf(qfile_t* file, const char* format, ...)
3089 va_start (args, format);
3090 result = FS_VPrintf (file, format, args);
3098 ====================
3101 Print a string into a file
3102 ====================
3104 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
3107 fs_offset_t buff_size = MAX_INPUTLINE;
3112 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
3113 len = dpvsnprintf (tempbuff, buff_size, format, ap);
3114 if (len >= 0 && len < buff_size)
3116 Mem_Free (tempbuff);
3120 len = FILEDESC_WRITE (file->handle, tempbuff, len);
3121 Mem_Free (tempbuff);
3128 ====================
3131 Get the next character of a file
3132 ====================
3134 int FS_Getc (qfile_t* file)
3138 if (FS_Read (file, &c, 1) != 1)
3146 ====================
3149 Put a character back into the read buffer (only supports one character!)
3150 ====================
3152 int FS_UnGetc (qfile_t* file, unsigned char c)
3154 // If there's already a character waiting to be read
3155 if (file->ungetc != EOF)
3164 ====================
3167 Move the position index in a file
3168 ====================
3170 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3173 unsigned char* buffer;
3174 fs_offset_t buffersize;
3176 // Compute the file offset
3180 offset += file->position - file->buff_len + file->buff_ind;
3187 offset += file->real_length;
3193 if (offset < 0 || offset > file->real_length)
3196 if(file->flags & QFILE_FLAG_DATA)
3198 file->position = offset;
3202 // If we have the data in our read buffer, we don't need to actually seek
3203 if (file->position - file->buff_len <= offset && offset <= file->position)
3205 file->buff_ind = offset + file->buff_len - file->position;
3209 // Purge cached data
3212 // Unpacked or uncompressed files can seek directly
3213 if (! (file->flags & QFILE_FLAG_DEFLATED))
3215 if (FILEDESC_SEEK (file->handle, file->offset + offset, SEEK_SET) == -1)
3217 file->position = offset;
3221 // Seeking in compressed files is more a hack than anything else,
3222 // but we need to support it, so here we go.
3225 // If we have to go back in the file, we need to restart from the beginning
3226 if (offset <= file->position)
3230 ztk->in_position = 0;
3232 if (FILEDESC_SEEK (file->handle, file->offset, SEEK_SET) == -1)
3233 Con_Printf("IMPOSSIBLE: couldn't seek in already opened pk3 file.\n");
3235 // Reset the Zlib stream
3236 ztk->zstream.next_in = ztk->input;
3237 ztk->zstream.avail_in = 0;
3238 qz_inflateReset (&ztk->zstream);
3241 // We need a big buffer to force inflating into it directly
3242 buffersize = 2 * sizeof (file->buff);
3243 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3245 // Skip all data until we reach the requested offset
3246 while (offset > file->position)
3248 fs_offset_t diff = offset - file->position;
3249 fs_offset_t count, len;
3251 count = (diff > buffersize) ? buffersize : diff;
3252 len = FS_Read (file, buffer, count);
3266 ====================
3269 Give the current position in a file
3270 ====================
3272 fs_offset_t FS_Tell (qfile_t* file)
3274 return file->position - file->buff_len + file->buff_ind;
3279 ====================
3282 Give the total size of a file
3283 ====================
3285 fs_offset_t FS_FileSize (qfile_t* file)
3287 return file->real_length;
3292 ====================
3295 Erases any buffered input or output data
3296 ====================
3298 void FS_Purge (qfile_t* file)
3308 FS_LoadAndCloseQFile
3310 Loads full content of a qfile_t and closes it.
3311 Always appends a 0 byte.
3314 static unsigned char *FS_LoadAndCloseQFile (qfile_t *file, const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3316 unsigned char *buf = NULL;
3317 fs_offset_t filesize = 0;
3321 filesize = file->real_length;
3324 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3329 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3330 buf[filesize] = '\0';
3331 FS_Read (file, buf, filesize);
3333 if (developer_loadfile.integer)
3334 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3337 if (filesizepointer)
3338 *filesizepointer = filesize;
3347 Filename are relative to the quake directory.
3348 Always appends a 0 byte.
3351 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3353 qfile_t *file = FS_OpenVirtualFile(path, quiet);
3354 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3362 Filename are OS paths.
3363 Always appends a 0 byte.
3366 unsigned char *FS_SysLoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3368 qfile_t *file = FS_SysOpen(path, "rb", false);
3369 return FS_LoadAndCloseQFile(file, path, pool, quiet, filesizepointer);
3377 The filename will be prefixed by the current game directory
3380 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3384 fs_offset_t lentotal;
3386 file = FS_OpenRealFile(filename, "wb", false);
3389 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3394 for(i = 0; i < count; ++i)
3396 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3397 for(i = 0; i < count; ++i)
3398 FS_Write (file, data[i], len[i]);
3403 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3405 return FS_WriteFileInBlocks(filename, &data, &len, 1);
3410 =============================================================================
3412 OTHERS PUBLIC FUNCTIONS
3414 =============================================================================
3422 void FS_StripExtension (const char *in, char *out, size_t size_out)
3430 while ((currentchar = *in) && size_out > 1)
3432 if (currentchar == '.')
3434 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3436 *out++ = currentchar;
3452 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3456 // if path doesn't have a .EXT, append extension
3457 // (extension should include the .)
3458 src = path + strlen(path);
3460 while (*src != '/' && src != path)
3463 return; // it has an extension
3467 strlcat (path, extension, size_path);
3475 Look for a file in the packages and in the filesystem
3478 int FS_FileType (const char *filename)
3480 searchpath_t *search;
3481 char fullpath[MAX_OSPATH];
3483 search = FS_FindFile (filename, NULL, true);
3485 return FS_FILETYPE_NONE;
3487 if(search->pack && !search->pack->vpack)
3488 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3490 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3491 return FS_SysFileType(fullpath);
3499 Look for a file in the packages and in the filesystem
3502 qboolean FS_FileExists (const char *filename)
3504 return (FS_FindFile (filename, NULL, true) != NULL);
3512 Look for a file in the filesystem only
3515 int FS_SysFileType (const char *path)
3518 // Sajt - some older sdks are missing this define
3519 # ifndef INVALID_FILE_ATTRIBUTES
3520 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3523 DWORD result = GetFileAttributes(path);
3525 if(result == INVALID_FILE_ATTRIBUTES)
3526 return FS_FILETYPE_NONE;
3528 if(result & FILE_ATTRIBUTE_DIRECTORY)
3529 return FS_FILETYPE_DIRECTORY;
3531 return FS_FILETYPE_FILE;
3535 if (stat (path,&buf) == -1)
3536 return FS_FILETYPE_NONE;
3539 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3541 if(S_ISDIR(buf.st_mode))
3542 return FS_FILETYPE_DIRECTORY;
3544 return FS_FILETYPE_FILE;
3548 qboolean FS_SysFileExists (const char *path)
3550 return FS_SysFileType (path) != FS_FILETYPE_NONE;
3557 Allocate and fill a search structure with information on matching filenames.
3560 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3563 searchpath_t *searchpath;
3565 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3566 stringlist_t resultlist;
3567 stringlist_t dirlist;
3568 const char *slash, *backslash, *colon, *separator;
3570 char temp[MAX_OSPATH];
3572 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3577 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3581 stringlistinit(&resultlist);
3582 stringlistinit(&dirlist);
3584 slash = strrchr(pattern, '/');
3585 backslash = strrchr(pattern, '\\');
3586 colon = strrchr(pattern, ':');
3587 separator = max(slash, backslash);
3588 separator = max(separator, colon);
3589 basepathlength = separator ? (separator + 1 - pattern) : 0;
3590 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3592 memcpy(basepath, pattern, basepathlength);
3593 basepath[basepathlength] = 0;
3595 // search through the path, one element at a time
3596 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3598 // is the element a pak file?
3599 if (searchpath->pack && !searchpath->pack->vpack)
3601 // look through all the pak file elements
3602 pak = searchpath->pack;
3603 for (i = 0;i < pak->numfiles;i++)
3605 strlcpy(temp, pak->files[i].name, sizeof(temp));
3608 if (matchpattern(temp, (char *)pattern, true))
3610 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3611 if (!strcmp(resultlist.strings[resultlistindex], temp))
3613 if (resultlistindex == resultlist.numstrings)
3615 stringlistappend(&resultlist, temp);
3616 if (!quiet && developer_loading.integer)
3617 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3620 // strip off one path element at a time until empty
3621 // this way directories are added to the listing if they match the pattern
3622 slash = strrchr(temp, '/');
3623 backslash = strrchr(temp, '\\');
3624 colon = strrchr(temp, ':');
3626 if (separator < slash)
3628 if (separator < backslash)
3629 separator = backslash;
3630 if (separator < colon)
3632 *((char *)separator) = 0;
3638 stringlist_t matchedSet, foundSet;
3639 const char *start = pattern;
3641 stringlistinit(&matchedSet);
3642 stringlistinit(&foundSet);
3643 // add a first entry to the set
3644 stringlistappend(&matchedSet, "");
3645 // iterate through pattern's path
3648 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3649 char subpath[MAX_OSPATH];
3650 char subpattern[MAX_OSPATH];
3652 // find the next wildcard
3653 wildcard = strchr(start, '?');
3654 asterisk = strchr(start, '*');
3655 if (asterisk && (!wildcard || asterisk < wildcard))
3657 wildcard = asterisk;
3662 nextseparator = strchr( wildcard, '/' );
3666 nextseparator = NULL;
3669 if( !nextseparator ) {
3670 nextseparator = start + strlen( start );
3673 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3674 // copy everything up except nextseperator
3675 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3676 // find the last '/' before the wildcard
3677 prevseparator = strrchr( subpattern, '/' );
3679 prevseparator = subpattern;
3682 // copy everything from start to the previous including the '/' (before the wildcard)
3683 // everything up to start is already included in the path of matchedSet's entries
3684 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3686 // for each entry in matchedSet try to open the subdirectories specified in subpath
3687 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3688 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3689 strlcat( temp, subpath, sizeof(temp) );
3690 listdirectory( &foundSet, searchpath->filename, temp );
3692 if( dirlistindex == 0 ) {
3695 // reset the current result set
3696 stringlistfreecontents( &matchedSet );
3697 // match against the pattern
3698 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3699 const char *direntry = foundSet.strings[ dirlistindex ];
3700 if (matchpattern(direntry, subpattern, true)) {
3701 stringlistappend( &matchedSet, direntry );
3704 stringlistfreecontents( &foundSet );
3706 start = nextseparator;
3709 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3711 const char *temp = matchedSet.strings[dirlistindex];
3712 if (matchpattern(temp, (char *)pattern, true))
3714 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3715 if (!strcmp(resultlist.strings[resultlistindex], temp))
3717 if (resultlistindex == resultlist.numstrings)
3719 stringlistappend(&resultlist, temp);
3720 if (!quiet && developer_loading.integer)
3721 Con_Printf("SearchDirFile: %s\n", temp);
3725 stringlistfreecontents( &matchedSet );
3729 if (resultlist.numstrings)
3731 stringlistsort(&resultlist, true);
3732 numfiles = resultlist.numstrings;
3734 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3735 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3736 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3737 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3738 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3739 search->numfilenames = (int)numfiles;
3742 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3745 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3746 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3747 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3749 numchars += (int)textlen;
3752 stringlistfreecontents(&resultlist);
3758 void FS_FreeSearch(fssearch_t *search)
3763 extern int con_linewidth;
3764 static int FS_ListDirectory(const char *pattern, int oneperline)
3773 char linebuf[MAX_INPUTLINE];
3775 search = FS_Search(pattern, true, true);
3778 numfiles = search->numfilenames;
3781 // FIXME: the names could be added to one column list and then
3782 // gradually shifted into the next column if they fit, and then the
3783 // next to make a compact variable width listing but it's a lot more
3785 // find width for columns
3787 for (i = 0;i < numfiles;i++)
3789 l = (int)strlen(search->filenames[i]);
3790 if (columnwidth < l)
3793 // count the spacing character
3795 // calculate number of columns
3796 numcolumns = con_linewidth / columnwidth;
3797 // don't bother with the column printing if it's only one column
3798 if (numcolumns >= 2)
3800 numlines = (numfiles + numcolumns - 1) / numcolumns;
3801 for (i = 0;i < numlines;i++)
3804 for (k = 0;k < numcolumns;k++)
3806 l = i * numcolumns + k;
3809 name = search->filenames[l];
3810 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3811 linebuf[linebufpos++] = name[j];
3812 // space out name unless it's the last on the line
3813 if (k + 1 < numcolumns && l + 1 < numfiles)
3814 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3815 linebuf[linebufpos++] = ' ';
3818 linebuf[linebufpos] = 0;
3819 Con_Printf("%s\n", linebuf);
3826 for (i = 0;i < numfiles;i++)
3827 Con_Printf("%s\n", search->filenames[i]);
3828 FS_FreeSearch(search);
3829 return (int)numfiles;
3832 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3834 const char *pattern;
3835 if (Cmd_Argc() >= 3)
3837 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3840 if (Cmd_Argc() == 2)
3841 pattern = Cmd_Argv(1);
3844 if (!FS_ListDirectory(pattern, oneperline))
3845 Con_Print("No files found.\n");
3850 FS_ListDirectoryCmd("dir", true);
3855 FS_ListDirectoryCmd("ls", false);
3858 void FS_Which_f(void)
3860 const char *filename;
3863 if (Cmd_Argc() != 2)
3865 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3868 filename = Cmd_Argv(1);
3869 sp = FS_FindFile(filename, &index, true);
3871 Con_Printf("%s isn't anywhere\n", filename);
3877 Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3879 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3882 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3886 const char *FS_WhichPack(const char *filename)
3889 searchpath_t *sp = FS_FindFile(filename, &index, true);
3891 return sp->pack->shortname;
3899 ====================
3900 FS_IsRegisteredQuakePack
3902 Look for a proof of purchase file file in the requested package
3904 If it is found, this file should NOT be downloaded.
3905 ====================
3907 qboolean FS_IsRegisteredQuakePack(const char *name)
3909 searchpath_t *search;
3912 // search through the path, one element at a time
3913 for (search = fs_searchpaths;search;search = search->next)
3915 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3916 // TODO do we want to support vpacks in here too?
3918 int (*strcmp_funct) (const char* str1, const char* str2);
3919 int left, right, middle;
3922 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3924 // Look for the file (binary search)
3926 right = pak->numfiles - 1;
3927 while (left <= right)
3931 middle = (left + right) / 2;
3932 diff = strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3938 // If we're too far in the list
3945 // we found the requested pack but it is not registered quake
3953 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3956 unsigned char *filedata;
3957 fs_offset_t filesize;
3958 if (filesizepointer)
3959 *filesizepointer = 0;
3960 if (!filename || !*filename)
3962 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3965 if (filesizepointer)
3966 *filesizepointer = filesize;
3967 crc = CRC_Block(filedata, filesize);
3973 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3976 unsigned char *out = NULL;
3980 #ifndef LINK_TO_ZLIB
3985 memset(&strm, 0, sizeof(strm));
3986 strm.zalloc = Z_NULL;
3987 strm.zfree = Z_NULL;
3988 strm.opaque = Z_NULL;
3991 level = Z_DEFAULT_COMPRESSION;
3993 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3995 Con_Printf("FS_Deflate: deflate init error!\n");
3999 strm.next_in = (unsigned char*)data;
4000 strm.avail_in = (unsigned int)size;
4002 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
4005 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
4006 qz_deflateEnd(&strm);
4010 strm.next_out = tmp;
4011 strm.avail_out = (unsigned int)size;
4013 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
4015 Con_Printf("FS_Deflate: deflate failed!\n");
4016 qz_deflateEnd(&strm);
4021 if(qz_deflateEnd(&strm) != Z_OK)
4023 Con_Printf("FS_Deflate: deflateEnd failed\n");
4028 if(strm.total_out >= size)
4030 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
4035 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
4038 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
4043 *deflated_size = (size_t)strm.total_out;
4045 memcpy(out, tmp, strm.total_out);
4051 static void AssertBufsize(sizebuf_t *buf, int length)
4053 if(buf->cursize + length > buf->maxsize)
4055 int oldsize = buf->maxsize;
4056 unsigned char *olddata;
4057 olddata = buf->data;
4058 buf->maxsize += length;
4059 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
4062 memcpy(buf->data, olddata, oldsize);
4068 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
4072 unsigned char *out = NULL;
4073 unsigned char tmp[2048];
4078 #ifndef LINK_TO_ZLIB
4083 memset(&outbuf, 0, sizeof(outbuf));
4084 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
4085 outbuf.maxsize = sizeof(tmp);
4087 memset(&strm, 0, sizeof(strm));
4088 strm.zalloc = Z_NULL;
4089 strm.zfree = Z_NULL;
4090 strm.opaque = Z_NULL;
4092 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
4094 Con_Printf("FS_Inflate: inflate init error!\n");
4095 Mem_Free(outbuf.data);
4099 strm.next_in = (unsigned char*)data;
4100 strm.avail_in = (unsigned int)size;
4104 strm.next_out = tmp;
4105 strm.avail_out = sizeof(tmp);
4106 ret = qz_inflate(&strm, Z_NO_FLUSH);
4107 // it either returns Z_OK on progress, Z_STREAM_END on end
4115 case Z_STREAM_ERROR:
4116 Con_Print("FS_Inflate: stream error!\n");
4119 Con_Print("FS_Inflate: data error!\n");
4122 Con_Print("FS_Inflate: mem error!\n");
4125 Con_Print("FS_Inflate: buf error!\n");
4128 Con_Print("FS_Inflate: unknown error!\n");
4132 if(ret != Z_OK && ret != Z_STREAM_END)
4134 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
4135 Mem_Free(outbuf.data);
4136 qz_inflateEnd(&strm);
4139 have = sizeof(tmp) - strm.avail_out;
4140 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
4141 SZ_Write(&outbuf, tmp, have);
4142 } while(ret != Z_STREAM_END);
4144 qz_inflateEnd(&strm);
4146 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4149 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4150 Mem_Free(outbuf.data);
4154 memcpy(out, outbuf.data, outbuf.cursize);
4155 Mem_Free(outbuf.data);
4157 *inflated_size = (size_t)outbuf.cursize;