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
36 # include <sys/stat.h>
43 // Win32 requires us to add O_BINARY, but the other OSes don't have it
48 // In case the system doesn't support the O_NONBLOCK flag
53 // largefile support for Win32
55 # define lseek _lseeki64
59 // suppress deprecated warnings
60 # include <sys/stat.h>
65 # define unlink _unlink
69 /** \page fs File System
71 All of Quake's data access is through a hierchal file system, but the contents
72 of the file system can be transparently merged from several sources.
74 The "base directory" is the path to the directory holding the quake.exe and
75 all game directories. The sys_* files pass this to host_init in
76 quakeparms_t->basedir. This can be overridden with the "-basedir" command
77 line parm to allow code debugging in a different directory. The base
78 directory is only used during filesystem initialization.
80 The "game directory" is the first tree on the search path and directory that
81 all generated files (savegames, screenshots, demos, config files) will be
82 saved to. This can be overridden with the "-game" command line parameter.
83 The game directory can never be changed while quake is executing. This is a
84 precaution against having a malicious server instruct clients to write files
85 over areas they shouldn't.
91 =============================================================================
95 =============================================================================
98 // Magic numbers of a ZIP file (big-endian format)
99 #define ZIP_DATA_HEADER 0x504B0304 // "PK\3\4"
100 #define ZIP_CDIR_HEADER 0x504B0102 // "PK\1\2"
101 #define ZIP_END_HEADER 0x504B0506 // "PK\5\6"
103 // Other constants for ZIP files
104 #define ZIP_MAX_COMMENTS_SIZE ((unsigned short)0xFFFF)
105 #define ZIP_END_CDIR_SIZE 22
106 #define ZIP_CDIR_CHUNK_BASE_SIZE 46
107 #define ZIP_LOCAL_CHUNK_BASE_SIZE 30
109 // Zlib constants (from zlib.h)
110 #define Z_SYNC_FLUSH 2
113 #define Z_STREAM_END 1
114 #define Z_STREAM_ERROR (-2)
115 #define Z_DATA_ERROR (-3)
116 #define Z_MEM_ERROR (-4)
117 #define Z_BUF_ERROR (-5)
118 #define ZLIB_VERSION "1.2.3"
122 #define Z_MEMLEVEL_DEFAULT 8
125 #define Z_DEFAULT_COMPRESSION (-1)
127 #define Z_SYNC_FLUSH 2
128 #define Z_FULL_FLUSH 3
131 // Uncomment the following line if the zlib DLL you have still uses
132 // the 1.1.x series calling convention on Win32 (WINAPI)
133 //#define ZLIB_USES_WINAPI
137 =============================================================================
141 =============================================================================
144 /*! Zlib stream (from zlib.h)
145 * \warning: some pointers we don't use directly have
146 * been cast to "void*" for a matter of simplicity
150 unsigned char *next_in; ///< next input byte
151 unsigned int avail_in; ///< number of bytes available at next_in
152 unsigned long total_in; ///< total nb of input bytes read so far
154 unsigned char *next_out; ///< next output byte should be put there
155 unsigned int avail_out; ///< remaining free space at next_out
156 unsigned long total_out; ///< total nb of bytes output so far
158 char *msg; ///< last error message, NULL if no error
159 void *state; ///< not visible by applications
161 void *zalloc; ///< used to allocate the internal state
162 void *zfree; ///< used to free the internal state
163 void *opaque; ///< private data object passed to zalloc and zfree
165 int data_type; ///< best guess about the data type: ascii or binary
166 unsigned long adler; ///< adler32 value of the uncompressed data
167 unsigned long reserved; ///< reserved for future use
171 /// inside a package (PAK or PK3)
172 #define QFILE_FLAG_PACKED (1 << 0)
173 /// file is compressed using the deflate algorithm (PK3 only)
174 #define QFILE_FLAG_DEFLATED (1 << 1)
175 /// file is actually already loaded data
176 #define QFILE_FLAG_DATA (1 << 2)
178 #define FILE_BUFF_SIZE 2048
182 size_t comp_length; ///< length of the compressed file
183 size_t in_ind, in_len; ///< input buffer current index and length
184 size_t in_position; ///< position in the compressed file
185 unsigned char input [FILE_BUFF_SIZE];
191 int handle; ///< file descriptor
192 fs_offset_t real_length; ///< uncompressed file size (for files opened in "read" mode)
193 fs_offset_t position; ///< current position in the file
194 fs_offset_t offset; ///< offset into the package (0 if external file)
195 int ungetc; ///< single stored character from ungetc, cleared to EOF when read
198 fs_offset_t buff_ind, buff_len; ///< buffer current index and length
199 unsigned char buff [FILE_BUFF_SIZE];
201 ztoolkit_t* ztk; ///< For zipped files.
203 const unsigned char *data; ///< For data files.
207 // ------ PK3 files on disk ------ //
209 // You can get the complete ZIP format description from PKWARE website
211 typedef struct pk3_endOfCentralDir_s
213 unsigned int signature;
214 unsigned short disknum;
215 unsigned short cdir_disknum; ///< number of the disk with the start of the central directory
216 unsigned short localentries; ///< number of entries in the central directory on this disk
217 unsigned short nbentries; ///< total number of entries in the central directory on this disk
218 unsigned int cdir_size; ///< size of the central directory
219 unsigned int cdir_offset; ///< with respect to the starting disk number
220 unsigned short comment_size;
221 } pk3_endOfCentralDir_t;
224 // ------ PAK files on disk ------ //
225 typedef struct dpackfile_s
228 int filepos, filelen;
231 typedef struct dpackheader_s
239 /*! \name Packages in memory
242 /// the offset in packfile_t is the true contents offset
243 #define PACKFILE_FLAG_TRUEOFFS (1 << 0)
244 /// file compressed using the deflate algorithm
245 #define PACKFILE_FLAG_DEFLATED (1 << 1)
246 /// file is a symbolic link
247 #define PACKFILE_FLAG_SYMLINK (1 << 2)
249 typedef struct packfile_s
251 char name [MAX_QPATH];
254 fs_offset_t packsize; ///< size in the package
255 fs_offset_t realsize; ///< real file size (uncompressed)
258 typedef struct pack_s
260 char filename [MAX_OSPATH];
261 char shortname [MAX_QPATH];
263 int ignorecase; ///< PK3 ignores case
269 /// Search paths for files (including packages)
270 typedef struct searchpath_s
272 // only one of filename / pack will be used
273 char filename[MAX_OSPATH];
275 struct searchpath_s *next;
280 =============================================================================
284 =============================================================================
289 void FS_Which_f(void);
291 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet);
292 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
293 fs_offset_t offset, fs_offset_t packsize,
294 fs_offset_t realsize, int flags);
298 =============================================================================
302 =============================================================================
305 mempool_t *fs_mempool;
307 searchpath_t *fs_searchpaths = NULL;
308 const char *const fs_checkgamedir_missing = "missing";
310 #define MAX_FILES_IN_PACK 65536
312 char fs_userdir[MAX_OSPATH];
313 char fs_gamedir[MAX_OSPATH];
314 char fs_basedir[MAX_OSPATH];
316 // list of active game directories (empty if not running a mod)
317 int fs_numgamedirs = 0;
318 char fs_gamedirs[MAX_GAMEDIRS][MAX_QPATH];
320 // list of all gamedirs with modinfo.txt
321 gamedir_t *fs_all_gamedirs = NULL;
322 int fs_all_gamedirs_count = 0;
324 cvar_t scr_screenshot_name = {0, "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)"};
325 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"};
329 =============================================================================
331 PRIVATE FUNCTIONS - PK3 HANDLING
333 =============================================================================
336 // Functions exported from zlib
337 #if defined(WIN32) && defined(ZLIB_USES_WINAPI)
338 # define ZEXPORT WINAPI
343 static int (ZEXPORT *qz_inflate) (z_stream* strm, int flush);
344 static int (ZEXPORT *qz_inflateEnd) (z_stream* strm);
345 static int (ZEXPORT *qz_inflateInit2_) (z_stream* strm, int windowBits, const char *version, int stream_size);
346 static int (ZEXPORT *qz_inflateReset) (z_stream* strm);
347 static int (ZEXPORT *qz_deflateInit2_) (z_stream* strm, int level, int method, int windowBits, int memLevel, int strategy, const char *version, int stream_size);
348 static int (ZEXPORT *qz_deflateEnd) (z_stream* strm);
349 static int (ZEXPORT *qz_deflate) (z_stream* strm, int flush);
351 #define qz_inflateInit2(strm, windowBits) \
352 qz_inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream))
353 #define qz_deflateInit2(strm, level, method, windowBits, memLevel, strategy) \
354 qz_deflateInit2_((strm), (level), (method), (windowBits), (memLevel), (strategy), ZLIB_VERSION, sizeof(z_stream))
356 // qz_deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream))
358 static dllfunction_t zlibfuncs[] =
360 {"inflate", (void **) &qz_inflate},
361 {"inflateEnd", (void **) &qz_inflateEnd},
362 {"inflateInit2_", (void **) &qz_inflateInit2_},
363 {"inflateReset", (void **) &qz_inflateReset},
364 {"deflateInit2_", (void **) &qz_deflateInit2_},
365 {"deflateEnd", (void **) &qz_deflateEnd},
366 {"deflate", (void **) &qz_deflate},
370 /// Handle for Zlib DLL
371 static dllhandle_t zlib_dll = NULL;
374 static HRESULT (WINAPI *qSHGetFolderPath) (HWND hwndOwner, int nFolder, HANDLE hToken, DWORD dwFlags, LPTSTR pszPath);
375 static dllfunction_t shfolderfuncs[] =
377 {"SHGetFolderPathA", (void **) &qSHGetFolderPath},
380 static dllhandle_t shfolder_dll = NULL;
390 void PK3_CloseLibrary (void)
392 Sys_UnloadLibrary (&zlib_dll);
400 Try to load the Zlib DLL
403 qboolean PK3_OpenLibrary (void)
405 const char* dllnames [] =
410 # ifdef ZLIB_USES_WINAPI
416 #elif defined(MACOSX)
430 return Sys_LoadLibrary (dllnames, &zlib_dll, zlibfuncs);
437 See if zlib is available
440 qboolean FS_HasZlib(void)
442 PK3_OpenLibrary(); // to be safe
443 return (zlib_dll != 0);
448 PK3_GetEndOfCentralDir
450 Extract the end of the central directory from a PK3 package
453 qboolean PK3_GetEndOfCentralDir (const char *packfile, int packhandle, pk3_endOfCentralDir_t *eocd)
455 fs_offset_t filesize, maxsize;
456 unsigned char *buffer, *ptr;
459 // Get the package size
460 filesize = lseek (packhandle, 0, SEEK_END);
461 if (filesize < ZIP_END_CDIR_SIZE)
464 // Load the end of the file in memory
465 if (filesize < ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE)
468 maxsize = ZIP_MAX_COMMENTS_SIZE + ZIP_END_CDIR_SIZE;
469 buffer = (unsigned char *)Mem_Alloc (tempmempool, maxsize);
470 lseek (packhandle, filesize - maxsize, SEEK_SET);
471 if (read (packhandle, buffer, maxsize) != (fs_offset_t) maxsize)
477 // Look for the end of central dir signature around the end of the file
478 maxsize -= ZIP_END_CDIR_SIZE;
479 ptr = &buffer[maxsize];
481 while (BuffBigLong (ptr) != ZIP_END_HEADER)
493 memcpy (eocd, ptr, ZIP_END_CDIR_SIZE);
494 eocd->signature = LittleLong (eocd->signature);
495 eocd->disknum = LittleShort (eocd->disknum);
496 eocd->cdir_disknum = LittleShort (eocd->cdir_disknum);
497 eocd->localentries = LittleShort (eocd->localentries);
498 eocd->nbentries = LittleShort (eocd->nbentries);
499 eocd->cdir_size = LittleLong (eocd->cdir_size);
500 eocd->cdir_offset = LittleLong (eocd->cdir_offset);
501 eocd->comment_size = LittleShort (eocd->comment_size);
513 Extract the file list from a PK3 file
516 int PK3_BuildFileList (pack_t *pack, const pk3_endOfCentralDir_t *eocd)
518 unsigned char *central_dir, *ptr;
520 fs_offset_t remaining;
522 // Load the central directory in memory
523 central_dir = (unsigned char *)Mem_Alloc (tempmempool, eocd->cdir_size);
524 lseek (pack->handle, eocd->cdir_offset, SEEK_SET);
525 if(read (pack->handle, central_dir, eocd->cdir_size) != (ssize_t) eocd->cdir_size)
527 Mem_Free (central_dir);
531 // Extract the files properties
532 // The parsing is done "by hand" because some fields have variable sizes and
533 // the constant part isn't 4-bytes aligned, which makes the use of structs difficult
534 remaining = eocd->cdir_size;
537 for (ind = 0; ind < eocd->nbentries; ind++)
539 fs_offset_t namesize, count;
541 // Checking the remaining size
542 if (remaining < ZIP_CDIR_CHUNK_BASE_SIZE)
544 Mem_Free (central_dir);
547 remaining -= ZIP_CDIR_CHUNK_BASE_SIZE;
550 if (BuffBigLong (ptr) != ZIP_CDIR_HEADER)
552 Mem_Free (central_dir);
556 namesize = BuffLittleShort (&ptr[28]); // filename length
558 // Check encryption, compression, and attributes
559 // 1st uint8 : general purpose bit flag
560 // Check bits 0 (encryption), 3 (data descriptor after the file), and 5 (compressed patched data (?))
562 // LordHavoc: bit 3 would be a problem if we were scanning the archive
563 // but is not a problem in the central directory where the values are
566 // bit 3 seems to always be set by the standard Mac OSX zip maker
568 // 2nd uint8 : external file attributes
569 // Check bits 3 (file is a directory) and 5 (file is a volume (?))
570 if ((ptr[8] & 0x21) == 0 && (ptr[38] & 0x18) == 0)
572 // Still enough bytes for the name?
573 if (remaining < namesize || namesize >= (int)sizeof (*pack->files))
575 Mem_Free (central_dir);
579 // WinZip doesn't use the "directory" attribute, so we need to check the name directly
580 if (ptr[ZIP_CDIR_CHUNK_BASE_SIZE + namesize - 1] != '/')
582 char filename [sizeof (pack->files[0].name)];
583 fs_offset_t offset, packsize, realsize;
586 // Extract the name (strip it if necessary)
587 namesize = min(namesize, (int)sizeof (filename) - 1);
588 memcpy (filename, &ptr[ZIP_CDIR_CHUNK_BASE_SIZE], namesize);
589 filename[namesize] = '\0';
591 if (BuffLittleShort (&ptr[10]))
592 flags = PACKFILE_FLAG_DEFLATED;
595 offset = BuffLittleLong (&ptr[42]);
596 packsize = BuffLittleLong (&ptr[20]);
597 realsize = BuffLittleLong (&ptr[24]);
599 switch(ptr[5]) // C_VERSION_MADE_BY_1
604 if((BuffLittleShort(&ptr[40]) & 0120000) == 0120000)
605 // can't use S_ISLNK here, as this has to compile on non-UNIX too
606 flags |= PACKFILE_FLAG_SYMLINK;
610 FS_AddFileToPack (filename, pack, offset, packsize, realsize, flags);
614 // Skip the name, additionnal field, and comment
615 // 1er uint16 : extra field length
616 // 2eme uint16 : file comment length
617 count = namesize + BuffLittleShort (&ptr[30]) + BuffLittleShort (&ptr[32]);
618 ptr += ZIP_CDIR_CHUNK_BASE_SIZE + count;
622 // If the package is empty, central_dir is NULL here
623 if (central_dir != NULL)
624 Mem_Free (central_dir);
625 return pack->numfiles;
633 Create a package entry associated with a PK3 file
636 pack_t *FS_LoadPackPK3 (const char *packfile)
639 pk3_endOfCentralDir_t eocd;
644 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
646 packhandle = open (packfile, O_RDONLY | O_BINARY);
651 if (! PK3_GetEndOfCentralDir (packfile, packhandle, &eocd))
653 Con_Printf ("%s is not a PK3 file\n", packfile);
658 // Multi-volume ZIP archives are NOT allowed
659 if (eocd.disknum != 0 || eocd.cdir_disknum != 0)
661 Con_Printf ("%s is a multi-volume ZIP archive\n", packfile);
666 // We only need to do this test if MAX_FILES_IN_PACK is lesser than 65535
667 // since eocd.nbentries is an unsigned 16 bits integer
668 #if MAX_FILES_IN_PACK < 65535
669 if (eocd.nbentries > MAX_FILES_IN_PACK)
671 Con_Printf ("%s contains too many files (%hu)\n", packfile, eocd.nbentries);
677 // Create a package structure in memory
678 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
679 pack->ignorecase = true; // PK3 ignores case
680 strlcpy (pack->filename, packfile, sizeof (pack->filename));
681 pack->handle = packhandle;
682 pack->numfiles = eocd.nbentries;
683 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, eocd.nbentries * sizeof(packfile_t));
685 real_nb_files = PK3_BuildFileList (pack, &eocd);
686 if (real_nb_files < 0)
688 Con_Printf ("%s is not a valid PK3 file\n", packfile);
694 Con_Printf("Added packfile %s (%i files)\n", packfile, real_nb_files);
701 PK3_GetTrueFileOffset
703 Find where the true file data offset is
706 qboolean PK3_GetTrueFileOffset (packfile_t *pfile, pack_t *pack)
708 unsigned char buffer [ZIP_LOCAL_CHUNK_BASE_SIZE];
712 if (pfile->flags & PACKFILE_FLAG_TRUEOFFS)
715 // Load the local file description
716 lseek (pack->handle, pfile->offset, SEEK_SET);
717 count = read (pack->handle, buffer, ZIP_LOCAL_CHUNK_BASE_SIZE);
718 if (count != ZIP_LOCAL_CHUNK_BASE_SIZE || BuffBigLong (buffer) != ZIP_DATA_HEADER)
720 Con_Printf ("Can't retrieve file %s in package %s\n", pfile->name, pack->filename);
724 // Skip name and extra field
725 pfile->offset += BuffLittleShort (&buffer[26]) + BuffLittleShort (&buffer[28]) + ZIP_LOCAL_CHUNK_BASE_SIZE;
727 pfile->flags |= PACKFILE_FLAG_TRUEOFFS;
733 =============================================================================
735 OTHER PRIVATE FUNCTIONS
737 =============================================================================
745 Add a file to the list of files contained into a package
748 static packfile_t* FS_AddFileToPack (const char* name, pack_t* pack,
749 fs_offset_t offset, fs_offset_t packsize,
750 fs_offset_t realsize, int flags)
752 int (*strcmp_funct) (const char* str1, const char* str2);
753 int left, right, middle;
756 strcmp_funct = pack->ignorecase ? strcasecmp : strcmp;
758 // Look for the slot we should put that file into (binary search)
760 right = pack->numfiles - 1;
761 while (left <= right)
765 middle = (left + right) / 2;
766 diff = strcmp_funct (pack->files[middle].name, name);
768 // If we found the file, there's a problem
770 Con_Printf ("Package %s contains the file %s several times\n", pack->filename, name);
772 // If we're too far in the list
779 // We have to move the right of the list by one slot to free the one we need
780 pfile = &pack->files[left];
781 memmove (pfile + 1, pfile, (pack->numfiles - left) * sizeof (*pfile));
784 strlcpy (pfile->name, name, sizeof (pfile->name));
785 pfile->offset = offset;
786 pfile->packsize = packsize;
787 pfile->realsize = realsize;
788 pfile->flags = flags;
798 Only used for FS_OpenRealFile.
801 void FS_CreatePath (char *path)
805 for (ofs = path+1 ; *ofs ; ofs++)
807 if (*ofs == '/' || *ofs == '\\')
809 // create the directory
825 void FS_Path_f (void)
829 Con_Print("Current search path:\n");
830 for (s=fs_searchpaths ; s ; s=s->next)
833 Con_Printf("%s (%i files)\n", s->pack->filename, s->pack->numfiles);
835 Con_Printf("%s\n", s->filename);
845 /*! Takes an explicit (not game tree related) path to a pak file.
846 *Loads the header and directory, adding the files at the beginning
847 *of the list so they override previous pack files.
849 pack_t *FS_LoadPackPAK (const char *packfile)
851 dpackheader_t header;
858 _sopen_s(&packhandle, packfile, O_RDONLY | O_BINARY, _SH_DENYNO, _S_IREAD | _S_IWRITE);
860 packhandle = open (packfile, O_RDONLY | O_BINARY);
864 if(read (packhandle, (void *)&header, sizeof(header)) != sizeof(header))
866 Con_Printf ("%s is not a packfile\n", packfile);
870 if (memcmp(header.id, "PACK", 4))
872 Con_Printf ("%s is not a packfile\n", packfile);
876 header.dirofs = LittleLong (header.dirofs);
877 header.dirlen = LittleLong (header.dirlen);
879 if (header.dirlen % sizeof(dpackfile_t))
881 Con_Printf ("%s has an invalid directory size\n", packfile);
886 numpackfiles = header.dirlen / sizeof(dpackfile_t);
888 if (numpackfiles > MAX_FILES_IN_PACK)
890 Con_Printf ("%s has %i files\n", packfile, numpackfiles);
895 info = (dpackfile_t *)Mem_Alloc(tempmempool, sizeof(*info) * numpackfiles);
896 lseek (packhandle, header.dirofs, SEEK_SET);
897 if(header.dirlen != read (packhandle, (void *)info, header.dirlen))
899 Con_Printf("%s is an incomplete PAK, not loading\n", packfile);
905 pack = (pack_t *)Mem_Alloc(fs_mempool, sizeof (pack_t));
906 pack->ignorecase = false; // PAK is case sensitive
907 strlcpy (pack->filename, packfile, sizeof (pack->filename));
908 pack->handle = packhandle;
910 pack->files = (packfile_t *)Mem_Alloc(fs_mempool, numpackfiles * sizeof(packfile_t));
912 // parse the directory
913 for (i = 0;i < numpackfiles;i++)
915 fs_offset_t offset = LittleLong (info[i].filepos);
916 fs_offset_t size = LittleLong (info[i].filelen);
918 FS_AddFileToPack (info[i].name, pack, offset, size, size, PACKFILE_FLAG_TRUEOFFS);
923 Con_Printf("Added packfile %s (%i files)\n", packfile, numpackfiles);
932 /*! Adds the given pack to the search path.
933 * The pack type is autodetected by the file extension.
935 * Returns true if the file was successfully added to the
936 * search path or if it was already included.
938 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
942 static qboolean FS_AddPack_Fullpath(const char *pakfile, const char *shortname, qboolean *already_loaded, qboolean keep_plain_dirs)
944 searchpath_t *search;
946 const char *ext = FS_FileExtension(pakfile);
948 for(search = fs_searchpaths; search; search = search->next)
950 if(search->pack && !strcasecmp(search->pack->filename, pakfile))
953 *already_loaded = true;
954 return true; // already loaded
959 *already_loaded = false;
961 if(!strcasecmp(ext, "pak"))
962 pak = FS_LoadPackPAK (pakfile);
963 else if(!strcasecmp(ext, "pk3"))
964 pak = FS_LoadPackPK3 (pakfile);
966 Con_Printf("\"%s\" does not have a pack extension\n", pakfile);
970 strlcpy(pak->shortname, shortname, sizeof(pak->shortname));
971 //Con_DPrintf(" Registered pack with short name %s\n", shortname);
974 // find the first item whose next one is a pack or NULL
975 searchpath_t *insertion_point = 0;
976 if(fs_searchpaths && !fs_searchpaths->pack)
978 insertion_point = fs_searchpaths;
981 if(!insertion_point->next)
983 if(insertion_point->next->pack)
985 insertion_point = insertion_point->next;
988 // If insertion_point is NULL, this means that either there is no
989 // item in the list yet, or that the very first item is a pack. In
990 // that case, we want to insert at the beginning...
993 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
995 search->next = fs_searchpaths;
996 fs_searchpaths = search;
999 // otherwise we want to append directly after insertion_point.
1001 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1003 search->next = insertion_point->next;
1004 insertion_point->next = search;
1009 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1011 search->next = fs_searchpaths;
1012 fs_searchpaths = search;
1018 Con_Printf("unable to load pak \"%s\"\n", pakfile);
1029 /*! Adds the given pack to the search path and searches for it in the game path.
1030 * The pack type is autodetected by the file extension.
1032 * Returns true if the file was successfully added to the
1033 * search path or if it was already included.
1035 * If keep_plain_dirs is set, the pack will be added AFTER the first sequence of
1036 * plain directories.
1038 qboolean FS_AddPack(const char *pakfile, qboolean *already_loaded, qboolean keep_plain_dirs)
1040 char fullpath[MAX_QPATH];
1042 searchpath_t *search;
1045 *already_loaded = false;
1047 // then find the real name...
1048 search = FS_FindFile(pakfile, &index, true);
1049 if(!search || search->pack)
1051 Con_Printf("could not find pak \"%s\"\n", pakfile);
1055 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, pakfile);
1057 return FS_AddPack_Fullpath(fullpath, pakfile, already_loaded, keep_plain_dirs);
1065 Sets fs_gamedir, adds the directory to the head of the path,
1066 then loads and adds pak1.pak pak2.pak ...
1069 void FS_AddGameDirectory (const char *dir)
1073 searchpath_t *search;
1075 strlcpy (fs_gamedir, dir, sizeof (fs_gamedir));
1077 stringlistinit(&list);
1078 listdirectory(&list, "", dir);
1079 stringlistsort(&list);
1081 // add any PAK package in the directory
1082 for (i = 0;i < list.numstrings;i++)
1084 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pak"))
1086 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1090 // add any PK3 package in the directory
1091 for (i = 0;i < list.numstrings;i++)
1093 if (!strcasecmp(FS_FileExtension(list.strings[i]), "pk3"))
1095 FS_AddPack_Fullpath(list.strings[i], list.strings[i] + strlen(dir), NULL, false);
1099 stringlistfreecontents(&list);
1101 // Add the directory to the search path
1102 // (unpacked files have the priority over packed files)
1103 search = (searchpath_t *)Mem_Alloc(fs_mempool, sizeof(searchpath_t));
1104 strlcpy (search->filename, dir, sizeof (search->filename));
1105 search->next = fs_searchpaths;
1106 fs_searchpaths = search;
1115 void FS_AddGameHierarchy (const char *dir)
1117 // Add the common game directory
1118 FS_AddGameDirectory (va("%s%s/", fs_basedir, dir));
1121 FS_AddGameDirectory(va("%s%s/", fs_userdir, dir));
1130 const char *FS_FileExtension (const char *in)
1132 const char *separator, *backslash, *colon, *dot;
1134 separator = strrchr(in, '/');
1135 backslash = strrchr(in, '\\');
1136 if (!separator || separator < backslash)
1137 separator = backslash;
1138 colon = strrchr(in, ':');
1139 if (!separator || separator < colon)
1142 dot = strrchr(in, '.');
1143 if (dot == NULL || (separator && (dot < separator)))
1155 const char *FS_FileWithoutPath (const char *in)
1157 const char *separator, *backslash, *colon;
1159 separator = strrchr(in, '/');
1160 backslash = strrchr(in, '\\');
1161 if (!separator || separator < backslash)
1162 separator = backslash;
1163 colon = strrchr(in, ':');
1164 if (!separator || separator < colon)
1166 return separator ? separator + 1 : in;
1175 void FS_ClearSearchPath (void)
1177 // unload all packs and directory information, close all pack files
1178 // (if a qfile is still reading a pack it won't be harmed because it used
1179 // dup() to get its own handle already)
1180 while (fs_searchpaths)
1182 searchpath_t *search = fs_searchpaths;
1183 fs_searchpaths = search->next;
1187 close(search->pack->handle);
1188 // free any memory associated with it
1189 if (search->pack->files)
1190 Mem_Free(search->pack->files);
1191 Mem_Free(search->pack);
1203 void FS_Rescan (void)
1206 qboolean fs_modified = false;
1208 FS_ClearSearchPath();
1210 // add the game-specific paths
1211 // gamedirname1 (typically id1)
1212 FS_AddGameHierarchy (gamedirname1);
1213 // update the com_modname (used for server info)
1214 strlcpy(com_modname, gamedirname1, sizeof(com_modname));
1216 // add the game-specific path, if any
1217 // (only used for mission packs and the like, which should set fs_modified)
1221 FS_AddGameHierarchy (gamedirname2);
1225 // Adds basedir/gamedir as an override game
1226 // LordHavoc: now supports multiple -game directories
1227 // set the com_modname (reported in server info)
1228 for (i = 0;i < fs_numgamedirs;i++)
1231 FS_AddGameHierarchy (fs_gamedirs[i]);
1232 // update the com_modname (used server info)
1233 strlcpy (com_modname, fs_gamedirs[i], sizeof (com_modname));
1236 // set the default screenshot name to either the mod name or the
1237 // gamemode screenshot name
1238 if (strcmp(com_modname, gamedirname1))
1239 Cvar_SetQuick (&scr_screenshot_name, com_modname);
1241 Cvar_SetQuick (&scr_screenshot_name, gamescreenshotname);
1243 // If "-condebug" is in the command line, remove the previous log file
1244 if (COM_CheckParm ("-condebug") != 0)
1245 unlink (va("%s/qconsole.log", fs_gamedir));
1247 // look for the pop.lmp file and set registered to true if it is found
1248 if ((gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE) && !FS_FileExists("gfx/pop.lmp"))
1251 Con_Print("Playing shareware version, with modification.\nwarning: most mods require full quake data.\n");
1253 Con_Print("Playing shareware version.\n");
1257 Cvar_Set ("registered", "1");
1258 if (gamemode == GAME_NORMAL || gamemode == GAME_HIPNOTIC || gamemode == GAME_ROGUE)
1259 Con_Print("Playing registered version.\n");
1262 // unload all wads so that future queries will return the new data
1266 void FS_Rescan_f(void)
1276 extern void Host_SaveConfig (void);
1277 extern void Host_LoadConfig_f (void);
1278 qboolean FS_ChangeGameDirs(int numgamedirs, char gamedirs[][MAX_QPATH], qboolean complain, qboolean failmissing)
1283 if (fs_numgamedirs == numgamedirs)
1285 for (i = 0;i < numgamedirs;i++)
1286 if (strcasecmp(fs_gamedirs[i], gamedirs[i]))
1288 if (i == numgamedirs)
1289 return true; // already using this set of gamedirs, do nothing
1292 if (numgamedirs > MAX_GAMEDIRS)
1295 Con_Printf("That is too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1296 return false; // too many gamedirs
1299 for (i = 0;i < numgamedirs;i++)
1301 // if string is nasty, reject it
1302 p = FS_CheckGameDir(gamedirs[i]);
1306 Con_Printf("Nasty gamedir name rejected: %s\n", gamedirs[i]);
1307 return false; // nasty gamedirs
1309 if(p == fs_checkgamedir_missing && failmissing)
1312 Con_Printf("Gamedir missing: %s%s/\n", fs_basedir, gamedirs[i]);
1313 return false; // missing gamedirs
1319 fs_numgamedirs = numgamedirs;
1320 for (i = 0;i < fs_numgamedirs;i++)
1321 strlcpy(fs_gamedirs[i], gamedirs[i], sizeof(fs_gamedirs[i]));
1323 // reinitialize filesystem to detect the new paks
1326 // exec the new config
1327 Host_LoadConfig_f();
1329 // unload all sounds so they will be reloaded from the new files as needed
1330 S_UnloadAllSounds_f();
1332 // reinitialize renderer (this reloads hud/console background/etc)
1333 R_Modules_Restart();
1343 void FS_GameDir_f (void)
1347 char gamedirs[MAX_GAMEDIRS][MAX_QPATH];
1351 Con_Printf("gamedirs active:");
1352 for (i = 0;i < fs_numgamedirs;i++)
1353 Con_Printf(" %s", fs_gamedirs[i]);
1358 numgamedirs = Cmd_Argc() - 1;
1359 if (numgamedirs > MAX_GAMEDIRS)
1361 Con_Printf("Too many gamedirs (%i > %i)\n", numgamedirs, MAX_GAMEDIRS);
1365 for (i = 0;i < numgamedirs;i++)
1366 strlcpy(gamedirs[i], Cmd_Argv(i+1), sizeof(gamedirs[i]));
1368 if ((cls.state == ca_connected && !cls.demoplayback) || sv.active)
1370 // actually, changing during game would work fine, but would be stupid
1371 Con_Printf("Can not change gamedir while client is connected or server is running!\n");
1375 // halt demo playback to close the file
1378 FS_ChangeGameDirs(numgamedirs, gamedirs, true, true);
1381 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking);
1382 static const char *FS_SysCheckGameDir(const char *gamedir)
1384 static char buf[8192];
1390 stringlistinit(&list);
1391 listdirectory(&list, gamedir, "");
1392 success = list.numstrings > 0;
1393 stringlistfreecontents(&list);
1397 f = FS_SysOpen(va("%smodinfo.txt", gamedir), "r", false);
1400 n = FS_Read (f, buf, sizeof(buf) - 1);
1420 const char *FS_CheckGameDir(const char *gamedir)
1424 if (FS_CheckNastyPath(gamedir, true))
1427 ret = FS_SysCheckGameDir(va("%s%s/", fs_userdir, gamedir));
1432 // get description from basedir
1433 ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1441 ret = FS_SysCheckGameDir(va("%s%s/", fs_basedir, gamedir));
1445 return fs_checkgamedir_missing;
1448 static void FS_ListGameDirs()
1450 stringlist_t list, list2;
1454 fs_all_gamedirs_count = 0;
1456 Mem_Free(fs_all_gamedirs);
1458 stringlistinit(&list);
1459 listdirectory(&list, va("%s/", fs_basedir), "");
1460 listdirectory(&list, va("%s/", fs_userdir), "");
1461 stringlistsort(&list);
1463 stringlistinit(&list2);
1464 for(i = 0; i < list.numstrings; ++i)
1467 if(!strcmp(list.strings[i-1], list.strings[i]))
1469 info = FS_CheckGameDir(list.strings[i]);
1472 if(info == fs_checkgamedir_missing)
1476 stringlistappend(&list2, list.strings[i]);
1478 stringlistfreecontents(&list);
1480 fs_all_gamedirs = Mem_Alloc(fs_mempool, list2.numstrings * sizeof(*fs_all_gamedirs));
1481 for(i = 0; i < list2.numstrings; ++i)
1483 info = FS_CheckGameDir(list2.strings[i]);
1484 // all this cannot happen any more, but better be safe than sorry
1487 if(info == fs_checkgamedir_missing)
1491 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].name, list2.strings[i], sizeof(fs_all_gamedirs[j].name));
1492 strlcpy(fs_all_gamedirs[fs_all_gamedirs_count].description, info, sizeof(fs_all_gamedirs[j].description));
1493 ++fs_all_gamedirs_count;
1507 TCHAR mydocsdir[MAX_PATH + 1];
1508 #if _MSC_VER >= 1400
1515 const char* dllnames [] =
1517 "shfolder.dll", // IE 4, or Win NT and higher
1520 Sys_LoadLibrary(dllnames, &shfolder_dll, shfolderfuncs);
1521 // don't care for the result; if it fails, %USERPROFILE% will be used instead
1524 fs_mempool = Mem_AllocPool("file management", 0, NULL);
1526 // Add the personal game directory
1527 if((i = COM_CheckParm("-userdir")) && i < com_argc - 1)
1529 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/", com_argv[i+1]);
1531 else if(COM_CheckParm("-nohome"))
1538 if(qSHGetFolderPath && (qSHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, mydocsdir) == S_OK))
1540 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Games/%s/", mydocsdir, gameuserdirname);
1541 Con_DPrintf("Obtained personal directory %s from SHGetFolderPath\n", fs_userdir);
1545 // use the environment
1546 #if _MSC_VER >= 1400
1547 _dupenv_s (&homedir, &homedirlen, "USERPROFILE");
1549 homedir = getenv("USERPROFILE");
1554 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/My Documents/My Games/%s/", homedir, gameuserdirname);
1555 #if _MSC_VER >= 1400
1558 Con_DPrintf("Obtained personal directory %s from environment\n", fs_userdir);
1563 Con_DPrintf("Could not obtain home directory; not supporting -mygames\n");
1565 homedir = getenv ("HOME");
1567 dpsnprintf(fs_userdir, sizeof(fs_userdir), "%s/.%s/", homedir, gameuserdirname);
1570 Con_DPrintf("Could not obtain home directory; assuming -nohome\n");
1574 if(!COM_CheckParm("-mygames"))
1576 #if _MSC_VER >= 1400
1578 _sopen_s(&fd, va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, _SH_DENYNO, _S_IREAD | _S_IWRITE); // note: no O_TRUNC here!
1580 int fd = open (va("%s%s/config.cfg", fs_basedir, gamedirname1), O_WRONLY | O_CREAT, 0666); // note: no O_TRUNC here!
1585 *fs_userdir = 0; // we have write access to the game dir, so let's use it
1591 strlcpy(fs_gamedir, "", sizeof(fs_gamedir));
1593 // If the base directory is explicitly defined by the compilation process
1594 #ifdef DP_FS_BASEDIR
1595 strlcpy(fs_basedir, DP_FS_BASEDIR, sizeof(fs_basedir));
1600 // FIXME: is there a better way to find the directory outside the .app?
1601 if (strstr(com_argv[0], ".app/"))
1605 split = strstr(com_argv[0], ".app/");
1606 while (split > com_argv[0] && *split != '/')
1608 strlcpy(fs_basedir, com_argv[0], sizeof(fs_basedir));
1609 fs_basedir[split - com_argv[0]] = 0;
1617 // Overrides the system supplied base directory (under GAMENAME)
1618 // 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)
1619 i = COM_CheckParm ("-basedir");
1620 if (i && i < com_argc-1)
1622 strlcpy (fs_basedir, com_argv[i+1], sizeof (fs_basedir));
1623 i = (int)strlen (fs_basedir);
1624 if (i > 0 && (fs_basedir[i-1] == '\\' || fs_basedir[i-1] == '/'))
1625 fs_basedir[i-1] = 0;
1628 // add a path separator to the end of the basedir if it lacks one
1629 if (fs_basedir[0] && fs_basedir[strlen(fs_basedir) - 1] != '/' && fs_basedir[strlen(fs_basedir) - 1] != '\\')
1630 strlcat(fs_basedir, "/", sizeof(fs_basedir));
1634 p = FS_CheckGameDir(gamedirname1);
1635 if(!p || p == fs_checkgamedir_missing)
1636 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname1);
1640 p = FS_CheckGameDir(gamedirname2);
1641 if(!p || p == fs_checkgamedir_missing)
1642 Con_Printf("WARNING: base gamedir %s%s/ not found!\n", fs_basedir, gamedirname2);
1646 // Adds basedir/gamedir as an override game
1647 // LordHavoc: now supports multiple -game directories
1648 for (i = 1;i < com_argc && fs_numgamedirs < MAX_GAMEDIRS;i++)
1652 if (!strcmp (com_argv[i], "-game") && i < com_argc-1)
1655 p = FS_CheckGameDir(com_argv[i]);
1657 Sys_Error("Nasty -game name rejected: %s", com_argv[i]);
1658 if(p == fs_checkgamedir_missing)
1659 Con_Printf("WARNING: -game %s%s/ not found!\n", fs_basedir, com_argv[i]);
1660 // add the gamedir to the list of active gamedirs
1661 strlcpy (fs_gamedirs[fs_numgamedirs], com_argv[i], sizeof(fs_gamedirs[fs_numgamedirs]));
1666 // generate the searchpath
1670 void FS_Init_Commands(void)
1672 Cvar_RegisterVariable (&scr_screenshot_name);
1673 Cvar_RegisterVariable (&fs_empty_files_in_pack_mark_deletions);
1675 Cmd_AddCommand ("gamedir", FS_GameDir_f, "changes active gamedir list (can take multiple arguments), not including base directory (example usage: gamedir ctf)");
1676 Cmd_AddCommand ("fs_rescan", FS_Rescan_f, "rescans filesystem for new pack archives and any other changes");
1677 Cmd_AddCommand ("path", FS_Path_f, "print searchpath (game directories and archives)");
1678 Cmd_AddCommand ("dir", FS_Dir_f, "list files in searchpath matching an * filename pattern, one per line");
1679 Cmd_AddCommand ("ls", FS_Ls_f, "list files in searchpath matching an * filename pattern, multiple per line");
1680 Cmd_AddCommand ("which", FS_Which_f, "accepts a file name as argument and reports where the file is taken from");
1688 void FS_Shutdown (void)
1690 // close all pack files and such
1691 // (hopefully there aren't any other open files, but they'll be cleaned up
1692 // by the OS anyway)
1693 FS_ClearSearchPath();
1694 Mem_FreePool (&fs_mempool);
1697 Sys_UnloadLibrary (&shfolder_dll);
1702 ====================
1705 Internal function used to create a qfile_t and open the relevant non-packed file on disk
1706 ====================
1708 static qfile_t* FS_SysOpen (const char* filepath, const char* mode, qboolean nonblocking)
1714 // Parse the mode string
1723 opt = O_CREAT | O_TRUNC;
1727 opt = O_CREAT | O_APPEND;
1730 Con_Printf ("FS_SysOpen(%s, %s): invalid mode\n", filepath, mode);
1733 for (ind = 1; mode[ind] != '\0'; ind++)
1744 Con_Printf ("FS_SysOpen(%s, %s): unknown character in mode (%c)\n",
1745 filepath, mode, mode[ind]);
1752 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1753 memset (file, 0, sizeof (*file));
1756 #if _MSC_VER >= 1400
1757 _sopen_s(&file->handle, filepath, mod | opt, _SH_DENYNO, _S_IREAD | _S_IWRITE);
1759 file->handle = open (filepath, mod | opt, 0666);
1761 if (file->handle < 0)
1767 file->real_length = lseek (file->handle, 0, SEEK_END);
1769 // For files opened in append mode, we start at the end of the file
1771 file->position = file->real_length;
1773 lseek (file->handle, 0, SEEK_SET);
1783 Open a packed file using its package file descriptor
1786 qfile_t *FS_OpenPackedFile (pack_t* pack, int pack_ind)
1792 pfile = &pack->files[pack_ind];
1794 // If we don't have the true offset, get it now
1795 if (! (pfile->flags & PACKFILE_FLAG_TRUEOFFS))
1796 if (!PK3_GetTrueFileOffset (pfile, pack))
1799 // No Zlib DLL = no compressed files
1800 if (!zlib_dll && (pfile->flags & PACKFILE_FLAG_DEFLATED))
1802 Con_Printf("WARNING: can't open the compressed file %s\n"
1803 "You need the Zlib DLL to use compressed files\n",
1808 // LordHavoc: lseek affects all duplicates of a handle so we do it before
1809 // the dup() call to avoid having to close the dup_handle on error here
1810 if (lseek (pack->handle, pfile->offset, SEEK_SET) == -1)
1812 Con_Printf ("FS_OpenPackedFile: can't lseek to %s in %s (offset: %d)\n",
1813 pfile->name, pack->filename, (int) pfile->offset);
1817 dup_handle = dup (pack->handle);
1820 Con_Printf ("FS_OpenPackedFile: can't dup package's handle (pack: %s)\n", pack->filename);
1824 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
1825 memset (file, 0, sizeof (*file));
1826 file->handle = dup_handle;
1827 file->flags = QFILE_FLAG_PACKED;
1828 file->real_length = pfile->realsize;
1829 file->offset = pfile->offset;
1833 if (pfile->flags & PACKFILE_FLAG_DEFLATED)
1837 file->flags |= QFILE_FLAG_DEFLATED;
1839 // We need some more variables
1840 ztk = (ztoolkit_t *)Mem_Alloc (fs_mempool, sizeof (*ztk));
1842 ztk->comp_length = pfile->packsize;
1844 // Initialize zlib stream
1845 ztk->zstream.next_in = ztk->input;
1846 ztk->zstream.avail_in = 0;
1848 /* From Zlib's "unzip.c":
1850 * windowBits is passed < 0 to tell that there is no zlib header.
1851 * Note that in this case inflate *requires* an extra "dummy" byte
1852 * after the compressed stream in order to complete decompression and
1853 * return Z_STREAM_END.
1854 * In unzip, i don't wait absolutely Z_STREAM_END because I known the
1855 * size of both compressed and uncompressed data
1857 if (qz_inflateInit2 (&ztk->zstream, -MAX_WBITS) != Z_OK)
1859 Con_Printf ("FS_OpenPackedFile: inflate init error (file: %s)\n", pfile->name);
1865 ztk->zstream.next_out = file->buff;
1866 ztk->zstream.avail_out = sizeof (file->buff);
1875 ====================
1878 Return true if the path should be rejected due to one of the following:
1879 1: path elements that are non-portable
1880 2: path elements that would allow access to files outside the game directory,
1881 or are just not a good idea for a mod to be using.
1882 ====================
1884 int FS_CheckNastyPath (const char *path, qboolean isgamedir)
1886 // all: never allow an empty path, as for gamedir it would access the parent directory and a non-gamedir path it is just useless
1890 // Windows: don't allow \ in filenames (windows-only), period.
1891 // (on Windows \ is a directory separator, but / is also supported)
1892 if (strstr(path, "\\"))
1893 return 1; // non-portable
1895 // Mac: don't allow Mac-only filenames - : is a directory separator
1896 // instead of /, but we rely on / working already, so there's no reason to
1897 // support a Mac-only path
1898 // Amiga and Windows: : tries to go to root of drive
1899 if (strstr(path, ":"))
1900 return 1; // non-portable attempt to go to root of drive
1902 // Amiga: // is parent directory
1903 if (strstr(path, "//"))
1904 return 1; // non-portable attempt to go to parent directory
1906 // all: don't allow going to parent directory (../ or /../)
1907 if (strstr(path, ".."))
1908 return 2; // attempt to go outside the game directory
1910 // Windows and UNIXes: don't allow absolute paths
1912 return 2; // attempt to go outside the game directory
1914 // all: don't allow . characters before the last slash (it should only be used in filenames, not path elements), this catches all imaginable cases of ./, ../, .../, etc
1915 if (strchr(path, '.'))
1919 // gamedir is entirely path elements, so simply forbid . entirely
1922 if (strchr(path, '.') < strrchr(path, '/'))
1923 return 2; // possible attempt to go outside the game directory
1926 // all: forbid trailing slash on gamedir
1927 if (isgamedir && path[strlen(path)-1] == '/')
1930 // all: forbid leading dot on any filename for any reason
1931 if (strstr(path, "/."))
1932 return 2; // attempt to go outside the game directory
1934 // after all these checks we're pretty sure it's a / separated filename
1935 // and won't do much if any harm
1941 ====================
1944 Look for a file in the packages and in the filesystem
1946 Return the searchpath where the file was found (or NULL)
1947 and the file index in the package if relevant
1948 ====================
1950 static searchpath_t *FS_FindFile (const char *name, int* index, qboolean quiet)
1952 searchpath_t *search;
1955 // search through the path, one element at a time
1956 for (search = fs_searchpaths;search;search = search->next)
1958 // is the element a pak file?
1961 int (*strcmp_funct) (const char* str1, const char* str2);
1962 int left, right, middle;
1965 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
1967 // Look for the file (binary search)
1969 right = pak->numfiles - 1;
1970 while (left <= right)
1974 middle = (left + right) / 2;
1975 diff = strcmp_funct (pak->files[middle].name, name);
1980 if (fs_empty_files_in_pack_mark_deletions.integer && pak->files[middle].realsize == 0)
1982 // yes, but the first one is empty so we treat it as not being there
1983 if (!quiet && developer.integer >= 10)
1984 Con_Printf("FS_FindFile: %s is marked as deleted\n", name);
1991 if (!quiet && developer.integer >= 10)
1992 Con_Printf("FS_FindFile: %s in %s\n",
1993 pak->files[middle].name, pak->filename);
2000 // If we're too far in the list
2009 char netpath[MAX_OSPATH];
2010 dpsnprintf(netpath, sizeof(netpath), "%s%s", search->filename, name);
2011 if (FS_SysFileExists (netpath))
2013 if (!quiet && developer.integer >= 10)
2014 Con_Printf("FS_FindFile: %s\n", netpath);
2023 if (!quiet && developer.integer >= 10)
2024 Con_Printf("FS_FindFile: can't find %s\n", name);
2036 Look for a file in the search paths and open it in read-only mode
2039 qfile_t *FS_OpenReadFile (const char *filename, qboolean quiet, qboolean nonblocking, int symlinkLevels)
2041 searchpath_t *search;
2044 search = FS_FindFile (filename, &pack_ind, quiet);
2050 // Found in the filesystem?
2053 char path [MAX_OSPATH];
2054 dpsnprintf (path, sizeof (path), "%s%s", search->filename, filename);
2055 return FS_SysOpen (path, "rb", nonblocking);
2058 // So, we found it in a package...
2060 // Is it a PK3 symlink?
2061 // TODO also handle directory symlinks by parsing the whole structure...
2062 // but heck, file symlinks are good enough for now
2063 if(search->pack->files[pack_ind].flags & PACKFILE_FLAG_SYMLINK)
2065 if(symlinkLevels <= 0)
2067 Con_Printf("symlink: %s: too many levels of symbolic links\n", filename);
2072 char linkbuf[MAX_QPATH];
2074 qfile_t *linkfile = FS_OpenPackedFile (search->pack, pack_ind);
2075 const char *mergeslash;
2080 count = FS_Read(linkfile, linkbuf, sizeof(linkbuf) - 1);
2086 // Now combine the paths...
2087 mergeslash = strrchr(filename, '/');
2088 mergestart = linkbuf;
2090 mergeslash = filename;
2091 while(!strncmp(mergestart, "../", 3))
2094 while(mergeslash > filename)
2097 if(*mergeslash == '/')
2101 // Now, mergestart will point to the path to be appended, and mergeslash points to where it should be appended
2102 if(mergeslash == filename)
2104 // Either mergeslash == filename, then we just replace the name (done below)
2108 // Or, we append the name after mergeslash;
2109 // or rather, we can also shift the linkbuf so we can put everything up to and including mergeslash first
2110 int spaceNeeded = mergeslash - filename + 1;
2111 int spaceRemoved = mergestart - linkbuf;
2112 if(count - spaceRemoved + spaceNeeded >= MAX_QPATH)
2114 Con_DPrintf("symlink: too long path rejected\n");
2117 memmove(linkbuf + spaceNeeded, linkbuf + spaceRemoved, count - spaceRemoved);
2118 memcpy(linkbuf, filename, spaceNeeded);
2119 linkbuf[count - spaceRemoved + spaceNeeded] = 0;
2120 mergestart = linkbuf;
2122 if (!quiet && developer_loading.integer)
2123 Con_DPrintf("symlink: %s -> %s\n", filename, mergestart);
2124 if(FS_CheckNastyPath (mergestart, false))
2126 Con_DPrintf("symlink: nasty path %s rejected\n", mergestart);
2129 return FS_OpenReadFile(mergestart, quiet, nonblocking, symlinkLevels - 1);
2133 return FS_OpenPackedFile (search->pack, pack_ind);
2138 =============================================================================
2140 MAIN PUBLIC FUNCTIONS
2142 =============================================================================
2146 ====================
2149 Open a file in the userpath. The syntax is the same as fopen
2150 Used for savegame scanning in menu, and all file writing.
2151 ====================
2153 qfile_t* FS_OpenRealFile (const char* filepath, const char* mode, qboolean quiet)
2155 char real_path [MAX_OSPATH];
2157 if (FS_CheckNastyPath(filepath, false))
2159 Con_Printf("FS_OpenRealFile(\"%s\", \"%s\", %s): nasty filename rejected\n", filepath, mode, quiet ? "true" : "false");
2163 dpsnprintf (real_path, sizeof (real_path), "%s/%s", fs_gamedir, filepath);
2165 // If the file is opened in "write", "append", or "read/write" mode,
2166 // create directories up to the file.
2167 if (mode[0] == 'w' || mode[0] == 'a' || strchr (mode, '+'))
2168 FS_CreatePath (real_path);
2169 return FS_SysOpen (real_path, mode, false);
2174 ====================
2177 Open a file. The syntax is the same as fopen
2178 ====================
2180 qfile_t* FS_OpenVirtualFile (const char* filepath, qboolean quiet)
2182 if (FS_CheckNastyPath(filepath, false))
2184 Con_Printf("FS_OpenVirtualFile(\"%s\", %s): nasty filename rejected\n", filepath, quiet ? "true" : "false");
2188 return FS_OpenReadFile (filepath, quiet, false, 16);
2193 ====================
2196 Open a file. The syntax is the same as fopen
2197 ====================
2199 qfile_t* FS_FileFromData (const unsigned char *data, const size_t size, qboolean quiet)
2202 file = (qfile_t *)Mem_Alloc (fs_mempool, sizeof (*file));
2203 memset (file, 0, sizeof (*file));
2204 file->flags = QFILE_FLAG_DATA;
2206 file->real_length = size;
2212 ====================
2216 ====================
2218 int FS_Close (qfile_t* file)
2220 if(file->flags & QFILE_FLAG_DATA)
2226 if (close (file->handle))
2231 qz_inflateEnd (&file->ztk->zstream);
2232 Mem_Free (file->ztk);
2241 ====================
2244 Write "datasize" bytes into a file
2245 ====================
2247 fs_offset_t FS_Write (qfile_t* file, const void* data, size_t datasize)
2251 // If necessary, seek to the exact file position we're supposed to be
2252 if (file->buff_ind != file->buff_len)
2253 lseek (file->handle, file->buff_ind - file->buff_len, SEEK_CUR);
2255 // Purge cached data
2258 // Write the buffer and update the position
2259 result = write (file->handle, data, (fs_offset_t)datasize);
2260 file->position = lseek (file->handle, 0, SEEK_CUR);
2261 if (file->real_length < file->position)
2262 file->real_length = file->position;
2272 ====================
2275 Read up to "buffersize" bytes from a file
2276 ====================
2278 fs_offset_t FS_Read (qfile_t* file, void* buffer, size_t buffersize)
2280 fs_offset_t count, done;
2282 if (buffersize == 0)
2285 // Get rid of the ungetc character
2286 if (file->ungetc != EOF)
2288 ((char*)buffer)[0] = file->ungetc;
2296 if(file->flags & QFILE_FLAG_DATA)
2298 size_t left = file->real_length - file->position;
2299 if(buffersize > left)
2301 memcpy(buffer, file->data + file->position, buffersize);
2302 file->position += buffersize;
2306 // First, we copy as many bytes as we can from "buff"
2307 if (file->buff_ind < file->buff_len)
2309 count = file->buff_len - file->buff_ind;
2310 count = ((fs_offset_t)buffersize > count) ? count : (fs_offset_t)buffersize;
2312 memcpy (buffer, &file->buff[file->buff_ind], count);
2313 file->buff_ind += count;
2315 buffersize -= count;
2316 if (buffersize == 0)
2320 // NOTE: at this point, the read buffer is always empty
2322 // If the file isn't compressed
2323 if (! (file->flags & QFILE_FLAG_DEFLATED))
2327 // We must take care to not read after the end of the file
2328 count = file->real_length - file->position;
2330 // If we have a lot of data to get, put them directly into "buffer"
2331 if (buffersize > sizeof (file->buff) / 2)
2333 if (count > (fs_offset_t)buffersize)
2334 count = (fs_offset_t)buffersize;
2335 lseek (file->handle, file->offset + file->position, SEEK_SET);
2336 nb = read (file->handle, &((unsigned char*)buffer)[done], count);
2340 file->position += nb;
2342 // Purge cached data
2348 if (count > (fs_offset_t)sizeof (file->buff))
2349 count = (fs_offset_t)sizeof (file->buff);
2350 lseek (file->handle, file->offset + file->position, SEEK_SET);
2351 nb = read (file->handle, file->buff, count);
2354 file->buff_len = nb;
2355 file->position += nb;
2357 // Copy the requested data in "buffer" (as much as we can)
2358 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2359 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2360 file->buff_ind = count;
2368 // If the file is compressed, it's more complicated...
2369 // We cycle through a few operations until we have read enough data
2370 while (buffersize > 0)
2372 ztoolkit_t *ztk = file->ztk;
2375 // NOTE: at this point, the read buffer is always empty
2377 // If "input" is also empty, we need to refill it
2378 if (ztk->in_ind == ztk->in_len)
2380 // If we are at the end of the file
2381 if (file->position == file->real_length)
2384 count = (fs_offset_t)(ztk->comp_length - ztk->in_position);
2385 if (count > (fs_offset_t)sizeof (ztk->input))
2386 count = (fs_offset_t)sizeof (ztk->input);
2387 lseek (file->handle, file->offset + (fs_offset_t)ztk->in_position, SEEK_SET);
2388 if (read (file->handle, ztk->input, count) != count)
2390 Con_Printf ("FS_Read: unexpected end of file\n");
2395 ztk->in_len = count;
2396 ztk->in_position += count;
2399 ztk->zstream.next_in = &ztk->input[ztk->in_ind];
2400 ztk->zstream.avail_in = (unsigned int)(ztk->in_len - ztk->in_ind);
2402 // Now that we are sure we have compressed data available, we need to determine
2403 // if it's better to inflate it in "file->buff" or directly in "buffer"
2405 // Inflate the data in "file->buff"
2406 if (buffersize < sizeof (file->buff) / 2)
2408 ztk->zstream.next_out = file->buff;
2409 ztk->zstream.avail_out = sizeof (file->buff);
2410 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2411 if (error != Z_OK && error != Z_STREAM_END)
2413 Con_Printf ("FS_Read: Can't inflate file\n");
2416 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2418 file->buff_len = (fs_offset_t)sizeof (file->buff) - ztk->zstream.avail_out;
2419 file->position += file->buff_len;
2421 // Copy the requested data in "buffer" (as much as we can)
2422 count = (fs_offset_t)buffersize > file->buff_len ? file->buff_len : (fs_offset_t)buffersize;
2423 memcpy (&((unsigned char*)buffer)[done], file->buff, count);
2424 file->buff_ind = count;
2427 // Else, we inflate directly in "buffer"
2430 ztk->zstream.next_out = &((unsigned char*)buffer)[done];
2431 ztk->zstream.avail_out = (unsigned int)buffersize;
2432 error = qz_inflate (&ztk->zstream, Z_SYNC_FLUSH);
2433 if (error != Z_OK && error != Z_STREAM_END)
2435 Con_Printf ("FS_Read: Can't inflate file\n");
2438 ztk->in_ind = ztk->in_len - ztk->zstream.avail_in;
2440 // How much data did it inflate?
2441 count = (fs_offset_t)(buffersize - ztk->zstream.avail_out);
2442 file->position += count;
2444 // Purge cached data
2449 buffersize -= count;
2457 ====================
2460 Print a string into a file
2461 ====================
2463 int FS_Print (qfile_t* file, const char *msg)
2465 return (int)FS_Write (file, msg, strlen (msg));
2469 ====================
2472 Print a string into a file
2473 ====================
2475 int FS_Printf(qfile_t* file, const char* format, ...)
2480 va_start (args, format);
2481 result = FS_VPrintf (file, format, args);
2489 ====================
2492 Print a string into a file
2493 ====================
2495 int FS_VPrintf (qfile_t* file, const char* format, va_list ap)
2498 fs_offset_t buff_size = MAX_INPUTLINE;
2503 tempbuff = (char *)Mem_Alloc (tempmempool, buff_size);
2504 len = dpvsnprintf (tempbuff, buff_size, format, ap);
2505 if (len >= 0 && len < buff_size)
2507 Mem_Free (tempbuff);
2511 len = write (file->handle, tempbuff, len);
2512 Mem_Free (tempbuff);
2519 ====================
2522 Get the next character of a file
2523 ====================
2525 int FS_Getc (qfile_t* file)
2529 if (FS_Read (file, &c, 1) != 1)
2537 ====================
2540 Put a character back into the read buffer (only supports one character!)
2541 ====================
2543 int FS_UnGetc (qfile_t* file, unsigned char c)
2545 // If there's already a character waiting to be read
2546 if (file->ungetc != EOF)
2555 ====================
2558 Move the position index in a file
2559 ====================
2561 int FS_Seek (qfile_t* file, fs_offset_t offset, int whence)
2564 unsigned char* buffer;
2565 fs_offset_t buffersize;
2567 // Compute the file offset
2571 offset += file->position - file->buff_len + file->buff_ind;
2578 offset += file->real_length;
2584 if (offset < 0 || offset > file->real_length)
2587 if(file->flags & QFILE_FLAG_DATA)
2589 file->position = offset;
2593 // If we have the data in our read buffer, we don't need to actually seek
2594 if (file->position - file->buff_len <= offset && offset <= file->position)
2596 file->buff_ind = offset + file->buff_len - file->position;
2600 // Purge cached data
2603 // Unpacked or uncompressed files can seek directly
2604 if (! (file->flags & QFILE_FLAG_DEFLATED))
2606 if (lseek (file->handle, file->offset + offset, SEEK_SET) == -1)
2608 file->position = offset;
2612 // Seeking in compressed files is more a hack than anything else,
2613 // but we need to support it, so here we go.
2616 // If we have to go back in the file, we need to restart from the beginning
2617 if (offset <= file->position)
2621 ztk->in_position = 0;
2623 lseek (file->handle, file->offset, SEEK_SET);
2625 // Reset the Zlib stream
2626 ztk->zstream.next_in = ztk->input;
2627 ztk->zstream.avail_in = 0;
2628 qz_inflateReset (&ztk->zstream);
2631 // We need a big buffer to force inflating into it directly
2632 buffersize = 2 * sizeof (file->buff);
2633 buffer = (unsigned char *)Mem_Alloc (tempmempool, buffersize);
2635 // Skip all data until we reach the requested offset
2636 while (offset > file->position)
2638 fs_offset_t diff = offset - file->position;
2639 fs_offset_t count, len;
2641 count = (diff > buffersize) ? buffersize : diff;
2642 len = FS_Read (file, buffer, count);
2656 ====================
2659 Give the current position in a file
2660 ====================
2662 fs_offset_t FS_Tell (qfile_t* file)
2664 return file->position - file->buff_len + file->buff_ind;
2669 ====================
2672 Give the total size of a file
2673 ====================
2675 fs_offset_t FS_FileSize (qfile_t* file)
2677 return file->real_length;
2682 ====================
2685 Erases any buffered input or output data
2686 ====================
2688 void FS_Purge (qfile_t* file)
2700 Filename are relative to the quake directory.
2701 Always appends a 0 byte.
2704 unsigned char *FS_LoadFile (const char *path, mempool_t *pool, qboolean quiet, fs_offset_t *filesizepointer)
2707 unsigned char *buf = NULL;
2708 fs_offset_t filesize = 0;
2710 file = FS_OpenVirtualFile(path, quiet);
2713 filesize = file->real_length;
2716 Con_Printf("FS_LoadFile(\"%s\", pool, %s, filesizepointer): trying to open a non-regular file\n", path, quiet ? "true" : "false");
2721 buf = (unsigned char *)Mem_Alloc (pool, filesize + 1);
2722 buf[filesize] = '\0';
2723 FS_Read (file, buf, filesize);
2725 if (developer_loadfile.integer)
2726 Con_Printf("loaded file \"%s\" (%u bytes)\n", path, (unsigned int)filesize);
2729 if (filesizepointer)
2730 *filesizepointer = filesize;
2739 The filename will be prefixed by the current game directory
2742 qboolean FS_WriteFile (const char *filename, void *data, fs_offset_t len)
2746 file = FS_OpenRealFile(filename, "wb", false);
2749 Con_Printf("FS_WriteFile: failed on %s\n", filename);
2753 Con_DPrintf("FS_WriteFile: %s (%u bytes)\n", filename, (unsigned int)len);
2754 FS_Write (file, data, len);
2761 =============================================================================
2763 OTHERS PUBLIC FUNCTIONS
2765 =============================================================================
2773 void FS_StripExtension (const char *in, char *out, size_t size_out)
2781 while ((currentchar = *in) && size_out > 1)
2783 if (currentchar == '.')
2785 else if (currentchar == '/' || currentchar == '\\' || currentchar == ':')
2787 *out++ = currentchar;
2803 void FS_DefaultExtension (char *path, const char *extension, size_t size_path)
2807 // if path doesn't have a .EXT, append extension
2808 // (extension should include the .)
2809 src = path + strlen(path) - 1;
2811 while (*src != '/' && src != path)
2814 return; // it has an extension
2818 strlcat (path, extension, size_path);
2826 Look for a file in the packages and in the filesystem
2829 int FS_FileType (const char *filename)
2831 searchpath_t *search;
2832 char fullpath[MAX_QPATH];
2834 search = FS_FindFile (filename, NULL, true);
2836 return FS_FILETYPE_NONE;
2839 return FS_FILETYPE_FILE; // TODO can't check directories in paks yet, maybe later
2841 dpsnprintf(fullpath, sizeof(fullpath), "%s%s", search->filename, filename);
2842 return FS_SysFileType(fullpath);
2850 Look for a file in the packages and in the filesystem
2853 qboolean FS_FileExists (const char *filename)
2855 return (FS_FindFile (filename, NULL, true) != NULL);
2863 Look for a file in the filesystem only
2866 int FS_SysFileType (const char *path)
2869 // Sajt - some older sdks are missing this define
2870 # ifndef INVALID_FILE_ATTRIBUTES
2871 # define INVALID_FILE_ATTRIBUTES ((DWORD)-1)
2874 DWORD result = GetFileAttributes(path);
2876 if(result == INVALID_FILE_ATTRIBUTES)
2877 return FS_FILETYPE_NONE;
2879 if(result & FILE_ATTRIBUTE_DIRECTORY)
2880 return FS_FILETYPE_DIRECTORY;
2882 return FS_FILETYPE_FILE;
2886 if (stat (path,&buf) == -1)
2887 return FS_FILETYPE_NONE;
2889 if(S_ISDIR(buf.st_mode))
2890 return FS_FILETYPE_DIRECTORY;
2892 return FS_FILETYPE_FILE;
2896 qboolean FS_SysFileExists (const char *path)
2898 return FS_SysFileType (path) != FS_FILETYPE_NONE;
2901 void FS_mkdir (const char *path)
2914 Allocate and fill a search structure with information on matching filenames.
2917 fssearch_t *FS_Search(const char *pattern, int caseinsensitive, int quiet)
2920 searchpath_t *searchpath;
2922 int i, basepathlength, numfiles, numchars, resultlistindex, dirlistindex;
2923 stringlist_t resultlist;
2924 stringlist_t dirlist;
2925 const char *slash, *backslash, *colon, *separator;
2927 char temp[MAX_OSPATH];
2929 for (i = 0;pattern[i] == '.' || pattern[i] == ':' || pattern[i] == '/' || pattern[i] == '\\';i++)
2934 Con_Printf("Don't use punctuation at the beginning of a search pattern!\n");
2938 stringlistinit(&resultlist);
2939 stringlistinit(&dirlist);
2941 slash = strrchr(pattern, '/');
2942 backslash = strrchr(pattern, '\\');
2943 colon = strrchr(pattern, ':');
2944 separator = max(slash, backslash);
2945 separator = max(separator, colon);
2946 basepathlength = separator ? (separator + 1 - pattern) : 0;
2947 basepath = (char *)Mem_Alloc (tempmempool, basepathlength + 1);
2949 memcpy(basepath, pattern, basepathlength);
2950 basepath[basepathlength] = 0;
2952 // search through the path, one element at a time
2953 for (searchpath = fs_searchpaths;searchpath;searchpath = searchpath->next)
2955 // is the element a pak file?
2956 if (searchpath->pack)
2958 // look through all the pak file elements
2959 pak = searchpath->pack;
2960 for (i = 0;i < pak->numfiles;i++)
2962 strlcpy(temp, pak->files[i].name, sizeof(temp));
2965 if (matchpattern(temp, (char *)pattern, true))
2967 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
2968 if (!strcmp(resultlist.strings[resultlistindex], temp))
2970 if (resultlistindex == resultlist.numstrings)
2972 stringlistappend(&resultlist, temp);
2973 if (!quiet && developer_loading.integer)
2974 Con_Printf("SearchPackFile: %s : %s\n", pak->filename, temp);
2977 // strip off one path element at a time until empty
2978 // this way directories are added to the listing if they match the pattern
2979 slash = strrchr(temp, '/');
2980 backslash = strrchr(temp, '\\');
2981 colon = strrchr(temp, ':');
2983 if (separator < slash)
2985 if (separator < backslash)
2986 separator = backslash;
2987 if (separator < colon)
2989 *((char *)separator) = 0;
2995 stringlist_t matchedSet, foundSet;
2996 const char *start = pattern;
2998 stringlistinit(&matchedSet);
2999 stringlistinit(&foundSet);
3000 // add a first entry to the set
3001 stringlistappend(&matchedSet, "");
3002 // iterate through pattern's path
3005 const char *asterisk, *wildcard, *nextseparator, *prevseparator;
3006 char subpath[MAX_OSPATH];
3007 char subpattern[MAX_OSPATH];
3009 // find the next wildcard
3010 wildcard = strchr(start, '?');
3011 asterisk = strchr(start, '*');
3012 if (asterisk && (!wildcard || asterisk < wildcard))
3014 wildcard = asterisk;
3019 nextseparator = strchr( wildcard, '/' );
3023 nextseparator = NULL;
3026 if( !nextseparator ) {
3027 nextseparator = start + strlen( start );
3030 // prevseparator points past the '/' right before the wildcard and nextseparator at the one following it (or at the end of the string)
3031 // copy everything up except nextseperator
3032 strlcpy(subpattern, pattern, min(sizeof(subpattern), (size_t) (nextseparator - pattern + 1)));
3033 // find the last '/' before the wildcard
3034 prevseparator = strrchr( subpattern, '/' );
3036 prevseparator = subpattern;
3039 // copy everything from start to the previous including the '/' (before the wildcard)
3040 // everything up to start is already included in the path of matchedSet's entries
3041 strlcpy(subpath, start, min(sizeof(subpath), (size_t) ((prevseparator - subpattern) - (start - pattern) + 1)));
3043 // for each entry in matchedSet try to open the subdirectories specified in subpath
3044 for( dirlistindex = 0 ; dirlistindex < matchedSet.numstrings ; dirlistindex++ ) {
3045 strlcpy( temp, matchedSet.strings[ dirlistindex ], sizeof(temp) );
3046 strlcat( temp, subpath, sizeof(temp) );
3047 listdirectory( &foundSet, searchpath->filename, temp );
3049 if( dirlistindex == 0 ) {
3052 // reset the current result set
3053 stringlistfreecontents( &matchedSet );
3054 // match against the pattern
3055 for( dirlistindex = 0 ; dirlistindex < foundSet.numstrings ; dirlistindex++ ) {
3056 const char *direntry = foundSet.strings[ dirlistindex ];
3057 if (matchpattern(direntry, subpattern, true)) {
3058 stringlistappend( &matchedSet, direntry );
3061 stringlistfreecontents( &foundSet );
3063 start = nextseparator;
3066 for (dirlistindex = 0;dirlistindex < matchedSet.numstrings;dirlistindex++)
3068 const char *temp = matchedSet.strings[dirlistindex];
3069 if (matchpattern(temp, (char *)pattern, true))
3071 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3072 if (!strcmp(resultlist.strings[resultlistindex], temp))
3074 if (resultlistindex == resultlist.numstrings)
3076 stringlistappend(&resultlist, temp);
3077 if (!quiet && developer_loading.integer)
3078 Con_Printf("SearchDirFile: %s\n", temp);
3082 stringlistfreecontents( &matchedSet );
3086 if (resultlist.numstrings)
3088 stringlistsort(&resultlist);
3089 numfiles = resultlist.numstrings;
3091 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3092 numchars += (int)strlen(resultlist.strings[resultlistindex]) + 1;
3093 search = (fssearch_t *)Z_Malloc(sizeof(fssearch_t) + numchars + numfiles * sizeof(char *));
3094 search->filenames = (char **)((char *)search + sizeof(fssearch_t));
3095 search->filenamesbuffer = (char *)((char *)search + sizeof(fssearch_t) + numfiles * sizeof(char *));
3096 search->numfilenames = (int)numfiles;
3099 for (resultlistindex = 0;resultlistindex < resultlist.numstrings;resultlistindex++)
3102 search->filenames[numfiles] = search->filenamesbuffer + numchars;
3103 textlen = strlen(resultlist.strings[resultlistindex]) + 1;
3104 memcpy(search->filenames[numfiles], resultlist.strings[resultlistindex], textlen);
3106 numchars += (int)textlen;
3109 stringlistfreecontents(&resultlist);
3115 void FS_FreeSearch(fssearch_t *search)
3120 extern int con_linewidth;
3121 int FS_ListDirectory(const char *pattern, int oneperline)
3130 char linebuf[MAX_INPUTLINE];
3132 search = FS_Search(pattern, true, true);
3135 numfiles = search->numfilenames;
3138 // FIXME: the names could be added to one column list and then
3139 // gradually shifted into the next column if they fit, and then the
3140 // next to make a compact variable width listing but it's a lot more
3142 // find width for columns
3144 for (i = 0;i < numfiles;i++)
3146 l = (int)strlen(search->filenames[i]);
3147 if (columnwidth < l)
3150 // count the spacing character
3152 // calculate number of columns
3153 numcolumns = con_linewidth / columnwidth;
3154 // don't bother with the column printing if it's only one column
3155 if (numcolumns >= 2)
3157 numlines = (numfiles + numcolumns - 1) / numcolumns;
3158 for (i = 0;i < numlines;i++)
3161 for (k = 0;k < numcolumns;k++)
3163 l = i * numcolumns + k;
3166 name = search->filenames[l];
3167 for (j = 0;name[j] && linebufpos + 1 < (int)sizeof(linebuf);j++)
3168 linebuf[linebufpos++] = name[j];
3169 // space out name unless it's the last on the line
3170 if (k + 1 < numcolumns && l + 1 < numfiles)
3171 for (;j < columnwidth && linebufpos + 1 < (int)sizeof(linebuf);j++)
3172 linebuf[linebufpos++] = ' ';
3175 linebuf[linebufpos] = 0;
3176 Con_Printf("%s\n", linebuf);
3183 for (i = 0;i < numfiles;i++)
3184 Con_Printf("%s\n", search->filenames[i]);
3185 FS_FreeSearch(search);
3186 return (int)numfiles;
3189 static void FS_ListDirectoryCmd (const char* cmdname, int oneperline)
3191 const char *pattern;
3194 Con_Printf("usage:\n%s [path/pattern]\n", cmdname);
3197 if (Cmd_Argc() == 2)
3198 pattern = Cmd_Argv(1);
3201 if (!FS_ListDirectory(pattern, oneperline))
3202 Con_Print("No files found.\n");
3207 FS_ListDirectoryCmd("dir", true);
3212 FS_ListDirectoryCmd("ls", false);
3215 void FS_Which_f(void)
3217 const char *filename;
3220 if (Cmd_Argc() != 2)
3222 Con_Printf("usage:\n%s <file>\n", Cmd_Argv(0));
3225 filename = Cmd_Argv(1);
3226 sp = FS_FindFile(filename, &index, true);
3228 Con_Printf("%s isn't anywhere\n", filename);
3232 Con_Printf("%s is in package %s\n", filename, sp->pack->shortname);
3234 Con_Printf("%s is file %s%s\n", filename, sp->filename, filename);
3238 const char *FS_WhichPack(const char *filename)
3241 searchpath_t *sp = FS_FindFile(filename, &index, true);
3243 return sp->pack->shortname;
3249 ====================
3250 FS_IsRegisteredQuakePack
3252 Look for a proof of purchase file file in the requested package
3254 If it is found, this file should NOT be downloaded.
3255 ====================
3257 qboolean FS_IsRegisteredQuakePack(const char *name)
3259 searchpath_t *search;
3262 // search through the path, one element at a time
3263 for (search = fs_searchpaths;search;search = search->next)
3265 if (search->pack && !strcasecmp(FS_FileWithoutPath(search->filename), name))
3267 int (*strcmp_funct) (const char* str1, const char* str2);
3268 int left, right, middle;
3271 strcmp_funct = pak->ignorecase ? strcasecmp : strcmp;
3273 // Look for the file (binary search)
3275 right = pak->numfiles - 1;
3276 while (left <= right)
3280 middle = (left + right) / 2;
3281 diff = !strcmp_funct (pak->files[middle].name, "gfx/pop.lmp");
3287 // If we're too far in the list
3294 // we found the requested pack but it is not registered quake
3302 int FS_CRCFile(const char *filename, size_t *filesizepointer)
3305 unsigned char *filedata;
3306 fs_offset_t filesize;
3307 if (filesizepointer)
3308 *filesizepointer = 0;
3309 if (!filename || !*filename)
3311 filedata = FS_LoadFile(filename, tempmempool, true, &filesize);
3314 if (filesizepointer)
3315 *filesizepointer = filesize;
3316 crc = CRC_Block(filedata, filesize);
3322 unsigned char *FS_Deflate(const unsigned char *data, size_t size, size_t *deflated_size, int level, mempool_t *mempool)
3325 unsigned char *out = NULL;
3328 memset(&strm, 0, sizeof(strm));
3329 strm.zalloc = Z_NULL;
3330 strm.zfree = Z_NULL;
3331 strm.opaque = Z_NULL;
3334 level = Z_DEFAULT_COMPRESSION;
3336 if(qz_deflateInit2(&strm, level, Z_DEFLATED, -MAX_WBITS, Z_MEMLEVEL_DEFAULT, Z_BINARY) != Z_OK)
3338 Con_Printf("FS_Deflate: deflate init error!\n");
3342 strm.next_in = (unsigned char*)data;
3343 strm.avail_in = size;
3345 tmp = (unsigned char *) Mem_Alloc(tempmempool, size);
3348 Con_Printf("FS_Deflate: not enough memory in tempmempool!\n");
3349 qz_deflateEnd(&strm);
3353 strm.next_out = tmp;
3354 strm.avail_out = size;
3356 if(qz_deflate(&strm, Z_FINISH) != Z_STREAM_END)
3358 Con_Printf("FS_Deflate: deflate failed!\n");
3359 qz_deflateEnd(&strm);
3364 if(qz_deflateEnd(&strm) != Z_OK)
3366 Con_Printf("FS_Deflate: deflateEnd failed\n");
3371 if(strm.total_out >= size)
3373 Con_Printf("FS_Deflate: deflate is useless on this data!\n");
3378 out = (unsigned char *) Mem_Alloc(mempool, strm.total_out);
3381 Con_Printf("FS_Deflate: not enough memory in target mempool!\n");
3387 *deflated_size = (size_t)strm.total_out;
3389 memcpy(out, tmp, strm.total_out);
3395 static void AssertBufsize(sizebuf_t *buf, int length)
3397 if(buf->cursize + length > buf->maxsize)
3399 int oldsize = buf->maxsize;
3400 unsigned char *olddata;
3401 olddata = buf->data;
3402 buf->maxsize += length;
3403 buf->data = (unsigned char *) Mem_Alloc(tempmempool, buf->maxsize);
3406 memcpy(buf->data, olddata, oldsize);
3412 unsigned char *FS_Inflate(const unsigned char *data, size_t size, size_t *inflated_size, mempool_t *mempool)
3416 unsigned char *out = NULL;
3417 unsigned char tmp[2048];
3421 memset(&outbuf, 0, sizeof(outbuf));
3422 outbuf.data = (unsigned char *) Mem_Alloc(tempmempool, sizeof(tmp));
3423 outbuf.maxsize = sizeof(tmp);
3425 memset(&strm, 0, sizeof(strm));
3426 strm.zalloc = Z_NULL;
3427 strm.zfree = Z_NULL;
3428 strm.opaque = Z_NULL;
3430 if(qz_inflateInit2(&strm, -MAX_WBITS) != Z_OK)
3432 Con_Printf("FS_Inflate: inflate init error!\n");
3433 Mem_Free(outbuf.data);
3437 strm.next_in = (unsigned char*)data;
3438 strm.avail_in = size;
3442 strm.next_out = tmp;
3443 strm.avail_out = sizeof(tmp);
3444 ret = qz_inflate(&strm, Z_NO_FLUSH);
3445 // it either returns Z_OK on progress, Z_STREAM_END on end
3453 case Z_STREAM_ERROR:
3454 Con_Print("FS_Inflate: stream error!\n");
3457 Con_Print("FS_Inflate: data error!\n");
3460 Con_Print("FS_Inflate: mem error!\n");
3463 Con_Print("FS_Inflate: buf error!\n");
3466 Con_Print("FS_Inflate: unknown error!\n");
3470 if(ret != Z_OK && ret != Z_STREAM_END)
3472 Con_Printf("Error after inflating %u bytes\n", (unsigned)strm.total_in);
3473 Mem_Free(outbuf.data);
3474 qz_inflateEnd(&strm);
3477 have = sizeof(tmp) - strm.avail_out;
3478 AssertBufsize(&outbuf, max(have, sizeof(tmp)));
3479 SZ_Write(&outbuf, tmp, have);
3480 } while(ret != Z_STREAM_END);
3482 qz_inflateEnd(&strm);
3484 out = (unsigned char *) Mem_Alloc(mempool, outbuf.cursize);
3487 Con_Printf("FS_Inflate: not enough memory in target mempool!\n");
3488 Mem_Free(outbuf.data);
3492 memcpy(out, outbuf.data, outbuf.cursize);
3493 Mem_Free(outbuf.data);
3496 *inflated_size = (size_t)outbuf.cursize;