DOOM-3-BFG

DOOM 3 BFG Edition
Log | Files | Refs

FileSystem.cpp (97294B)


      1 /*
      2 ===========================================================================
      3 
      4 Doom 3 BFG Edition GPL Source Code
      5 Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. 
      6 
      7 This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").  
      8 
      9 Doom 3 BFG Edition 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.
     13 
     14 Doom 3 BFG Edition 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.
     18 
     19 You should have received a copy of the GNU General Public License
     20 along with Doom 3 BFG Edition Source Code.  If not, see <http://www.gnu.org/licenses/>.
     21 
     22 In addition, the Doom 3 BFG Edition 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 BFG Edition Source Code.  If not, please request a copy in writing from id Software at the address below.
     23 
     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.
     25 
     26 ===========================================================================
     27 */
     28 
     29 #include "../idlib/precompiled.h"
     30 #pragma hdrstop
     31 
     32 #include "Unzip.h"
     33 #include "Zip.h"
     34 
     35 #ifdef WIN32
     36 	#include <io.h>	// for _read
     37 #else
     38 	#if !__MACH__ && __MWERKS__
     39 		#include <types.h>
     40 		#include <stat.h>
     41 #else
     42 		#include <sys/types.h>
     43 		#include <sys/stat.h>
     44 	#endif
     45 	#include <unistd.h>
     46 #endif
     47 
     48 
     49 /*
     50 =============================================================================
     51 
     52 DOOM FILESYSTEM
     53 
     54 All of Doom's data access is through a hierarchical file system, but the contents of 
     55 the file system can be transparently merged from several sources.
     56 
     57 A "relativePath" is a reference to game file data, which must include a terminating zero.
     58 "..", "\\", and ":" are explicitly illegal in qpaths to prevent any references
     59 outside the Doom directory system.
     60 
     61 The "base path" is the path to the directory holding all the game directories and
     62 usually the executable. It defaults to the current directory, but can be overridden
     63 with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified
     64 at all after startup.
     65 
     66 The "save path" is the path to the directory where game files will be saved. It defaults
     67 to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the
     68 command line. Any files that are created during the game (demos, screenshots, etc.) will
     69 be created reletive to the save path.
     70 
     71 If a user runs the game directly from a CD, the base path would be on the CD. This
     72 should still function correctly, but all file writes will fail (harmlessly).
     73 
     74 The "base game" is the directory under the paths where data comes from by default, and
     75 can be either "base" or "demo".
     76 
     77 The "current game" may be the same as the base game, or it may be the name of another
     78 directory under the paths that should be searched for files before looking in the base
     79 game. The game directory is set with "+set fs_game myaddon" on the command line. This is
     80 the basis for addons.
     81 
     82 No other directories outside of the base game and current game will ever be referenced by
     83 filesystem functions.
     84 
     85 Because we will have updated executables freely available online, there is no point to
     86 trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
     87 should be exactly the same executables as release versions, but with different data that
     88 automatically restricts where game media can come from to prevent add-ons from working.
     89 
     90 If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the base
     91 path, it will be copied over to the save path. This is a development aid to help build
     92 test releases and to copy working sets of files.
     93 
     94 The relative path "sound/newstuff/test.wav" would be searched for in the following places:
     95 
     96 for save path, base path:
     97 	for current game, base game:
     98 		search directory
     99 		search zip files
    100 
    101 downloaded files, to be written to save path + current game's directory
    102 
    103 The filesystem can be safely shutdown and reinitialized with different
    104 basedir / cddir / game combinations, but all other subsystems that rely on it
    105 (sound, video) must also be forced to restart.
    106 
    107 "additional mod path search":
    108 fs_game_base can be used to set an additional search path
    109 in search order, fs_game, fs_game_base, BASEGAME
    110 for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp
    111 
    112 =============================================================================
    113 */
    114 
    115 #define MAX_ZIPPED_FILE_NAME	2048
    116 #define FILE_HASH_SIZE			1024
    117 
    118 struct searchpath_t {
    119 	idStr	path;		// c:\doom
    120 	idStr	gamedir;	// base
    121 };
    122 
    123 // search flags when opening a file
    124 #define FSFLAG_SEARCH_DIRS		( 1 << 0 )
    125 #define FSFLAG_RETURN_FILE_MEM	( 1 << 1 )
    126 
    127 class idFileSystemLocal : public idFileSystem {
    128 public:
    129 							idFileSystemLocal();
    130 
    131 	virtual void			Init();
    132 	virtual void			Restart();
    133 	virtual void			Shutdown( bool reloading );
    134 	virtual bool			IsInitialized() const;
    135 	virtual idFileList *	ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL );
    136 	virtual idFileList *	ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL );
    137 	virtual void			FreeFileList( idFileList *fileList );
    138 	virtual const char *	OSPathToRelativePath( const char *OSPath );
    139 	virtual const char *	RelativePathToOSPath( const char *relativePath, const char *basePath );
    140 	virtual const char *	BuildOSPath( const char *base, const char *game, const char *relativePath );
    141 	virtual const char *	BuildOSPath( const char *base, const char *relativePath );
    142 	virtual void			CreateOSPath( const char *OSPath );
    143 	virtual int				ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp );
    144 	virtual void			FreeFile( void *buffer );
    145 	virtual int				WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" );
    146 	virtual void			RemoveFile( const char *relativePath );	
    147 	virtual	bool			RemoveDir( const char * relativePath );
    148 	virtual bool			RenameFile( const char * relativePath, const char * newName, const char * basePath = "fs_savepath" );
    149 	virtual idFile *		OpenFileReadFlags( const char *relativePath, int searchFlags, bool allowCopyFiles = true, const char* gamedir = NULL );
    150 	virtual idFile *		OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL );
    151 	virtual idFile *		OpenFileReadMemory( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL );
    152 	virtual idFile *		OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" );
    153 	virtual idFile *		OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath"   );
    154 	virtual idFile *		OpenFileByMode( const char *relativePath, fsMode_t mode );
    155 	virtual idFile *		OpenExplicitFileRead( const char *OSPath );
    156 	virtual idFile *		OpenExplicitFileWrite( const char *OSPath );
    157 	virtual idFile_Cached *		OpenExplicitPakFile( const char *OSPath );
    158 	virtual void			CloseFile( idFile *f );
    159 	virtual void			FindDLL( const char *basename, char dllPath[ MAX_OSPATH ] );
    160 	virtual void			CopyFile( const char *fromOSPath, const char *toOSPath );
    161 	virtual findFile_t		FindFile( const char *path );
    162 	virtual bool			FilenameCompare( const char *s1, const char *s2 ) const;
    163 	virtual int				GetFileLength( const char * relativePath );
    164 	virtual sysFolder_t		IsFolder( const char * relativePath, const char *basePath = "fs_basepath" );
    165 	// resource tracking
    166 	virtual void			EnableBackgroundCache( bool enable );
    167 	virtual void			BeginLevelLoad( const char *name, char *_blockBuffer, int _blockBufferSize );
    168 	virtual void			EndLevelLoad();
    169 	virtual bool			InProductionMode() { return ( resourceFiles.Num() > 0 ) |  ( com_productionMode.GetInteger() != 0 ); }
    170 	virtual bool			UsingResourceFiles() { return resourceFiles.Num() > 0; }
    171 	virtual void			UnloadMapResources( const char *name );
    172 	virtual void			UnloadResourceContainer( const char *name );
    173 	idFile *				GetResourceContainer( int idx ) {
    174 		if ( idx >= 0 && idx < resourceFiles.Num() ) {
    175 			return resourceFiles[ idx ]->resourceFile;
    176 		}
    177 		return NULL;
    178 	}
    179 
    180 	virtual void			StartPreload( const idStrList &_preload );
    181 	virtual void			StopPreload();
    182 	idFile *				GetResourceFile( const char *fileName, bool memFile );
    183 	bool					GetResourceCacheEntry( const char *fileName, idResourceCacheEntry &rc );
    184 	virtual int				ReadFromBGL( idFile *_resourceFile, void * _buffer, int _offset, int _len );
    185 	virtual bool			IsBinaryModel( const idStr & resName ) const;
    186 	virtual bool			IsSoundSample( const idStr & resName ) const;
    187 	virtual void			FreeResourceBuffer() { resourceBufferAvailable = resourceBufferSize; }
    188 	virtual void			AddImagePreload( const char *resName, int _filter, int _repeat, int _usage, int _cube ) {
    189 		preloadList.AddImage( resName, _filter, _repeat, _usage, _cube );
    190 	}
    191 	virtual void			AddSamplePreload( const char *resName ) {
    192 		preloadList.AddSample( resName );
    193 	}
    194 	virtual void			AddModelPreload( const char *resName ) {
    195 		preloadList.AddModel( resName );
    196 	}
    197 	virtual void			AddAnimPreload( const char *resName ) {
    198 		preloadList.AddAnim( resName );
    199 	}
    200 	virtual void			AddCollisionPreload( const char *resName ) {
    201 		preloadList.AddCollisionModel( resName );
    202 	}
    203 	virtual void			AddParticlePreload( const char *resName ) {
    204 		preloadList.AddParticle( resName );
    205 	}
    206 
    207 	static void				Dir_f( const idCmdArgs &args );
    208 	static void				DirTree_f( const idCmdArgs &args );
    209 	static void				Path_f( const idCmdArgs &args );
    210 	static void				TouchFile_f( const idCmdArgs &args );
    211 	static void				TouchFileList_f( const idCmdArgs &args );
    212 	static void				BuildGame_f( const idCmdArgs &args );
    213 	//static void				FileStats_f( const idCmdArgs &args );
    214 	static void				WriteResourceFile_f ( const idCmdArgs &args );
    215 	static void				ExtractResourceFile_f( const idCmdArgs &args );
    216 	static void				UpdateResourceFile_f( const idCmdArgs &args );
    217 	static void				GenerateResourceCRCs_f( const idCmdArgs &args );
    218 	static void				CreateCRCsForResourceFileList( const idFileList & list );
    219 
    220 	void					BuildOrderedStartupContainer();
    221 private:
    222 	idList<searchpath_t>	searchPaths;
    223 	int						loadCount;			// total files read
    224 	int						loadStack;			// total files in memory
    225 	idStr					gameFolder;			// this will be a single name without separators
    226 
    227 	static idCVar			fs_debug;
    228 	static idCVar			fs_debugResources;
    229 	static idCVar			fs_copyfiles;
    230 	static idCVar			fs_buildResources;
    231 	static idCVar			fs_game;
    232 	static idCVar			fs_game_base;
    233 	static idCVar			fs_enableBGL;
    234 	static idCVar			fs_debugBGL;
    235 
    236 	idStr					manifestName;
    237 	idStrList				fileManifest;
    238 	idPreloadManifest		preloadList;
    239 
    240 	idList< idResourceContainer * > resourceFiles;
    241 	byte *	resourceBufferPtr;
    242 	int		resourceBufferSize;
    243 	int		resourceBufferAvailable;
    244 	int		numFilesOpenedAsCached;
    245 
    246 private:
    247 
    248 	// .resource file creation
    249 	void					ClearResourcePacks();
    250 	void					WriteResourcePacks();
    251 	void					AddRenderProgs( idStrList &files );
    252 	void					AddFonts( idStrList &files );
    253 
    254 	void					ReplaceSeparators( idStr &path, char sep = PATHSEPARATOR_CHAR );
    255 	int						ListOSFiles( const char *directory, const char *extension, idStrList &list );
    256 	idFileHandle			OpenOSFile( const char *name, fsMode_t mode );
    257 	void					CloseOSFile( idFileHandle o );
    258 	int						DirectFileLength( idFileHandle o );
    259 	void					CopyFile( idFile *src, const char *toOSPath );
    260 	int						AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const;
    261 	void					GetExtensionList( const char *extension, idStrList &extensionList ) const;
    262 	int						GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL );
    263 
    264 	int						GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL );
    265 	void					AddGameDirectory( const char *path, const char *dir );
    266 
    267 	int						AddResourceFile( const char * resourceFileName );
    268 	void					RemoveMapResourceFile( const char * resourceFileName );
    269 	void					RemoveResourceFileByIndex( const int & idx );
    270 	void					RemoveResourceFile( const char * resourceFileName );
    271 	int						FindResourceFile( const char * resourceFileName );
    272 
    273 	void					SetupGameDirectories( const char *gameName );
    274 	void					Startup();
    275 	void					InitPrecache();
    276 	void					ReOpenCacheFiles();
    277 };
    278 
    279 idCVar	idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
    280 idCVar	idFileSystemLocal::fs_debugResources( "fs_debugResources", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
    281 idCVar	idFileSystemLocal::fs_enableBGL( "fs_enableBGL", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
    282 idCVar	idFileSystemLocal::fs_debugBGL( "fs_debugBGL", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
    283 idCVar	idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "Copy every file touched to fs_savepath" );
    284 idCVar	idFileSystemLocal::fs_buildResources( "fs_buildresources", "0", CVAR_SYSTEM | CVAR_BOOL | CVAR_INIT, "Copy every file touched to a resource file" );
    285 idCVar	idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" );
    286 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" );
    287 
    288 idCVar	fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
    289 idCVar	fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
    290 idCVar	fs_resourceLoadPriority( "fs_resourceLoadPriority", "1", CVAR_SYSTEM , "if 1, open requests will be honored from resource files first; if 0, the resource files are checked after normal search paths" );
    291 idCVar	fs_enableBackgroundCaching( "fs_enableBackgroundCaching", "1", CVAR_SYSTEM , "if 1 allow the 360 to precache game files in the background" );
    292 
    293 idFileSystemLocal	fileSystemLocal;
    294 idFileSystem *		fileSystem = &fileSystemLocal;
    295 
    296 /*
    297 ================
    298 idFileSystemLocal::ReadFromBGL
    299 ================
    300 */
    301 int idFileSystemLocal::ReadFromBGL( idFile *_resourceFile, void * _buffer, int _offset, int _len ) {
    302 	if ( _resourceFile->Tell() != _offset ) {
    303 		_resourceFile->Seek( _offset, FS_SEEK_SET );
    304 	}
    305 	return _resourceFile->Read( _buffer, _len );
    306 }
    307 
    308 /*
    309 ================
    310 idFileSystemLocal::StartPreload
    311 ================
    312 */
    313 void idFileSystemLocal::StartPreload( const idStrList & _preload ) {
    314 }
    315 
    316 /*
    317 ================
    318 idFileSystemLocal::StopPreload
    319 ================
    320 */
    321 void idFileSystemLocal::StopPreload() {
    322 }
    323 
    324 /*
    325 ================
    326 idFileSystemLocal::idFileSystemLocal
    327 ================
    328 */
    329 idFileSystemLocal::idFileSystemLocal() {
    330 	loadCount = 0;
    331 	loadStack = 0;
    332 	resourceBufferPtr = NULL;
    333 	resourceBufferSize = 0;
    334 	resourceBufferAvailable = 0;
    335 	numFilesOpenedAsCached = 0;
    336 }
    337 
    338 /*
    339 ===========
    340 idFileSystemLocal::FilenameCompare
    341 
    342 Ignore case and separator char distinctions
    343 ===========
    344 */
    345 bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const {
    346 	int		c1, c2;
    347 	
    348 	do {
    349 		c1 = *s1++;
    350 		c2 = *s2++;
    351 
    352 		if ( c1 >= 'a' && c1 <= 'z' ) {
    353 			c1 -= ('a' - 'A');
    354 		}
    355 		if ( c2 >= 'a' && c2 <= 'z' ) {
    356 			c2 -= ('a' - 'A');
    357 		}
    358 
    359 		if ( c1 == '\\' || c1 == ':' ) {
    360 			c1 = '/';
    361 		}
    362 		if ( c2 == '\\' || c2 == ':' ) {
    363 			c2 = '/';
    364 		}
    365 		
    366 		if ( c1 != c2 ) {
    367 			return true;		// strings not equal
    368 		}
    369 	} while( c1 );
    370 	
    371 	return false;		// strings are equal
    372 }
    373 
    374 /*
    375 ========================
    376 idFileSystemLocal::GetFileLength
    377 ========================
    378 */
    379 int idFileSystemLocal::GetFileLength( const char * relativePath ) {
    380 	idFile *	f;
    381 	int			len;
    382 
    383 	if ( !IsInitialized() ) {
    384 		idLib::FatalError( "Filesystem call made without initialization" );
    385 	}
    386 
    387 	if ( !relativePath || !relativePath[0] ) {
    388 		idLib::Warning( "idFileSystemLocal::GetFileLength with empty name" );
    389 		return -1;
    390 	}
    391 
    392 	if ( resourceFiles.Num() > 0 ) {
    393 		idResourceCacheEntry rc;
    394 		if ( GetResourceCacheEntry( relativePath, rc ) ) {
    395 			return rc.length;
    396 		}
    397 	}
    398 
    399 	// look for it in the filesystem or pack files
    400 	f = OpenFileRead( relativePath, false );
    401 	if ( f == NULL ) {
    402 		return -1;
    403 	}
    404 
    405 	len = (int)f->Length();
    406 
    407 	delete f;
    408 	return len;
    409 }
    410 
    411 /*
    412 ================
    413 idFileSystemLocal::OpenOSFile
    414 ================
    415 */
    416 idFileHandle idFileSystemLocal::OpenOSFile( const char *fileName, fsMode_t mode ) {
    417 	idFileHandle fp;
    418 
    419 
    420 	DWORD dwAccess = 0;
    421 	DWORD dwShare = 0;
    422 	DWORD dwCreate = 0;
    423 	DWORD dwFlags = 0;
    424 
    425 	if ( mode == FS_WRITE ) {
    426 		dwAccess = GENERIC_READ | GENERIC_WRITE;
    427 		dwShare = FILE_SHARE_READ;
    428 		dwCreate = CREATE_ALWAYS;
    429 		dwFlags = FILE_ATTRIBUTE_NORMAL;
    430 	} else if ( mode == FS_READ ) {
    431 		dwAccess = GENERIC_READ;
    432 		dwShare = FILE_SHARE_READ;
    433 		dwCreate = OPEN_EXISTING;
    434 		dwFlags = FILE_ATTRIBUTE_NORMAL;
    435 	} else if ( mode == FS_APPEND ) {
    436 		dwAccess = GENERIC_READ | GENERIC_WRITE;
    437 		dwShare = FILE_SHARE_READ;
    438 		dwCreate = OPEN_ALWAYS;
    439 		dwFlags = FILE_ATTRIBUTE_NORMAL;
    440 					}
    441 
    442 	fp = CreateFile( fileName, dwAccess, dwShare, NULL, dwCreate, dwFlags, NULL );
    443 	if ( fp == INVALID_HANDLE_VALUE ) {
    444 		return NULL;
    445 				}
    446 	return fp;
    447 }
    448 
    449 /*
    450 ================
    451 idFileSystemLocal::CloseOSFile
    452 ================
    453 */
    454 void idFileSystemLocal::CloseOSFile( idFileHandle o ) {
    455 	::CloseHandle( o );
    456 }
    457 
    458 /*
    459 ================
    460 idFileSystemLocal::DirectFileLength
    461 ================
    462 */
    463 int idFileSystemLocal::DirectFileLength( idFileHandle o ) {
    464 	return GetFileSize( o, NULL );
    465 }
    466 
    467 /*
    468 ============
    469 idFileSystemLocal::CreateOSPath
    470 
    471 Creates any directories needed to store the given filename
    472 ============
    473 */
    474 void idFileSystemLocal::CreateOSPath( const char *OSPath ) {
    475 	char	*ofs;
    476 	
    477 	// make absolutely sure that it can't back up the path
    478 	// FIXME: what about c: ?
    479 	if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
    480 #ifdef _DEBUG		
    481 		common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath );
    482 #endif
    483 		return;
    484 	}
    485 
    486 	idStrStatic< MAX_OSPATH > path( OSPath );
    487 	path.SlashesToBackSlashes();
    488 	for( ofs = &path[ 1 ]; *ofs ; ofs++ ) {
    489 		if ( *ofs == PATHSEPARATOR_CHAR ) {	
    490 			// create the directory
    491 			*ofs = 0;
    492 			Sys_Mkdir( path );
    493 			*ofs = PATHSEPARATOR_CHAR;
    494 		}
    495 	}
    496 }
    497 
    498 /*
    499 =================
    500 idFileSystemLocal::EnableBackgroundCache
    501 =================
    502 */
    503 void idFileSystemLocal::EnableBackgroundCache( bool enable ) {
    504 	if ( !fs_enableBackgroundCaching.GetBool() ) {
    505 		return;
    506 	}
    507 }
    508 
    509 /*
    510 =================
    511 idFileSystemLocal::BeginLevelLoad
    512 =================
    513 */
    514 void idFileSystemLocal::BeginLevelLoad( const char *name, char *_blockBuffer, int _blockBufferSize ) {
    515 	
    516 	if ( name == NULL || *name == '\0' ) {
    517 		return;
    518 	}
    519 
    520 	resourceBufferPtr = ( byte* )_blockBuffer;
    521 	resourceBufferAvailable = _blockBufferSize;
    522 	resourceBufferSize = _blockBufferSize;
    523 
    524 	manifestName = name;
    525 
    526 	fileManifest.Clear();
    527 	preloadList.Clear();
    528 
    529 	EnableBackgroundCache( false );
    530 
    531 	ReOpenCacheFiles();
    532 	manifestName.StripPath();
    533 	
    534 	if ( resourceFiles.Num() > 0 ) {
    535 		AddResourceFile( va( "%s.resources", manifestName.c_str() ) );
    536 	}
    537 
    538 }
    539 
    540 /*
    541 =================
    542 idFileSystemLocal::UnloadResourceContainer
    543 
    544 =================	
    545 */
    546 void idFileSystemLocal::UnloadResourceContainer( const char *name ) {
    547 	if ( name == NULL || *name == '\0' ) {
    548 		return;
    549 	}
    550 	RemoveResourceFile( va( "%s.resources", name ) );
    551 }
    552 
    553 /*
    554 =================
    555 idFileSystemLocal::UnloadMapResources
    556 =================	
    557 */
    558 void idFileSystemLocal::UnloadMapResources( const char *name ) {
    559 	if ( name == NULL || *name == '\0' || idStr::Icmp( "_startup", name ) == 0 ) {
    560 		return;
    561 	}
    562 
    563 	if ( resourceFiles.Num() > 0 ) {
    564 		RemoveMapResourceFile( va( "%s.resources", name ) );
    565 	}
    566 }
    567 
    568 /*
    569 =================
    570 idFileSystemLocal::EndLevelLoad
    571 
    572 =================	
    573 */
    574 void idFileSystemLocal::EndLevelLoad() {
    575 	if ( fs_buildResources.GetBool() ) {
    576 		int saveCopyFiles = fs_copyfiles.GetInteger();
    577 		fs_copyfiles.SetInteger( 0 );
    578 
    579 		idStr manifestFileName = manifestName;
    580 		manifestFileName.StripPath();
    581 		manifestFileName.SetFileExtension( "manifest" );
    582 		manifestFileName.Insert( "maps/", 0 );
    583 		idFile *outFile = fileSystem->OpenFileWrite( manifestFileName );
    584 		if ( outFile != NULL ) {
    585 			int num = fileManifest.Num();
    586 			outFile->WriteBig( num );
    587 			for ( int i = 0; i < num; i++ ) {
    588 				outFile->WriteString( fileManifest[ i ] );
    589 			}
    590 			delete outFile;
    591 		}
    592 
    593 		idStrStatic< MAX_OSPATH > preloadName = manifestName;
    594 		preloadName.Insert( "maps/", 0 );
    595 		preloadName += ".preload";
    596 		idFile *fileOut = fileSystem->OpenFileWrite( preloadName, "fs_savepath" );
    597 		preloadList.WriteManifestToFile( fileOut );
    598 		delete fileOut;
    599 
    600 		fs_copyfiles.SetInteger( saveCopyFiles );
    601 	}
    602 
    603 	EnableBackgroundCache( true );
    604 
    605 	resourceBufferPtr = NULL;
    606 	resourceBufferAvailable = 0;
    607 	resourceBufferSize = 0;
    608 	
    609 }
    610 
    611 bool FileExistsInAllManifests( const char *filename, idList< idFileManifest > &manifests ) {
    612 	for ( int i = 0; i < manifests.Num(); i++ ) {
    613 		if ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) {
    614 			continue;
    615 		}
    616 		if ( strstr( manifests[ i ].GetManifestName(), "_pc" ) != NULL ) {
    617 			continue;
    618 		}
    619 		if ( manifests[ i ].FindFile( filename ) == -1 ) {
    620 			return false;
    621 		}
    622 	}
    623 	return true;
    624 }
    625 
    626 bool FileExistsInAllPreloadManifests( const char *filename, idList< idPreloadManifest > &manifests ) {
    627 	for ( int i = 0; i < manifests.Num(); i++ ) {
    628 		if ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) {
    629 			continue;
    630 		}
    631 		if ( manifests[ i ].FindResource( filename ) == -1 ) {
    632 			return false;
    633 		}
    634 	}
    635 	return true;
    636 }
    637 
    638 void RemoveFileFromAllManifests( const char *filename, idList< idFileManifest > &manifests ) {
    639 	for ( int i = 0; i < manifests.Num(); i++ ) {
    640 		if ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) {
    641 			continue;
    642 		}
    643 		if ( strstr( manifests[ i ].GetManifestName(), "_pc" ) != NULL ) {
    644 			continue;
    645 		}
    646 		manifests[ i ].RemoveAll( filename );
    647 	}
    648 }
    649 
    650 
    651 /*
    652 ================
    653 idFileSystemLocal::AddPerPlatformResources
    654 ================
    655 */
    656 void idFileSystemLocal::AddRenderProgs( idStrList &files ) {
    657 	idStrList work;
    658 
    659 	// grab all the renderprogs
    660 	idStr path = RelativePathToOSPath( "renderprogs/cgb", "fs_basepath" );
    661 	ListOSFiles( path, "*.cgb", work );
    662 	for ( int i = 0; i < work.Num(); i++ ) {
    663 		files.Append( idStr( "renderprogs/cgb/" ) + work[i] );
    664 	}
    665 
    666 	path = RelativePathToOSPath( "renderprogs/hlsl", "fs_basepath" );
    667 	ListOSFiles( path, "*.v360", work );
    668 	for ( int i = 0; i < work.Num(); i++ ) {
    669 		files.Append( idStr( "renderprogs/hlsl/" ) + work[i] );
    670 	}
    671 	ListOSFiles( path, "*.p360", work );
    672 	for ( int i = 0; i < work.Num(); i++ ) {
    673 		files.Append( idStr( "renderprogs/hlsl/" ) + work[i] );
    674 	}
    675 
    676 	path = RelativePathToOSPath( "renderprogs/gl", "fs_basepath" );
    677 	ListOSFiles( path, "*.*", work );
    678 	for ( int i = 0; i < work.Num(); i++ ) {
    679 		files.Append( idStr( "renderprogs/gl/" ) + work[i] );
    680 	}
    681 
    682 }
    683 
    684 /*
    685 ================
    686 idFileSystemLocal::AddSoundResources
    687 ================
    688 */
    689 void idFileSystemLocal::AddFonts( idStrList &files ) {
    690 	// temp fix for getting idaudio files in
    691 	idFileList *fl = ListFilesTree( "generated/images/newfonts", "*.bimage", false );
    692 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    693 		files.AddUnique( fl->GetList()[i] );
    694 	}
    695 	FreeFileList( fl );
    696 
    697 	fl = ListFilesTree( "newfonts", "*.dat", false );
    698 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    699 		files.AddUnique( fl->GetList()[i] );
    700 	}
    701 	FreeFileList( fl );
    702 }
    703 
    704 
    705 const char * excludeExtensions[] = {
    706 	".idxma", ".idmsf", ".idwav", ".xma", ".msf", ".wav", ".resource"
    707 };
    708 const int numExcludeExtensions = sizeof( excludeExtensions ) / sizeof( excludeExtensions[ 0 ] );
    709 
    710 bool IsExcludedFile( const idStr & resName ) {
    711 	for ( int k = 0; k < numExcludeExtensions; k++ ) {
    712 		if ( resName.Find( excludeExtensions[ k ], false ) >= 0 ) {
    713 			return true;
    714 		}
    715 	}
    716 	return false;
    717 }
    718 
    719 /*
    720 ================
    721 idFileSystemLocal::IsBinaryModel
    722 ================
    723 */
    724 bool idFileSystemLocal::IsBinaryModel( const idStr & resName ) const {
    725 	idStrStatic< 32 > ext;
    726 	resName.ExtractFileExtension( ext );
    727 	if ( ( ext.Icmp( "base" ) == 0 ) || ( ext.Icmp( "blwo" ) == 0 ) || ( ext.Icmp( "bflt" ) == 0 ) || ( ext.Icmp( "bma" ) == 0 ) ) {
    728 		return true;
    729 	}
    730 	return false;
    731 }
    732 
    733 /*
    734 ================
    735 idFileSystemLocal::IsSoundSample
    736 ================
    737 */
    738 bool idFileSystemLocal::IsSoundSample( const idStr & resName ) const {
    739 	idStrStatic< 32 > ext;
    740 	resName.ExtractFileExtension( ext );
    741 	if ( ( ext.Icmp( "idxma" ) == 0 ) || ( ext.Icmp( "idwav" ) == 0 ) || ( ext.Icmp( "idmsf" ) == 0 ) || ( ext.Icmp( "xma" ) == 0 ) || ( ext.Icmp( "wav" ) == 0 ) || ( ext.Icmp( "msf" ) == 0 ) || ( ext.Icmp( "msadpcm" ) == 0 ) ) {
    742 		return true;
    743 	}
    744 	return false;
    745 }
    746 
    747 
    748 void idFileSystemLocal::BuildOrderedStartupContainer() {
    749 	idStrList orderedFiles( 1024 );
    750 
    751 	idFileList * fl = ListFilesTree( "materials", "*.mtr", true );
    752 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    753 		orderedFiles.AddUnique( fl->GetList()[i] );
    754 	}
    755 	FreeFileList( fl );
    756 
    757 	fl = ListFilesTree( "renderprogs", "*.v360", true );
    758 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    759 		orderedFiles.AddUnique( fl->GetList()[i] );
    760 	}
    761 	FreeFileList( fl );
    762 
    763 	fl = ListFilesTree( "renderprogs", "*.p360", true );
    764 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    765 		orderedFiles.AddUnique( fl->GetList()[i] );
    766 	}
    767 	FreeFileList( fl );
    768 
    769 	fl = ListFilesTree( "renderprogs", "*.cgb", true );
    770 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    771 		orderedFiles.AddUnique( fl->GetList()[i] );
    772 	}
    773 	FreeFileList( fl );
    774 
    775 	fl = ListFilesTree( "renderprogs/gl", "*.*", true );
    776 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    777 		orderedFiles.AddUnique( fl->GetList()[i] );
    778 	}
    779 	FreeFileList( fl );
    780 
    781 	fl = ListFilesTree( "skins", "*.skin", true );
    782 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    783 		orderedFiles.AddUnique( fl->GetList()[i] );
    784 	}
    785 	FreeFileList( fl );
    786 
    787 	fl = ListFilesTree( "sound", "*.sndshd", false );
    788 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    789 		orderedFiles.AddUnique( fl->GetList()[i] );
    790 	}
    791 	FreeFileList( fl );
    792 
    793 	fl = ListFilesTree( "def", "*.def", false );
    794 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    795 		orderedFiles.AddUnique( fl->GetList()[i] );
    796 	}
    797 	FreeFileList( fl );
    798 
    799 	fl = ListFilesTree( "fx", "*.fx", false );
    800 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    801 		orderedFiles.AddUnique( fl->GetList()[i] );
    802 	}
    803 	FreeFileList( fl );
    804 
    805 	fl = ListFilesTree( "particles", "*.prt", false );
    806 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    807 		orderedFiles.AddUnique( fl->GetList()[i] );
    808 	}
    809 	FreeFileList( fl );
    810 
    811 	fl = ListFilesTree( "af", "*.af", false );
    812 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    813 		orderedFiles.AddUnique( fl->GetList()[i] );
    814 	}
    815 	FreeFileList( fl );
    816 	fl = ListFilesTree( "newpdas", "*.pda", false );
    817 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    818 		orderedFiles.AddUnique( fl->GetList()[i] );
    819 	}
    820 	FreeFileList( fl );
    821 
    822 	orderedFiles.Append( "script/doom_main.script" );
    823 	orderedFiles.Append( "script/doom_defs.script" );
    824 	orderedFiles.Append( "script/doom_defs.script" );
    825 	orderedFiles.Append( "script/doom_events.script" );
    826 	orderedFiles.Append( "script/doom_util.script" );
    827 	orderedFiles.Append( "script/weapon_base.script" );
    828 	orderedFiles.Append( "script/ai_base.script" );
    829 	orderedFiles.Append( "script/weapon_fists.script" );
    830 	orderedFiles.Append( "script/weapon_pistol.script" );
    831 	orderedFiles.Append( "script/weapon_shotgun.script" );
    832 	orderedFiles.Append( "script/weapon_machinegun.script" );
    833 	orderedFiles.Append( "script/weapon_chaingun.script" );
    834 	orderedFiles.Append( "script/weapon_handgrenade.script" );
    835 	orderedFiles.Append( "script/weapon_plasmagun.script" );
    836 	orderedFiles.Append( "script/weapon_rocketlauncher.script" );
    837 	orderedFiles.Append( "script/weapon_bfg.script" );
    838 	orderedFiles.Append( "script/weapon_soulcube.script" );
    839 	orderedFiles.Append( "script/weapon_chainsaw.script" );
    840 	orderedFiles.Append( "script/weapon_flashlight.script" );
    841 	orderedFiles.Append( "script/weapon_pda.script" );
    842 	orderedFiles.Append( "script/ai_monster_base.script" );
    843 	orderedFiles.Append( "script/ai_monster_zombie_base.script" );
    844 	orderedFiles.Append( "script/ai_monster_demon_archvile.script" );
    845 	orderedFiles.Append( "script/ai_monster_demon_cherub.script" );
    846 	orderedFiles.Append( "script/ai_monster_demon_hellknight.script" );
    847 	orderedFiles.Append( "script/ai_monster_demon_imp.script" );
    848 	orderedFiles.Append( "script/ai_monster_demon_maggot.script" );
    849 	orderedFiles.Append( "script/ai_monster_demon_mancubus.script" );
    850 	orderedFiles.Append( "script/ai_monster_demon_pinky.script" );
    851 	orderedFiles.Append( "script/ai_monster_demon_revenant.script" );
    852 	orderedFiles.Append( "script/ai_monster_demon_trite.script" );
    853 	orderedFiles.Append( "script/ai_monster_demon_wraith.script" );
    854 	orderedFiles.Append( "script/ai_monster_flying_lostsoul.script" );
    855 	orderedFiles.Append( "script/ai_monster_flying_cacodemon.script" );
    856 	orderedFiles.Append( "script/ai_monster_zombie.script" );
    857 	orderedFiles.Append( "script/ai_monster_zombie_morgue.script" );
    858 	orderedFiles.Append( "script/ai_monster_zombie_sawyer.script" );
    859 	orderedFiles.Append( "script/ai_monster_zombie_bernie.script" );
    860 	orderedFiles.Append( "script/ai_monster_zombie_commando_cgun.script" );
    861 	orderedFiles.Append( "script/ai_monster_zombie_commando_tentacle.script" );
    862 	orderedFiles.Append( "script/ai_monster_zombie_security_pistol.script" );
    863 	orderedFiles.Append( "script/ai_monster_turret.script" );
    864 	orderedFiles.Append( "script/ai_monster_boss_vagary.script" );
    865 	orderedFiles.Append( "script/ai_monster_boss_cyberdemon.script" );
    866 	orderedFiles.Append( "script/ai_monster_boss_guardian.script" );
    867 	orderedFiles.Append( "script/ai_monster_boss_guardian_seeker.script" );
    868 	orderedFiles.Append( "script/ai_monster_boss_sabaoth.script" );
    869 	orderedFiles.Append( "script/ai_character.script" );
    870 	orderedFiles.Append( "script/ai_character_prone.script" );
    871 	orderedFiles.Append( "script/ai_character_sentry.script" );
    872 	orderedFiles.Append( "script/ai_player.script" );
    873 	orderedFiles.Append( "script/ai_alphalabs2_scientist1.script" );
    874 	orderedFiles.Append( "script/ai_cinematic_le.script" );
    875 	orderedFiles.Append( "script/map_admin1.script" );
    876 	orderedFiles.Append( "script/map_alphalabs1.script" );
    877 	orderedFiles.Append( "script/map_alphalabs2.script" );
    878 	orderedFiles.Append( "script/map_alphalabs3.script" );
    879 	orderedFiles.Append( "script/map_alphalabs3_crane.script" );
    880 	orderedFiles.Append( "script/map_alphalabs4.script" );
    881 	orderedFiles.Append( "script/map_caves.script" );
    882 	orderedFiles.Append( "script/map_caves2.script" );
    883 	orderedFiles.Append( "script/map_comm1.script" );
    884 	orderedFiles.Append( "script/map_commoutside_lift.script" );
    885 	orderedFiles.Append( "script/map_commoutside.script" );
    886 	orderedFiles.Append( "script/map_cpu.script" );
    887 	orderedFiles.Append( "script/map_cpuboss.script" );
    888 	orderedFiles.Append( "script/map_delta1.script" );
    889 	orderedFiles.Append( "script/map_delta2a.script" );
    890 	orderedFiles.Append( "script/map_delta2b.script" );
    891 	orderedFiles.Append( "script/map_delta3.script" );
    892 	orderedFiles.Append( "script/map_delta5.script" );
    893 	orderedFiles.Append( "script/map_enpro.script" );
    894 	orderedFiles.Append( "script/map_hell1.script" );
    895 	orderedFiles.Append( "script/map_hellhole.script" );
    896 	orderedFiles.Append( "script/map_recycling1.script" );
    897 	orderedFiles.Append( "script/map_recycling2.script" );
    898 	orderedFiles.Append( "script/map_site3.script" );
    899 	orderedFiles.Append( "script/map_marscity1.script" );
    900 	orderedFiles.Append( "script/map_marscity2.script" );
    901 	orderedFiles.Append( "script/map_mc_underground.script" );
    902 	orderedFiles.Append( "script/map_monorail.script" );
    903 	orderedFiles.Append( "script/d3xp_events.script" );
    904 	orderedFiles.Append( "script/weapon_bloodstone_passive.script" );
    905 	orderedFiles.Append( "script/weapon_bloodstone_active1.script" );
    906 	orderedFiles.Append( "script/weapon_bloodstone_active2.script" );
    907 	orderedFiles.Append( "script/weapon_bloodstone_active3.script" );
    908 	orderedFiles.Append( "script/weapon_shotgun_double.script" );
    909 	orderedFiles.Append( "script/weapon_grabber.script" );
    910 	orderedFiles.Append( "script/ai_monster_hunter_helltime.script" );
    911 	orderedFiles.Append( "script/ai_monster_hunter_berserk.script" );
    912 	orderedFiles.Append( "script/ai_monster_hunter_invul.script" );
    913 	orderedFiles.Append( "script/ai_monster_boss_maledict.script" );
    914 	orderedFiles.Append( "script/ai_monster_demon_vulgar.script" );
    915 	orderedFiles.Append( "script/ai_monster_demon_d3xp_bruiser.script" );
    916 	orderedFiles.Append( "script/ai_monster_dummy_target.script" );
    917 	orderedFiles.Append( "script/ai_monster_dummy.script" );
    918 	orderedFiles.Append( "script/ai_monster_demon_sentry.script" );
    919 	orderedFiles.Append( "script/ai_monster_demon_trite_jump.script" );
    920 	orderedFiles.Append( "script/ai_monster_turret_ancient.script" );
    921 	orderedFiles.Append( "script/ai_monster_flying_forgotten.script" );
    922 	orderedFiles.Append( "script/ai_character_erebus3.script" );
    923 	orderedFiles.Append( "script/d3xp_airlock.script" );
    924 	orderedFiles.Append( "script/d3xp_bloodstone.script" );
    925 	orderedFiles.Append( "script/map_erebus1.script" );
    926 	orderedFiles.Append( "script/map_erebus2_helltime.script" );
    927 	orderedFiles.Append( "script/map_erebus2.script" );
    928 	orderedFiles.Append( "script/map_erebus3.script" );
    929 	orderedFiles.Append( "script/map_erebus4.script" );
    930 	orderedFiles.Append( "script/map_erebus5.script" );
    931 	orderedFiles.Append( "script/map_erebus5_cloud.script" );
    932 	orderedFiles.Append( "script/map_erebus6.script" );
    933 	orderedFiles.Append( "script/map_erebus6_berzerk.script" );
    934 	orderedFiles.Append( "script/map_phobos1.script" );
    935 	orderedFiles.Append( "script/map_phobos2.script" );
    936 	orderedFiles.Append( "script/map_phobos2_invul.script" );
    937 	orderedFiles.Append( "script/map_phobos3.script" );
    938 	orderedFiles.Append( "script/map_phobos4.script" );
    939 	orderedFiles.Append( "script/map_deltax.script" );
    940 	orderedFiles.Append( "script/map_hell.script" );
    941 	orderedFiles.Append( "script/map_maledict.script" );
    942 	orderedFiles.Append( "script/d3le-ai_monster_boss_guardian2.script" );
    943 	orderedFiles.Append( "script/ai_follower.script" );
    944 	orderedFiles.Append( "generated/swf/shell.bswf" );
    945 	fl = ListFilesTree( "newfonts", "*.dat", false );
    946 	for ( int i = 0; i < fl->GetList().Num(); i++ ) {
    947 		orderedFiles.AddUnique( fl->GetList()[i] );
    948 	}
    949 	FreeFileList( fl );
    950 
    951 	idResourceContainer::WriteResourceFile( "_ordered.resources", orderedFiles, false );
    952 }
    953 
    954 /*
    955 ================
    956 idFileSystemLocal::WriteResourcePacks
    957 ================
    958 */
    959 void idFileSystemLocal::WriteResourcePacks() {
    960 
    961 	idStrList filesNotCommonToAllMaps( 16384 );		// files that are not shared by all maps, used to trim the common list
    962 	idStrList filesCommonToAllMaps( 16384 );		// files that are shared by all maps, will include startup files, renderprogs etc..
    963 	idPreloadManifest commonPreloads;				// preload entries that exist in all map preload files
    964 
    965 	idStr path = RelativePathToOSPath( "maps/", "fs_savepath" ); 
    966 
    967 	idStrList manifestFiles;										
    968 	ListOSFiles( path, ".manifest", manifestFiles );
    969 	idStrList preloadFiles;
    970 	ListOSFiles( path, ".preload", preloadFiles );
    971 
    972 	idList< idFileManifest > manifests;				// list of all manifest files
    973 	// load all file manifests
    974 	for ( int i = 0; i < manifestFiles.Num(); i++ ) {
    975 		idStr path = "maps/";
    976 		path += manifestFiles[ i ];
    977 		idFileManifest manifest;
    978 		if ( manifest.LoadManifest( path ) ) {
    979 			//manifest.Print();
    980 			manifest.RemoveAll( va( "strings/%s", ID_LANG_ENGLISH ) );	// remove all .lang files
    981 			manifest.RemoveAll( va( "strings/%s", ID_LANG_FRENCH ) );
    982 			manifest.RemoveAll( va( "strings/%s", ID_LANG_ITALIAN ) );
    983 			manifest.RemoveAll( va( "strings/%s", ID_LANG_GERMAN ) );
    984 			manifest.RemoveAll( va( "strings/%s", ID_LANG_SPANISH ) );
    985 			manifest.RemoveAll( va( "strings/%s", ID_LANG_JAPANESE ) );
    986 			manifests.Append( manifest );
    987 		}
    988 	}
    989 
    990 	idList< idPreloadManifest > preloadManifests;	// list of all preload manifest files
    991 	// load all preload manifests
    992 	for ( int i = 0; i < preloadFiles.Num(); i++ ) {
    993 		idStr path = "maps/";
    994 		path += preloadFiles[ i ];
    995 		if ( path.Find( "_startup", false ) >= 0 ) {
    996 			continue;
    997 		}
    998 		idPreloadManifest preload;
    999 		if ( preload.LoadManifest( path ) ) {
   1000 			preloadManifests.Append( preload );
   1001 			//preload.Print();
   1002 		}
   1003 	}
   1004 
   1005 	// build common list of files
   1006 	for ( int i = 0; i < manifests.Num(); i++ ) {
   1007 		idFileManifest &manifest = manifests[ i ];
   1008 		for ( int j = 0; j < manifest.NumFiles(); j++ ) {
   1009 			idStr name = manifest.GetFileNameByIndex( j );
   1010 			if ( name.CheckExtension( ".cfg" ) || (name.Find( ".lang", false ) >= 0) ) {
   1011 				continue;
   1012 			}
   1013 			if ( FileExistsInAllManifests( name, manifests ) ) {
   1014 				filesCommonToAllMaps.AddUnique( name );
   1015 			} else {
   1016 				filesNotCommonToAllMaps.AddUnique( name );
   1017 			}
   1018 		}
   1019 	}
   1020 	// common list of preload reosurces, image, sample or models 
   1021 	for ( int i = 0; i < preloadManifests.Num(); i++ ) {
   1022 		idPreloadManifest &preload = preloadManifests[ i ];
   1023 		for ( int j = 0; j < preload.NumResources(); j++ ) {
   1024 			idStr name = preload.GetResourceNameByIndex( j );
   1025 			if ( FileExistsInAllPreloadManifests( name, preloadManifests ) ) {
   1026 				commonPreloads.Add( preload.GetPreloadByIndex( j ) );
   1027 				idLib::Printf( "Common preload added %s\n", name.c_str() );
   1028 			} else {
   1029 				idLib::Printf( "preload missed %s\n", name.c_str() );
   1030 			}
   1031 		}
   1032 	}
   1033 
   1034 	AddRenderProgs( filesCommonToAllMaps );
   1035 	AddFonts( filesCommonToAllMaps );
   1036 
   1037 	idStrList work;
   1038 
   1039 	// remove all common files from each map manifest
   1040 	for ( int i = 0; i < manifests.Num(); i++ ) {
   1041 		if ( ( strstr( manifests[ i ].GetManifestName(), "_startup" ) != NULL ) || ( strstr( manifests[ i ].GetManifestName(), "_pc" ) != NULL ) ) {
   1042 			continue;
   1043 		}
   1044 		//idLib::Printf( "%04d referenced files for %s\n", manifests[ i ].GetReferencedFileCount(), manifests[ i ].GetManifestName() );
   1045 
   1046 		for ( int j = 0; j < filesCommonToAllMaps.Num(); j++ ) {
   1047 			manifests[ i ].RemoveAll( filesCommonToAllMaps[ j ] );
   1048 		}
   1049 		//idLib::Printf( "%04d referenced files for %s\n", manifests[ i ].GetReferencedFileCount(), manifests[ i ].GetManifestName() );
   1050 	}
   1051 
   1052 	idStrList commonImages( 2048 );
   1053 	idStrList commonModels( 2048 );
   1054 	idStrList commonAnims( 2048 );
   1055 	idStrList commonCollision( 2048 );
   1056 	idStrList soundFiles( 2048 );		// don't write these per map so we fit on disc
   1057 
   1058 	for ( int i = 0; i < manifests.Num(); i++ ) {
   1059 		idStr resourceFileName = manifests[ i ].GetManifestName();
   1060 		if ( resourceFileName.Find( "_startup.manifest", false ) >= 0 ) {
   1061 			// add all the startup manifest files to the common list
   1062 			for ( int j = 0; j < manifests[ i ].NumFiles(); j++ ) {
   1063 				idStr check = manifests[i].GetFileNameByIndex( j );
   1064 				if ( check.CheckExtension( ".cfg" ) == false ) {
   1065 					filesCommonToAllMaps.AddUnique( check.c_str() );
   1066 				}
   1067 			}
   1068 			continue;
   1069 		}
   1070 
   1071 		idStaticList< idStr, 16384 > mapFiles;		// map files from the manifest, these are static for easy debugging
   1072 		idStaticList< idStr, 16384 > mapFilesTwo;	// accumulates non bimage, bmodel and sample files
   1073 		commonImages.Clear();	// collect images and models separately so they can be added in linear preload order
   1074 		commonModels.Clear();
   1075 		commonAnims.Clear();
   1076 		commonCollision.Clear();
   1077 
   1078 		manifests[ i ].PopulateList( mapFiles );
   1079 
   1080 		for ( int j = 0; j < mapFiles.Num(); j++ ) {
   1081 			idStr & resName = mapFiles[ j ];
   1082 			if ( resName.Find( ".bimage", false ) >= 0 ) {
   1083 				commonImages.AddUnique( resName );
   1084 				continue;
   1085 			}
   1086 			if ( IsBinaryModel( resName ) ) {
   1087 				commonModels.AddUnique( resName );
   1088 				continue;
   1089 			}
   1090 			if ( IsSoundSample( resName ) ) {
   1091 				soundFiles.AddUnique( resName );
   1092 				continue;
   1093 			}
   1094 			if ( resName.Find( ".bik", false ) >= 0 ) {
   1095 				// don't add bik files
   1096 				continue;
   1097 			}
   1098 			if ( resName.Find ( ".bmd5anim", false ) >= 0 ) {
   1099 				commonAnims.AddUnique( resName );
   1100 				continue;
   1101 			}
   1102 			if ( resName.Find ( ".bcmodel", false ) >= 0 ) {
   1103 				commonCollision.AddUnique( resName );
   1104 				continue;
   1105 			}
   1106 			if ( resName.Find( ".lang", false ) >= 0 ) {
   1107 				continue;
   1108 			}
   1109 			mapFilesTwo.AddUnique( resName );
   1110 		}
   1111 
   1112 		for ( int j = 0; j < commonImages.Num(); j++ ) {
   1113 			mapFilesTwo.AddUnique( commonImages[ j ] );
   1114 		}
   1115 		for ( int j = 0; j < commonModels.Num(); j++ ) {
   1116 			mapFilesTwo.AddUnique( commonModels[ j ] );
   1117 		}
   1118 		for ( int j = 0; j < commonAnims.Num(); j++ ) {
   1119 			mapFilesTwo.AddUnique( commonAnims[ j ] );
   1120 		}
   1121 		for ( int j = 0; j < commonCollision.Num(); j++ ) {
   1122 			mapFilesTwo.AddUnique( commonCollision[ j ] );
   1123 		}
   1124 		// write map resources
   1125 		idStrList mapFilesToWrite;
   1126 		for ( int j = 0; j < mapFilesTwo.Num(); j++ ) {
   1127 			mapFilesToWrite.Append( mapFilesTwo[ j ] );
   1128 		}
   1129 		idResourceContainer::WriteResourceFile( resourceFileName, mapFilesToWrite, false );
   1130 	}
   1131 
   1132 	// add  the new manifests just written
   1133 	path = RelativePathToOSPath( "maps", "fs_savepath" );
   1134 	ListOSFiles( path, "*.preload", work );
   1135 	for ( int i = 0; i < work.Num(); i++ ) {
   1136 		filesCommonToAllMaps.Append( idStr( "maps/" ) + work[ i ] );
   1137 	}
   1138 
   1139 	filesCommonToAllMaps.Append( "_common.preload" );
   1140 
   1141 	// write out common models, images and sounds to separate containers
   1142 	//idStrList commonSounds( 2048 );
   1143 	commonImages.Clear();
   1144 	commonModels.Clear();
   1145 
   1146 	idStrList commonFiles;
   1147 	for ( int i = 0; i < filesCommonToAllMaps.Num(); i++ ) {
   1148 		idStr & resName = filesCommonToAllMaps[ i ];
   1149 		if ( resName.Find( ".bimage", false ) >= 0 ) {
   1150 			commonImages.AddUnique( resName );
   1151 			continue;
   1152 		}
   1153 		if ( IsBinaryModel( resName ) ) {
   1154 			commonModels.AddUnique( resName );
   1155 			continue;
   1156 		}
   1157 		if ( IsSoundSample( resName ) ) {
   1158 			soundFiles.AddUnique( resName );
   1159 			continue;
   1160 		}
   1161 		if ( resName.Find( ".bik", false ) >= 0 ) {
   1162 			// no bik files in the .resource
   1163 			continue;
   1164 		}
   1165 		if ( resName.Find( ".lang", false ) >= 0 ) {
   1166 			// no bik files in the .resource
   1167 			continue;
   1168 		}
   1169 		commonFiles.AddUnique( resName );
   1170 	}
   1171 
   1172 	for ( int j = 0; j < commonImages.Num(); j++ ) {
   1173 		commonFiles.AddUnique( commonImages[ j ] );
   1174 	}
   1175 	for ( int j = 0; j < commonModels.Num(); j++ ) {
   1176 		commonFiles.AddUnique( commonModels[ j ] );
   1177 	}
   1178 
   1179 	//idResourceContainer::WriteResourceFile( "_common_images", commonImages );
   1180 	//idResourceContainer::WriteResourceFile( "_common_models", commonModels );
   1181 
   1182 	commonPreloads.WriteManifest( "_common.preload" );
   1183 	idResourceContainer::WriteResourceFile( "_common", commonFiles, false );
   1184 
   1185 
   1186 	idList< idStrList > soundOutputFiles;
   1187 	soundOutputFiles.SetNum( 16 );
   1188 
   1189 	struct soundVOInfo_t {
   1190 		const char *filename;
   1191 		const char *voqualifier;
   1192 		idStrList * samples;
   1193 	};
   1194 	const soundVOInfo_t soundFileInfo[] = {
   1195 		{ "fr", "sound/vo/french/", &soundOutputFiles[ 0 ] },
   1196 		{ "it", "sound/vo/italian/", &soundOutputFiles[ 1 ] },
   1197 		{ "gr", "sound/vo/german/", &soundOutputFiles[ 2 ] },
   1198 		{ "sp", "sound/vo/spanish/", &soundOutputFiles[ 3 ] },
   1199 		{ "jp", "sound/vo/japanese/", &soundOutputFiles[ 4 ] },
   1200 		{ "en", "sound/vo/", &soundOutputFiles[ 5 ] }	// english last so the other langs are culled first
   1201 	};
   1202 	const int numSoundFiles = sizeof( soundFileInfo ) / sizeof ( soundVOInfo_t );
   1203 
   1204 	for ( int k = soundFiles.Num() - 1; k > 0; k-- ) {
   1205 		for ( int l = 0; l < numSoundFiles; l++ ) {
   1206 			if ( soundFiles[ k ].Find( soundFileInfo[ l ].voqualifier, false ) >= 0 ) {
   1207 				soundFileInfo[ l ].samples->AddUnique( soundFiles[ k ] );
   1208 				soundFiles.RemoveIndex( k );
   1209 			}
   1210 		}
   1211 	}
   1212 
   1213 	for ( int k = 0; k < numSoundFiles; k++ ) {
   1214 		idStrList & sampleList = *soundFileInfo[ k ].samples;
   1215 
   1216 		// write pc
   1217 		idResourceContainer::WriteResourceFile( va( "_sound_pc_%s", soundFileInfo[ k ].filename ), sampleList, false );
   1218 		for ( int l = 0; l < sampleList.Num(); l++ ) {
   1219 			sampleList[ l ].Replace( ".idwav", ".idxma" );
   1220 		}
   1221 	}
   1222 
   1223 	idResourceContainer::WriteResourceFile( "_sound_pc", soundFiles, false );
   1224 	for ( int k = 0; k < soundFiles.Num(); k++ ) {
   1225 		soundFiles[ k ].Replace( ".idwav", ".idxma" );
   1226 	}
   1227 
   1228 	for ( int k = 0; k < soundFiles.Num(); k++ ) {
   1229 		soundFiles[ k ].Replace( ".idxma", ".idmsf" );
   1230 	}
   1231 
   1232 	BuildOrderedStartupContainer();
   1233 
   1234 	ClearResourcePacks();
   1235 }
   1236 
   1237 
   1238 /*
   1239 =================
   1240 idFileSystemLocal::CopyFile
   1241 
   1242 Copy a fully specified file from one place to another`
   1243 =================
   1244 */
   1245 void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) {
   1246 		
   1247 	idFile * src = OpenExplicitFileRead( fromOSPath );
   1248 	if ( src == NULL ) {
   1249 		idLib::Warning( "Could not open %s for read", fromOSPath );
   1250 		return;
   1251 	}
   1252 
   1253 	if ( idStr::Icmp( fromOSPath, toOSPath ) == 0 ) {
   1254 		// same file can happen during build games
   1255 		return;
   1256 	}
   1257 
   1258 	CopyFile( src, toOSPath );
   1259 	delete src;
   1260 
   1261 	if ( strstr( fromOSPath, ".wav" ) != NULL ) {
   1262 		idStrStatic< MAX_OSPATH > newFromPath = fromOSPath;
   1263 		idStrStatic< MAX_OSPATH > newToPath = toOSPath;
   1264 
   1265 		idLib::Printf( "Copying console samples for %s\n", newFromPath.c_str() );
   1266 		newFromPath.SetFileExtension( "xma" );
   1267 		newToPath.SetFileExtension( "xma" );
   1268 		src = OpenExplicitFileRead( newFromPath );
   1269 		if ( src == NULL ) {
   1270 			idLib::Warning( "Could not open %s for read", newFromPath.c_str() );
   1271 		} else {
   1272 			CopyFile( src, newToPath );
   1273 			delete src;
   1274 			src = NULL;
   1275 		}
   1276 
   1277 		newFromPath.SetFileExtension( "msf" );
   1278 		newToPath.SetFileExtension( "msf" );
   1279 		src = OpenExplicitFileRead( newFromPath );
   1280 		if ( src == NULL ) {
   1281 			idLib::Warning( "Could not open %s for read", newFromPath.c_str() );
   1282 		} else {
   1283 			CopyFile( src, newToPath );
   1284 			delete src;
   1285 		}
   1286 
   1287 		newFromPath.BackSlashesToSlashes();
   1288 		newFromPath.ToLower();
   1289 		if ( newFromPath.Find( "/vo/", false ) >= 0 ) {
   1290 			for ( int i = 0; i < Sys_NumLangs(); i++ ) {
   1291 				const char *lang = Sys_Lang( i );
   1292 				if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) {
   1293 					continue;
   1294 				}
   1295 				newFromPath = fromOSPath;
   1296 				newToPath = toOSPath;
   1297 				newFromPath.BackSlashesToSlashes();
   1298 				newFromPath.ToLower();
   1299 				newToPath.BackSlashesToSlashes();
   1300 				newToPath.ToLower();
   1301 				newFromPath.Replace( "/vo/", va( "/vo/%s/", lang ) );
   1302 				newToPath.Replace( "/vo/", va( "/vo/%s/", lang ) );
   1303 
   1304 				src = OpenExplicitFileRead( newFromPath );
   1305 				if ( src == NULL ) {
   1306 					idLib::Warning( "LOCALIZATION PROBLEM: Could not open %s for read", newFromPath.c_str() );
   1307 				} else {
   1308 					CopyFile( src, newToPath );
   1309 					delete src;
   1310 					src = NULL;
   1311 				}
   1312 
   1313 				newFromPath.SetFileExtension( "xma" );
   1314 				newToPath.SetFileExtension( "xma" );
   1315 				src = OpenExplicitFileRead( newFromPath );
   1316 				if ( src == NULL ) {
   1317 					idLib::Warning( "LOCALIZATION PROBLEM: Could not open %s for read", newFromPath.c_str() );
   1318 				} else {
   1319 					CopyFile( src, newToPath );
   1320 					delete src;
   1321 					src = NULL;
   1322 				}
   1323 
   1324 				newFromPath.SetFileExtension( "msf" );
   1325 				newToPath.SetFileExtension( "msf" );
   1326 				src = OpenExplicitFileRead( newFromPath );
   1327 				if ( src == NULL ) {
   1328 					idLib::Warning( "LOCALIZATION PROBLEM: Could not open %s for read", newFromPath.c_str() );
   1329 				} else {
   1330 					CopyFile( src, newToPath );
   1331 					delete src;
   1332 				}
   1333 
   1334 			}
   1335 		} 
   1336 	}
   1337 }
   1338 
   1339 /*
   1340 =================
   1341 idFileSystemLocal::CopyFile
   1342 =================
   1343 */
   1344 void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) {
   1345 	idFile * dst = OpenExplicitFileWrite( toOSPath );
   1346 	if ( dst == NULL ) {
   1347 		idLib::Warning( "Could not open %s for write", toOSPath );
   1348 		return;
   1349 	}
   1350 
   1351 	common->Printf( "copy %s to %s\n", src->GetName(), toOSPath );
   1352 
   1353 	int len = src->Length();
   1354 	int copied = 0;
   1355 	while ( copied < len ) {
   1356 		byte buffer[4096];
   1357 		int read = src->Read( buffer, Min( 4096, len - copied ) );
   1358 		if ( read <= 0 ) {
   1359 			idLib::Warning( "Copy failed during read" );
   1360 			break;
   1361 	}
   1362 		int written = dst->Write( buffer, read );
   1363 		if ( written < read ) {
   1364 			idLib::Warning( "Copy failed during write" );
   1365 			break;
   1366 	}
   1367 		copied += written;
   1368 	}
   1369 
   1370 	delete dst;
   1371 }
   1372 
   1373 /*
   1374 ====================
   1375 idFileSystemLocal::ReplaceSeparators
   1376 
   1377 Fix things up differently for win/unix/mac
   1378 ====================
   1379 */
   1380 void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) {
   1381 	char *s;
   1382 
   1383 	for( s = &path[ 0 ]; *s ; s++ ) {
   1384 		if ( *s == '/' || *s == '\\' ) {
   1385 			*s = sep;
   1386 		}
   1387 	}
   1388 }
   1389 
   1390 /*
   1391 ========================
   1392 IsOSPath
   1393 ========================
   1394 */
   1395 static bool IsOSPath( const char * path ) {
   1396 	assert( path );
   1397 
   1398 	if ( idStr::Icmpn( path, "mtp:", 4 ) == 0 ) {
   1399 		return true;
   1400 	}
   1401 
   1402 
   1403 	if ( idStr::Length( path ) >= 2 ) {
   1404 		if ( path[ 1 ] == ':' ) {
   1405 			if ( ( path[ 0 ] > 64 && path[ 0 ] < 91 ) || ( path[ 0 ] > 96 && path[ 0 ] < 123 ) ) {
   1406 				// already an OS path starting with a drive.
   1407 				return true;
   1408 			}
   1409 		}
   1410 		if ( path[ 0 ] == '\\' || path[ 0 ] == '/' ) {
   1411 			// a root path
   1412 			return true;
   1413 		}
   1414 	}
   1415 	return false;
   1416 }
   1417 
   1418 /*
   1419 ========================
   1420 idFileSystemLocal::BuildOSPath
   1421 ========================
   1422 */
   1423 const char * idFileSystemLocal::BuildOSPath( const char * base, const char * relativePath ) {
   1424 	// handle case of this already being an OS path
   1425 	if ( IsOSPath( relativePath ) ) {
   1426 		return relativePath;
   1427 	}
   1428 
   1429 	return BuildOSPath( base, gameFolder, relativePath );
   1430 }
   1431 
   1432 /*
   1433 ===================
   1434 idFileSystemLocal::BuildOSPath
   1435 ===================
   1436 */
   1437 const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) {
   1438 	static char OSPath[MAX_STRING_CHARS];
   1439 	idStr newPath;
   1440 
   1441 	// handle case of this already being an OS path
   1442 	if ( IsOSPath( relativePath ) ) {
   1443 		return relativePath;
   1444 	}
   1445 
   1446 	idStr strBase = base;
   1447 	strBase.StripTrailing( '/' );
   1448 	strBase.StripTrailing( '\\' );
   1449 	sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath );
   1450 	ReplaceSeparators( newPath );
   1451 	idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
   1452 	return OSPath;
   1453 }
   1454 
   1455 /*
   1456 ================
   1457 idFileSystemLocal::OSPathToRelativePath
   1458 
   1459 takes a full OS path, as might be found in data from a media creation
   1460 program, and converts it to a relativePath by stripping off directories
   1461 
   1462 Returns false if the osPath tree doesn't match any of the existing
   1463 search paths.
   1464 
   1465 ================
   1466 */
   1467 const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) {
   1468 	if ( ( OSPath[0] != '/' ) && ( OSPath[0] != '\\' ) && ( idStr::FindChar( OSPath, ':' ) < 0 ) ) {
   1469 		// No colon and it doesn't start with a slash... it must already be a relative path
   1470 		return OSPath;
   1471 	}
   1472 	idStaticList< idStrStatic< 32 >, 5 > basePaths;
   1473 	basePaths.Append( "base" );
   1474 	basePaths.Append( "d3xp" );
   1475 	basePaths.Append( "d3le" );
   1476 	if ( fs_game.GetString()[0] != 0 ) {
   1477 		basePaths.Append( fs_game.GetString() );
   1478 	}
   1479 	if ( fs_game_base.GetString()[0] != 0 ) {
   1480 		basePaths.Append( fs_game_base.GetString() );
   1481 	}
   1482 	idStaticList<int, MAX_OSPATH> slashes;
   1483 	for ( const char * s = OSPath; *s != 0; s++ ) {
   1484 		if ( *s == '/' || *s == '\\' ) {
   1485 			slashes.Append( s - OSPath );
   1486 		}
   1487 	}
   1488 	for ( int n = 0; n < slashes.Num() - 1; n++ ) {
   1489 		const char * start = OSPath + slashes[n] + 1;
   1490 		const char * end = OSPath + slashes[n+1];
   1491 		int componentLength = end - start;
   1492 		if ( componentLength == 0 ) {
   1493 			continue;
   1494 		}
   1495 		for ( int i = 0; i < basePaths.Num(); i++ ) {
   1496 			if ( componentLength != basePaths[i].Length() ) {
   1497 				continue;
   1498 			}
   1499 			if ( basePaths[i].Icmpn( start, componentLength ) == 0 ) {
   1500 				// There are some files like:
   1501 				// W:\d3xp\base\...
   1502 				// But we can't search backwards because there are others like:
   1503 				// W:\doom3\base\models\mapobjects\base\...
   1504 				// So instead we check for 2 base paths next to each other and take the 2nd in that case
   1505 				if ( n < slashes.Num() - 2 ) {
   1506 					const char * start2 = OSPath + slashes[n+1] + 1;
   1507 					const char * end2 = OSPath + slashes[n+2];
   1508 					int componentLength2 = end2 - start2;
   1509 					if ( componentLength2 > 0 ) {
   1510 						for ( int j = 0; j < basePaths.Num(); j++ ) {
   1511 							if ( componentLength2 != basePaths[j].Length() ) {
   1512 								continue;
   1513 							}
   1514 							if ( basePaths[j].Icmpn( start2, basePaths[j].Length() ) == 0 ) {
   1515 								return end2 + 1;
   1516 							}
   1517 						}
   1518 					}
   1519 				}
   1520 				return end + 1;
   1521 			}
   1522 		}
   1523 	}
   1524 	idLib::Warning( "OSPathToRelativePath failed on %s", OSPath );
   1525 	return OSPath;
   1526 }
   1527 
   1528 /*
   1529 =====================
   1530 idFileSystemLocal::RelativePathToOSPath
   1531 
   1532 Returns a fully qualified path that can be used with stdio libraries
   1533 =====================
   1534 */
   1535 const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) {
   1536 	const char *path = cvarSystem->GetCVarString( basePath );
   1537 	if ( !path[0] ) {
   1538 		path = fs_savepath.GetString();
   1539 	}
   1540 	return BuildOSPath( path, gameFolder, relativePath );
   1541 }
   1542 
   1543 /*
   1544 =================
   1545 idFileSystemLocal::RemoveFile
   1546 =================
   1547 */
   1548 void idFileSystemLocal::RemoveFile( const char *relativePath ) {
   1549 	idStr OSPath;
   1550 
   1551 	if ( fs_basepath.GetString()[0] ) {
   1552 		OSPath = BuildOSPath( fs_basepath.GetString(), gameFolder, relativePath );
   1553 		::DeleteFile( OSPath );
   1554 	}
   1555 
   1556 	OSPath = BuildOSPath( fs_savepath.GetString(), gameFolder, relativePath );
   1557 	::DeleteFile( OSPath );
   1558 }
   1559 
   1560 /*
   1561 ========================
   1562 idFileSystemLocal::RemoveDir
   1563 ========================
   1564 */
   1565 bool idFileSystemLocal::RemoveDir( const char * relativePath ) {
   1566 	bool success = true;
   1567 	if ( fs_savepath.GetString()[0] ) {
   1568 		success &= Sys_Rmdir( BuildOSPath( fs_savepath.GetString(), relativePath ) );
   1569 	}
   1570 	success &= Sys_Rmdir( BuildOSPath( fs_basepath.GetString(), relativePath ) );
   1571 	return success;
   1572 }
   1573 
   1574 /*
   1575 ============
   1576 idFileSystemLocal::ReadFile
   1577 
   1578 Filename are relative to the search path
   1579 a null buffer will just return the file length and time without loading
   1580 timestamp can be NULL if not required
   1581 ============
   1582 */
   1583 int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ) {
   1584 
   1585 	idFile *	f;
   1586 	byte *		buf;
   1587 	int			len;
   1588 	bool		isConfig;
   1589 
   1590 	if ( !IsInitialized() ) {
   1591 		common->FatalError( "Filesystem call made without initialization\n" );
   1592 		return 0;
   1593 	}
   1594 
   1595 	if ( relativePath == NULL || !relativePath[0] ) {
   1596 		common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" );
   1597 		return 0;
   1598 	}
   1599 
   1600 	if ( timestamp ) {
   1601 		*timestamp = FILE_NOT_FOUND_TIMESTAMP;
   1602 	}
   1603 
   1604 	if ( buffer ) {
   1605 		*buffer = NULL;
   1606 	} 
   1607 
   1608 	if ( buffer == NULL && timestamp != NULL && resourceFiles.Num() > 0 ) {
   1609 		static idResourceCacheEntry rc;
   1610 		int size = 0;
   1611 		if ( GetResourceCacheEntry( relativePath, rc ) ) {
   1612 			*timestamp = 0;
   1613 			size = rc.length;
   1614 		} 
   1615 		return size;
   1616 	}
   1617 
   1618 	buf = NULL;	// quiet compiler warning
   1619 
   1620 	// if this is a .cfg file and we are playing back a journal, read
   1621 	// it from the journal file
   1622 	if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) {
   1623 		isConfig = true;
   1624 		if ( eventLoop && eventLoop->JournalLevel() == 2 ) {
   1625 			int		r;
   1626 
   1627 			loadCount++;
   1628 			loadStack++;
   1629 
   1630 			common->DPrintf( "Loading %s from journal file.\n", relativePath );
   1631 			len = 0;
   1632 			r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) );
   1633 			if ( r != sizeof( len ) ) {
   1634 				*buffer = NULL;
   1635 				return -1;
   1636 			}
   1637 			buf = (byte *)Mem_ClearedAlloc(len+1, TAG_IDFILE);
   1638 			*buffer = buf;
   1639 			r = eventLoop->com_journalDataFile->Read( buf, len );
   1640 			if ( r != len ) {
   1641 				common->FatalError( "Read from journalDataFile failed" );
   1642 			}
   1643 
   1644 			// guarantee that it will have a trailing 0 for string operations
   1645 			buf[len] = 0;
   1646 
   1647 			return len;
   1648 		}
   1649 	} else {
   1650 		isConfig = false;
   1651 	}
   1652 
   1653 	// look for it in the filesystem or pack files
   1654 	f = OpenFileRead( relativePath, ( buffer != NULL ) );
   1655 	if ( f == NULL ) {
   1656 		if ( buffer ) {
   1657 			*buffer = NULL;
   1658 		}
   1659 		return -1;
   1660 	}
   1661 	len = f->Length();
   1662 
   1663 	if ( timestamp ) {
   1664 		*timestamp = f->Timestamp();
   1665 	}
   1666 	
   1667 	if ( !buffer ) {
   1668 		CloseFile( f );
   1669 		return len;
   1670 	}
   1671 
   1672 	loadCount++;
   1673 	loadStack++;
   1674 
   1675 	buf = (byte *)Mem_ClearedAlloc(len+1, TAG_IDFILE);
   1676 	*buffer = buf;
   1677 
   1678 	f->Read( buf, len );
   1679 
   1680 	// guarantee that it will have a trailing 0 for string operations
   1681 	buf[len] = 0;
   1682 	CloseFile( f );
   1683 
   1684 	// if we are journalling and it is a config file, write it to the journal file
   1685 	if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) {
   1686 		common->DPrintf( "Writing %s to journal file.\n", relativePath );
   1687 		eventLoop->com_journalDataFile->Write( &len, sizeof( len ) );
   1688 		eventLoop->com_journalDataFile->Write( buf, len );
   1689 		eventLoop->com_journalDataFile->Flush();
   1690 	}
   1691 
   1692 	return len;
   1693 }
   1694 
   1695 /*
   1696 =============
   1697 idFileSystemLocal::FreeFile
   1698 =============
   1699 */
   1700 void idFileSystemLocal::FreeFile( void *buffer ) {
   1701 	if ( !IsInitialized() ) {
   1702 		common->FatalError( "Filesystem call made without initialization\n" );
   1703 	}
   1704 	if ( !buffer ) {
   1705 		common->FatalError( "idFileSystemLocal::FreeFile( NULL )" );
   1706 	}
   1707 	loadStack--;
   1708 
   1709 	Mem_Free( buffer );
   1710 }
   1711 
   1712 /*
   1713 ============
   1714 idFileSystemLocal::WriteFile
   1715 
   1716 Filenames are relative to the search path
   1717 ============
   1718 */
   1719 int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) {
   1720 	idFile *f;
   1721 
   1722 	if ( !IsInitialized() ) {
   1723 		common->FatalError( "Filesystem call made without initialization\n" );
   1724 	}
   1725 
   1726 	if ( !relativePath || !buffer ) {
   1727 		common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" );
   1728 	}
   1729 
   1730 	f = idFileSystemLocal::OpenFileWrite( relativePath, basePath );
   1731 	if ( !f ) {
   1732 		common->Printf( "Failed to open %s\n", relativePath );
   1733 		return -1;
   1734 	}
   1735 
   1736 	size = f->Write( buffer, size );
   1737 
   1738 	CloseFile( f );
   1739 
   1740 	return size;
   1741 }
   1742 
   1743 /*
   1744 ========================
   1745 idFileSystemLocal::RenameFile
   1746 ========================
   1747 */
   1748 bool idFileSystemLocal::RenameFile( const char * relativePath, const char * newName, const char * basePath ) {
   1749 	const char * path = cvarSystem->GetCVarString( basePath );
   1750 	if ( !path[0] ) {
   1751 		path = fs_savepath.GetString();
   1752 	}
   1753 
   1754 	idStr oldOSPath = BuildOSPath( path, gameFolder, relativePath );
   1755 	idStr newOSPath = BuildOSPath( path, gameFolder, newName );
   1756 
   1757 	// this gives atomic-delete-on-rename, like POSIX rename()
   1758 	// There is a MoveFileTransacted() on vista and above, not sure if that means there
   1759 	// is a race condition inside MoveFileEx...
   1760 	const bool success = ( MoveFileEx( oldOSPath.c_str(), newOSPath.c_str(), MOVEFILE_REPLACE_EXISTING ) != 0 );
   1761 
   1762 	if ( !success ) {
   1763 		const int err = GetLastError();
   1764 		idLib::Warning( "RenameFile( %s, %s ) error %i", newOSPath.c_str(), oldOSPath.c_str(), err );
   1765 	}
   1766 	return success;
   1767 }
   1768 
   1769 /*
   1770 ===============
   1771 idFileSystemLocal::AddUnique
   1772 ===============
   1773 */
   1774 int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const {
   1775 	int i, hashKey;
   1776 
   1777 	hashKey = hashIndex.GenerateKey( name );
   1778 	for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) {
   1779 		if ( list[i].Icmp( name ) == 0 ) {
   1780 			return i;
   1781 		}
   1782 	}
   1783 	i = list.Append( name );
   1784 	hashIndex.Add( hashKey, i );
   1785 	return i;
   1786 }
   1787 
   1788 /*
   1789 ===============
   1790 idFileSystemLocal::GetExtensionList
   1791 ===============
   1792 */
   1793 void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const {
   1794 	int s, e, l;
   1795 
   1796 	l = idStr::Length( extension );
   1797 	s = 0;
   1798 	while( 1 ) {
   1799 		e = idStr::FindChar( extension, '|', s, l );
   1800 		if ( e != -1 ) {
   1801 			extensionList.Append( idStr( extension, s, e ) );
   1802 			s = e + 1;
   1803 		} else {
   1804 			extensionList.Append( idStr( extension, s, l ) );
   1805 			break;
   1806 		}
   1807 	}
   1808 }
   1809 
   1810 /*
   1811 ===============
   1812 idFileSystemLocal::GetFileList
   1813 
   1814 Does not clear the list first so this can be used to progressively build a file list.
   1815 When 'sort' is true only the new files added to the list are sorted.
   1816 ===============
   1817 */
   1818 int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char * gamedir ) {
   1819 	if ( !IsInitialized() ) {
   1820 		common->FatalError( "Filesystem call made without initialization\n" );
   1821 	}
   1822 
   1823 	if ( !extensions.Num() ) {
   1824 		return 0;
   1825 	}
   1826 
   1827 	if ( !relativePath ) {
   1828 		return 0;
   1829 	}
   1830 
   1831 	int pathLength = strlen( relativePath );
   1832 	if ( pathLength ) {
   1833 		pathLength++;	// for the trailing '/'
   1834 	}
   1835 
   1836 	idStrStatic< MAX_OSPATH > strippedName;
   1837 	if ( resourceFiles.Num() > 0 ) {
   1838 		int idx = resourceFiles.Num() - 1;
   1839 		while ( idx >= 0 ) {
   1840 			for ( int i = 0; i < resourceFiles[ idx ]->cacheTable.Num(); i++ ) {
   1841 				idResourceCacheEntry & rt = resourceFiles[ idx ]->cacheTable[ i ];
   1842 				// if the name is not long anough to at least contain the path
   1843 
   1844 				if ( rt.filename.Length() <= pathLength ) {
   1845 					continue;
   1846 				}
   1847 
   1848 				// check for a path match without the trailing '/'
   1849 				if ( pathLength && idStr::Icmpn( rt.filename, relativePath, pathLength - 1 ) != 0 ) {
   1850 					continue;
   1851 				}
   1852  
   1853 				// ensure we have a path, and not just a filename containing the path
   1854 				if ( rt.filename[ pathLength ] == '\0' || rt.filename[pathLength - 1] != '/' ) {
   1855 					continue;
   1856 				}
   1857  
   1858 				// make sure the file is not in a subdirectory
   1859 				int j = pathLength;
   1860 				for ( ; rt.filename[j+1] != '\0'; j++ ) {
   1861 					if ( rt.filename[ j ] == '/' ) {
   1862 						break;
   1863 					}
   1864 				}
   1865 				if ( rt.filename[ j + 1 ] ) {
   1866 					continue;
   1867 				}
   1868 
   1869 				// check for extension match
   1870 				for ( j = 0; j < extensions.Num(); j++ ) {
   1871 					if ( rt.filename.Length() >= extensions[j].Length() && extensions[j].Icmp( rt.filename.c_str() +   rt.filename.Length() - extensions[j].Length() ) == 0 ) {
   1872 						break;
   1873 					}
   1874 				}
   1875 				if ( j >= extensions.Num() ) {
   1876 					continue;
   1877 				}
   1878 
   1879 				// unique the match
   1880 				if ( fullRelativePath ) {
   1881 					idStr work = relativePath;
   1882 					work += "/";
   1883 					work += rt.filename.c_str() + pathLength;
   1884 					work.StripTrailing( '/' );
   1885 					AddUnique( work, list, hashIndex );
   1886 				} else {
   1887 					idStr work = rt.filename.c_str() + pathLength;
   1888 					work.StripTrailing( '/' );
   1889 					AddUnique( work, list, hashIndex );
   1890 				}
   1891 			}
   1892 			idx--;
   1893 		}
   1894 	}
   1895 
   1896 	// search through the path, one element at a time, adding to list
   1897 	for ( int sp = searchPaths.Num() - 1; sp >= 0; sp-- ) {
   1898 		if ( gamedir != NULL && gamedir[0] != 0 ) {
   1899 			if ( searchPaths[sp].gamedir != gamedir) {
   1900 				continue;
   1901 			}
   1902 		}
   1903 
   1904 		idStr netpath = BuildOSPath( searchPaths[sp].path, searchPaths[sp].gamedir, relativePath );
   1905 
   1906 		for ( int i = 0; i < extensions.Num(); i++ ) {
   1907 
   1908 			// scan for files in the filesystem
   1909 			idStrList sysFiles;
   1910 			ListOSFiles( netpath, extensions[i], sysFiles );
   1911 
   1912 			// if we are searching for directories, remove . and ..
   1913 			if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) {
   1914 				sysFiles.Remove( "." );
   1915 				sysFiles.Remove( ".." );
   1916 			}
   1917 
   1918 			for ( int j = 0; j < sysFiles.Num(); j++ ) {
   1919 				// unique the match
   1920 				if ( fullRelativePath ) {
   1921 					idStr work = relativePath;
   1922 					work += "/";
   1923 					work += sysFiles[j];
   1924 					AddUnique( work, list, hashIndex );
   1925 				} else {
   1926 					AddUnique( sysFiles[j], list, hashIndex );
   1927 				}
   1928 			}
   1929 		}
   1930 	}
   1931 
   1932 	return list.Num();
   1933 }
   1934 
   1935 /*
   1936 ===============
   1937 idFileSystemLocal::ListFiles
   1938 ===============
   1939 */
   1940 idFileList *idFileSystemLocal::ListFiles( const char *relativePath, const char *extension, bool sort, bool fullRelativePath, const char* gamedir ) {
   1941 	idHashIndex hashIndex( 4096, 4096 );
   1942 	idStrList extensionList;
   1943 
   1944 	idFileList *fileList = new (TAG_IDFILE) idFileList;
   1945 	fileList->basePath = relativePath;
   1946 
   1947 	GetExtensionList( extension, extensionList );
   1948 
   1949 	GetFileList( relativePath, extensionList, fileList->list, hashIndex, fullRelativePath, gamedir );
   1950 
   1951 	if ( sort ) {
   1952 		fileList->list.SortWithTemplate( idSort_PathStr() );
   1953 	}
   1954 
   1955 	return fileList;
   1956 }
   1957 
   1958 /*
   1959 ===============
   1960 idFileSystemLocal::GetFileListTree
   1961 ===============
   1962 */
   1963 int idFileSystemLocal::GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir ) {
   1964 	int i;
   1965 	idStrList slash, folders( 128 );
   1966 	idHashIndex folderHashIndex( 1024, 128 );
   1967 
   1968 	// recurse through the subdirectories
   1969 	slash.Append( "/" );
   1970 	GetFileList( relativePath, slash, folders, folderHashIndex, true, gamedir );
   1971 	for ( i = 0; i < folders.Num(); i++ ) {
   1972 		if ( folders[i][0] == '.' ) {
   1973 			continue;
   1974 		}
   1975 		if ( folders[i].Icmp( relativePath ) == 0 ){
   1976 			continue;
   1977 		}
   1978 		GetFileListTree( folders[i], extensions, list, hashIndex, gamedir );
   1979 	}
   1980 
   1981 	// list files in the current directory
   1982 	GetFileList( relativePath, extensions, list, hashIndex, true, gamedir );
   1983 
   1984 	return list.Num();
   1985 }
   1986 
   1987 /*
   1988 ===============
   1989 idFileSystemLocal::ListFilesTree
   1990 ===============
   1991 */
   1992 idFileList *idFileSystemLocal::ListFilesTree( const char *relativePath, const char *extension, bool sort, const char* gamedir ) {
   1993 	idHashIndex hashIndex( 4096, 4096 );
   1994 	idStrList extensionList;
   1995 
   1996 	idFileList *fileList = new (TAG_IDFILE) idFileList();
   1997 	fileList->basePath = relativePath;
   1998 	fileList->list.SetGranularity( 4096 );
   1999 
   2000 	GetExtensionList( extension, extensionList );
   2001 
   2002 	GetFileListTree( relativePath, extensionList, fileList->list, hashIndex, gamedir );
   2003 
   2004 	if ( sort ) {
   2005 		fileList->list.SortWithTemplate( idSort_PathStr() );
   2006 	}
   2007 
   2008 	return fileList;
   2009 }
   2010 
   2011 /*
   2012 ===============
   2013 idFileSystemLocal::FreeFileList
   2014 ===============
   2015 */
   2016 void idFileSystemLocal::FreeFileList( idFileList *fileList ) {
   2017 	delete fileList;
   2018 }
   2019 
   2020 /*
   2021 ===============
   2022 idFileSystemLocal::ListOSFiles
   2023 
   2024  call to the OS for a listing of files in an OS directory
   2025 ===============
   2026 */
   2027 int	idFileSystemLocal::ListOSFiles( const char *directory, const char *extension, idStrList &list ) {
   2028 	if ( !extension ) {
   2029 		extension = "";
   2030 	}
   2031 
   2032 	return Sys_ListFiles( directory, extension, list );
   2033 }
   2034 
   2035 /*
   2036 ================
   2037 idFileSystemLocal::Dir_f
   2038 ================
   2039 */
   2040 void idFileSystemLocal::Dir_f( const idCmdArgs &args ) {
   2041 	idStr		relativePath;
   2042 	idStr		extension;
   2043 	idFileList *fileList;
   2044 	int			i;
   2045 
   2046 	if ( args.Argc() < 2 || args.Argc() > 3 ) {
   2047 		common->Printf( "usage: dir <directory> [extension]\n" );
   2048 		return;
   2049 	}
   2050 
   2051 	if ( args.Argc() == 2 ) {
   2052 		relativePath = args.Argv( 1 );
   2053 		extension = "";
   2054 	}
   2055 	else {
   2056 		relativePath = args.Argv( 1 );
   2057 		extension = args.Argv( 2 );
   2058 		if ( extension[0] != '.' ) {
   2059 			common->Warning( "extension should have a leading dot" );
   2060 		}
   2061 	}
   2062 	relativePath.BackSlashesToSlashes();
   2063 	relativePath.StripTrailing( '/' );
   2064 
   2065 	common->Printf( "Listing of %s/*%s\n", relativePath.c_str(), extension.c_str() );
   2066 	common->Printf( "---------------\n" );
   2067 
   2068 	fileList = fileSystemLocal.ListFiles( relativePath, extension );
   2069 
   2070 	for ( i = 0; i < fileList->GetNumFiles(); i++ ) {
   2071 		common->Printf( "%s\n", fileList->GetFile( i ) );
   2072 	}
   2073 	common->Printf( "%d files\n", fileList->list.Num() );
   2074 
   2075 	fileSystemLocal.FreeFileList( fileList );
   2076 }
   2077 
   2078 /*
   2079 ================
   2080 idFileSystemLocal::DirTree_f
   2081 ================
   2082 */
   2083 void idFileSystemLocal::DirTree_f( const idCmdArgs &args ) {
   2084 	idStr		relativePath;
   2085 	idStr		extension;
   2086 	idFileList *fileList;
   2087 	int			i;
   2088 
   2089 	if ( args.Argc() < 2 || args.Argc() > 3 ) {
   2090 		common->Printf( "usage: dirtree <directory> [extension]\n" );
   2091 		return;
   2092 	}
   2093 
   2094 	if ( args.Argc() == 2 ) {
   2095 		relativePath = args.Argv( 1 );
   2096 		extension = "";
   2097 	}
   2098 	else {
   2099 		relativePath = args.Argv( 1 );
   2100 		extension = args.Argv( 2 );
   2101 		if ( extension[0] != '.' ) {
   2102 			common->Warning( "extension should have a leading dot" );
   2103 		}
   2104 	}
   2105 	relativePath.BackSlashesToSlashes();
   2106 	relativePath.StripTrailing( '/' );
   2107 
   2108 	common->Printf( "Listing of %s/*%s /s\n", relativePath.c_str(), extension.c_str() );
   2109 	common->Printf( "---------------\n" );
   2110 
   2111 	fileList = fileSystemLocal.ListFilesTree( relativePath, extension );
   2112 
   2113 	for ( i = 0; i < fileList->GetNumFiles(); i++ ) {
   2114 		common->Printf( "%s\n", fileList->GetFile( i ) );
   2115 	}
   2116 	common->Printf( "%d files\n", fileList->list.Num() );
   2117 
   2118 	fileSystemLocal.FreeFileList( fileList );
   2119 }
   2120 
   2121 /*
   2122 ================
   2123 idFileSystemLocal::ClearResourcePacks
   2124 ================
   2125 */
   2126 void idFileSystemLocal::ClearResourcePacks() {
   2127 }
   2128 
   2129 /*
   2130 ================
   2131 idFileSystemLocal::BuildGame_f
   2132 ================
   2133 */
   2134 void idFileSystemLocal::BuildGame_f( const idCmdArgs &args ) {
   2135 	fileSystemLocal.WriteResourcePacks();
   2136 }
   2137 
   2138 /*
   2139 ================
   2140 idFileSystemLocal::WriteResourceFile_f
   2141 ================
   2142 */
   2143 void idFileSystemLocal::WriteResourceFile_f( const idCmdArgs &args ) {
   2144 	if ( args.Argc() != 2 ) {
   2145 		common->Printf( "Usage: writeResourceFile <manifest file>\n" );
   2146 		return;
   2147 	}
   2148 
   2149 	idStrList manifest;
   2150 	idResourceContainer::ReadManifestFile( args.Argv( 1 ), manifest );
   2151 	idResourceContainer::WriteResourceFile( args.Argv( 1 ), manifest, false );
   2152 }
   2153 
   2154 
   2155 /*
   2156 ================
   2157 idFileSystemLocal::UpdateResourceFile_f
   2158 ================
   2159 */
   2160 void idFileSystemLocal::UpdateResourceFile_f( const idCmdArgs &args ) {
   2161 	if ( args.Argc() < 3  ) {
   2162 		common->Printf( "Usage: updateResourceFile <resource file> <files>\n" );
   2163 		return;
   2164 	}
   2165 
   2166 	idStr filename =  args.Argv( 1 );
   2167 	idStrList filesToAdd;
   2168 	for ( int i = 2; i < args.Argc(); i++ ) {
   2169 		filesToAdd.Append( args.Argv( i ) );
   2170 	}
   2171 	idResourceContainer::UpdateResourceFile( filename, filesToAdd );
   2172 }
   2173 
   2174 /*
   2175 ================
   2176 idFileSystemLocal::ExtractResourceFile_f
   2177 ================
   2178 */
   2179 void idFileSystemLocal::ExtractResourceFile_f( const idCmdArgs &args ) {
   2180 	if ( args.Argc() < 3  ) {
   2181 		common->Printf( "Usage: extractResourceFile <resource file> <outpath> <copysound>\n" );
   2182 		return;
   2183 	}
   2184 
   2185 	idStr filename =  args.Argv( 1 );
   2186 	idStr outPath = args.Argv( 2 );
   2187 	bool copyWaves = ( args.Argc() > 3 );
   2188 	idResourceContainer::ExtractResourceFile( filename, outPath, copyWaves );
   2189 }
   2190 
   2191 /*
   2192 ============
   2193 idFileSystemLocal::Path_f
   2194 ============
   2195 */
   2196 void idFileSystemLocal::Path_f( const idCmdArgs &args ) {
   2197 	common->Printf( "Current search path:\n" );
   2198 	for ( int i = 0; i < fileSystemLocal.searchPaths.Num(); i++ ) {
   2199 		common->Printf( "%s/%s\n", fileSystemLocal.searchPaths[i].path.c_str(), fileSystemLocal.searchPaths[i].gamedir.c_str() );
   2200 	}
   2201 }
   2202 
   2203 /*
   2204 ============
   2205 idFileSystemLocal::TouchFile_f
   2206 
   2207 The only purpose of this function is to allow game script files to copy
   2208 arbitrary files furing an "fs_copyfiles 1" run.
   2209 ============
   2210 */
   2211 void idFileSystemLocal::TouchFile_f( const idCmdArgs &args ) {
   2212 	idFile *f;
   2213 
   2214 	if ( args.Argc() != 2 ) {
   2215 		common->Printf( "Usage: touchFile <file>\n" );
   2216 		return;
   2217 	}
   2218 
   2219 	f = fileSystemLocal.OpenFileRead( args.Argv( 1 ) );
   2220 	if ( f ) {
   2221 		fileSystemLocal.CloseFile( f );
   2222 	}
   2223 }
   2224 
   2225 /*
   2226 ============
   2227 idFileSystemLocal::TouchFileList_f
   2228 
   2229 Takes a text file and touches every file in it, use one file per line.
   2230 ============
   2231 */
   2232 void idFileSystemLocal::TouchFileList_f( const idCmdArgs &args ) {
   2233 	
   2234 	if ( args.Argc() != 2 ) {
   2235 		common->Printf( "Usage: touchFileList <filename>\n" );
   2236 		return;
   2237 	}
   2238 
   2239 	const char *buffer = NULL;
   2240 	idParser src( LEXFL_NOFATALERRORS | LEXFL_NOSTRINGCONCAT | LEXFL_ALLOWMULTICHARLITERALS | LEXFL_ALLOWBACKSLASHSTRINGCONCAT );
   2241 	if ( fileSystem->ReadFile( args.Argv( 1 ), ( void** )&buffer, NULL ) && buffer ) {
   2242 		src.LoadMemory( buffer, strlen( buffer ), args.Argv( 1 ) );
   2243 		if ( src.IsLoaded() ) {
   2244 			idToken token;
   2245 			while( src.ReadToken( &token ) ) {
   2246 				common->Printf( "%s\n", token.c_str() );
   2247 				const bool captureToImage = false;
   2248 				common->UpdateScreen( captureToImage );
   2249 				idFile *f = fileSystemLocal.OpenFileRead( token );
   2250 				if ( f ) {
   2251 					fileSystemLocal.CloseFile( f );
   2252 				}
   2253 			}
   2254 		}
   2255 	}
   2256 
   2257 }
   2258 
   2259 /*
   2260 ============
   2261 idFileSystemLocal::GenerateResourceCRCs_f
   2262 
   2263 Generates a CRC checksum file for each .resources file.
   2264 ============
   2265 */
   2266 void idFileSystemLocal::GenerateResourceCRCs_f( const idCmdArgs &args ) {
   2267 	idLib::Printf( "Generating CRCs for resource files...\n" );
   2268 
   2269 	std::auto_ptr<idFileList> baseResourceFileList( fileSystem->ListFiles( ".", ".resources" ) );
   2270 	if ( baseResourceFileList.get() != NULL ) {
   2271 		CreateCRCsForResourceFileList ( *baseResourceFileList );
   2272 	}
   2273 
   2274 	std::auto_ptr<idFileList> mapResourceFileList( fileSystem->ListFilesTree( "maps", ".resources" ) );
   2275 	if ( mapResourceFileList.get() != NULL ) {
   2276 		CreateCRCsForResourceFileList ( *mapResourceFileList );
   2277 	}
   2278 
   2279 	idLib::Printf( "Done generating CRCs for resource files.\n" );
   2280 }
   2281 
   2282 /*
   2283 ================
   2284 idFileSystemLocal::CreateCRCsForResourceFileList
   2285 ================
   2286 */
   2287 void idFileSystemLocal::CreateCRCsForResourceFileList( const idFileList & list ) {
   2288 	for ( int fileIndex = 0; fileIndex < list.GetNumFiles(); ++fileIndex ) {
   2289 		idLib::Printf( " Processing %s.\n", list.GetFile( fileIndex ) );
   2290 
   2291 		std::auto_ptr<idFile_Memory> currentFile( static_cast<idFile_Memory *>( fileSystem->OpenFileReadMemory( list.GetFile( fileIndex ) ) ) );
   2292 
   2293 		if ( currentFile.get() == NULL ) {
   2294 			idLib::Printf( " Error reading %s.\n", list.GetFile( fileIndex ) );
   2295 			continue;
   2296 		}
   2297 
   2298 		uint32 resourceMagic;
   2299 		currentFile->ReadBig( resourceMagic );
   2300 
   2301 		if ( resourceMagic != RESOURCE_FILE_MAGIC ) {
   2302 			idLib::Printf( "Resource file magic number doesn't match, skipping %s.\n", list.GetFile( fileIndex ) );
   2303 			continue;
   2304 		}
   2305 
   2306 		int tableOffset;
   2307 		currentFile->ReadBig( tableOffset );
   2308 
   2309 		int tableLength;
   2310 		currentFile->ReadBig( tableLength );
   2311 
   2312 		// Read in the table
   2313 		currentFile->Seek( tableOffset, FS_SEEK_SET );
   2314 
   2315 		int numFileResources;
   2316 		currentFile->ReadBig( numFileResources );
   2317 
   2318 		idList< idResourceCacheEntry > cacheEntries;
   2319 		cacheEntries.SetNum( numFileResources );
   2320 
   2321 		for ( int innerFileIndex = 0; innerFileIndex < numFileResources; ++innerFileIndex ) {
   2322 			cacheEntries[innerFileIndex].Read( currentFile.get() );
   2323 		}
   2324 
   2325 		// All tables read, now seek to each one and calculate the CRC.
   2326 		idTempArray< unsigned long > innerFileCRCs( numFileResources );
   2327 		for ( int innerFileIndex = 0; innerFileIndex < numFileResources; ++innerFileIndex ) {
   2328 			const char * innerFileDataBegin = currentFile->GetDataPtr() + cacheEntries[innerFileIndex].offset;
   2329 
   2330 			innerFileCRCs[innerFileIndex] = CRC32_BlockChecksum( innerFileDataBegin, cacheEntries[innerFileIndex].length );
   2331 		}
   2332 
   2333 		// Get the CRC for all the CRCs.
   2334 		const unsigned long totalCRC = CRC32_BlockChecksum( innerFileCRCs.Ptr(), innerFileCRCs.Size() );
   2335 
   2336 		// Write the .crc file corresponding to the .resources file.
   2337 		idStr crcFilename = list.GetFile( fileIndex );
   2338 		crcFilename.SetFileExtension( ".crc" );
   2339 		std::auto_ptr<idFile> crcOutputFile( fileSystem->OpenFileWrite( crcFilename, "fs_basepath" ) );
   2340 		if ( crcOutputFile.get() == NULL ) {
   2341 			idLib::Printf( "Error writing CRC file %s.\n", crcFilename );
   2342 			continue;
   2343 		}
   2344 		
   2345 		const uint32 CRC_FILE_MAGIC = 0xCC00CC00; // I just made this up, it has no meaning.
   2346 		const uint32 CRC_FILE_VERSION = 1;
   2347 		crcOutputFile->WriteBig( CRC_FILE_MAGIC );
   2348 		crcOutputFile->WriteBig( CRC_FILE_VERSION );
   2349 		crcOutputFile->WriteBig( totalCRC );
   2350 		crcOutputFile->WriteBig( numFileResources );
   2351 		crcOutputFile->WriteBigArray( innerFileCRCs.Ptr(), numFileResources );
   2352 	}
   2353 }
   2354 
   2355 /*
   2356 ================
   2357 idFileSystemLocal::AddResourceFile
   2358 ================
   2359 */
   2360 int idFileSystemLocal::AddResourceFile( const char * resourceFileName ) {
   2361 	idStrStatic< MAX_OSPATH > resourceFile = va( "maps/%s", resourceFileName );
   2362 	idResourceContainer *rc = new idResourceContainer();
   2363 	if ( rc->Init( resourceFile, resourceFiles.Num() ) ) {
   2364 		resourceFiles.Append( rc );
   2365 		common->Printf( "Loaded resource file %s\n", resourceFile.c_str() );
   2366 		return resourceFiles.Num() - 1;
   2367 	} 
   2368 	return -1;
   2369 }
   2370 
   2371 /*
   2372 ================
   2373 idFileSystemLocal::FindResourceFile
   2374 ================
   2375 */
   2376 int idFileSystemLocal::FindResourceFile( const char * resourceFileName ) {
   2377 	for ( int i = 0; i < resourceFiles.Num(); i++ ) {
   2378 		if ( idStr::Icmp( resourceFileName, resourceFiles[ i ]->GetFileName() ) == 0 ) {
   2379 			return i;
   2380 		}
   2381 	}
   2382 	return -1;
   2383 }
   2384 /*
   2385 ================
   2386 idFileSystemLocal::RemoveResourceFileByIndex
   2387 ================
   2388 */
   2389 void idFileSystemLocal::RemoveResourceFileByIndex( const int &idx ) {
   2390 	if ( idx >= 0 && idx < resourceFiles.Num() ) {
   2391 		if ( idx >= 0 && idx < resourceFiles.Num() ) {
   2392 			delete resourceFiles[ idx ];
   2393 			resourceFiles.RemoveIndex( idx );
   2394 			for ( int i = 0; i < resourceFiles.Num(); i++ ) {
   2395 				// fixup any container indexes
   2396 				resourceFiles[ i ]->SetContainerIndex( i );
   2397 			}
   2398 		}
   2399 	}
   2400 }
   2401 
   2402 /*
   2403 ================
   2404 idFileSystemLocal::RemoveMapResourceFile
   2405 ================
   2406 */
   2407 void idFileSystemLocal::RemoveMapResourceFile( const char * resourceFileName ) {
   2408 	int idx = FindResourceFile( va( "maps/%s", resourceFileName ) );
   2409 	if ( idx >= 0 ) {
   2410 		RemoveResourceFileByIndex( idx );
   2411 	}
   2412 }
   2413 
   2414 /*
   2415 ================
   2416 idFileSystemLocal::RemoveResourceFile
   2417 ================
   2418 */
   2419 void idFileSystemLocal::RemoveResourceFile( const char * resourceFileName ) {
   2420 	int idx = FindResourceFile( resourceFileName );
   2421 	if ( idx >= 0 ) {
   2422 		RemoveResourceFileByIndex( idx );
   2423 	}
   2424 }
   2425 
   2426 /*
   2427 ================
   2428 idFileSystemLocal::AddGameDirectory
   2429 
   2430 Sets gameFolder, adds the directory to the head of the search paths
   2431 ================
   2432 */
   2433 void idFileSystemLocal::AddGameDirectory( const char *path, const char *dir ) {
   2434 	// check if the search path already exists
   2435 	for ( int i = 0; i < searchPaths.Num(); i++ ) {
   2436 		if ( searchPaths[i].path.Cmp( path ) == 0 && searchPaths[i].gamedir.Cmp( dir ) == 0 ) {
   2437 			return;
   2438 		}
   2439 	}
   2440 
   2441 	gameFolder = dir;
   2442 
   2443 	//
   2444 	// add the directory to the search path
   2445 	//
   2446 	searchpath_t & search = searchPaths.Alloc();
   2447 	search.path = path;
   2448 	search.gamedir = dir;
   2449 
   2450 	idStr pakfile = BuildOSPath( path, dir, "" );
   2451 	pakfile[ pakfile.Length() - 1 ] = 0;	// strip the trailing slash
   2452 
   2453 	idStrList pakfiles;
   2454 	ListOSFiles( pakfile, ".resources", pakfiles );
   2455 	pakfiles.SortWithTemplate( idSort_PathStr() );
   2456 	if ( pakfiles.Num() > 0 ) {
   2457 		// resource files present, ignore pak files
   2458 		for ( int i = 0; i < pakfiles.Num(); i++ ) {
   2459 			pakfile = pakfiles[i]; //BuildOSPath( path, dir, pakfiles[i] );
   2460 			idResourceContainer *rc = new idResourceContainer();
   2461 			if ( rc->Init( pakfile, resourceFiles.Num() ) ) {
   2462 				resourceFiles.Append( rc );
   2463 				common->Printf( "Loaded resource file %s\n", pakfile.c_str() );
   2464 				//com_productionMode.SetInteger( 2 );
   2465 			}
   2466 		}
   2467 	}
   2468 }
   2469 
   2470 /*
   2471 ================
   2472 idFileSystemLocal::SetupGameDirectories
   2473 
   2474   Takes care of the correct search order.
   2475 ================
   2476 */
   2477 void idFileSystemLocal::SetupGameDirectories( const char *gameName ) {
   2478 	// setup basepath
   2479 	if ( fs_basepath.GetString()[0] ) {
   2480 		AddGameDirectory( fs_basepath.GetString(), gameName );
   2481 	}
   2482 	// setup savepath
   2483 	if ( fs_savepath.GetString()[0] ) {
   2484 		AddGameDirectory( fs_savepath.GetString(), gameName );
   2485 	}
   2486 }
   2487 
   2488 
   2489 const char *cachedStartupFiles[] = {
   2490 	"game:\\base\\video\\loadvideo.bik"
   2491 };
   2492 const int numStartupFiles = sizeof( cachedStartupFiles ) / sizeof ( cachedStartupFiles[ 0 ] );
   2493 
   2494 const char *cachedNormalFiles[] = {
   2495 	"game:\\base\\_sound_xenon_en.resources",	// these will fail silently on the files that are not on disc
   2496 	"game:\\base\\_sound_xenon_fr.resources",
   2497 	"game:\\base\\_sound_xenon_jp.resources",
   2498 	"game:\\base\\_sound_xenon_sp.resources",
   2499 	"game:\\base\\_sound_xenon_it.resources",
   2500 	"game:\\base\\_sound_xenon_gr.resources",
   2501 	"game:\\base\\_sound_xenon.resources",
   2502 	"game:\\base\\_common.resources",
   2503 	"game:\\base\\_ordered.resources",
   2504 	"game:\\base\\video\\mars_rotation.bik"		// cache this to save the consumer from hearing SEEK.. SEEK... SEEK.. SEEK  SEEEK while at the main menu
   2505 };	
   2506 const int numNormalFiles = sizeof( cachedNormalFiles ) / sizeof ( cachedNormalFiles[ 0 ] );
   2507 
   2508 const char *dontCacheFiles[] = {
   2509 	"game:\\base\\maps\\*.*",	// these will fail silently on the files that are not on disc
   2510 	"game:\\base\\video\\*.*",
   2511 	"game:\\base\\sound\\*.*",
   2512 };	
   2513 const int numDontCacheFiles = sizeof( dontCacheFiles ) / sizeof ( dontCacheFiles[ 0 ] );
   2514 
   2515 /*
   2516 ================
   2517 idFileSystemLocal::InitPrecache
   2518 ================
   2519 */
   2520 void idFileSystemLocal::InitPrecache() {
   2521 	if ( !fs_enableBackgroundCaching.GetBool() ) {
   2522 		return;
   2523 	}
   2524 	numFilesOpenedAsCached = 0;
   2525 }
   2526 
   2527 /*
   2528 ================
   2529 idFileSystemLocal::ReOpenCacheFiles
   2530 ================
   2531 */
   2532 void idFileSystemLocal::ReOpenCacheFiles() {
   2533 
   2534 	if ( !fs_enableBackgroundCaching.GetBool() ) {
   2535 		return;
   2536 	}
   2537 }
   2538 
   2539 
   2540 /*
   2541 ================
   2542 idFileSystemLocal::Startup
   2543 ================
   2544 */
   2545 void idFileSystemLocal::Startup() {
   2546 	common->Printf( "------ Initializing File System ------\n" );
   2547 
   2548 	InitPrecache();
   2549 
   2550 	SetupGameDirectories( BASE_GAMEDIR );
   2551 
   2552 	// fs_game_base override
   2553 	if ( fs_game_base.GetString()[0] &&
   2554 		 idStr::Icmp( fs_game_base.GetString(), BASE_GAMEDIR ) ) {
   2555 		SetupGameDirectories( fs_game_base.GetString() );
   2556 	}
   2557 
   2558 	// fs_game override
   2559 	if ( fs_game.GetString()[0] &&
   2560 		 idStr::Icmp( fs_game.GetString(), BASE_GAMEDIR ) &&
   2561 		 idStr::Icmp( fs_game.GetString(), fs_game_base.GetString() ) ) {
   2562 		SetupGameDirectories( fs_game.GetString() );
   2563 	}
   2564 
   2565 	// add our commands
   2566 	cmdSystem->AddCommand( "dir", Dir_f, CMD_FL_SYSTEM, "lists a folder", idCmdSystem::ArgCompletion_FileName );
   2567 	cmdSystem->AddCommand( "dirtree", DirTree_f, CMD_FL_SYSTEM, "lists a folder with subfolders" );
   2568 	cmdSystem->AddCommand( "path", Path_f, CMD_FL_SYSTEM, "lists search paths" );
   2569 	cmdSystem->AddCommand( "touchFile", TouchFile_f, CMD_FL_SYSTEM, "touches a file" );
   2570 	cmdSystem->AddCommand( "touchFileList", TouchFileList_f, CMD_FL_SYSTEM, "touches a list of files" );
   2571 
   2572 	cmdSystem->AddCommand( "buildGame", BuildGame_f, CMD_FL_SYSTEM, "builds game pak files" );
   2573 	cmdSystem->AddCommand( "writeResourceFile", WriteResourceFile_f, CMD_FL_SYSTEM, "writes a .resources file from a supplied manifest" );
   2574 	cmdSystem->AddCommand( "extractResourceFile", ExtractResourceFile_f, CMD_FL_SYSTEM, "extracts to the supplied resource file to the supplied path" );
   2575 	cmdSystem->AddCommand( "updateResourceFile", UpdateResourceFile_f, CMD_FL_SYSTEM, "updates or appends the supplied files in the supplied resource file" );
   2576 
   2577 	cmdSystem->AddCommand( "generateResourceCRCs", GenerateResourceCRCs_f, CMD_FL_SYSTEM, "Generates CRC checksums for all the resource files." );
   2578 
   2579 	// print the current search paths
   2580 	Path_f( idCmdArgs() );
   2581 
   2582 	common->Printf( "file system initialized.\n" );
   2583 	common->Printf( "--------------------------------------\n" );
   2584 }
   2585 
   2586 /*
   2587 ================
   2588 idFileSystemLocal::Init
   2589 
   2590 Called only at inital startup, not when the filesystem
   2591 is resetting due to a game change
   2592 ================
   2593 */
   2594 void idFileSystemLocal::Init() {
   2595 	// allow command line parms to override our defaults
   2596 	// we have to specially handle this, because normal command
   2597 	// line variable sets don't happen until after the filesystem
   2598 	// has already been initialized
   2599 	common->StartupVariable( "fs_basepath" );
   2600 	common->StartupVariable( "fs_savepath" );
   2601 	common->StartupVariable( "fs_game" );
   2602 	common->StartupVariable( "fs_game_base" );
   2603 	common->StartupVariable( "fs_copyfiles" );
   2604 
   2605 	if ( fs_basepath.GetString()[0] == '\0' ) {
   2606 		fs_basepath.SetString( Sys_DefaultBasePath() );
   2607 	}
   2608 	if ( fs_savepath.GetString()[0] == '\0' ) {
   2609 		fs_savepath.SetString( Sys_DefaultSavePath() );
   2610 	}
   2611 
   2612 	// try to start up normally
   2613 	Startup();
   2614 
   2615 	// if we can't find default.cfg, assume that the paths are
   2616 	// busted and error out now, rather than getting an unreadable
   2617 	// graphics screen when the font fails to load
   2618 	// Dedicated servers can run with no outside files at all
   2619 	if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) {
   2620 		common->FatalError( "Couldn't load default.cfg" );
   2621 	}
   2622 }
   2623 
   2624 /*
   2625 ================
   2626 idFileSystemLocal::Restart
   2627 ================
   2628 */
   2629 void idFileSystemLocal::Restart() {
   2630 	// free anything we currently have loaded
   2631 	Shutdown( true );
   2632 
   2633 	Startup();
   2634 
   2635 	// if we can't find default.cfg, assume that the paths are
   2636 	// busted and error out now, rather than getting an unreadable
   2637 	// graphics screen when the font fails to load
   2638 	if ( ReadFile( "default.cfg", NULL, NULL ) <= 0 ) {
   2639 		common->FatalError( "Couldn't load default.cfg" );
   2640 	}
   2641 }
   2642 
   2643 /*
   2644 ================
   2645 idFileSystemLocal::Shutdown
   2646 
   2647 Frees all resources and closes all files
   2648 ================
   2649 */
   2650 void idFileSystemLocal::Shutdown( bool reloading ) {
   2651 	gameFolder.Clear();
   2652 	searchPaths.Clear();
   2653 
   2654 	resourceFiles.DeleteContents();
   2655 
   2656 
   2657 	cmdSystem->RemoveCommand( "path" );
   2658 	cmdSystem->RemoveCommand( "dir" );
   2659 	cmdSystem->RemoveCommand( "dirtree" );
   2660 	cmdSystem->RemoveCommand( "touchFile" );
   2661 }
   2662 
   2663 /*
   2664 ================
   2665 idFileSystemLocal::IsInitialized
   2666 ================
   2667 */
   2668 bool idFileSystemLocal::IsInitialized() const {
   2669 	return ( searchPaths.Num() != 0 );
   2670 }
   2671 
   2672 
   2673 /*
   2674 =================================================================================
   2675 
   2676 Opening files
   2677 
   2678 =================================================================================
   2679 */
   2680 
   2681 /*
   2682 ========================
   2683 idFileSystemLocal::GetResourceCacheEntry
   2684 
   2685 Returns false if the entry isn't found
   2686 ========================
   2687 */
   2688 bool idFileSystemLocal::GetResourceCacheEntry( const char *fileName, idResourceCacheEntry &rc ) {
   2689 	idStrStatic< MAX_OSPATH > canonical;
   2690 	if ( strstr( fileName, ":") != NULL ) {
   2691 		// os path, convert to relative? scripts can pass in an OS path
   2692 		//idLib::Printf( "RESOURCE: os path passed %s\n", fileName );
   2693 		return NULL;
   2694 	} else {
   2695 		canonical = fileName;
   2696 	}
   2697 
   2698 	canonical.BackSlashesToSlashes();
   2699 	canonical.ToLower();
   2700 	int idx = resourceFiles.Num() - 1;
   2701 	while ( idx >= 0 ) {
   2702 		const int key = resourceFiles[ idx ]->cacheHash.GenerateKey( canonical, false );
   2703 		for ( int index = resourceFiles[ idx ]->cacheHash.GetFirst( key ); index != idHashIndex::NULL_INDEX; index = resourceFiles[ idx ]->cacheHash.GetNext( index ) ) {
   2704 			idResourceCacheEntry & rt = resourceFiles[ idx ]->cacheTable[ index ];
   2705 			if ( idStr::Icmp( rt.filename, canonical ) == 0 ) {
   2706 				rc.filename = rt.filename;
   2707 				rc.length = rt.length;
   2708 				rc.containerIndex = idx;
   2709 				rc.offset = rt.offset;
   2710 				return true;
   2711 			}
   2712 		}
   2713 		idx--;
   2714 	}
   2715 	return false;
   2716 }
   2717 
   2718 /*
   2719 ========================
   2720 idFileSystemLocal::GetResourceFile
   2721 
   2722 Returns NULL
   2723 ========================
   2724 */
   2725 
   2726 idFile * idFileSystemLocal::GetResourceFile( const char *fileName, bool memFile ) { 
   2727 	
   2728 	if ( resourceFiles.Num() == 0 ) {
   2729 		return NULL;
   2730 	}
   2731 
   2732 	static idResourceCacheEntry rc;
   2733 	if ( GetResourceCacheEntry( fileName, rc ) ) {
   2734 		if ( fs_debugResources.GetBool() ) {
   2735 			idLib::Printf( "RES: loading file %s\n", rc.filename.c_str() );
   2736 		}
   2737 		idFile_InnerResource *file = new idFile_InnerResource( rc.filename, resourceFiles[ rc.containerIndex ]->resourceFile, rc.offset, rc.length );
   2738 		if ( file != NULL && ( memFile || rc.length <= resourceBufferAvailable ) || rc.length < 8 * 1024 * 1024 ) {
   2739 			byte *buf = NULL;
   2740 			if ( rc.length < resourceBufferAvailable ) {
   2741 				buf = resourceBufferPtr;
   2742 				resourceBufferAvailable = 0;
   2743 			} else {
   2744 		if ( fs_debugResources.GetBool() ) {
   2745 				idLib::Printf( "MEM: Allocating %05d bytes for a resource load\n", rc.length );
   2746 }
   2747 				buf = ( byte * )Mem_Alloc( rc.length, TAG_TEMP );
   2748 			}
   2749 			file->Read( (void*)buf, rc.length );
   2750 
   2751 			if ( buf == resourceBufferPtr ) {
   2752 				file->SetResourceBuffer( buf );
   2753 				return file;
   2754 			} else {
   2755 				idFile_Memory *mfile = new idFile_Memory( rc.filename, ( const char * )buf, rc.length );
   2756 				if ( mfile != NULL ) {
   2757 					mfile->TakeDataOwnership();
   2758 					delete file;
   2759 					return mfile;
   2760 				}
   2761 			}
   2762 		} 
   2763 		return file;
   2764 	}
   2765 
   2766 	return NULL;
   2767 }
   2768 
   2769 
   2770 /*
   2771 ===========
   2772 idFileSystemLocal::OpenFileReadFlags
   2773 
   2774 Finds the file in the search path, following search flag recommendations
   2775 Returns filesize and an open FILE pointer.
   2776 Used for streaming data out of either a
   2777 separate file or a ZIP file.
   2778 ===========
   2779 */
   2780 idFile *idFileSystemLocal::OpenFileReadFlags( const char *relativePath, int searchFlags, bool allowCopyFiles, const char* gamedir ) {
   2781 	
   2782 	if ( !IsInitialized() ) {
   2783 		common->FatalError( "Filesystem call made without initialization\n" );
   2784 		return NULL;
   2785 	}
   2786 
   2787 	if ( relativePath == NULL ) {
   2788 		common->FatalError( "idFileSystemLocal::OpenFileRead: NULL 'relativePath' parameter passed\n" );
   2789 		return NULL;
   2790 	}
   2791 
   2792 	// qpaths are not supposed to have a leading slash
   2793 	if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
   2794 		relativePath++;
   2795 	}
   2796 
   2797 	// make absolutely sure that it can't back up the path.
   2798 	// The searchpaths do guarantee that something will always
   2799 	// be prepended, so we don't need to worry about "c:" or "//limbo" 
   2800 	if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
   2801 		return NULL;
   2802 	}
   2803 	
   2804 	// edge case
   2805 	if ( relativePath[0] == '\0' ) {
   2806 		return NULL;
   2807 	}
   2808 
   2809 	if ( fs_debug.GetBool() ) {
   2810 		idLib::Printf( "FILE DEBUG: opening %s\n", relativePath );
   2811 	}
   2812 
   2813 	if ( resourceFiles.Num() > 0 && fs_resourceLoadPriority.GetInteger() ==  1 ) {
   2814 		idFile * rf = GetResourceFile( relativePath, ( searchFlags & FSFLAG_RETURN_FILE_MEM ) != 0 );
   2815 		if ( rf != NULL ) {
   2816 			return rf;
   2817 		}
   2818  	}
   2819 
   2820 	//
   2821 	// search through the path, one element at a time
   2822 	//
   2823 	if ( searchFlags & FSFLAG_SEARCH_DIRS ) {
   2824 		for ( int sp = searchPaths.Num() - 1; sp >= 0; sp-- ) {
   2825 			if ( gamedir != NULL && gamedir[0] != 0 ) {
   2826 				if ( searchPaths[sp].gamedir != gamedir ) {
   2827 					continue;
   2828 				}
   2829 			}
   2830 
   2831 			idStr netpath = BuildOSPath( searchPaths[sp].path, searchPaths[sp].gamedir, relativePath );
   2832 			idFileHandle fp = OpenOSFile( netpath, FS_READ );
   2833 			if ( !fp ) {
   2834 				continue;
   2835 			}
   2836 
   2837 			idFile_Permanent * file = new (TAG_IDFILE) idFile_Permanent();
   2838 			file->o = fp;
   2839 			file->name = relativePath;
   2840 			file->fullPath = netpath;
   2841 			file->mode = ( 1 << FS_READ );
   2842 			file->fileSize = DirectFileLength( file->o );
   2843 			if ( fs_debug.GetInteger() ) {
   2844 				common->Printf( "idFileSystem::OpenFileRead: %s (found in '%s/%s')\n", relativePath, searchPaths[sp].path.c_str(), searchPaths[sp].gamedir.c_str() );
   2845 			}
   2846 
   2847 			// if fs_copyfiles is set
   2848 			if ( allowCopyFiles ) {
   2849 
   2850 				idStr copypath;
   2851 				idStr name;
   2852 				copypath = BuildOSPath( fs_savepath.GetString(), searchPaths[sp].gamedir, relativePath );
   2853 				netpath.ExtractFileName( name );
   2854 				copypath.StripFilename();
   2855 				copypath += PATHSEPARATOR_STR;
   2856 				copypath += name;
   2857 
   2858 				if ( fs_buildResources.GetBool() ) {
   2859 					idStrStatic< MAX_OSPATH > relativePath = OSPathToRelativePath( copypath );
   2860 					relativePath.BackSlashesToSlashes();
   2861 					relativePath.ToLower();
   2862 
   2863 					if ( IsSoundSample( relativePath ) ) {
   2864 						idStrStatic< MAX_OSPATH > samplePath = relativePath;
   2865 						samplePath.SetFileExtension( "idwav" );
   2866 						if ( samplePath.Find( "generated/" ) == -1 ) {
   2867 							samplePath.Insert( "generated/", 0 );
   2868 						}
   2869 						fileManifest.AddUnique( samplePath );
   2870 						if ( relativePath.Find( "/vo/", false ) >= 0 ) {
   2871 							// this is vo so add the language variants
   2872 							for ( int i = 0; i < Sys_NumLangs(); i++ ) {
   2873 								const char *lang = Sys_Lang( i );
   2874 								if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) {
   2875 									continue;
   2876 								}
   2877 								samplePath = relativePath;
   2878 								samplePath.Replace( "/vo/", va( "/vo/%s/", lang ) );
   2879 								samplePath.SetFileExtension( "idwav" );
   2880 								if ( samplePath.Find( "generated/" ) == -1 ) {
   2881 									samplePath.Insert( "generated/", 0 );
   2882 								}
   2883 								fileManifest.AddUnique( samplePath );
   2884 
   2885 							}
   2886 						}
   2887 					} else if ( relativePath.Icmpn( "guis/", 5 ) == 0 ) {
   2888 						// this is a gui so add the language variants
   2889 						for ( int i = 0; i < Sys_NumLangs(); i++ ) {
   2890 							const char *lang = Sys_Lang( i );
   2891 							if ( idStr::Icmp( lang, ID_LANG_ENGLISH ) == 0 ) {
   2892 								fileManifest.Append( relativePath );
   2893 								continue;
   2894 							}
   2895 							idStrStatic< MAX_OSPATH > guiPath = relativePath;
   2896 							guiPath.Replace( "guis/", va( "guis/%s/", lang ) );
   2897 							fileManifest.Append( guiPath );
   2898 						}
   2899 					} else {
   2900 						// never add .amp files
   2901 						if ( strstr( relativePath, ".amp" ) == NULL ) {
   2902 							fileManifest.Append( relativePath );
   2903 						}
   2904 					}
   2905 
   2906 				}
   2907 
   2908 				if ( fs_copyfiles.GetBool() ) {
   2909 					CopyFile( netpath, copypath );
   2910 				}
   2911 			}
   2912 
   2913 			if ( searchFlags & FSFLAG_RETURN_FILE_MEM ) {
   2914 				idFile_Memory * memFile = new (TAG_IDFILE) idFile_Memory( file->name );
   2915 				memFile->SetLength( file->fileSize );
   2916 				file->Read( (void *)memFile->GetDataPtr(), file->fileSize );
   2917 				delete file;
   2918 				memFile->TakeDataOwnership();
   2919 				return memFile;
   2920 			}
   2921 
   2922 			return file;
   2923 		}
   2924 	}
   2925 
   2926 	if ( resourceFiles.Num() > 0 && fs_resourceLoadPriority.GetInteger() ==  0 ) {
   2927 		idFile * rf = GetResourceFile( relativePath, ( searchFlags & FSFLAG_RETURN_FILE_MEM ) != 0 );
   2928 		if ( rf != NULL ) {
   2929 			return rf;
   2930 		}
   2931 	}
   2932 	
   2933 	if ( fs_debug.GetInteger( ) ) {
   2934 		common->Printf( "Can't find %s\n", relativePath );
   2935 	}
   2936 	
   2937 	return NULL;
   2938 }
   2939 
   2940 /*
   2941 ===========
   2942 idFileSystemLocal::OpenFileRead
   2943 ===========
   2944 */
   2945 idFile *idFileSystemLocal::OpenFileRead( const char *relativePath, bool allowCopyFiles, const char* gamedir ) {
   2946 	return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS, allowCopyFiles, gamedir );
   2947 }
   2948 
   2949 /*
   2950 ===========
   2951 idFileSystemLocal::OpenFileReadMemory
   2952 ===========
   2953 */
   2954 idFile *idFileSystemLocal::OpenFileReadMemory( const char *relativePath, bool allowCopyFiles, const char* gamedir ) {
   2955 	return OpenFileReadFlags( relativePath, FSFLAG_SEARCH_DIRS | FSFLAG_RETURN_FILE_MEM, allowCopyFiles, gamedir );
   2956 }
   2957 
   2958 /*
   2959 ===========
   2960 idFileSystemLocal::OpenFileWrite
   2961 ===========
   2962 */
   2963 idFile *idFileSystemLocal::OpenFileWrite( const char *relativePath, const char *basePath ) {
   2964 
   2965 	const char *path;
   2966 	idStr OSpath;
   2967 	idFile_Permanent *f;
   2968 
   2969 	if ( !IsInitialized() ) {
   2970 		common->FatalError( "Filesystem call made without initialization\n" );
   2971 	}
   2972 
   2973 	path = cvarSystem->GetCVarString( basePath );
   2974 	if ( !path[0] ) {
   2975 		path = fs_savepath.GetString();
   2976 	}
   2977 
   2978 	OSpath = BuildOSPath( path, gameFolder, relativePath );
   2979 
   2980 	if ( fs_debug.GetInteger() ) {
   2981 		common->Printf( "idFileSystem::OpenFileWrite: %s\n", OSpath.c_str() );
   2982 	}
   2983 
   2984 	common->DPrintf( "writing to: %s\n", OSpath.c_str() );
   2985 	CreateOSPath( OSpath );
   2986 
   2987 	f = new (TAG_IDFILE) idFile_Permanent();
   2988 	f->o = OpenOSFile( OSpath, FS_WRITE );
   2989 	if ( !f->o ) {
   2990 		delete f;
   2991 		return NULL;
   2992 	}
   2993 	f->name = relativePath;
   2994 	f->fullPath = OSpath;
   2995 	f->mode = ( 1 << FS_WRITE );
   2996 	f->handleSync = false;
   2997 	f->fileSize = 0;
   2998 
   2999 	return f;
   3000 }
   3001 
   3002 /*
   3003 ===========
   3004 idFileSystemLocal::OpenExplicitFileRead
   3005 ===========
   3006 */
   3007 idFile *idFileSystemLocal::OpenExplicitFileRead( const char *OSPath ) {
   3008 	idFile_Permanent *f;
   3009 
   3010 	if ( !IsInitialized() ) {
   3011 		common->FatalError( "Filesystem call made without initialization\n" );
   3012 	}
   3013 
   3014 	if ( fs_debug.GetInteger() ) {
   3015 		common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath );
   3016 	}
   3017 
   3018 	//common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath );
   3019 
   3020 	f = new (TAG_IDFILE) idFile_Permanent();
   3021 	f->o = OpenOSFile( OSPath, FS_READ );
   3022 	if ( !f->o ) {
   3023 		delete f;
   3024 		return NULL;
   3025 	}
   3026 	f->name = OSPath;
   3027 	f->fullPath = OSPath;
   3028 	f->mode = ( 1 << FS_READ );
   3029 	f->handleSync = false;
   3030 	f->fileSize = DirectFileLength( f->o );
   3031 
   3032 	return f;
   3033 }
   3034 
   3035 /*
   3036 ===========
   3037 idFileSystemLocal::OpenExplicitPakFile
   3038 ===========
   3039 */
   3040 idFile_Cached *idFileSystemLocal::OpenExplicitPakFile( const char *OSPath ) {
   3041 	idFile_Cached *f;
   3042 
   3043 	if ( !IsInitialized() ) {
   3044 		common->FatalError( "Filesystem call made without initialization\n" );
   3045 	}
   3046 
   3047 	//if ( fs_debug.GetInteger() ) {
   3048 	//	common->Printf( "idFileSystem::OpenExplicitFileRead: %s\n", OSPath );
   3049 	//}
   3050 
   3051 	//common->DPrintf( "idFileSystem::OpenExplicitFileRead - reading from: %s\n", OSPath );
   3052 
   3053 	f = new (TAG_IDFILE) idFile_Cached();
   3054 	f->o = OpenOSFile( OSPath, FS_READ );
   3055 	if ( !f->o ) {
   3056 		delete f;
   3057 		return NULL;
   3058 	}
   3059 	f->name = OSPath;
   3060 	f->fullPath = OSPath;
   3061 	f->mode = ( 1 << FS_READ );
   3062 	f->handleSync = false;
   3063 	f->fileSize = DirectFileLength( f->o );
   3064 
   3065 	return f;
   3066 }
   3067 
   3068 /*
   3069 ===========
   3070 idFileSystemLocal::OpenExplicitFileWrite
   3071 ===========
   3072 */
   3073 idFile *idFileSystemLocal::OpenExplicitFileWrite( const char *OSPath ) {
   3074 	idFile_Permanent *f;
   3075 
   3076 	if ( !IsInitialized() ) {
   3077 		common->FatalError( "Filesystem call made without initialization\n" );
   3078 	}
   3079 
   3080 	if ( fs_debug.GetInteger() ) {
   3081 		common->Printf( "idFileSystem::OpenExplicitFileWrite: %s\n", OSPath );
   3082 	}
   3083 
   3084 	common->DPrintf( "writing to: %s\n", OSPath );
   3085 	CreateOSPath( OSPath );
   3086 
   3087 	f = new (TAG_IDFILE) idFile_Permanent();
   3088 	f->o = OpenOSFile( OSPath, FS_WRITE );
   3089 	if ( !f->o ) {
   3090 		delete f;
   3091 		return NULL;
   3092 	}
   3093 	f->name = OSPath;
   3094 	f->fullPath = OSPath;
   3095 	f->mode = ( 1 << FS_WRITE );
   3096 	f->handleSync = false;
   3097 	f->fileSize = 0;
   3098 
   3099 	return f;
   3100 }
   3101 
   3102 /*
   3103 ===========
   3104 idFileSystemLocal::OpenFileAppend
   3105 ===========
   3106 */
   3107 idFile *idFileSystemLocal::OpenFileAppend( const char *relativePath, bool sync, const char *basePath ) {
   3108 
   3109 	const char *path;
   3110 	idStr OSpath;
   3111 	idFile_Permanent *f;
   3112 
   3113 	if ( !IsInitialized() ) {
   3114 		common->FatalError( "Filesystem call made without initialization\n" );
   3115 	}
   3116 
   3117 	path = cvarSystem->GetCVarString( basePath );
   3118 	if ( !path[0] ) {
   3119 		path = fs_savepath.GetString();
   3120 	}
   3121 
   3122 	OSpath = BuildOSPath( path, gameFolder, relativePath );
   3123 	CreateOSPath( OSpath );
   3124 
   3125 	if ( fs_debug.GetInteger() ) {
   3126 		common->Printf( "idFileSystem::OpenFileAppend: %s\n", OSpath.c_str() );
   3127 	}
   3128 
   3129 	f = new (TAG_IDFILE) idFile_Permanent();
   3130 	f->o = OpenOSFile( OSpath, FS_APPEND );
   3131 	if ( !f->o ) {
   3132 		delete f;
   3133 		return NULL;
   3134 	}
   3135 	f->name = relativePath;
   3136 	f->fullPath = OSpath;
   3137 	f->mode = ( 1 << FS_WRITE ) + ( 1 << FS_APPEND );
   3138 	f->handleSync = sync;
   3139 	f->fileSize = DirectFileLength( f->o );
   3140 
   3141 	return f;
   3142 }
   3143 
   3144 /*
   3145 ================
   3146 idFileSystemLocal::OpenFileByMode
   3147 ================
   3148 */
   3149 idFile *idFileSystemLocal::OpenFileByMode( const char *relativePath, fsMode_t mode ) {
   3150 	if ( mode == FS_READ ) {
   3151 		return OpenFileRead( relativePath );
   3152 	}
   3153 	if ( mode == FS_WRITE ) {
   3154 		return OpenFileWrite( relativePath );
   3155 	}
   3156 	if ( mode == FS_APPEND ) {
   3157 		return OpenFileAppend( relativePath, true );
   3158 	}
   3159 	common->FatalError( "idFileSystemLocal::OpenFileByMode: bad mode" );
   3160 	return NULL;
   3161 }
   3162 
   3163 /*
   3164 ==============
   3165 idFileSystemLocal::CloseFile
   3166 ==============
   3167 */
   3168 void idFileSystemLocal::CloseFile( idFile *f ) {
   3169 	if ( !IsInitialized() ) {
   3170 		common->FatalError( "Filesystem call made without initialization\n" );
   3171 	}
   3172 	delete f;
   3173 }
   3174 
   3175 /*
   3176 =================
   3177 idFileSystemLocal::FindDLL
   3178 =================
   3179 */
   3180 void idFileSystemLocal::FindDLL( const char *name, char _dllPath[ MAX_OSPATH ] ) {
   3181 	char dllName[MAX_OSPATH];
   3182 	sys->DLL_GetFileName( name, dllName, MAX_OSPATH );
   3183 
   3184 	// from executable directory first - this is handy for developement
   3185 	idStr dllPath = Sys_EXEPath( );
   3186 	dllPath.StripFilename( );
   3187 	dllPath.AppendPath( dllName );
   3188 	idFile * dllFile = OpenExplicitFileRead( dllPath );
   3189 
   3190 	if ( dllFile ) {
   3191 		dllPath = dllFile->GetFullPath();
   3192 		CloseFile( dllFile );
   3193 		dllFile = NULL;
   3194 	} else {
   3195 		dllPath = "";
   3196 	}
   3197 	idStr::snPrintf( _dllPath, MAX_OSPATH, dllPath.c_str() );
   3198 }
   3199 
   3200 /*
   3201 ===============
   3202 idFileSystemLocal::FindFile
   3203 ===============
   3204 */
   3205  findFile_t idFileSystemLocal::FindFile( const char *path ) {
   3206 	idFile *f = OpenFileReadFlags( path, FSFLAG_SEARCH_DIRS );
   3207 	if ( f == NULL ) {
   3208 		return FIND_NO;
   3209 	}
   3210 	delete f;
   3211 	return FIND_YES;
   3212 }
   3213 
   3214 /*
   3215 ===============
   3216 idFileSystemLocal::IsFolder
   3217 ===============
   3218 */
   3219 sysFolder_t idFileSystemLocal::IsFolder( const char * relativePath, const char *basePath ) {
   3220 	return Sys_IsFolder( RelativePathToOSPath( relativePath, basePath ) );
   3221 }