2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below.
24 If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
35 #include <io.h> // for _read
37 #if !__MACH__ && __MWERKS__
41 #include <sys/types.h>
48 #include "../curl/include/curl/curl.h"
52 =============================================================================
56 All of Doom's data access is through a hierarchical file system, but the contents of
57 the file system can be transparently merged from several sources.
59 A "relativePath" is a reference to game file data, which must include a terminating zero.
60 "..", "\\", and ":" are explicitly illegal in qpaths to prevent any references
61 outside the Doom directory system.
63 The "base path" is the path to the directory holding all the game directories and
64 usually the executable. It defaults to the current directory, but can be overridden
65 with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified
68 The "save path" is the path to the directory where game files will be saved. It defaults
69 to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the
70 command line. Any files that are created during the game (demos, screenshots, etc.) will
71 be created reletive to the save path.
73 The "cd path" is the path to an alternate hierarchy that will be searched if a file
74 is not located in the base path. A user can do a partial install that copies some
75 data to a base path created on their hard drive and leave the rest on the cd. It defaults
76 to the current directory, but it can be overridden with "+set fs_cdpath g:\doom" on the
79 The "dev path" is the path to an alternate hierarchy where the editors and tools used
80 during development (Radiant, AF editor, dmap, runAAS) will write files to. It defaults to
81 the cd path, but can be overridden with a "+set fs_devpath c:\doom" on the command line.
83 If a user runs the game directly from a CD, the base path would be on the CD. This
84 should still function correctly, but all file writes will fail (harmlessly).
86 The "base game" is the directory under the paths where data comes from by default, and
87 can be either "base" or "demo".
89 The "current game" may be the same as the base game, or it may be the name of another
90 directory under the paths that should be searched for files before looking in the base
91 game. The game directory is set with "+set fs_game myaddon" on the command line. This is
94 No other directories outside of the base game and current game will ever be referenced by
97 To save disk space and speed up file loading, directory trees can be collapsed into zip
98 files. The files use a ".pk4" extension to prevent users from unzipping them accidentally,
99 but otherwise they are simply normal zip files. A game directory can have multiple zip
100 files of the form "pak0.pk4", "pak1.pk4", etc. Zip files are searched in decending order
101 from the highest number to the lowest, and will always take precedence over the filesystem.
102 This allows a pk4 distributed as a patch to override all existing data.
104 Because we will have updated executables freely available online, there is no point to
105 trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
106 should be exactly the same executables as release versions, but with different data that
107 automatically restricts where game media can come from to prevent add-ons from working.
109 After the paths are initialized, Doom will look for the product.txt file. If not found
110 and verified, the game will run in restricted mode. In restricted mode, only files
111 contained in demo/pak0.pk4 will be available for loading, and only if the zip header is
112 verified to not have been modified. A single exception is made for DoomConfig.cfg. Files
113 can still be written out in restricted mode, so screenshots and demos are allowed.
114 Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
115 if there is a valid product.txt under the basepath or cdpath.
117 If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
118 path, it will be copied over to the save path. This is a development aid to help build
119 test releases and to copy working sets of files.
121 If the "fs_copyfiles" cvar is set to 2, any file found in fs_cdpath that is newer than
122 it's fs_savepath version will be copied to fs_savepath (in addition to the fs_copyfiles 1
125 If the "fs_copyfiles" cvar is set to 3, files from both basepath and cdpath will be copied
126 over to the save path. This is useful when copying working sets of files mainly from base
127 path with an additional cd path (which can be a slower network drive for instance).
129 If the "fs_copyfiles" cvar is set to 4, files that exist in the cd path but NOT the base path
130 will be copied to the save path
132 NOTE: fs_copyfiles and case sensitivity. On fs_caseSensitiveOS 0 filesystems ( win32 ), the
133 copied files may change casing when copied over.
135 The relative path "sound/newstuff/test.wav" would be searched for in the following places:
137 for save path, dev path, base path, cd path:
138 for current game, base game:
142 downloaded files, to be written to save path + current game's directory
144 The filesystem can be safely shutdown and reinitialized with different
145 basedir / cddir / game combinations, but all other subsystems that rely on it
146 (sound, video) must also be forced to restart.
149 "fs_caseSensitiveOS":
150 This cvar is set on operating systems that use case sensitive filesystems (Linux and OSX)
151 It is a common situation to have the media reference filenames, whereas the file on disc
152 only matches in a case-insensitive way. When "fs_caseSensitiveOS" is set, the filesystem
153 will always do a case insensitive search.
154 IMPORTANT: This only applies to files, and not to directories. There is no case-insensitive
155 matching of directories. All directory names should be lowercase, when "com_developer" is 1,
156 the filesystem will warn when it catches bad directory situations (regardless of the
157 "fs_caseSensitiveOS" setting)
158 When bad casing in directories happen and "fs_caseSensitiveOS" is set, BuildOSPath will
159 attempt to correct the situation by forcing the path to lowercase. This assumes the media
160 is stored all lowercase.
162 "additional mod path search":
163 fs_game_base can be used to set an additional search path
164 in search order, fs_game, fs_game_base, BASEGAME
165 for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp
167 =============================================================================
172 // define to fix special-cases for GetPackStatus so that files that shipped in
173 // the wrong place for Doom 3 don't break pure servers.
174 #define DOOM3_PURE_SPECIAL_CASES
176 typedef bool (*pureExclusionFunc_t)( const struct pureExclusion_s &excl, int l, const idStr &name );
178 typedef struct pureExclusion_s {
183 pureExclusionFunc_t func;
186 bool excludeExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
187 if ( l > excl.extLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) ) {
193 bool excludePathPrefixAndExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
194 if ( l > excl.nameLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) && !name.IcmpPrefixPath( excl.name ) ) {
200 bool excludeFullName( const pureExclusion_t &excl, int l, const idStr &name ) {
201 if ( l == excl.nameLen && !name.Icmp( excl.name ) ) {
207 static pureExclusion_t pureExclusions[] = {
208 { 0, 0, NULL, "/", excludeExtension },
209 { 0, 0, NULL, "\\", excludeExtension },
210 { 0, 0, NULL, ".pda", excludeExtension },
211 { 0, 0, NULL, ".gui", excludeExtension },
212 { 0, 0, NULL, ".pd", excludeExtension },
213 { 0, 0, NULL, ".lang", excludeExtension },
214 { 0, 0, "sound/VO", ".ogg", excludePathPrefixAndExtension },
215 { 0, 0, "sound/VO", ".wav", excludePathPrefixAndExtension },
216 #if defined DOOM3_PURE_SPECIAL_CASES
217 // add any special-case files or paths for pure servers here
218 { 0, 0, "sound/ed/marscity/vo_intro_cutscene.ogg", NULL, excludeFullName },
219 { 0, 0, "sound/weapons/soulcube/energize_01.ogg", NULL, excludeFullName },
220 { 0, 0, "sound/xian/creepy/vocal_fx", ".ogg", excludePathPrefixAndExtension },
221 { 0, 0, "sound/xian/creepy/vocal_fx", ".wav", excludePathPrefixAndExtension },
222 { 0, 0, "sound/feedback", ".ogg", excludePathPrefixAndExtension },
223 { 0, 0, "sound/feedback", ".wav", excludePathPrefixAndExtension },
224 { 0, 0, "guis/assets/mainmenu/chnote.tga", NULL, excludeFullName },
225 { 0, 0, "sound/levels/alphalabs2/uac_better_place.ogg", NULL, excludeFullName },
226 { 0, 0, "textures/bigchars.tga", NULL, excludeFullName },
227 { 0, 0, "dds/textures/bigchars.dds", NULL, excludeFullName },
228 { 0, 0, "fonts", ".tga", excludePathPrefixAndExtension },
229 { 0, 0, "dds/fonts", ".dds", excludePathPrefixAndExtension },
230 { 0, 0, "default.cfg", NULL, excludeFullName },
231 // russian zpak001.pk4
232 { 0, 0, "fonts", ".dat", excludePathPrefixAndExtension },
233 { 0, 0, "guis/temp.guied", NULL, excludeFullName },
235 { 0, 0, NULL, NULL, NULL }
238 // ensures that lengths for pure exclusions are correct
239 class idInitExclusions {
242 for ( int i = 0; pureExclusions[i].func != NULL; i++ ) {
243 if ( pureExclusions[i].name ) {
244 pureExclusions[i].nameLen = idStr::Length( pureExclusions[i].name );
246 if ( pureExclusions[i].ext ) {
247 pureExclusions[i].extLen = idStr::Length( pureExclusions[i].ext );
253 static idInitExclusions initExclusions;
255 #define MAX_ZIPPED_FILE_NAME 2048
256 #define FILE_HASH_SIZE 1024
258 typedef struct fileInPack_s {
259 idStr name; // name of the file
260 unsigned long pos; // file info position in zip
261 struct fileInPack_s * next; // next file in the hash
271 PURE_UNKNOWN = 0, // need to run the pak through GetPackStatus
272 PURE_NEUTRAL, // neutral regarding pureness. gets in the pure list if referenced
273 PURE_ALWAYS, // always referenced - for pak* named files, unless NEVER
274 PURE_NEVER // VO paks. may be referenced, won't be in the pure lists
279 idList<idDict *> mapDecls;
283 idStr pakFilename; // c:\doom\base\pak0.pk4
289 binaryStatus_t binary;
290 bool addon; // this is an addon pack - addon_search tells if it's 'active'
291 bool addon_search; // is in the search list
292 addonInfo_t *addon_info;
293 pureStatus_t pureStatus;
294 bool isNew; // for downloaded paks
295 fileInPack_t *hashTable[FILE_HASH_SIZE];
296 fileInPack_t *buildBuffer;
300 idStr path; // c:\doom
301 idStr gamedir; // base
304 typedef struct searchpath_s {
305 pack_t * pack; // only one of pack / dir will be non NULL
307 struct searchpath_s *next;
310 // search flags when opening a file
311 #define FSFLAG_SEARCH_DIRS ( 1 << 0 )
312 #define FSFLAG_SEARCH_PAKS ( 1 << 1 )
313 #define FSFLAG_PURE_NOREF ( 1 << 2 )
314 #define FSFLAG_BINARY_ONLY ( 1 << 3 )
315 #define FSFLAG_SEARCH_ADDONS ( 1 << 4 )
317 // 3 search path (fs_savepath fs_basepath fs_cdpath)
319 #define MAX_CACHED_DIRS 6
321 // how many OSes to handle game paks for ( we don't have to know them precisely )
322 #define MAX_GAME_OS 6
323 #define BINARY_CONFIG "binary.conf"
324 #define ADDON_CONFIG "addon.conf"
326 class idDEntry : public idStrList {
329 virtual ~idDEntry() {}
331 bool Matches( const char *directory, const char *extension ) const;
332 void Init( const char *directory, const char *extension, const idStrList &list );
340 class idFileSystemLocal : public idFileSystem {
342 idFileSystemLocal( void );
344 virtual void Init( void );
345 virtual void StartBackgroundDownloadThread( void );
346 virtual void Restart( void );
347 virtual void Shutdown( bool reloading );
348 virtual bool IsInitialized( void ) const;
349 virtual bool PerformingCopyFiles( void ) const;
350 virtual idModList * ListMods( void );
351 virtual void FreeModList( idModList *modList );
352 virtual idFileList * ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL );
353 virtual idFileList * ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL );
354 virtual void FreeFileList( idFileList *fileList );
355 virtual const char * OSPathToRelativePath( const char *OSPath );
356 virtual const char * RelativePathToOSPath( const char *relativePath, const char *basePath );
357 virtual const char * BuildOSPath( const char *base, const char *game, const char *relativePath );
358 virtual void CreateOSPath( const char *OSPath );
359 virtual bool FileIsInPAK( const char *relativePath );
360 virtual void UpdatePureServerChecksums( void );
361 virtual bool UpdateGamePakChecksums( void );
362 virtual fsPureReply_t SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum );
363 virtual void GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *gamePakChecksum );
364 virtual void SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum );
365 virtual void ClearPureChecksums( void );
366 virtual int GetOSMask( void );
367 virtual int ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp );
368 virtual void FreeFile( void *buffer );
369 virtual int WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" );
370 virtual void RemoveFile( const char *relativePath );
371 virtual idFile * OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak = NULL, bool allowCopyFiles = true, const char* gamedir = NULL );
372 virtual idFile * OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL );
373 virtual idFile * OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" );
374 virtual idFile * OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath" );
375 virtual idFile * OpenFileByMode( const char *relativePath, fsMode_t mode );
376 virtual idFile * OpenExplicitFileRead( const char *OSPath );
377 virtual idFile * OpenExplicitFileWrite( const char *OSPath );
378 virtual void CloseFile( idFile *f );
379 virtual void BackgroundDownload( backgroundDownload_t *bgl );
380 virtual void ResetReadCount( void ) { readCount = 0; }
381 virtual void AddToReadCount( int c ) { readCount += c; }
382 virtual int GetReadCount( void ) { return readCount; }
383 virtual void FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum );
384 virtual void ClearDirCache( void );
385 virtual bool HasD3XP( void );
386 virtual bool RunningD3XP( void );
387 virtual void CopyFile( const char *fromOSPath, const char *toOSPath );
388 virtual int ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary );
389 virtual idFile * MakeTemporaryFile( void );
390 virtual int AddZipFile( const char *path );
391 virtual findFile_t FindFile( const char *path, bool scheduleAddons );
392 virtual int GetNumMaps();
393 virtual const idDict * GetMapDecl( int i );
394 virtual void FindMapScreenshot( const char *path, char *buf, int len );
395 virtual bool FilenameCompare( const char *s1, const char *s2 ) const;
397 static void Dir_f( const idCmdArgs &args );
398 static void DirTree_f( const idCmdArgs &args );
399 static void Path_f( const idCmdArgs &args );
400 static void TouchFile_f( const idCmdArgs &args );
401 static void TouchFileList_f( const idCmdArgs &args );
404 friend dword BackgroundDownloadThread( void *parms );
406 searchpath_t * searchPaths;
407 int readCount; // total bytes read
408 int loadCount; // total files read
409 int loadStack; // total files in memory
410 idStr gameFolder; // this will be a single name without separators
412 searchpath_t *addonPaks; // not loaded up, but we saw them
414 idDict mapDict; // for GetMapDecl
416 static idCVar fs_debug;
417 static idCVar fs_restrict;
418 static idCVar fs_copyfiles;
419 static idCVar fs_basepath;
420 static idCVar fs_savepath;
421 static idCVar fs_cdpath;
422 static idCVar fs_devpath;
423 static idCVar fs_game;
424 static idCVar fs_game_base;
425 static idCVar fs_caseSensitiveOS;
426 static idCVar fs_searchAddons;
428 backgroundDownload_t * backgroundDownloads;
429 backgroundDownload_t defaultBackgroundDownload;
430 xthreadInfo backgroundThread;
432 idList<pack_t *> serverPaks;
433 bool loadedFileFromDir; // set to true once a file was loaded from a directory - can't switch to pure anymore
434 idList<int> restartChecksums; // used during a restart to set things in right order
435 idList<int> addonChecksums; // list of checksums that should go to the search list directly ( for restarts )
436 int restartGamePakChecksum;
437 int gameDLLChecksum; // the checksum of the last loaded game DLL
438 int gamePakChecksum; // the checksum of the pak holding the loaded game DLL
440 int gamePakForOS[ MAX_GAME_OS ];
442 idDEntry dir_cache[ MAX_CACHED_DIRS ]; // fifo
446 int d3xp; // 0: didn't check, -1: not installed, 1: installed
449 void ReplaceSeparators( idStr &path, char sep = PATHSEPERATOR_CHAR );
450 long HashFileName( const char *fname ) const;
451 int ListOSFiles( const char *directory, const char *extension, idStrList &list );
452 FILE * OpenOSFile( const char *name, const char *mode, idStr *caseSensitiveName = NULL );
453 FILE * OpenOSFileCorrectName( idStr &path, const char *mode );
454 int DirectFileLength( FILE *o );
455 void CopyFile( idFile *src, const char *toOSPath );
456 int AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const;
457 void GetExtensionList( const char *extension, idStrList &extensionList ) const;
458 int GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL );
460 int GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL );
461 pack_t * LoadZipFile( const char *zipfile );
462 void AddGameDirectory( const char *path, const char *dir );
463 void SetupGameDirectories( const char *gameName );
464 void Startup( void );
465 void SetRestrictions( void );
466 // some files can be obtained from directories without compromising si_pure
467 bool FileAllowedFromDir( const char *path );
468 // searches all the paks, no pure check
469 pack_t * GetPackForChecksum( int checksum, bool searchAddons = false );
470 // searches all the paks, no pure check
471 pack_t * FindPakForFileChecksum( const char *relativePath, int fileChecksum, bool bReference );
472 idFile_InZip * ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath );
473 int GetFileChecksum( idFile *file );
474 pureStatus_t GetPackStatus( pack_t *pak );
475 addonInfo_t * ParseAddonDef( const char *buf, const int len );
476 void FollowAddonDependencies( pack_t *pak );
478 static size_t CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream );
479 // curl_progress_callback in curl.h
480 static int CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow );
483 idCVar idFileSystemLocal::fs_restrict( "fs_restrict", "", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "" );
484 idCVar idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
485 idCVar idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_INTEGER, "", 0, 4, idCmdSystem::ArgCompletion_Integer<0,3> );
486 idCVar idFileSystemLocal::fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
487 idCVar idFileSystemLocal::fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
488 idCVar idFileSystemLocal::fs_cdpath( "fs_cdpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
489 idCVar idFileSystemLocal::fs_devpath( "fs_devpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
490 idCVar idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" );
491 idCVar idFileSystemLocal::fs_game_base( "fs_game_base", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "alternate mod path, searched after the main fs_game path, before the basedir" );
493 idCVar idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
495 idCVar idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "1", CVAR_SYSTEM | CVAR_BOOL, "" );
497 idCVar idFileSystemLocal::fs_searchAddons( "fs_searchAddons", "0", CVAR_SYSTEM | CVAR_BOOL, "search all addon pk4s ( disables addon functionality )" );
499 idFileSystemLocal fileSystemLocal;
500 idFileSystem * fileSystem = &fileSystemLocal;
504 idFileSystemLocal::idFileSystemLocal
507 idFileSystemLocal::idFileSystemLocal( void ) {
515 loadedFileFromDir = false;
516 restartGamePakChecksum = 0;
517 memset( &backgroundThread, 0, sizeof( backgroundThread ) );
523 idFileSystemLocal::HashFileName
525 return a hash value for the filename
528 long idFileSystemLocal::HashFileName( const char *fname ) const {
535 while( fname[i] != '\0' ) {
536 letter = idStr::ToLower( fname[i] );
537 if ( letter == '.' ) {
538 break; // don't include extension
540 if ( letter == '\\' ) {
541 letter = '/'; // damn path names
543 hash += (long)(letter) * (i+119);
546 hash &= (FILE_HASH_SIZE-1);
552 idFileSystemLocal::FilenameCompare
554 Ignore case and separator char distinctions
557 bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const {
564 if ( c1 >= 'a' && c1 <= 'z' ) {
567 if ( c2 >= 'a' && c2 <= 'z' ) {
571 if ( c1 == '\\' || c1 == ':' ) {
574 if ( c2 == '\\' || c2 == ':' ) {
579 return true; // strings not equal
583 return false; // strings are equal
588 idFileSystemLocal::OpenOSFile
589 optional caseSensitiveName is set to case sensitive file name as found on disc (fs_caseSensitiveOS only)
592 FILE *idFileSystemLocal::OpenOSFile( const char *fileName, const char *mode, idStr *caseSensitiveName ) {
600 // some systems will let you fopen a directory
602 if ( stat( fileName, &buf ) != -1 && !S_ISREG(buf.st_mode) ) {
607 fp = fopen( fileName, mode );
608 if ( !fp && fs_caseSensitiveOS.GetBool() ) {
610 fpath.StripFilename();
611 fpath.StripTrailing( PATHSEPERATOR_CHAR );
612 if ( ListOSFiles( fpath, NULL, list ) == -1 ) {
616 for ( i = 0; i < list.Num(); i++ ) {
617 entry = fpath + PATHSEPERATOR_CHAR + list[i];
618 if ( !entry.Icmp( fileName ) ) {
619 fp = fopen( entry, mode );
621 if ( caseSensitiveName ) {
622 *caseSensitiveName = entry;
623 caseSensitiveName->StripPath();
625 if ( fs_debug.GetInteger() ) {
626 common->Printf( "idFileSystemLocal::OpenFileRead: changed %s to %s\n", fileName, entry.c_str() );
630 // not supposed to happen if ListOSFiles is doing it's job correctly
631 common->Warning( "idFileSystemLocal::OpenFileRead: fs_caseSensitiveOS 1 could not open %s", entry.c_str() );
635 } else if ( caseSensitiveName ) {
636 *caseSensitiveName = fileName;
637 caseSensitiveName->StripPath();
644 idFileSystemLocal::OpenOSFileCorrectName
647 FILE *idFileSystemLocal::OpenOSFileCorrectName( idStr &path, const char *mode ) {
649 FILE *f = OpenOSFile( path.c_str(), mode, &caseName );
651 path.StripFilename();
652 path += PATHSEPERATOR_STR;
660 idFileSystemLocal::DirectFileLength
663 int idFileSystemLocal::DirectFileLength( FILE *o ) {
668 fseek( o, 0, SEEK_END );
670 fseek( o, pos, SEEK_SET );
676 idFileSystemLocal::CreateOSPath
678 Creates any directories needed to store the given filename
681 void idFileSystemLocal::CreateOSPath( const char *OSPath ) {
684 // make absolutely sure that it can't back up the path
685 // FIXME: what about c: ?
686 if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
688 common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath );
693 idStr path( OSPath );
694 for( ofs = &path[ 1 ]; *ofs ; ofs++ ) {
695 if ( *ofs == PATHSEPERATOR_CHAR ) {
696 // create the directory
699 *ofs = PATHSEPERATOR_CHAR;
706 idFileSystemLocal::CopyFile
708 Copy a fully specified file from one place to another
711 void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) {
716 common->Printf( "copy %s to %s\n", fromOSPath, toOSPath );
717 f = OpenOSFile( fromOSPath, "rb" );
721 fseek( f, 0, SEEK_END );
723 fseek( f, 0, SEEK_SET );
725 buf = (byte *)Mem_Alloc( len );
726 if ( fread( buf, 1, len, f ) != (unsigned int)len ) {
727 common->FatalError( "short read in idFileSystemLocal::CopyFile()\n" );
731 CreateOSPath( toOSPath );
732 f = OpenOSFile( toOSPath, "wb" );
734 common->Printf( "could not create destination file\n" );
738 if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
739 common->FatalError( "short write in idFileSystemLocal::CopyFile()\n" );
747 idFileSystemLocal::CopyFile
750 void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) {
755 common->Printf( "copy %s to %s\n", src->GetName(), toOSPath );
756 src->Seek( 0, FS_SEEK_END );
758 src->Seek( 0, FS_SEEK_SET );
760 buf = (byte *)Mem_Alloc( len );
761 if ( src->Read( buf, len ) != len ) {
762 common->FatalError( "Short read in idFileSystemLocal::CopyFile()\n" );
765 CreateOSPath( toOSPath );
766 f = OpenOSFile( toOSPath, "wb" );
768 common->Printf( "could not create destination file\n" );
772 if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
773 common->FatalError( "Short write in idFileSystemLocal::CopyFile()\n" );
781 idFileSystemLocal::ReplaceSeparators
783 Fix things up differently for win/unix/mac
786 void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) {
789 for( s = &path[ 0 ]; *s ; s++ ) {
790 if ( *s == '/' || *s == '\\' ) {
798 idFileSystemLocal::BuildOSPath
801 const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) {
802 static char OSPath[MAX_STRING_CHARS];
805 if ( fs_caseSensitiveOS.GetBool() || com_developer.GetBool() ) {
806 // extract the path, make sure it's all lowercase
807 idStr testPath, fileName;
809 sprintf( testPath, "%s/%s", game , relativePath );
810 testPath.StripFilename();
812 if ( testPath.HasUpper() ) {
814 common->Warning( "Non-portable: path contains uppercase characters: %s", testPath.c_str() );
816 // attempt a fixup on the fly
817 if ( fs_caseSensitiveOS.GetBool() ) {
819 fileName = relativePath;
820 fileName.StripPath();
821 sprintf( newPath, "%s/%s/%s", base, testPath.c_str(), fileName.c_str() );
822 ReplaceSeparators( newPath );
823 common->DPrintf( "Fixed up to %s\n", newPath.c_str() );
824 idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
830 idStr strBase = base;
831 strBase.StripTrailing( '/' );
832 strBase.StripTrailing( '\\' );
833 sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath );
834 ReplaceSeparators( newPath );
835 idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
841 idFileSystemLocal::OSPathToRelativePath
843 takes a full OS path, as might be found in data from a media creation
844 program, and converts it to a relativePath by stripping off directories
846 Returns false if the osPath tree doesn't match any of the existing
851 const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) {
852 static char relativePath[MAX_STRING_CHARS];
855 // skip a drive letter?
857 // search for anything with "base" in it
858 // Ase files from max may have the form of:
859 // "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga"
860 // which won't match any of our drive letter based search paths
861 bool ignoreWarning = false;
863 base = strstr( OSPath, BASE_GAMEDIR );
864 idStr tempStr = OSPath;
866 if ( ( strstr( tempStr, "//" ) || strstr( tempStr, "w:" ) ) && strstr( tempStr, "/doom/base/") ) {
867 // will cause a warning but will load the file. ase models have
868 // hard coded doom/base/ in the material names
869 base = strstr( OSPath, "base" );
870 ignoreWarning = true;
873 // look for the first complete directory name
874 base = (char *)strstr( OSPath, BASE_GAMEDIR );
877 if ( base > OSPath ) {
880 c2 = *( base + strlen( BASE_GAMEDIR ) );
881 if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
884 base = strstr( base + 1, BASE_GAMEDIR );
887 // fs_game and fs_game_base support - look for first complete name with a mod path
888 // ( fs_game searched before fs_game_base )
889 const char *fsgame = NULL;
891 for ( igame = 0; igame < 2; igame++ ) {
893 fsgame = fs_game.GetString();
894 } else if ( igame == 1 ) {
895 fsgame = fs_game_base.GetString();
897 if ( base == NULL && fsgame && strlen( fsgame ) ) {
898 base = (char *)strstr( OSPath, fsgame );
901 if ( base > OSPath ) {
904 c2 = *( base + strlen( fsgame ) );
905 if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
908 base = strstr( base + 1, fsgame );
914 s = strstr( base, "/" );
916 s = strstr( base, "\\" );
919 strcpy( relativePath, s + 1 );
920 if ( fs_debug.GetInteger() > 1 ) {
921 common->Printf( "idFileSystem::OSPathToRelativePath: %s becomes %s\n", OSPath, relativePath );
927 if ( !ignoreWarning ) {
928 common->Warning( "idFileSystem::OSPathToRelativePath failed on %s", OSPath );
930 strcpy( relativePath, "" );
935 =====================
936 idFileSystemLocal::RelativePathToOSPath
938 Returns a fully qualified path that can be used with stdio libraries
939 =====================
941 const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) {
942 const char *path = cvarSystem->GetCVarString( basePath );
944 path = fs_savepath.GetString();
946 return BuildOSPath( path, gameFolder, relativePath );
951 idFileSystemLocal::RemoveFile
954 void idFileSystemLocal::RemoveFile( const char *relativePath ) {
957 if ( fs_devpath.GetString()[0] ) {
958 OSPath = BuildOSPath( fs_devpath.GetString(), gameFolder, relativePath );
962 OSPath = BuildOSPath( fs_savepath.GetString(), gameFolder, relativePath );
970 idFileSystemLocal::FileIsInPAK
973 bool idFileSystemLocal::FileIsInPAK( const char *relativePath ) {
974 searchpath_t *search;
976 fileInPack_t *pakFile;
979 if ( !searchPaths ) {
980 common->FatalError( "Filesystem call made without initialization\n" );
983 if ( !relativePath ) {
984 common->FatalError( "idFileSystemLocal::FileIsInPAK: NULL 'relativePath' parameter passed\n" );
987 // qpaths are not supposed to have a leading slash
988 if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
992 // make absolutely sure that it can't back up the path.
993 // The searchpaths do guarantee that something will always
994 // be prepended, so we don't need to worry about "c:" or "//limbo"
995 if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
1000 // search through the path, one element at a time
1003 hash = HashFileName( relativePath );
1005 for ( search = searchPaths; search; search = search->next ) {
1006 // is the element a pak file?
1007 if ( search->pack && search->pack->hashTable[hash] ) {
1009 // disregard if it doesn't match one of the allowed pure pak files - or is a localization file
1010 if ( serverPaks.Num() ) {
1011 GetPackStatus( search->pack );
1012 if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
1013 continue; // not on the pure server pak list
1017 // look through all the pak file elements
1019 pakFile = pak->hashTable[hash];
1021 // case and separator insensitive comparisons
1022 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
1025 pakFile = pakFile->next;
1026 } while( pakFile != NULL );
1034 idFileSystemLocal::ReadFile
1036 Filename are relative to the search path
1037 a null buffer will just return the file length and time without loading
1038 timestamp can be NULL if not required
1041 int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ) {
1047 if ( !searchPaths ) {
1048 common->FatalError( "Filesystem call made without initialization\n" );
1051 if ( !relativePath || !relativePath[0] ) {
1052 common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" );
1056 *timestamp = FILE_NOT_FOUND_TIMESTAMP;
1063 buf = NULL; // quiet compiler warning
1065 // if this is a .cfg file and we are playing back a journal, read
1066 // it from the journal file
1067 if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) {
1069 if ( eventLoop && eventLoop->JournalLevel() == 2 ) {
1075 common->DPrintf( "Loading %s from journal file.\n", relativePath );
1077 r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) );
1078 if ( r != sizeof( len ) ) {
1082 buf = (byte *)Mem_ClearedAlloc(len+1);
1084 r = eventLoop->com_journalDataFile->Read( buf, len );
1086 common->FatalError( "Read from journalDataFile failed" );
1089 // guarantee that it will have a trailing 0 for string operations
1098 // look for it in the filesystem or pack files
1099 f = OpenFileRead( relativePath, ( buffer != NULL ) );
1109 *timestamp = f->Timestamp();
1120 buf = (byte *)Mem_ClearedAlloc(len+1);
1123 f->Read( buf, len );
1125 // guarantee that it will have a trailing 0 for string operations
1129 // if we are journalling and it is a config file, write it to the journal file
1130 if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) {
1131 common->DPrintf( "Writing %s to journal file.\n", relativePath );
1132 eventLoop->com_journalDataFile->Write( &len, sizeof( len ) );
1133 eventLoop->com_journalDataFile->Write( buf, len );
1134 eventLoop->com_journalDataFile->Flush();
1142 idFileSystemLocal::FreeFile
1145 void idFileSystemLocal::FreeFile( void *buffer ) {
1146 if ( !searchPaths ) {
1147 common->FatalError( "Filesystem call made without initialization\n" );
1150 common->FatalError( "idFileSystemLocal::FreeFile( NULL )" );
1159 idFileSystemLocal::WriteFile
1161 Filenames are relative to the search path
1164 int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) {
1167 if ( !searchPaths ) {
1168 common->FatalError( "Filesystem call made without initialization\n" );
1171 if ( !relativePath || !buffer ) {
1172 common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" );
1175 f = idFileSystemLocal::OpenFileWrite( relativePath, basePath );
1177 common->Printf( "Failed to open %s\n", relativePath );
1181 size = f->Write( buffer, size );
1190 idFileSystemLocal::ParseAddonDef
1193 addonInfo_t *idFileSystemLocal::ParseAddonDef( const char *buf, const int len ) {
1195 idToken token, token2;
1198 src.LoadMemory( buf, len, "<addon.conf>" );
1199 src.SetFlags( DECL_LEXER_FLAGS );
1200 if ( !src.SkipUntilString( "addonDef" ) ) {
1201 src.Warning( "ParseAddonDef: no addonDef" );
1204 if ( !src.ReadToken( &token ) ) {
1205 src.Warning( "Expected {" );
1208 info = new addonInfo_t;
1211 if ( !src.ReadToken( &token ) ) {
1215 if ( !token.Icmp( "}" ) ) {
1218 if ( token.type != TT_STRING ) {
1219 src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
1224 if ( sscanf( token.c_str(), "0x%x", &checksum ) != 1 && sscanf( token.c_str(), "%x", &checksum ) != 1 ) {
1225 src.Warning( "Could not parse checksum '%s'", token.c_str() );
1229 info->depends.Append( checksum );
1231 // read any number of mapDef entries
1233 if ( !src.SkipUntilString( "mapDef" ) ) {
1236 if ( !src.ReadToken( &token ) ) {
1237 src.Warning( "Expected map path" );
1238 info->mapDecls.DeleteContents( true );
1242 idDict *dict = new idDict;
1243 dict->Set( "path", token.c_str() );
1244 if ( !src.ReadToken( &token ) ) {
1245 src.Warning( "Expected {" );
1246 info->mapDecls.DeleteContents( true );
1252 if ( !src.ReadToken( &token ) ) {
1255 if ( !token.Icmp( "}" ) ) {
1258 if ( token.type != TT_STRING ) {
1259 src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
1260 info->mapDecls.DeleteContents( true );
1266 if ( !src.ReadToken( &token2 ) ) {
1267 src.Warning( "Unexpected end of file" );
1268 info->mapDecls.DeleteContents( true );
1274 if ( dict->FindKey( token ) ) {
1275 src.Warning( "'%s' already defined", token.c_str() );
1277 dict->Set( token, token2 );
1279 info->mapDecls.Append( dict );
1287 idFileSystemLocal::LoadZipFile
1290 pack_t *idFileSystemLocal::LoadZipFile( const char *zipfile ) {
1291 fileInPack_t * buildBuffer;
1296 char filename_inzip[MAX_ZIPPED_FILE_NAME];
1297 unz_file_info file_info;
1300 int fs_numHeaderLongs;
1301 int * fs_headerLongs;
1305 fileInPack_t *pakFile;
1307 f = OpenOSFile( zipfile, "rb" );
1311 fseek( f, 0, SEEK_END );
1315 fs_numHeaderLongs = 0;
1317 uf = unzOpen( zipfile );
1318 err = unzGetGlobalInfo( uf, &gi );
1320 if ( err != UNZ_OK ) {
1324 buildBuffer = new fileInPack_t[gi.number_entry];
1326 for( i = 0; i < FILE_HASH_SIZE; i++ ) {
1327 pack->hashTable[i] = NULL;
1330 pack->pakFilename = zipfile;
1332 pack->numfiles = gi.number_entry;
1333 pack->buildBuffer = buildBuffer;
1334 pack->referenced = false;
1335 pack->binary = BINARY_UNKNOWN;
1336 pack->addon = false;
1337 pack->addon_search = false;
1338 pack->addon_info = NULL;
1339 pack->pureStatus = PURE_UNKNOWN;
1340 pack->isNew = false;
1344 unzGoToFirstFile(uf);
1345 fs_headerLongs = (int *)Mem_ClearedAlloc( gi.number_entry * sizeof(int) );
1346 for ( i = 0; i < (int)gi.number_entry; i++ ) {
1347 err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0 );
1348 if ( err != UNZ_OK ) {
1351 if ( file_info.uncompressed_size > 0 ) {
1352 fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc );
1354 hash = HashFileName( filename_inzip );
1355 buildBuffer[i].name = filename_inzip;
1356 buildBuffer[i].name.ToLower();
1357 buildBuffer[i].name.BackSlashesToSlashes();
1358 // store the file position in the zip
1359 unzGetCurrentFileInfoPosition( uf, &buildBuffer[i].pos );
1360 // add the file to the hash
1361 buildBuffer[i].next = pack->hashTable[hash];
1362 pack->hashTable[hash] = &buildBuffer[i];
1363 // go to the next file in the zip
1364 unzGoToNextFile(uf);
1367 // check if this is an addon pak
1368 pack->addon = false;
1369 confHash = HashFileName( ADDON_CONFIG );
1370 for ( pakFile = pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
1371 if ( !FilenameCompare( pakFile->name, ADDON_CONFIG ) ) {
1373 idFile_InZip *file = ReadFileFromZip( pack, pakFile, ADDON_CONFIG );
1374 // may be just an empty file if you don't bother about the mapDef
1375 if ( file && file->Length() ) {
1377 buf = new char[ file->Length() + 1 ];
1378 file->Read( (void *)buf, file->Length() );
1379 buf[ file->Length() ] = '\0';
1380 pack->addon_info = ParseAddonDef( buf, file->Length() );
1390 pack->checksum = MD4_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
1391 pack->checksum = LittleLong( pack->checksum );
1393 Mem_Free( fs_headerLongs );
1400 idFileSystemLocal::AddZipFile
1401 adds a downloaded pak file to the list so we can work out what we have and what we still need
1402 the isNew flag is set to true, indicating that we cannot add this pak to the search lists without a restart
1405 int idFileSystemLocal::AddZipFile( const char *path ) {
1406 idStr fullpath = fs_savepath.GetString();
1408 searchpath_t *search, *last;
1410 fullpath.AppendPath( path );
1411 pak = LoadZipFile( fullpath );
1413 common->Warning( "AddZipFile %s failed\n", path );
1416 // insert the pak at the end of the search list - temporary until we restart
1418 search = new searchpath_t;
1421 search->next = NULL;
1423 while ( last->next ) {
1426 last->next = search;
1427 common->Printf( "Appended pk4 %s with checksum 0x%x\n", pak->pakFilename.c_str(), pak->checksum );
1428 return pak->checksum;
1433 idFileSystemLocal::AddUnique
1436 int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const {
1439 hashKey = hashIndex.GenerateKey( name );
1440 for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) {
1441 if ( list[i].Icmp( name ) == 0 ) {
1445 i = list.Append( name );
1446 hashIndex.Add( hashKey, i );
1452 idFileSystemLocal::GetExtensionList
1455 void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const {
1458 l = idStr::Length( extension );
1461 e = idStr::FindChar( extension, '|', s, l );
1463 extensionList.Append( idStr( extension, s, e ) );
1466 extensionList.Append( idStr( extension, s, l ) );
1474 idFileSystemLocal::GetFileList
1476 Does not clear the list first so this can be used to progressively build a file list.
1477 When 'sort' is true only the new files added to the list are sorted.
1480 int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir ) {
1481 searchpath_t * search;
1482 fileInPack_t * buildBuffer;
1490 if ( !searchPaths ) {
1491 common->FatalError( "Filesystem call made without initialization\n" );
1494 if ( !extensions.Num() ) {
1498 if ( !relativePath ) {
1501 pathLength = strlen( relativePath );
1503 pathLength++; // for the trailing '/'
1506 // search through the path, one element at a time, adding to list
1507 for( search = searchPaths; search != NULL; search = search->next ) {
1508 if ( search->dir ) {
1509 if(gamedir && strlen(gamedir)) {
1510 if(search->dir->gamedir != gamedir) {
1518 netpath = BuildOSPath( search->dir->path, search->dir->gamedir, relativePath );
1520 for ( i = 0; i < extensions.Num(); i++ ) {
1522 // scan for files in the filesystem
1523 ListOSFiles( netpath, extensions[i], sysFiles );
1525 // if we are searching for directories, remove . and ..
1526 if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) {
1527 sysFiles.Remove( "." );
1528 sysFiles.Remove( ".." );
1531 for( j = 0; j < sysFiles.Num(); j++ ) {
1533 if ( fullRelativePath ) {
1534 work = relativePath;
1536 work += sysFiles[j];
1537 AddUnique( work, list, hashIndex );
1540 AddUnique( sysFiles[j], list, hashIndex );
1544 } else if ( search->pack ) {
1545 // look through all the pak file elements
1547 // exclude any extra packs if we have server paks to search
1548 if ( serverPaks.Num() ) {
1549 GetPackStatus( search->pack );
1550 if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
1551 continue; // not on the pure server pak list
1556 buildBuffer = pak->buildBuffer;
1557 for( i = 0; i < pak->numfiles; i++ ) {
1559 length = buildBuffer[i].name.Length();
1561 // if the name is not long anough to at least contain the path
1562 if ( length <= pathLength ) {
1566 name = buildBuffer[i].name;
1569 // check for a path match without the trailing '/'
1570 if ( pathLength && idStr::Icmpn( name, relativePath, pathLength - 1 ) != 0 ) {
1574 // ensure we have a path, and not just a filename containing the path
1575 if ( name[ pathLength ] == '\0' || name[pathLength - 1] != '/' ) {
1579 // make sure the file is not in a subdirectory
1580 for ( j = pathLength; name[j+1] != '\0'; j++ ) {
1581 if ( name[j] == '/' ) {
1589 // check for extension match
1590 for ( j = 0; j < extensions.Num(); j++ ) {
1591 if ( length >= extensions[j].Length() && extensions[j].Icmp( name + length - extensions[j].Length() ) == 0 ) {
1595 if ( j >= extensions.Num() ) {
1600 if ( fullRelativePath ) {
1601 work = relativePath;
1603 work += name + pathLength;
1604 work.StripTrailing( '/' );
1605 AddUnique( work, list, hashIndex );
1607 work = name + pathLength;
1608 work.StripTrailing( '/' );
1609 AddUnique( work, list, hashIndex );
1620 idFileSystemLocal::ListFiles
1623 idFileList *idFileSystemLocal::ListFiles( const char *relativePath, const char *extension, bool sort, bool fullRelativePath, const char* gamedir ) {
1624 idHashIndex hashIndex( 4096, 4096 );
1625 idStrList extensionList;
1627 idFileList *fileList = new idFileList;
1628 fileList->basePath = relativePath;
1630 GetExtensionList( extension, extensionList );
1632 GetFileList( relativePath, extensionList, fileList->list, hashIndex, fullRelativePath, gamedir );
1635 idStrListSortPaths( fileList->list );
1643 idFileSystemLocal::GetFileListTree
1646 int idFileSystemLocal::GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir ) {
1648 idStrList slash, folders( 128 );
1649 idHashIndex folderHashIndex( 1024, 128 );
1651 // recurse through the subdirectories
1652 slash.Append( "/" );
1653 GetFileList( relativePath, slash, folders, folderHashIndex, true, gamedir );
1654 for ( i = 0; i < folders.Num(); i++ ) {
1655 if ( folders[i][0] == '.' ) {
1658 if ( folders[i].Icmp( relativePath ) == 0 ){
1661 GetFileListTree( folders[i], extensions, list, hashIndex, gamedir );
1664 // list files in the current directory
1665 GetFileList( relativePath, extensions, list, hashIndex, true, gamedir );
1672 idFileSystemLocal::ListFilesTree
1675 idFileList *idFileSystemLocal::ListFilesTree( const char *relativePath, const char *extension, bool sort, const char* gamedir ) {
1676 idHashIndex hashIndex( 4096, 4096 );
1677 idStrList extensionList;
1679 idFileList *fileList = new idFileList();
1680 fileList->basePath = relativePath;
1681 fileList->list.SetGranularity( 4096 );
1683 GetExtensionList( extension, extensionList );
1685 GetFileListTree( relativePath, extensionList, fileList->list, hashIndex, gamedir );
1688 idStrListSortPaths( fileList->list );
1696 idFileSystemLocal::FreeFileList
1699 void idFileSystemLocal::FreeFileList( idFileList *fileList ) {
1705 idFileSystemLocal::ListMods
1708 idModList *idFileSystemLocal::ListMods( void ) {
1710 const int MAX_DESCRIPTION = 256;
1711 char desc[ MAX_DESCRIPTION ];
1716 idModList *list = new idModList;
1718 const char *search[ 4 ];
1721 search[0] = fs_savepath.GetString();
1722 search[1] = fs_devpath.GetString();
1723 search[2] = fs_basepath.GetString();
1724 search[3] = fs_cdpath.GetString();
1726 for ( isearch = 0; isearch < 4; isearch++ ) {
1731 // scan for directories
1732 ListOSFiles( search[ isearch ], "/", dirs );
1735 dirs.Remove( ".." );
1736 dirs.Remove( "base" );
1737 dirs.Remove( "pb" );
1739 // see if there are any pk4 files in each directory
1740 for( i = 0; i < dirs.Num(); i++ ) {
1741 idStr gamepath = BuildOSPath( search[ isearch ], dirs[ i ], "" );
1742 ListOSFiles( gamepath, ".pk4", pk4s );
1744 if ( !list->mods.Find( dirs[ i ] ) ) {
1745 // D3 1.3 #31, only list d3xp if the pak is present
1746 if ( dirs[ i ].Icmp( "d3xp" ) || HasD3XP() ) {
1747 list->mods.Append( dirs[ i ] );
1756 // read the descriptions for each mod - search all paths
1757 for ( i = 0; i < list->mods.Num(); i++ ) {
1759 for ( isearch = 0; isearch < 4; isearch++ ) {
1761 idStr descfile = BuildOSPath( search[ isearch ], list->mods[ i ], "description.txt" );
1762 FILE *f = OpenOSFile( descfile, "r" );
1764 if ( fgets( desc, MAX_DESCRIPTION, f ) ) {
1765 list->descriptions.Append( desc );
1769 common->DWarning( "Error reading %s", descfile.c_str() );
1776 if ( isearch == 4 ) {
1777 list->descriptions.Append( list->mods[ i ] );
1781 list->mods.Insert( "" );
1782 list->descriptions.Insert( "Doom 3" );
1784 assert( list->mods.Num() == list->descriptions.Num() );
1791 idFileSystemLocal::FreeModList
1794 void idFileSystemLocal::FreeModList( idModList *modList ) {
1803 bool idDEntry::Matches(const char *directory, const char *extension) const {
1804 if ( !idDEntry::directory.Icmp( directory ) && !idDEntry::extension.Icmp( extension ) ) {
1815 void idDEntry::Init( const char *directory, const char *extension, const idStrList &list ) {
1816 idDEntry::directory = directory;
1817 idDEntry::extension = extension;
1818 idStrList::operator=(list);
1826 void idDEntry::Clear( void ) {
1834 idFileSystemLocal::ListOSFiles
1836 call to the OS for a listing of files in an OS directory
1837 optionally, perform some caching of the entries
1840 int idFileSystemLocal::ListOSFiles( const char *directory, const char *extension, idStrList &list ) {
1847 if ( !fs_caseSensitiveOS.GetBool() ) {
1848 return Sys_ListFiles( directory, extension, list );
1852 i = dir_cache_index - 1;
1853 while( i >= dir_cache_index - dir_cache_count ) {
1854 j = (i+MAX_CACHED_DIRS) % MAX_CACHED_DIRS;
1855 if ( dir_cache[j].Matches( directory, extension ) ) {
1856 if ( fs_debug.GetInteger() ) {
1857 //common->Printf( "idFileSystemLocal::ListOSFiles: cache hit: %s\n", directory );
1859 list = dir_cache[j];
1865 if ( fs_debug.GetInteger() ) {
1866 //common->Printf( "idFileSystemLocal::ListOSFiles: cache miss: %s\n", directory );
1869 ret = Sys_ListFiles( directory, extension, list );
1876 dir_cache[dir_cache_index].Init( directory, extension, list );
1877 dir_cache_index = (++dir_cache_index) % MAX_CACHED_DIRS;
1878 if ( dir_cache_count < MAX_CACHED_DIRS ) {
1887 idFileSystemLocal::Dir_f
1890 void idFileSystemLocal::Dir_f( const idCmdArgs &args ) {
1893 idFileList *fileList;
1896 if ( args.Argc() < 2 || args.Argc() > 3 ) {
1897 common->Printf( "usage: dir <directory> [extension]\n" );
1901 if ( args.Argc() == 2 ) {
1902 relativePath = args.Argv( 1 );
1906 relativePath = args.Argv( 1 );
1907 extension = args.Argv( 2 );
1908 if ( extension[0] != '.' ) {
1909 common->Warning( "extension should have a leading dot" );
1912 relativePath.BackSlashesToSlashes();
1913 relativePath.StripTrailing( '/' );
1915 common->Printf( "Listing of %s/*%s\n", relativePath.c_str(), extension.c_str() );
1916 common->Printf( "---------------\n" );
1918 fileList = fileSystemLocal.ListFiles( relativePath, extension );
1920 for ( i = 0; i < fileList->GetNumFiles(); i++ ) {
1921 common->Printf( "%s\n", fileList->GetFile( i ) );
1923 common->Printf( "%d files\n", fileList->list.Num() );
1925 fileSystemLocal.FreeFileList( fileList );
1930 idFileSystemLocal::DirTree_f
1933 void idFileSystemLocal::DirTree_f( const idCmdArgs &args ) {
1936 idFileList *fileList;
1939 if ( args.Argc() < 2 || args.Argc() > 3 ) {
1940 common->Printf( "usage: dirtree <directory> [extension]\n" );
1944 if ( args.Argc() == 2 ) {
1945 relativePath = args.Argv( 1 );
1949 relativePath = args.Argv( 1 );
1950 extension = args.Argv( 2 );
1951 if ( extension[0] != '.' ) {
1952 common->Warning( "extension should have a leading dot" );
1955 relativePath.BackSlashesToSlashes();
1956 relativePath.StripTrailing( '/' );
1958 common->Printf( "Listing of %s/*%s /s\n", relativePath.c_str(), extension.c_str() );
1959 common->Printf( "---------------\n" );
1961 fileList = fileSystemLocal.ListFilesTree( relativePath, extension );
1963 for ( i = 0; i < fileList->GetNumFiles(); i++ ) {
1964 common->Printf( "%s\n", fileList->GetFile( i ) );
1966 common->Printf( "%d files\n", fileList->list.Num() );
1968 fileSystemLocal.FreeFileList( fileList );
1973 idFileSystemLocal::Path_f
1976 void idFileSystemLocal::Path_f( const idCmdArgs &args ) {
1981 common->Printf( "Current search path:\n" );
1982 for ( sp = fileSystemLocal.searchPaths; sp; sp = sp->next ) {
1984 if ( com_developer.GetBool() ) {
1985 sprintf( status, "%s (%i files - 0x%x %s", sp->pack->pakFilename.c_str(), sp->pack->numfiles, sp->pack->checksum, sp->pack->referenced ? "referenced" : "not referenced" );
1986 if ( sp->pack->addon ) {
1987 status += " - addon)\n";
1991 common->Printf( status.c_str() );
1993 common->Printf( "%s (%i files)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles );
1995 if ( fileSystemLocal.serverPaks.Num() ) {
1996 if ( fileSystemLocal.serverPaks.Find( sp->pack ) ) {
1997 common->Printf( " on the pure list\n" );
1999 common->Printf( " not on the pure list\n" );
2003 common->Printf( "%s/%s\n", sp->dir->path.c_str(), sp->dir->gamedir.c_str() );
2006 common->Printf( "game DLL: 0x%x in pak: 0x%x\n", fileSystemLocal.gameDLLChecksum, fileSystemLocal.gamePakChecksum );
2008 common->Printf( "Note: ID_FAKE_PURE is enabled\n" );
2010 for( i = 0; i < MAX_GAME_OS; i++ ) {
2011 if ( fileSystemLocal.gamePakForOS[ i ] ) {
2012 common->Printf( "OS %d - pak 0x%x\n", i, fileSystemLocal.gamePakForOS[ i ] );
2015 // show addon packs that are *not* in the search lists
2016 common->Printf( "Addon pk4s:\n" );
2017 for ( sp = fileSystemLocal.addonPaks; sp; sp = sp->next ) {
2018 if ( com_developer.GetBool() ) {
2019 common->Printf( "%s (%i files - 0x%x)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles, sp->pack->checksum );
2021 common->Printf( "%s (%i files)\n", sp->pack->pakFilename.c_str(), sp->pack->numfiles );
2028 idFileSystemLocal::GetOSMask
2031 int idFileSystemLocal::GetOSMask( void ) {
2033 for( i = 0; i < MAX_GAME_OS; i++ ) {
2034 if ( fileSystemLocal.gamePakForOS[ i ] ) {
2046 idFileSystemLocal::TouchFile_f
2048 The only purpose of this function is to allow game script files to copy
2049 arbitrary files furing an "fs_copyfiles 1" run.
2052 void idFileSystemLocal::TouchFile_f( const idCmdArgs &args ) {
2055 if ( args.Argc() != 2 ) {
2056 common->Printf( "Usage: touchFile <file>\n" );
2060 f = fileSystemLocal.OpenFileRead( args.Argv( 1 ) );
2062 fileSystemLocal.CloseFile( f );
2068 idFileSystemLocal::TouchFileList_f
2070 Takes a text file and touches every file in it, use one file per line.
2073 void idFileSystemLocal::TouchFileList_f( const idCmdArgs &args ) {
2075 if ( args.Argc() != 2 ) {
2076 common->Printf( "Usage: touchFileList <filename>\n" );
2080 const char *buffer = NULL;
2081 idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT );
2082 if ( fileSystem->ReadFile( args.Argv( 1 ), ( void** )&buffer, NULL ) && buffer ) {
2083 src.LoadMemory( buffer, strlen( buffer ), args.Argv( 1 ) );
2084 if ( src.IsLoaded() ) {
2086 while( src.ReadToken( &token ) ) {
2087 common->Printf( "%s\n", token.c_str() );
2088 session->UpdateScreen();
2089 idFile *f = fileSystemLocal.OpenFileRead( token );
2091 fileSystemLocal.CloseFile( f );
2102 idFileSystemLocal::AddGameDirectory
2104 Sets gameFolder, adds the directory to the head of the search paths, then loads any pk4 files.
2107 void idFileSystemLocal::AddGameDirectory( const char *path, const char *dir ) {
2109 searchpath_t * search;
2114 // check if the search path already exists
2115 for ( search = searchPaths; search; search = search->next ) {
2116 // if this element is a pak file
2117 if ( !search->dir ) {
2120 if ( search->dir->path.Cmp( path ) == 0 && search->dir->gamedir.Cmp( dir ) == 0 ) {
2128 // add the directory to the search path
2130 search = new searchpath_t;
2131 search->dir = new directory_t;
2132 search->pack = NULL;
2134 search->dir->path = path;
2135 search->dir->gamedir = dir;
2136 search->next = searchPaths;
2137 searchPaths = search;
2139 // find all pak files in this directory
2140 pakfile = BuildOSPath( path, dir, "" );
2141 pakfile[ pakfile.Length() - 1 ] = 0; // strip the trailing slash
2143 ListOSFiles( pakfile, ".pk4", pakfiles );
2145 // sort them so that later alphabetic matches override
2146 // earlier ones. This makes pak1.pk4 override pak0.pk4
2149 for ( i = 0; i < pakfiles.Num(); i++ ) {
2150 pakfile = BuildOSPath( path, dir, pakfiles[i] );
2151 pak = LoadZipFile( pakfile );
2155 // insert the pak after the directory it comes from
2156 search = new searchpath_t;
2159 search->next = searchPaths->next;
2160 searchPaths->next = search;
2161 common->Printf( "Loaded pk4 %s with checksum 0x%x\n", pakfile.c_str(), pak->checksum );
2167 idFileSystemLocal::SetupGameDirectories
2169 Takes care of the correct search order.
2172 void idFileSystemLocal::SetupGameDirectories( const char *gameName ) {
2174 if ( fs_cdpath.GetString()[0] ) {
2175 AddGameDirectory( fs_cdpath.GetString(), gameName );
2179 if ( fs_basepath.GetString()[0] ) {
2180 AddGameDirectory( fs_basepath.GetString(), gameName );
2184 if ( fs_devpath.GetString()[0] ) {
2185 AddGameDirectory( fs_devpath.GetString(), gameName );
2189 if ( fs_savepath.GetString()[0] ) {
2190 AddGameDirectory( fs_savepath.GetString(), gameName );
2196 idFileSystemLocal::FollowDependencies
2199 void idFileSystemLocal::FollowAddonDependencies( pack_t *pak ) {
2201 if ( !pak->addon_info || !pak->addon_info->depends.Num() ) {
2204 int i, num = pak->addon_info->depends.Num();
2205 for ( i = 0; i < num; i++ ) {
2206 pack_t *deppak = GetPackForChecksum( pak->addon_info->depends[ i ], true );
2208 // make sure it hasn't been marked for search already
2209 if ( !deppak->addon_search ) {
2210 // must clean addonChecksums as we go
2211 int addon_index = addonChecksums.FindIndex( deppak->checksum );
2212 if ( addon_index >= 0 ) {
2213 addonChecksums.RemoveIndex( addon_index );
2215 deppak->addon_search = true;
2216 common->Printf( "Addon pk4 %s 0x%x depends on pak %s 0x%x, will be searched\n",
2217 pak->pakFilename.c_str(), pak->checksum,
2218 deppak->pakFilename.c_str(), deppak->checksum );
2219 FollowAddonDependencies( deppak );
2222 common->Printf( "Addon pk4 %s 0x%x depends on unknown pak 0x%x\n",
2223 pak->pakFilename.c_str(), pak->checksum, pak->addon_info->depends[ i ] );
2230 idFileSystemLocal::Startup
2233 void idFileSystemLocal::Startup( void ) {
2234 searchpath_t **search;
2239 common->Printf( "------ Initializing File System ------\n" );
2241 if ( restartChecksums.Num() ) {
2242 common->Printf( "restarting in pure mode with %d pak files\n", restartChecksums.Num() );
2244 if ( addonChecksums.Num() ) {
2245 common->Printf( "restarting filesystem with %d addon pak file(s) to include\n", addonChecksums.Num() );
2248 SetupGameDirectories( BASE_GAMEDIR );
2250 // fs_game_base override
2251 if ( fs_game_base.GetString()[0] &&
2252 idStr::Icmp( fs_game_base.GetString(), BASE_GAMEDIR ) ) {
2253 SetupGameDirectories( fs_game_base.GetString() );
2257 if ( fs_game.GetString()[0] &&
2258 idStr::Icmp( fs_game.GetString(), BASE_GAMEDIR ) &&
2259 idStr::Icmp( fs_game.GetString(), fs_game_base.GetString() ) ) {
2260 SetupGameDirectories( fs_game.GetString() );
2263 // currently all addons are in the search list - deal with filtering out and dependencies now
2264 // scan through and deal with dependencies
2265 search = &searchPaths;
2267 if ( !( *search )->pack || !( *search )->pack->addon ) {
2268 search = &( ( *search )->next );
2271 pak = ( *search )->pack;
2272 if ( fs_searchAddons.GetBool() ) {
2273 // when we have fs_searchAddons on we should never have addonChecksums
2274 assert( !addonChecksums.Num() );
2275 pak->addon_search = true;
2276 search = &( ( *search )->next );
2279 addon_index = addonChecksums.FindIndex( pak->checksum );
2280 if ( addon_index >= 0 ) {
2281 assert( !pak->addon_search ); // any pak getting flagged as addon_search should also have been removed from addonChecksums already
2282 pak->addon_search = true;
2283 addonChecksums.RemoveIndex( addon_index );
2284 FollowAddonDependencies( pak );
2286 search = &( ( *search )->next );
2289 // now scan to filter out addons not marked addon_search
2290 search = &searchPaths;
2292 if ( !( *search )->pack || !( *search )->pack->addon ) {
2293 search = &( ( *search )->next );
2296 assert( !( *search )->dir );
2297 pak = ( *search )->pack;
2298 if ( pak->addon_search ) {
2299 common->Printf( "Addon pk4 %s with checksum 0x%x is on the search list\n",
2300 pak->pakFilename.c_str(), pak->checksum );
2301 search = &( ( *search )->next );
2303 // remove from search list, put in addons list
2304 searchpath_t *paksearch = *search;
2305 *search = ( *search )->next;
2306 paksearch->next = addonPaks;
2307 addonPaks = paksearch;
2308 common->Printf( "Addon pk4 %s with checksum 0x%x is on addon list\n",
2309 pak->pakFilename.c_str(), pak->checksum );
2313 // all addon paks found and accounted for
2314 assert( !addonChecksums.Num() );
2315 addonChecksums.Clear(); // just in case
2317 if ( restartChecksums.Num() ) {
2318 search = &searchPaths;
2320 if ( !( *search )->pack ) {
2321 search = &( ( *search )->next );
2324 if ( ( i = restartChecksums.FindIndex( ( *search )->pack->checksum ) ) != -1 ) {
2326 // this pak is the next one in the pure search order
2327 serverPaks.Append( ( *search )->pack );
2328 restartChecksums.RemoveIndex( 0 );
2329 if ( !restartChecksums.Num() ) {
2330 break; // early out, we're done
2332 search = &( ( *search )->next );
2335 // this pak will be on the pure list, but order is not right yet
2337 aux = ( *search )->next;
2339 // last of the list can't be swapped back
2340 if ( fs_debug.GetBool() ) {
2341 common->Printf( "found pure checksum %x at index %d, but the end of search path is reached\n", ( *search )->pack->checksum, i );
2344 for ( i = 0; i < serverPaks.Num(); i++ ) {
2345 checks += va( "%p ", serverPaks[ i ] );
2347 common->Printf( "%d pure paks - %s \n", serverPaks.Num(), checks.c_str() );
2349 for ( i = 0; i < restartChecksums.Num(); i++ ) {
2350 checks += va( "%x ", restartChecksums[ i ] );
2352 common->Printf( "%d paks left - %s\n", restartChecksums.Num(), checks.c_str() );
2354 common->FatalError( "Failed to restart with pure mode restrictions for server connect" );
2356 // put this search path at the end of the list
2357 searchpath_t *search_end;
2358 search_end = ( *search )->next;
2359 while ( search_end->next ) {
2360 search_end = search_end->next;
2362 search_end->next = *search;
2363 *search = ( *search )->next;
2364 search_end->next->next = NULL;
2368 // this pak is not on the pure list
2369 search = &( ( *search )->next );
2371 // the list must be empty
2372 if ( restartChecksums.Num() ) {
2373 if ( fs_debug.GetBool() ) {
2376 for ( i = 0; i < serverPaks.Num(); i++ ) {
2377 checks += va( "%p ", serverPaks[ i ] );
2379 common->Printf( "%d pure paks - %s \n", serverPaks.Num(), checks.c_str() );
2381 for ( i = 0; i < restartChecksums.Num(); i++ ) {
2382 checks += va( "%x ", restartChecksums[ i ] );
2384 common->Printf( "%d paks left - %s\n", restartChecksums.Num(), checks.c_str() );
2386 common->FatalError( "Failed to restart with pure mode restrictions for server connect" );
2388 // also the game pak checksum
2389 // we could check if the game pak is actually present, but we would not be restarting if there wasn't one @ first pure check
2390 gamePakChecksum = restartGamePakChecksum;
2394 cmdSystem->AddCommand( "dir", Dir_f, CMD_FL_SYSTEM, "lists a folder", idCmdSystem::ArgCompletion_FileName );
2395 cmdSystem->AddCommand( "dirtree", DirTree_f, CMD_FL_SYSTEM, "lists a folder with subfolders" );
2396 cmdSystem->AddCommand( "path", Path_f, CMD_FL_SYSTEM, "lists search paths" );
2397 cmdSystem->AddCommand( "touchFile", TouchFile_f, CMD_FL_SYSTEM, "touches a file" );
2398 cmdSystem->AddCommand( "touchFileList", TouchFileList_f, CMD_FL_SYSTEM, "touches a list of files" );
2400 // print the current search paths
2401 Path_f( idCmdArgs() );
2403 common->Printf( "file system initialized.\n" );
2404 common->Printf( "--------------------------------------\n" );
2409 idFileSystemLocal::SetRestrictions
2411 Looks for product keys and restricts media add on ability
2412 if the full version is not found
2415 void idFileSystemLocal::SetRestrictions( void ) {
2416 #ifdef ID_DEMO_BUILD
2417 common->Printf( "\nRunning in restricted demo mode.\n\n" );
2418 // make sure that the pak file has the header checksum we expect
2419 searchpath_t *search;
2420 for ( search = searchPaths; search; search = search->next ) {
2421 if ( search->pack ) {
2422 // a tiny attempt to keep the checksum from being scannable from the exe
2423 if ( ( search->pack->checksum ^ 0x84268436u ) != ( DEMO_PAK_CHECKSUM ^ 0x84268436u ) ) {
2424 common->FatalError( "Corrupted %s: 0x%x", search->pack->pakFilename.c_str(), search->pack->checksum );
2428 cvarSystem->SetCVarBool( "fs_restrict", true );
2433 =====================
2434 idFileSystemLocal::UpdatePureServerChecksums
2435 =====================
2437 void idFileSystemLocal::UpdatePureServerChecksums( void ) {
2438 searchpath_t *search;
2440 pureStatus_t status;
2443 for ( search = searchPaths; search; search = search->next ) {
2444 // is the element a referenced pak file?
2445 if ( !search->pack ) {
2448 status = GetPackStatus( search->pack );
2449 if ( status == PURE_NEVER ) {
2452 if ( status == PURE_NEUTRAL && !search->pack->referenced ) {
2455 serverPaks.Append( search->pack );
2456 if ( serverPaks.Num() >= MAX_PURE_PAKS ) {
2457 common->FatalError( "MAX_PURE_PAKS ( %d ) exceeded\n", MAX_PURE_PAKS );
2460 if ( fs_debug.GetBool() ) {
2462 for ( i = 0; i < serverPaks.Num(); i++ ) {
2463 checks += va( "%x ", serverPaks[ i ]->checksum );
2465 common->Printf( "set pure list - %d paks ( %s)\n", serverPaks.Num(), checks.c_str() );
2470 =====================
2471 idFileSystemLocal::UpdateGamePakChecksums
2472 =====================
2474 bool idFileSystemLocal::UpdateGamePakChecksums( void ) {
2475 searchpath_t *search;
2476 fileInPack_t *pakFile;
2484 confHash = HashFileName( BINARY_CONFIG );
2486 memset( gamePakForOS, 0, sizeof( gamePakForOS ) );
2487 for ( search = searchPaths; search; search = search->next ) {
2488 if ( !search->pack ) {
2491 search->pack->binary = BINARY_NO;
2492 for ( pakFile = search->pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
2493 if ( !FilenameCompare( pakFile->name, BINARY_CONFIG ) ) {
2494 search->pack->binary = BINARY_YES;
2495 confFile = ReadFileFromZip( search->pack, pakFile, BINARY_CONFIG );
2496 buf = new char[ confFile->Length() + 1 ];
2497 confFile->Read( (void *)buf, confFile->Length() );
2498 buf[ confFile->Length() ] = '\0';
2499 lexConf = new idLexer( buf, confFile->Length(), confFile->GetFullPath() );
2500 while ( lexConf->ReadToken( &token ) ) {
2501 if ( token.IsNumeric() ) {
2503 if ( id < MAX_GAME_OS && !gamePakForOS[ id ] ) {
2504 if ( fs_debug.GetBool() ) {
2505 common->Printf( "Adding game pak checksum for OS %d: %s 0x%x\n", id, confFile->GetFullPath(), search->pack->checksum );
2507 gamePakForOS[ id ] = search->pack->checksum;
2511 CloseFile( confFile );
2518 // some sanity checks on the game code references
2519 // make sure that at least the local OS got a pure reference
2520 if ( !gamePakForOS[ BUILD_OS_ID ] ) {
2521 common->Warning( "No game code pak reference found for the local OS" );
2525 if ( !cvarSystem->GetCVarBool( "net_serverAllowServerMod" ) &&
2526 gamePakChecksum != gamePakForOS[ BUILD_OS_ID ] ) {
2527 common->Warning( "The current game code doesn't match pak files (net_serverAllowServerMod is off)" );
2535 =====================
2536 idFileSystemLocal::GetPackForChecksum
2537 =====================
2539 pack_t* idFileSystemLocal::GetPackForChecksum( int checksum, bool searchAddons ) {
2540 searchpath_t *search;
2541 for ( search = searchPaths; search; search = search->next ) {
2542 if ( !search->pack ) {
2545 if ( search->pack->checksum == checksum ) {
2546 return search->pack;
2549 if ( searchAddons ) {
2550 for ( search = addonPaks; search; search = search->next ) {
2551 assert( search->pack && search->pack->addon );
2552 if ( search->pack->checksum == checksum ) {
2553 return search->pack;
2562 idFileSystemLocal::ValidateDownloadPakForChecksum
2565 int idFileSystemLocal::ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary ) {
2571 pack_t *pak = GetPackForChecksum( checksum );
2577 // validate this pak for a potential download
2578 // ignore pak*.pk4 for download. those are reserved to distribution and cannot be downloaded
2579 name = pak->pakFilename;
2581 if ( strstr( name.c_str(), "pak" ) == name.c_str() ) {
2582 common->DPrintf( "%s is not a donwloadable pak\n", pak->pakFilename.c_str() );
2586 // a pure server sets the binary flag when starting the game
2587 assert( pak->binary != BINARY_UNKNOWN );
2588 pakBinary = ( pak->binary == BINARY_YES ) ? true : false;
2589 if ( isBinary != pakBinary ) {
2590 common->DPrintf( "%s binary flag mismatch\n", pak->pakFilename.c_str() );
2594 // extract a path that includes the fs_game: != OSPathToRelativePath
2595 testList.Append( fs_savepath.GetString() );
2596 testList.Append( fs_devpath.GetString() );
2597 testList.Append( fs_basepath.GetString() );
2598 testList.Append( fs_cdpath.GetString() );
2599 for ( i = 0; i < testList.Num(); i ++ ) {
2600 if ( testList[ i ].Length() && !testList[ i ].Icmpn( pak->pakFilename, testList[ i ].Length() ) ) {
2601 relativePath = pak->pakFilename.c_str() + testList[ i ].Length() + 1;
2605 if ( i == testList.Num() ) {
2606 common->Warning( "idFileSystem::ValidateDownloadPak: failed to extract relative path for %s", pak->pakFilename.c_str() );
2609 idStr::Copynz( path, relativePath, MAX_STRING_CHARS );
2614 =====================
2615 idFileSystemLocal::ClearPureChecksums
2616 =====================
2618 void idFileSystemLocal::ClearPureChecksums( void ) {
2619 common->DPrintf( "Cleared pure server lock\n" );
2624 =====================
2625 idFileSystemLocal::SetPureServerChecksums
2626 set the pure paks according to what the server asks
2627 if that's not possible, identify why and build an answer
2629 loadedFileFromDir - some files were loaded from directories instead of paks (a restart in pure pak-only is required)
2630 missing/wrong checksums - some pak files would need to be installed/updated (downloaded for instance)
2631 some pak files currently referenced are not referenced by the server
2632 wrong order - if the pak order doesn't match, means some stuff could have been loaded from somewhere else
2633 server referenced files are prepended to the list if possible ( that doesn't break pureness )
2635 the checksum of the pak containing the DLL is maintained seperately, the server can send different replies by OS
2636 =====================
2638 fsPureReply_t idFileSystemLocal::SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int _gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum ) {
2641 bool success = true;
2642 bool canPrepend = true;
2643 char dllName[MAX_OSPATH];
2645 fileInPack_t * pakFile;
2647 sys->DLL_GetFileName( "game", dllName, MAX_OSPATH );
2648 dllHash = HashFileName( dllName );
2651 missingChecksums[ 0 ] = 0;
2652 assert( missingGamePakChecksum );
2653 *missingGamePakChecksum = 0;
2655 if ( pureChecksums[ 0 ] == 0 ) {
2656 ClearPureChecksums();
2660 if ( !serverPaks.Num() ) {
2661 // there was no pure lockdown yet - lock to what we already have
2662 UpdatePureServerChecksums();
2665 while ( pureChecksums[ i ] ) {
2666 if ( j < serverPaks.Num() && serverPaks[ j ]->checksum == pureChecksums[ i ] ) {
2667 canPrepend = false; // once you start matching into the list there is no prepending anymore
2668 i++; j++; // the pak is matched, is in the right order, continue..
2670 pack = GetPackForChecksum( pureChecksums[ i ], true );
2671 if ( pack && pack->addon && !pack->addon_search ) {
2672 // this is an addon pack, and it's not on our current search list
2673 // setting success to false meaning that a restart including this addon is required
2674 if ( fs_debug.GetBool() ) {
2675 common->Printf( "pak %s checksumed 0x%x is on addon list. Restart required.\n", pack->pakFilename.c_str(), pack->checksum );
2679 if ( pack && pack->isNew ) {
2680 // that's a downloaded pack, we will need to restart
2681 if ( fs_debug.GetBool() ) {
2682 common->Printf( "pak %s checksumed 0x%x is a newly downloaded file. Restart required.\n", pack->pakFilename.c_str(), pack->checksum );
2688 // we still have a chance
2689 if ( fs_debug.GetBool() ) {
2690 common->Printf( "prepend pak %s checksumed 0x%x at index %d\n", pack->pakFilename.c_str(), pack->checksum, j );
2692 // NOTE: there is a light possibility this adds at the end of the list if UpdatePureServerChecksums didn't set anything
2693 serverPaks.Insert( pack, j );
2694 i++; j++; // continue..
2697 if ( fs_debug.GetBool() ) {
2698 // verbose the situation
2699 if ( serverPaks.Find( pack ) ) {
2700 common->Printf( "pak %s checksumed 0x%x is in the pure list at wrong index. Current index is %d, found at %d\n", pack->pakFilename.c_str(), pack->checksum, j, serverPaks.FindIndex( pack ) );
2702 common->Printf( "pak %s checksumed 0x%x can't be added to pure list because of search order\n", pack->pakFilename.c_str(), pack->checksum );
2705 i++; // advance server checksums only
2708 // didn't find a matching checksum
2710 missingChecksums[ imissing++ ] = pureChecksums[ i ];
2711 missingChecksums[ imissing ] = 0;
2712 if ( fs_debug.GetBool() ) {
2713 common->Printf( "checksum not found - 0x%x\n", pureChecksums[ i ] );
2715 i++; // advance the server checksums only
2719 while ( j < serverPaks.Num() ) {
2720 success = false; // just in case some extra pak files are referenced at the end of our local list
2721 if ( fs_debug.GetBool() ) {
2722 common->Printf( "pak %s checksumed 0x%x is an extra reference at the end of local pure list\n", serverPaks[ j ]->pakFilename.c_str(), serverPaks[ j ]->checksum );
2728 if ( !_gamePakChecksum ) {
2729 // server doesn't have knowledge of code we can use ( OS issue )
2732 assert( gameDLLChecksum );
2734 gamePakChecksum = _gamePakChecksum;
2736 if ( _gamePakChecksum != gamePakChecksum ) {
2737 // current DLL is wrong, search for a pak with the approriate checksum
2738 // ( search all paks, the pure list is not relevant here )
2739 pack = GetPackForChecksum( _gamePakChecksum );
2741 if ( fs_debug.GetBool() ) {
2742 common->Printf( "missing the game code pak ( 0x%x )\n", _gamePakChecksum );
2744 // if there are other paks missing they have also been marked above
2745 *missingGamePakChecksum = _gamePakChecksum;
2746 return PURE_MISSING;
2748 // if assets paks are missing, don't try any of the DLL restart / NODLL
2750 return PURE_MISSING;
2752 // we have a matching pak
2753 if ( fs_debug.GetBool() ) {
2754 common->Printf( "server's game code pak candidate is '%s' ( 0x%x )\n", pack->pakFilename.c_str(), pack->checksum );
2756 // make sure there is a valid DLL for us
2757 if ( pack->hashTable[ dllHash ] ) {
2758 for ( pakFile = pack->hashTable[ dllHash ]; pakFile; pakFile = pakFile->next ) {
2759 if ( !FilenameCompare( pakFile->name, dllName ) ) {
2760 gamePakChecksum = _gamePakChecksum; // this will be used to extract the DLL in pure mode FindDLL
2761 return PURE_RESTART;
2765 common->Warning( "media is misconfigured. server claims pak '%s' ( 0x%x ) has media for us, but '%s' is not found\n", pack->pakFilename.c_str(), pack->checksum, dllName );
2769 // we reply to missing after DLL check so it can be part of the list
2771 return PURE_MISSING;
2775 if ( loadedFileFromDir ) {
2777 if ( fs_debug.GetBool() ) {
2778 common->Printf( "SetPureServerChecksums: there are files loaded from dir\n" );
2781 return ( success ? PURE_OK : PURE_RESTART );
2785 =====================
2786 idFileSystemLocal::GetPureServerChecksums
2787 =====================
2789 void idFileSystemLocal::GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *_gamePakChecksum ) {
2792 for ( i = 0; i < serverPaks.Num(); i++ ) {
2793 checksums[ i ] = serverPaks[ i ]->checksum;
2796 if ( _gamePakChecksum ) {
2798 *_gamePakChecksum = gamePakForOS[ OS ];
2800 *_gamePakChecksum = gamePakChecksum;
2806 =====================
2807 idFileSystemLocal::SetRestartChecksums
2808 =====================
2810 void idFileSystemLocal::SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum ) {
2814 restartChecksums.Clear();
2816 while ( pureChecksums[ i ] ) {
2817 pack = GetPackForChecksum( pureChecksums[ i ], true );
2819 common->FatalError( "SetRestartChecksums failed: no pak for checksum 0x%x\n", pureChecksums[i] );
2821 if ( pack->addon && addonChecksums.FindIndex( pack->checksum ) < 0 ) {
2822 // can't mark it pure if we're not even gonna search it :-)
2823 addonChecksums.Append( pack->checksum );
2825 restartChecksums.Append( pureChecksums[ i ] );
2828 restartGamePakChecksum = gamePakChecksum;
2833 idFileSystemLocal::Init
2835 Called only at inital startup, not when the filesystem
2836 is resetting due to a game change
2839 void idFileSystemLocal::Init( void ) {
2840 // allow command line parms to override our defaults
2841 // we have to specially handle this, because normal command
2842 // line variable sets don't happen until after the filesystem
2843 // has already been initialized
2844 common->StartupVariable( "fs_basepath", false );
2845 common->StartupVariable( "fs_savepath", false );
2846 common->StartupVariable( "fs_cdpath", false );
2847 common->StartupVariable( "fs_devpath", false );
2848 common->StartupVariable( "fs_game", false );
2849 common->StartupVariable( "fs_game_base", false );
2850 common->StartupVariable( "fs_copyfiles", false );
2851 common->StartupVariable( "fs_restrict", false );
2852 common->StartupVariable( "fs_searchAddons", false );
2855 if ( fs_game.GetString()[0] && !idStr::Icmp( fs_game.GetString(), "d3xp" ) ) {
2856 fs_game.SetString( NULL );
2858 if ( fs_game_base.GetString()[0] && !idStr::Icmp( fs_game_base.GetString(), "d3xp" ) ) {
2859 fs_game_base.SetString( NULL );
2863 if ( fs_basepath.GetString()[0] == '\0' ) {
2864 fs_basepath.SetString( Sys_DefaultBasePath() );
2866 if ( fs_savepath.GetString()[0] == '\0' ) {
2867 fs_savepath.SetString( Sys_DefaultSavePath() );
2869 if ( fs_cdpath.GetString()[0] == '\0' ) {
2870 fs_cdpath.SetString( Sys_DefaultCDPath() );
2873 if ( fs_devpath.GetString()[0] == '\0' ) {
2875 fs_devpath.SetString( fs_cdpath.GetString()[0] ? fs_cdpath.GetString() : fs_basepath.GetString() );
2877 fs_devpath.SetString( fs_savepath.GetString() );
2881 // try to start up normally
2884 // see if we are going to allow add-ons
2887 // spawn a thread to handle background file reads
2888 StartBackgroundDownloadThread();
2890 // if we can't find default.cfg, assume that the paths are
2891 // busted and error out now, rather than getting an unreadable
2892 // graphics screen when the font fails to load
2893 // Dedicated servers can run with no outside files at all
2894 if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) {
2895 common->FatalError( "Couldn't load default.cfg" );
2901 idFileSystemLocal::Restart
2904 void idFileSystemLocal::Restart( void ) {
2905 // free anything we currently have loaded
2910 // see if we are going to allow add-ons
2913 // if we can't find default.cfg, assume that the paths are
2914 // busted and error out now, rather than getting an unreadable
2915 // graphics screen when the font fails to load
2916 if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) {
2917 common->FatalError( "Couldn't load default.cfg" );
2923 idFileSystemLocal::Shutdown
2925 Frees all resources and closes all files
2928 void idFileSystemLocal::Shutdown( bool reloading ) {
2929 searchpath_t *sp, *next, *loop;
2935 restartChecksums.Clear();
2936 addonChecksums.Clear();
2938 loadedFileFromDir = false;
2939 gameDLLChecksum = 0;
2940 gamePakChecksum = 0;
2944 // free everything - loop through searchPaths and addonPaks
2945 for ( loop = searchPaths; loop; loop == searchPaths ? loop = addonPaks : loop = NULL ) {
2946 for ( sp = loop; sp; sp = next ) {
2950 unzClose( sp->pack->handle );
2951 delete [] sp->pack->buildBuffer;
2952 if ( sp->pack->addon_info ) {
2953 sp->pack->addon_info->mapDecls.DeleteContents( true );
2954 delete sp->pack->addon_info;
2965 // any FS_ calls will now be an error until reinitialized
2969 cmdSystem->RemoveCommand( "path" );
2970 cmdSystem->RemoveCommand( "dir" );
2971 cmdSystem->RemoveCommand( "dirtree" );
2972 cmdSystem->RemoveCommand( "touchFile" );
2979 idFileSystemLocal::IsInitialized
2982 bool idFileSystemLocal::IsInitialized( void ) const {
2983 return ( searchPaths != NULL );
2988 =================================================================================
2992 =================================================================================
2997 idFileSystemLocal::FileAllowedFromDir
3000 bool idFileSystemLocal::FileAllowedFromDir( const char *path ) {
3005 if ( !strcmp( path + l - 4, ".cfg" ) // for config files
3006 || !strcmp( path + l - 4, ".dat" ) // for journal files
3007 || !strcmp( path + l - 4, ".dll" ) // dynamic modules are handled a different way for pure
3008 || !strcmp( path + l - 3, ".so" )
3009 || ( l > 6 && !strcmp( path + l - 6, ".dylib" ) )
3010 || ( l > 10 && !strcmp( path + l - 10, ".scriptcfg" ) ) // configuration script, such as map cycle
3011 #if ID_PURE_ALLOWDDS
3012 || !strcmp( path + l - 4, ".dds" )
3015 // note: cd and xp keys, as well as config.spec are opened through an explicit OS path and don't hit this
3019 if ( strstr( path, "savegames" ) == path &&
3020 ( !strcmp( path + l - 4, ".tga" ) || !strcmp( path + l -4, ".txt" ) || !strcmp( path + l - 5, ".save" ) ) ) {
3024 if ( strstr( path, "screenshots" ) == path && !strcmp( path + l - 4, ".tga" ) ) {
3028 if ( strstr( path, "maps/game" ) == path &&
3029 !strcmp( path + l - 4, ".tga" ) ) {
3032 // splash screens extracted from addons
3033 if ( strstr( path, "guis/assets/splash/addon" ) == path &&
3034 !strcmp( path + l -4, ".tga" ) ) {
3043 idFileSystemLocal::GetPackStatus
3046 pureStatus_t idFileSystemLocal::GetPackStatus( pack_t *pak ) {
3047 int i, l, hashindex;
3052 if ( pak->pureStatus != PURE_UNKNOWN ) {
3053 return pak->pureStatus;
3056 // check content for PURE_NEVER
3058 file = pak->buildBuffer;
3059 for ( hashindex = 0; hashindex < FILE_HASH_SIZE; hashindex++ ) {
3061 file = pak->hashTable[ hashindex ];
3064 l = file->name.Length();
3065 for ( int j = 0; pureExclusions[j].func != NULL; j++ ) {
3066 if ( pureExclusions[j].func( pureExclusions[j], l, file->name ) ) {
3072 common->DPrintf( "pak '%s' candidate for pure: '%s'\n", pak->pakFilename.c_str(), file->name.c_str() );
3082 if ( i == pak->numfiles ) {
3083 pak->pureStatus = PURE_NEVER;
3087 // check pak name for PURE_ALWAYS
3088 pak->pakFilename.ExtractFileName( name );
3089 if ( !name.IcmpPrefixPath( "pak" ) ) {
3090 pak->pureStatus = PURE_ALWAYS;
3094 pak->pureStatus = PURE_NEUTRAL;
3095 return PURE_NEUTRAL;
3100 idFileSystemLocal::ReadFileFromZip
3103 idFile_InZip * idFileSystemLocal::ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath ) {
3106 idFile_InZip *file = new idFile_InZip();
3108 // open a new file on the pakfile
3109 file->z = unzReOpen( pak->pakFilename, pak->handle );
3110 if ( file->z == NULL ) {
3111 common->FatalError( "Couldn't reopen %s", pak->pakFilename.c_str() );
3113 file->name = relativePath;
3114 file->fullPath = pak->pakFilename + "/" + relativePath;
3115 zfi = (unz_s *)file->z;
3116 // in case the file was new
3118 // set the file position in the zip file (also sets the current file info)
3119 unzSetCurrentFileInfoPosition( pak->handle, pakFile->pos );
3120 // copy the file info into the unzip structure
3121 memcpy( zfi, pak->handle, sizeof(unz_s) );
3122 // we copy this back into the structure
3124 // open the file in the zip
3125 unzOpenCurrentFile( file->z );
3126 file->zipFilePos = pakFile->pos;
3127 file->fileSize = zfi->cur_file_info.uncompressed_size;
3133 idFileSystemLocal::OpenFileReadFlags
3135 Finds the file in the search path, following search flag recommendations
3136 Returns filesize and an open FILE pointer.
3137 Used for streaming data out of either a
3138 separate file or a ZIP file.
3141 idFile *idFileSystemLocal::OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak, bool allowCopyFiles, const char* gamedir ) {
3142 searchpath_t * search;
3145 fileInPack_t * pakFile;
3150 if ( !searchPaths ) {
3151 common->FatalError( "Filesystem call made without initialization\n" );
3154 if ( !relativePath ) {
3155 common->FatalError( "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n" );
3162 // qpaths are not supposed to have a leading slash
3163 if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
3167 // make absolutely sure that it can't back up the path.
3168 // The searchpaths do guarantee that something will always
3169 // be prepended, so we don't need to worry about "c:" or "//limbo"
3170 if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
3175 if ( relativePath[0] == '\0' ) {
3179 // make sure the doomkey file is only readable by game at initialization
3180 // any other time the key should only be accessed in memory using the provided functions
3181 if( common->IsInitialized() && ( idStr::Icmp( relativePath, CDKEY_FILE ) == 0 || idStr::Icmp( relativePath, XPKEY_FILE ) == 0 ) ) {
3186 // search through the path, one element at a time
3189 hash = HashFileName( relativePath );
3191 for ( search = searchPaths; search; search = search->next ) {
3192 if ( search->dir && ( searchFlags & FSFLAG_SEARCH_DIRS ) ) {
3193 // check a file in the directory tree
3195 // if we are running restricted, the only files we
3196 // will allow to come from the directory are .cfg files
3197 if ( fs_restrict.GetBool() || serverPaks.Num() ) {
3198 if ( !FileAllowedFromDir( relativePath ) ) {
3205 if(gamedir && strlen(gamedir)) {
3206 if(dir->gamedir != gamedir) {
3211 netpath = BuildOSPath( dir->path, dir->gamedir, relativePath );
3212 fp = OpenOSFileCorrectName( netpath, "rb" );
3217 idFile_Permanent *file = new idFile_Permanent();
3219 file->name = relativePath;
3220 file->fullPath = netpath;
3221 file->mode = ( 1 << FS_READ );
3222 file->fileSize = DirectFileLength( file->o );
3223 if ( fs_debug.GetInteger() ) {
3224 common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s/%s')\n", relativePath, dir->path.c_str(), dir->gamedir.c_str() );
3227 if ( !loadedFileFromDir && !FileAllowedFromDir( relativePath ) ) {
3228 if ( restartChecksums.Num() ) {
3229 common->FatalError( "'%s' loaded from directory: Failed to restart with pure mode restrictions for server connect", relativePath );
3231 common->DPrintf( "filesystem: switching to pure mode will require a restart. '%s' loaded from directory.\n", relativePath );
3232 loadedFileFromDir = true;
3235 // if fs_copyfiles is set
3236 if ( allowCopyFiles && fs_copyfiles.GetInteger() ) {
3240 copypath = BuildOSPath( fs_savepath.GetString(), dir->gamedir, relativePath );
3241 netpath.ExtractFileName( name );
3242 copypath.StripFilename( );
3243 copypath += PATHSEPERATOR_STR;
3246 bool isFromCDPath = !dir->path.Cmp( fs_cdpath.GetString() );
3247 bool isFromSavePath = !dir->path.Cmp( fs_savepath.GetString() );
3248 bool isFromBasePath = !dir->path.Cmp( fs_basepath.GetString() );
3250 switch ( fs_copyfiles.GetInteger() ) {
3252 // copy from cd path only
3253 if ( isFromCDPath ) {
3254 CopyFile( netpath, copypath );
3258 // from cd path + timestamps
3259 if ( isFromCDPath ) {
3260 CopyFile( netpath, copypath );
3261 } else if ( isFromSavePath || isFromBasePath ) {
3263 sourcepath = BuildOSPath( fs_cdpath.GetString(), dir->gamedir, relativePath );
3264 FILE *f1 = OpenOSFile( sourcepath, "r" );
3266 ID_TIME_T t1 = Sys_FileTimeStamp( f1 );
3268 FILE *f2 = OpenOSFile( copypath, "r" );
3270 ID_TIME_T t2 = Sys_FileTimeStamp( f2 );
3273 CopyFile( sourcepath, copypath );
3280 if ( isFromCDPath || isFromBasePath ) {
3281 CopyFile( netpath, copypath );
3285 if ( isFromCDPath && !isFromBasePath ) {
3286 CopyFile( netpath, copypath );
3293 } else if ( search->pack && ( searchFlags & FSFLAG_SEARCH_PAKS ) ) {
3295 if ( !search->pack->hashTable[hash] ) {
3299 // disregard if it doesn't match one of the allowed pure pak files
3300 if ( serverPaks.Num() ) {
3301 GetPackStatus( search->pack );
3302 if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
3303 continue; // not on the pure server pak list
3307 // look through all the pak file elements
3310 if ( searchFlags & FSFLAG_BINARY_ONLY ) {
3311 // make sure this pak is tagged as a binary file
3312 if ( pak->binary == BINARY_UNKNOWN ) {
3314 fileInPack_t *pakFile;
3315 confHash = HashFileName( BINARY_CONFIG );
3316 pak->binary = BINARY_NO;
3317 for ( pakFile = search->pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
3318 if ( !FilenameCompare( pakFile->name, BINARY_CONFIG ) ) {
3319 pak->binary = BINARY_YES;
3324 if ( pak->binary == BINARY_NO ) {
3325 continue; // not a binary pak, skip
3329 for ( pakFile = pak->hashTable[hash]; pakFile; pakFile = pakFile->next ) {
3330 // case and separator insensitive comparisons
3331 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
3332 idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath );
3338 if ( !pak->referenced && !( searchFlags & FSFLAG_PURE_NOREF ) ) {
3339 // mark this pak referenced
3340 if ( fs_debug.GetInteger( ) ) {
3341 common->Printf( "idFileSystem::OpenFileRead: %s -> adding %s to referenced paks\n", relativePath, pak->pakFilename.c_str() );
3343 pak->referenced = true;
3346 if ( fs_debug.GetInteger( ) ) {
3347 common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s')\n", relativePath, pak->pakFilename.c_str() );
3355 if ( searchFlags & FSFLAG_SEARCH_ADDONS ) {
3356 for ( search = addonPaks; search; search = search->next ) {
3357 assert( search->pack );
3358 fileInPack_t *pakFile;
3360 for ( pakFile = pak->hashTable[hash]; pakFile; pakFile = pakFile->next ) {
3361 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
3362 idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath );
3366 // we don't toggle pure on paks found in addons - they can't be used without a reloadEngine anyway
3367 if ( fs_debug.GetInteger( ) ) {
3368 common->Printf( "idFileSystem::OpenFileRead: %s (found in addon pk4 '%s')\n", relativePath, search->pack->pakFilename.c_str() );
3376 if ( fs_debug.GetInteger( ) ) {
3377 common->Printf( "Can't find %s\n", relativePath );
3385 idFileSystemLocal::OpenFileRead
3388 idFile *idFileSystemLocal::OpenFileRead( const char *relativePath, bool allowCopyFiles, const char* gamedir ) {
3389 return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS | FSFLAG_SEARCH_PAKS, NULL, allowCopyFiles, gamedir );
3394 idFileSystemLocal::OpenFileWrite
3397 idFile *idFileSystemLocal::OpenFileWrite( const char *relativePath, const char *basePath ) {
3400 idFile_Permanent *f;
3402 if ( !searchPaths ) {
3403 common->FatalError( "Filesystem call made without initialization\n" );
3406 path = cvarSystem->GetCVarString( basePath );
3408 path = fs_savepath.GetString();
3411 OSpath = BuildOSPath( path, gameFolder, relativePath );
3413 if ( fs_debug.GetInteger() ) {
3414 common->Printf( "idFileSystem::OpenFileWrite: %s\n", OSpath.c_str() );
3417 // if the dir we are writing to is in our current list, it will be outdated
3418 // so just flush everything
3421 common->DPrintf( "writing to: %s\n", OSpath.c_str() );
3422 CreateOSPath( OSpath );
3424 f = new idFile_Permanent();
3425 f->o = OpenOSFile( OSpath, "wb" );
3430 f->name = relativePath;
3431 f->fullPath = OSpath;
3432 f->mode = ( 1 << FS_WRITE );
3433 f->handleSync = false;
3441 idFileSystemLocal::OpenExplicitFileRead
3444 idFile *idFileSystemLocal::OpenExplicitFileRead( const char *OSPath ) {
3445 idFile_Permanent *f;
3447 if ( !searchPaths ) {
3448 common->FatalError( "Filesystem call made without initialization\n" );
3451 if ( fs_debug.GetInteger() ) {
3452 common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath );
3455 common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath );
3457 f = new idFile_Permanent();
3458 f->o = OpenOSFile( OSPath, "rb" );
3464 f->fullPath = OSPath;
3465 f->mode = ( 1 << FS_READ );
3466 f->handleSync = false;
3467 f->fileSize = DirectFileLength( f->o );
3474 idFileSystemLocal::OpenExplicitFileWrite
3477 idFile *idFileSystemLocal::OpenExplicitFileWrite( const char *OSPath ) {
3478 idFile_Permanent *f;
3480 if ( !searchPaths ) {
3481 common->FatalError( "Filesystem call made without initialization\n" );
3484 if ( fs_debug.GetInteger() ) {
3485 common->Printf( "idFileSystem::OpenExplicitFileWrite: %s\n", OSPath );
3488 common->DPrintf( "writing to: %s\n", OSPath );
3489 CreateOSPath( OSPath );
3491 f = new idFile_Permanent();
3492 f->o = OpenOSFile( OSPath, "wb" );
3498 f->fullPath = OSPath;
3499 f->mode = ( 1 << FS_WRITE );
3500 f->handleSync = false;
3508 idFileSystemLocal::OpenFileAppend
3511 idFile *idFileSystemLocal::OpenFileAppend( const char *relativePath, bool sync, const char *basePath ) {
3514 idFile_Permanent *f;
3516 if ( !searchPaths ) {
3517 common->FatalError( "Filesystem call made without initialization\n" );
3520 path = cvarSystem->GetCVarString( basePath );
3522 path = fs_savepath.GetString();
3525 OSpath = BuildOSPath( path, gameFolder, relativePath );
3526 CreateOSPath( OSpath );
3528 if ( fs_debug.GetInteger() ) {
3529 common->Printf( "idFileSystem::OpenFileAppend: %s\n", OSpath.c_str() );
3532 f = new idFile_Permanent();
3533 f->o = OpenOSFile( OSpath, "ab" );
3538 f->name = relativePath;
3539 f->fullPath = OSpath;
3540 f->mode = ( 1 << FS_WRITE ) + ( 1 << FS_APPEND );
3541 f->handleSync = sync;
3542 f->fileSize = DirectFileLength( f->o );
3549 idFileSystemLocal::OpenFileByMode
3552 idFile *idFileSystemLocal::OpenFileByMode( const char *relativePath, fsMode_t mode ) {
3553 if ( mode == FS_READ ) {
3554 return OpenFileRead( relativePath );
3556 if ( mode == FS_WRITE ) {
3557 return OpenFileWrite( relativePath );
3559 if ( mode == FS_APPEND ) {
3560 return OpenFileAppend( relativePath, true );
3562 common->FatalError( "idFileSystemLocal::OpenFileByMode: bad mode" );
3568 idFileSystemLocal::CloseFile
3571 void idFileSystemLocal::CloseFile( idFile *f ) {
3572 if ( !searchPaths ) {
3573 common->FatalError( "Filesystem call made without initialization\n" );
3580 =================================================================================
3584 =================================================================================
3589 idFileSystemLocal::CurlWriteFunction
3592 size_t idFileSystemLocal::CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream ) {
3593 backgroundDownload_t *bgl = (backgroundDownload_t *)stream;
3595 return size * nmemb;
3598 return _write( static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr()->_file, ptr, size * nmemb );
3600 return fwrite( ptr, size, nmemb, static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr() );
3606 idFileSystemLocal::CurlProgressFunction
3609 int idFileSystemLocal::CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ) {
3610 backgroundDownload_t *bgl = (backgroundDownload_t *)clientp;
3611 if ( bgl->url.status == DL_ABORTING ) {
3614 bgl->url.dltotal = dltotal;
3615 bgl->url.dlnow = dlnow;
3623 Reads part of a file from a background thread.
3626 dword BackgroundDownloadThread( void *parms ) {
3628 Sys_EnterCriticalSection();
3629 backgroundDownload_t *bgl = fileSystemLocal.backgroundDownloads;
3631 Sys_LeaveCriticalSection();
3635 // remove this from the list
3636 fileSystemLocal.backgroundDownloads = bgl->next;
3637 Sys_LeaveCriticalSection();
3641 if ( bgl->opcode == DLTYPE_FILE ) {
3642 // use the low level read function, because fread may allocate memory
3644 _read( static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr()->_file, bgl->file.buffer, bgl->file.length );
3646 fread( bgl->file.buffer, bgl->file.length, 1, static_cast<idFile_Permanent*>(bgl->f)->GetFilePtr() );
3648 bgl->completed = true;
3652 // use a local buffer for curl error since the size define is local
3653 char error_buf[ CURL_ERROR_SIZE ];
3654 bgl->url.dlerror[ 0 ] = '\0';
3655 CURL *session = curl_easy_init();
3658 bgl->url.dlstatus = CURLE_FAILED_INIT;
3659 bgl->url.status = DL_FAILED;
3660 bgl->completed = true;
3663 ret = curl_easy_setopt( session, CURLOPT_ERRORBUFFER, error_buf );
3665 bgl->url.dlstatus = ret;
3666 bgl->url.status = DL_FAILED;
3667 bgl->completed = true;
3670 ret = curl_easy_setopt( session, CURLOPT_URL, bgl->url.url.c_str() );
3672 bgl->url.dlstatus = ret;
3673 bgl->url.status = DL_FAILED;
3674 bgl->completed = true;
3677 ret = curl_easy_setopt( session, CURLOPT_FAILONERROR, 1 );
3679 bgl->url.dlstatus = ret;
3680 bgl->url.status = DL_FAILED;
3681 bgl->completed = true;
3684 ret = curl_easy_setopt( session, CURLOPT_WRITEFUNCTION, idFileSystemLocal::CurlWriteFunction );
3686 bgl->url.dlstatus = ret;
3687 bgl->url.status = DL_FAILED;
3688 bgl->completed = true;
3691 ret = curl_easy_setopt( session, CURLOPT_WRITEDATA, bgl );
3693 bgl->url.dlstatus = ret;
3694 bgl->url.status = DL_FAILED;
3695 bgl->completed = true;
3698 ret = curl_easy_setopt( session, CURLOPT_NOPROGRESS, 0 );
3700 bgl->url.dlstatus = ret;
3701 bgl->url.status = DL_FAILED;
3702 bgl->completed = true;
3705 ret = curl_easy_setopt( session, CURLOPT_PROGRESSFUNCTION, idFileSystemLocal::CurlProgressFunction );
3707 bgl->url.dlstatus = ret;
3708 bgl->url.status = DL_FAILED;
3709 bgl->completed = true;
3712 ret = curl_easy_setopt( session, CURLOPT_PROGRESSDATA, bgl );
3714 bgl->url.dlstatus = ret;
3715 bgl->url.status = DL_FAILED;
3716 bgl->completed = true;
3720 bgl->url.dltotal = 0;
3721 bgl->url.status = DL_INPROGRESS;
3722 ret = curl_easy_perform( session );
3724 Sys_Printf( "curl_easy_perform failed: %s\n", error_buf );
3725 idStr::Copynz( bgl->url.dlerror, error_buf, MAX_STRING_CHARS );
3726 bgl->url.dlstatus = ret;
3727 bgl->url.status = DL_FAILED;
3728 bgl->completed = true;
3731 bgl->url.status = DL_DONE;
3732 bgl->completed = true;
3734 bgl->url.status = DL_FAILED;
3735 bgl->completed = true;
3744 idFileSystemLocal::StartBackgroundReadThread
3747 void idFileSystemLocal::StartBackgroundDownloadThread() {
3748 if ( !backgroundThread.threadHandle ) {
3749 Sys_CreateThread( (xthread_t)BackgroundDownloadThread, NULL, THREAD_NORMAL, backgroundThread, "backgroundDownload", g_threads, &g_thread_count );
3750 if ( !backgroundThread.threadHandle ) {
3751 common->Warning( "idFileSystemLocal::StartBackgroundDownloadThread: failed" );
3754 common->Printf( "background thread already running\n" );
3760 idFileSystemLocal::BackgroundDownload
3763 void idFileSystemLocal::BackgroundDownload( backgroundDownload_t *bgl ) {
3764 if ( bgl->opcode == DLTYPE_FILE ) {
3765 if ( dynamic_cast<idFile_Permanent *>(bgl->f) ) {
3766 // add the bgl to the background download list
3767 Sys_EnterCriticalSection();
3768 bgl->next = backgroundDownloads;
3769 backgroundDownloads = bgl;
3771 Sys_LeaveCriticalSection();
3773 // read zipped file directly
3774 bgl->f->Seek( bgl->file.position, FS_SEEK_SET );
3775 bgl->f->Read( bgl->file.buffer, bgl->file.length );
3776 bgl->completed = true;
3779 Sys_EnterCriticalSection();
3780 bgl->next = backgroundDownloads;
3781 backgroundDownloads = bgl;
3783 Sys_LeaveCriticalSection();
3789 idFileSystemLocal::PerformingCopyFiles
3792 bool idFileSystemLocal::PerformingCopyFiles( void ) const {
3793 return fs_copyfiles.GetInteger() > 0;
3798 idFileSystemLocal::FindPakForFileChecksum
3801 pack_t *idFileSystemLocal::FindPakForFileChecksum( const char *relativePath, int findChecksum, bool bReference ) {
3802 searchpath_t *search;
3804 fileInPack_t *pakFile;
3806 assert( !serverPaks.Num() );
3807 hash = HashFileName( relativePath );
3808 for ( search = searchPaths; search; search = search->next ) {
3809 if ( search->pack && search->pack->hashTable[ hash ] ) {
3811 for ( pakFile = pak->hashTable[ hash ]; pakFile; pakFile = pakFile->next ) {
3812 if ( !FilenameCompare( pakFile->name, relativePath ) ) {
3813 idFile_InZip *file = ReadFileFromZip( pak, pakFile, relativePath );
3814 if ( findChecksum == GetFileChecksum( file ) ) {
3815 if ( fs_debug.GetBool() ) {
3816 common->Printf( "found '%s' with checksum 0x%x in pak '%s'\n", relativePath, findChecksum, pak->pakFilename.c_str() );
3819 pak->referenced = true;
3820 // FIXME: use dependencies for pak references
3824 } else if ( fs_debug.GetBool() ) {
3825 common->Printf( "'%s' in pak '%s' has != checksum %x\n", relativePath, pak->pakFilename.c_str(), GetFileChecksum( file ) );
3832 if ( fs_debug.GetBool() ) {
3833 common->Printf( "no pak file found for '%s' checksumed %x\n", relativePath, findChecksum );
3840 idFileSystemLocal::GetFileChecksum
3843 int idFileSystemLocal::GetFileChecksum( idFile *file ) {
3847 file->Seek( 0, FS_SEEK_END );
3849 file->Seek( 0, FS_SEEK_SET );
3850 buf = (byte *)Mem_Alloc( len );
3851 if ( file->Read( buf, len ) != len ) {
3852 common->FatalError( "Short read in idFileSystemLocal::GetFileChecksum()\n" );
3854 ret = MD4_BlockChecksum( buf, len );
3861 idFileSystemLocal::FindDLL
3864 void idFileSystemLocal::FindDLL( const char *name, char _dllPath[ MAX_OSPATH ], bool updateChecksum ) {
3865 idFile *dllFile = NULL;
3866 char dllName[MAX_OSPATH];
3871 fileInPack_t *pakFile;
3873 sys->DLL_GetFileName( name, dllName, MAX_OSPATH );
3874 dllHash = HashFileName( dllName );
3879 if ( !serverPaks.Num() ) {
3881 // from executable directory first - this is handy for developement
3882 dllPath = Sys_EXEPath( );
3883 dllPath.StripFilename( );
3884 dllPath.AppendPath( dllName );
3885 dllFile = OpenExplicitFileRead( dllPath );
3888 if ( !serverPaks.Num() ) {
3889 // not running in pure mode, try to extract from a pak file first
3890 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_PAKS | FSFLAG_PURE_NOREF | FSFLAG_BINARY_ONLY, &inPak );
3892 common->Printf( "found DLL in pak file: %s\n", dllFile->GetFullPath() );
3893 dllPath = RelativePathToOSPath( dllName, "fs_savepath" );
3894 CopyFile( dllFile, dllPath );
3895 CloseFile( dllFile );
3896 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS );
3898 common->Error( "DLL extraction to fs_savepath failed\n" );
3899 } else if ( updateChecksum ) {
3900 gameDLLChecksum = GetFileChecksum( dllFile );
3901 gamePakChecksum = inPak->checksum;
3902 updateChecksum = false; // don't try again below
3905 // didn't find a source in a pak file, try in the directory
3906 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS );
3908 if ( updateChecksum ) {
3909 gameDLLChecksum = GetFileChecksum( dllFile );
3910 // see if we can mark a pak file
3911 pak = FindPakForFileChecksum( dllName, gameDLLChecksum, false );
3912 pak ? gamePakChecksum = pak->checksum : gamePakChecksum = 0;
3913 updateChecksum = false;
3918 // we are in pure mode. this path to be reached only for game DLL situations
3919 // with a code pak checksum given by server
3920 assert( gamePakChecksum );
3921 assert( updateChecksum );
3922 pak = GetPackForChecksum( gamePakChecksum );
3924 // not supposed to happen, bug in pure code?
3925 common->Warning( "FindDLL in pure mode: game pak not found ( 0x%x )\n", gamePakChecksum );
3928 for ( pakFile = pak->hashTable[dllHash]; pakFile; pakFile = pakFile->next ) {
3929 if ( !FilenameCompare( pakFile->name, dllName ) ) {
3930 dllFile = ReadFileFromZip( pak, pakFile, dllName );
3931 common->Printf( "found DLL in game pak file: %s\n", pak->pakFilename.c_str() );
3932 dllPath = RelativePathToOSPath( dllName, "fs_savepath" );
3933 CopyFile( dllFile, dllPath );
3934 CloseFile( dllFile );
3935 dllFile = OpenFileReadFlags( dllName, FSFLAG_SEARCH_DIRS );
3937 common->Error( "DLL extraction to fs_savepath failed\n" );
3939 gameDLLChecksum = GetFileChecksum( dllFile );
3940 updateChecksum = false; // don't try again below
3947 if ( updateChecksum ) {
3949 gameDLLChecksum = GetFileChecksum( dllFile );
3951 gameDLLChecksum = 0;
3953 gamePakChecksum = 0;
3956 dllPath = dllFile->GetFullPath( );
3957 CloseFile( dllFile );
3962 idStr::snPrintf( _dllPath, MAX_OSPATH, dllPath.c_str() );
3967 idFileSystemLocal::ClearDirCache
3970 void idFileSystemLocal::ClearDirCache( void ) {
3973 dir_cache_index = 0;
3974 dir_cache_count = 0;
3975 for( i = 0; i < MAX_CACHED_DIRS; i++ ) {
3976 dir_cache[ i ].Clear();
3982 idFileSystemLocal::HasD3XP
3985 bool idFileSystemLocal::HasD3XP( void ) {
3987 idStrList dirs, pk4s;
3992 } else if ( d3xp == 1 ) {
3997 // check for a d3xp directory with a pk4 file
3998 // copied over from ListMods - only looks in basepath
3999 ListOSFiles( fs_basepath.GetString(), "/", dirs );
4000 for ( i = 0; i < dirs.Num(); i++ ) {
4001 if ( dirs[i].Icmp( "d3xp" ) == 0 ) {
4002 gamepath = BuildOSPath( fs_basepath.GetString(), dirs[ i ], "" );
4003 ListOSFiles( gamepath, ".pk4", pk4s );
4011 // check for d3xp's d3xp/pak000.pk4 in any search path
4012 // checking wether the pak is loaded by checksum wouldn't be enough:
4013 // we may have a different fs_game right now but still need to reply that it's installed
4014 const char *search[4];
4016 search[0] = fs_savepath.GetString();
4017 search[1] = fs_devpath.GetString();
4018 search[2] = fs_basepath.GetString();
4019 search[3] = fs_cdpath.GetString();
4020 for ( i = 0; i < 4; i++ ) {
4021 pakfile = OpenExplicitFileRead( BuildOSPath( search[ i ], "d3xp", "pak000.pk4" ) );
4023 CloseFile( pakfile );
4031 // if we didn't find a pk4 file then the user might have unpacked so look for default.cfg file
4032 // that's the old way mostly used during developement. don't think it hurts to leave it there
4033 ListOSFiles( fs_basepath.GetString(), "/", dirs );
4034 for ( i = 0; i < dirs.Num(); i++ ) {
4035 if ( dirs[i].Icmp( "d3xp" ) == 0 ) {
4037 gamepath = BuildOSPath( fs_savepath.GetString(), dirs[ i ], "default.cfg" );
4038 idFile* cfg = OpenExplicitFileRead(gamepath);
4053 idFileSystemLocal::RunningD3XP
4056 bool idFileSystemLocal::RunningD3XP( void ) {
4057 // TODO: mark the checksum of the gold XP and check for it being referenced ( for double mod support )
4058 // a simple fs_game check should be enough for now..
4059 if ( !idStr::Icmp( fs_game.GetString(), "d3xp" ) ||
4060 !idStr::Icmp( fs_game_base.GetString(), "d3xp" ) ) {
4068 idFileSystemLocal::MakeTemporaryFile
4071 idFile * idFileSystemLocal::MakeTemporaryFile( void ) {
4072 FILE *f = tmpfile();
4074 common->Warning( "idFileSystem::MakeTemporaryFile failed: %s", strerror( errno ) );
4077 idFile_Permanent *file = new idFile_Permanent();
4079 file->name = "<tempfile>";
4080 file->fullPath = "<tempfile>";
4081 file->mode = ( 1 << FS_READ ) + ( 1 << FS_WRITE );
4088 idFileSystemLocal::FindFile
4091 findFile_t idFileSystemLocal::FindFile( const char *path, bool scheduleAddons ) {
4093 idFile *f = OpenFileReadFlags( path, FSFLAG_SEARCH_DIRS | FSFLAG_SEARCH_PAKS | FSFLAG_SEARCH_ADDONS, &pak );
4098 // found in FS, not even in paks
4101 // marking addons for inclusion on reload - may need to do that even when already in the search path
4102 if ( scheduleAddons && pak->addon && addonChecksums.FindIndex( pak->checksum ) < 0 ) {
4103 addonChecksums.Append( pak->checksum );
4105 // an addon that's not on search list yet? that will require a restart
4106 if ( pak->addon && !pak->addon_search ) {
4116 idFileSystemLocal::GetNumMaps
4117 account for actual decls and for addon maps
4120 int idFileSystemLocal::GetNumMaps() {
4122 searchpath_t *search = NULL;
4123 int ret = declManager->GetNumDecls( DECL_MAPDEF );
4125 // add to this all addon decls - coming from all addon packs ( searched or not )
4126 for ( i = 0; i < 2; i++ ) {
4128 search = searchPaths;
4129 } else if ( i == 1 ) {
4132 for ( ; search ; search = search->next ) {
4133 if ( !search->pack || !search->pack->addon || !search->pack->addon_info ) {
4136 ret += search->pack->addon_info->mapDecls.Num();
4144 idFileSystemLocal::GetMapDecl
4145 retrieve the decl dictionary, add a 'path' value
4148 const idDict * idFileSystemLocal::GetMapDecl( int idecl ) {
4150 const idDecl *mapDecl;
4151 const idDeclEntityDef *mapDef;
4152 int numdecls = declManager->GetNumDecls( DECL_MAPDEF );
4153 searchpath_t *search = NULL;
4155 if ( idecl < numdecls ) {
4156 mapDecl = declManager->DeclByIndex( DECL_MAPDEF, idecl );
4157 mapDef = static_cast<const idDeclEntityDef *>( mapDecl );
4159 common->Error( "idFileSystemLocal::GetMapDecl %d: not found\n", idecl );
4161 mapDict = mapDef->dict;
4162 mapDict.Set( "path", mapDef->GetName() );
4166 for ( i = 0; i < 2; i++ ) {
4168 search = searchPaths;
4169 } else if ( i == 1 ) {
4172 for ( ; search ; search = search->next ) {
4173 if ( !search->pack || !search->pack->addon || !search->pack->addon_info ) {
4176 // each addon may have a bunch of map decls
4177 if ( idecl < search->pack->addon_info->mapDecls.Num() ) {
4178 mapDict = *search->pack->addon_info->mapDecls[ idecl ];
4181 idecl -= search->pack->addon_info->mapDecls.Num();
4182 assert( idecl >= 0 );
4190 idFileSystemLocal::FindMapScreenshot
4193 void idFileSystemLocal::FindMapScreenshot( const char *path, char *buf, int len ) {
4195 idStr mapname = path;
4197 mapname.StripPath();
4198 mapname.StripFileExtension();
4200 idStr::snPrintf( buf, len, "guis/assets/splash/%s.tga", mapname.c_str() );
4201 if ( ReadFile( buf, NULL, NULL ) == -1 ) {
4202 // try to extract from an addon
4203 file = OpenFileReadFlags( buf, FSFLAG_SEARCH_ADDONS );
4205 // save it out to an addon splash directory
4206 int dlen = file->Length();
4207 char *data = new char[ dlen ];
4208 file->Read( data, dlen );
4210 idStr::snPrintf( buf, len, "guis/assets/splash/addon/%s.tga", mapname.c_str() );
4211 WriteFile( buf, data, dlen );
4214 idStr::Copynz( buf, "guis/assets/splash/pdtempa", len );