PageRenderTime 321ms CodeModel.GetById 108ms app.highlight 181ms RepoModel.GetById 1ms app.codeStats 2ms

/neo/framework/FileSystem.cpp

https://bitbucket.org/thayamizu/doom-3
C++ | 4217 lines | 3104 code | 354 blank | 759 comment | 661 complexity | 88d6c3609e62929b425374411469ad52 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2===========================================================================
   3
   4Doom 3 GPL Source Code
   5Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
   6
   7This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
   8
   9Doom 3 Source Code is free software: you can redistribute it and/or modify
  10it under the terms of the GNU General Public License as published by
  11the Free Software Foundation, either version 3 of the License, or
  12(at your option) any later version.
  13
  14Doom 3 Source Code is distributed in the hope that it will be useful,
  15but WITHOUT ANY WARRANTY; without even the implied warranty of
  16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17GNU General Public License for more details.
  18
  19You should have received a copy of the GNU General Public License
  20along with Doom 3 Source Code.  If not, see <http://www.gnu.org/licenses/>.
  21
  22In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code.  If not, please request a copy in writing from id Software at the address below.
  23
  24If 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
  34#ifdef WIN32
  35	#include <io.h>	// for _read
  36#else
  37	#if !__MACH__ && __MWERKS__
  38		#include <types.h>
  39		#include <stat.h>
  40	#else
  41		#include <sys/types.h>
  42		#include <sys/stat.h>
  43	#endif
  44	#include <unistd.h>
  45#endif
  46
  47#if ID_ENABLE_CURL
  48	#include "../curl/include/curl/curl.h"
  49#endif
  50
  51/*
  52=============================================================================
  53
  54DOOM FILESYSTEM
  55
  56All of Doom's data access is through a hierarchical file system, but the contents of 
  57the file system can be transparently merged from several sources.
  58
  59A "relativePath" is a reference to game file data, which must include a terminating zero.
  60"..", "\\", and ":" are explicitly illegal in qpaths to prevent any references
  61outside the Doom directory system.
  62
  63The "base path" is the path to the directory holding all the game directories and
  64usually the executable. It defaults to the current directory, but can be overridden
  65with "+set fs_basepath c:\doom" on the command line. The base path cannot be modified
  66at all after startup.
  67
  68The "save path" is the path to the directory where game files will be saved. It defaults
  69to the base path, but can be overridden with a "+set fs_savepath c:\doom" on the
  70command line. Any files that are created during the game (demos, screenshots, etc.) will
  71be created reletive to the save path.
  72
  73The "cd path" is the path to an alternate hierarchy that will be searched if a file
  74is not located in the base path. A user can do a partial install that copies some
  75data to a base path created on their hard drive and leave the rest on the cd. It defaults
  76to the current directory, but it can be overridden with "+set fs_cdpath g:\doom" on the
  77command line.
  78
  79The "dev path" is the path to an alternate hierarchy where the editors and tools used
  80during development (Radiant, AF editor, dmap, runAAS) will write files to. It defaults to
  81the cd path, but can be overridden with a "+set fs_devpath c:\doom" on the command line.
  82
  83If a user runs the game directly from a CD, the base path would be on the CD. This
  84should still function correctly, but all file writes will fail (harmlessly).
  85
  86The "base game" is the directory under the paths where data comes from by default, and
  87can be either "base" or "demo".
  88
  89The "current game" may be the same as the base game, or it may be the name of another
  90directory under the paths that should be searched for files before looking in the base
  91game. The game directory is set with "+set fs_game myaddon" on the command line. This is
  92the basis for addons.
  93
  94No other directories outside of the base game and current game will ever be referenced by
  95filesystem functions.
  96
  97To save disk space and speed up file loading, directory trees can be collapsed into zip
  98files. The files use a ".pk4" extension to prevent users from unzipping them accidentally,
  99but otherwise they are simply normal zip files. A game directory can have multiple zip
 100files of the form "pak0.pk4", "pak1.pk4", etc. Zip files are searched in decending order
 101from the highest number to the lowest, and will always take precedence over the filesystem.
 102This allows a pk4 distributed as a patch to override all existing data.
 103
 104Because we will have updated executables freely available online, there is no point to
 105trying to restrict demo / oem versions of the game with code changes. Demo / oem versions
 106should be exactly the same executables as release versions, but with different data that
 107automatically restricts where game media can come from to prevent add-ons from working.
 108
 109After the paths are initialized, Doom will look for the product.txt file. If not found
 110and verified, the game will run in restricted mode. In restricted mode, only files
 111contained in demo/pak0.pk4 will be available for loading, and only if the zip header is
 112verified to not have been modified. A single exception is made for DoomConfig.cfg. Files
 113can still be written out in restricted mode, so screenshots and demos are allowed.
 114Restricted mode can be tested by setting "+set fs_restrict 1" on the command line, even
 115if there is a valid product.txt under the basepath or cdpath.
 116
 117If the "fs_copyfiles" cvar is set to 1, then every time a file is sourced from the cd
 118path, it will be copied over to the save path. This is a development aid to help build
 119test releases and to copy working sets of files.
 120
 121If the "fs_copyfiles" cvar is set to 2, any file found in fs_cdpath that is newer than
 122it's fs_savepath version will be copied to fs_savepath (in addition to the fs_copyfiles 1
 123behaviour).
 124
 125If the "fs_copyfiles" cvar is set to 3, files from both basepath and cdpath will be copied
 126over to the save path. This is useful when copying working sets of files mainly from base
 127path with an additional cd path (which can be a slower network drive for instance).
 128
 129If the "fs_copyfiles" cvar is set to 4, files that exist in the cd path but NOT the base path
 130will be copied to the save path
 131
 132NOTE: fs_copyfiles and case sensitivity. On fs_caseSensitiveOS 0 filesystems ( win32 ), the
 133copied files may change casing when copied over.
 134
 135The relative path "sound/newstuff/test.wav" would be searched for in the following places:
 136
 137for save path, dev path, base path, cd path:
 138	for current game, base game:
 139		search directory
 140		search zip files
 141
 142downloaded files, to be written to save path + current game's directory
 143
 144The filesystem can be safely shutdown and reinitialized with different
 145basedir / cddir / game combinations, but all other subsystems that rely on it
 146(sound, video) must also be forced to restart.
 147
 148
 149"fs_caseSensitiveOS":
 150This cvar is set on operating systems that use case sensitive filesystems (Linux and OSX)
 151It is a common situation to have the media reference filenames, whereas the file on disc 
 152only matches in a case-insensitive way. When "fs_caseSensitiveOS" is set, the filesystem
 153will always do a case insensitive search.
 154IMPORTANT: This only applies to files, and not to directories. There is no case-insensitive
 155matching of directories. All directory names should be lowercase, when "com_developer" is 1,
 156the filesystem will warn when it catches bad directory situations (regardless of the
 157"fs_caseSensitiveOS" setting)
 158When bad casing in directories happen and "fs_caseSensitiveOS" is set, BuildOSPath will
 159attempt to correct the situation by forcing the path to lowercase. This assumes the media
 160is stored all lowercase.
 161
 162"additional mod path search":
 163fs_game_base can be used to set an additional search path
 164in search order, fs_game, fs_game_base, BASEGAME
 165for instance to base a mod of D3 + D3XP assets, fs_game mymod, fs_game_base d3xp
 166
 167=============================================================================
 168*/
 169
 170
 171
 172// define to fix special-cases for GetPackStatus so that files that shipped in 
 173// the wrong place for Doom 3 don't break pure servers.
 174#define DOOM3_PURE_SPECIAL_CASES	
 175
 176typedef bool (*pureExclusionFunc_t)( const struct pureExclusion_s &excl, int l, const idStr &name );
 177
 178typedef struct pureExclusion_s {
 179	int					nameLen;
 180	int					extLen;
 181	const char *		name;
 182	const char *		ext;
 183	pureExclusionFunc_t	func;
 184} pureExclusion_t;
 185
 186bool excludeExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
 187	if ( l > excl.extLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) ) {
 188		return true;
 189	}
 190	return false;
 191}
 192
 193bool excludePathPrefixAndExtension( const pureExclusion_t &excl, int l, const idStr &name ) {
 194	if ( l > excl.nameLen && !idStr::Icmp( name.c_str() + l - excl.extLen, excl.ext ) && !name.IcmpPrefixPath( excl.name ) ) {
 195		return true;
 196	}
 197	return false;
 198}
 199
 200bool excludeFullName( const pureExclusion_t &excl, int l, const idStr &name ) {
 201	if ( l == excl.nameLen && !name.Icmp( excl.name ) ) {
 202		return true;
 203	}
 204	return false;
 205}
 206
 207static pureExclusion_t pureExclusions[] = {
 208	{ 0,	0,	NULL,											"/",		excludeExtension },
 209	{ 0,	0,	NULL,											"\\",		excludeExtension },
 210	{ 0,	0,	NULL,											".pda",		excludeExtension },
 211	{ 0,	0,	NULL,											".gui",		excludeExtension },
 212	{ 0,	0,	NULL,											".pd",		excludeExtension },
 213	{ 0,	0,	NULL,											".lang",	excludeExtension },
 214	{ 0,	0,	"sound/VO",										".ogg",		excludePathPrefixAndExtension },
 215	{ 0,	0,	"sound/VO",										".wav",		excludePathPrefixAndExtension },
 216#if	defined DOOM3_PURE_SPECIAL_CASES	
 217	// add any special-case files or paths for pure servers here
 218	{ 0,	0,	"sound/ed/marscity/vo_intro_cutscene.ogg",		NULL,		excludeFullName },
 219	{ 0,	0,	"sound/weapons/soulcube/energize_01.ogg",		NULL,		excludeFullName },
 220	{ 0,	0,	"sound/xian/creepy/vocal_fx",					".ogg",		excludePathPrefixAndExtension },
 221	{ 0,	0,	"sound/xian/creepy/vocal_fx",					".wav",		excludePathPrefixAndExtension },
 222	{ 0,	0,	"sound/feedback",								".ogg",		excludePathPrefixAndExtension },
 223	{ 0,	0,	"sound/feedback",								".wav",		excludePathPrefixAndExtension },
 224	{ 0,	0,	"guis/assets/mainmenu/chnote.tga",				NULL,		excludeFullName },
 225	{ 0,	0,	"sound/levels/alphalabs2/uac_better_place.ogg",	NULL,		excludeFullName },
 226	{ 0,	0,	"textures/bigchars.tga",						NULL,		excludeFullName },
 227	{ 0,	0,	"dds/textures/bigchars.dds",					NULL,		excludeFullName },
 228	{ 0,	0,	"fonts",										".tga",		excludePathPrefixAndExtension },
 229	{ 0,	0,	"dds/fonts",									".dds",		excludePathPrefixAndExtension },
 230	{ 0,	0,	"default.cfg",									NULL,		excludeFullName },
 231	// russian zpak001.pk4
 232	{ 0,	0,  "fonts",										".dat",		excludePathPrefixAndExtension },
 233	{ 0,	0,	"guis/temp.guied",								NULL,		excludeFullName },
 234#endif
 235	{ 0,	0,	NULL,											NULL,		NULL }
 236};
 237
 238// ensures that lengths for pure exclusions are correct
 239class idInitExclusions {
 240public:
 241	idInitExclusions() {
 242		for ( int i = 0; pureExclusions[i].func != NULL; i++ ) {
 243			if ( pureExclusions[i].name ) {
 244				pureExclusions[i].nameLen = idStr::Length( pureExclusions[i].name );
 245			}
 246			if ( pureExclusions[i].ext ) {
 247				pureExclusions[i].extLen = idStr::Length( pureExclusions[i].ext );
 248			}
 249		}
 250	}
 251};
 252
 253static idInitExclusions	initExclusions;
 254
 255#define MAX_ZIPPED_FILE_NAME	2048
 256#define FILE_HASH_SIZE			1024
 257
 258typedef struct fileInPack_s {
 259	idStr				name;						// name of the file
 260	unsigned long		pos;						// file info position in zip
 261	struct fileInPack_s * next;						// next file in the hash
 262} fileInPack_t;
 263
 264typedef enum {
 265	BINARY_UNKNOWN = 0,
 266	BINARY_YES,
 267	BINARY_NO
 268} binaryStatus_t;
 269
 270typedef enum {
 271	PURE_UNKNOWN = 0,	// need to run the pak through GetPackStatus
 272	PURE_NEUTRAL,	// neutral regarding pureness. gets in the pure list if referenced
 273	PURE_ALWAYS,	// always referenced - for pak* named files, unless NEVER
 274	PURE_NEVER		// VO paks. may be referenced, won't be in the pure lists
 275} pureStatus_t;
 276
 277typedef struct {
 278	idList<int>			depends;
 279	idList<idDict *>	mapDecls;
 280} addonInfo_t;
 281
 282typedef struct {
 283	idStr				pakFilename;				// c:\doom\base\pak0.pk4
 284	unzFile				handle;
 285	int					checksum;
 286	int					numfiles;
 287	int					length;
 288	bool				referenced;
 289	binaryStatus_t		binary;
 290	bool				addon;						// this is an addon pack - addon_search tells if it's 'active'
 291	bool				addon_search;				// is in the search list
 292	addonInfo_t			*addon_info;
 293	pureStatus_t		pureStatus;
 294	bool				isNew;						// for downloaded paks
 295	fileInPack_t		*hashTable[FILE_HASH_SIZE];
 296	fileInPack_t		*buildBuffer;
 297} pack_t;
 298
 299typedef struct {
 300	idStr				path;						// c:\doom
 301	idStr				gamedir;					// base
 302} directory_t;
 303
 304typedef struct searchpath_s {
 305	pack_t *			pack;						// only one of pack / dir will be non NULL
 306	directory_t *		dir;
 307	struct searchpath_s *next;
 308} searchpath_t;
 309
 310// search flags when opening a file
 311#define FSFLAG_SEARCH_DIRS		( 1 << 0 )
 312#define FSFLAG_SEARCH_PAKS		( 1 << 1 )
 313#define FSFLAG_PURE_NOREF		( 1 << 2 )
 314#define FSFLAG_BINARY_ONLY		( 1 << 3 )
 315#define FSFLAG_SEARCH_ADDONS	( 1 << 4 )
 316
 317// 3 search path (fs_savepath fs_basepath fs_cdpath)
 318// + .jpg and .tga
 319#define MAX_CACHED_DIRS 6
 320
 321// how many OSes to handle game paks for ( we don't have to know them precisely )
 322#define MAX_GAME_OS	6
 323#define BINARY_CONFIG "binary.conf"
 324#define ADDON_CONFIG "addon.conf"
 325
 326class idDEntry : public idStrList {
 327public:
 328						idDEntry() {}
 329	virtual				~idDEntry() {}
 330
 331	bool				Matches( const char *directory, const char *extension ) const;
 332	void				Init( const char *directory, const char *extension, const idStrList &list );
 333	void				Clear( void );
 334
 335private:
 336	idStr				directory;
 337	idStr				extension;
 338};
 339
 340class idFileSystemLocal : public idFileSystem {
 341public:
 342							idFileSystemLocal( void );
 343
 344	virtual void			Init( void );
 345	virtual void			StartBackgroundDownloadThread( void );
 346	virtual void			Restart( void );
 347	virtual void			Shutdown( bool reloading );
 348	virtual bool			IsInitialized( void ) const;
 349	virtual bool			PerformingCopyFiles( void ) const;
 350	virtual idModList *		ListMods( void );
 351	virtual void			FreeModList( idModList *modList );
 352	virtual idFileList *	ListFiles( const char *relativePath, const char *extension, bool sort = false, bool fullRelativePath = false, const char* gamedir = NULL );
 353	virtual idFileList *	ListFilesTree( const char *relativePath, const char *extension, bool sort = false, const char* gamedir = NULL );
 354	virtual void			FreeFileList( idFileList *fileList );
 355	virtual const char *	OSPathToRelativePath( const char *OSPath );
 356	virtual const char *	RelativePathToOSPath( const char *relativePath, const char *basePath );
 357	virtual const char *	BuildOSPath( const char *base, const char *game, const char *relativePath );
 358	virtual void			CreateOSPath( const char *OSPath );
 359	virtual bool			FileIsInPAK( const char *relativePath );
 360	virtual void			UpdatePureServerChecksums( void );
 361	virtual bool			UpdateGamePakChecksums( void );
 362	virtual fsPureReply_t	SetPureServerChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum, int missingChecksums[ MAX_PURE_PAKS ], int *missingGamePakChecksum );
 363	virtual void			GetPureServerChecksums( int checksums[ MAX_PURE_PAKS ], int OS, int *gamePakChecksum );
 364	virtual void			SetRestartChecksums( const int pureChecksums[ MAX_PURE_PAKS ], int gamePakChecksum );
 365	virtual	void			ClearPureChecksums( void );
 366	virtual int				GetOSMask( void );
 367	virtual int				ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp );
 368	virtual void			FreeFile( void *buffer );
 369	virtual int				WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath = "fs_savepath" );
 370	virtual void			RemoveFile( const char *relativePath );	
 371	virtual idFile *		OpenFileReadFlags( const char *relativePath, int searchFlags, pack_t **foundInPak = NULL, bool allowCopyFiles = true, const char* gamedir = NULL );
 372	virtual idFile *		OpenFileRead( const char *relativePath, bool allowCopyFiles = true, const char* gamedir = NULL );
 373	virtual idFile *		OpenFileWrite( const char *relativePath, const char *basePath = "fs_savepath" );
 374	virtual idFile *		OpenFileAppend( const char *relativePath, bool sync = false, const char *basePath = "fs_basepath"   );
 375	virtual idFile *		OpenFileByMode( const char *relativePath, fsMode_t mode );
 376	virtual idFile *		OpenExplicitFileRead( const char *OSPath );
 377	virtual idFile *		OpenExplicitFileWrite( const char *OSPath );
 378	virtual void			CloseFile( idFile *f );
 379	virtual void			BackgroundDownload( backgroundDownload_t *bgl );
 380	virtual void			ResetReadCount( void ) { readCount = 0; }
 381	virtual void			AddToReadCount( int c ) { readCount += c; }
 382	virtual int				GetReadCount( void ) { return readCount; }
 383	virtual void			FindDLL( const char *basename, char dllPath[ MAX_OSPATH ], bool updateChecksum );
 384	virtual void			ClearDirCache( void );
 385	virtual bool			HasD3XP( void );
 386	virtual bool			RunningD3XP( void );
 387	virtual void			CopyFile( const char *fromOSPath, const char *toOSPath );
 388	virtual int				ValidateDownloadPakForChecksum( int checksum, char path[ MAX_STRING_CHARS ], bool isBinary );
 389	virtual idFile *		MakeTemporaryFile( void );
 390	virtual int				AddZipFile( const char *path );
 391	virtual findFile_t		FindFile( const char *path, bool scheduleAddons );
 392	virtual int				GetNumMaps();
 393	virtual const idDict *	GetMapDecl( int i );
 394	virtual void			FindMapScreenshot( const char *path, char *buf, int len );
 395	virtual bool			FilenameCompare( const char *s1, const char *s2 ) const;
 396
 397	static void				Dir_f( const idCmdArgs &args );
 398	static void				DirTree_f( const idCmdArgs &args );
 399	static void				Path_f( const idCmdArgs &args );
 400	static void				TouchFile_f( const idCmdArgs &args );
 401	static void				TouchFileList_f( const idCmdArgs &args );
 402
 403private:
 404	friend dword 			BackgroundDownloadThread( void *parms );
 405
 406	searchpath_t *			searchPaths;
 407	int						readCount;			// total bytes read
 408	int						loadCount;			// total files read
 409	int						loadStack;			// total files in memory
 410	idStr					gameFolder;			// this will be a single name without separators
 411
 412	searchpath_t			*addonPaks;			// not loaded up, but we saw them
 413
 414	idDict					mapDict;			// for GetMapDecl
 415
 416	static idCVar			fs_debug;
 417	static idCVar			fs_restrict;
 418	static idCVar			fs_copyfiles;
 419	static idCVar			fs_basepath;
 420	static idCVar			fs_savepath;
 421	static idCVar			fs_cdpath;
 422	static idCVar			fs_devpath;
 423	static idCVar			fs_game;
 424	static idCVar			fs_game_base;
 425	static idCVar			fs_caseSensitiveOS;
 426	static idCVar			fs_searchAddons;
 427
 428	backgroundDownload_t *	backgroundDownloads;
 429	backgroundDownload_t	defaultBackgroundDownload;
 430	xthreadInfo				backgroundThread;
 431
 432	idList<pack_t *>		serverPaks;
 433	bool					loadedFileFromDir;		// set to true once a file was loaded from a directory - can't switch to pure anymore
 434	idList<int>				restartChecksums;		// used during a restart to set things in right order
 435	idList<int>				addonChecksums;			// list of checksums that should go to the search list directly ( for restarts )
 436	int						restartGamePakChecksum;
 437	int						gameDLLChecksum;		// the checksum of the last loaded game DLL
 438	int						gamePakChecksum;		// the checksum of the pak holding the loaded game DLL
 439
 440	int						gamePakForOS[ MAX_GAME_OS ];
 441
 442	idDEntry				dir_cache[ MAX_CACHED_DIRS ]; // fifo
 443	int						dir_cache_index;
 444	int						dir_cache_count;
 445
 446	int						d3xp;	// 0: didn't check, -1: not installed, 1: installed
 447
 448private:
 449	void					ReplaceSeparators( idStr &path, char sep = PATHSEPERATOR_CHAR );
 450	long					HashFileName( const char *fname ) const;
 451	int						ListOSFiles( const char *directory, const char *extension, idStrList &list );
 452	FILE *					OpenOSFile( const char *name, const char *mode, idStr *caseSensitiveName = NULL );
 453	FILE *					OpenOSFileCorrectName( idStr &path, const char *mode );
 454	int						DirectFileLength( FILE *o );
 455	void					CopyFile( idFile *src, const char *toOSPath );
 456	int						AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const;
 457	void					GetExtensionList( const char *extension, idStrList &extensionList ) const;
 458	int						GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir = NULL );
 459
 460	int						GetFileListTree( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, const char* gamedir = NULL );
 461	pack_t *				LoadZipFile( const char *zipfile );
 462	void					AddGameDirectory( const char *path, const char *dir );
 463	void					SetupGameDirectories( const char *gameName );
 464	void					Startup( void );
 465	void					SetRestrictions( void );
 466							// some files can be obtained from directories without compromising si_pure
 467	bool					FileAllowedFromDir( const char *path );
 468							// searches all the paks, no pure check
 469	pack_t *				GetPackForChecksum( int checksum, bool searchAddons = false );
 470							// searches all the paks, no pure check
 471	pack_t *				FindPakForFileChecksum( const char *relativePath, int fileChecksum, bool bReference );
 472	idFile_InZip *			ReadFileFromZip( pack_t *pak, fileInPack_t *pakFile, const char *relativePath );
 473	int						GetFileChecksum( idFile *file );
 474	pureStatus_t			GetPackStatus( pack_t *pak );
 475	addonInfo_t *			ParseAddonDef( const char *buf, const int len );
 476	void					FollowAddonDependencies( pack_t *pak );
 477
 478	static size_t			CurlWriteFunction( void *ptr, size_t size, size_t nmemb, void *stream );
 479							// curl_progress_callback in curl.h
 480	static int				CurlProgressFunction( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow );
 481};
 482
 483idCVar	idFileSystemLocal::fs_restrict( "fs_restrict", "", CVAR_SYSTEM | CVAR_INIT | CVAR_BOOL, "" );
 484idCVar	idFileSystemLocal::fs_debug( "fs_debug", "0", CVAR_SYSTEM | CVAR_INTEGER, "", 0, 2, idCmdSystem::ArgCompletion_Integer<0,2> );
 485idCVar	idFileSystemLocal::fs_copyfiles( "fs_copyfiles", "0", CVAR_SYSTEM | CVAR_INIT | CVAR_INTEGER, "", 0, 4, idCmdSystem::ArgCompletion_Integer<0,3> );
 486idCVar	idFileSystemLocal::fs_basepath( "fs_basepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
 487idCVar	idFileSystemLocal::fs_savepath( "fs_savepath", "", CVAR_SYSTEM | CVAR_INIT, "" );
 488idCVar	idFileSystemLocal::fs_cdpath( "fs_cdpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
 489idCVar	idFileSystemLocal::fs_devpath( "fs_devpath", "", CVAR_SYSTEM | CVAR_INIT, "" );
 490idCVar	idFileSystemLocal::fs_game( "fs_game", "", CVAR_SYSTEM | CVAR_INIT | CVAR_SERVERINFO, "mod path" );
 491idCVar  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" );
 492#ifdef WIN32
 493idCVar	idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "0", CVAR_SYSTEM | CVAR_BOOL, "" );
 494#else
 495idCVar	idFileSystemLocal::fs_caseSensitiveOS( "fs_caseSensitiveOS", "1", CVAR_SYSTEM | CVAR_BOOL, "" );
 496#endif
 497idCVar	idFileSystemLocal::fs_searchAddons( "fs_searchAddons", "0", CVAR_SYSTEM | CVAR_BOOL, "search all addon pk4s ( disables addon functionality )" );
 498
 499idFileSystemLocal	fileSystemLocal;
 500idFileSystem *		fileSystem = &fileSystemLocal;
 501
 502/*
 503================
 504idFileSystemLocal::idFileSystemLocal
 505================
 506*/
 507idFileSystemLocal::idFileSystemLocal( void ) {
 508	searchPaths = NULL;
 509	readCount = 0;
 510	loadCount = 0;
 511	loadStack = 0;
 512	dir_cache_index = 0;
 513	dir_cache_count = 0;
 514	d3xp = 0;
 515	loadedFileFromDir = false;
 516	restartGamePakChecksum = 0;
 517	memset( &backgroundThread, 0, sizeof( backgroundThread ) );
 518	addonPaks = NULL;
 519}
 520
 521/*
 522================
 523idFileSystemLocal::HashFileName
 524
 525return a hash value for the filename
 526================
 527*/
 528long idFileSystemLocal::HashFileName( const char *fname ) const {
 529	int		i;
 530	long	hash;
 531	char	letter;
 532
 533	hash = 0;
 534	i = 0;
 535	while( fname[i] != '\0' ) {
 536		letter = idStr::ToLower( fname[i] );
 537		if ( letter == '.' ) {
 538			break;				// don't include extension
 539		}
 540		if ( letter == '\\' ) {
 541			letter = '/';		// damn path names
 542		}
 543		hash += (long)(letter) * (i+119);
 544		i++;
 545	}
 546	hash &= (FILE_HASH_SIZE-1);
 547	return hash;
 548}
 549
 550/*
 551===========
 552idFileSystemLocal::FilenameCompare
 553
 554Ignore case and separator char distinctions
 555===========
 556*/
 557bool idFileSystemLocal::FilenameCompare( const char *s1, const char *s2 ) const {
 558	int		c1, c2;
 559	
 560	do {
 561		c1 = *s1++;
 562		c2 = *s2++;
 563
 564		if ( c1 >= 'a' && c1 <= 'z' ) {
 565			c1 -= ('a' - 'A');
 566		}
 567		if ( c2 >= 'a' && c2 <= 'z' ) {
 568			c2 -= ('a' - 'A');
 569		}
 570
 571		if ( c1 == '\\' || c1 == ':' ) {
 572			c1 = '/';
 573		}
 574		if ( c2 == '\\' || c2 == ':' ) {
 575			c2 = '/';
 576		}
 577		
 578		if ( c1 != c2 ) {
 579			return true;		// strings not equal
 580		}
 581	} while( c1 );
 582	
 583	return false;		// strings are equal
 584}
 585
 586/*
 587================
 588idFileSystemLocal::OpenOSFile
 589optional caseSensitiveName is set to case sensitive file name as found on disc (fs_caseSensitiveOS only)
 590================
 591*/
 592FILE *idFileSystemLocal::OpenOSFile( const char *fileName, const char *mode, idStr *caseSensitiveName ) {
 593	int i;
 594	FILE *fp;
 595	idStr fpath, entry;
 596	idStrList list;
 597
 598#ifndef __MWERKS__
 599#ifndef WIN32 
 600	// some systems will let you fopen a directory
 601	struct stat buf;
 602	if ( stat( fileName, &buf ) != -1 && !S_ISREG(buf.st_mode) ) {
 603		return NULL;
 604	}
 605#endif
 606#endif
 607	fp = fopen( fileName, mode );
 608	if ( !fp && fs_caseSensitiveOS.GetBool() ) {
 609		fpath = fileName;
 610		fpath.StripFilename();
 611		fpath.StripTrailing( PATHSEPERATOR_CHAR );
 612		if ( ListOSFiles( fpath, NULL, list ) == -1 ) {
 613			return NULL;
 614		}
 615		
 616		for ( i = 0; i < list.Num(); i++ ) {
 617			entry = fpath + PATHSEPERATOR_CHAR + list[i];
 618			if ( !entry.Icmp( fileName ) ) {
 619				fp = fopen( entry, mode );
 620				if ( fp ) {
 621					if ( caseSensitiveName ) {
 622						*caseSensitiveName = entry;
 623						caseSensitiveName->StripPath();
 624					}
 625					if ( fs_debug.GetInteger() ) {
 626						common->Printf( "idFileSystemLocal::OpenFileRead: changed %s to %s\n", fileName, entry.c_str() );
 627					}
 628					break;
 629				} else {
 630					// not supposed to happen if ListOSFiles is doing it's job correctly
 631					common->Warning( "idFileSystemLocal::OpenFileRead: fs_caseSensitiveOS 1 could not open %s", entry.c_str() );
 632				}
 633			}
 634		}
 635	} else if ( caseSensitiveName ) {
 636		*caseSensitiveName = fileName;
 637		caseSensitiveName->StripPath();
 638	}
 639	return fp;
 640}
 641
 642/*
 643================
 644idFileSystemLocal::OpenOSFileCorrectName
 645================
 646*/
 647FILE *idFileSystemLocal::OpenOSFileCorrectName( idStr &path, const char *mode ) {
 648	idStr caseName;
 649	FILE *f = OpenOSFile( path.c_str(), mode, &caseName );
 650	if ( f ) {
 651		path.StripFilename();
 652		path += PATHSEPERATOR_STR;
 653		path += caseName;
 654	}
 655	return f;
 656}
 657
 658/*
 659================
 660idFileSystemLocal::DirectFileLength
 661================
 662*/
 663int idFileSystemLocal::DirectFileLength( FILE *o ) {
 664	int		pos;
 665	int		end;
 666
 667	pos = ftell( o );
 668	fseek( o, 0, SEEK_END );
 669	end = ftell( o );
 670	fseek( o, pos, SEEK_SET );
 671	return end;
 672}
 673
 674/*
 675============
 676idFileSystemLocal::CreateOSPath
 677
 678Creates any directories needed to store the given filename
 679============
 680*/
 681void idFileSystemLocal::CreateOSPath( const char *OSPath ) {
 682	char	*ofs;
 683	
 684	// make absolutely sure that it can't back up the path
 685	// FIXME: what about c: ?
 686	if ( strstr( OSPath, ".." ) || strstr( OSPath, "::" ) ) {
 687#ifdef _DEBUG		
 688		common->DPrintf( "refusing to create relative path \"%s\"\n", OSPath );
 689#endif
 690		return;
 691	}
 692
 693	idStr path( OSPath );
 694	for( ofs = &path[ 1 ]; *ofs ; ofs++ ) {
 695		if ( *ofs == PATHSEPERATOR_CHAR ) {	
 696			// create the directory
 697			*ofs = 0;
 698			Sys_Mkdir( path );
 699			*ofs = PATHSEPERATOR_CHAR;
 700		}
 701	}
 702}
 703
 704/*
 705=================
 706idFileSystemLocal::CopyFile
 707
 708Copy a fully specified file from one place to another
 709=================
 710*/
 711void idFileSystemLocal::CopyFile( const char *fromOSPath, const char *toOSPath ) {
 712	FILE	*f;
 713	int		len;
 714	byte	*buf;
 715
 716	common->Printf( "copy %s to %s\n", fromOSPath, toOSPath );
 717	f = OpenOSFile( fromOSPath, "rb" );
 718	if ( !f ) {
 719		return;
 720	}
 721	fseek( f, 0, SEEK_END );
 722	len = ftell( f );
 723	fseek( f, 0, SEEK_SET );
 724
 725	buf = (byte *)Mem_Alloc( len );
 726	if ( fread( buf, 1, len, f ) != (unsigned int)len ) {
 727		common->FatalError( "short read in idFileSystemLocal::CopyFile()\n" );
 728	}
 729	fclose( f );
 730
 731	CreateOSPath( toOSPath );
 732	f = OpenOSFile( toOSPath, "wb" );
 733	if ( !f ) {
 734		common->Printf( "could not create destination file\n" );
 735		Mem_Free( buf );
 736		return;
 737	}
 738	if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
 739		common->FatalError( "short write in idFileSystemLocal::CopyFile()\n" );
 740	}
 741	fclose( f );
 742	Mem_Free( buf );
 743}
 744
 745/*
 746=================
 747idFileSystemLocal::CopyFile
 748=================
 749*/
 750void idFileSystemLocal::CopyFile( idFile *src, const char *toOSPath ) {
 751	FILE	*f;
 752	int		len;
 753	byte	*buf;
 754
 755	common->Printf( "copy %s to %s\n", src->GetName(), toOSPath );
 756	src->Seek( 0, FS_SEEK_END );
 757	len = src->Tell();
 758	src->Seek( 0, FS_SEEK_SET );
 759
 760	buf = (byte *)Mem_Alloc( len );
 761	if ( src->Read( buf, len ) != len ) {
 762		common->FatalError( "Short read in idFileSystemLocal::CopyFile()\n" );
 763	}
 764
 765	CreateOSPath( toOSPath );
 766	f = OpenOSFile( toOSPath, "wb" );
 767	if ( !f ) {
 768		common->Printf( "could not create destination file\n" );
 769		Mem_Free( buf );
 770		return;
 771	}
 772	if ( fwrite( buf, 1, len, f ) != (unsigned int)len ) {
 773		common->FatalError( "Short write in idFileSystemLocal::CopyFile()\n" );
 774	}
 775	fclose( f );
 776	Mem_Free( buf );
 777}
 778
 779/*
 780====================
 781idFileSystemLocal::ReplaceSeparators
 782
 783Fix things up differently for win/unix/mac
 784====================
 785*/
 786void idFileSystemLocal::ReplaceSeparators( idStr &path, char sep ) {
 787	char *s;
 788
 789	for( s = &path[ 0 ]; *s ; s++ ) {
 790		if ( *s == '/' || *s == '\\' ) {
 791			*s = sep;
 792		}
 793	}
 794}
 795
 796/*
 797===================
 798idFileSystemLocal::BuildOSPath
 799===================
 800*/
 801const char *idFileSystemLocal::BuildOSPath( const char *base, const char *game, const char *relativePath ) {
 802	static char OSPath[MAX_STRING_CHARS];
 803	idStr newPath;
 804
 805	if ( fs_caseSensitiveOS.GetBool() || com_developer.GetBool() ) {
 806		// extract the path, make sure it's all lowercase
 807		idStr testPath, fileName;
 808
 809		sprintf( testPath, "%s/%s", game , relativePath );
 810		testPath.StripFilename();
 811
 812		if ( testPath.HasUpper() ) {
 813
 814			common->Warning( "Non-portable: path contains uppercase characters: %s", testPath.c_str() );
 815
 816			// attempt a fixup on the fly
 817			if ( fs_caseSensitiveOS.GetBool() ) {
 818				testPath.ToLower();
 819				fileName = relativePath;
 820				fileName.StripPath();
 821				sprintf( newPath, "%s/%s/%s", base, testPath.c_str(), fileName.c_str() );
 822				ReplaceSeparators( newPath );
 823				common->DPrintf( "Fixed up to %s\n", newPath.c_str() );
 824				idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
 825				return OSPath;
 826			}
 827		}
 828	}
 829
 830	idStr strBase = base;
 831	strBase.StripTrailing( '/' );
 832	strBase.StripTrailing( '\\' );
 833	sprintf( newPath, "%s/%s/%s", strBase.c_str(), game, relativePath );
 834	ReplaceSeparators( newPath );
 835	idStr::Copynz( OSPath, newPath, sizeof( OSPath ) );
 836	return OSPath;
 837}
 838
 839/*
 840================
 841idFileSystemLocal::OSPathToRelativePath
 842
 843takes a full OS path, as might be found in data from a media creation
 844program, and converts it to a relativePath by stripping off directories
 845
 846Returns false if the osPath tree doesn't match any of the existing
 847search paths.
 848
 849================
 850*/
 851const char *idFileSystemLocal::OSPathToRelativePath( const char *OSPath ) {
 852	static char relativePath[MAX_STRING_CHARS];
 853	char *s, *base;
 854
 855	// skip a drive letter?
 856
 857	// search for anything with "base" in it
 858	// Ase files from max may have the form of:
 859	// "//Purgatory/purgatory/doom/base/models/mapobjects/bitch/hologirl.tga"
 860	// which won't match any of our drive letter based search paths
 861	bool ignoreWarning = false;
 862#ifdef ID_DEMO_BUILD
 863	base = strstr( OSPath, BASE_GAMEDIR );	
 864	idStr tempStr = OSPath;
 865	tempStr.ToLower();
 866	if ( ( strstr( tempStr, "//" ) || strstr( tempStr, "w:" ) ) && strstr( tempStr, "/doom/base/") ) {
 867		// will cause a warning but will load the file. ase models have
 868		// hard coded doom/base/ in the material names
 869		base = strstr( OSPath, "base" );
 870		ignoreWarning = true;
 871	}
 872#else
 873	// look for the first complete directory name
 874	base = (char *)strstr( OSPath, BASE_GAMEDIR );
 875	while ( base ) {
 876		char c1 = '\0', c2;
 877		if ( base > OSPath ) {
 878			c1 = *(base - 1);
 879		}
 880		c2 = *( base + strlen( BASE_GAMEDIR ) );
 881		if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
 882			break;
 883		}
 884		base = strstr( base + 1, BASE_GAMEDIR );
 885	}
 886#endif
 887	// fs_game and fs_game_base support - look for first complete name with a mod path
 888	// ( fs_game searched before fs_game_base )
 889	const char *fsgame = NULL;
 890	int igame = 0;
 891	for ( igame = 0; igame < 2; igame++ ) {
 892		if ( igame == 0 ) {
 893			fsgame = fs_game.GetString();
 894		} else if ( igame == 1 ) {
 895			fsgame = fs_game_base.GetString();
 896		}
 897		if ( base == NULL && fsgame && strlen( fsgame ) ) {
 898			base = (char *)strstr( OSPath, fsgame );
 899			while ( base ) {
 900				char c1 = '\0', c2;
 901				if ( base > OSPath ) {
 902					c1 = *(base - 1);
 903				}
 904				c2 = *( base + strlen( fsgame ) );
 905				if ( ( c1 == '/' || c1 == '\\' ) && ( c2 == '/' || c2 == '\\' ) ) {
 906					break;
 907				}
 908				base = strstr( base + 1, fsgame );
 909			}
 910		}
 911	}
 912
 913	if ( base ) {
 914		s = strstr( base, "/" );
 915		if ( !s ) {
 916			s = strstr( base, "\\" );
 917		}
 918		if ( s ) {
 919			strcpy( relativePath, s + 1 );
 920			if ( fs_debug.GetInteger() > 1 ) {
 921				common->Printf( "idFileSystem::OSPathToRelativePath: %s becomes %s\n", OSPath, relativePath );
 922			}
 923			return relativePath;
 924		}
 925	}
 926
 927	if ( !ignoreWarning ) {
 928		common->Warning( "idFileSystem::OSPathToRelativePath failed on %s", OSPath );
 929	}
 930	strcpy( relativePath, "" );
 931	return relativePath;
 932}
 933
 934/*
 935=====================
 936idFileSystemLocal::RelativePathToOSPath
 937
 938Returns a fully qualified path that can be used with stdio libraries
 939=====================
 940*/
 941const char *idFileSystemLocal::RelativePathToOSPath( const char *relativePath, const char *basePath ) {
 942	const char *path = cvarSystem->GetCVarString( basePath );
 943	if ( !path[0] ) {
 944		path = fs_savepath.GetString();
 945	}
 946	return BuildOSPath( path, gameFolder, relativePath );
 947}
 948
 949/*
 950=================
 951idFileSystemLocal::RemoveFile
 952=================
 953*/
 954void idFileSystemLocal::RemoveFile( const char *relativePath ) {
 955	idStr OSPath;
 956
 957	if ( fs_devpath.GetString()[0] ) {
 958		OSPath = BuildOSPath( fs_devpath.GetString(), gameFolder, relativePath );
 959		remove( OSPath );
 960	}
 961
 962	OSPath = BuildOSPath( fs_savepath.GetString(), gameFolder, relativePath );
 963	remove( OSPath );
 964
 965	ClearDirCache();
 966}
 967
 968/*
 969================
 970idFileSystemLocal::FileIsInPAK
 971================
 972*/
 973bool idFileSystemLocal::FileIsInPAK( const char *relativePath ) {
 974	searchpath_t	*search;
 975	pack_t			*pak;
 976	fileInPack_t	*pakFile;
 977	long			hash;
 978
 979	if ( !searchPaths ) {
 980		common->FatalError( "Filesystem call made without initialization\n" );
 981	}
 982
 983	if ( !relativePath ) {
 984		common->FatalError( "idFileSystemLocal::FileIsInPAK: NULL 'relativePath' parameter passed\n" );
 985	}
 986
 987	// qpaths are not supposed to have a leading slash
 988	if ( relativePath[0] == '/' || relativePath[0] == '\\' ) {
 989		relativePath++;
 990	}
 991
 992	// make absolutely sure that it can't back up the path.
 993	// The searchpaths do guarantee that something will always
 994	// be prepended, so we don't need to worry about "c:" or "//limbo" 
 995	if ( strstr( relativePath, ".." ) || strstr( relativePath, "::" ) ) {
 996		return false;
 997	}
 998
 999	//
1000	// search through the path, one element at a time
1001	//
1002
1003	hash = HashFileName( relativePath );
1004
1005	for ( search = searchPaths; search; search = search->next ) {
1006		// is the element a pak file?
1007		if ( search->pack && search->pack->hashTable[hash] ) {
1008
1009			// disregard if it doesn't match one of the allowed pure pak files - or is a localization file
1010			if ( serverPaks.Num() ) {
1011				GetPackStatus( search->pack );
1012				if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
1013					continue; // not on the pure server pak list
1014				}
1015			}
1016
1017			// look through all the pak file elements
1018			pak = search->pack;
1019			pakFile = pak->hashTable[hash];
1020			do {
1021				// case and separator insensitive comparisons
1022				if ( !FilenameCompare( pakFile->name, relativePath ) ) {
1023					return true;
1024				}
1025				pakFile = pakFile->next;
1026			} while( pakFile != NULL );
1027		}
1028	}
1029	return false;
1030}
1031
1032/*
1033============
1034idFileSystemLocal::ReadFile
1035
1036Filename are relative to the search path
1037a null buffer will just return the file length and time without loading
1038timestamp can be NULL if not required
1039============
1040*/
1041int idFileSystemLocal::ReadFile( const char *relativePath, void **buffer, ID_TIME_T *timestamp ) {
1042	idFile *	f;
1043	byte *		buf;
1044	int			len;
1045	bool		isConfig;
1046
1047	if ( !searchPaths ) {
1048		common->FatalError( "Filesystem call made without initialization\n" );
1049	}
1050
1051	if ( !relativePath || !relativePath[0] ) {
1052		common->FatalError( "idFileSystemLocal::ReadFile with empty name\n" );
1053	}
1054
1055	if ( timestamp ) {
1056		*timestamp = FILE_NOT_FOUND_TIMESTAMP;
1057	}
1058
1059	if ( buffer ) {
1060		*buffer = NULL;
1061	}
1062
1063	buf = NULL;	// quiet compiler warning
1064
1065	// if this is a .cfg file and we are playing back a journal, read
1066	// it from the journal file
1067	if ( strstr( relativePath, ".cfg" ) == relativePath + strlen( relativePath ) - 4 ) {
1068		isConfig = true;
1069		if ( eventLoop && eventLoop->JournalLevel() == 2 ) {
1070			int		r;
1071
1072			loadCount++;
1073			loadStack++;
1074
1075			common->DPrintf( "Loading %s from journal file.\n", relativePath );
1076			len = 0;
1077			r = eventLoop->com_journalDataFile->Read( &len, sizeof( len ) );
1078			if ( r != sizeof( len ) ) {
1079				*buffer = NULL;
1080				return -1;
1081			}
1082			buf = (byte *)Mem_ClearedAlloc(len+1);
1083			*buffer = buf;
1084			r = eventLoop->com_journalDataFile->Read( buf, len );
1085			if ( r != len ) {
1086				common->FatalError( "Read from journalDataFile failed" );
1087			}
1088
1089			// guarantee that it will have a trailing 0 for string operations
1090			buf[len] = 0;
1091
1092			return len;
1093		}
1094	} else {
1095		isConfig = false;
1096	}
1097
1098	// look for it in the filesystem or pack files
1099	f = OpenFileRead( relativePath, ( buffer != NULL ) );
1100	if ( f == NULL ) {
1101		if ( buffer ) {
1102			*buffer = NULL;
1103		}
1104		return -1;
1105	}
1106	len = f->Length();
1107
1108	if ( timestamp ) {
1109		*timestamp = f->Timestamp();
1110	}
1111	
1112	if ( !buffer ) {
1113		CloseFile( f );
1114		return len;
1115	}
1116
1117	loadCount++;
1118	loadStack++;
1119
1120	buf = (byte *)Mem_ClearedAlloc(len+1);
1121	*buffer = buf;
1122
1123	f->Read( buf, len );
1124
1125	// guarantee that it will have a trailing 0 for string operations
1126	buf[len] = 0;
1127	CloseFile( f );
1128
1129	// if we are journalling and it is a config file, write it to the journal file
1130	if ( isConfig && eventLoop && eventLoop->JournalLevel() == 1 ) {
1131		common->DPrintf( "Writing %s to journal file.\n", relativePath );
1132		eventLoop->com_journalDataFile->Write( &len, sizeof( len ) );
1133		eventLoop->com_journalDataFile->Write( buf, len );
1134		eventLoop->com_journalDataFile->Flush();
1135	}
1136
1137	return len;
1138}
1139
1140/*
1141=============
1142idFileSystemLocal::FreeFile
1143=============
1144*/
1145void idFileSystemLocal::FreeFile( void *buffer ) {
1146	if ( !searchPaths ) {
1147		common->FatalError( "Filesystem call made without initialization\n" );
1148	}
1149	if ( !buffer ) {
1150		common->FatalError( "idFileSystemLocal::FreeFile( NULL )" );
1151	}
1152	loadStack--;
1153
1154	Mem_Free( buffer );
1155}
1156
1157/*
1158============
1159idFileSystemLocal::WriteFile
1160
1161Filenames are relative to the search path
1162============
1163*/
1164int idFileSystemLocal::WriteFile( const char *relativePath, const void *buffer, int size, const char *basePath ) {
1165	idFile *f;
1166
1167	if ( !searchPaths ) {
1168		common->FatalError( "Filesystem call made without initialization\n" );
1169	}
1170
1171	if ( !relativePath || !buffer ) {
1172		common->FatalError( "idFileSystemLocal::WriteFile: NULL parameter" );
1173	}
1174
1175	f = idFileSystemLocal::OpenFileWrite( relativePath, basePath );
1176	if ( !f ) {
1177		common->Printf( "Failed to open %s\n", relativePath );
1178		return -1;
1179	}
1180
1181	size = f->Write( buffer, size );
1182
1183	CloseFile( f );
1184
1185	return size;
1186}
1187
1188/*
1189=================
1190idFileSystemLocal::ParseAddonDef
1191=================
1192*/
1193addonInfo_t *idFileSystemLocal::ParseAddonDef( const char *buf, const int len ) {
1194	idLexer		src;
1195	idToken		token, token2;
1196	addonInfo_t	*info;
1197
1198	src.LoadMemory( buf, len, "<addon.conf>" );
1199	src.SetFlags( DECL_LEXER_FLAGS );
1200	if ( !src.SkipUntilString( "addonDef" ) ) {
1201		src.Warning( "ParseAddonDef: no addonDef" );
1202		return NULL;
1203	}
1204	if ( !src.ReadToken( &token ) ) {
1205		src.Warning( "Expected {" );
1206		return NULL;
1207	}
1208	info = new addonInfo_t;
1209	// read addonDef
1210	while ( 1 ) {
1211		if ( !src.ReadToken( &token ) ) {
1212			delete info;
1213			return NULL;
1214		}
1215		if ( !token.Icmp( "}" ) ) {
1216			break;
1217		}
1218		if ( token.type != TT_STRING ) {
1219			src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
1220			delete info;
1221			return NULL;
1222		}
1223		int checksum;
1224		if ( sscanf( token.c_str(), "0x%x", &checksum ) != 1 && sscanf( token.c_str(), "%x", &checksum ) != 1 ) {
1225			src.Warning( "Could not parse checksum '%s'", token.c_str() );
1226			delete info;
1227			return NULL;
1228		}
1229		info->depends.Append( checksum );
1230	}
1231	// read any number of mapDef entries
1232	while ( 1 ) {
1233		if ( !src.SkipUntilString( "mapDef" ) ) {
1234			return info;
1235		}
1236		if ( !src.ReadToken( &token ) ) {
1237			src.Warning( "Expected map path" );
1238			info->mapDecls.DeleteContents( true );
1239			delete info;
1240			return NULL;
1241		}
1242		idDict *dict = new idDict;
1243		dict->Set( "path", token.c_str() );
1244		if ( !src.ReadToken( &token ) ) {
1245			src.Warning( "Expected {" );
1246			info->mapDecls.DeleteContents( true );
1247			delete dict;
1248			delete info;
1249			return NULL;
1250		}
1251		while ( 1 ) {
1252			if ( !src.ReadToken( &token ) ) {
1253				break;
1254			}
1255			if ( !token.Icmp( "}" ) ) {
1256				break;
1257			}
1258			if ( token.type != TT_STRING ) {
1259				src.Warning( "Expected quoted string, but found '%s'", token.c_str() );
1260				info->mapDecls.DeleteContents( true );
1261				delete dict;
1262				delete info;
1263				return NULL;
1264			}
1265
1266			if ( !src.ReadToken( &token2 ) ) {
1267				src.Warning( "Unexpected end of file" );
1268				info->mapDecls.DeleteContents( true );
1269				delete dict;
1270				delete info;
1271				return NULL;
1272			}
1273
1274			if ( dict->FindKey( token ) ) {
1275				src.Warning( "'%s' already defined", token.c_str() );
1276			}
1277			dict->Set( token, token2 );
1278		}
1279		info->mapDecls.Append( dict );
1280	}
1281	assert( false );
1282	return NULL;
1283}
1284
1285/*
1286=================
1287idFileSystemLocal::LoadZipFile
1288=================
1289*/
1290pack_t *idFileSystemLocal::LoadZipFile( const char *zipfile ) {
1291	fileInPack_t *	buildBuffer;
1292	pack_t *		pack;
1293	unzFile			uf;
1294	int				err;
1295	unz_global_info gi;
1296	char			filename_inzip[MAX_ZIPPED_FILE_NAME];
1297	unz_file_info	file_info;
1298	int				i;
1299	long			hash;
1300	int				fs_numHeaderLongs;
1301	int *			fs_headerLongs;
1302	FILE			*f;
1303	int				len;
1304	int				confHash;
1305	fileInPack_t	*pakFile;
1306
1307	f = OpenOSFile( zipfile, "rb" );
1308	if ( !f ) {
1309		return NULL;
1310	}
1311	fseek( f, 0, SEEK_END );
1312	len = ftell( f );
1313	fclose( f );
1314
1315	fs_numHeaderLongs = 0;
1316
1317	uf = unzOpen( zipfile );
1318	err = unzGetGlobalInfo( uf, &gi );
1319
1320	if ( err != UNZ_OK ) {
1321		return NULL;
1322	}
1323
1324	buildBuffer = new fileInPack_t[gi.number_entry];
1325	pack = new pack_t;
1326	for( i = 0; i < FILE_HASH_SIZE; i++ ) {
1327		pack->hashTable[i] = NULL;
1328	}
1329
1330	pack->pakFilename = zipfile;
1331	pack->handle = uf;
1332	pack->numfiles = gi.number_entry;
1333	pack->buildBuffer = buildBuffer;
1334	pack->referenced = false;
1335	pack->binary = BINARY_UNKNOWN;
1336	pack->addon = false;
1337	pack->addon_search = false;
1338	pack->addon_info = NULL;
1339	pack->pureStatus = PURE_UNKNOWN;
1340	pack->isNew = false;
1341
1342	pack->length = len;
1343
1344	unzGoToFirstFile(uf);
1345	fs_headerLongs = (int *)Mem_ClearedAlloc( gi.number_entry * sizeof(int) );
1346	for ( i = 0; i < (int)gi.number_entry; i++ ) {
1347		err = unzGetCurrentFileInfo( uf, &file_info, filename_inzip, sizeof(filename_inzip), NULL, 0, NULL, 0 );
1348		if ( err != UNZ_OK ) {
1349			break;
1350		}
1351		if ( file_info.uncompressed_size > 0 ) {
1352			fs_headerLongs[fs_numHeaderLongs++] = LittleLong( file_info.crc );
1353		}
1354		hash = HashFileName( filename_inzip );
1355		buildBuffer[i].name = filename_inzip;
1356		buildBuffer[i].name.ToLower();
1357		buildBuffer[i].name.BackSlashesToSlashes();
1358		// store the file position in the zip
1359		unzGetCurrentFileInfoPosition( uf, &buildBuffer[i].pos );
1360		// add the file to the hash
1361		buildBuffer[i].next = pack->hashTable[hash];
1362		pack->hashTable[hash] = &buildBuffer[i];
1363		// go to the next file in the zip
1364		unzGoToNextFile(uf);
1365	}
1366
1367	// check if this is an addon pak
1368	pack->addon = false;
1369	confHash = HashFileName( ADDON_CONFIG );
1370	for ( pakFile = pack->hashTable[confHash]; pakFile; pakFile = pakFile->next ) {
1371		if ( !FilenameCompare( pakFile->name, ADDON_CONFIG ) ) {			
1372			pack->addon = true;			
1373			idFile_InZip *file = ReadFileFromZip( pack, pakFile, ADDON_CONFIG );
1374			// may be just an empty file if you don't bother about the mapDef
1375			if ( file && file->Length() ) {
1376				char *buf;
1377				buf = new char[ file->Length() + 1 ];
1378				file->Read( (void *)buf, file->Length() );
1379				buf[ file->Length() ] = '\0';
1380				pack->addon_info = ParseAddonDef( buf, file->Length() );
1381				delete[] buf;
1382			}
1383			if ( file ) {
1384				CloseFile( file );
1385			}
1386			break;
1387		}
1388	}
1389
1390	pack->checksum = MD4_BlockChecksum( fs_headerLongs, 4 * fs_numHeaderLongs );
1391	pack->checksum = LittleLong( pack->checksum );
1392
1393	Mem_Free( fs_headerLongs );
1394
1395	return pack;
1396}
1397
1398/*
1399===============
1400idFileSystemLocal::AddZipFile
1401adds a downloaded pak file to the list so we can work out what we have and what we still need
1402the isNew flag is set to true, indicating that we cannot add this pak to the search lists without a restart
1403===============
1404*/
1405int idFileSystemLocal::AddZipFile( const char *path ) {
1406	idStr			fullpath = fs_savepath.GetString();
1407	pack_t			*pak;
1408	searchpath_t	*search, *last;
1409
1410	fullpath.AppendPath( path );
1411	pak = LoadZipFile( fullpath );
1412	if ( !pak ) {
1413		common->Warning( "AddZipFile %s failed\n", path );
1414		return 0;
1415	}
1416	// insert the pak at the end of the search list - temporary until we restart
1417	pak->isNew = true;
1418	search = new searchpath_t;
1419	search->dir = NULL;
1420	search->pack = pak;
1421	search->next = NULL;
1422	last = searchPaths;
1423	while ( last->next ) {
1424		last = last->next;
1425	}
1426	last->next = search;
1427	common->Printf( "Appended pk4 %s with checksum 0x%x\n", pak->pakFilename.c_str(), pak->checksum );
1428	return pak->checksum;
1429}
1430
1431/*
1432===============
1433idFileSystemLocal::AddUnique
1434===============
1435*/
1436int idFileSystemLocal::AddUnique( const char *name, idStrList &list, idHashIndex &hashIndex ) const {
1437	int i, hashKey;
1438
1439	hashKey = hashIndex.GenerateKey( name );
1440	for ( i = hashIndex.First( hashKey ); i >= 0; i = hashIndex.Next( i ) ) {
1441		if ( list[i].Icmp( name ) == 0 ) {
1442			return i;
1443		}
1444	}
1445	i = list.Append( name );
1446	hashIndex.Add( hashKey, i );
1447	return i;
1448}
1449
1450/*
1451===============
1452idFileSystemLocal::GetExtensionList
1453===============
1454*/
1455void idFileSystemLocal::GetExtensionList( const char *extension, idStrList &extensionList ) const {
1456	int s, e, l;
1457
1458	l = idStr::Length( extension );
1459	s = 0;
1460	while( 1 ) {
1461		e = idStr::FindChar( extension, '|', s, l );
1462		if ( e != -1 ) {
1463			extensionList.Append( idStr( extension, s, e ) );
1464			s = e + 1;
1465		} else {
1466			extensionList.Append( idStr( extension, s, l ) );
1467			break;
1468		}
1469	}
1470}
1471
1472/*
1473===============
1474idFileSystemLocal::GetFileList
1475
1476Does not clear the list first so this can be used to progressively build a file list.
1477When 'sort' is true only the new files added to the list are sorted.
1478===============
1479*/
1480int idFileSystemLocal::GetFileList( const char *relativePath, const idStrList &extensions, idStrList &list, idHashIndex &hashIndex, bool fullRelativePath, const char* gamedir ) {
1481	searchpath_t *	search;
1482	fileInPack_t *	buildBuffer;
1483	int				i, j;
1484	int				pathLength;
1485	int				length;
1486	const char *	name;
1487	pack_t *		pak;
1488	idStr			work;
1489
1490	if ( !searchPaths ) {
1491		common->FatalError( "Filesystem call made without initialization\n" );
1492	}
1493
1494	if ( !extensions.Num() ) {
1495		return 0;
1496	}
1497
1498	if ( !relativePath ) {
1499		return 0;
1500	}
1501	pathLength = strlen( relativePath );
1502	if ( pathLength ) {
1503		pathLength++;	// for the trailing '/'
1504	}
1505
1506	// search through the path, one element at a time, adding to list
1507	for( search = searchPaths; search != NULL; search = search->next ) {
1508		if ( search->dir ) {
1509			if(gamedir && strlen(gamedir)) {
1510				if(search->dir->gamedir != gamedir) {
1511					continue;
1512				}
1513			}
1514
1515			idStrList	sysFiles;
1516			idStr		netpath;
1517
1518			netpath = BuildOSPath( search->dir->path, search->dir->gamedir, relativePath );
1519
1520			for ( i = 0; i < extensions.Num(); i++ ) {
1521
1522				// scan for files in the filesystem
1523				ListOSFiles( netpath, extensions[i], sysFiles );
1524
1525				// if we are searching for directories, remove . and ..
1526				if ( extensions[i][0] == '/' && extensions[i][1] == 0 ) {
1527					sysFiles.Remove( "." );
1528					sysFiles.Remove( ".." );
1529				}
1530
1531				for( j = 0; j < sysFiles.Num(); j++ ) {
1532					// unique the match
1533					if ( fullRelativePath ) {
1534						work = relativePath;
1535						work += "/";
1536						work += sysFiles[j];
1537						AddUnique( work, list, hashIndex );
1538					}
1539					else {
1540						AddUnique( sysFiles[j], list, hashIndex );
1541					}
1542				}
1543			}
1544		} else if ( search->pack ) {
1545			// look through all the pak file elements
1546
1547			// exclude any extra packs if we have server paks to search
1548			if ( serverPaks.Num() ) {
1549				GetPackStatus( search->pack );
1550				if ( search->pack->pureStatus != PURE_NEVER && !serverPaks.Find( search->pack ) ) {
1551					continue; // not on the pure server pak list
1552				}
1553			}
1554
1555			pak = search->pack;
1556			buildBuffer = pak->buildBuffer;
1557			for( i = 0; i < pak->numfiles; i++ ) {
1558
1559				length = buildBuffer[i].name.Length();
1560
1561				// if the name is not long anough to at least contain the path
1562				if ( length <= pathLength ) {
1563					continue;
1564				}
1565
1566				name = buildBuffer[i].name;
1567
1568
1569				// check for a path match without the trailing '/'
1570				if ( pathLength && idStr::Icmpn( name, relativePath, pathLength - 1 ) != 0 ) {
1571					continue;
1572				}
1573 
1574				// ensure we have a path, and not just a filename containing the path
1575				if ( name[ pathLength ] == '\0' || name[pathLength - 1] != '/' ) {
1576					continue;
1577				}
1578 
1579				// make sure the file is not in a subdirectory
1580				for ( j = pathLength; name[j+1] != '\0'; j++ ) {
1581					if ( name[j] == '/' ) {
1582						break;
1583					}
1584				}
1585				if ( name[j+1] ) {
1586					continue;
1587				}
1588
1589				// check for extension match
1590				for ( j = 0; j < extensions.Num(); j++ ) {
1591					if ( length >= extensions[j].Length() && extensions[j].Icmp( name + length - extensions[j].Length() ) == 0 ) {
1592						break;
1593					}
1594				}
1595

Large files files are truncated, but you can click here to view the full file