//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//===========================================================================//

#ifdef POSIX
#define _snwprintf swprintf
#endif

#include "basefilesystem.h"
#include "tier0/vprof.h"
#include "tier1/characterset.h"
#include "tier1/utlbuffer.h"
#include "tier1/convar.h"
#include "tier1/KeyValues.h"
#include "tier0/icommandline.h"
#include "generichash.h"
#include "tier1/utllinkedlist.h"
#include "filesystem/IQueuedLoader.h"
#include "tier2/tier2.h"
#include "zip_utils.h"
#include "packfile.h"
#ifdef _X360
#include "xbox/xbox_launch.h"
#endif

#ifndef DEDICATED
#include "keyvaluescompiler.h"
#endif
#include "ifilelist.h"

#ifdef IS_WINDOWS_PC
// Needed for getting file type string
#define WIN32_LEAN_AND_MEAN
#include <shellapi.h>
#endif

#if defined( _X360 )
#include "xbox\xbox_win32stubs.h"
#undef GetCurrentDirectory
#endif

#include <time.h>

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"


#pragma warning( disable : 4355 )  // warning C4355: 'this' : used in base member initializer list


ConVar fs_report_sync_opens( "fs_report_sync_opens", "0", 0, "0:Off, 1:Blocking only, 2:All" );
ConVar fs_warning_mode( "fs_warning_mode", "0", 0, "0:Off, 1:Warn main thread, 2:Warn other threads"  );

#define BSPOUTPUT	0	// bsp output flag -- determines type of fs_log output to generate

static void AddSeperatorAndFixPath( char *str );

// Case-insensitive symbol table for path IDs.
CUtlSymbolTableMT g_PathIDTable( 0, 32, true );

int g_iNextSearchPathID = 1;

// This can be used to easily fix a filename on the stack.
#ifdef	DBGFLAG_ASSERT
// Look for cases like materials\\blah.vmt.
static bool V_CheckDoubleSlashes( const char *pStr )
{
	int len = V_strlen( pStr );

	for ( int i=1; i < len-1; i++ )
	{
		if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') )
		{
			Msg( "Double slashes found in path '%s'\n", pStr );
			return true;
		}
	}
	return false;
}

#define CHECK_DOUBLE_SLASHES( x ) V_CheckDoubleSlashes(x)
#else
#define CHECK_DOUBLE_SLASHES( x ) 
#endif

static void LogFileOpen( const char *vpk, const char *pFilename, const char *pAbsPath )
{
	static const char *mode = NULL;

	// Figure out if we should be doing this at all, the first time we are acalled
	if ( mode == NULL )
	{
		if ( CommandLine()->FindParm( "-log_opened_files" ) )
			mode = "wt";
		else
			mode = "";
	}
	if ( *mode == '\0' )
		return;

	// Open file for write or append
	FILE *f = fopen( "opened_files.txt", mode );
	Assert( f );
	if ( f )
	{
		fprintf( f, "%s, %s, %s\n", vpk, pFilename, pAbsPath );
		//fprintf( f, "%s\n", pFilename );
		fclose(f);

		// If this was the first time, switch from write to append for further writes
		mode = "at";
	}
}


static CBaseFileSystem *g_pBaseFileSystem;
CBaseFileSystem *BaseFileSystem()
{
	return g_pBaseFileSystem;
}

ConVar filesystem_buffer_size( "filesystem_buffer_size", "0", 0, "Size of per file buffers. 0 for none" );

#if defined( TRACK_BLOCKING_IO )

// If we hit more than 100 items in a frame, we're probably doing a level load...
#define MAX_ITEMS	100

class CBlockingFileItemList : public IBlockingFileItemList
{
public:
	CBlockingFileItemList( CBaseFileSystem *fs ) 
		: 
		m_pFS( fs ),
		m_bLocked( false )
	{
	}

	// You can't call any of the below calls without calling these methods!!!!
	virtual void	LockMutex()
	{
		Assert( !m_bLocked );
		if ( m_bLocked )
			return;
		m_bLocked = true;
		m_pFS->BlockingFileAccess_EnterCriticalSection();
	}

	virtual void	UnlockMutex()
	{
		Assert( m_bLocked );
		if ( !m_bLocked )
			return;

		m_pFS->BlockingFileAccess_LeaveCriticalSection();
		m_bLocked = false;
	}

	virtual int First() const
	{
		if ( !m_bLocked )
		{
			Error( "CBlockingFileItemList::First() w/o calling EnterCriticalSectionFirst!" );
		}
		return m_Items.Head();
	}

	virtual int Next( int i ) const
	{
		if ( !m_bLocked )
		{
			Error( "CBlockingFileItemList::Next() w/o calling EnterCriticalSectionFirst!" );
		}
		return m_Items.Next( i ); 
	}

	virtual int InvalidIndex() const
	{
		return m_Items.InvalidIndex();
	}

	virtual const FileBlockingItem& Get( int index ) const
	{
		if ( !m_bLocked )
		{
			Error( "CBlockingFileItemList::Get( %d ) w/o calling EnterCriticalSectionFirst!", index );
		}
		return m_Items[ index ];
	}

	virtual void Reset()
	{
		if ( !m_bLocked )
		{
			Error( "CBlockingFileItemList::Reset() w/o calling EnterCriticalSectionFirst!" );
		}
		m_Items.RemoveAll();
	}

	void Add( const FileBlockingItem& item )
	{
		// Ack, should use a linked list probably...
		while ( m_Items.Count() > MAX_ITEMS )
		{
			m_Items.Remove( m_Items.Head() );
		}
		m_Items.AddToTail( item );
	}


private:
	CUtlLinkedList< FileBlockingItem, unsigned short >	m_Items;
	CBaseFileSystem						*m_pFS;
	bool								m_bLocked;
};
#endif

CUtlSymbol CBaseFileSystem::m_GamePathID;
CUtlSymbol CBaseFileSystem::m_BSPPathID;
DVDMode_t CBaseFileSystem::m_DVDMode;
CUtlVector< FileNameHandle_t > CBaseFileSystem::m_ExcludePaths;


class CStoreIDEntry
{
public:
	CStoreIDEntry() = default;
	CStoreIDEntry( const char *pPathIDStr, int storeID )
	{
		m_PathIDString = pPathIDStr;
		m_StoreID = storeID;
	}

public:
	CUtlSymbol	m_PathIDString;
	int			m_StoreID;
};

#if 0
static CStoreIDEntry* FindPrevFileByStoreID( CUtlDict< CUtlVector<CStoreIDEntry>* ,int> &filesByStoreID, const char *pFilename, const char *pPathIDStr, int foundStoreID )
{
	int iEntry = filesByStoreID.Find( pFilename );
	if ( iEntry == filesByStoreID.InvalidIndex() )
	{
		CUtlVector<CStoreIDEntry> *pList = new CUtlVector<CStoreIDEntry>; 
		pList->AddToTail( CStoreIDEntry(pPathIDStr, foundStoreID) );
		filesByStoreID.Insert( pFilename, pList );
		return NULL;
	}
	else
	{
		// Now is there a previous entry with a different path ID string and the same store ID?
		CUtlVector<CStoreIDEntry> *pList = filesByStoreID[iEntry]; 
		for ( int i=0; i < pList->Count(); i++ )
		{
			CStoreIDEntry &entry = pList->Element( i );
			if ( entry.m_StoreID == foundStoreID && V_stricmp( entry.m_PathIDString.String(), pPathIDStr ) != 0 )
				return &entry;
		}
		return NULL;
	}
}
#endif

//-----------------------------------------------------------------------------

class CBaseFileSystem::CFileCacheObject
{
public:
	CFileCacheObject( CBaseFileSystem* pFS );
	~CFileCacheObject();
	void AddFiles( const char **ppFileNames, int nFileNames );
	bool IsReady() const { return m_nPending == 0; }

	static void IOCallback( const FileAsyncRequest_t &request, int nBytesRead, FSAsyncStatus_t err );

	struct Info_t
	{
		const char* pFileName;
		FSAsyncControl_t hIOAsync;
		CMemoryFileBacking* pBacking;
		CFileCacheObject* pOwner;
	};

	CBaseFileSystem* m_pFS;
	CInterlockedInt m_nPending;
	CThreadFastMutex m_InfosMutex;
	CUtlVector< Info_t* > m_Infos;

private:
	void ProcessNewEntries( int start );

	CFileCacheObject( const CFileCacheObject& ); // not implemented
	CFileCacheObject & operator=(const CFileCacheObject& ); // not implemented
};

//-----------------------------------------------------------------------------
// constructor
//-----------------------------------------------------------------------------

CBaseFileSystem::CBaseFileSystem()
	: m_FileTracker2( this )
{
	g_pBaseFileSystem = this;
	g_pFullFileSystem = this;

	m_WhitelistFileTrackingEnabled = -1;

	// If this changes then FileNameHandleInternal_t/FileNameHandle_t needs to be fixed!!!
	Assert( sizeof( CUtlSymbol ) == sizeof( short ) );

	// Clear out statistics
	memset( &m_Stats, 0, sizeof(m_Stats) );

	m_fwLevel    = FILESYSTEM_WARNING_REPORTUNCLOSED;
	m_pfnWarning = NULL;
	m_pLogFile   = NULL;
	m_bOutputDebugString = false;
	m_WhitelistSpewFlags = 0;
	m_DirtyDiskReportFunc = NULL;
	m_pPureServerWhitelist = NULL;

	m_pThreadPool = NULL;
#if defined( TRACK_BLOCKING_IO )
	m_pBlockingItems = new CBlockingFileItemList( this );
	m_bBlockingFileAccessReportingEnabled = false;
	m_bAllowSynchronousLogging = true;
#endif

	m_iMapLoad = 0;

	Q_memset( m_PreloadData, 0, sizeof( m_PreloadData ) );

	// allows very specifc constrained behavior
	m_DVDMode = DVDMODE_OFF;
	if ( IsX360() )
	{
		if ( CommandLine()->FindParm( "-dvd" ) )
		{
			m_DVDMode = DVDMODE_STRICT;
		}
		else if ( CommandLine()->FindParm( "-dvddev" ) )
		{
			m_DVDMode = DVDMODE_DEV;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::~CBaseFileSystem()
{
	m_PathIDInfos.PurgeAndDeleteElements();
#if defined( TRACK_BLOCKING_IO )
	delete m_pBlockingItems;
#endif

	// Free the whitelist.
	RegisterFileWhitelist( NULL, NULL );
}


//-----------------------------------------------------------------------------
// Methods of IAppSystem
//-----------------------------------------------------------------------------
void *CBaseFileSystem::QueryInterface( const char *pInterfaceName )
{
	// We also implement the IMatSystemSurface interface
	if (!Q_strncmp(	pInterfaceName, BASEFILESYSTEM_INTERFACE_VERSION, Q_strlen(BASEFILESYSTEM_INTERFACE_VERSION) + 1))
		return (IBaseFileSystem*)this;

	return NULL;
}

InitReturnVal_t CBaseFileSystem::Init()
{
	InitReturnVal_t nRetVal = BaseClass::Init();
	if ( nRetVal != INIT_OK )
		return nRetVal;

	// This is a special tag to allow iterating just the BSP file, it doesn't show up in the list per se, but gets converted to "GAME" in the filter function
	m_BSPPathID = g_PathIDTable.AddString( "BSP" );
	m_GamePathID = g_PathIDTable.AddString( "GAME" );

	if ( getenv( "fs_debug" ) )
	{
		m_bOutputDebugString = true;
	}

	const char *logFileName = CommandLine()->ParmValue( "-fs_log" );
	if( logFileName )
	{
		m_pLogFile = fopen( logFileName, "w" ); // STEAM OK
		if ( !m_pLogFile )
			return INIT_FAILED;
		fprintf( m_pLogFile, "@echo off\n" );
		fprintf( m_pLogFile, "setlocal\n" );
		const char *fs_target = CommandLine()->ParmValue( "-fs_target" );
		if( fs_target )
		{
			fprintf( m_pLogFile, "set fs_target=\"%s\"\n", fs_target );
		}
		fprintf( m_pLogFile, "if \"%%fs_target%%\" == \"\" goto error\n" );
		fprintf( m_pLogFile, "@echo on\n" );
	}

	InitAsync();

	if ( IsX360() && m_DVDMode == DVDMODE_DEV )
	{
		// exclude paths are valid ony in dvddev mode
		char szExcludeFile[MAX_PATH];
		const char *pRemotePath = CommandLine()->ParmValue( "-remote" );
		const char *pBasePath = CommandLine()->ParmValue( "-basedir" );
		if ( pRemotePath && pBasePath )
		{
			// the optional exclude path file only exists at the remote path
			V_ComposeFileName( pRemotePath, "xbox_exclude_paths.txt", szExcludeFile, sizeof( szExcludeFile ) );

			// populate the exclusion list
			CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER );
			if ( ReadFile( szExcludeFile, NULL, buf, 0, 0 ) )
			{
				characterset_t breakSet;
				CharacterSetBuild( &breakSet, "" );
				char szPath[MAX_PATH];
				char szToken[MAX_PATH];
				for ( ;; )
				{
					int nTokenSize = buf.ParseToken( &breakSet, szToken, sizeof( szToken ) );
					if ( nTokenSize <= 0 )
					{
						break;
					}

					char *pToken = szToken;
					if ( pToken[0] == '\\' )
					{
						// skip past possible initial seperator
						pToken++;
					}

					V_ComposeFileName( pBasePath, pToken, szPath, sizeof( szPath ) );
					V_AppendSlash( szPath, sizeof( szPath ) );
					
					FileNameHandle_t hFileName = FindOrAddFileName( szPath );
					if ( m_ExcludePaths.Find( hFileName ) == -1 )
					{
						m_ExcludePaths.AddToTail( hFileName );
					}
				}
			}
		}
	}

	return INIT_OK;
}

void CBaseFileSystem::Shutdown()
{
	ShutdownAsync();
	m_FileTracker2.ShutdownAsync();

#ifndef _X360
	if( m_pLogFile )
	{
		if( CommandLine()->FindParm( "-fs_logbins" ) >= 0 )
		{
			char cwd[MAX_FILEPATH];
			getcwd( cwd, MAX_FILEPATH-1 );
			fprintf( m_pLogFile, "set binsrc=\"%s\"\n", cwd );
			fprintf( m_pLogFile, "mkdir \"%%fs_target%%\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2.exe\" \"%%fs_target%%\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2.dat\" \"%%fs_target%%\"\n" );
			fprintf( m_pLogFile, "mkdir \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\*.asi\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\materialsystem.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\shaderapidx9.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\filesystem_stdio.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\soundemittersystem.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\stdshader*.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\shader_nv*.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\launcher.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\engine.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\mss32.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\tier0.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vgui2.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vguimatsurface.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\voice_miles.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vphysics.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vstdlib.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\studiorender.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vaudio_miles.dll\" \"%%fs_target%%\\bin\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2\\resource\\*.ttf\" \"%%fs_target%%\\hl2\\resource\"\n" );
			fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2\\bin\\gameui.dll\" \"%%fs_target%%\\hl2\\bin\"\n" );
		}
		fprintf( m_pLogFile, "goto done\n" );
		fprintf( m_pLogFile, ":error\n" );
		fprintf( m_pLogFile, "echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n" );
		fprintf( m_pLogFile, "echo ERROR: must set fs_target=targetpath (ie. \"set fs_target=u:\\destdir\")!\n" );
		fprintf( m_pLogFile, "echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n" );
		fprintf( m_pLogFile, ":done\n" );
		fclose( m_pLogFile ); // STEAM OK
	}
#endif

	UnloadCompiledKeyValues();

	RemoveAllSearchPaths();
	Trace_DumpUnclosedFiles();
	BaseClass::Shutdown();
}


//-----------------------------------------------------------------------------
// Computes a full write path
//-----------------------------------------------------------------------------
inline void CBaseFileSystem::ComputeFullWritePath( char* pDest, int maxlen, const char *pRelativePath, const char *pWritePathID )
{
	Q_strncpy( pDest, GetWritePath( pRelativePath, pWritePathID ), maxlen );
	Q_strncat( pDest, pRelativePath, maxlen, COPY_ALL_CHARACTERS );
	Q_FixSlashes( pDest );
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : src1 - 
//			src2 - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::OpenedFileLessFunc( COpenedFile const& src1, COpenedFile const& src2 )
{
	return src1.m_pFile < src2.m_pFile;
}


void CBaseFileSystem::InstallDirtyDiskReportFunc( FSDirtyDiskReportFunc_t func )
{
	m_DirtyDiskReportFunc = func;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fullpath - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::LogAccessToFile( char const *accesstype, char const *fullpath, char const *options )
{
	LOCAL_THREAD_LOCK();

	if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES && !V_stristr( fullpath, "console.log" ) )
	{
		Warning( FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS%s:  %s %s (%.3f)\n", ThreadInMainThread() ? "" : "[a]", accesstype, fullpath, Plat_FloatTime() );
	}

	int c = m_LogFuncs.Count();
	if ( !c )
		return;

	for ( int i = 0; i < c; ++i )
	{
		( m_LogFuncs[ i ] )( fullpath, options );
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *filename - 
//			*options - 
// Output : FILE
//-----------------------------------------------------------------------------
FILE *CBaseFileSystem::Trace_FOpen( const char *filenameT, const char *options, unsigned flags, int64 *size )
{
	AUTOBLOCKREPORTER_FN( Trace_FOpen, this, true, filenameT, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_OPEN );

	char filename[MAX_PATH];

	FixUpPath ( filenameT, filename, sizeof( filename ) );

	FILE *fp = FS_fopen( filename, options, flags, size );

	if ( fp )
	{
		if ( options[0] == 'r' )
		{
			FS_setbufsize(fp, filesystem_buffer_size.GetInt() );
		}
		else
		{
			FS_setbufsize(fp, 32*1024 );
		}

		AUTO_LOCK( m_OpenedFilesMutex );
		COpenedFile file;

		file.SetName( filename );
		file.m_pFile = fp;

		m_OpenedFiles.AddToTail( file );

		LogAccessToFile( "open", filename, options );
	}

	return fp;
}

void CBaseFileSystem::GetFileNameForHandle( FileHandle_t handle, char *buf, size_t buflen )
{
	V_strncpy( buf, "Unknown", buflen );
	/*
	CFileHandle *fh = ( CFileHandle *)handle;
	if ( !fh )
	{
		buf[ 0 ] = 0;
		return;
	}

#if !defined( _RETAIL )
	// Pack file filehandles store the underlying name for convenience
	if ( fh->IsPack() )
	{
		Q_strncpy( buf, fh->Name(), buflen );
		return;
	}
#endif

	AUTO_LOCK( m_OpenedFilesMutex );

	COpenedFile file;
	file.m_pFile = fh->GetFileHandle();

	int result = m_OpenedFiles.Find( file );
	if ( result != -1 )
	{
		COpenedFile found = m_OpenedFiles[ result ];
		Q_strncpy( buf, found.GetName(), buflen );
	}
	else
	{
		buf[ 0 ] = 0;
	}
	*/
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *fp - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::Trace_FClose( FILE *fp )
{
	if ( fp )
	{
		m_OpenedFilesMutex.Lock();

		COpenedFile file;
		file.m_pFile = fp;

		int result = m_OpenedFiles.Find( file );
		if ( result != -1 /*m_OpenedFiles.InvalidIdx()*/ )
		{
			COpenedFile found = m_OpenedFiles[ result ];
			if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES && !V_stristr( found.GetName(), "console.log" ) )
			{
				Warning( FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS%s:  close %s %p %i (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), fp, m_OpenedFiles.Count(), Plat_FloatTime() );
			}
			m_OpenedFiles.Remove( result );
		}
		else
		{
			Assert( 0 );

			if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES )
			{
				Warning( FILESYSTEM_WARNING_REPORTALLACCESSES, "Tried to close unknown file pointer %p\n", fp );
			}
		}

		m_OpenedFilesMutex.Unlock();

		FS_fclose( fp );
	}
}


void CBaseFileSystem::Trace_FRead( int size, FILE* fp )
{
	if ( !fp || m_fwLevel < FILESYSTEM_WARNING_REPORTALLACCESSES_READ )
		return;

	AUTO_LOCK( m_OpenedFilesMutex );

	COpenedFile file;
	file.m_pFile = fp;

	int result = m_OpenedFiles.Find( file );

	if( result != -1 )
	{
		COpenedFile found = m_OpenedFiles[ result ];
		
		Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "---FS%s:  read %s %i %p (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp, Plat_FloatTime()  );
	} 
	else
	{
		Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "Tried to read %i bytes from unknown file pointer %p\n", size, fp );
		
	}
}

void CBaseFileSystem::Trace_FWrite( int size, FILE* fp )
{
	if ( !fp || m_fwLevel < FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE )
		return;

	COpenedFile file;
	file.m_pFile = fp;

	AUTO_LOCK( m_OpenedFilesMutex );

	int result = m_OpenedFiles.Find( file );

	if( result != -1 )
	{
		COpenedFile found = m_OpenedFiles[ result ];

		Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "---FS%s:  write %s %i %p\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp  );
	} 
	else
	{
		Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "Tried to write %i bytes from unknown file pointer %p\n", size, fp );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::Trace_DumpUnclosedFiles( void )
{
	/*
	AUTO_LOCK( m_OpenedFilesMutex );
	for ( int i = 0 ; i < m_OpenedFiles.Count(); i++ )
	{
		COpenedFile *found = &m_OpenedFiles[ i ];

		if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTUNCLOSED )
		{
			// This warning can legitimately trigger because the log file is, by design, not
			// closed. It is not closed at shutdown in order to avoid race conditions. It is
			// not closed at each call to log information because the performance costs,
			// especially if Microsoft Security Essentials is running, are extreme.
			// In addition, when this warning triggers it can, due to the state of the process,
			// actually cause a crash.
			Warning( FILESYSTEM_WARNING_REPORTUNCLOSED, "File %s was never closed\n", found->GetName() );
		}
	}
	*/
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::PrintOpenedFiles( void )
{
	FileWarningLevel_t saveLevel = m_fwLevel;
	m_fwLevel = FILESYSTEM_WARNING_REPORTUNCLOSED;
	Trace_DumpUnclosedFiles();
	m_fwLevel = saveLevel;
}

#if defined( SUPPORT_PACKED_STORE )

CPackedStoreRefCount::CPackedStoreRefCount( char const *pFileBasename, char *pszFName, IBaseFileSystem *pFS )
: CPackedStore( pFileBasename, pszFName, pFS, false )
{
	// If the VPK is signed, check the signature
	//
	// !FIXME! This code is simple a linchpin that a hacker could detour to bypass sv_pure
	#ifdef VPK_ENABLE_SIGNING
		CPackedStore::ESignatureCheckResult eSigResult = CPackedStore::CheckSignature( 0, NULL );
		m_bSignatureValid = ( eSigResult == CPackedStore::eSignatureCheckResult_ValidSignature );
	#else
		m_bSignatureValid = false;
	#endif
}

#endif

void CBaseFileSystem::AddVPKFile( char const *pPath, const char *pPathID, SearchPathAdd_t addType )
{
#if defined( SUPPORT_PACKED_STORE )
	char nameBuf[MAX_PATH];

	Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pPath );
	Q_FixSlashes( nameBuf );

	CUtlSymbol pathIDSym = g_PathIDTable.AddString( pPathID );

	// See if we already have this vpk file as a search path
	CPackedStoreRefCount *pVPK = NULL;
	for ( int i = 0; i < m_SearchPaths.Count(); i++ )
	{
		CPackedStoreRefCount *p = m_SearchPaths[i].GetPackedStore();
		if ( p && V_stricmp( p->FullPathName(), nameBuf ) == 0 )
		{
			// Already present
			if ( m_SearchPaths[i].GetPath() == pathIDSym )
				return;

			// Present, but for a different path
			pVPK = p;
		}
	}

	// Create a new VPK if we didn't don't already have it opened
	if ( pVPK == NULL )
	{
		char pszFName[MAX_PATH];
		pVPK = new CPackedStoreRefCount( nameBuf, pszFName, this ); 
		if ( pVPK->IsEmpty() )
		{
			delete pVPK;
			return;
		}
		pVPK->RegisterFileTracker( (IThreadedFileMD5Processor *)&m_FileTracker2 );

		pVPK->m_PackFileID = m_FileTracker2.NotePackFileOpened( pVPK->FullPathName(), pPathID, 0 );
	}
	else
	{
		pVPK->AddRef();
	}

	// Crete a search path for this
	CSearchPath *sp = &m_SearchPaths[ ( addType == PATH_ADD_TO_TAIL ) ? m_SearchPaths.AddToTail() : m_SearchPaths.AddToHead() ];
	sp->SetPackedStore( pVPK );
	sp->m_storeId = g_iNextSearchPathID++;
	sp->SetPath( pathIDSym );
	sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );

	// Check if we're trusted or not
	SetSearchPathIsTrustedSource( sp );
#endif // SUPPORT_PACKED_STORE
}


bool CBaseFileSystem::RemoveVPKFile( const char *pPath, const char *pPathID )
{
#if defined( SUPPORT_PACKED_STORE )
	char nameBuf[MAX_PATH];

	Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pPath );
	Q_FixSlashes( nameBuf );

	CUtlSymbol pathIDSym = g_PathIDTable.AddString( pPathID );

	// See if we already have this vpk file as a search path
	for ( int i = 0; i < m_SearchPaths.Count(); i++ )
	{
		CPackedStoreRefCount *p = m_SearchPaths[i].GetPackedStore();
		if ( p && V_stricmp( p->FullPathName(), nameBuf ) == 0 )
		{
			// remove if we find one
			if ( m_SearchPaths[i].GetPath() == pathIDSym )
			{
				m_SearchPaths.Remove( i );
				return true;
			}
		}
	}
#endif // SUPPORT_PACKED_STORE

	return false;
}


//-----------------------------------------------------------------------------
// Purpose: Adds the specified pack file to the list
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::AddPackFile( const char *pFileName, const char *pathID )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	AsyncFinishAll();
	return AddPackFileFromPath( "", pFileName, true, pathID );
}

//-----------------------------------------------------------------------------
// Purpose: Adds a pack file from the specified path
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::AddPackFileFromPath( const char *pPath, const char *pakfile, bool bCheckForAppendedPack, const char *pathID )
{
	char fullpath[ MAX_PATH ];
	_snprintf( fullpath, sizeof(fullpath), "%s%s", pPath, pakfile );
	Q_FixSlashes( fullpath );

	struct	_stat buf;
	if ( FS_stat( fullpath, &buf ) == -1 )
		return false;

	CPackFile *pf = new CZipPackFile( this );
	pf->m_hPackFileHandleFS = Trace_FOpen( fullpath, "rb", 0, NULL );
	if ( !pf->m_hPackFileHandleFS )
	{
		delete pf;
		return false;
	}

	// NOTE: Opening .bsp fiels inside of VPK files is not supported

	// Get the length of the pack file:
	FS_fseek( ( FILE * )pf->m_hPackFileHandleFS, 0, FILESYSTEM_SEEK_TAIL );
	int64 len = FS_ftell( ( FILE * )pf->m_hPackFileHandleFS );
	FS_fseek( ( FILE * )pf->m_hPackFileHandleFS, 0, FILESYSTEM_SEEK_HEAD );

	if ( !pf->Prepare( len ) )
	{
		// Failed for some reason, ignore it
		Trace_FClose( pf->m_hPackFileHandleFS );
		pf->m_hPackFileHandleFS = NULL;
		delete pf;

		return false;
	}

	// Add this pack file to the search path:
	CSearchPath *sp = &m_SearchPaths[ m_SearchPaths.AddToTail() ];
	pf->SetPath( sp->GetPath() );
	pf->m_lPackFileTime = GetFileTime( pakfile );

	sp->SetPath( pPath );
	sp->m_pPathIDInfo->SetPathID( pathID );
	sp->SetPackFile( pf );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Search pPath for pak?.pak files and add to search path if found
// Input  : *pPath - 
//-----------------------------------------------------------------------------
#if !defined( _X360 )
#define PACK_NAME_FORMAT "zip%i.zip"
#else
#define PACK_NAME_FORMAT "zip%i.360.zip"
#define PACK_LOCALIZED_NAME_FORMAT "zip%i_%s.360.zip"
#endif

void CBaseFileSystem::AddPackFiles( const char *pPath, const CUtlSymbol &pathID, SearchPathAdd_t addType )
{
	Assert( ThreadInMainThread() );
	DISK_INTENSIVE();

	CUtlVector< CUtlString > pakNames;
	CUtlVector< int64 > pakSizes;

	// determine pak files, [zip0..zipN]
	for ( int i = 0; ; i++ )
	{
		char pakfile[MAX_PATH];
		char fullpath[MAX_PATH];
		V_snprintf( pakfile, sizeof( pakfile ), PACK_NAME_FORMAT, i );
		V_ComposeFileName( pPath, pakfile, fullpath, sizeof( fullpath ) );

		struct _stat buf;
		if ( FS_stat( fullpath, &buf ) == -1 )
			break;

		MEM_ALLOC_CREDIT();

		pakNames.AddToTail( pakfile );
		pakSizes.AddToTail( (int64)((unsigned int)buf.st_size) );
	}

#if defined( _X360 )
	// localized zips are added last to ensure they appear first in the search path construction
	// localized zips can only appear in the game or mod directories
	bool bUseEnglishAudio = XboxLaunch()->GetForceEnglish();

	if ( XBX_IsLocalized() && ( bUseEnglishAudio == false ) && 
		 ( V_stricmp( g_PathIDTable.String( pathID ), "game" ) == 0 || V_stricmp( g_PathIDTable.String( pathID ), "mod" ) == 0 ) )
	{
		// determine localized pak files, [zip0_language..zipN_language]
		for ( int i = 0; ; i++ )
		{
			char pakfile[MAX_PATH];
			char fullpath[MAX_PATH];
			V_snprintf( pakfile, sizeof( pakfile ), PACK_LOCALIZED_NAME_FORMAT, i, XBX_GetLanguageString() );
			V_ComposeFileName( pPath, pakfile, fullpath, sizeof( fullpath ) );

			struct _stat buf;
			if ( FS_stat( fullpath, &buf ) == -1 )
				break;

			pakNames.AddToTail( pakfile );
			pakSizes.AddToTail( (int64)((unsigned int)buf.st_size) );
		}
	}
#endif

	// Add any zip files in the format zip1.zip ... zip0.zip
	// Add them backwards so zip(N) is higher priority than zip(N-1), etc.
	int pakcount = pakSizes.Count();
	int nCount = 0;
	for ( int i = pakcount-1; i >= 0; i-- )
	{
		char fullpath[MAX_PATH];
		V_ComposeFileName( pPath, pakNames[i].Get(), fullpath, sizeof( fullpath ) );

		int nIndex;
		if ( addType == PATH_ADD_TO_TAIL )
		{
			nIndex = m_SearchPaths.AddToTail();
		}
		else
		{
			nIndex = m_SearchPaths.InsertBefore( nCount );
			++nCount;
		}

		CSearchPath *sp = &m_SearchPaths[ nIndex ];
		
		sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathID, -1 );
		sp->m_storeId = g_iNextSearchPathID++;
		sp->SetPath( g_PathIDTable.AddString( pPath ) );

		CPackFile *pf = NULL;
		for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ )
		{
			if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), fullpath ) )
			{
				pf = m_ZipFiles[iPackFile];
				sp->SetPackFile( pf );
				pf->AddRef();
			}
		}

		if ( !pf )
		{
			MEM_ALLOC_CREDIT();

			pf = new CZipPackFile( this );

			pf->SetPath( sp->GetPath() );
			pf->m_ZipName = fullpath;

			m_ZipFiles.AddToTail( pf );
			sp->SetPackFile( pf );
			pf->m_lPackFileTime = GetFileTime( fullpath );

			pf->m_hPackFileHandleFS = Trace_FOpen( fullpath, "rb", 0, NULL );

			if ( pf->m_hPackFileHandleFS )
			{
				FS_setbufsize( pf->m_hPackFileHandleFS, 32*1024 );	// 32k buffer.

				if ( pf->Prepare( pakSizes[i] ) )
				{
					FS_setbufsize( pf->m_hPackFileHandleFS, filesystem_buffer_size.GetInt() );
				}
				else
				{
					// Failed for some reason, ignore it
					if ( pf->m_hPackFileHandleFS )
					{
						Trace_FClose( pf->m_hPackFileHandleFS );
						pf->m_hPackFileHandleFS = NULL;
					}
					m_SearchPaths.Remove( nIndex );
				}
			}
			else
			{
				// !NOTE! Pack files inside of VPK not supported
				//pf->m_hPackFileHandleVPK = FindFileInVPKs( fullpath );
				//if ( !pf->m_hPackFileHandleVPK || !pf->Prepare( pakSizes[i] ) )
				{
					m_SearchPaths.Remove( nIndex );
				} 
			}
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Wipe all map (.bsp) pak file search paths
//-----------------------------------------------------------------------------
void CBaseFileSystem::RemoveAllMapSearchPaths( void )
{
	AsyncFinishAll();

	int c = m_SearchPaths.Count();
	for ( int i = c - 1; i >= 0; i-- )
	{
		if ( !( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) )
		{
			continue;
		}
		
		m_SearchPaths.Remove( i );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::AddMapPackFile( const char *pPath, const char *pPathID, SearchPathAdd_t addType )
{
	char tempPathID[MAX_PATH];
	ParsePathID( pPath, pPathID, tempPathID );

	// Security nightmares already, should not let things explicitly loading from e.g. "MOD" get surprise untrusted
	// files unless you really really know what you're doing.
	AssertMsg( V_strcasecmp( pPathID, "GAME" ) == 0,
	           "Mounting map files anywhere besides GAME is asking for pain" );

	char newPath[ MAX_FILEPATH ];
	// +2 for '\0' and potential slash added at end.
	V_strncpy( newPath, pPath, sizeof( newPath ) );
#ifdef _WIN32 // don't do this on linux!
	V_strlower( newPath );
#endif
	V_FixSlashes( newPath );

	// Open the .bsp and find the map lump
	char fullpath[ MAX_FILEPATH ];
	if ( V_IsAbsolutePath( newPath ) ) // If it's an absolute path, just use that.
	{
		V_strncpy( fullpath, newPath, sizeof(fullpath) );
	}
	else
	{
		if ( !GetLocalPath( newPath, fullpath, sizeof(fullpath) ) )
		{
			// Couldn't find that .bsp file!!!
			return;
		}
	}

	int c = m_SearchPaths.Count();
	for ( int i = c - 1; i >= 0; i-- )
	{
		if ( !( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) )
			continue;
		
		if ( V_stricmp( m_SearchPaths[i].GetPackFile()->m_ZipName.Get(), fullpath ) == 0 )
		{
			// Already set as map path
			return;
		}
	}

	RemoveAllMapSearchPaths();

	CUtlSymbol pathSymbol = g_PathIDTable.AddString( newPath );

	int iStoreId = CRC32_ProcessSingleBuffer( fullpath, V_strlen(fullpath) ) | 0x80000000; // set store ID based on .bsp filename, so if we remount the same map file, it will have the same storeid

	// Look through already-open packfiles in case we intentionally
	// preserved this ZIP across a map reload via refcount holding
	FOR_EACH_VEC( m_ZipFiles, i )
	{
		CPackFile* pf = m_ZipFiles[i];
		if ( pf && pf->m_bIsMapPath && pf->GetPath() == pathSymbol && V_stricmp( pf->m_ZipName.Get(), fullpath ) == 0 )
		{
			CSearchPath *sp = &m_SearchPaths[ ( addType == PATH_ADD_TO_TAIL ) ? m_SearchPaths.AddToTail() : m_SearchPaths.AddToHead() ];
			pf->AddRef();
			sp->SetPackFile( pf );
			sp->m_storeId = iStoreId;
			sp->SetPath( pathSymbol );
			sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );
			if ( IsX360() && !V_strnicmp( newPath, "net:", 4 ) )
			{
				sp->m_bIsRemotePath = true;
			}
			SetSearchPathIsTrustedSource( sp );
			return;
		}
	}

	{
		FILE *fp = Trace_FOpen( fullpath, "rb", 0, NULL );
		if ( !fp )
		{
			// Couldn't open it
			Warning( FILESYSTEM_WARNING, "Couldn't open .bsp %s for embedded pack file check\n", fullpath );
			return;
		}
	
		// Get the .bsp file header
		dheader_t header;
		memset( &header, 0, sizeof(dheader_t) );
		m_Stats.nBytesRead += FS_fread( &header, sizeof( header ), fp );
		m_Stats.nReads++;
	
		if ( header.ident != IDBSPHEADER || header.version < MINBSPVERSION || header.version > BSPVERSION )
		{
			Trace_FClose( fp );
			return;
		}
	
		// Find the LUMP_PAKFILE offset
		lump_t *packfile = &header.lumps[ LUMP_PAKFILE ];
		if ( packfile->filelen <= sizeof( lump_t ) )
		{
			// It's empty or only contains a file header ( so there are no entries ), so don't add to search paths
			Trace_FClose( fp );
			return;
		}
	
		// Seek to correct position
		FS_fseek( fp, packfile->fileofs, FILESYSTEM_SEEK_HEAD );
	
		CPackFile *pf = new CZipPackFile( this );
	
		pf->m_bIsMapPath = true;
		pf->m_hPackFileHandleFS = fp;

		MEM_ALLOC_CREDIT();
		pf->m_ZipName = fullpath;
	
		if ( pf->Prepare( packfile->filelen, packfile->fileofs ) )
		{
			int nIndex;
			if ( addType == PATH_ADD_TO_TAIL )
			{
				nIndex = m_SearchPaths.AddToTail();	
			}
			else
			{
				nIndex = m_SearchPaths.AddToHead();	
			}
	
			CSearchPath *sp = &m_SearchPaths[ nIndex ];
	
			sp->SetPackFile( pf );
			sp->m_storeId = iStoreId;
			sp->SetPath( pathSymbol );
			sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );
	
			if ( IsX360() && !V_strnicmp( newPath, "net:", 4 ) )
			{
				sp->m_bIsRemotePath = true;
			}
	
			pf->SetPath( pathSymbol );
			pf->m_lPackFileTime = GetFileTime( newPath );
	
			Trace_FClose( pf->m_hPackFileHandleFS );
			pf->m_hPackFileHandleFS = NULL;

			m_ZipFiles.AddToTail( pf );

			SetSearchPathIsTrustedSource( sp );
		}
		else
		{
			delete pf;
		}
	}
}


void CBaseFileSystem::BeginMapAccess() 
{
	if ( m_iMapLoad++ == 0 )
	{
		int c = m_SearchPaths.Count();
		for( int i = 0; i < c; i++ )
		{
			CSearchPath *pSearchPath = &m_SearchPaths[i];
			CPackFile *pPackFile = pSearchPath->GetPackFile();

			if ( pPackFile && pPackFile->m_bIsMapPath )
			{
				pPackFile->AddRef();
				pPackFile->m_mutex.Lock();

#if defined( SUPPORT_PACKED_STORE )
				if ( pPackFile->m_nOpenFiles == 0 && pPackFile->m_hPackFileHandleFS == NULL && !pPackFile->m_hPackFileHandleVPK )
#else
				if ( pPackFile->m_nOpenFiles == 0 && pPackFile->m_hPackFileHandleFS == NULL )
#endif
				{
					// Try opening the file as a regular file 
					pPackFile->m_hPackFileHandleFS = Trace_FOpen( pPackFile->m_ZipName, "rb", 0, NULL );

// !NOTE! Pack files inside of VPK not supported
//#if defined( SUPPORT_PACKED_STORE )
//					if ( !pPackFile->m_hPackFileHandleFS )
//					{
//						pPackFile->m_hPackFileHandleVPK = FindFileInVPKs( pPackFile->m_ZipName );
//					}
//#endif
				}
				pPackFile->m_nOpenFiles++;
				pPackFile->m_mutex.Unlock();
			}
		}
	}
}


void CBaseFileSystem::EndMapAccess() 
{
	if ( m_iMapLoad-- == 1 )
	{
		int c = m_SearchPaths.Count();
		for( int i = 0; i < c; i++ )
		{
			CSearchPath *pSearchPath = &m_SearchPaths[i];
			CPackFile *pPackFile = pSearchPath->GetPackFile();

			if ( pPackFile && pPackFile->m_bIsMapPath )
			{
				pPackFile->m_mutex.Lock();
				pPackFile->m_nOpenFiles--;
				if ( pPackFile->m_nOpenFiles == 0  )
				{
					if ( pPackFile->m_hPackFileHandleFS )
					{
						Trace_FClose( pPackFile->m_hPackFileHandleFS );
						pPackFile->m_hPackFileHandleFS = NULL;
					}
				}
				pPackFile->m_mutex.Unlock();
				pPackFile->Release();
			}
		}
	}
}


void CBaseFileSystem::PrintSearchPaths( void )
{
	Msg( "---------------\n" );
	Msg( "Paths:\n" );

	int c = m_SearchPaths.Count();
	for( int i = 0; i < c; i++ )
	{
		CSearchPath *pSearchPath = &m_SearchPaths[i];

		const char *pszPack = "";
		const char *pszType = "";
		if ( pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath )
		{
			pszType = "(map)";
		}
		else if ( pSearchPath->GetPackFile()  )
		{
			pszType = "(pack) ";
			pszPack = pSearchPath->GetPackFile()->m_ZipName;
		}
		#ifdef SUPPORT_PACKED_STORE
			else if ( pSearchPath->GetPackedStore()  )
			{
				pszType = "(VPK)";
				pszPack = pSearchPath->GetPackedStore()->FullPathName();
			}
		#endif

		Msg( "\"%s\" \"%s\" %s%s\n", pSearchPath->GetPathString(), (const char *)pSearchPath->GetPathIDString(), pszType, pszPack );
	}

	if ( IsX360() && m_ExcludePaths.Count() )
	{
		// dump current list
		Msg( "\nExclude:\n" );
		char szPath[MAX_PATH];
		for ( int i = 0; i < m_ExcludePaths.Count(); i++ )
		{
			if ( String( m_ExcludePaths[i], szPath, sizeof( szPath ) ) )
			{
				Msg( "\"%s\"\n", szPath );
			}
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: This is where search paths are created.  map files are created at head of list (they occur after
//  file system paths have already been set ) so they get highest priority.  Otherwise, we add the disk (non-packfile)
//  path and then the paks if they exist for the path
// Input  : *pPath - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::AddSearchPathInternal( const char *pPath, const char *pathID, SearchPathAdd_t addType, bool bAddPackFiles )
{
	AsyncFinishAll();

	Assert( ThreadInMainThread() );

	// Map pak files have their own handler
	if ( V_stristr( pPath, ".bsp" ) )
	{
		AddMapPackFile( pPath, pathID, addType );
		return;
	}

	// So do VPK files
	if ( V_stristr( pPath, ".vpk" ) )
	{
		AddVPKFile( pPath, pathID, addType );
		return;
	}

	// Clean up the name
	char newPath[ MAX_FILEPATH ];
	if ( pPath[0] == 0 )
	{
		newPath[0] = newPath[1] = 0;
	}
	else
	{
		if ( IsX360() || Q_IsAbsolutePath( pPath ) )
		{
			Q_strncpy( newPath, pPath, sizeof( newPath ) );
		}
		else
		{
			Q_MakeAbsolutePath( newPath, sizeof(newPath), pPath );
		}
#ifdef _WIN32
		Q_strlower( newPath );
#endif
		AddSeperatorAndFixPath( newPath );
	}

	// Make sure that it doesn't already exist
	CUtlSymbol pathSym, pathIDSym;
	pathSym = g_PathIDTable.AddString( newPath );
	pathIDSym = g_PathIDTable.AddString( pathID );
	int i;
	int c = m_SearchPaths.Count();
	int id = 0;
	for ( i = 0; i < c; i++ )
	{
		CSearchPath *pSearchPath = &m_SearchPaths[i];
		if ( pSearchPath->GetPath() == pathSym && pSearchPath->GetPathID() == pathIDSym )
		{
			if ( ( addType == PATH_ADD_TO_HEAD && i == 0 ) || ( addType == PATH_ADD_TO_TAIL ) )
			{
				return; // this entry is already at the head
			}
			else
			{
				m_SearchPaths.Remove(i); // remove it from its current position so we can add it back to the head
				i--;
				c--;
			}
		}
		if ( !id && pSearchPath->GetPath() == pathSym )
		{
			// get first found - all reference the same store
			id = pSearchPath->m_storeId;
		}
	}

	if (!id)
	{
		id = g_iNextSearchPathID++;
	}

	if ( IsX360() && bAddPackFiles && ( !Q_stricmp( pathID, "DEFAULT_WRITE_PATH" ) || !Q_stricmp( pathID, "LOGDIR" ) ) )
	{
		// xbox can be assured that no zips would ever be loaded on its write path
		// otherwise xbox reloads zips because of mirrored drive mappings
		bAddPackFiles = false;
	}

	// Add to list
	bool bAdded = false;
	int nIndex = m_SearchPaths.Count();
	if ( bAddPackFiles )
	{
		// Add pack files for this path next
		AddPackFiles( newPath, pathIDSym, addType );
		bAdded = m_SearchPaths.Count() != nIndex;
	}
	if ( addType == PATH_ADD_TO_HEAD )
	{
		nIndex = m_SearchPaths.Count() - nIndex;
		Assert( nIndex >= 0 );
	}

	if ( IsPC() || !bAddPackFiles || !bAdded )
	{
		// Grab last entry and set the path
		m_SearchPaths.InsertBefore( nIndex );
	}
	else if ( IsX360() && bAddPackFiles && bAdded )
	{
		// 360 needs to find files (for the preload hit) in the zip first for fast loading
		// 360 always adds the non-pack search path *after* the pack file but respects the overall list ordering
		if ( addType == PATH_ADD_TO_HEAD )
		{
			m_SearchPaths.InsertBefore( nIndex );
		}
		else
		{
			nIndex = m_SearchPaths.Count() - 1;
			m_SearchPaths.InsertAfter( nIndex );
			nIndex++;
		}
	}

	CSearchPath *sp = &m_SearchPaths[ nIndex ];
	
	sp->SetPath( pathSym );
	sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathIDSym, -1 );

	// all matching paths have a reference to the same store
	sp->m_storeId = id;
	if ( IsX360() && !V_strnicmp( newPath, "net:", 4 ) )
	{
		sp->m_bIsRemotePath = true;
	}
}

//-----------------------------------------------------------------------------
CBaseFileSystem::CSearchPath *CBaseFileSystem::FindSearchPathByStoreId( int storeId )
{
	FOR_EACH_VEC( m_SearchPaths, i )
	{
		if ( m_SearchPaths[i].m_storeId == storeId )
			return &m_SearchPaths[i];
	}

	return NULL;
}

//-----------------------------------------------------------------------------
// Create the search path.
//-----------------------------------------------------------------------------
void CBaseFileSystem::AddSearchPath( const char *pPath, const char *pathID, SearchPathAdd_t addType )
{
	int currCount = m_SearchPaths.Count();

	AddSearchPathInternal( pPath, pathID, addType, true );

	if ( IsX360() && m_DVDMode == DVDMODE_DEV )
	{
		// dvd development mode clones a search path based on the remote path for fall through
		const char *pRemotePath = CommandLine()->ParmValue( "-remote" );
		const char *pBasePath = CommandLine()->ParmValue( "-basedir" );
		if ( pRemotePath && pBasePath && !V_stristr( pPath, ".bsp" ) )
		{
			// isolate the search path from the base path
			if ( !V_strnicmp( pPath, pBasePath, strlen( pBasePath ) ) )
			{
				// substitue the remote path
				char szRemotePath[MAX_PATH];
				V_strncpy( szRemotePath, pRemotePath, sizeof( szRemotePath ) );
				V_strncat( szRemotePath, pPath + strlen( pBasePath ), sizeof( szRemotePath ) );

				// no pack files are allowed on the fall through remote path
				AddSearchPathInternal( szRemotePath, pathID, addType, false );
			}
		}
	}

	if ( currCount != m_SearchPaths.Count() )
	{
#if !defined( DEDICATED )
		if ( IsDebug() )
		{
			// spew updated search paths
			// PrintSearchPaths();
		}
#endif
	}
}

//-----------------------------------------------------------------------------
// Returns the search path, each path is separated by ;s. Returns the length of the string returned
// Pack search paths include the pack name, so that callers can still form absolute paths
// and that absolute path can be sent to the filesystem, and mounted as a file inside a pack.
//-----------------------------------------------------------------------------
int CBaseFileSystem::GetSearchPath( const char *pathID, bool bGetPackFiles, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars )
{
	AUTO_LOCK( m_SearchPathsMutex );

	if ( maxLenInChars )
	{
		pDest[0] = 0;
	}

	// Build up result into string object
	CUtlString sResult;
	CSearchPathsIterator iter( this, pathID, bGetPackFiles ? FILTER_NONE : FILTER_CULLPACK );
	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{
		if ( !sResult.IsEmpty() )
			sResult += ";";
		CUtlString sName;
		if ( pSearchPath->GetPackFile() )
		{
			sResult += pSearchPath->GetPackFile()->m_ZipName.String();
			sResult += CORRECT_PATH_SEPARATOR_S;
		}
		#ifdef SUPPORT_PACKED_STORE
		else if ( pSearchPath->GetPackedStore() )
		{
			sResult += pSearchPath->GetPackedStore()->FullPathName();
		}
		#endif
		else
		{
			sResult += pSearchPath->GetPathString();
		}
	}

	// Copy into user's buffer, possibly truncating
	if ( maxLenInChars )
	{
		V_strncpy( pDest, sResult.String(), maxLenInChars );
	}

	// Return 1 extra for the NULL terminator
	return sResult.Length()+1;
}



//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pPath - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::RemoveSearchPath( const char *pPath, const char *pathID )
{
	AsyncFinishAll();

	char newPath[ MAX_FILEPATH ];
	newPath[ 0 ] = 0;

	if ( pPath )
	{
		// +2 for '\0' and potential slash added at end.
		Q_strncpy( newPath, pPath, sizeof( newPath ) );
#ifdef _WIN32 // don't do this on linux!
		Q_strlower( newPath );
#endif

		if ( V_stristr( newPath, ".bsp" ) )
		{
			Q_FixSlashes( newPath );
		}
		else if ( V_stristr( newPath, ".vpk" ) )
		{
			return RemoveVPKFile( newPath, pathID );
		}
		else
		{
			AddSeperatorAndFixPath( newPath );
		}
	}

	CUtlSymbol lookup = g_PathIDTable.AddString( newPath );
	CUtlSymbol id = g_PathIDTable.AddString( pathID );

	bool bret = false;

	// Count backward since we're possibly deleting one or more pack files, too
	int i;
	int c = m_SearchPaths.Count();
	for( i = c - 1; i >= 0; i-- )
	{
		if ( newPath[0] && m_SearchPaths[i].GetPath() != lookup )
			continue;

		if ( FilterByPathID( &m_SearchPaths[i], id ) )
			continue;

		m_SearchPaths.Remove( i );
		bret = true;
	}
	return bret;
}


//-----------------------------------------------------------------------------
// Purpose: Removes all search paths for a given pathID, such as all "GAME" paths.
// Input  : pathID - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::RemoveSearchPaths( const char *pathID )
{
	AsyncFinishAll();

	int nCount = m_SearchPaths.Count();
	for (int i = nCount - 1; i >= 0; i--)
	{
		if (!Q_stricmp(m_SearchPaths.Element(i).GetPathIDString(), pathID))
		{
			m_SearchPaths.FastRemove(i);
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
CBaseFileSystem::CSearchPath *CBaseFileSystem::FindWritePath( const char *pFilename, const char *pathID )
{
	CUtlSymbol lookup = g_PathIDTable.AddString( pathID );

	AUTO_LOCK( m_SearchPathsMutex );

	// a pathID has been specified, find the first match in the path list
	int c = m_SearchPaths.Count();
	for ( int i = 0; i < c; i++ )
	{
		// pak files are not allowed to be written to...
		CSearchPath *pSearchPath = &m_SearchPaths[i];
		if ( pSearchPath->GetPackFile() || pSearchPath->GetPackedStore() )
		{
			continue;
		}

		if ( IsX360() && ( m_DVDMode == DVDMODE_DEV ) && pFilename && !pSearchPath->m_bIsRemotePath )
		{
			bool bIgnorePath = false;
			char szExcludePath[MAX_PATH];
			char szFilename[MAX_PATH];
			V_ComposeFileName( pSearchPath->GetPathString(), pFilename, szFilename, sizeof( szFilename ) );
			for ( int j = 0; j < m_ExcludePaths.Count(); j++ )
			{
				if ( g_pFullFileSystem->String( m_ExcludePaths[j], szExcludePath, sizeof( szExcludePath ) ) )
				{
					if ( !V_strnicmp( szFilename, szExcludePath, strlen( szExcludePath ) ) )
					{
						bIgnorePath = true;
						break;
					}
				}
			}
			if ( bIgnorePath )
			{
				// filename matches exclusion path, skip it
				// favoring the next path which should be the path fall through hit
				continue;
			}
		}

		if ( !pathID || ( pSearchPath->GetPathID() == lookup ) )
		{
			return pSearchPath;
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: Finds a search path that should be used for writing to, given a pathID.
//-----------------------------------------------------------------------------
const char *CBaseFileSystem::GetWritePath( const char *pFilename, const char *pathID )
{
	CSearchPath *pSearchPath = NULL;
	if ( pathID && pathID[ 0 ] != '\0' )
	{

		// Check for "game_write" and "mod_write"
		if ( V_stricmp( pathID, "game" ) == 0 )
			pSearchPath = FindWritePath( pFilename, "game_write" );
		else if ( V_stricmp( pathID, "mod" ) == 0 )
			pSearchPath = FindWritePath( pFilename, "mod_write" );

		if ( pSearchPath == NULL )
			pSearchPath = FindWritePath( pFilename, pathID );

		if ( pSearchPath )
		{
			return pSearchPath->GetPathString();
		}

		Warning( FILESYSTEM_WARNING, "Requested non-existent write path %s!\n", pathID );
	}

	pSearchPath = FindWritePath( pFilename, "DEFAULT_WRITE_PATH" );
	if ( pSearchPath )
	{
		return pSearchPath->GetPathString();
	}

	pSearchPath = FindWritePath( pFilename, NULL ); // okay, just return the first search path added!
	if ( pSearchPath )
	{
		return pSearchPath->GetPathString();
	}

	// Hope this is reasonable!!
	return ".\\";
}

//-----------------------------------------------------------------------------
// Reads/writes files to utlbuffers.  Attempts alignment fixups for optimal read
//-----------------------------------------------------------------------------
CTHREADLOCAL(char *) g_pszReadFilename;
bool CBaseFileSystem::ReadToBuffer( FileHandle_t fp, CUtlBuffer &buf, int nMaxBytes, FSAllocFunc_t pfnAlloc )
{
	SetBufferSize( fp, 0 );  // TODO: what if it's a pack file? restore buffer size?

	int nBytesToRead = Size( fp );
	if ( nBytesToRead == 0 )
	{
		// no data in file
		return true;
	}

	if ( nMaxBytes > 0 )
	{
		// can't read more than file has
		nBytesToRead = min( nMaxBytes, nBytesToRead );
	}

	int nBytesRead = 0;
	int nBytesOffset = 0;

	int iStartPos = Tell( fp );

	if ( nBytesToRead != 0 )
	{
		int nBytesDestBuffer = nBytesToRead;
		unsigned nSizeAlign = 0, nBufferAlign = 0, nOffsetAlign = 0;

		bool bBinary = !( buf.IsText() && !buf.ContainsCRLF() );

		if ( bBinary && !IsLinux() && !buf.IsExternallyAllocated() && !pfnAlloc && 
			( buf.TellPut() == 0 ) && ( buf.TellGet() == 0 ) && ( iStartPos % 4 == 0 ) &&
			GetOptimalIOConstraints( fp, &nOffsetAlign, &nSizeAlign, &nBufferAlign ) )
		{
			// correct conditions to allow an optimal read
			if ( iStartPos % nOffsetAlign != 0 )
			{
				// move starting position back to nearest alignment
				nBytesOffset = ( iStartPos % nOffsetAlign );
				Assert ( ( iStartPos - nBytesOffset ) % nOffsetAlign == 0 );
				Seek( fp, -nBytesOffset, FILESYSTEM_SEEK_CURRENT );

				// going to read from aligned start, increase target buffer size by offset alignment
				nBytesDestBuffer += nBytesOffset;
			}

			// snap target buffer size to its size alignment
			// add additional alignment slop for target pointer adjustment
			nBytesDestBuffer = AlignValue( nBytesDestBuffer, nSizeAlign ) + nBufferAlign;
		}

		if ( !pfnAlloc )
		{
			buf.EnsureCapacity( nBytesDestBuffer + buf.TellPut() );
		}
		else
		{
			// caller provided allocator
			void *pMemory = (*pfnAlloc)( g_pszReadFilename.Get(), nBytesDestBuffer );
			buf.SetExternalBuffer( pMemory, nBytesDestBuffer, 0, buf.GetFlags() & ~CUtlBuffer::EXTERNAL_GROWABLE );
		}

		int seekGet = -1;
		if ( nBytesDestBuffer != nBytesToRead )
		{
			// doing optimal read, align target pointer
			int nAlignedBase = AlignValue( (byte *)buf.Base(), nBufferAlign ) - (byte *)buf.Base();
			buf.SeekPut( CUtlBuffer::SEEK_HEAD, nAlignedBase );
	
			// the buffer read position is slid forward to ignore the addtional
			// starting offset alignment
			seekGet = nAlignedBase + nBytesOffset;
		}

		nBytesRead = ReadEx( buf.PeekPut(), buf.Size() - buf.TellPut(), nBytesToRead + nBytesOffset, fp );
		buf.SeekPut( CUtlBuffer::SEEK_CURRENT, nBytesRead );

		if ( seekGet != -1 )
		{
			// can only seek the get after data has been put, otherwise buffer sets overflow error
			buf.SeekGet( CUtlBuffer::SEEK_HEAD, seekGet );
		}

		Seek( fp, iStartPos + ( nBytesRead - nBytesOffset ), FILESYSTEM_SEEK_HEAD );
	}

	return (nBytesRead != 0);
}

//-----------------------------------------------------------------------------
// Reads/writes files to utlbuffers
// NOTE NOTE!! 
// If you change this implementation, copy it into CBaseVMPIFileSystem::ReadFile
// in vmpi_filesystem.cpp
//-----------------------------------------------------------------------------
bool CBaseFileSystem::ReadFile( const char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	bool bBinary = !( buf.IsText() && !buf.ContainsCRLF() );

	FileHandle_t fp = Open( pFileName, ( bBinary ) ? "rb" : "rt", pPath );
	if ( !fp )
		return false;

	if ( nStartingByte != 0 )
	{
		Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD );
	}

	if ( pfnAlloc )
	{
		g_pszReadFilename.Set( (char *)pFileName );
	}

	bool bSuccess = ReadToBuffer( fp, buf, nMaxBytes, pfnAlloc );

	Close( fp );

	return bSuccess;
}

//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
int CBaseFileSystem::ReadFileEx( const char *pFileName, const char *pPath, void **ppBuf, bool bNullTerminate, bool bOptimalAlloc, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc )
{
	FileHandle_t fp = Open( pFileName, "rb", pPath );
	if ( !fp )
	{
		return 0;
	}

	if ( IsX360() )
	{
		// callers are sloppy, always want optimal
		bOptimalAlloc = true;
	}

	SetBufferSize( fp, 0 );  // TODO: what if it's a pack file? restore buffer size?

	int nBytesToRead = Size( fp );
	int nBytesRead = 0;
	if ( nMaxBytes > 0 )
	{
		nBytesToRead = min( nMaxBytes, nBytesToRead );
		if ( bNullTerminate )
		{
			nBytesToRead--;
		}
	}

	if ( nBytesToRead != 0 )
	{
		int nBytesBuf;
		if ( !*ppBuf )
		{
			nBytesBuf = nBytesToRead + ( ( bNullTerminate ) ? 1 : 0 );

			if ( !pfnAlloc && !bOptimalAlloc )
			{
				// AllocOptimalReadBuffer does malloc...
				*ppBuf = malloc( nBytesBuf );
			}
			else if ( !pfnAlloc )
			{
				*ppBuf = AllocOptimalReadBuffer( fp, nBytesBuf, 0 );
				nBytesBuf = GetOptimalReadSize( fp, nBytesBuf );
			}
			else
			{
				*ppBuf = (*pfnAlloc)( pFileName, nBytesBuf );
			}
		}
		else
		{
			nBytesBuf = nMaxBytes;
		}

		if ( nStartingByte != 0 )
		{
			Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD );
		}

		nBytesRead = ReadEx( *ppBuf, nBytesBuf, nBytesToRead, fp );

		if ( bNullTerminate )
		{
			((byte *)(*ppBuf))[nBytesToRead] = 0;
		}
	}

	Close( fp );
	return nBytesRead;
}


//-----------------------------------------------------------------------------
// NOTE NOTE!! 
// If you change this implementation, copy it into CBaseVMPIFileSystem::WriteFile
// in vmpi_filesystem.cpp
//-----------------------------------------------------------------------------
bool CBaseFileSystem::WriteFile( const char *pFileName, const char *pPath, CUtlBuffer &buf )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	const char *pWriteFlags = "wb";
	if ( buf.IsText() && !buf.ContainsCRLF() )
	{
		pWriteFlags = "wt";
	}

	FileHandle_t fp = Open( pFileName, pWriteFlags, pPath );
	if ( !fp )
		return false;

	int nBytesWritten = Write( buf.Base(), buf.TellPut(), fp );

	Close( fp );
	return (nBytesWritten == buf.TellPut());
}


bool CBaseFileSystem::UnzipFile( const char *pFileName, const char *pPath, const char *pDestination )
{
	IZip *pZip = IZip::CreateZip( NULL, true );

	HANDLE hZipFile = pZip->ParseFromDisk( pFileName );
	if ( !hZipFile )
	{
		Msg( "Bad or missing zip file, failed to open '%s'\n", pFileName );
		return false;
	}

	int iZipIndex = -1;
	int iFileSize;
	char szFileName[MAX_PATH];

	// Create Directories
	CreateDirHierarchy( pDestination, pPath );

	while ( 1 )
	{
		// Get the next file in the zip
		szFileName[0] = '\0';
		iFileSize = 0;
		iZipIndex = pZip->GetNextFilename( iZipIndex, szFileName, sizeof( szFileName ), iFileSize );

		// If there aren't any more files then break out of this while
		if ( iZipIndex == -1 )
			break;

		int iFileNameLength = Q_strlen( szFileName );
		if ( szFileName[ iFileNameLength - 1 ] == '/' )
		{
			// Its a directory, so create it
			szFileName[ iFileNameLength - 1 ] = '\0';

			char szFinalName[ MAX_PATH ];
			Q_snprintf( szFinalName, sizeof( szFinalName ), "%s%c%s", pDestination, CORRECT_PATH_SEPARATOR, szFileName );
			CreateDirHierarchy( szFinalName, pPath );
		}
	}

	// Write Files
	while ( 1 )
	{
		szFileName[0] = '\0';
		iFileSize = 0;
		iZipIndex = pZip->GetNextFilename( iZipIndex, szFileName, sizeof( szFileName ), iFileSize );

		// If there aren't any more files then break out of this while
		if ( iZipIndex == -1 )
			break;

		int iFileNameLength = Q_strlen( szFileName );
		if ( szFileName[ iFileNameLength - 1 ] != '/' )
		{
			// It's not a directory, so write the file
			CUtlBuffer fileBuffer;
			fileBuffer.Purge();

			if ( pZip->ReadFileFromZip( hZipFile, szFileName, false, fileBuffer ) )
			{
				char szFinalName[ MAX_PATH ];
				Q_snprintf( szFinalName, sizeof( szFinalName ), "%s%c%s", pDestination, CORRECT_PATH_SEPARATOR, szFileName );

				// Make sure the directory actually exists in case the ZIP doesn't list it (our zip utils create zips like this)
				char szFilePath[ MAX_PATH ];
				Q_strncpy( szFilePath, szFinalName, sizeof(szFilePath) );
				Q_StripFilename( szFilePath );
				CreateDirHierarchy( szFilePath, pPath );

				WriteFile( szFinalName, pPath, fileBuffer );
			}
		}
	}

#ifdef WIN32
	::CloseHandle( hZipFile );
#else
	fclose( (FILE *)hZipFile );
#endif

	IZip::ReleaseZip( pZip );
	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::RemoveAllSearchPaths( void )
{
	AUTO_LOCK( m_SearchPathsMutex );
	m_SearchPaths.Purge();
	//m_PackFileHandles.Purge();
}


void CBaseFileSystem::LogFileAccess( const char *pFullFileName )
{
	if( !m_pLogFile )
	{
		return;
	}
	char buf[1024];
#if BSPOUTPUT
	Q_snprintf( buf, sizeof( buf ), "%s\n%s\n", pShortFileName, pFullFileName);
	fprintf( m_pLogFile, "%s", buf ); // STEAM OK
#else
	char cwd[MAX_FILEPATH];
	getcwd( cwd, MAX_FILEPATH-1 );
	Q_strcat( cwd, "\\", sizeof( cwd ) );
	if( Q_strnicmp( cwd, pFullFileName, strlen( cwd ) ) == 0 )
	{
		const char *pFileNameWithoutExeDir = pFullFileName + strlen( cwd );
		char targetPath[ MAX_FILEPATH ];
		strcpy( targetPath, "%fs_target%\\" );
		strcat( targetPath, pFileNameWithoutExeDir );
		Q_snprintf( buf, sizeof( buf ), "copy \"%s\" \"%s\"\n", pFullFileName, targetPath );
		char tmp[ MAX_FILEPATH ];
		Q_strncpy( tmp, targetPath, sizeof( tmp ) );
		Q_StripFilename( tmp );
		fprintf( m_pLogFile, "mkdir \"%s\"\n", tmp ); // STEAM OK
		fprintf( m_pLogFile, "%s", buf ); // STEAM OK
	}
	else
	{
		Assert( 0 );
	}
#endif
}

class CPackedStore;

class CFileOpenInfo
{
public:
	CFileOpenInfo( CBaseFileSystem *pFileSystem, const char *pFileName, const CBaseFileSystem::CSearchPath *path, const char *pOptions, int flags, char **ppszResolvedFilename ) : 
		m_pFileSystem( pFileSystem ), m_pFileName( pFileName ), m_pSearchPath( path ), m_pOptions( pOptions ), m_Flags( flags ), m_ppszResolvedFilename( ppszResolvedFilename )
	{
		m_pFileHandle = NULL;
		m_ePureFileClass = ePureServerFileClass_Any;
		if ( m_pFileSystem->m_pPureServerWhitelist )
		{
			m_ePureFileClass = m_pFileSystem->m_pPureServerWhitelist->GetFileClass( pFileName );
		}

		if ( m_ppszResolvedFilename )
			*m_ppszResolvedFilename = NULL;
		m_pPackFile = NULL;
		m_pVPKFile = NULL;
		m_AbsolutePath[0] = '\0';
	}
	
	~CFileOpenInfo()
	{
		if ( IsX360() )
		{
			return;
		}
	}
	
	void SetAbsolutePath( const char *pFormat, ... )
	{
		va_list marker;
		va_start( marker, pFormat );
		V_vsnprintf( m_AbsolutePath, sizeof( m_AbsolutePath ), pFormat, marker );
		va_end( marker );

		V_FixSlashes( m_AbsolutePath );
	}
	
	void SetResolvedFilename( const char *pStr )
	{
		if ( m_ppszResolvedFilename )
		{
			Assert( !( *m_ppszResolvedFilename ) );
			*m_ppszResolvedFilename = strdup( pStr );
		}
	}

	// Handles telling CFileTracker about the file we just opened so it can remember
	// where the file came from, and possibly calculate a CRC if necessary.
	void HandleFileCRCTracking( const char *pRelativeFileName )
	{
		if ( IsX360() )
		{
			return;
		}

		if ( m_pFileSystem->m_WhitelistFileTrackingEnabled == 0 )
			return;

		if ( m_pFileHandle && m_pSearchPath )
		{
			FILE *fp = m_pFileHandle->m_pFile;
			int64 nLength = m_pFileHandle->m_nLength;
#ifdef SUPPORT_PACKED_STORE
			if ( m_pVPKFile )
			{
				m_pFileSystem->m_FileTracker2.NotePackFileAccess( pRelativeFileName, m_pSearchPath->GetPathIDString(), m_pSearchPath->m_storeId, m_pFileHandle->m_VPKHandle );
				return;
			}
#endif
			// we always record hashes of everything we load. we may filter later.
			m_pFileSystem->m_FileTracker2.NoteFileLoadedFromDisk( pRelativeFileName, m_pSearchPath->GetPathIDString(), m_pSearchPath->m_storeId, fp, nLength );
		}
	}

#ifdef SUPPORT_PACKED_STORE
	void SetFromPackedStoredFileHandle( const CPackedStoreFileHandle &fHandle, CBaseFileSystem *pFileSystem )
	{
		Assert( fHandle );
		Assert( m_pFileHandle == NULL );
		m_pFileHandle = new CFileHandle(pFileSystem);
		m_pFileHandle->m_VPKHandle = fHandle;
		m_pFileHandle->m_type = FT_NORMAL;
		m_pFileHandle->m_nLength = fHandle.m_nFileSize;
	}
#endif

public:
	CBaseFileSystem *m_pFileSystem;

	// These are output parameters.
	CFileHandle *m_pFileHandle;
	char **m_ppszResolvedFilename;

	CPackFile *m_pPackFile;
	CPackedStore *m_pVPKFile;

	const char *m_pFileName;
	const CBaseFileSystem::CSearchPath *m_pSearchPath;
	const char *m_pOptions;
	int m_Flags;

	EPureServerFileClass m_ePureFileClass;

	char m_AbsolutePath[MAX_FILEPATH];	// This is set 
};


void CBaseFileSystem::HandleOpenRegularFile( CFileOpenInfo &openInfo, bool bIsAbsolutePath )
{
	openInfo.m_pFileHandle = NULL;

	int64 size;
	FILE *fp = Trace_FOpen( openInfo.m_AbsolutePath, openInfo.m_pOptions, openInfo.m_Flags, &size );
	if ( fp )
	{
		if ( m_pLogFile )
		{
			LogFileAccess( openInfo.m_AbsolutePath );
		}

		if ( m_bOutputDebugString )
		{
#ifdef _WIN32
			Plat_DebugString( "fs_debug: " );
			Plat_DebugString( openInfo.m_AbsolutePath );
			Plat_DebugString( "\n" );
#elif POSIX
			fprintf(stderr, "fs_debug: %s\n", openInfo.m_AbsolutePath );
#endif
		}

		openInfo.m_pFileHandle = new CFileHandle(this);
		openInfo.m_pFileHandle->m_pFile = fp;
		openInfo.m_pFileHandle->m_type = FT_NORMAL;
		openInfo.m_pFileHandle->m_nLength = size;

		openInfo.SetResolvedFilename( openInfo.m_AbsolutePath );
		
		LogFileOpen( "Loose", openInfo.m_pFileName, openInfo.m_AbsolutePath );
	}
}


//-----------------------------------------------------------------------------
// Purpose: The base file search goes through here
// Input  : *path - 
//			*pFileName - 
//			*pOptions - 
//			packfile - 
//			*filetime - 
// Output : FileHandle_t
//-----------------------------------------------------------------------------
FileHandle_t CBaseFileSystem::FindFileInSearchPath( CFileOpenInfo &openInfo )
{
	VPROF( "CBaseFileSystem::FindFile" );
	
	Assert( openInfo.m_pSearchPath );
	openInfo.m_pFileHandle = NULL;

	// Loading from pack file?
	CPackFile *pPackFile = openInfo.m_pSearchPath->GetPackFile();
	if ( pPackFile )
	{
		openInfo.m_pFileHandle = pPackFile->OpenFile( openInfo.m_pFileName, openInfo.m_pOptions );
		openInfo.m_pPackFile = pPackFile;
		if ( openInfo.m_pFileHandle )
		{
			char tempStr[MAX_PATH*2+2];
			V_snprintf( tempStr, sizeof( tempStr ), "%s%c%s", pPackFile->m_ZipName.String(), CORRECT_PATH_SEPARATOR, openInfo.m_pFileName );
			openInfo.SetResolvedFilename( tempStr );
		}

		// If it's a BSP file, then the BSP file got CRC'd elsewhere so no need to verify stuff in there.
		return (FileHandle_t)openInfo.m_pFileHandle;
	}

	// Loading from VPK?
	#ifdef SUPPORT_PACKED_STORE
		CPackedStore *pVPK = openInfo.m_pSearchPath->GetPackedStore();
		if ( pVPK )
		{
			CPackedStoreFileHandle fHandle = pVPK->OpenFile( openInfo.m_pFileName );
			if ( fHandle )
			{
				openInfo.SetFromPackedStoredFileHandle( fHandle, this );
				openInfo.SetResolvedFilename( openInfo.m_pFileName );

				openInfo.m_pVPKFile = pVPK;
				LogFileOpen( "VPK", openInfo.m_pFileName, pVPK->BaseName() );
				openInfo.HandleFileCRCTracking( openInfo.m_pFileName );
				return ( FileHandle_t ) openInfo.m_pFileHandle;
			}
			return NULL;
		}
	#endif

	// Load loose file specified as relative filename
	// Convert filename to lowercase.  All files in the
	// game logical filesystem must be accessed by lowercase name
	char szLowercaseFilename[ MAX_PATH ];
	V_strcpy_safe( szLowercaseFilename, openInfo.m_pFileName );
	V_strlower( szLowercaseFilename );

	openInfo.SetAbsolutePath( "%s%s", openInfo.m_pSearchPath->GetPathString(), szLowercaseFilename );

	// now have an absolute name
	HandleOpenRegularFile( openInfo, false );
	return (FileHandle_t)openInfo.m_pFileHandle;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
FileHandle_t CBaseFileSystem::OpenForRead( const char *pFileNameT, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename )
{
	VPROF( "CBaseFileSystem::OpenForRead" );

	char pFileNameBuff[MAX_PATH];
	const char *pFileName = pFileNameBuff;

	FixUpPath ( pFileNameT, pFileNameBuff, sizeof( pFileNameBuff ) );		

	// Try the memory cache for un-restricted searches or "GAME" items.
	if ( !pathID || Q_stricmp( pathID, "GAME" ) == 0 )
	{
		CMemoryFileBacking* pBacking = NULL;
		{
			AUTO_LOCK( m_MemoryFileMutex );
			CUtlHashtable< const char*, CMemoryFileBacking* >& table = m_MemoryFileHash;
			UtlHashHandle_t idx = table.Find( pFileName );
			if ( idx != table.InvalidHandle() )
			{
				pBacking = table[idx];
				pBacking->AddRef();
			}
		}
		if ( pBacking )
		{
			if ( pBacking->m_nLength != -1 )
			{
				CFileHandle* pFile = new CMemoryFileHandle( this, pBacking );
				pFile->m_type = strstr( pOptions, "b" ) ? FT_MEMORY_BINARY : FT_MEMORY_TEXT;
				return ( FileHandle_t )pFile;
			}
			else
			{
				// length -1 == cached failure to read
				return ( FileHandle_t )NULL;
			}
		}
		else if ( ThreadInMainThread() && fs_report_sync_opens.GetInt() > 0 )
		{
			DevWarning("blocking load %s\n", pFileName);
		}
	}

	CFileOpenInfo openInfo( this, pFileName, NULL, pOptions, flags, ppszResolvedFilename );

	// Already have an absolute path?
	// If so, don't bother iterating search paths.
	if ( V_IsAbsolutePath( pFileName ) )
	{
		openInfo.SetAbsolutePath( "%s", pFileName );

		// Check if it's of the form C:/a/b/c/blah.zip/materials/blah.vtf
		// an absolute path can encode a zip pack file (i.e. caller wants to open the file from within the pack file)
		// format must strictly be ????.zip\????
		// assuming a reasonable restriction that the zip must be a pre-existing search path zip
		char *pZipExt = V_stristr( openInfo.m_AbsolutePath, ".zip" CORRECT_PATH_SEPARATOR_S );
		if ( !pZipExt )
			pZipExt = V_stristr( openInfo.m_AbsolutePath, ".bsp" CORRECT_PATH_SEPARATOR_S );
		#if defined( SUPPORT_PACKED_STORE )
			if ( !pZipExt )
				pZipExt = V_stristr( openInfo.m_AbsolutePath, ".vpk" CORRECT_PATH_SEPARATOR_S );
		#endif
	
		if ( pZipExt && pZipExt[5] )
		{
			// Cut string at the slash
			char *pSlash = pZipExt + 4;
			Assert( *pSlash == CORRECT_PATH_SEPARATOR );
			*pSlash = '\0';

			// want relative portion only, everything after the slash
			char *pRelativeFileName = pSlash + 1;

			// Find the zip or VPK in the search paths
			for ( int i = 0; i < m_SearchPaths.Count(); i++ )
			{

				// In VPK?
				#if defined( SUPPORT_PACKED_STORE )
					CPackedStore *pVPK = m_SearchPaths[i].GetPackedStore();
					if ( pVPK )
					{
						if ( V_stricmp( pVPK->FullPathName(), openInfo.m_AbsolutePath ) == 0 )
						{
							CPackedStoreFileHandle fHandle = pVPK->OpenFile( pRelativeFileName );
							if ( fHandle )
							{
								openInfo.m_pSearchPath = &m_SearchPaths[i];
								openInfo.SetFromPackedStoredFileHandle( fHandle, this );
							}
							break;
						}
						continue;
					}
				#endif

				// In .zip?
				CPackFile *pPackFile = m_SearchPaths[i].GetPackFile();
				if ( pPackFile )
				{
					if ( Q_stricmp( pPackFile->m_ZipName.Get(), openInfo.m_AbsolutePath ) == 0 )
					{
						openInfo.m_pSearchPath = &m_SearchPaths[i];
						openInfo.m_pFileHandle = pPackFile->OpenFile( pRelativeFileName, openInfo.m_pOptions );
						openInfo.m_pPackFile = pPackFile;
						break;
					}
				}
			}

			if ( openInfo.m_pFileHandle )
			{
				openInfo.SetResolvedFilename( openInfo.m_pFileName );
				openInfo.HandleFileCRCTracking( pRelativeFileName );
			}
			return (FileHandle_t)openInfo.m_pFileHandle;
		}

		// Otherwise, it must be a regular file, specified by absolute filename
		HandleOpenRegularFile( openInfo, true );

		// !FIXME! We probably need to deal with CRC tracking, right?

		return (FileHandle_t)openInfo.m_pFileHandle;
	}

	// Run through all the search paths.
	PathTypeFilter_t pathFilter = FILTER_NONE;
	if ( IsX360() )
	{
		if ( flags & FSOPEN_NEVERINPACK )
		{
			pathFilter = FILTER_CULLPACK;
		}
		else if ( m_DVDMode == DVDMODE_STRICT )
		{
			// most all files on the dvd are expected to be in the pack
			// don't allow disk paths to be searched, which is very expensive on the dvd
			pathFilter = FILTER_CULLNONPACK;
		}
	}

	CSearchPathsIterator iter( this, &pFileName, pathID, pathFilter );
	for ( openInfo.m_pSearchPath = iter.GetFirst(); openInfo.m_pSearchPath != NULL; openInfo.m_pSearchPath = iter.GetNext() )
	{
		FileHandle_t filehandle = FindFileInSearchPath( openInfo );
		if ( filehandle )
		{
			// Check if search path is excluded due to pure server white list,
			// then we should make a note of this fact, and keep searching
			if ( !openInfo.m_pSearchPath->m_bIsTrustedForPureServer && openInfo.m_ePureFileClass == ePureServerFileClass_AnyTrusted )
			{
				#ifdef PURE_SERVER_DEBUG_SPEW
					Msg( "Ignoring %s from %s for pure server operation\n", openInfo.m_pFileName, openInfo.m_pSearchPath->GetDebugString() );
				#endif

				m_FileTracker2.NoteFileIgnoredForPureServer( openInfo.m_pFileName, pathID, openInfo.m_pSearchPath->m_storeId );
				Close( filehandle );
				openInfo.m_pFileHandle = NULL;
				if ( ppszResolvedFilename && *ppszResolvedFilename )
				{
					free( *ppszResolvedFilename );
					*ppszResolvedFilename = NULL;
				}
				continue;
			}

			// 
			openInfo.HandleFileCRCTracking( openInfo.m_pFileName );
			return filehandle;
		}
	}

	LogFileOpen( "[Failed]", pFileName, "" );
	return ( FileHandle_t )0;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
FileHandle_t CBaseFileSystem::OpenForWrite( const char *pFileName, const char *pOptions, const char *pathID )
{
	char tempPathID[MAX_PATH];
	ParsePathID( pFileName, pathID, tempPathID );

	if ( ThreadInMainThread() && fs_report_sync_opens.GetInt() )
	{
		DevWarning("blocking write %s\n", pFileName);
	}

	// Opening for write or append uses the write path
	// Unless an absolute path is specified...
	const char *pTmpFileName;
	char szScratchFileName[MAX_PATH];
	if ( Q_IsAbsolutePath( pFileName ) )
	{
		pTmpFileName = pFileName;
	}
	else
	{
		ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pFileName, pathID );
		pTmpFileName = szScratchFileName; 
	}

	int64 size;
	FILE *fp = Trace_FOpen( pTmpFileName, pOptions, 0, &size );
	if ( !fp )
	{
		return ( FileHandle_t )0;
	}

	CFileHandle *fh = new CFileHandle( this );
	fh->m_nLength = size;
	fh->m_type = FT_NORMAL;
	fh->m_pFile = fp;

	return ( FileHandle_t )fh;
}


// This looks for UNC-type filename specifiers, which should be used instead of 
// passing in path ID. So if it finds //mod/cfg/config.cfg, it translates
// pFilename to "cfg/config.cfg" and pPathID to "mod" (mod is placed in tempPathID).
void CBaseFileSystem::ParsePathID( const char* &pFilename, const char* &pPathID, char tempPathID[MAX_PATH] )
{
	tempPathID[0] = 0;
	
	if ( !pFilename || pFilename[0] == 0 )
		return;

	// FIXME: Pain! Backslashes are used to denote network drives, forward to denote path ids
	// HOORAY! We call FixSlashes everywhere. That will definitely not work
	// I'm not changing it yet though because I don't know how painful the bugs would be that would be generated
	bool bIsForwardSlash = ( pFilename[0] == '/' && pFilename[1] == '/' );
//	bool bIsBackwardSlash = ( pFilename[0] == '\\' && pFilename[1] == '\\' );
	if ( !bIsForwardSlash ) //&& !bIsBackwardSlash ) 
		return;

	// They're specifying two path IDs. Ignore the one passed-in.
	if ( pPathID )
	{
		Warning( FILESYSTEM_WARNING, "FS: Specified two path IDs (%s, %s).\n", pFilename, pPathID );
	}

	// Parse out the path ID.
	const char *pIn = &pFilename[2];
	char *pOut = tempPathID;
	while ( *pIn && !PATHSEPARATOR( *pIn ) && (pOut - tempPathID) < (MAX_PATH-1) )
	{
		*pOut++ = *pIn++;
	}

	*pOut = 0;

	if ( tempPathID[0] == '*' )
	{
		// * means NULL.
		pPathID = NULL;
	}
	else
	{
		pPathID = tempPathID;
	}

	// Move pFilename up past the part with the path ID.
	if ( *pIn == 0 )
		pFilename = pIn;
	else
		pFilename = pIn + 1;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
FileHandle_t CBaseFileSystem::Open( const char *pFileName, const char *pOptions, const char *pathID )
{
	return OpenEx( pFileName, pOptions, 0, pathID );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
FileHandle_t CBaseFileSystem::OpenEx( const char *pFileName, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s(%s, %s, %u %s )", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, pFileName ), tmDynamicString( TELEMETRY_LEVEL0, pOptions ), flags, tmDynamicString( TELEMETRY_LEVEL0, pathID ) );

	VPROF_BUDGET( "CBaseFileSystem::Open", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );

	if ( !pFileName )
		return (FileHandle_t)0;
		
	CHECK_DOUBLE_SLASHES( pFileName );

	if ( ThreadInMainThread() && fs_report_sync_opens.GetInt() > 1 )
	{
		::Warning( "Open( %s )\n", pFileName );
	}

	// Allow for UNC-type syntax to specify the path ID.
	char tempPathID[MAX_PATH];
	ParsePathID( pFileName, pathID, tempPathID );


	// Try each of the search paths in succession
	// FIXME: call createdirhierarchy upon opening for write.
	if ( strstr( pOptions, "r" ) && !strstr( pOptions, "+" ) )
	{
		return OpenForRead( pFileName, pOptions, flags, pathID, ppszResolvedFilename );
	}

	return OpenForWrite( pFileName, pOptions, pathID );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::Close( FileHandle_t file )
{
	VPROF_BUDGET( "CBaseFileSystem::Close", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	if ( !file )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Close NULL file handle!\n" );
		return;
	}
	
	delete (CFileHandle*)file;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::Seek( FileHandle_t file, int pos, FileSystemSeek_t whence )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (pos=%d, whence=%d)", __FUNCTION__, pos, whence );

	VPROF_BUDGET( "CBaseFileSystem::Seek", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "Tried to Seek NULL file handle!\n" );
		return;
	}
	
	fh->Seek( pos, whence );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : file - 
// Output : unsigned int
//-----------------------------------------------------------------------------
unsigned int CBaseFileSystem::Tell( FileHandle_t file )
{
	VPROF_BUDGET( "CBaseFileSystem::Tell", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );

	if ( !file )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Tell NULL file handle!\n" );
		return 0;
	}


	// Pack files are relative
	return (( CFileHandle *)file)->Tell(); 
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : file - 
// Output : unsigned int
//-----------------------------------------------------------------------------
unsigned int CBaseFileSystem::Size( FileHandle_t file )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );

	VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	if ( !file )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Size NULL file handle!\n" );
		return 0;
	}

	return ((CFileHandle *)file)->Size();
}



//-----------------------------------------------------------------------------
// Purpose: 
// Input  : file - 
// Output : unsigned int
//-----------------------------------------------------------------------------
unsigned int CBaseFileSystem::Size( const char* pFileName, const char *pPathID )
{
	VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	CHECK_DOUBLE_SLASHES( pFileName );
	
	// handle the case where no name passed...
	if ( !pFileName || !pFileName[0] )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Size NULL filename!\n" );
		return 0;
	}
	
	// Ok, fall through to the fast path.
	unsigned result = 0;
	FileHandle_t h = Open( pFileName, "rb", pPathID );
	if ( h )
	{
		result = Size( h );
		Close(h);
	}

	return result;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *path - 
//			*pFileName - 
// Output : long
//-----------------------------------------------------------------------------
time_t CBaseFileSystem::FastFileTime( const CSearchPath *path, const char *pFileName )
{
	struct	_stat buf;

	if ( path->GetPackFile() )
	{
		// If we found the file:
		if ( path->GetPackFile()->ContainsFile( pFileName ) )
		{
			return (path->GetPackFile()->m_lPackFileTime);
		}
	}
#ifdef SUPPORT_PACKED_STORE
	else if ( path->GetPackedStore() )
	{
		// Hm, should we support this in some way?
		return 0L;
	}
#endif
	else
	{
		// Is it an absolute path?
		char pTmpFileName[ MAX_FILEPATH ]; 
		
		if ( Q_IsAbsolutePath( pFileName ) )
		{
			Q_strncpy( pTmpFileName, pFileName, sizeof( pTmpFileName ) );
		}
		else
		{
			Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", path->GetPathString(), pFileName );
		}

		Q_FixSlashes( pTmpFileName );

		if( FS_stat( pTmpFileName, &buf ) != -1 )
		{
			return buf.st_mtime;
		}
#if defined(LINUX) || defined(PLATFORM_BSD)
		char caseFixedName[ MAX_PATH ];
		bool found = findFileInDirCaseInsensitive_safe( pTmpFileName, caseFixedName );
		if ( found && FS_stat( caseFixedName, &buf ) != -1 )
		{
			return buf.st_mtime;
		}
#endif
	}

	return ( 0L );
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseFileSystem::EndOfFile( FileHandle_t file )
{
	if ( !file )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to EndOfFile NULL file handle!\n" );
		return true;
	}

	return ((CFileHandle *)file)->EndOfFile();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseFileSystem::Read( void *pOutput, int size, FileHandle_t file )
{
	return ReadEx( pOutput, size, size, file );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseFileSystem::ReadEx( void *pOutput, int destSize, int size, FileHandle_t file )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (%d bytes)", __FUNCTION__, size );

	VPROF_BUDGET( "CBaseFileSystem::Read", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	if ( !file )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Read NULL file handle!\n" );
		return 0;
	}
	if ( size < 0 )
	{
		return 0;
	}
	return ((CFileHandle*)file)->Read(pOutput, destSize, size );
}

//-----------------------------------------------------------------------------
// Purpose: Blow away current readers
// Input  :  - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::UnloadCompiledKeyValues()
{
#ifndef DEDICATED
	for ( int i = 0; i < IFileSystem::NUM_PRELOAD_TYPES; ++i )
	{
		delete m_PreloadData[ i ].m_pReader;
		m_PreloadData[ i ].m_pReader = NULL;
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: Put data file into list of at specific slot, will be loaded when ::SetupPreloadData() gets called
// Input  : type - 
//			*archiveFile - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::LoadCompiledKeyValues( KeyValuesPreloadType_t type, char const *archiveFile )
{
	// Just add to list for appropriate loader
	Assert( type >= 0 && type < IFileSystem::NUM_PRELOAD_TYPES );
	CompiledKeyValuesPreloaders_t& loader = m_PreloadData[ type ];
	Assert( loader.m_CacheFile == 0 );
	loader.m_CacheFile = FindOrAddFileName( archiveFile );
}

//-----------------------------------------------------------------------------
// Purpose: Takes a passed in KeyValues& head and fills in the precompiled keyvalues data into it.
// Input  : head - 
//			type - 
//			*filename - 
//			*pPathID - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::LoadKeyValues( KeyValues& head, KeyValuesPreloadType_t type, char const *filename, char const *pPathID /*= 0*/ )
{
	bool bret = true;

#ifndef DEDICATED
	char tempPathID[MAX_PATH];
	ParsePathID( filename, pPathID, tempPathID );

	// FIXME:  THIS STUFF DOESN'T TRACK pPathID AT ALL RIGHT NOW!!!!!
	if ( !m_PreloadData[ type ].m_pReader || !m_PreloadData[ type ].m_pReader->InstanceInPlace( head, filename ) )
	{
		bret = head.LoadFromFile( this, filename, pPathID );
	}
	return bret;
#else
	bret = head.LoadFromFile( this, filename, pPathID );
	return bret;
#endif
}


//-----------------------------------------------------------------------------
// Purpose: If the "PreloadedData" hasn't been purged, then this'll try and instance the KeyValues using the fast path of 
/// compiled keyvalues loaded during startup.
// Otherwise, it'll just fall through to the regular KeyValues loading routines
// Input  : type - 
//			*filename - 
//			*pPathID - 
// Output : KeyValues
//-----------------------------------------------------------------------------
KeyValues *CBaseFileSystem::LoadKeyValues( KeyValuesPreloadType_t type, char const *filename, char const *pPathID /*= 0*/ )
{
	KeyValues *kv = NULL;

	if ( !m_PreloadData[ type ].m_pReader )
	{
		kv = new KeyValues( filename );
		if ( kv )
		{
			kv->LoadFromFile( this, filename, pPathID );
		}
	}
	else
	{
#ifndef DEDICATED
		// FIXME:  THIS STUFF DOESN'T TRACK pPathID AT ALL RIGHT NOW!!!!!
		kv = m_PreloadData[ type ].m_pReader->Instance( filename );
		if ( !kv )
		{
			kv = new KeyValues( filename );
			if ( kv )
			{
				kv->LoadFromFile( this, filename, pPathID );
			}
		}
#endif
	}
	return kv;
}

//-----------------------------------------------------------------------------
// Purpose: This is the fallback method of reading the name of the first key in the file
// Input  : *filename - 
//			*pPathID - 
//			*rootName - 
//			bufsize - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::LookupKeyValuesRootKeyName( char const *filename, char const *pPathID, char *rootName, size_t bufsize )
{
	if ( FileExists( filename, pPathID ) )
	{
		// open file and get shader name
		FileHandle_t hFile = Open( filename, "r", pPathID );
		if ( hFile == FILESYSTEM_INVALID_HANDLE )
		{
			return false;
		}

		char buf[ 128 ];
		ReadLine( buf, sizeof( buf ), hFile );
		Close( hFile );

		// The name will possibly come in as "foo"\n

		// So we need to strip the starting " character
		char *pStart = buf;
		if ( *pStart == '\"' )
		{
			++pStart;
		}
		// Then copy the rest of the string
		Q_strncpy( rootName, pStart, bufsize );

		// And then strip off the \n and the " character at the end, in that order
		int len = Q_strlen( pStart );
		while ( len > 0 && rootName[ len - 1 ] == '\n' )
		{
			rootName[ len - 1 ] = 0;
			--len;
		}
		while ( len > 0 && rootName[ len - 1 ] == '\"' )
		{
			rootName[ len - 1 ] = 0;
			--len;
		}
	}
	else
	{
		return false;
	}
	return true;
}

//-----------------------------------------------------------------------------
// Purpose: Tries to look up the name of the first key in the file from the compiled data
// Input  : type - 
//			*outbuf - 
//			bufsize - 
//			*filename - 
//			*pPathID - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::ExtractRootKeyName( KeyValuesPreloadType_t type, char *outbuf, size_t bufsize, char const *filename, char const *pPathID /*= 0*/ )
{
	char tempPathID[MAX_PATH];
	ParsePathID( filename, pPathID, tempPathID );

	bool bret = true;

	if ( !m_PreloadData[ type ].m_pReader )
	{
		// Use fallback
		bret = LookupKeyValuesRootKeyName( filename, pPathID, outbuf, bufsize );
	}
	else
	{
#ifndef DEDICATED
		// Try to use cache
		bret = m_PreloadData[ type ].m_pReader->LookupKeyValuesRootKeyName( filename, outbuf, bufsize );
		if ( !bret )
		{
			// Not in cache, use fallback
			bret = LookupKeyValuesRootKeyName( filename, pPathID, outbuf, bufsize );
		}
#endif
	}
	return bret;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::SetupPreloadData()
{
	int i;

	for ( i = 0; i < m_SearchPaths.Count(); i++ )
	{
		CPackFile* pf = m_SearchPaths[i].GetPackFile();
		if ( pf ) 
		{
			pf->SetupPreloadData();
		}
	}

#ifndef DEDICATED
	if ( !CommandLine()->FindParm( "-fs_nopreloaddata" ) )
	{
		// Loads in the precompiled keyvalues data for each type
		for ( i = 0; i < NUM_PRELOAD_TYPES; ++i )
		{
			CompiledKeyValuesPreloaders_t& preloader = m_PreloadData[ i ];
			Assert( !preloader.m_pReader );

			char fn[MAX_PATH];
			if ( preloader.m_CacheFile != 0 && 
				String( preloader.m_CacheFile, fn, sizeof( fn ) ) )
			{
				preloader.m_pReader = new CCompiledKeyValuesReader();
				preloader.m_pReader->LoadFile( fn );
			}
		}
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::DiscardPreloadData()
{
	int i;
	for( i = 0; i < m_SearchPaths.Count(); i++ )
	{
		CPackFile* pf = m_SearchPaths[i].GetPackFile();
		if ( pf )
		{
			pf->DiscardPreloadData();
		}
	}

	UnloadCompiledKeyValues();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseFileSystem::Write( void const* pInput, int size, FileHandle_t file )
{
	VPROF_BUDGET( "CBaseFileSystem::Write", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );

	AUTOBLOCKREPORTER_FH( Write, this, true, file, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_WRITE );

	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Write NULL file handle!\n" );
		return 0;
	}
	return fh->Write( pInput, size );

}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
int CBaseFileSystem::FPrintf( FileHandle_t file, const char *pFormat, ... )
{
	va_list args;
	va_start( args, pFormat );
	VPROF_BUDGET( "CBaseFileSystem::FPrintf", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to FPrintf NULL file handle!\n" );
		return 0;
	}
/*
	if ( !fh->GetFileHandle() )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to FPrintf NULL file pointer inside valid file handle!\n" );
		return 0;
	}
	*/


	char buffer[65535];
	int len = vsnprintf( buffer, sizeof( buffer), pFormat, args );
	len = fh->Write( buffer, len );
	//int len = FS_vfprintf( fh->GetFileHandle() , pFormat, args );
	va_end( args );

	
	return len;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::SetBufferSize( FileHandle_t file, unsigned nBytes )
{
	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to SetBufferSize NULL file handle!\n" );
		return;
	}
	fh->SetBufferSize( nBytes );
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
bool CBaseFileSystem::IsOk( FileHandle_t file )
{
	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to IsOk NULL file handle!\n" );
		return false;
	}

	return fh->IsOK();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::Flush( FileHandle_t file )
{
	VPROF_BUDGET( "CBaseFileSystem::Flush", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to Flush NULL file handle!\n" );
		return;
	}

	fh->Flush();

}

bool CBaseFileSystem::Precache( const char *pFileName, const char *pPathID)
{
	CHECK_DOUBLE_SLASHES( pFileName );

	// Allow for UNC-type syntax to specify the path ID.
	char tempPathID[MAX_PATH];
	ParsePathID( pFileName, pPathID, tempPathID );
	Assert( pPathID );

	// Really simple, just open, the file, read it all in and close it. 
	// We probably want to use file mapping to do this eventually.
	FileHandle_t f = Open( pFileName, "rb", pPathID );
	if ( !f )
		return false;

	// not for consoles, the read discard is a negative benefit, slow and clobbers small drive caches
	if ( IsPC() )
	{
		char buffer[16384];
		while( sizeof(buffer) == Read(buffer,sizeof(buffer),f) );
	}

	Close( f );

	return true;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
char *CBaseFileSystem::ReadLine( char *pOutput, int maxChars, FileHandle_t file )
{
	VPROF_BUDGET( "CBaseFileSystem::ReadLine", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	CFileHandle *fh = ( CFileHandle *)file;
	if ( !fh )
	{
		Warning( FILESYSTEM_WARNING, "FS:  Tried to ReadLine NULL file handle!\n" );
		return NULL;
	}
	m_Stats.nReads++;

	int nRead = 0;

	// Read up to maxchars:
	while( nRead < ( maxChars - 1 ) )
	{
		// Are we at the end of the file?
		if( 1 != fh->Read( pOutput + nRead, 1 ) )
			break;

		// Translate for text mode files:
		if( ( fh->m_type == FT_PACK_TEXT || fh->m_type == FT_MEMORY_TEXT ) && pOutput[nRead] == '\r' )
		{
			// Ignore \r
			continue;
		}

		// We're done when we hit a '\n'
		if( pOutput[nRead] == '\n' )
		{
			nRead++;
			break;
		}

		// Get outta here if we find a NULL.
		if( pOutput[nRead] == '\0' )
		{
			pOutput[nRead] = '\n';
			nRead++;
			break;
		}

		nRead++;
	}

	if( nRead < maxChars )
		pOutput[nRead] = '\0';

	
	m_Stats.nBytesRead += nRead;
	return ( nRead ) ? pOutput : NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFileName - 
// Output : long
//-----------------------------------------------------------------------------
time_t CBaseFileSystem::GetFileTime( const char *pFileName, const char *pPathID )
{
	VPROF_BUDGET( "CBaseFileSystem::GetFileTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );

	CHECK_DOUBLE_SLASHES( pFileName );

	CSearchPathsIterator iter( this, &pFileName, pPathID );

	char tempFileName[MAX_PATH];
	Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) );
	Q_FixSlashes( tempFileName );
#ifdef _WIN32
	Q_strlower( tempFileName );
#endif

	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{
		time_t ft = FastFileTime( pSearchPath, tempFileName );
		if ( ft != 0L )
		{
			if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() )
			{
				char pTmpFileName[ MAX_FILEPATH ]; 
				if ( strchr( tempFileName, ':' ) )
				{
					Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) );
				}
				else
				{
					Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName );
				}

				Q_FixSlashes( tempFileName );

				LogAccessToFile( "filetime", pTmpFileName, "" );
			}

			return ft;
		}
	}
	return (time_t)0L;
}

time_t CBaseFileSystem::GetPathTime( const char *pFileName, const char *pPathID )
{
	VPROF_BUDGET( "CBaseFileSystem::GetPathTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );

	CSearchPathsIterator iter( this, &pFileName, pPathID );

	char tempFileName[MAX_PATH];
	Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) );
	Q_FixSlashes( tempFileName );
#ifdef _WIN32
	Q_strlower( tempFileName );
#endif

	time_t pathTime = 0L;
	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{
		time_t ft = FastFileTime( pSearchPath, tempFileName );
		if ( ft > pathTime )
			pathTime = ft;
		if ( ft != 0L )
		{
			if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() )
			{
				char pTmpFileName[ MAX_FILEPATH ]; 
				if ( strchr( tempFileName, ':' ) )
				{
					Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) );
				}
				else
				{
					Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName );
				}

				Q_FixSlashes( tempFileName );

				LogAccessToFile( "filetime", pTmpFileName, "" );
			}
		}
	}
	return pathTime;
}

void CBaseFileSystem::MarkAllCRCsUnverified()
{
	if ( IsX360() )
	{
		return;
	}

	m_FileTracker2.MarkAllCRCsUnverified();
}


void CBaseFileSystem::CacheFileCRCs( const char *pPathname, ECacheCRCType eType, IFileList *pFilter )
{
	if ( IsX360() )
	{
		return;
	}
}

EFileCRCStatus CBaseFileSystem::CheckCachedFileHash( const char *pPathID, const char *pRelativeFilename, int nFileFraction, FileHash_t *pFileHash )
{
	return m_FileTracker2.CheckCachedFileHash( pPathID, pRelativeFilename, nFileFraction, pFileHash );
}


void CBaseFileSystem::EnableWhitelistFileTracking( bool bEnable, bool bCacheAllVPKHashes, bool bRecalculateAndCheckHashes )
{
	if ( IsX360() )
	{
		m_WhitelistFileTrackingEnabled = false;
		return;
	}

	if ( m_WhitelistFileTrackingEnabled != -1 )
	{
		Error( "CBaseFileSystem::EnableWhitelistFileTracking called more than once." );
	}

	m_WhitelistFileTrackingEnabled = bEnable;
	if ( m_WhitelistFileTrackingEnabled && bCacheAllVPKHashes )
	{
		CacheAllVPKFileHashes( bCacheAllVPKHashes, bRecalculateAndCheckHashes );
	}
}


void CBaseFileSystem::CacheAllVPKFileHashes( bool bCacheAllVPKHashes, bool bRecalculateAndCheckHashes )
{
#ifdef SUPPORT_PACKED_STORE
	for ( int i = 0; i < m_SearchPaths.Count(); i++ )
	{
		CPackedStore *pVPK = m_SearchPaths[i].GetPackedStore();
		if ( pVPK == NULL )
			continue;
		if ( !pVPK->BTestDirectoryHash() )
		{
			Msg( "VPK dir file hash does not match. File corrupted or modified.\n" );
		}
		if ( !pVPK->BTestMasterChunkHash() )
		{
			Msg( "VPK chunk hash hash does not match. File corrupted or modified.\n" );
		}

		CUtlVector<ChunkHashFraction_t> &vecChunkHash = pVPK->AccessPackFileHashes();
		CPackedStoreFileHandle fhandle = pVPK->GetHandleForHashingFiles();
		CUtlVector<ChunkHashFraction_t> vecChunkHashFractionCopy;
		if ( bRecalculateAndCheckHashes )
		{
			CUtlString sPackFileErrors;
			pVPK->GetPackFileLoadErrorSummary( sPackFileErrors );

			if ( sPackFileErrors.Length() )
			{
				Msg( "Errors occured loading files.\n" );
				Msg( "%s", sPackFileErrors.String() );
				Msg( "Verify integrity of your game files, perform memory and disk diagnostics on your system.\n" );
			}
			else
				Msg( "No VPK Errors occured loading files.\n" );

			Msg( "Recomputing all VPK file hashes.\n" );
			vecChunkHashFractionCopy.Swap( vecChunkHash );
		}
		int cFailures = 0;
		if ( vecChunkHash.Count() == 0 )
		{
			if ( vecChunkHashFractionCopy.Count() == 0 )
				Msg( "File hash information not found: Hashing all VPK files for pure server operation.\n" );
			pVPK->HashAllChunkFiles();
			if ( vecChunkHashFractionCopy.Count() != 0 )
			{
				if ( vecChunkHash.Count() != vecChunkHashFractionCopy.Count() )
				{
					Msg( "VPK hash count does not match. VPK content may be corrupt.\n" );
				}
				else if  ( Q_memcmp( vecChunkHash.Base(), vecChunkHashFractionCopy.Base(), vecChunkHash.Count()*sizeof(vecChunkHash[0])) != 0 )
				{
					Msg( "VPK hashes do not match. VPK content may be corrupt.\n" );
					// find the actual mismatch
					FOR_EACH_VEC( vecChunkHashFractionCopy, iHash )
					{
						if ( Q_memcmp( vecChunkHashFractionCopy[iHash].m_md5contents.bits, vecChunkHash[iHash].m_md5contents.bits, sizeof( vecChunkHashFractionCopy[iHash].m_md5contents.bits ) ) != 0 )
						{
							Msg( "VPK hash for file %d failure at offset %x.\n", vecChunkHashFractionCopy[iHash].m_nPackFileNumber, vecChunkHashFractionCopy[iHash].m_nFileFraction );
							cFailures++;
						}
					}
				}
			}
		}
		if ( bCacheAllVPKHashes )
		{
			Msg( "Loaded %d VPK file hashes from %s for pure server operation.\n", vecChunkHash.Count(), pVPK->FullPathName() );
			FOR_EACH_VEC( vecChunkHash, i )
			{
				m_FileTracker2.AddFileHashForVPKFile( vecChunkHash[i].m_nPackFileNumber, vecChunkHash[i].m_nFileFraction, vecChunkHash[i].m_cbChunkLen, vecChunkHash[i].m_md5contents, fhandle );
			}
		}
		else
		{
			if ( cFailures == 0 && vecChunkHash.Count() == vecChunkHashFractionCopy.Count() )
				Msg( "File hashes checked. %d matches. no failures.\n", vecChunkHash.Count() );
			else
				Msg( "File hashes checked. %d matches. %d failures.\n", vecChunkHash.Count(), cFailures );
		}
	}
#endif
}


bool CBaseFileSystem::CheckVPKFileHash( int PackFileID, int nPackFileNumber, int nFileFraction, MD5Value_t &md5Value )
{
#ifdef SUPPORT_PACKED_STORE
	for ( int i = 0; i < m_SearchPaths.Count(); i++ )
	{
		CPackedStore *pVPK = m_SearchPaths[i].GetPackedStore();
		if ( pVPK == NULL || pVPK->m_PackFileID != PackFileID )
			continue;
		ChunkHashFraction_t fileHashFraction;
		if ( pVPK->FindFileHashFraction( nPackFileNumber, nFileFraction, fileHashFraction ) )
		{
			CPackedStoreFileHandle fhandle = pVPK->GetHandleForHashingFiles();
			fhandle.m_nFileNumber = nPackFileNumber;
			char szFileName[MAX_PATH];

			pVPK->GetPackFileName( fhandle, szFileName, sizeof(szFileName) );

			char hex[ 34 ];
			Q_memset( hex, 0, sizeof( hex ) );
			Q_binarytohex( (const byte *)md5Value.bits, sizeof( md5Value.bits ), hex, sizeof( hex ) );

			char hex2[ 34 ];
			Q_memset( hex2, 0, sizeof( hex2 ) );
			Q_binarytohex( (const byte *)fileHashFraction.m_md5contents.bits, sizeof( fileHashFraction.m_md5contents.bits ), hex2, sizeof( hex2 ) );

			if ( Q_memcmp( fileHashFraction.m_md5contents.bits, md5Value.bits, sizeof(md5Value.bits) ) != 0 )
			{
				Msg( "File %s offset %x hash %s does not match ( should be %s ) \n", szFileName, nFileFraction, hex, hex2 );
				return false;
			}
			else
			{
				return true;
			}
		}
	}
#else
	Error("CBaseFileSystem::CheckVPKFileHash should not be called, SUPPORT_PACKED_STORE not defined" );
#endif
	return false;
}

void CBaseFileSystem::RegisterFileWhitelist( IPureServerWhitelist *pWhiteList, IFileList **pFilesToReload )
{
	if ( pFilesToReload )
		*pFilesToReload = NULL;

	if ( IsX360() )
	{
		return;
	}

	if ( m_pPureServerWhitelist )
	{
		m_pPureServerWhitelist->Release();
		m_pPureServerWhitelist = NULL;
	}
	if ( pWhiteList )
	{
		pWhiteList->AddRef();
		m_pPureServerWhitelist = pWhiteList;
	}

	// update which search paths are considered trusted
	FOR_EACH_VEC( m_SearchPaths, i )
	{
		SetSearchPathIsTrustedSource( &m_SearchPaths[i] );
	}

	// See if we need to reload any files
	if ( pFilesToReload )
		*pFilesToReload = m_FileTracker2.GetFilesToUnloadForWhitelistChange( pWhiteList );
}

void CBaseFileSystem::NotifyFileUnloaded( const char *pszFilename, const char *pPathId )
{
	m_FileTracker2.NoteFileUnloaded( pszFilename, pPathId );
}

void CBaseFileSystem::SetSearchPathIsTrustedSource( CSearchPath *pSearchPath )
{
#if 1
	pSearchPath->m_bIsTrustedForPureServer = true;
#else // Broken, I am lazy to fix this
	// Most paths are not considered trusted
	pSearchPath->m_bIsTrustedForPureServer = false;

	// If we don't have a pure server whitelist, we cannot say that any
	// particular files are trusted.  (But then again, all files will be
	// accepted, so it won't really matter.)
	if ( m_pPureServerWhitelist == NULL )
		return;

	// Treat map packs as trusted, because we will send the CRC of the map pack to the server
	if ( pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath )
	{
		#ifdef PURE_SERVER_DEBUG_SPEW
			Msg( "Setting map pack search path %s as trusted\n", pSearchPath->GetDebugString() );
		#endif
		pSearchPath->m_bIsTrustedForPureServer = true;
		return;
	}

	#ifdef SUPPORT_PACKED_STORE
		// Only signed VPK's can be trusted
		CPackedStoreRefCount *pVPK = pSearchPath->GetPackedStore();
		if ( pVPK == NULL )
		{
			#ifdef PURE_SERVER_DEBUG_SPEW
				Msg( "Setting %s as untrusted (loose files)\n", pSearchPath->GetDebugString() );
			#endif
			return;
		}
		if ( !pVPK->m_bSignatureValid )
		{
			#ifdef PURE_SERVER_DEBUG_SPEW
				Msg( "Setting %s as untrusted (unsigned VPK)\n", pSearchPath->GetDebugString() );
			#endif
			return;
		}
		const CUtlVector<uint8> &key = pVPK->GetSignaturePublicKey();
		for ( int iKeyIndex = 0 ; iKeyIndex < m_pPureServerWhitelist->GetTrustedKeyCount() ; ++iKeyIndex )
		{
			int nKeySz = 0;
			const byte *pbKey = m_pPureServerWhitelist->GetTrustedKey( iKeyIndex, &nKeySz );
			Assert( pbKey != NULL && nKeySz > 0 );
			if ( key.Count() == nKeySz && V_memcmp( pbKey, key.Base(), nKeySz ) == 0 )
			{
				#ifdef PURE_SERVER_DEBUG_SPEW
					Msg( "Setting %s as untrusted\n", pSearchPath->GetDebugString() );
				#endif
				pSearchPath->m_bIsTrustedForPureServer = true;
				return;
			}
		}

		#ifdef PURE_SERVER_DEBUG_SPEW
			Msg( "Setting %s as untrusted.  (Key not in trusted key list)\n", pSearchPath->GetDebugString() );
		#endif
	#endif
#endif
}


int CBaseFileSystem::GetUnverifiedFileHashes( CUnverifiedFileHash *pFiles, int nMaxFiles )
{
	return m_FileTracker2.GetUnverifiedFileHashes( pFiles, nMaxFiles );
}



int CBaseFileSystem::GetWhitelistSpewFlags()
{
	return m_WhitelistSpewFlags;
}


void CBaseFileSystem::SetWhitelistSpewFlags( int flags )
{
	m_WhitelistSpewFlags = flags;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pString - 
//			maxCharsIncludingTerminator - 
//			fileTime - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::FileTimeToString( char *pString, int maxCharsIncludingTerminator, time_t fileTime )
{
	if ( IsX360() )
	{
		char szTemp[ 256 ];

		time_t time = fileTime;
		V_strncpy( szTemp, ctime( &time ), sizeof( szTemp ) );
		char *pFinalColon = Q_strrchr( szTemp, ':' );
		if ( pFinalColon )
			*pFinalColon = '\0';

		// Clip off the day of the week
		V_strncpy( pString, szTemp + 4, maxCharsIncludingTerminator );
	}
	else
	{
		time_t time = fileTime;
		V_strncpy( pString, ctime( &time ), maxCharsIncludingTerminator );

		// We see a linefeed at the end of these strings...if there is one, gobble it up
		int len = V_strlen( pString );
		if ( pString[ len - 1 ] == '\n' )
		{
			pString[ len - 1 ] = '\0';
		}

		pString[maxCharsIncludingTerminator-1] = '\0';
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFileName - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::FileExists( const char *pFileName, const char *pPathID )
{
	VPROF_BUDGET( "CBaseFileSystem::FileExists", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );

	CHECK_DOUBLE_SLASHES( pFileName );

	FileHandle_t h = Open( pFileName, "rb", pPathID );
	if ( h )
	{
		Close(h);
		return true;
	}

	return false;
}

bool CBaseFileSystem::IsFileWritable( char const *pFileName, char const *pPathID /*=0*/ )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	struct	_stat buf;

	char tempPathID[MAX_PATH];
	ParsePathID( pFileName, pPathID, tempPathID );

	if ( Q_IsAbsolutePath( pFileName ) )
	{
		if( FS_stat( pFileName, &buf ) != -1 )
		{
#ifdef WIN32
			if( buf.st_mode & _S_IWRITE )
#elif defined (LINUX) && !defined (ANDROID) 
			if( buf.st_mode & S_IWRITE )
#elif ANDROID
			if( buf.st_mode & S_IWUSR )
#else
			if( buf.st_mode & S_IWRITE )
#endif
			{
				return true;
			}
		}
		return false;
	}

	CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK );
	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{
		char pTmpFileName[ MAX_FILEPATH ];
		Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFileName );
		Q_FixSlashes( pTmpFileName );
		if ( FS_stat( pTmpFileName, &buf ) != -1 )
		{
#ifdef WIN32
			if ( buf.st_mode & _S_IWRITE )
#elif defined (LINUX) && !defined (ANDROID) 
			if ( buf.st_mode & S_IWRITE )
#elif ANDROID
			if ( buf.st_mode & S_IWUSR )
#else
			if ( buf.st_mode & S_IWRITE )
#endif
			{
				return true;
			}
		}
	}
	return false;
}


bool CBaseFileSystem::SetFileWritable( char const *pFileName, bool writable, const char *pPathID /*= 0*/ )
{
	CHECK_DOUBLE_SLASHES( pFileName );

#ifdef _WIN32
	int pmode = writable ? ( _S_IWRITE | _S_IREAD ) : ( _S_IREAD );
#elif ANDROID
	int pmode = writable ? ( S_IWUSR | S_IRUSR ) : ( S_IRUSR );
#else
	int pmode = writable ? ( S_IWRITE | S_IREAD ) : ( S_IREAD );
#endif

	char tempPathID[MAX_PATH];
	ParsePathID( pFileName, pPathID, tempPathID );

	if ( Q_IsAbsolutePath( pFileName ) )
	{
		return ( FS_chmod( pFileName, pmode ) == 0 );
	}

	CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK );
	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{
		char pTmpFileName[ MAX_FILEPATH ];
		Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFileName );
		Q_FixSlashes( pTmpFileName );

		if ( FS_chmod( pTmpFileName, pmode ) == 0 )
		{
			return true;
		}
	}

	// Failure
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFileName - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::IsDirectory( const char *pFileName, const char *pathID )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	// Allow for UNC-type syntax to specify the path ID.
	struct	_stat buf;

	char pTempBuf[MAX_PATH];
	Q_strncpy( pTempBuf, pFileName, sizeof(pTempBuf) );
	Q_StripTrailingSlash( pTempBuf );
	pFileName = pTempBuf;

	char tempPathID[MAX_PATH];
	ParsePathID( pFileName, pathID, tempPathID );
	if ( Q_IsAbsolutePath( pFileName ) )
	{
		if ( FS_stat( pFileName, &buf ) != -1 )
		{
			if ( buf.st_mode & _S_IFDIR )
				return true;
		}
		return false;
	}

	CSearchPathsIterator iter( this, &pFileName, pathID, FILTER_CULLPACK );
	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{
#ifdef SUPPORT_PACKED_STORE
		if ( pSearchPath->GetPackedStore() )
		{
			CUtlStringList outDir, outFile;
			pSearchPath->GetPackedStore()->GetFileAndDirLists( outDir, outFile, false );
			FOR_EACH_VEC( outDir, i )
			{
				if ( !Q_stricmp( outDir[i], pFileName ) )
					return true;
			}

		}
		else
#endif // SUPPORT_PACKED_STORE
		{
			char pTmpFileName[ MAX_FILEPATH ];
			Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFileName );
			Q_FixSlashes( pTmpFileName );
			if ( FS_stat( pTmpFileName, &buf ) != -1 )
			{
				if ( buf.st_mode & _S_IFDIR )
					return true;
			}
		}
	}
	return false;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *path - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::CreateDirHierarchy( const char *pRelativePathT, const char *pathID )
{	
	// Allow for UNC-type syntax to specify the path ID.
	char tempPathID[MAX_PATH];
	ParsePathID(pRelativePathT, pathID, tempPathID); // use the original path param to preserve "//"

	char pRelativePathBuff[ MAX_PATH ];
	const char *pRelativePath = pRelativePathBuff;

	FixUpPath ( pRelativePathT, pRelativePathBuff, sizeof( pRelativePathBuff ) );
		
	CHECK_DOUBLE_SLASHES( pRelativePath );

	char szScratchFileName[MAX_PATH];
	if ( !Q_IsAbsolutePath( pRelativePath ) )
	{
		Assert( pathID );


		ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID );
	}
	else
	{
		Q_strncpy( szScratchFileName, pRelativePath, sizeof(szScratchFileName) );
	}

	int len = strlen( szScratchFileName ) + 1;
	char *end = szScratchFileName + len;
	char *s = szScratchFileName;
	while( s < end )
	{
		if( *s == CORRECT_PATH_SEPARATOR && s != szScratchFileName && ( IsLinux() || *( s - 1 ) != ':' ) )
		{
			*s = '\0';
#if defined( _WIN32 )
			_mkdir( szScratchFileName );
#elif defined( POSIX )
			mkdir( szScratchFileName, S_IRWXU |  S_IRGRP |  S_IROTH );// owner has rwx, rest have r
#endif
			*s = CORRECT_PATH_SEPARATOR;
		}
		s++;
	}

#if defined( _WIN32 )
	_mkdir( szScratchFileName );
#elif defined( POSIX )
	mkdir( szScratchFileName, S_IRWXU |  S_IRGRP |  S_IROTH );
#endif
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pWildCard - 
//			*pHandle - 
// Output : const char
//-----------------------------------------------------------------------------
const char *CBaseFileSystem::FindFirstEx( const char *pWildCard, const char *pPathID, FileFindHandle_t *pHandle )
{
	CHECK_DOUBLE_SLASHES( pWildCard );

	return FindFirstHelper( pWildCard, pPathID, pHandle, NULL );
}


const char *CBaseFileSystem::FindFirstHelper( const char *pWildCardT, const char *pPathID, FileFindHandle_t *pHandle, int *pFoundStoreID )
{
	VPROF_BUDGET( "CBaseFileSystem::FindFirst", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
 	Assert(pWildCardT);
 	Assert(pHandle);

	FileFindHandle_t hTmpHandle = m_FindData.AddToTail();
	FindData_t *pFindData = &m_FindData[hTmpHandle];
	Assert( pFindData );
	if ( pPathID )
	{
		pFindData->m_FilterPathID = g_PathIDTable.AddString( pPathID );
	}

	char pWildCard[ MAX_PATH ];

	FixUpPath ( pWildCardT, pWildCard, sizeof( pWildCard ) );

	int maxlen = strlen( pWildCard ) + 1;
	pFindData->wildCardString.AddMultipleToTail( maxlen );
	Q_strncpy( pFindData->wildCardString.Base(), pWildCard, maxlen );
	pFindData->findHandle = INVALID_HANDLE_VALUE;

	if ( Q_IsAbsolutePath( pWildCard ) )
	{
		// Absolute path, cannot be VPK or Pak
		pFindData->findHandle = FS_FindFirstFile( pWildCard, &pFindData->findData );
		pFindData->currentSearchPathID = -1;
	}
	else
	{
		int c = m_SearchPaths.Count();
		for(	pFindData->currentSearchPathID = 0;
				pFindData->currentSearchPathID < c;
				pFindData->currentSearchPathID++ )
		{
			CSearchPath *pSearchPath = &m_SearchPaths[pFindData->currentSearchPathID];

			if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) )
				continue;

			// already visited this path?
			if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) )
				continue;

			// If this is a VPK or Pak file, build list of matches now and use helper
			bool bIsVPKOrPak = false;
			if ( pSearchPath->GetPackFile() )
			{
				// XXX(johns) This support didn't exist for a long time, and I'm now worried about various things
				//            looking for misc files suddenly finding them in the (untrusted) BSP and causing security
				//            nightmares. For now, restricting FindFirst() support to BSPs only when the BSP search path
				//            is explicitly requested, but this would otherwise work fine.
				if ( !pPathID || V_strcmp( pPathID, "BSP" ) != 0 )
				{
					continue;
				}

				Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 );
				Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 );
				pSearchPath->GetPackFile()->GetFileAndDirLists( pWildCard, pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true );
				bIsVPKOrPak = true;
			}

			#ifdef SUPPORT_PACKED_STORE
				if ( pSearchPath->GetPackedStore() )
				{
					Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 );
					Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 );
					pSearchPath->GetPackedStore()->GetFileAndDirLists( pWildCard, pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true );
					bIsVPKOrPak = true;
				}
			#endif

			if ( bIsVPKOrPak )
			{
				if ( FindNextFileInVPKOrPakHelper( pFindData ) )
				{
					// Remember that we visited this file already.
					pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 );
					*pHandle = hTmpHandle;
					return pFindData->findData.cFileName;
				}
				continue;
			}

			// Otherwise, raw FS find for relative path
			char pTmpFileName[ MAX_FILEPATH ];
			Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() );
			Q_FixSlashes( pTmpFileName );
			pFindData->findHandle = FS_FindFirstFile( pTmpFileName, &pFindData->findData );
			pFindData->m_CurrentStoreID = pSearchPath->m_storeId;

			if( pFindData->findHandle != INVALID_HANDLE_VALUE )
				break;
		}
	}

	// We have a result from the filesystem
 	if( pFindData->findHandle != INVALID_HANDLE_VALUE )
	{
		// Remember that we visited this file already.
		pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 );

		if ( pFoundStoreID )
			*pFoundStoreID = pFindData->m_CurrentStoreID;

		*pHandle = hTmpHandle;
		return pFindData->findData.cFileName;
	}

	// Handle failure here
	pFindData = 0;
	m_FindData.Remove(hTmpHandle);
	*pHandle = -1;

	return NULL;
}

const char *CBaseFileSystem::FindFirst( const char *pWildCard, FileFindHandle_t *pHandle )
{
	return FindFirstEx( pWildCard, NULL, pHandle );
}


// Get the next file, trucking through the path. . don't check for duplicates.
bool CBaseFileSystem::FindNextFileHelper( FindData_t *pFindData, int *pFoundStoreID )
{
	// Try the same search path that we were already searching on.
	if( FS_FindNextFile( pFindData->findHandle, &pFindData->findData ) )
	{
		if ( pFoundStoreID )
			*pFoundStoreID = pFindData->m_CurrentStoreID;

		return true;
	}

	if ( FindNextFileInVPKOrPakHelper( pFindData ) )
		return true;

	// This happens when we searched a full path
	if ( pFindData->currentSearchPathID < 0 )
		return false;

	pFindData->currentSearchPathID++;

	if ( pFindData->findHandle != INVALID_HANDLE_VALUE )
	{
		FS_FindClose( pFindData->findHandle );
	}
	pFindData->findHandle = INVALID_HANDLE_VALUE;

	int c = m_SearchPaths.Count();
	for( ; pFindData->currentSearchPathID < c; ++pFindData->currentSearchPathID ) 
	{
		CSearchPath *pSearchPath = &m_SearchPaths[pFindData->currentSearchPathID];

		if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) )
			continue;

		// already visited this path
		if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) )
			continue;

		if ( pSearchPath->GetPackFile() )
		{
			// XXX(johns) This support didn't exist for a long time, and I'm now worried about various things
			//            looking for misc files suddenly finding them in the (untrusted) BSP and causing security
			//            nightmares. For now, restricting FindFirst() support to BSPs only when the BSP search path
			//            is explicitly requested, but this would otherwise work fine.
			if ( !pFindData->m_FilterPathID || V_strcmp( g_PathIDTable.String( pFindData->m_FilterPathID ), "BSP" ) != 0 )
			{
				continue;
			}
			Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 );
			Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 );
			pSearchPath->GetPackFile()->GetFileAndDirLists( pFindData->wildCardString.Base(), pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true );
			if ( FindNextFileInVPKOrPakHelper( pFindData ) )
				return true;
			continue;
		}

		#ifdef SUPPORT_PACKED_STORE
			if ( pSearchPath->GetPackedStore() )
			{
				Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 );
				Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 );
				pSearchPath->GetPackedStore()->GetFileAndDirLists( pFindData->wildCardString.Base(), pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true );
				if ( FindNextFileInVPKOrPakHelper( pFindData ) )
					return true;
				continue;
			}
		#endif

		char pTmpFileName[ MAX_FILEPATH ];
		Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() );
		Q_FixSlashes( pTmpFileName );
		pFindData->findHandle = FS_FindFirstFile( pTmpFileName, &pFindData->findData );
		pFindData->m_CurrentStoreID = pSearchPath->m_storeId;
		if( pFindData->findHandle != INVALID_HANDLE_VALUE )
		{
			if ( pFoundStoreID )
				*pFoundStoreID = pFindData->m_CurrentStoreID;

			return true;
		}
	}

	return false;
}

bool CBaseFileSystem::FindNextFileInVPKOrPakHelper( FindData_t *pFindData )
{
	// Return the next one from the list of VPK matches if there is one
	if ( pFindData->m_fileMatchesFromVPKOrPak.Count() > 0 )
	{
		V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPKOrPak[0] ), sizeof( pFindData->findData.cFileName ) );
		pFindData->findData.dwFileAttributes = 0;
		delete[] pFindData->m_fileMatchesFromVPKOrPak.Head();
		pFindData->m_fileMatchesFromVPKOrPak.RemoveMultipleFromHead( 1 );

		return true;
	}

	// Return the next one from the list of VPK matches if there is one
	if ( pFindData->m_dirMatchesFromVPKOrPak.Count() > 0 )
	{
		V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPKOrPak[0] ), sizeof( pFindData->findData.cFileName ) );
		pFindData->findData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY;
		delete pFindData->m_dirMatchesFromVPKOrPak.Head();
		pFindData->m_dirMatchesFromVPKOrPak.RemoveMultipleFromHead( 1 );

		return true;
	}

	return false;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handle - 
// Output : const char
//-----------------------------------------------------------------------------
const char *CBaseFileSystem::FindNext( FileFindHandle_t handle )
{
	VPROF_BUDGET( "CBaseFileSystem::FindNext", VPROF_BUDGETGROUP_OTHER_FILESYSTEM );
	FindData_t *pFindData = &m_FindData[handle];

	while( 1 )
	{
		if( FindNextFileHelper( pFindData, NULL ) )
		{
			if ( pFindData->m_VisitedFiles.Find( pFindData->findData.cFileName ) == -1 )
			{
				pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 );
				return pFindData->findData.cFileName;
			}
		}
		else
		{
			return NULL;
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handle - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::FindIsDirectory( FileFindHandle_t handle )
{
	FindData_t *pFindData = &m_FindData[handle];
	return !!( pFindData->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handle - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::FindClose( FileFindHandle_t handle )
{
	if ( ( handle < 0 ) || ( !m_FindData.IsInList( handle ) ) )
		return;

	FindData_t *pFindData = &m_FindData[handle];
	Assert(pFindData);

	if ( pFindData->findHandle != INVALID_HANDLE_VALUE)
	{
		FS_FindClose( pFindData->findHandle );
	}
	pFindData->findHandle = INVALID_HANDLE_VALUE;

	pFindData->wildCardString.Purge();
	pFindData->m_fileMatchesFromVPKOrPak.PurgeAndDeleteElements();
	pFindData->m_dirMatchesFromVPKOrPak.PurgeAndDeleteElements();
	m_FindData.Remove( handle );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFileName - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::GetLocalCopy( const char *pFileName )
{
	// do nothing. . everything is local.
}

//-----------------------------------------------------------------------------
// Purpose: Fixes up Path names.  Will fix up platform specific slashes, remove
//          any ../ or ./, fix //s, and lowercase anything under the directory
//          that the game is installed to.  We expect all files to be lower cased
//          there - especially on Linux (where case sensitivity is the norm). 
//
// Input  : *pFileName - Original name to convert
//          *pFixedUpFileName - a buffer to put the converted filename into
//          sizeFixedUpFileName - the size of the above buffer in chars 
//-----------------------------------------------------------------------------
bool CBaseFileSystem::FixUpPath( const char *pFileName, char *pFixedUpFileName, int sizeFixedUpFileName )
{
	//  If appropriate fixes up the filename to ensure that it's handled properly by the system.
	//
	V_strncpy( pFixedUpFileName, pFileName, sizeFixedUpFileName );
	V_FixSlashes( pFixedUpFileName, CORRECT_PATH_SEPARATOR );
//	V_RemoveDotSlashes( pFixedUpFileName, CORRECT_PATH_SEPARATOR, true );
	V_FixDoubleSlashes( pFixedUpFileName );

	if ( !V_IsAbsolutePath( pFixedUpFileName ) )
	{
		V_strlower( pFixedUpFileName );
	}
	else 
	{
		//  Get the BASE_PATH, skip past  - if necessary, and lowercase the rest
		//  Not just yet...


		int iBaseLength = 0;
		char pBaseDir[MAX_PATH];

		//  Need to get "BASE_PATH" from the filesystem paths, and then check this name against it.
		//
		iBaseLength = GetSearchPath( "BASE_PATH", true, pBaseDir, sizeof( pBaseDir ) );
		if ( iBaseLength )
		{
			//  If the first part of the pFixedUpFilename is pBaseDir
			//  then lowercase the part after that.
			if ( *pBaseDir && (iBaseLength+1 < V_strlen( pFixedUpFileName ) ) && (0 != V_strncmp( pBaseDir, pFixedUpFileName, iBaseLength ) )  )
			{
				V_strlower( &pFixedUpFileName[iBaseLength-1] );
			}
		}
	    
	}

//	Msg("CBaseFileSystem::FixUpPath: Converted %s to %s\n", pFileName, pFixedUpFileName);  // too noisy

#ifdef NEVER // Useful if you're trying to see why your file may not be found (if you have a mixed case file)
	if (strncmp(pFixedUpFileName, pFileName, 256))
	{
	    printf("FixUpPath->Converting %s to %s\n",pFileName, pFixedUpFileName);
	}
#endif // NEVER
	return true;
}


//-----------------------------------------------------------------------------
// Converts a partial path into a full path
// Relative paths that are pack based are returned as an absolute path .../zip?.zip/foo
// A pack absolute path can be sent back in for opening, and the file will be properly
// detected as pack based and mounted inside the pack.
//-----------------------------------------------------------------------------
const char *CBaseFileSystem::RelativePathToFullPath( const char *pFileName, const char *pPathID, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars, PathTypeFilter_t pathFilter, PathTypeQuery_t *pPathType )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	struct	_stat buf;

	if ( pPathType )
	{
		*pPathType = PATH_IS_NORMAL;
	}

	// Convert filename to lowercase.  All files in the
	// game logical filesystem must be accessed by lowercase name
	char szLowercaseFilename[ MAX_PATH ];
	FixUpPath( pFileName, szLowercaseFilename, sizeof( szLowercaseFilename ) );
	pFileName = szLowercaseFilename;

	// Fill in the default in case it's not found...
	V_strncpy( pDest, pFileName, maxLenInChars );

// @FD This is arbitrary and seems broken.  If the caller needs this filter, they should
//     request it with the flag themselves.  As it is, I cannot search all the file paths
//     for a file using this function because there is no option that says, "No, really, I
//     mean ALL SEARCH PATHS."  The current problem I'm trying to fix is that sounds are not
//     working if they are in the BSP.  I wrote code that assumed that I could just ask for
//     the absolute path of a file, since we are able to open files with these absolute
//     filenames, and that each particular filesystem call wouldn't have its own individual
//     quirks.
//	if ( IsPC() && pathFilter == FILTER_NONE )
//	{
//		// X360TBD: PC legacy behavior never returned pack paths
//		// do legacy behavior to ensure naive callers don't break
//		pathFilter = FILTER_CULLPACK;
//	}


	CSearchPathsIterator iter( this, &pFileName, pPathID, pathFilter );
	for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
	{

		CPackFile *pPack = pSearchPath->GetPackFile();
		if ( pPack )
		{
			if ( pPack->ContainsFile( pFileName ) )
			{
				if ( pPathType )
				{
					if ( pPack->m_bIsMapPath )
					{
						*pPathType |= PATH_IS_MAPPACKFILE;
					}
					else
					{
						*pPathType |= PATH_IS_PACKFILE;
					}
					if ( pSearchPath->m_bIsRemotePath )
					{
						*pPathType |= PATH_IS_REMOTE;
					}
				}

				// form an encoded absolute path that can be decoded by our FS as pak based
				const char *pszPackName = pPack->m_ZipName.String();
				int len = V_strlen( pszPackName );
				int nTotalLen = len + 1 + V_strlen( pFileName );
				if ( nTotalLen >= maxLenInChars )
				{
					::Warning( "File %s was found in %s, but resulting abs filename won't fit in callers buffer of %d bytes\n",
						pFileName, pszPackName, maxLenInChars );
					Assert( false );
					return NULL;
				}

				V_strncpy( pDest, pszPackName, maxLenInChars );
				V_AppendSlash( pDest, maxLenInChars );
				V_strncat( pDest, pFileName, maxLenInChars ); 
				Assert( V_strlen( pDest ) == nTotalLen );
				return pDest;
			}

			continue;
		}

		// Found in VPK?
		#ifdef SUPPORT_PACKED_STORE
			CPackedStore *pVPK = pSearchPath->GetPackedStore();
			if ( pVPK )
			{
				CPackedStoreFileHandle vpkHandle = pVPK->OpenFile( pFileName );
				if ( vpkHandle )
				{
					const char *pszVpkName = vpkHandle.m_pOwner->FullPathName();
					Assert( V_GetFileExtension( pszVpkName ) != NULL );

					int len = V_strlen( pszVpkName );
					int nTotalLen = len + 1 + V_strlen( pFileName );
					if ( nTotalLen >= maxLenInChars )
					{
						::Warning( "File %s was found in %s, but resulting abs filename won't fit in callers buffer of %d bytes\n",
							pFileName, pszVpkName, maxLenInChars );
						Assert( false );
						return NULL;
					}

					V_strncpy( pDest, pszVpkName, maxLenInChars );
					V_AppendSlash( pDest, maxLenInChars );
					V_strncat( pDest, pFileName, maxLenInChars );
					V_FixSlashes( pDest );
					return pDest;
				}
				continue;
			}
		#endif

		char pTmpFileName[ MAX_FILEPATH ];
		V_sprintf_safe( pTmpFileName, "%s%s", pSearchPath->GetPathString(), pFileName );
		V_FixSlashes( pTmpFileName );
		if ( FS_stat( pTmpFileName, &buf ) != -1 )
		{
			V_strncpy( pDest, pTmpFileName, maxLenInChars );
			if ( pPathType && pSearchPath->m_bIsRemotePath )
			{
				*pPathType |= PATH_IS_REMOTE;
			}
			return pDest;
		}
	}

	// not found
	return NULL;
}

const char *CBaseFileSystem::GetLocalPath( const char *pFileName, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars )
{
	CHECK_DOUBLE_SLASHES( pFileName );

	return RelativePathToFullPath( pFileName, NULL, pDest, maxLenInChars );
}


//-----------------------------------------------------------------------------
// Returns true on success, otherwise false if it can't be resolved
//-----------------------------------------------------------------------------
bool CBaseFileSystem::FullPathToRelativePathEx( const char *pFullPath, const char *pPathId, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars )
{
	CHECK_DOUBLE_SLASHES( pFullPath );

	int nInlen = V_strlen( pFullPath );
	if ( nInlen <= 0 )
	{
		pDest[ 0 ] = 0;
		return false;
	}

	V_strncpy( pDest, pFullPath, maxLenInChars );

	char pInPath[ MAX_FILEPATH ];
	V_strcpy_safe( pInPath, pFullPath );
#ifdef _WIN32
	V_strlower( pInPath );
#endif
	V_FixSlashes( pInPath );

	CUtlSymbol lookup;
	if ( pPathId )
	{
		lookup = g_PathIDTable.AddString( pPathId );
	}

	int c = m_SearchPaths.Count();
	for( int i = 0; i < c; i++ )
	{
		// FIXME: Should this work with embedded pak files?
		if ( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath )
			continue;

		// Skip paths that are not on the specified search path
		if ( FilterByPathID( &m_SearchPaths[i], lookup ) )
			continue;

		char pSearchBase[ MAX_FILEPATH ];
		V_strncpy( pSearchBase, m_SearchPaths[i].GetPathString(), sizeof( pSearchBase ) );
#ifdef _WIN32
		V_strlower( pSearchBase );
#endif
		V_FixSlashes( pSearchBase );
		int nSearchLen = V_strlen( pSearchBase );
		if ( V_strnicmp( pSearchBase, pInPath, nSearchLen ) )
			continue;

		V_strncpy( pDest, &pInPath[ nSearchLen ], maxLenInChars );
		return true;
	}

	return false;
}

	
//-----------------------------------------------------------------------------
// Obsolete version
//-----------------------------------------------------------------------------
bool CBaseFileSystem::FullPathToRelativePath( const char *pFullPath, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars )
{
	return FullPathToRelativePathEx( pFullPath, NULL, pDest, maxLenInChars );
}


//-----------------------------------------------------------------------------
// Returns true on successfully retrieve case-sensitive full path, otherwise false
//-----------------------------------------------------------------------------
bool CBaseFileSystem::GetCaseCorrectFullPath_Ptr( const char *pFullPath, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars )
{
	CHECK_DOUBLE_SLASHES( pFullPath );
	
#ifndef _WIN32
	
	V_strncpy( pDest, pFullPath, maxLenInChars );
	return true;

#else

	char szCurrentDir[MAX_PATH];
	V_strcpy_safe( szCurrentDir, pFullPath );
	V_StripLastDir( szCurrentDir, sizeof( szCurrentDir ) );

	CUtlString strSearchName = pFullPath;
	strSearchName.StripTrailingSlash();
	strSearchName = strSearchName.Slice( V_strlen( szCurrentDir ), strSearchName.Length() );

	CUtlString strSearchPath = szCurrentDir;
	strSearchPath += "*";

	CUtlString strFoundCaseCorrectName;
	FileFindHandle_t findHandle;
	const char *pszCaseCorrectName = FindFirst( strSearchPath.Get(), &findHandle );
	while ( pszCaseCorrectName )
	{
		if ( V_stricmp( strSearchName.String(), pszCaseCorrectName ) == 0 )
		{
			strFoundCaseCorrectName = pszCaseCorrectName;
			break;
		}
		pszCaseCorrectName = FindNext( findHandle );
	}
	FindClose( findHandle );

	// Not found
	if ( strFoundCaseCorrectName.IsEmpty() )
	{
		V_strncpy( pDest, pFullPath, maxLenInChars );
		return false;
	}

	// If drive path, no need to recurse anymore.
	bool bResult = false;
	char szDir[MAX_PATH];
	if ( !IsDirectory( szCurrentDir, NULL ) )
	{
		V_strupr( szCurrentDir );
		V_strncpy( szDir, szCurrentDir, sizeof( szDir ) );
		bResult = true;
	}
	else
	{
		bResult = GetCaseCorrectFullPath( szCurrentDir, szDir );
	}

	// connect the current path with the case-correct dir/file name
	V_MakeAbsolutePath( pDest, maxLenInChars, strFoundCaseCorrectName.String(), szDir );

	return bResult;
#endif // _WIN32
}



//-----------------------------------------------------------------------------
// Deletes a file
//-----------------------------------------------------------------------------
void CBaseFileSystem::RemoveFile( char const* pRelativePath, const char *pathID )
{
	CHECK_DOUBLE_SLASHES( pRelativePath );

	// Allow for UNC-type syntax to specify the path ID.
	char tempPathID[MAX_PATH];
	ParsePathID( pRelativePath, pathID, tempPathID );

	Assert( pathID || !IsX360() );

	// Opening for write or append uses Write Path
	char szScratchFileName[MAX_PATH];
	if ( Q_IsAbsolutePath( pRelativePath ) )
	{
		Q_strncpy( szScratchFileName, pRelativePath, sizeof( szScratchFileName ) );
	}
	else
	{
		ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID );
	}
	int fail = unlink( szScratchFileName );
	if ( fail != 0 )
	{
		Warning( FILESYSTEM_WARNING, "Unable to remove %s!\n", szScratchFileName );
	}
}


//-----------------------------------------------------------------------------
// Renames a file
//-----------------------------------------------------------------------------
bool CBaseFileSystem::RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID )
{
	Assert( pOldPath && pNewPath );

	CHECK_DOUBLE_SLASHES( pOldPath );
	CHECK_DOUBLE_SLASHES( pNewPath );

	// Allow for UNC-type syntax to specify the path ID.
	char pPathIdCopy[MAX_PATH];
	const char *pOldPathId = pathID;
	if ( pathID )
	{
		Q_strncpy( pPathIdCopy, pathID, sizeof( pPathIdCopy ) );
		pOldPathId = pPathIdCopy;
	}

	char tempOldPathID[MAX_PATH];
	ParsePathID( pOldPath, pOldPathId, tempOldPathID );
	Assert( pOldPathId );

	// Allow for UNC-type syntax to specify the path ID.
	char tempNewPathID[MAX_PATH];
	ParsePathID( pNewPath, pathID, tempNewPathID );
	Assert( pathID );

	char pNewFileName[ MAX_PATH ];
	char szScratchFileName[MAX_PATH];

	// The source file may be in a fallback directory, so just resolve the actual path, don't assume pathid...
	RelativePathToFullPath( pOldPath, pOldPathId, szScratchFileName, sizeof( szScratchFileName ) );

	// Figure out the dest path
	if ( !Q_IsAbsolutePath( pNewPath ) )
	{
		ComputeFullWritePath( pNewFileName, sizeof( pNewFileName ), pNewPath, pathID );
	}
	else
	{
		Q_strncpy( pNewFileName, pNewPath, sizeof(pNewFileName) );
	}

	// Make sure the directory exitsts, too
	char pPathOnly[ MAX_PATH ];
	Q_strncpy( pPathOnly, pNewFileName, sizeof( pPathOnly ) );
	Q_StripFilename( pPathOnly );
	CreateDirHierarchy( pPathOnly, pathID );

	// Now copy the file over
	int fail = rename( szScratchFileName, pNewFileName );
	if (fail != 0)
	{
		Warning( FILESYSTEM_WARNING, "Unable to rename %s to %s!\n", szScratchFileName, pNewFileName );
		return false;
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : **ppdir - 
//-----------------------------------------------------------------------------
bool CBaseFileSystem::GetCurrentDirectory( char* pDirectory, int maxlen )
{
#if defined( _WIN32 ) && !defined( _X360 )
	if ( !::GetCurrentDirectoryA( maxlen, pDirectory ) )
#elif defined( POSIX ) || defined( _X360 )
	if ( !getcwd( pDirectory, maxlen ) )
#endif
		return false;

	Q_FixSlashes(pDirectory);

	// Strip the last slash
	int len = strlen(pDirectory);
	if ( pDirectory[ len-1 ] == CORRECT_PATH_SEPARATOR )
	{
		pDirectory[ len-1 ] = 0;
	}

	return true;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pfnWarning - warning function callback
//-----------------------------------------------------------------------------
void CBaseFileSystem::SetWarningFunc( void (*pfnWarning)( const char *fmt, ... ) )
{
	m_pfnWarning = pfnWarning;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : level - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::SetWarningLevel( FileWarningLevel_t level )
{
	m_fwLevel = level;
}

const FileSystemStatistics *CBaseFileSystem::GetFilesystemStatistics()
{
	return &m_Stats;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : level - 
//			*fmt - 
//			... - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::Warning( FileWarningLevel_t level, const char *fmt, ... )
{
	if ( level > m_fwLevel )
		return;

	if ( ( fs_warning_mode.GetInt() == 1 && !ThreadInMainThread() ) || ( fs_warning_mode.GetInt() == 2 && ThreadInMainThread() ) )
		return;

	va_list argptr; 
    char warningtext[ 4096 ];
    
    va_start( argptr, fmt );
    Q_vsnprintf( warningtext, sizeof( warningtext ), fmt, argptr );
    va_end( argptr );

	// Dump to stdio
	printf( "%s", warningtext );
	if ( m_pfnWarning )
	{
		(*m_pfnWarning)( warningtext );
	}
	else
	{
#ifdef _WIN32
		Plat_DebugString( warningtext );
#endif
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::COpenedFile::COpenedFile( void )
{
	m_pFile = NULL;
	m_pName = NULL;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::COpenedFile::~COpenedFile( void )
{
	delete[] m_pName;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : src - 
//-----------------------------------------------------------------------------
CBaseFileSystem::COpenedFile::COpenedFile( const COpenedFile& src )
{
	m_pFile = src.m_pFile;
	if ( src.m_pName )
	{
		int len = strlen( src.m_pName ) + 1;
		m_pName = new char[ len ];
		Q_strncpy( m_pName, src.m_pName, len );
	}
	else
	{
		m_pName = NULL;
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : src - 
// Output : Returns true on success, false on failure.
//-----------------------------------------------------------------------------
bool CBaseFileSystem::COpenedFile::operator==( const CBaseFileSystem::COpenedFile& src ) const
{
	return src.m_pFile == m_pFile;
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *name - 
//-----------------------------------------------------------------------------
void CBaseFileSystem::COpenedFile::SetName( char const *name )
{
	delete[] m_pName;
	int len = strlen( name ) + 1;
	m_pName = new char[ len ];
	Q_strncpy( m_pName, name, len );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Output : char
//-----------------------------------------------------------------------------
char const *CBaseFileSystem::COpenedFile::GetName( void )
{
	return m_pName ? m_pName : "???";
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::CSearchPath::CSearchPath( void )
{
	m_Path = g_PathIDTable.AddString( "" );
	m_pDebugPath = "";

	m_storeId = 0;
	m_pPackFile = NULL;
	m_pPathIDInfo = NULL;
	m_bIsRemotePath = false;
	m_pPackedStore = NULL;
	m_bIsTrustedForPureServer = false;
}

const char *CBaseFileSystem::CSearchPath::GetDebugString() const
{
	if ( GetPackFile()  )
	{
		return GetPackFile()->m_ZipName;
	}
	#ifdef SUPPORT_PACKED_STORE
		if ( GetPackedStore()  )
		{
			return GetPackedStore()->FullPathName();
		}
	#endif
	return GetPathString();
}

bool CBaseFileSystem::CSearchPath::IsMapPath() const
{
	return GetPackFile()->m_bIsMapPath;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::CSearchPath::~CSearchPath( void )
{
	if ( m_pPackFile )
	{	
		m_pPackFile->Release();
	}
	if ( m_pPackedStore )
	{
		m_pPackedStore->Release();
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetFirst()
{
	if ( m_SearchPaths.Count() )
	{
		m_visits.Reset();
		m_iCurrent = -1;
		return GetNext();
	}
	return &m_EmptySearchPath;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetNext()
{
	CSearchPath *pSearchPath = NULL;

	for ( m_iCurrent++; m_iCurrent < m_SearchPaths.Count(); m_iCurrent++ )
	{
		pSearchPath = &m_SearchPaths[m_iCurrent];

		if ( m_PathTypeFilter == FILTER_CULLPACK && pSearchPath->GetPackFile() )
			continue;

		if ( m_PathTypeFilter == FILTER_CULLNONPACK && !pSearchPath->GetPackFile() )
			continue;

		if ( CBaseFileSystem::FilterByPathID( pSearchPath, m_pathID ) )
			continue;

		// 360 can optionally ignore a local search path in dvddev mode
		// ignoring a local search path falls through to its cloned remote path
		// map paths are exempt from this exclusion logic
		if ( IsX360() && ( m_DVDMode == DVDMODE_DEV ) && m_Filename[0] && !pSearchPath->m_bIsRemotePath )
		{
			bool bIsMapPath = pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath;
			if ( !bIsMapPath )
			{
				bool bIgnorePath = false;
				char szExcludePath[MAX_PATH];
				char szFilename[MAX_PATH];
				V_ComposeFileName( pSearchPath->GetPathString(), m_Filename, szFilename, sizeof( szFilename ) );
				for ( int i = 0; i < m_ExcludePaths.Count(); i++ )
				{
					if ( g_pFullFileSystem->String( m_ExcludePaths[i], szExcludePath, sizeof( szExcludePath ) ) )
					{
						if ( !V_strnicmp( szFilename, szExcludePath, strlen( szExcludePath ) ) )
						{
							bIgnorePath = true;
							break;
						}
					}
				}
				if ( bIgnorePath )
				{
					// filename matches exclusion path, skip it
					continue;
				}
			}
		}

		if ( !m_visits.MarkVisit( *pSearchPath ) )
			break;
	}

	if ( m_iCurrent < m_SearchPaths.Count() )
	{
		return pSearchPath;
	}

	return NULL;
}

void CBaseFileSystem::CSearchPathsIterator::CopySearchPaths( const CUtlVector<CSearchPath>	&searchPaths )
{
	m_SearchPaths = searchPaths;
	for ( int i = 0; i <  m_SearchPaths.Count(); i++ )
	{
		if ( m_SearchPaths[i].GetPackFile() )
		{
			m_SearchPaths[i].GetPackFile()->AddRef();
		}
		else if ( m_SearchPaths[i].GetPackedStore() )
		{
			m_SearchPaths[i].GetPackedStore()->AddRef();
		}
	}
}

//-----------------------------------------------------------------------------
// Purpose: Load/unload a DLL
//-----------------------------------------------------------------------------
CSysModule *CBaseFileSystem::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly )
{
	CHECK_DOUBLE_SLASHES( pFileName );
	CSysModule *pModule = NULL;

	LogFileAccess( pFileName );
	if ( !pPathID )
	{
		pPathID = "EXECUTABLE_PATH"; // default to the bin dir
	}

	char tempPathID[ MAX_PATH ];
	ParsePathID( pFileName, pPathID, tempPathID );

	CUtlSymbol lookup = g_PathIDTable.AddString( pPathID );

	// a pathID has been specified, find the first match in the path list
#ifndef ANDROID
	int c = m_SearchPaths.Count();
	for ( int i = 0; i < c; i++ )
	{
		// pak files don't have modules
		if ( m_SearchPaths[i].GetPackFile() )
			continue;

		if ( FilterByPathID( &m_SearchPaths[i], lookup ) )
			continue;

		Q_snprintf( tempPathID, sizeof(tempPathID), "%s%s", m_SearchPaths[i].GetPathString(), pFileName ); // append the path to this dir.
		pModule = Sys_LoadModule( tempPathID );
		if ( pModule ) 
		{
			// we found the binary in one of our search paths
			return pModule;
		}

#ifdef POSIX
		Q_snprintf( tempPathID, sizeof(tempPathID), "%slib%s", m_SearchPaths[i].GetPathString(), pFileName ); // append the path to this dir.
		pModule = Sys_LoadModule( tempPathID );
		if ( pModule )
			return pModule;
#endif
	}
#endif

	if( !pModule )
		pModule = Sys_LoadModule( pFileName );

	return pModule;
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CBaseFileSystem::UnloadModule( CSysModule *pModule )
{
	Sys_UnloadModule( pModule );
}

//-----------------------------------------------------------------------------
// Purpose: Adds a filesystem logging function
//-----------------------------------------------------------------------------
void CBaseFileSystem::AddLoggingFunc( FileSystemLoggingFunc_t logFunc )
{
	Assert(!m_LogFuncs.IsValidIndex(m_LogFuncs.Find(logFunc)));
	m_LogFuncs.AddToTail(logFunc);
}

//-----------------------------------------------------------------------------
// Purpose: Removes a filesystem logging function
//-----------------------------------------------------------------------------
void CBaseFileSystem::RemoveLoggingFunc( FileSystemLoggingFunc_t logFunc )
{
	m_LogFuncs.FindAndRemove(logFunc);
}

//-----------------------------------------------------------------------------
// Make sure that slashes are of the right kind and that there is a slash at the 
// end of the filename.
// WARNING!!: assumes that you have an extra byte allocated in the case that you need
// a slash at the end.
//-----------------------------------------------------------------------------
static void AddSeperatorAndFixPath( char *str )
{
	char *lastChar = &str[strlen( str ) - 1];
	if( *lastChar != CORRECT_PATH_SEPARATOR && *lastChar != INCORRECT_PATH_SEPARATOR )
	{
		lastChar[1] = CORRECT_PATH_SEPARATOR;
		lastChar[2] = '\0';
	}
	Q_FixSlashes( str );

	if ( IsX360() )
	{
		// 360 FS won't resolve any path with ../
		V_RemoveDotSlashes( str );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pFileName - 
// Output : FileNameHandle_t
//-----------------------------------------------------------------------------
FileNameHandle_t CBaseFileSystem::FindOrAddFileName( char const *pFileName )
{
	return m_FileNames.FindOrAddFileName( pFileName );
}

FileNameHandle_t CBaseFileSystem::FindFileName( char const *pFileName )
{
	return m_FileNames.FindFileName( pFileName );
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : handle - 
// Output : char const
//-----------------------------------------------------------------------------
bool CBaseFileSystem::String( const FileNameHandle_t& handle, char *buf, int buflen )
{
	return m_FileNames.String( handle, buf, buflen );
}

int	CBaseFileSystem::GetPathIndex( const FileNameHandle_t &handle )
{
	return m_FileNames.PathIndex(handle);
}

CBaseFileSystem::CPathIDInfo* CBaseFileSystem::FindOrAddPathIDInfo( const CUtlSymbol &id, int bByRequestOnly )
{
	for ( int i=0; i < m_PathIDInfos.Count(); i++ )
	{
		CBaseFileSystem::CPathIDInfo *pInfo = m_PathIDInfos[i];
		if ( pInfo->GetPathID() == id )
		{
			if ( bByRequestOnly != -1 )
			{
				pInfo->m_bByRequestOnly = (bByRequestOnly != 0);
			}
			return pInfo;
		}
	}

	// Add a new one.
	CBaseFileSystem::CPathIDInfo *pInfo = new CBaseFileSystem::CPathIDInfo;
	m_PathIDInfos.AddToTail( pInfo );
	pInfo->SetPathID( id );
	pInfo->m_bByRequestOnly = (bByRequestOnly == 1);
	return pInfo;
}
		

void CBaseFileSystem::MarkPathIDByRequestOnly( const char *pPathID, bool bRequestOnly )
{
	FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), bRequestOnly );
}

#if defined( TRACK_BLOCKING_IO )

void CBaseFileSystem::EnableBlockingFileAccessTracking( bool state )
{
	m_bBlockingFileAccessReportingEnabled = state;
}

bool CBaseFileSystem::IsBlockingFileAccessEnabled() const
{
	return m_bBlockingFileAccessReportingEnabled;
}

IBlockingFileItemList *CBaseFileSystem::RetrieveBlockingFileAccessInfo()
{
	Assert( m_pBlockingItems );
	return m_pBlockingItems;
}

void CBaseFileSystem::RecordBlockingFileAccess( bool synchronous, const FileBlockingItem& item )
{
	AUTO_LOCK( m_BlockingFileMutex );

	// Not tracking anything
	if ( !m_bBlockingFileAccessReportingEnabled )
		return;

	if ( synchronous && !m_bAllowSynchronousLogging && ( item.m_ItemType == FILESYSTEM_BLOCKING_SYNCHRONOUS ) )
		return;

	// Track it
	m_pBlockingItems->Add( item );
}

bool CBaseFileSystem::SetAllowSynchronousLogging( bool state )
{
	bool oldState = m_bAllowSynchronousLogging;
	m_bAllowSynchronousLogging = state;
	return oldState;
}

void CBaseFileSystem::BlockingFileAccess_EnterCriticalSection()
{
	m_BlockingFileMutex.Lock();
}

void CBaseFileSystem::BlockingFileAccess_LeaveCriticalSection()
{
	m_BlockingFileMutex.Unlock();
}

#endif // TRACK_BLOCKING_IO

bool CBaseFileSystem::GetFileTypeForFullPath( char const *pFullPath, wchar_t *buf, size_t bufSizeInBytes )
{
#if !defined( _X360 ) && !defined( POSIX )
	wchar_t wcharpath[512];
	::MultiByteToWideChar( CP_UTF8, 0, pFullPath, -1, wcharpath, sizeof( wcharpath ) / sizeof(wchar_t) );
	wcharpath[(sizeof( wcharpath ) / sizeof(wchar_t)) - 1] = L'\0';

	SHFILEINFOW info = { 0 };
	DWORD_PTR dwResult = SHGetFileInfoW( 
		wcharpath,
		0,
		&info,
		sizeof( info ),
		SHGFI_TYPENAME 
	);
	if ( dwResult )
	{
		wcsncpy( buf, info.szTypeName, ( bufSizeInBytes / sizeof( wchar_t  ) ) );
		buf[( bufSizeInBytes / sizeof( wchar_t ) ) - 1] = L'\0';
		return true;
	}
	else
#endif
	{
		char ext[32];
		Q_ExtractFileExtension( pFullPath, ext, sizeof( ext ) );
#ifdef POSIX		
		_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L"%s File", V_strupr( ext ) ); // Matches what Windows does
#else
		_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L".%S", ext );
#endif
		buf[( bufSizeInBytes / sizeof( wchar_t ) ) - 1] = L'\0';
	}
	return false;
}


bool CBaseFileSystem::GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign )
{
	if ( pOffsetAlign )
		*pOffsetAlign = 1;
	if ( pSizeAlign )
		*pSizeAlign = 1;
	if ( pBufferAlign )
		*pBufferAlign = 1;
	return false;
}

//-----------------------------------------------------------------------------

FileCacheHandle_t CBaseFileSystem::CreateFileCache( )
{
	return new CFileCacheObject( this );
}

//-----------------------------------------------------------------------------

void CBaseFileSystem::AddFilesToFileCache( FileCacheHandle_t cacheId, const char **ppFileNames, int nFileNames, const char *pPathID )
{
	// For now, assuming that we're only used with GAME.
	Assert( pPathID && Q_strcasecmp( pPathID, "GAME" ) == 0 );
	return static_cast< CFileCacheObject * >( cacheId )->AddFiles( ppFileNames, nFileNames );
}

//-----------------------------------------------------------------------------

bool CBaseFileSystem::IsFileCacheLoaded( FileCacheHandle_t cacheId )
{
	return static_cast< CFileCacheObject * >( cacheId )->IsReady();
}

//-----------------------------------------------------------------------------

void CBaseFileSystem::DestroyFileCache( FileCacheHandle_t cacheId )
{
	delete static_cast< CFileCacheObject * >( cacheId );
}

//-----------------------------------------------------------------------------

bool CBaseFileSystem::IsFileCacheFileLoaded( FileCacheHandle_t cacheId, const char* pFileName )
{
#ifdef _DEBUG
	CFileCacheObject* pFileCache = static_cast< CFileCacheObject * >( cacheId );
	bool bFileIsHeldByCache = false;
	{
		AUTO_LOCK( pFileCache->m_InfosMutex );
		for ( int i = 0; i < pFileCache->m_Infos.Count(); ++i )
		{
			if ( pFileCache->m_Infos[i]->pFileName && Q_strcmp( pFileCache->m_Infos[i]->pFileName, pFileName ) )
			{
				bFileIsHeldByCache = true;
				break;
			}
		}
	}
	Assert( bFileIsHeldByCache );
#endif

	AUTO_LOCK( m_MemoryFileMutex );
	return m_MemoryFileHash.Find( pFileName ) != m_MemoryFileHash.InvalidHandle();
}

//-----------------------------------------------------------------------------

bool CBaseFileSystem::RegisterMemoryFile( CMemoryFileBacking *pFile, CMemoryFileBacking **ppExistingFileWithRef )
{
	Assert( pFile->m_pFS == static_cast< IFileSystem* >( this ) );
	Assert( pFile->m_pFileName && pFile->m_pFileName[0] );
	AUTO_LOCK( m_MemoryFileMutex );
	
	CMemoryFileBacking *pInTable = m_MemoryFileHash[ m_MemoryFileHash.Insert( pFile->m_pFileName, pFile ) ];
	pInTable->m_nRegistered++;
	pInTable->AddRef(); // either for table or for ppExistingFileWithRef return

	if ( pFile == pInTable )
	{
		Assert( pInTable->m_nRegistered == 1 );
		*ppExistingFileWithRef = NULL;
		return true;
	}
	else
	{
		Assert( pInTable->m_nRegistered > 1 );
		*ppExistingFileWithRef = pInTable;
		return false;
	}
}

//-----------------------------------------------------------------------------

void CBaseFileSystem::UnregisterMemoryFile( CMemoryFileBacking *pFile )
{
	Assert( pFile->m_pFS == static_cast< IFileSystem* >( this ) && pFile->m_nRegistered > 0 );
	bool bRelease = false;
	{
		AUTO_LOCK( m_MemoryFileMutex );
		pFile->m_nRegistered--;
		if ( pFile->m_nRegistered == 0 )
		{
			m_MemoryFileHash.Remove( pFile->m_pFileName );
			bRelease = true;
		}
	}
	// Release potentially a complex op, prefer to perform it outside of mutex.
	if (bRelease)
	{
		pFile->Release();
	}
}


//-----------------------------------------------------------------------------
// Purpose: Constructs a file handle
// Input  : base file system handle
// Output : 
//-----------------------------------------------------------------------------
CFileHandle::CFileHandle( CBaseFileSystem* fs )
{
	Init( fs );
}

CFileHandle::~CFileHandle()
{
	Assert( IsValid() );
#if !defined( _RETAIL )
	delete[] m_pszTrueFileName;
#endif

	if ( m_pPackFileHandle )
	{
		delete m_pPackFileHandle;
		m_pPackFileHandle = NULL;
	}

	if ( m_pFile )
	{
		m_fs->Trace_FClose( m_pFile );
		m_pFile = NULL;
	}

	m_nMagic = FREE_MAGIC;
}

void CFileHandle::Init( CBaseFileSystem *fs )
{
	m_nMagic = MAGIC;
	m_pFile = NULL;
	m_nLength = 0;
	m_type = FT_NORMAL;		
	m_pPackFileHandle = NULL;

	m_fs = fs;

#if !defined( _RETAIL )
	m_pszTrueFileName = 0;
#endif
}

bool CFileHandle::IsValid()
{
	return ( m_nMagic == MAGIC );
}

int CFileHandle::GetSectorSize()
{
	Assert( IsValid() );

	if ( m_pFile )
	{
		return m_fs->FS_GetSectorSize( m_pFile );
	}
	else if ( m_pPackFileHandle )
	{
		return m_pPackFileHandle->GetSectorSize();
	}
	else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT )
	{
		return 1;
	}
	else
	{
		return -1;
	}
}

bool CFileHandle::IsOK()
{
#if defined( SUPPORT_PACKED_STORE )
	if ( m_VPKHandle )
	{
		return true;
	}
#endif
	if ( m_pFile )
	{
		return ( IsValid() && m_fs->FS_ferror( m_pFile ) == 0 );
	}
	else if ( m_pPackFileHandle || m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT )
	{
		return IsValid();
	}

	m_fs->Warning( FILESYSTEM_WARNING, "FS:  Tried to IsOk NULL file pointer inside valid file handle!\n" );
	return false;
}

void CFileHandle::Flush()
{
	Assert( IsValid() );

	if ( m_pFile )
	{
		m_fs->FS_fflush( m_pFile );
	}
}

void CFileHandle::SetBufferSize( int nBytes )
{
	Assert( IsValid() );

	if ( m_pFile )
	{
		m_fs->FS_setbufsize( m_pFile, nBytes );
	}
	else if ( m_pPackFileHandle )
	{
		m_pPackFileHandle->SetBufferSize( nBytes );
	}
}

int CFileHandle::Read( void* pBuffer, int nLength )
{
	Assert( IsValid() );
	return Read( pBuffer, -1, nLength );
}

int CFileHandle::Read( void* pBuffer, int nDestSize, int nLength )
{
	Assert( IsValid() );

#if defined( SUPPORT_PACKED_STORE )
	if ( m_VPKHandle )
	{
		if ( nDestSize >= 0 )
			nLength = MIN( nLength, nDestSize );
		return m_VPKHandle.Read( pBuffer, nLength );
	}
#endif
	// Is this a regular file or a pack file?  
	if ( m_pFile )
	{
		return m_fs->FS_fread( pBuffer, nDestSize, nLength, m_pFile );
	}
	else if ( m_pPackFileHandle )
	{
		// Pack file handle handles clamping all the reads:
		return m_pPackFileHandle->Read( pBuffer, nDestSize, nLength );
	}
	else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT )
	{
		return static_cast< CMemoryFileHandle* >( this )->Read( pBuffer, nDestSize, nLength );
	}

	return 0;
}

int CFileHandle::Write( const void* pBuffer, int nLength )
{
	tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ );
	if( ThreadInMainThread() )
	{
		tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, nLength, "FileBytesWrite" );
	}

	Assert( IsValid() );

	if ( !m_pFile )
	{
		m_fs->Warning( FILESYSTEM_WARNING, "FS:  Tried to Write NULL file pointer inside valid file handle!\n" );
		return 0;
	}

	size_t nBytesWritten = m_fs->FS_fwrite( (void*)pBuffer, nLength, m_pFile  );

	m_fs->Trace_FWrite(nBytesWritten,m_pFile);

	return nBytesWritten;
}

int CFileHandle::Seek( int64 nOffset, int nWhence )
{
	Assert( IsValid() );

#if defined( SUPPORT_PACKED_STORE )
	if ( m_VPKHandle )
	{
		return m_VPKHandle.Seek( nOffset, nWhence );
	}
#endif
	if ( m_pFile )
	{
		m_fs->FS_fseek( m_pFile, nOffset, nWhence );
		// TODO - FS_fseek should return the resultant offset
		return 0;
	}
	else if ( m_pPackFileHandle )
	{
		return m_pPackFileHandle->Seek( nOffset, nWhence );
	}
	else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT )
	{
		return static_cast< CMemoryFileHandle* >( this )->Seek( nOffset, nWhence );
	}

	return -1;
}

int CFileHandle::Tell()
{
	Assert( IsValid() );

#if defined( SUPPORT_PACKED_STORE )
	if ( m_VPKHandle )
	{
		return m_VPKHandle.Tell();
	}
#endif
	if ( m_pFile )
	{
		return m_fs->FS_ftell( m_pFile );
	}
	else if ( m_pPackFileHandle )
	{
		return m_pPackFileHandle->Tell();
	}
	else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT )
	{
		return static_cast< CMemoryFileHandle* >( this )->Tell();
	}

	return -1;
}

int CFileHandle::Size()
{
	Assert( IsValid() );

	int nReturnedSize = -1;

#if defined( SUPPORT_PACKED_STORE )
	if ( m_VPKHandle )
	{
		return m_VPKHandle.m_nFileSize;
	}
#endif
	if ( m_pFile  )
	{
		nReturnedSize = m_nLength; 
	}
	else if ( m_pPackFileHandle )
	{
		nReturnedSize = m_pPackFileHandle->Size();
	}
	else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT )
	{
		return static_cast< CMemoryFileHandle* >( this )->Size();
	}

	return nReturnedSize;
}

int64 CFileHandle::AbsoluteBaseOffset()
{
	Assert( IsValid() );

	if ( !m_pFile && m_pPackFileHandle )
	{
		return m_pPackFileHandle->AbsoluteBaseOffset();
	}
	else
	{
		return 0;
	}
}

bool CFileHandle::EndOfFile()
{
	Assert( IsValid() );

	return ( Tell() >= Size() );
}

//-----------------------------------------------------------------------------

int CMemoryFileHandle::Read( void* pBuffer, int nDestSize, int nLength )
{
	nLength = min( nLength, (int) m_nLength - m_nPosition );
	if ( nLength > 0 )
	{
		Assert( m_nPosition >= 0 );
		memcpy( pBuffer, m_pBacking->m_pData + m_nPosition, nLength );
		m_nPosition += nLength;
		return nLength;
	}
	if ( m_nPosition >= (int) m_nLength )
	{
		return -1;
	}
	return 0;
}

int CMemoryFileHandle::Seek( int64 nOffset64, int nWhence )
{
	if ( nWhence == SEEK_SET )
	{
		m_nPosition = (int) clamp( nOffset64, 0ll, m_nLength );
	}
	else if ( nWhence == SEEK_CUR )
	{
		m_nPosition += (int) clamp( nOffset64, -(int64)m_nPosition, m_nLength - m_nPosition );
	}
	else if ( nWhence == SEEK_END )
	{
		m_nPosition = (int) m_nLength + (int) clamp( nOffset64, -m_nLength, 0ll );
	}
	return m_nPosition;
}

//-----------------------------------------------------------------------------

CBaseFileSystem::CFileCacheObject::CFileCacheObject( CBaseFileSystem* pFS )
:	m_pFS( pFS )
{
}

void CBaseFileSystem::CFileCacheObject::AddFiles( const char **ppFileNames, int nFileNames )
{
	CUtlVector< Info_t* > infos;
	infos.SetCount( nFileNames );
	for ( int i = 0; i < nFileNames; ++i )
	{
		infos[i] = new Info_t;
		Info_t &info = *infos[i];
		info.pFileName = strdup( ppFileNames[i] );
		V_FixSlashes( (char*) info.pFileName );
#ifdef _WIN32
		Q_strlower( (char*) info.pFileName );
#endif
		info.hIOAsync = NULL;
		info.pBacking = NULL;
		info.pOwner = NULL;
	}

	AUTO_LOCK( m_InfosMutex );
	int offset = m_Infos.AddMultipleToTail( nFileNames, infos.Base() );

	m_nPending += nFileNames;
	ProcessNewEntries(offset);
}

void CBaseFileSystem::CFileCacheObject::ProcessNewEntries( int start )
{
	for ( int i = start; i < m_Infos.Count(); ++i )
	{
		Info_t &info = *m_Infos[i];
		if ( !info.pOwner )
		{
			info.pOwner = this;

			// NOTE: currently only caching files with GAME pathID
			FileAsyncRequest_t request;
			request.pszFilename = info.pFileName;
			request.pszPathID = "GAME";
			request.flags = FSASYNC_FLAGS_ALLOCNOFREE;
			request.pfnCallback = &IOCallback;
			request.pContext = &info;
			if ( m_pFS->AsyncRead( request, &info.hIOAsync ) != FSASYNC_OK )
			{
				--m_nPending;
			}
		}
	}
}

void CBaseFileSystem::CFileCacheObject::IOCallback( const FileAsyncRequest_t &request, int nBytesRead, FSAsyncStatus_t err )
{
	Assert( request.pContext );
	Info_t &info = *(Info_t *)request.pContext;

	CMemoryFileBacking *pBacking = new CMemoryFileBacking( info.pOwner->m_pFS );
	pBacking->m_pData = NULL;
	pBacking->m_pFileName = info.pFileName;
	pBacking->m_nLength = ( err == FSASYNC_OK && nBytesRead == 0 ) ? 0 : -1;

	if ( request.pData )
	{
		if ( err == FSASYNC_OK && nBytesRead > 0 )
		{
			pBacking->m_pData = (const char*) request.pData; // transfer data ownership
			pBacking->m_nLength = nBytesRead;
		}
		else
		{
			info.pOwner->m_pFS->FreeOptimalReadBuffer( request.pData );
		}
	}

	CMemoryFileBacking *pExistingBacking = NULL;
	if ( !info.pOwner->m_pFS->RegisterMemoryFile( pBacking, &pExistingBacking ) )
	{
		// Someone already registered this file
		info.pFileName = pExistingBacking->m_pFileName;
		pBacking->Release();
		pBacking = pExistingBacking;
	}

	//DevWarning("preload %s  %d\n", request.pszFilename, pBacking->m_nLength);

	info.pBacking = pBacking;
	info.pOwner->m_nPending--;
}

CBaseFileSystem::CFileCacheObject::~CFileCacheObject()
{
	AUTO_LOCK( m_InfosMutex );
	for ( int i = 0; i < m_Infos.Count(); ++i )
	{
		Info_t& info = *m_Infos[i];
		if ( info.hIOAsync )
		{
			// job is guaranteed to not be running after abort
			m_pFS->AsyncAbort( info.hIOAsync );
			m_pFS->AsyncRelease( info.hIOAsync );
		}

		if ( info.pBacking )
		{
			m_pFS->UnregisterMemoryFile( info.pBacking );
			info.pBacking->Release();
		}
		else
		{
			free( (char*) info.pFileName );
		}
		
		delete m_Infos[i];
	}
	Assert( m_nPending == 0 );
}