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
77 /** \page fs File System
79 All of Quake's data access is through a hierchal file system, but the contents
80 of the file system can be transparently merged from several sources.
82 The "base directory" is the path to the directory holding the quake.exe and
83 all game directories. The sys_* files pass this to host_init in
84 quakeparms_t->basedir. This can be overridden with the "-basedir" command
85 line parm to allow code debugging in a different directory. The base
86 directory is only used during filesystem initialization.
88 The "game directory" is the first tree on the search path and directory that
89 all generated files (savegames, screenshots, demos, config files) will be
90 saved to. This can be overridden with the "-game" command line parameter.
91 The game directory can never be changed while quake is executing. This is a
92 precaution against having a malicious server instruct clients to write files
93 over areas they shouldn't.
99 =============================================================================
103 =============================================================================
106 // Magic numbers of a ZIP file (big-endian format)
107 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
108 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
109 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
111 // Other constants for ZIP files
112 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
113 #define ZIP_END_CDIR_SIZE 22
114 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
115 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
120 #define qz_inflate inflate
121 #define qz_inflateEnd inflateEnd
122 #define qz_inflateInit2_ inflateInit2_
123 #define qz_inflateReset inflateReset
124 #define qz_deflateInit2_ deflateInit2_
125 #define qz_deflateEnd deflateEnd
126 #define qz_deflate deflate
127 #define Z_MEMLEVEL_DEFAULT 8
130 // Zlib constants (from zlib.h)
131 #define Z_SYNC_FLUSH 2
134 #define Z_STREAM_END 1
135 #define Z_STREAM_ERROR (-2)
136 #define Z_DATA_ERROR (-3)
137 #define Z_MEM_ERROR (-4)
138 #define Z_BUF_ERROR (-5)
139 #define ZLIB_VERSION "1.2.3"
143 #define Z_MEMLEVEL_DEFAULT 8
146 #define Z_DEFAULT_COMPRESSION (-1)
148 #define Z_SYNC_FLUSH 2
149 #define Z_FULL_FLUSH 3
152 // Uncomment the following line if the zlib DLL you have still uses
153 // the 1.1.x series calling convention on Win32 (WINAPI)
154 //#define ZLIB_USES_WINAPI
158 =============================================================================
162 =============================================================================
165 /*! Zlib stream (from zlib.h)
166 * \warning: some pointers we don't use directly have
167 * been cast to "void*" for a matter of simplicity
171 unsigned char *next_in; ///< next input byte
172 unsigned int avail_in; ///< number of bytes available at next_in
173 unsigned long total_in; ///< total nb of input bytes read so far
175 unsigned char *next_out; ///< next output byte should be put there
176 unsigned int avail_out; ///< remaining free space at next_out
177 unsigned long total_out; ///< total nb of bytes output so far
179 char *msg; ///< last error message, NULL if no error
180 void *state; ///< not visible by applications
182 void *zalloc; ///< used to allocate the internal state
183 void *zfree; ///< used to free the internal state
184 void *opaque; ///< private data object passed to zalloc and zfree
186 int data_type; ///< best guess about the data type: ascii or binary
187 unsigned long adler; ///< adler32 value of the uncompressed data
188 unsigned long reserved; ///< reserved for future use
193 /// inside a package (PAK or PK3)
194 #define QFILE_FLAG_PACKED (1 << 0)
195 /// file is compressed using the deflate algorithm (PK3 only)
196 #define QFILE_FLAG_DEFLATED (1 << 1)
197 /// file is actually already loaded data
198 #define QFILE_FLAG_DATA (1 << 2)
199 /// real file will be removed on close
200 #define QFILE_FLAG_REMOVE (1 << 3)
202 #define FILE_BUFF_SIZE 2048
206 size_t comp_length; ///< length of the compressed file
207 size_t in_ind, in_len; ///< input buffer current index and length
208 size_t in_position; ///< position in the compressed file
209 unsigned char input [FILE_BUFF_SIZE];
215 int handle; ///< file descriptor
216 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
217 fs_offset_t position; ///< current position in the file
218 fs_offset_t offset; ///< offset into the package (0 if external file)
219 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
222 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
223 unsigned char buff [FILE_BUFF_SIZE];
225 ztoolkit_t* ztk; ///< For zipped files.
227 const unsigned char *data; ///< For data files.
229 const char *filename; ///< Kept around for QFILE_FLAG_REMOVE, unused otherwise
233 // ------ PK3 files on disk ------ //
235 // You can get the complete ZIP format description from PKWARE website
237 typedef struct pk3_endOfCentralDir_s
239 unsigned int signature;
240 unsigned short disknum;
241 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
242 unsigned short localentries; ///< number of entries in the central directory on this disk
243 unsigned short nbentries; ///< total number of entries in the central directory on this disk
244 unsigned int cdir_size; ///< size of the central directory
245 unsigned int cdir_offset; ///< with respect to the starting disk number
246 unsigned short comment_size;
247 fs_offset_t prepended_garbage;
248 } pk3_endOfCentralDir_t;
251 // ------ PAK files on disk ------ //
252 typedef struct dpackfile_s
255 int filepos, filelen;
258 typedef struct dpackheader_s
266 /*! \name Packages in memory
269 /// the offset in packfile_t is the true contents offset
270 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
271 /// file compressed using the deflate algorithm
272 #define PACKFILE_FLAG_DEFLATED (1 << 1)
273 /// file is a symbolic link
274 #define PACKFILE_FLAG_SYMLINK (1 << 2)
276 typedef struct packfile_s
278 char name [MAX_QPATH];
281 fs_offset_t packsize; ///< size in the package
282 fs_offset_t realsize; ///< real file size (uncompressed)
285 typedef struct pack_s
287 char filename [MAX_OSPATH];
288 char shortname [MAX_QPATH];
290 int ignorecase; ///< PK3 ignores case
297 /// Search paths for files (including packages)
298 typedef struct searchpath_s
300 // only one of filename / pack will be used
301 char filename[MAX_OSPATH];
303 struct searchpath_s *next;
308 =============================================================================
312 =============================================================================
317 void FS_Which_f(void);
319 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
320 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
321 fs_offset_t offset, fs_offset_t packsize,
322 fs_offset_t realsize, int flags);
326 =============================================================================
330 =============================================================================
333 mempool_t *fs_mempool;
334 void *fs_mutex = NULL;
336 searchpath_t *fs_searchpaths = NULL;
337 const char *const fs_checkgamedir_missing = "missing";
339 #define MAX_FILES_IN_PACK 65536
341 char fs_userdir[MAX_OSPATH];
342 char fs_gamedir[MAX_OSPATH];
343 char fs_basedir[MAX_OSPATH];
344 static pack_t *fs_selfpack = NULL;
346 // list of active game directories (empty if not running a mod)
347 int fs_numgamedirs = 0;
348 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
350 // list of all gamedirs with modinfo.txt
351 gamedir_t *fs_all_gamedirs = NULL;
352 int fs_all_gamedirs_count = 0;
354 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)"};
355 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"};
356 cvar_t cvar_fs_gamedir = {CVAR_READONLY | CVAR_NORESETTODEFAULTS, "fs_gamedir", "", "the list of currently selected gamedirs (use the 'gamedir' command to change this)"};
360 =============================================================================
362 PRIVATE FUNCTIONS - PK3 HANDLING
364 =============================================================================
368 // Functions exported from zlib
369 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
370 # define ZEXPORT WINAPI
375 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
376 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
377 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
378 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
379 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
380 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
381 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
384 #define qz_inflateInit2(strm, windowBits) \
385 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
386 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
387 qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
390 // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
392 static dllfunction_t zlibfuncs[] =
394 {"inflate", (void **) &qz_inflate},
395 {"inflateEnd", (void **) &qz_inflateEnd},
396 {"inflateInit2_", (void **) &qz_inflateInit2_},
397 {"inflateReset", (void **) &qz_inflateReset},
398 {"deflateInit2_", (void **) &qz_deflateInit2_},
399 {"deflateEnd", (void **) &qz_deflateEnd},
400 {"deflate", (void **) &qz_deflate},
404 /// Handle for Zlib DLL
405 static dllhandle_t zlib_dll = NULL;
409 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
410 static dllfunction_t shfolderfuncs[] =
412 {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
415 static const char* shfolderdllnames [] =
417 "shfolder.dll", // IE 4, or Win NT and higher
420 static dllhandle_t shfolder_dll = NULL;
422 const GUID qFOLDERID_SavedGames = {0x4C5C32FF, 0xBB9D, 0x43b0, {0xB5, 0xB4, 0x2D, 0x72, 0xE5, 0x4E, 0xAA, 0xA4}};
423 #define qREFKNOWNFOLDERID const GUID *
424 #define qKF_FLAG_CREATE 0x8000
425 #define qKF_FLAG_NO_ALIAS 0x1000
426 static HRESULT (WINAPI *qSHGetKnownFolderPath) (qREFKNOWNFOLDERID rfid, DWORD dwFlags, HANDLE hToken, PWSTR *ppszPath);
427 static dllfunction_t shell32funcs[] =
429 {"SHGetKnownFolderPath", (void **) &qSHGetKnownFolderPath},
432 static const char* shell32dllnames [] =
434 "shell32.dll", // Vista and higher
437 static dllhandle_t shell32_dll = NULL;
439 static HRESULT (WINAPI *qCoInitializeEx)(LPVOID pvReserved, DWORD dwCoInit);
440 static void (WINAPI *qCoUninitialize)(void);
441 static void (WINAPI *qCoTaskMemFree)(LPVOID pv);
442 static dllfunction_t ole32funcs[] =
444 {"CoInitializeEx", (void **) &qCoInitializeEx},
445 {"CoUninitialize", (void **) &qCoUninitialize},
446 {"CoTaskMemFree", (void **) &qCoTaskMemFree},
449 static const char* ole32dllnames [] =
451 "ole32.dll", // 2000 and higher
454 static dllhandle_t ole32_dll = NULL;
464 static void PK3_CloseLibrary (void)
467 Sys_UnloadLibrary (&zlib_dll);
476 Try to load the Zlib DLL
479 static qboolean PK3_OpenLibrary (void)
484 const char* dllnames [] =
487 # ifdef ZLIB_USES_WINAPI
493 #elif defined(MACOSX)
507 return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
515 See if zlib is available
518 qboolean FS_HasZlib(void)
523 PK3_OpenLibrary(); // to be safe
524 return (zlib_dll != 0);
530 PK3_GetEndOfCentralDir
532 Extract the end of the central directory from a PK3 package
535 static qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
537 fs_offset_t filesize, maxsize;
538 unsigned char *buffer, *ptr;
541 // Get the package size
542 filesize = lseek (packhandle, 0, SEEK_END);
543 if (filesize < ZIP_END_CDIR_SIZE)
546 // Load the end of the file in memory
547 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
550 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
551 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
552 lseek (packhandle, filesize - maxsize, SEEK_SET);
553 if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
559 // Look for the end of central dir signature around the end of the file
560 maxsize -= ZIP_END_CDIR_SIZE;
561 ptr = &buffer[maxsize];
563 while (BuffBigLong (ptr) != ZIP_END_HEADER)
575 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
576 eocd->signature = LittleLong (eocd->signature);
577 eocd->disknum = LittleShort (eocd->disknum);
578 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
579 eocd->localentries = LittleShort (eocd->localentries);
580 eocd->nbentries = LittleShort (eocd->nbentries);
581 eocd->cdir_size = LittleLong (eocd->cdir_size);
582 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
583 eocd->comment_size = LittleShort (eocd->comment_size);
584 eocd->prepended_garbage = filesize - (ind + ZIP_END_CDIR_SIZE) - eocd->cdir_offset - eocd->cdir_size; // this detects "SFX" zip files
585 eocd->cdir_offset += eocd->prepended_garbage;
590 eocd->cdir_size < 0 || eocd->cdir_size > filesize ||
591 eocd->cdir_offset < 0 || eocd->cdir_offset >= filesize ||
592 eocd->cdir_offset + eocd->cdir_size > filesize
595 // Obviously invalid central directory.
607 Extract the file list from a PK3 file
610 static int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
612 unsigned char *central_dir, *ptr;
614 fs_offset_t remaining;
616 // Load the central directory in memory
617 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
618 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
619 if(read (pack->handle, central_dir, eocd->cdir_size) != (fs_offset_t) eocd->cdir_size)
621 Mem_Free (central_dir);
625 // Extract the files properties
626 // The parsing is done "by hand" because some fields have variable sizes and
627 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
628 remaining = eocd->cdir_size;
631 for (ind = 0; ind < eocd->nbentries; ind++)
633 fs_offset_t namesize, count;
635 // Checking the remaining size
636 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
638 Mem_Free (central_dir);
641 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
644 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
646 Mem_Free (central_dir);
650 namesize = BuffLittleShort (&ptr[28]); // filename length
652 // Check encryption, compression, and attributes
653 // 1st uint8 : general purpose bit flag
654 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
656 // LordHavoc: bit 3 would be a problem if we were scanning the archive
657 // but is not a problem in the central directory where the values are
660 // bit 3 seems to always be set by the standard Mac OSX zip maker
662 // 2nd uint8 : external file attributes
663 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
664 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
666 // Still enough bytes for the name?
667 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
669 Mem_Free (central_dir);
673 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
674 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
676 char filename [sizeof (pack->files[0].name)];
677 fs_offset_t offset, packsize, realsize;
680 // Extract the name (strip it if necessary)
681 namesize = min(namesize, (int)sizeof (filename) - 1);
682 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
683 filename[namesize] = '\0';
685 if (BuffLittleShort (&ptr[10]))
686 flags = PACKFILE_FLAG_DEFLATED;
689 offset = (unsigned int)(BuffLittleLong (&ptr[42]) + eocd->prepended_garbage);
690 packsize = (unsigned int)BuffLittleLong (&ptr[20]);
691 realsize = (unsigned int)BuffLittleLong (&ptr[24]);
693 switch(ptr[5]) // C_VERSION_MADE_BY_1
698 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
699 // can't use S_ISLNK here, as this has to compile on non-UNIX too
700 flags |= PACKFILE_FLAG_SYMLINK;
704 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
708 // Skip the name, additionnal field, and comment
709 // 1er uint16 : extra field length
710 // 2eme uint16 : file comment length
711 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
712 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
716 // If the package is empty, central_dir is NULL here
717 if (central_dir != NULL)
718 Mem_Free (central_dir);
719 return pack->numfiles;
727 Create a package entry associated with a PK3 file
730 static pack_t *FS_LoadPackPK3FromFD (const char *packfile, int packhandle, qboolean silent)
732 pk3_endOfCentralDir_t eocd;
736 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
739 Con_Printf ("%s is not a PK3 file\n", packfile);
744 // Multi-volume ZIP archives are NOT allowed
745 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
747 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
752 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
753 // since eocd.nbentries is an unsigned 16 bits integer
754 #if MAX_FILES_IN_PACK < 65535
755 if (eocd.nbentries > MAX_FILES_IN_PACK)
757 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
763 // Create a package structure in memory
764 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
765 pack->ignorecase = true; // PK3 ignores case
766 strlcpy (pack->filename, packfile, sizeof (pack->filename));
767 pack->handle = packhandle;
768 pack->numfiles = eocd.nbentries;
769 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
771 real_nb_files = PK3_BuildFileList (pack, &eocd);
772 if (real_nb_files < 0)
774 Con_Printf ("%s is not a valid PK3 file\n", packfile);
780 Con_DPrintf("Added packfile %s (%i files)\n", packfile, real_nb_files);
783 static pack_t *FS_LoadPackPK3 (const char *packfile)
786 packhandle = FS_SysOpenFD (packfile, "rb", false);
789 return FS_LoadPackPK3FromFD(packfile, packhandle, false);
795 PK3_GetTrueFileOffset
797 Find where the true file data offset is
800 static qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
802 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
806 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
809 // Load the local file description
810 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
812 Con_Printf ("Can't seek in package %s\n", pack->filename);
815 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
816 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
818 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
822 // Skip name and extra field
823 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
825 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
831 =============================================================================
833 OTHER PRIVATE FUNCTIONS
835 =============================================================================
843 Add a file to the list of files contained into a package
846 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
847 fs_offset_t offset, fs_offset_t packsize,
848 fs_offset_t realsize, int flags)
850 int (*strcmp_funct) (const char* str1, const char* str2);
851 int left, right, middle;
854 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
856 // Look for the slot we should put that file into (binary search)
858 right = pack->numfiles - 1;
859 while (left <= right)
863 middle = (left + right) / 2;
864 diff = strcmp_funct (pack->files[middle].name, name);
866 // If we found the file, there's a problem
868 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
870 // If we're too far in the list
877 // We have to move the right of the list by one slot to free the one we need
878 pfile = &pack->files[left];
879 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
882 strlcpy (pfile->name, name, sizeof (pfile->name));
883 pfile->offset = offset;
884 pfile->packsize = packsize;
885 pfile->realsize = realsize;
886 pfile->flags = flags;
896 Only used for FS_OpenRealFile.
899 void FS_CreatePath (char *path)
903 for (ofs = path+1 ; *ofs ; ofs++)
905 if (*ofs == '/' || *ofs == '\\')
907 // create the directory
923 static void FS_Path_f (void)
927 Con_Print("Current search path:\n");
928 for (s=fs_searchpaths ; s ; s=s->next)
933 Con_Printf("%sdir (virtual pack)\n", s->pack->filename);
935 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
938 Con_Printf("%s\n", s->filename);
948 /*! Takes an explicit (not game tree related) path to a pak file.
949 *Loads the header and directory, adding the files at the beginning
950 *of the list so they override previous pack files.
952 static pack_t *FS_LoadPackPAK (const char *packfile)
954 dpackheader_t header;
960 packhandle = FS_SysOpenFD(packfile, "rb", false);
963 if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
965 Con_Printf ("%s is not a packfile\n", packfile);
969 if (memcmp(header.id, "PACK", 4))
971 Con_Printf ("%s is not a packfile\n", packfile);
975 header.dirofs = LittleLong (header.dirofs);
976 header.dirlen = LittleLong (header.dirlen);
978 if (header.dirlen % sizeof(dpackfile_t))
980 Con_Printf ("%s has an invalid directory size\n", packfile);
985 numpackfiles = header.dirlen / sizeof(dpackfile_t);
987 if (numpackfiles > MAX_FILES_IN_PACK)
989 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
994 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
995 lseek (packhandle, header.dirofs, SEEK_SET);
996 if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
998 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
1004 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1005 pack->ignorecase = true; // PAK is sensitive in Quake1 but insensitive in Quake2
1006 strlcpy (pack->filename, packfile, sizeof (pack->filename));
1007 pack->handle = packhandle;
1009 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
1011 // parse the directory
1012 for (i = 0;i < numpackfiles;i++)
1014 fs_offset_t offset = (unsigned int)LittleLong (info[i].filepos);
1015 fs_offset_t size = (unsigned int)LittleLong (info[i].filelen);
1017 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
1022 Con_DPrintf("Added packfile %s (%i files)\n", packfile, numpackfiles);
1027 ====================
1030 Create a package entry associated with a directory file
1031 ====================
1033 static pack_t *FS_LoadPackVirtual (const char *dirname)
1036 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
1038 pack->ignorecase = false;
1039 strlcpy (pack->filename, dirname, sizeof(pack->filename));
1041 pack->numfiles = -1;
1043 Con_DPrintf("Added packfile %s (virtual pack)\n", dirname);
1052 /*! Adds the given pack to the search path.
1053 * The pack type is autodetected by the file extension.
1055 * Returns true if the file was successfully added to the
1056 * search path or if it was already included.
1058 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1059 * plain directories.
1062 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
1064 searchpath_t *search;
1066 const char *ext = FS_FileExtension(pakfile);
1069 for(search = fs_searchpaths; search; search = search->next)
1071 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
1074 *already_loaded = true;
1075 return true; // already loaded
1080 *already_loaded = false;
1082 if(!strcasecmp(ext, "pk3dir"))
1083 pak = FS_LoadPackVirtual (pakfile);
1084 else if(!strcasecmp(ext, "pak"))
1085 pak = FS_LoadPackPAK (pakfile);
1086 else if(!strcasecmp(ext, "pk3"))
1087 pak = FS_LoadPackPK3 (pakfile);
1088 else if(!strcasecmp(ext, "obb")) // android apk expansion
1089 pak = FS_LoadPackPK3 (pakfile);
1091 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
1095 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
1097 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
1100 // find the first item whose next one is a pack or NULL
1101 searchpath_t *insertion_point = 0;
1102 if(fs_searchpaths && !fs_searchpaths->pack)
1104 insertion_point = fs_searchpaths;
1107 if(!insertion_point->next)
1109 if(insertion_point->next->pack)
1111 insertion_point = insertion_point->next;
1114 // If insertion_point is NULL, this means that either there is no
1115 // item in the list yet, or that the very first item is a pack. In
1116 // that case, we want to insert at the beginning...
1117 if(!insertion_point)
1119 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1120 search->next = fs_searchpaths;
1121 fs_searchpaths = search;
1124 // otherwise we want to append directly after insertion_point.
1126 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1127 search->next = insertion_point->next;
1128 insertion_point->next = search;
1133 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1134 search->next = fs_searchpaths;
1135 fs_searchpaths = search;
1140 dpsnprintf(search->filename, sizeof(search->filename), "%s/", pakfile);
1141 // if shortname ends with "pk3dir", strip that suffix to make it just "pk3"
1142 // same goes for the name inside the pack structure
1143 l = strlen(pak->shortname);
1145 if(!strcasecmp(pak->shortname + l - 7, ".pk3dir"))
1146 pak->shortname[l - 3] = 0;
1147 l = strlen(pak->filename);
1149 if(!strcasecmp(pak->filename + l - 7, ".pk3dir"))
1150 pak->filename[l - 3] = 0;
1156 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1167 /*! Adds the given pack to the search path and searches for it in the game path.
1168 * The pack type is autodetected by the file extension.
1170 * Returns true if the file was successfully added to the
1171 * search path or if it was already included.
1173 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1174 * plain directories.
1176 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1178 char fullpath[MAX_OSPATH];
1180 searchpath_t *search;
1183 *already_loaded = false;
1185 // then find the real name...
1186 search = FS_FindFile(pakfile, &index, true);
1187 if(!search || search->pack)
1189 Con_Printf("could not find pak \"%s\"\n", pakfile);
1193 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1195 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1203 Sets fs_gamedir, adds the directory to the head of the path,
1204 then loads and adds pak1.pak pak2.pak ...
1207 static void FS_AddGameDirectory (const char *dir)
1211 searchpath_t *search;
1213 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1215 stringlistinit(&list);
1216 listdirectory(&list, "", dir);
1217 stringlistsort(&list, false);
1219 // add any PAK package in the directory
1220 for (i = 0;i < list.numstrings;i++)
1222 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1224 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1228 // add any PK3 package in the directory
1229 for (i = 0;i < list.numstrings;i++)
1231 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3") || !strcasecmp(FS_FileExtension(list.strings[i]), "obb") || !strcasecmp(FS_FileExtension(list.strings[i]), "pk3dir"))
1233 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1237 stringlistfreecontents(&list);
1239 // Add the directory to the search path
1240 // (unpacked files have the priority over packed files)
1241 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1242 strlcpy (search->filename, dir, sizeof (search->filename));
1243 search->next = fs_searchpaths;
1244 fs_searchpaths = search;
1253 static void FS_AddGameHierarchy (const char *dir)
1256 // Add the common game directory
1257 FS_AddGameDirectory (va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, dir));
1260 FS_AddGameDirectory(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, dir));
1269 const char *FS_FileExtension (const char *in)
1271 const char *separator, *backslash, *colon, *dot;
1273 separator = strrchr(in, '/');
1274 backslash = strrchr(in, '\\');
1275 if (!separator || separator < backslash)
1276 separator = backslash;
1277 colon = strrchr(in, ':');
1278 if (!separator || separator < colon)
1281 dot = strrchr(in, '.');
1282 if (dot == NULL || (separator && (dot < separator)))
1294 const char *FS_FileWithoutPath (const char *in)
1296 const char *separator, *backslash, *colon;
1298 separator = strrchr(in, '/');
1299 backslash = strrchr(in, '\\');
1300 if (!separator || separator < backslash)
1301 separator = backslash;
1302 colon = strrchr(in, ':');
1303 if (!separator || separator < colon)
1305 return separator ? separator + 1 : in;
1314 static void FS_ClearSearchPath (void)
1316 // unload all packs and directory information, close all pack files
1317 // (if a qfile is still reading a pack it won't be harmed because it used
1318 // dup() to get its own handle already)
1319 while (fs_searchpaths)
1321 searchpath_t *search = fs_searchpaths;
1322 fs_searchpaths = search->next;
1323 if (search->pack && search->pack != fs_selfpack)
1325 if(!search->pack->vpack)
1328 close(search->pack->handle);
1329 // free any memory associated with it
1330 if (search->pack->files)
1331 Mem_Free(search->pack->files);
1333 Mem_Free(search->pack);
1339 static void FS_AddSelfPack(void)
1343 searchpath_t *search;
1344 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1345 search->next = fs_searchpaths;
1346 search->pack = fs_selfpack;
1347 fs_searchpaths = search;
1357 void FS_Rescan (void)
1360 qboolean fs_modified = false;
1361 qboolean reset = false;
1362 char gamedirbuf[MAX_INPUTLINE];
1367 FS_ClearSearchPath();
1369 // automatically activate gamemode for the gamedirs specified
1371 COM_ChangeGameTypeForGameDirs();
1373 // add the game-specific paths
1374 // gamedirname1 (typically id1)
1375 FS_AddGameHierarchy (gamedirname1);
1376 // update the com_modname (used for server info)
1377 if (gamedirname2 && gamedirname2[0])
1378 strlcpy(com_modname, gamedirname2, sizeof(com_modname));
1380 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1382 // add the game-specific path, if any
1383 // (only used for mission packs and the like, which should set fs_modified)
1384 if (gamedirname2 && gamedirname2[0])
1387 FS_AddGameHierarchy (gamedirname2);
1391 // Adds basedir/gamedir as an override game
1392 // LordHavoc: now supports multiple -game directories
1393 // set the com_modname (reported in server info)
1395 for (i = 0;i < fs_numgamedirs;i++)
1398 FS_AddGameHierarchy (fs_gamedirs[i]);
1399 // update the com_modname (used server info)
1400 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1402 strlcat(gamedirbuf, va(vabuf, sizeof(vabuf), " %s", fs_gamedirs[i]), sizeof(gamedirbuf));
1404 strlcpy(gamedirbuf, fs_gamedirs[i], sizeof(gamedirbuf));
1406 Cvar_SetQuick(&cvar_fs_gamedir, gamedirbuf); // so QC or console code can query it
1408 // add back the selfpack as new first item
1411 // set the default screenshot name to either the mod name or the
1412 // gamemode screenshot name
1413 if (strcmp(com_modname, gamedirname1))
1414 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1416 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1418 if((i = COM_CheckParm("-modname")) && i < com_argc - 1)
1419 strlcpy(com_modname, com_argv[i+1], sizeof(com_modname));
1421 // If "-condebug" is in the command line, remove the previous log file
1422 if (COM_CheckParm ("-condebug") != 0)
1423 unlink (va(vabuf, sizeof(vabuf), "%s/qconsole.log", fs_gamedir));
1425 // look for the pop.lmp file and set registered to true if it is found
1426 if (FS_FileExists("gfx/pop.lmp"))
1427 Cvar_Set ("registered", "1");
1433 if (!registered.integer)
1436 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1438 Con_Print("Playing shareware version.\n");
1441 Con_Print("Playing registered version.\n");
1443 case GAME_STEELSTORM:
1444 if (registered.integer)
1445 Con_Print("Playing registered version.\n");
1447 Con_Print("Playing shareware version.\n");
1453 // unload all wads so that future queries will return the new data
1457 static void FS_Rescan_f(void)
1467 extern qboolean vid_opened;
1468 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1473 if (fs_numgamedirs == numgamedirs)
1475 for (i = 0;i < numgamedirs;i++)
1476 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1478 if (i == numgamedirs)
1479 return true; // already using this set of gamedirs, do nothing
1482 if (numgamedirs > MAX_GAMEDIRS)
1485 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1486 return false; // too many gamedirs
1489 for (i = 0;i < numgamedirs;i++)
1491 // if string is nasty, reject it
1492 p = FS_CheckGameDir(gamedirs[i]);
1496 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1497 return false; // nasty gamedirs
1499 if(p == fs_checkgamedir_missing && failmissing)
1502 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1503 return false; // missing gamedirs
1509 fs_numgamedirs = numgamedirs;
1510 for (i = 0;i < fs_numgamedirs;i++)
1511 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1513 // reinitialize filesystem to detect the new paks
1516 if (cls.demoplayback)
1522 // unload all sounds so they will be reloaded from the new files as needed
1523 S_UnloadAllSounds_f();
1525 // close down the video subsystem, it will start up again when the config finishes...
1529 // restart the video subsystem after the config is executed
1530 Cbuf_InsertText("\nloadconfig\nvid_restart\n\n");
1540 static void FS_GameDir_f (void)
1544 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1548 Con_Printf("gamedirs active:");
1549 for (i = 0;i < fs_numgamedirs;i++)
1550 Con_Printf(" %s", fs_gamedirs[i]);
1555 numgamedirs = Cmd_Argc() - 1;
1556 if (numgamedirs > MAX_GAMEDIRS)
1558 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1562 for (i = 0;i < numgamedirs;i++)
1563 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1565 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1567 // actually, changing during game would work fine, but would be stupid
1568 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1572 // halt demo playback to close the file
1575 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1578 static const char *FS_SysCheckGameDir(const char *gamedir, char *buf, size_t buflength)
1586 stringlistinit(&list);
1587 listdirectory(&list, gamedir, "");
1588 success = list.numstrings > 0;
1589 stringlistfreecontents(&list);
1593 f = FS_SysOpen(va(vabuf, sizeof(vabuf), "%smodinfo.txt", gamedir), "r", false);
1596 n = FS_Read (f, buf, buflength - 1);
1616 const char *FS_CheckGameDir(const char *gamedir)
1622 if (FS_CheckNastyPath(gamedir, true))
1625 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_userdir, gamedir), buf, sizeof(buf));
1630 // get description from basedir
1631 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1639 ret = FS_SysCheckGameDir(va(vabuf, sizeof(vabuf), "%s%s/", fs_basedir, gamedir), buf, sizeof(buf));
1643 return fs_checkgamedir_missing;
1646 static void FS_ListGameDirs(void)
1648 stringlist_t list, list2;
1653 fs_all_gamedirs_count = 0;
1655 Mem_Free(fs_all_gamedirs);
1657 stringlistinit(&list);
1658 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_basedir), "");
1659 listdirectory(&list, va(vabuf, sizeof(vabuf), "%s/", fs_userdir), "");
1660 stringlistsort(&list, false);
1662 stringlistinit(&list2);
1663 for(i = 0; i < list.numstrings; ++i)
1666 if(!strcmp(list.strings[i-1], list.strings[i]))
1668 info = FS_CheckGameDir(list.strings[i]);
1671 if(info == fs_checkgamedir_missing)
1675 stringlistappend(&list2, list.strings[i]);
1677 stringlistfreecontents(&list);
1679 fs_all_gamedirs = (gamedir_t *)Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1680 for(i = 0; i < list2.numstrings; ++i)
1682 info = FS_CheckGameDir(list2.strings[i]);
1683 // all this cannot happen any more, but better be safe than sorry
1686 if(info == fs_checkgamedir_missing)
1690 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[fs_all_gamedirs_count].name));
1691 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[fs_all_gamedirs_count].description));
1692 ++fs_all_gamedirs_count;
1698 #pragma comment(lib, "shell32.lib")
1708 void FS_Init_SelfPack (void)
1711 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1714 fs_selfpack = FS_LoadPackPK3FromFD(com_argv[0], com_selffd, true);
1720 buf = (char *) FS_LoadFile("darkplaces.opt", tempmempool, true, NULL);
1723 const char **new_argv;
1725 int args_left = 256;
1726 new_argv = (const char **)Mem_Alloc(fs_mempool, sizeof(*com_argv) * (com_argc + args_left + 2));
1729 new_argv[0] = "dummy";
1734 memcpy((char *)(&new_argv[0]), &com_argv[0], sizeof(*com_argv) * com_argc);
1737 while(COM_ParseToken_Console(&p))
1739 size_t sz = strlen(com_token) + 1; // shut up clang
1742 q = (char *)Mem_Alloc(fs_mempool, sz);
1743 strlcpy(q, com_token, sz);
1744 new_argv[com_argc + i] = q;
1747 new_argv[i+com_argc] = NULL;
1748 com_argv = new_argv;
1749 com_argc = com_argc + i;
1756 static int FS_ChooseUserDir(userdirmode_t userdirmode, char *userdir, size_t userdirsize)
1758 #if defined(__IPHONEOS__)
1759 if (userdirmode == USERDIRMODE_HOME)
1761 // fs_basedir is "" by default, to utilize this you can simply add your gamedir to the Resources in xcode
1762 // fs_userdir stores configurations to the Documents folder of the app
1763 strlcpy(userdir, "../Documents/", MAX_OSPATH);
1768 #elif defined(WIN32)
1770 #if _MSC_VER >= 1400
1773 TCHAR mydocsdir[MAX_PATH + 1];
1774 wchar_t *savedgamesdirw;
1775 char savedgamesdir[MAX_OSPATH];
1784 case USERDIRMODE_NOHOME:
1785 strlcpy(userdir, fs_basedir, userdirsize);
1787 case USERDIRMODE_MYGAMES:
1789 Sys_LoadLibrary(shfolderdllnames, &shfolder_dll, shfolderfuncs);
1791 if (qSHGetFolderPath && qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK)
1793 dpsnprintf(userdir, userdirsize, "%s/My Games/%s/", mydocsdir, gameuserdirname);
1796 #if _MSC_VER >= 1400
1797 _dupenv_s(&homedir, &homedirlen, "USERPROFILE");
1800 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1805 homedir = getenv("USERPROFILE");
1808 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1813 case USERDIRMODE_SAVEDGAMES:
1815 Sys_LoadLibrary(shell32dllnames, &shell32_dll, shell32funcs);
1817 Sys_LoadLibrary(ole32dllnames, &ole32_dll, ole32funcs);
1818 if (qSHGetKnownFolderPath && qCoInitializeEx && qCoTaskMemFree && qCoUninitialize)
1820 savedgamesdir[0] = 0;
1821 qCoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
1824 if (SHGetKnownFolderPath(FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1826 if (SHGetKnownFolderPath(&FOLDERID_SavedGames, KF_FLAG_CREATE | KF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1829 if (qSHGetKnownFolderPath(&qFOLDERID_SavedGames, qKF_FLAG_CREATE | qKF_FLAG_NO_ALIAS, NULL, &savedgamesdirw) == S_OK)
1831 memset(savedgamesdir, 0, sizeof(savedgamesdir));
1832 #if _MSC_VER >= 1400
1833 wcstombs_s(NULL, savedgamesdir, sizeof(savedgamesdir), savedgamesdirw, sizeof(savedgamesdir)-1);
1835 wcstombs(savedgamesdir, savedgamesdirw, sizeof(savedgamesdir)-1);
1837 qCoTaskMemFree(savedgamesdirw);
1840 if (savedgamesdir[0])
1842 dpsnprintf(userdir, userdirsize, "%s/%s/", savedgamesdir, gameuserdirname);
1857 case USERDIRMODE_NOHOME:
1858 strlcpy(userdir, fs_basedir, userdirsize);
1860 case USERDIRMODE_HOME:
1861 homedir = getenv("HOME");
1864 dpsnprintf(userdir, userdirsize, "%s/.%s/", homedir, gameuserdirname);
1868 case USERDIRMODE_SAVEDGAMES:
1869 homedir = getenv("HOME");
1873 dpsnprintf(userdir, userdirsize, "%s/Library/Application Support/%s/", homedir, gameuserdirname);
1875 // the XDG say some files would need to go in:
1876 // XDG_CONFIG_HOME (or ~/.config/%s/)
1877 // XDG_DATA_HOME (or ~/.local/share/%s/)
1878 // XDG_CACHE_HOME (or ~/.cache/%s/)
1879 // and also search the following global locations if defined:
1880 // XDG_CONFIG_DIRS (normally /etc/xdg/%s/)
1881 // XDG_DATA_DIRS (normally /usr/share/%s/)
1882 // this would be too complicated...
1892 #if !defined(__IPHONEOS__)
1895 // historical behavior...
1896 if (userdirmode == USERDIRMODE_NOHOME && strcmp(gamedirname1, "id1"))
1897 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
1900 // see if we can write to this path (note: won't create path)
1902 // no access() here, we must try to open the file for appending
1903 fd = FS_SysOpenFD(va(vabuf, sizeof(vabuf), "%s%s/config.cfg", userdir, gamedirname1), "a", false);
1907 // on Unix, we don't need to ACTUALLY attempt to open the file
1908 if(access(va(vabuf, sizeof(vabuf), "%s%s/", userdir, gamedirname1), W_OK | X_OK) >= 0)
1915 return 1; // good choice - the path exists and is writable
1919 if (userdirmode == USERDIRMODE_NOHOME)
1920 return -1; // path usually already exists, we lack permissions
1922 return 0; // probably good - failed to write but maybe we need to create path
1942 // Overrides the system supplied base directory (under GAMENAME)
1943 // 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)
1944 i = COM_CheckParm ("-basedir");
1945 if (i && i < com_argc-1)
1947 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1948 i = (int)strlen (fs_basedir);
1949 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1950 fs_basedir[i-1] = 0;
1954 // If the base directory is explicitly defined by the compilation process
1955 #ifdef DP_FS_BASEDIR
1956 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1957 #elif defined(__ANDROID__)
1958 dpsnprintf(fs_basedir, sizeof(fs_basedir), "/sdcard/%s/", gameuserdirname);
1959 #elif defined(MACOSX)
1960 // FIXME: is there a better way to find the directory outside the .app, without using Objective-C?
1961 if (strstr(com_argv[0], ".app/"))
1964 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1965 split = strstr(fs_basedir, ".app/");
1968 struct stat statresult;
1970 // truncate to just after the .app/
1972 // see if gamedir exists in Resources
1973 if (stat(va(vabuf, sizeof(vabuf), "%s/Contents/Resources/%s", fs_basedir, gamedirname1), &statresult) == 0)
1975 // found gamedir inside Resources, use it
1976 strlcat(fs_basedir, "Contents/Resources/", sizeof(fs_basedir));
1980 // no gamedir found in Resources, gamedir is probably
1981 // outside the .app, remove .app part of path
1982 while (split > fs_basedir && *split != '/')
1991 // make sure the appending of a path separator won't create an unterminated string
1992 memset(fs_basedir + sizeof(fs_basedir) - 2, 0, 2);
1993 // add a path separator to the end of the basedir if it lacks one
1994 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1995 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1997 // Add the personal game directory
1998 if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1999 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
2000 else if (COM_CheckParm("-nohome"))
2001 *fs_userdir = 0; // user wants roaming installation, no userdir
2005 int highestuserdirmode = USERDIRMODE_COUNT - 1;
2006 int preferreduserdirmode = USERDIRMODE_COUNT - 1;
2007 int userdirstatus[USERDIRMODE_COUNT];
2009 // historical behavior...
2010 if (!strcmp(gamedirname1, "id1"))
2011 preferreduserdirmode = USERDIRMODE_NOHOME;
2013 // check what limitations the user wants to impose
2014 if (COM_CheckParm("-home")) preferreduserdirmode = USERDIRMODE_HOME;
2015 if (COM_CheckParm("-mygames")) preferreduserdirmode = USERDIRMODE_MYGAMES;
2016 if (COM_CheckParm("-savedgames")) preferreduserdirmode = USERDIRMODE_SAVEDGAMES;
2017 // gather the status of the possible userdirs
2018 for (dirmode = 0;dirmode < USERDIRMODE_COUNT;dirmode++)
2020 userdirstatus[dirmode] = FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2021 if (userdirstatus[dirmode] == 1)
2022 Con_DPrintf("userdir %i = %s (writable)\n", dirmode, fs_userdir);
2023 else if (userdirstatus[dirmode] == 0)
2024 Con_DPrintf("userdir %i = %s (not writable or does not exist)\n", dirmode, fs_userdir);
2026 Con_DPrintf("userdir %i (not applicable)\n", dirmode);
2028 // some games may prefer writing to basedir, but if write fails we
2029 // have to search for a real userdir...
2030 if (preferreduserdirmode == 0 && userdirstatus[0] < 1)
2031 preferreduserdirmode = highestuserdirmode;
2032 // check for an existing userdir and continue using it if possible...
2033 for (dirmode = USERDIRMODE_COUNT - 1;dirmode > 0;dirmode--)
2034 if (userdirstatus[dirmode] == 1)
2036 // if no existing userdir found, make a new one...
2037 if (dirmode == 0 && preferreduserdirmode > 0)
2038 for (dirmode = preferreduserdirmode;dirmode > 0;dirmode--)
2039 if (userdirstatus[dirmode] >= 0)
2041 // and finally, we picked one...
2042 FS_ChooseUserDir((userdirmode_t)dirmode, fs_userdir, sizeof(fs_userdir));
2043 Con_DPrintf("userdir %i is the winner\n", dirmode);
2046 // if userdir equal to basedir, clear it to avoid confusion later
2047 if (!strcmp(fs_basedir, fs_userdir))
2052 p = FS_CheckGameDir(gamedirname1);
2053 if(!p || p == fs_checkgamedir_missing)
2054 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
2058 p = FS_CheckGameDir(gamedirname2);
2059 if(!p || p == fs_checkgamedir_missing)
2060 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
2064 // Adds basedir/gamedir as an override game
2065 // LordHavoc: now supports multiple -game directories
2066 for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
2070 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
2073 p = FS_CheckGameDir(com_argv[i]);
2075 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
2076 if(p == fs_checkgamedir_missing)
2077 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
2078 // add the gamedir to the list of active gamedirs
2079 strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
2084 // generate the searchpath
2087 if (Thread_HasThreads())
2088 fs_mutex = Thread_CreateMutex();
2091 void FS_Init_Commands(void)
2093 Cvar_RegisterVariable (&scr_screenshot_name);
2094 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
2095 Cvar_RegisterVariable (&cvar_fs_gamedir);
2097 Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
2098 Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
2099 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
2100 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
2101 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
2102 Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
2110 void FS_Shutdown (void)
2112 // close all pack files and such
2113 // (hopefully there aren't any other open files, but they'll be cleaned up
2114 // by the OS anyway)
2115 FS_ClearSearchPath();
2116 Mem_FreePool (&fs_mempool);
2117 PK3_CloseLibrary ();
2120 Sys_UnloadLibrary (&shfolder_dll);
2121 Sys_UnloadLibrary (&shell32_dll);
2122 Sys_UnloadLibrary (&ole32_dll);
2126 Thread_DestroyMutex(fs_mutex);
2129 int FS_SysOpenFD(const char *filepath, const char *mode, qboolean nonblocking)
2134 qboolean dolock = false;
2136 // Parse the mode string
2145 opt = O_CREAT | O_TRUNC;
2149 opt = O_CREAT | O_APPEND;
2152 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
2155 for (ind = 1; mode[ind] != '\0'; ind++)
2169 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
2170 filepath, mode, mode[ind]);
2177 if(COM_CheckParm("-readonly") && mod != O_RDONLY)
2181 # if _MSC_VER >= 1400
2182 _sopen_s(&handle, filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2184 handle = _sopen (filepath, mod | opt, (dolock ? ((mod == O_RDONLY) ? _SH_DENYRD : _SH_DENYRW) : _SH_DENYNO), _S_IREAD | _S_IWRITE);
2187 handle = open (filepath, mod | opt, 0666);
2188 if(handle >= 0 && dolock)
2191 l.l_type = ((mod == O_RDONLY) ? F_RDLCK : F_WRLCK);
2192 l.l_whence = SEEK_SET;
2195 if(fcntl(handle, F_SETLK, &l) == -1)
2207 ====================
2210 Internal function used to create a qfile_t and open the relevant non-packed file on disk
2211 ====================
2213 qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
2217 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2219 file->handle = FS_SysOpenFD(filepath, mode, nonblocking);
2220 if (file->handle < 0)
2226 file->filename = Mem_strdup(fs_mempool, filepath);
2228 file->real_length = lseek (file->handle, 0, SEEK_END);
2230 // For files opened in append mode, we start at the end of the file
2232 file->position = file->real_length;
2234 lseek (file->handle, 0, SEEK_SET);
2244 Open a packed file using its package file descriptor
2247 static qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
2253 pfile = &pack->files[pack_ind];
2255 // If we don't have the true offset, get it now
2256 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
2257 if (!PK3_GetTrueFileOffset (pfile, pack))
2260 #ifndef LINK_TO_ZLIB
2261 // No Zlib DLL = no compressed files
2262 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
2264 Con_Printf("WARNING: can't open the compressed file %s\n"
2265 "You need the Zlib DLL to use compressed files\n",
2271 // LordHavoc: lseek affects all duplicates of a handle so we do it before
2272 // the dup() call to avoid having to close the dup_handle on error here
2273 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
2275 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %08x%08x)\n",
2276 pfile->name, pack->filename, (unsigned int)(pfile->offset >> 32), (unsigned int)(pfile->offset));
2280 dup_handle = dup (pack->handle);
2283 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
2287 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2288 memset (file, 0, sizeof (*file));
2289 file->handle = dup_handle;
2290 file->flags = QFILE_FLAG_PACKED;
2291 file->real_length = pfile->realsize;
2292 file->offset = pfile->offset;
2296 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
2300 file->flags |= QFILE_FLAG_DEFLATED;
2302 // We need some more variables
2303 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
2305 ztk->comp_length = pfile->packsize;
2307 // Initialize zlib stream
2308 ztk->zstream.next_in = ztk->input;
2309 ztk->zstream.avail_in = 0;
2311 /* From Zlib's "unzip.c":
2313 * windowBits is passed < 0 to tell that there is no zlib header.
2314 * Note that in this case inflate *requires* an extra "dummy" byte
2315 * after the compressed stream in order to complete decompression and
2316 * return Z_STREAM_END.
2317 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
2318 * size of both compressed and uncompressed data
2320 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
2322 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
2328 ztk->zstream.next_out = file->buff;
2329 ztk->zstream.avail_out = sizeof (file->buff);
2338 ====================
2341 Return true if the path should be rejected due to one of the following:
2342 1: path elements that are non-portable
2343 2: path elements that would allow access to files outside the game directory,
2344 or are just not a good idea for a mod to be using.
2345 ====================
2347 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
2349 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
2353 // Windows: don't allow \ in filenames (windows-only), period.
2354 // (on Windows \ is a directory separator, but / is also supported)
2355 if (strstr(path, "\\"))
2356 return 1; // non-portable
2358 // Mac: don't allow Mac-only filenames - : is a directory separator
2359 // instead of /, but we rely on / working already, so there's no reason to
2360 // support a Mac-only path
2361 // Amiga and Windows: : tries to go to root of drive
2362 if (strstr(path, ":"))
2363 return 1; // non-portable attempt to go to root of drive
2365 // Amiga: // is parent directory
2366 if (strstr(path, "//"))
2367 return 1; // non-portable attempt to go to parent directory
2369 // all: don't allow going to parent directory (../ or /../)
2370 if (strstr(path, ".."))
2371 return 2; // attempt to go outside the game directory
2373 // Windows and UNIXes: don't allow absolute paths
2375 return 2; // attempt to go outside the game directory
2377 // all: don't allow . character immediately before a slash, this catches all imaginable cases of ./, ../, .../, etc
2378 if (strstr(path, "./"))
2379 return 2; // possible attempt to go outside the game directory
2381 // all: forbid trailing slash on gamedir
2382 if (isgamedir && path[strlen(path)-1] == '/')
2385 // all: forbid leading dot on any filename for any reason
2386 if (strstr(path, "/."))
2387 return 2; // attempt to go outside the game directory
2389 // after all these checks we're pretty sure it's a / separated filename
2390 // and won't do much if any harm
2396 ====================
2399 Look for a file in the packages and in the filesystem
2401 Return the searchpath where the file was found (or NULL)
2402 and the file index in the package if relevant
2403 ====================
2405 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
2407 searchpath_t *search;
2410 // search through the path, one element at a time
2411 for (search = fs_searchpaths;search;search = search->next)
2413 // is the element a pak file?
2414 if (search->pack && !search->pack->vpack)
2416 int (*strcmp_funct) (const char* str1, const char* str2);
2417 int left, right, middle;
2420 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
2422 // Look for the file (binary search)
2424 right = pak->numfiles - 1;
2425 while (left <= right)
2429 middle = (left + right) / 2;
2430 diff = strcmp_funct (pak->files[middle].name, name);
2435 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
2437 // yes, but the first one is empty so we treat it as not being there
2438 if (!quiet && developer_extra.integer)
2439 Con_DPrintf("FS_FindFile: %s is marked as deleted\n", name);
2446 if (!quiet && developer_extra.integer)
2447 Con_DPrintf("FS_FindFile: %s in %s\n",
2448 pak->files[middle].name, pak->filename);
2455 // If we're too far in the list
2464 char netpath[MAX_OSPATH];
2465 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2466 if (FS_SysFileExists (netpath))
2468 if (!quiet && developer_extra.integer)
2469 Con_DPrintf("FS_FindFile: %s\n", netpath);
2478 if (!quiet && developer_extra.integer)
2479 Con_DPrintf("FS_FindFile: can't find %s\n", name);
2491 Look for a file in the search paths and open it in read-only mode
2494 static qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2496 searchpath_t *search;
2499 search = FS_FindFile (filename, &pack_ind, quiet);
2505 // Found in the filesystem?
2508 // this works with vpacks, so we are fine
2509 char path [MAX_OSPATH];
2510 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2511 return FS_SysOpen (path, "rb", nonblocking);
2514 // So, we found it in a package...
2516 // Is it a PK3 symlink?
2517 // TODO also handle directory symlinks by parsing the whole structure...
2518 // but heck, file symlinks are good enough for now
2519 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2521 if(symlinkLevels <= 0)
2523 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2528 char linkbuf[MAX_QPATH];
2530 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2531 const char *mergeslash;
2536 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2542 // Now combine the paths...
2543 mergeslash = strrchr(filename, '/');
2544 mergestart = linkbuf;
2546 mergeslash = filename;
2547 while(!strncmp(mergestart, "../", 3))
2550 while(mergeslash > filename)
2553 if(*mergeslash == '/')
2557 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2558 if(mergeslash == filename)
2560 // Either mergeslash == filename, then we just replace the name (done below)
2564 // Or, we append the name after mergeslash;
2565 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2566 int spaceNeeded = mergeslash - filename + 1;
2567 int spaceRemoved = mergestart - linkbuf;
2568 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2570 Con_DPrintf("symlink: too long path rejected\n");
2573 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2574 memcpy(linkbuf, filename, spaceNeeded);
2575 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2576 mergestart = linkbuf;
2578 if (!quiet && developer_loading.integer)
2579 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2580 if(FS_CheckNastyPath (mergestart, false))
2582 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2585 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2589 return FS_OpenPackedFile (search->pack, pack_ind);
2594 =============================================================================
2596 MAIN PUBLIC FUNCTIONS
2598 =============================================================================
2602 ====================
2605 Open a file in the userpath. The syntax is the same as fopen
2606 Used for savegame scanning in menu, and all file writing.
2607 ====================
2609 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2611 char real_path [MAX_OSPATH];
2613 if (FS_CheckNastyPath(filepath, false))
2615 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2619 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath); // this is never a vpack
2621 // If the file is opened in "write", "append", or "read/write" mode,
2622 // create directories up to the file.
2623 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2624 FS_CreatePath (real_path);
2625 return FS_SysOpen (real_path, mode, false);
2630 ====================
2633 Open a file. The syntax is the same as fopen
2634 ====================
2636 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2638 qfile_t *result = NULL;
2639 if (FS_CheckNastyPath(filepath, false))
2641 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2645 if (fs_mutex) Thread_LockMutex(fs_mutex);
2646 result = FS_OpenReadFile (filepath, quiet, false, 16);
2647 if (fs_mutex) Thread_UnlockMutex(fs_mutex);
2653 ====================
2656 Open a file. The syntax is the same as fopen
2657 ====================
2659 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2662 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2663 memset (file, 0, sizeof (*file));
2664 file->flags = QFILE_FLAG_DATA;
2666 file->real_length = size;
2672 ====================
2676 ====================
2678 int FS_Close (qfile_t* file)
2680 if(file->flags & QFILE_FLAG_DATA)
2686 if (close (file->handle))
2691 if (file->flags & QFILE_FLAG_REMOVE)
2692 remove(file->filename);
2694 Mem_Free((void *) file->filename);
2699 qz_inflateEnd (&file->ztk->zstream);
2700 Mem_Free (file->ztk);
2707 void FS_RemoveOnClose(qfile_t* file)
2709 file->flags |= QFILE_FLAG_REMOVE;
2713 ====================
2716 Write "datasize" bytes into a file
2717 ====================
2719 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2721 fs_offset_t written = 0;
2723 // If necessary, seek to the exact file position we're supposed to be
2724 if (file->buff_ind != file->buff_len)
2725 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2727 // Purge cached data
2730 // Write the buffer and update the position
2731 // 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)
2732 while (written < (fs_offset_t)datasize)
2734 // figure out how much to write in one chunk
2735 fs_offset_t maxchunk = 1<<30; // 1 GiB
2736 int chunk = (int)min((fs_offset_t)datasize - written, maxchunk);
2737 int result = (int)write (file->handle, (const unsigned char *)data + written, chunk);
2738 // if at least some was written, add it to our accumulator
2741 // if the result is not what we expected, consider the write to be incomplete
2742 if (result != chunk)
2745 file->position = lseek (file->handle, 0, SEEK_CUR);
2746 if (file->real_length < file->position)
2747 file->real_length = file->position;
2749 // note that this will never be less than 0 even if the write failed
2755 ====================
2758 Read up to "buffersize" bytes from a file
2759 ====================
2761 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2763 fs_offset_t count, done;
2765 if (buffersize == 0)
2768 // Get rid of the ungetc character
2769 if (file->ungetc != EOF)
2771 ((char*)buffer)[0] = file->ungetc;
2779 if(file->flags & QFILE_FLAG_DATA)
2781 size_t left = file->real_length - file->position;
2782 if(buffersize > left)
2784 memcpy(buffer, file->data + file->position, buffersize);
2785 file->position += buffersize;
2789 // First, we copy as many bytes as we can from "buff"
2790 if (file->buff_ind < file->buff_len)
2792 count = file->buff_len - file->buff_ind;
2793 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2795 memcpy (buffer, &file->buff[file->buff_ind], count);
2796 file->buff_ind += count;
2798 buffersize -= count;
2799 if (buffersize == 0)
2803 // NOTE: at this point, the read buffer is always empty
2805 // If the file isn't compressed
2806 if (! (file->flags & QFILE_FLAG_DEFLATED))
2810 // We must take care to not read after the end of the file
2811 count = file->real_length - file->position;
2813 // If we have a lot of data to get, put them directly into "buffer"
2814 if (buffersize > sizeof (file->buff) / 2)
2816 if (count > (fs_offset_t)buffersize)
2817 count = (fs_offset_t)buffersize;
2818 lseek (file->handle, file->offset + file->position, SEEK_SET);
2819 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2823 file->position += nb;
2825 // Purge cached data
2831 if (count > (fs_offset_t)sizeof (file->buff))
2832 count = (fs_offset_t)sizeof (file->buff);
2833 lseek (file->handle, file->offset + file->position, SEEK_SET);
2834 nb = read (file->handle, file->buff, count);
2837 file->buff_len = nb;
2838 file->position += nb;
2840 // Copy the requested data in "buffer" (as much as we can)
2841 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2842 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2843 file->buff_ind = count;
2851 // If the file is compressed, it's more complicated...
2852 // We cycle through a few operations until we have read enough data
2853 while (buffersize > 0)
2855 ztoolkit_t *ztk = file->ztk;
2858 // NOTE: at this point, the read buffer is always empty
2860 // If "input" is also empty, we need to refill it
2861 if (ztk->in_ind == ztk->in_len)
2863 // If we are at the end of the file
2864 if (file->position == file->real_length)
2867 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2868 if (count > (fs_offset_t)sizeof (ztk->input))
2869 count = (fs_offset_t)sizeof (ztk->input);
2870 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2871 if (read (file->handle, ztk->input, count) != count)
2873 Con_Printf ("FS_Read: unexpected end of file\n");
2878 ztk->in_len = count;
2879 ztk->in_position += count;
2882 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2883 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2885 // Now that we are sure we have compressed data available, we need to determine
2886 // if it's better to inflate it in "file->buff" or directly in "buffer"
2888 // Inflate the data in "file->buff"
2889 if (buffersize < sizeof (file->buff) / 2)
2891 ztk->zstream.next_out = file->buff;
2892 ztk->zstream.avail_out = sizeof (file->buff);
2893 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2894 if (error != Z_OK && error != Z_STREAM_END)
2896 Con_Printf ("FS_Read: Can't inflate file\n");
2899 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2901 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2902 file->position += file->buff_len;
2904 // Copy the requested data in "buffer" (as much as we can)
2905 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2906 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2907 file->buff_ind = count;
2910 // Else, we inflate directly in "buffer"
2913 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2914 ztk->zstream.avail_out = (unsigned int)buffersize;
2915 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2916 if (error != Z_OK && error != Z_STREAM_END)
2918 Con_Printf ("FS_Read: Can't inflate file\n");
2921 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2923 // How much data did it inflate?
2924 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2925 file->position += count;
2927 // Purge cached data
2932 buffersize -= count;
2940 ====================
2943 Print a string into a file
2944 ====================
2946 int FS_Print (qfile_t* file, const char *msg)
2948 return (int)FS_Write (file, msg, strlen (msg));
2952 ====================
2955 Print a string into a file
2956 ====================
2958 int FS_Printf(qfile_t* file, const char* format, ...)
2963 va_start (args, format);
2964 result = FS_VPrintf (file, format, args);
2972 ====================
2975 Print a string into a file
2976 ====================
2978 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2981 fs_offset_t buff_size = MAX_INPUTLINE;
2986 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2987 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2988 if (len >= 0 && len < buff_size)
2990 Mem_Free (tempbuff);
2994 len = write (file->handle, tempbuff, len);
2995 Mem_Free (tempbuff);
3002 ====================
3005 Get the next character of a file
3006 ====================
3008 int FS_Getc (qfile_t* file)
3012 if (FS_Read (file, &c, 1) != 1)
3020 ====================
3023 Put a character back into the read buffer (only supports one character!)
3024 ====================
3026 int FS_UnGetc (qfile_t* file, unsigned char c)
3028 // If there's already a character waiting to be read
3029 if (file->ungetc != EOF)
3038 ====================
3041 Move the position index in a file
3042 ====================
3044 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
3047 unsigned char* buffer;
3048 fs_offset_t buffersize;
3050 // Compute the file offset
3054 offset += file->position - file->buff_len + file->buff_ind;
3061 offset += file->real_length;
3067 if (offset < 0 || offset > file->real_length)
3070 if(file->flags & QFILE_FLAG_DATA)
3072 file->position = offset;
3076 // If we have the data in our read buffer, we don't need to actually seek
3077 if (file->position - file->buff_len <= offset && offset <= file->position)
3079 file->buff_ind = offset + file->buff_len - file->position;
3083 // Purge cached data
3086 // Unpacked or uncompressed files can seek directly
3087 if (! (file->flags & QFILE_FLAG_DEFLATED))
3089 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
3091 file->position = offset;
3095 // Seeking in compressed files is more a hack than anything else,
3096 // but we need to support it, so here we go.
3099 // If we have to go back in the file, we need to restart from the beginning
3100 if (offset <= file->position)
3104 ztk->in_position = 0;
3106 lseek (file->handle, file->offset, SEEK_SET);
3108 // Reset the Zlib stream
3109 ztk->zstream.next_in = ztk->input;
3110 ztk->zstream.avail_in = 0;
3111 qz_inflateReset (&ztk->zstream);
3114 // We need a big buffer to force inflating into it directly
3115 buffersize = 2 * sizeof (file->buff);
3116 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
3118 // Skip all data until we reach the requested offset
3119 while (offset > file->position)
3121 fs_offset_t diff = offset - file->position;
3122 fs_offset_t count, len;
3124 count = (diff > buffersize) ? buffersize : diff;
3125 len = FS_Read (file, buffer, count);
3139 ====================
3142 Give the current position in a file
3143 ====================
3145 fs_offset_t FS_Tell (qfile_t* file)
3147 return file->position - file->buff_len + file->buff_ind;
3152 ====================
3155 Give the total size of a file
3156 ====================
3158 fs_offset_t FS_FileSize (qfile_t* file)
3160 return file->real_length;
3165 ====================
3168 Erases any buffered input or output data
3169 ====================
3171 void FS_Purge (qfile_t* file)
3183 Filename are relative to the quake directory.
3184 Always appends a 0 byte.
3187 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
3190 unsigned char *buf = NULL;
3191 fs_offset_t filesize = 0;
3193 file = FS_OpenVirtualFile(path, quiet);
3196 filesize = file->real_length;
3199 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
3204 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
3205 buf[filesize] = '\0';
3206 FS_Read (file, buf, filesize);
3208 if (developer_loadfile.integer)
3209 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
3212 if (filesizepointer)
3213 *filesizepointer = filesize;
3222 The filename will be prefixed by the current game directory
3225 qboolean FS_WriteFileInBlocks (const char *filename, const void *const *data, const fs_offset_t *len, size_t count)
3229 fs_offset_t lentotal;
3231 file = FS_OpenRealFile(filename, "wb", false);
3234 Con_Printf("FS_WriteFile: failed on %s\n", filename);
3239 for(i = 0; i < count; ++i)
3241 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)lentotal);
3242 for(i = 0; i < count; ++i)
3243 FS_Write (file, data[i], len[i]);
3248 qboolean FS_WriteFile (const char *filename, const void *data, fs_offset_t len)
3250 return FS_WriteFileInBlocks(filename, &data, &len, 1);
3255 =============================================================================
3257 OTHERS PUBLIC FUNCTIONS
3259 =============================================================================
3267 void FS_StripExtension (const char *in, char *out, size_t size_out)
3275 while ((currentchar = *in) && size_out > 1)
3277 if (currentchar == '.')
3279 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
3281 *out++ = currentchar;
3297 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
3301 // if path doesn't have a .EXT, append extension
3302 // (extension should include the .)
3303 src = path + strlen(path) - 1;
3305 while (*src != '/' && src != path)
3308 return; // it has an extension
3312 strlcat (path, extension, size_path);
3320 Look for a file in the packages and in the filesystem
3323 int FS_FileType (const char *filename)
3325 searchpath_t *search;
3326 char fullpath[MAX_OSPATH];
3328 search = FS_FindFile (filename, NULL, true);
3330 return FS_FILETYPE_NONE;
3332 if(search->pack && !search->pack->vpack)
3333 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
3335 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
3336 return FS_SysFileType(fullpath);
3344 Look for a file in the packages and in the filesystem
3347 qboolean FS_FileExists (const char *filename)
3349 return (FS_FindFile (filename, NULL, true) != NULL);
3357 Look for a file in the filesystem only
3360 int FS_SysFileType (const char *path)
3363 // Sajt - some older sdks are missing this define
3364 # ifndef INVALID_FILE_ATTRIBUTES
3365 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
3368 DWORD result = GetFileAttributes(path);
3370 if(result == INVALID_FILE_ATTRIBUTES)
3371 return FS_FILETYPE_NONE;
3373 if(result & FILE_ATTRIBUTE_DIRECTORY)
3374 return FS_FILETYPE_DIRECTORY;
3376 return FS_FILETYPE_FILE;
3380 if (stat (path,&buf) == -1)
3381 return FS_FILETYPE_NONE;
3384 #define S_ISDIR(a) (((a) & S_IFMT) == S_IFDIR)
3386 if(S_ISDIR(buf.st_mode))
3387 return FS_FILETYPE_DIRECTORY;
3389 return FS_FILETYPE_FILE;
3393 qboolean FS_SysFileExists (const char *path)
3395 return FS_SysFileType (path) != FS_FILETYPE_NONE;
3398 void FS_mkdir (const char *path)
3400 if(COM_CheckParm("-readonly"))
3414 Allocate and fill a search structure with information on matching filenames.
3417 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
3420 searchpath_t *searchpath;
3422 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
3423 stringlist_t resultlist;
3424 stringlist_t dirlist;
3425 const char *slash, *backslash, *colon, *separator;
3427 char temp[MAX_OSPATH];
3429 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
3434 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
3438 stringlistinit(&resultlist);
3439 stringlistinit(&dirlist);
3441 slash = strrchr(pattern, '/');
3442 backslash = strrchr(pattern, '\\');
3443 colon = strrchr(pattern, ':');
3444 separator = max(slash, backslash);
3445 separator = max(separator, colon);
3446 basepathlength = separator ? (separator + 1 - pattern) : 0;
3447 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
3449 memcpy(basepath, pattern, basepathlength);
3450 basepath[basepathlength] = 0;
3452 // search through the path, one element at a time
3453 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
3455 // is the element a pak file?
3456 if (searchpath->pack && !searchpath->pack->vpack)
3458 // look through all the pak file elements
3459 pak = searchpath->pack;
3460 for (i = 0;i < pak->numfiles;i++)
3462 strlcpy(temp, pak->files[i].name, sizeof(temp));
3465 if (matchpattern(temp, (char *)pattern, true))
3467 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3468 if (!strcmp(resultlist.strings[resultlistindex], temp))
3470 if (resultlistindex == resultlist.numstrings)
3472 stringlistappend(&resultlist, temp);
3473 if (!quiet && developer_loading.integer)
3474 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
3477 // strip off one path element at a time until empty
3478 // this way directories are added to the listing if they match the pattern
3479 slash = strrchr(temp, '/');
3480 backslash = strrchr(temp, '\\');
3481 colon = strrchr(temp, ':');
3483 if (separator < slash)
3485 if (separator < backslash)
3486 separator = backslash;
3487 if (separator < colon)
3489 *((char *)separator) = 0;
3495 stringlist_t matchedSet, foundSet;
3496 const char *start = pattern;
3498 stringlistinit(&matchedSet);
3499 stringlistinit(&foundSet);
3500 // add a first entry to the set
3501 stringlistappend(&matchedSet, "");
3502 // iterate through pattern's path
3505 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3506 char subpath[MAX_OSPATH];
3507 char subpattern[MAX_OSPATH];
3509 // find the next wildcard
3510 wildcard = strchr(start, '?');
3511 asterisk = strchr(start, '*');
3512 if (asterisk && (!wildcard || asterisk < wildcard))
3514 wildcard = asterisk;
3519 nextseparator = strchr( wildcard, '/' );
3523 nextseparator = NULL;
3526 if( !nextseparator ) {
3527 nextseparator = start + strlen( start );
3530 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3531 // copy everything up except nextseperator
3532 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3533 // find the last '/' before the wildcard
3534 prevseparator = strrchr( subpattern, '/' );
3536 prevseparator = subpattern;
3539 // copy everything from start to the previous including the '/' (before the wildcard)
3540 // everything up to start is already included in the path of matchedSet's entries
3541 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3543 // for each entry in matchedSet try to open the subdirectories specified in subpath
3544 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3545 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3546 strlcat( temp, subpath, sizeof(temp) );
3547 listdirectory( &foundSet, searchpath->filename, temp );
3549 if( dirlistindex == 0 ) {
3552 // reset the current result set
3553 stringlistfreecontents( &matchedSet );
3554 // match against the pattern
3555 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3556 const char *direntry = foundSet.strings[ dirlistindex ];
3557 if (matchpattern(direntry, subpattern, true)) {
3558 stringlistappend( &matchedSet, direntry );
3561 stringlistfreecontents( &foundSet );
3563 start = nextseparator;
3566 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3568 const char *temp = matchedSet.strings[dirlistindex];
3569 if (matchpattern(temp, (char *)pattern, true))
3571 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3572 if (!strcmp(resultlist.strings[resultlistindex], temp))
3574 if (resultlistindex == resultlist.numstrings)
3576 stringlistappend(&resultlist, temp);
3577 if (!quiet && developer_loading.integer)
3578 Con_Printf("SearchDirFile: %s\n", temp);
3582 stringlistfreecontents( &matchedSet );
3586 if (resultlist.numstrings)
3588 stringlistsort(&resultlist, true);
3589 numfiles = resultlist.numstrings;
3591 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3592 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3593 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3594 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3595 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3596 search->numfilenames = (int)numfiles;
3599 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3602 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3603 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3604 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3606 numchars += (int)textlen;
3609 stringlistfreecontents(&resultlist);
3615 void FS_FreeSearch(fssearch_t *search)
3620 extern int con_linewidth;
3621 static int FS_ListDirectory(const char *pattern, int oneperline)
3630 char linebuf[MAX_INPUTLINE];
3632 search = FS_Search(pattern, true, true);
3635 numfiles = search->numfilenames;
3638 // FIXME: the names could be added to one column list and then
3639 // gradually shifted into the next column if they fit, and then the
3640 // next to make a compact variable width listing but it's a lot more
3642 // find width for columns
3644 for (i = 0;i < numfiles;i++)
3646 l = (int)strlen(search->filenames[i]);
3647 if (columnwidth < l)
3650 // count the spacing character
3652 // calculate number of columns
3653 numcolumns = con_linewidth / columnwidth;
3654 // don't bother with the column printing if it's only one column
3655 if (numcolumns >= 2)
3657 numlines = (numfiles + numcolumns - 1) / numcolumns;
3658 for (i = 0;i < numlines;i++)
3661 for (k = 0;k < numcolumns;k++)
3663 l = i * numcolumns + k;
3666 name = search->filenames[l];
3667 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3668 linebuf[linebufpos++] = name[j];
3669 // space out name unless it's the last on the line
3670 if (k + 1 < numcolumns && l + 1 < numfiles)
3671 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3672 linebuf[linebufpos++] = ' ';
3675 linebuf[linebufpos] = 0;
3676 Con_Printf("%s\n", linebuf);
3683 for (i = 0;i < numfiles;i++)
3684 Con_Printf("%s\n", search->filenames[i]);
3685 FS_FreeSearch(search);
3686 return (int)numfiles;
3689 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3691 const char *pattern;
3692 if (Cmd_Argc() >= 3)
3694 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3697 if (Cmd_Argc() == 2)
3698 pattern = Cmd_Argv(1);
3701 if (!FS_ListDirectory(pattern, oneperline))
3702 Con_Print("No files found.\n");
3707 FS_ListDirectoryCmd("dir", true);
3712 FS_ListDirectoryCmd("ls", false);
3715 void FS_Which_f(void)
3717 const char *filename;
3720 if (Cmd_Argc() != 2)
3722 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3725 filename = Cmd_Argv(1);
3726 sp = FS_FindFile(filename, &index, true);
3728 Con_Printf("%s isn't anywhere\n", filename);
3734 Con_Printf("%s is in virtual package %sdir\n", filename, sp->pack->shortname);
3736 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3739 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3743 const char *FS_WhichPack(const char *filename)
3746 searchpath_t *sp = FS_FindFile(filename, &index, true);
3748 return sp->pack->shortname;
3756 ====================
3757 FS_IsRegisteredQuakePack
3759 Look for a proof of purchase file file in the requested package
3761 If it is found, this file should NOT be downloaded.
3762 ====================
3764 qboolean FS_IsRegisteredQuakePack(const char *name)
3766 searchpath_t *search;
3769 // search through the path, one element at a time
3770 for (search = fs_searchpaths;search;search = search->next)
3772 if (search->pack && !search->pack->vpack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3773 // TODO do we want to support vpacks in here too?
3775 int (*strcmp_funct) (const char* str1, const char* str2);
3776 int left, right, middle;
3779 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3781 // Look for the file (binary search)
3783 right = pak->numfiles - 1;
3784 while (left <= right)
3788 middle = (left + right) / 2;
3789 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3795 // If we're too far in the list
3802 // we found the requested pack but it is not registered quake
3810 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3813 unsigned char *filedata;
3814 fs_offset_t filesize;
3815 if (filesizepointer)
3816 *filesizepointer = 0;
3817 if (!filename || !*filename)
3819 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3822 if (filesizepointer)
3823 *filesizepointer = filesize;
3824 crc = CRC_Block(filedata, filesize);
3830 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3833 unsigned char *out = NULL;
3837 #ifndef LINK_TO_ZLIB
3842 memset(&strm, 0, sizeof(strm));
3843 strm.zalloc = Z_NULL;
3844 strm.zfree = Z_NULL;
3845 strm.opaque = Z_NULL;
3848 level = Z_DEFAULT_COMPRESSION;
3850 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3852 Con_Printf("FS_Deflate: deflate init error!\n");
3856 strm.next_in = (unsigned char*)data;
3857 strm.avail_in = (unsigned int)size;
3859 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3862 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
3863 qz_deflateEnd(&strm);
3867 strm.next_out = tmp;
3868 strm.avail_out = (unsigned int)size;
3870 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
3872 Con_Printf("FS_Deflate: deflate failed!\n");
3873 qz_deflateEnd(&strm);
3878 if(qz_deflateEnd(&strm) != Z_OK)
3880 Con_Printf("FS_Deflate: deflateEnd failed\n");
3885 if(strm.total_out >= size)
3887 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
3892 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
3895 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
3901 *deflated_size = (size_t)strm.total_out;
3903 memcpy(out, tmp, strm.total_out);
3909 static void AssertBufsize(sizebuf_t *buf, int length)
3911 if(buf->cursize + length > buf->maxsize)
3913 int oldsize = buf->maxsize;
3914 unsigned char *olddata;
3915 olddata = buf->data;
3916 buf->maxsize += length;
3917 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
3920 memcpy(buf->data, olddata, oldsize);
3926 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
3930 unsigned char *out = NULL;
3931 unsigned char tmp[2048];
3936 #ifndef LINK_TO_ZLIB
3941 memset(&outbuf, 0, sizeof(outbuf));
3942 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
3943 outbuf.maxsize = sizeof(tmp);
3945 memset(&strm, 0, sizeof(strm));
3946 strm.zalloc = Z_NULL;
3947 strm.zfree = Z_NULL;
3948 strm.opaque = Z_NULL;
3950 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
3952 Con_Printf("FS_Inflate: inflate init error!\n");
3953 Mem_Free(outbuf.data);
3957 strm.next_in = (unsigned char*)data;
3958 strm.avail_in = (unsigned int)size;
3962 strm.next_out = tmp;
3963 strm.avail_out = sizeof(tmp);
3964 ret = qz_inflate(&strm, Z_NO_FLUSH);
3965 // it either returns Z_OK on progress, Z_STREAM_END on end
3973 case Z_STREAM_ERROR:
3974 Con_Print("FS_Inflate: stream error!\n");
3977 Con_Print("FS_Inflate: data error!\n");
3980 Con_Print("FS_Inflate: mem error!\n");
3983 Con_Print("FS_Inflate: buf error!\n");
3986 Con_Print("FS_Inflate: unknown error!\n");
3990 if(ret != Z_OK && ret != Z_STREAM_END)
3992 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
3993 Mem_Free(outbuf.data);
3994 qz_inflateEnd(&strm);
3997 have = sizeof(tmp) - strm.avail_out;
3998 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
3999 SZ_Write(&outbuf, tmp, have);
4000 } while(ret != Z_STREAM_END);
4002 qz_inflateEnd(&strm);
4004 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
4007 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
4008 Mem_Free(outbuf.data);
4012 memcpy(out, outbuf.data, outbuf.cursize);
4013 Mem_Free(outbuf.data);
4016 *inflated_size = (size_t)outbuf.cursize;