summaryrefslogtreecommitdiff
path: root/filesystem/basefilesystem.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /filesystem/basefilesystem.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'filesystem/basefilesystem.cpp')
-rw-r--r--filesystem/basefilesystem.cpp5807
1 files changed, 5807 insertions, 0 deletions
diff --git a/filesystem/basefilesystem.cpp b/filesystem/basefilesystem.cpp
new file mode 100644
index 0000000..b699836
--- /dev/null
+++ b/filesystem/basefilesystem.cpp
@@ -0,0 +1,5807 @@
+//========= 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
+
+// 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() {}
+ 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
+//-----------------------------------------------------------------------------
+long 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;
+ }
+#ifdef LINUX
+ 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
+//-----------------------------------------------------------------------------
+long 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() )
+ {
+ long 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 0L;
+}
+
+long CBaseFileSystem::GetPathTime( const char *pFileName, const char *pPathID )
+{
+ VPROF_BUDGET( "CBaseFileSystem::GetFileTime", 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
+
+ long pathTime = 0L;
+ for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() )
+ {
+ long 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 )
+{
+ // 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
+}
+
+
+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, long 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 LINUX
+ if( buf.st_mode & S_IWRITE )
+#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 LINUX
+ if ( buf.st_mode & S_IWRITE )
+#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 );
+#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 );
+
+ 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
+ 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.
+ CSysModule *pModule = Sys_LoadModule( tempPathID );
+ if ( pModule )
+ {
+ // we found the binary in one of our search paths
+ return pModule;
+ }
+ }
+
+ // couldn't load it from any of the search paths, let LoadLibrary try
+ return Sys_LoadModule( pFileName );
+}
+
+//-----------------------------------------------------------------------------
+// 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 );
+}