You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
5831 lines
160 KiB
5831 lines
160 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
#ifdef POSIX |
|
#define _snwprintf swprintf |
|
#endif |
|
|
|
#include "basefilesystem.h" |
|
#include "tier0/vprof.h" |
|
#include "tier1/characterset.h" |
|
#include "tier1/utlbuffer.h" |
|
#include "tier1/convar.h" |
|
#include "tier1/KeyValues.h" |
|
#include "tier0/icommandline.h" |
|
#include "generichash.h" |
|
#include "tier1/utllinkedlist.h" |
|
#include "filesystem/IQueuedLoader.h" |
|
#include "tier2/tier2.h" |
|
#include "zip_utils.h" |
|
#include "packfile.h" |
|
#ifdef _X360 |
|
#include "xbox/xbox_launch.h" |
|
#endif |
|
|
|
#ifndef DEDICATED |
|
#include "keyvaluescompiler.h" |
|
#endif |
|
#include "ifilelist.h" |
|
|
|
#ifdef IS_WINDOWS_PC |
|
// Needed for getting file type string |
|
#define WIN32_LEAN_AND_MEAN |
|
#include <shellapi.h> |
|
#endif |
|
|
|
#if defined( _X360 ) |
|
#include "xbox\xbox_win32stubs.h" |
|
#undef GetCurrentDirectory |
|
#endif |
|
|
|
#include <time.h> |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
|
|
#pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list |
|
|
|
|
|
ConVar fs_report_sync_opens( "fs_report_sync_opens", "0", 0, "0:Off, 1:Blocking only, 2:All" ); |
|
ConVar fs_warning_mode( "fs_warning_mode", "0", 0, "0:Off, 1:Warn main thread, 2:Warn other threads" ); |
|
|
|
#define BSPOUTPUT 0 // bsp output flag -- determines type of fs_log output to generate |
|
|
|
static void AddSeperatorAndFixPath( char *str ); |
|
|
|
// Case-insensitive symbol table for path IDs. |
|
CUtlSymbolTableMT g_PathIDTable( 0, 32, true ); |
|
|
|
int g_iNextSearchPathID = 1; |
|
|
|
// This can be used to easily fix a filename on the stack. |
|
#ifdef DBGFLAG_ASSERT |
|
// Look for cases like materials\\blah.vmt. |
|
static bool V_CheckDoubleSlashes( const char *pStr ) |
|
{ |
|
int len = V_strlen( pStr ); |
|
|
|
for ( int i=1; i < len-1; i++ ) |
|
{ |
|
if ( (pStr[i] == '/' || pStr[i] == '\\') && (pStr[i+1] == '/' || pStr[i+1] == '\\') ) |
|
{ |
|
Msg( "Double slashes found in path '%s'\n", pStr ); |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
#define CHECK_DOUBLE_SLASHES( x ) V_CheckDoubleSlashes(x) |
|
#else |
|
#define CHECK_DOUBLE_SLASHES( x ) |
|
#endif |
|
|
|
static void LogFileOpen( const char *vpk, const char *pFilename, const char *pAbsPath ) |
|
{ |
|
static const char *mode = NULL; |
|
|
|
// Figure out if we should be doing this at all, the first time we are acalled |
|
if ( mode == NULL ) |
|
{ |
|
if ( CommandLine()->FindParm( "-log_opened_files" ) ) |
|
mode = "wt"; |
|
else |
|
mode = ""; |
|
} |
|
if ( *mode == '\0' ) |
|
return; |
|
|
|
// Open file for write or append |
|
FILE *f = fopen( "opened_files.txt", mode ); |
|
Assert( f ); |
|
if ( f ) |
|
{ |
|
fprintf( f, "%s, %s, %s\n", vpk, pFilename, pAbsPath ); |
|
//fprintf( f, "%s\n", pFilename ); |
|
fclose(f); |
|
|
|
// If this was the first time, switch from write to append for further writes |
|
mode = "at"; |
|
} |
|
} |
|
|
|
|
|
static CBaseFileSystem *g_pBaseFileSystem; |
|
CBaseFileSystem *BaseFileSystem() |
|
{ |
|
return g_pBaseFileSystem; |
|
} |
|
|
|
ConVar filesystem_buffer_size( "filesystem_buffer_size", "0", 0, "Size of per file buffers. 0 for none" ); |
|
|
|
#if defined( TRACK_BLOCKING_IO ) |
|
|
|
// If we hit more than 100 items in a frame, we're probably doing a level load... |
|
#define MAX_ITEMS 100 |
|
|
|
class CBlockingFileItemList : public IBlockingFileItemList |
|
{ |
|
public: |
|
CBlockingFileItemList( CBaseFileSystem *fs ) |
|
: |
|
m_pFS( fs ), |
|
m_bLocked( false ) |
|
{ |
|
} |
|
|
|
// You can't call any of the below calls without calling these methods!!!! |
|
virtual void LockMutex() |
|
{ |
|
Assert( !m_bLocked ); |
|
if ( m_bLocked ) |
|
return; |
|
m_bLocked = true; |
|
m_pFS->BlockingFileAccess_EnterCriticalSection(); |
|
} |
|
|
|
virtual void UnlockMutex() |
|
{ |
|
Assert( m_bLocked ); |
|
if ( !m_bLocked ) |
|
return; |
|
|
|
m_pFS->BlockingFileAccess_LeaveCriticalSection(); |
|
m_bLocked = false; |
|
} |
|
|
|
virtual int First() const |
|
{ |
|
if ( !m_bLocked ) |
|
{ |
|
Error( "CBlockingFileItemList::First() w/o calling EnterCriticalSectionFirst!" ); |
|
} |
|
return m_Items.Head(); |
|
} |
|
|
|
virtual int Next( int i ) const |
|
{ |
|
if ( !m_bLocked ) |
|
{ |
|
Error( "CBlockingFileItemList::Next() w/o calling EnterCriticalSectionFirst!" ); |
|
} |
|
return m_Items.Next( i ); |
|
} |
|
|
|
virtual int InvalidIndex() const |
|
{ |
|
return m_Items.InvalidIndex(); |
|
} |
|
|
|
virtual const FileBlockingItem& Get( int index ) const |
|
{ |
|
if ( !m_bLocked ) |
|
{ |
|
Error( "CBlockingFileItemList::Get( %d ) w/o calling EnterCriticalSectionFirst!", index ); |
|
} |
|
return m_Items[ index ]; |
|
} |
|
|
|
virtual void Reset() |
|
{ |
|
if ( !m_bLocked ) |
|
{ |
|
Error( "CBlockingFileItemList::Reset() w/o calling EnterCriticalSectionFirst!" ); |
|
} |
|
m_Items.RemoveAll(); |
|
} |
|
|
|
void Add( const FileBlockingItem& item ) |
|
{ |
|
// Ack, should use a linked list probably... |
|
while ( m_Items.Count() > MAX_ITEMS ) |
|
{ |
|
m_Items.Remove( m_Items.Head() ); |
|
} |
|
m_Items.AddToTail( item ); |
|
} |
|
|
|
|
|
private: |
|
CUtlLinkedList< FileBlockingItem, unsigned short > m_Items; |
|
CBaseFileSystem *m_pFS; |
|
bool m_bLocked; |
|
}; |
|
#endif |
|
|
|
CUtlSymbol CBaseFileSystem::m_GamePathID; |
|
CUtlSymbol CBaseFileSystem::m_BSPPathID; |
|
DVDMode_t CBaseFileSystem::m_DVDMode; |
|
CUtlVector< FileNameHandle_t > CBaseFileSystem::m_ExcludePaths; |
|
|
|
|
|
class CStoreIDEntry |
|
{ |
|
public: |
|
CStoreIDEntry() = default; |
|
CStoreIDEntry( const char *pPathIDStr, int storeID ) |
|
{ |
|
m_PathIDString = pPathIDStr; |
|
m_StoreID = storeID; |
|
} |
|
|
|
public: |
|
CUtlSymbol m_PathIDString; |
|
int m_StoreID; |
|
}; |
|
|
|
#if 0 |
|
static CStoreIDEntry* FindPrevFileByStoreID( CUtlDict< CUtlVector<CStoreIDEntry>* ,int> &filesByStoreID, const char *pFilename, const char *pPathIDStr, int foundStoreID ) |
|
{ |
|
int iEntry = filesByStoreID.Find( pFilename ); |
|
if ( iEntry == filesByStoreID.InvalidIndex() ) |
|
{ |
|
CUtlVector<CStoreIDEntry> *pList = new CUtlVector<CStoreIDEntry>; |
|
pList->AddToTail( CStoreIDEntry(pPathIDStr, foundStoreID) ); |
|
filesByStoreID.Insert( pFilename, pList ); |
|
return NULL; |
|
} |
|
else |
|
{ |
|
// Now is there a previous entry with a different path ID string and the same store ID? |
|
CUtlVector<CStoreIDEntry> *pList = filesByStoreID[iEntry]; |
|
for ( int i=0; i < pList->Count(); i++ ) |
|
{ |
|
CStoreIDEntry &entry = pList->Element( i ); |
|
if ( entry.m_StoreID == foundStoreID && V_stricmp( entry.m_PathIDString.String(), pPathIDStr ) != 0 ) |
|
return &entry; |
|
} |
|
return NULL; |
|
} |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
class CBaseFileSystem::CFileCacheObject |
|
{ |
|
public: |
|
CFileCacheObject( CBaseFileSystem* pFS ); |
|
~CFileCacheObject(); |
|
void AddFiles( const char **ppFileNames, int nFileNames ); |
|
bool IsReady() const { return m_nPending == 0; } |
|
|
|
static void IOCallback( const FileAsyncRequest_t &request, int nBytesRead, FSAsyncStatus_t err ); |
|
|
|
struct Info_t |
|
{ |
|
const char* pFileName; |
|
FSAsyncControl_t hIOAsync; |
|
CMemoryFileBacking* pBacking; |
|
CFileCacheObject* pOwner; |
|
}; |
|
|
|
CBaseFileSystem* m_pFS; |
|
CInterlockedInt m_nPending; |
|
CThreadFastMutex m_InfosMutex; |
|
CUtlVector< Info_t* > m_Infos; |
|
|
|
private: |
|
void ProcessNewEntries( int start ); |
|
|
|
CFileCacheObject( const CFileCacheObject& ); // not implemented |
|
CFileCacheObject & operator=(const CFileCacheObject& ); // not implemented |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// constructor |
|
//----------------------------------------------------------------------------- |
|
|
|
CBaseFileSystem::CBaseFileSystem() |
|
: m_FileTracker2( this ) |
|
{ |
|
g_pBaseFileSystem = this; |
|
g_pFullFileSystem = this; |
|
|
|
m_WhitelistFileTrackingEnabled = -1; |
|
|
|
// If this changes then FileNameHandleInternal_t/FileNameHandle_t needs to be fixed!!! |
|
Assert( sizeof( CUtlSymbol ) == sizeof( short ) ); |
|
|
|
// Clear out statistics |
|
memset( &m_Stats, 0, sizeof(m_Stats) ); |
|
|
|
m_fwLevel = FILESYSTEM_WARNING_REPORTUNCLOSED; |
|
m_pfnWarning = NULL; |
|
m_pLogFile = NULL; |
|
m_bOutputDebugString = false; |
|
m_WhitelistSpewFlags = 0; |
|
m_DirtyDiskReportFunc = NULL; |
|
m_pPureServerWhitelist = NULL; |
|
|
|
m_pThreadPool = NULL; |
|
#if defined( TRACK_BLOCKING_IO ) |
|
m_pBlockingItems = new CBlockingFileItemList( this ); |
|
m_bBlockingFileAccessReportingEnabled = false; |
|
m_bAllowSynchronousLogging = true; |
|
#endif |
|
|
|
m_iMapLoad = 0; |
|
|
|
Q_memset( m_PreloadData, 0, sizeof( m_PreloadData ) ); |
|
|
|
// allows very specifc constrained behavior |
|
m_DVDMode = DVDMODE_OFF; |
|
if ( IsX360() ) |
|
{ |
|
if ( CommandLine()->FindParm( "-dvd" ) ) |
|
{ |
|
m_DVDMode = DVDMODE_STRICT; |
|
} |
|
else if ( CommandLine()->FindParm( "-dvddev" ) ) |
|
{ |
|
m_DVDMode = DVDMODE_DEV; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::~CBaseFileSystem() |
|
{ |
|
m_PathIDInfos.PurgeAndDeleteElements(); |
|
#if defined( TRACK_BLOCKING_IO ) |
|
delete m_pBlockingItems; |
|
#endif |
|
|
|
// Free the whitelist. |
|
RegisterFileWhitelist( NULL, NULL ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Methods of IAppSystem |
|
//----------------------------------------------------------------------------- |
|
void *CBaseFileSystem::QueryInterface( const char *pInterfaceName ) |
|
{ |
|
// We also implement the IMatSystemSurface interface |
|
if (!Q_strncmp( pInterfaceName, BASEFILESYSTEM_INTERFACE_VERSION, Q_strlen(BASEFILESYSTEM_INTERFACE_VERSION) + 1)) |
|
return (IBaseFileSystem*)this; |
|
|
|
return NULL; |
|
} |
|
|
|
InitReturnVal_t CBaseFileSystem::Init() |
|
{ |
|
InitReturnVal_t nRetVal = BaseClass::Init(); |
|
if ( nRetVal != INIT_OK ) |
|
return nRetVal; |
|
|
|
// This is a special tag to allow iterating just the BSP file, it doesn't show up in the list per se, but gets converted to "GAME" in the filter function |
|
m_BSPPathID = g_PathIDTable.AddString( "BSP" ); |
|
m_GamePathID = g_PathIDTable.AddString( "GAME" ); |
|
|
|
if ( getenv( "fs_debug" ) ) |
|
{ |
|
m_bOutputDebugString = true; |
|
} |
|
|
|
const char *logFileName = CommandLine()->ParmValue( "-fs_log" ); |
|
if( logFileName ) |
|
{ |
|
m_pLogFile = fopen( logFileName, "w" ); // STEAM OK |
|
if ( !m_pLogFile ) |
|
return INIT_FAILED; |
|
fprintf( m_pLogFile, "@echo off\n" ); |
|
fprintf( m_pLogFile, "setlocal\n" ); |
|
const char *fs_target = CommandLine()->ParmValue( "-fs_target" ); |
|
if( fs_target ) |
|
{ |
|
fprintf( m_pLogFile, "set fs_target=\"%s\"\n", fs_target ); |
|
} |
|
fprintf( m_pLogFile, "if \"%%fs_target%%\" == \"\" goto error\n" ); |
|
fprintf( m_pLogFile, "@echo on\n" ); |
|
} |
|
|
|
InitAsync(); |
|
|
|
if ( IsX360() && m_DVDMode == DVDMODE_DEV ) |
|
{ |
|
// exclude paths are valid ony in dvddev mode |
|
char szExcludeFile[MAX_PATH]; |
|
const char *pRemotePath = CommandLine()->ParmValue( "-remote" ); |
|
const char *pBasePath = CommandLine()->ParmValue( "-basedir" ); |
|
if ( pRemotePath && pBasePath ) |
|
{ |
|
// the optional exclude path file only exists at the remote path |
|
V_ComposeFileName( pRemotePath, "xbox_exclude_paths.txt", szExcludeFile, sizeof( szExcludeFile ) ); |
|
|
|
// populate the exclusion list |
|
CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); |
|
if ( ReadFile( szExcludeFile, NULL, buf, 0, 0 ) ) |
|
{ |
|
characterset_t breakSet; |
|
CharacterSetBuild( &breakSet, "" ); |
|
char szPath[MAX_PATH]; |
|
char szToken[MAX_PATH]; |
|
for ( ;; ) |
|
{ |
|
int nTokenSize = buf.ParseToken( &breakSet, szToken, sizeof( szToken ) ); |
|
if ( nTokenSize <= 0 ) |
|
{ |
|
break; |
|
} |
|
|
|
char *pToken = szToken; |
|
if ( pToken[0] == '\\' ) |
|
{ |
|
// skip past possible initial seperator |
|
pToken++; |
|
} |
|
|
|
V_ComposeFileName( pBasePath, pToken, szPath, sizeof( szPath ) ); |
|
V_AppendSlash( szPath, sizeof( szPath ) ); |
|
|
|
FileNameHandle_t hFileName = FindOrAddFileName( szPath ); |
|
if ( m_ExcludePaths.Find( hFileName ) == -1 ) |
|
{ |
|
m_ExcludePaths.AddToTail( hFileName ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
return INIT_OK; |
|
} |
|
|
|
void CBaseFileSystem::Shutdown() |
|
{ |
|
ShutdownAsync(); |
|
m_FileTracker2.ShutdownAsync(); |
|
|
|
#ifndef _X360 |
|
if( m_pLogFile ) |
|
{ |
|
if( CommandLine()->FindParm( "-fs_logbins" ) >= 0 ) |
|
{ |
|
char cwd[MAX_FILEPATH]; |
|
getcwd( cwd, MAX_FILEPATH-1 ); |
|
fprintf( m_pLogFile, "set binsrc=\"%s\"\n", cwd ); |
|
fprintf( m_pLogFile, "mkdir \"%%fs_target%%\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2.exe\" \"%%fs_target%%\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2.dat\" \"%%fs_target%%\"\n" ); |
|
fprintf( m_pLogFile, "mkdir \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\*.asi\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\materialsystem.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\shaderapidx9.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\filesystem_stdio.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\soundemittersystem.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\stdshader*.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\shader_nv*.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\launcher.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\engine.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\mss32.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\tier0.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vgui2.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vguimatsurface.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\voice_miles.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vphysics.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vstdlib.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\studiorender.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\bin\\vaudio_miles.dll\" \"%%fs_target%%\\bin\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2\\resource\\*.ttf\" \"%%fs_target%%\\hl2\\resource\"\n" ); |
|
fprintf( m_pLogFile, "copy \"%%binsrc%%\\hl2\\bin\\gameui.dll\" \"%%fs_target%%\\hl2\\bin\"\n" ); |
|
} |
|
fprintf( m_pLogFile, "goto done\n" ); |
|
fprintf( m_pLogFile, ":error\n" ); |
|
fprintf( m_pLogFile, "echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n" ); |
|
fprintf( m_pLogFile, "echo ERROR: must set fs_target=targetpath (ie. \"set fs_target=u:\\destdir\")!\n" ); |
|
fprintf( m_pLogFile, "echo !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\"\n" ); |
|
fprintf( m_pLogFile, ":done\n" ); |
|
fclose( m_pLogFile ); // STEAM OK |
|
} |
|
#endif |
|
|
|
UnloadCompiledKeyValues(); |
|
|
|
RemoveAllSearchPaths(); |
|
Trace_DumpUnclosedFiles(); |
|
BaseClass::Shutdown(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Computes a full write path |
|
//----------------------------------------------------------------------------- |
|
inline void CBaseFileSystem::ComputeFullWritePath( char* pDest, int maxlen, const char *pRelativePath, const char *pWritePathID ) |
|
{ |
|
Q_strncpy( pDest, GetWritePath( pRelativePath, pWritePathID ), maxlen ); |
|
Q_strncat( pDest, pRelativePath, maxlen, COPY_ALL_CHARACTERS ); |
|
Q_FixSlashes( pDest ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : src1 - |
|
// src2 - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::OpenedFileLessFunc( COpenedFile const& src1, COpenedFile const& src2 ) |
|
{ |
|
return src1.m_pFile < src2.m_pFile; |
|
} |
|
|
|
|
|
void CBaseFileSystem::InstallDirtyDiskReportFunc( FSDirtyDiskReportFunc_t func ) |
|
{ |
|
m_DirtyDiskReportFunc = func; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *fullpath - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::LogAccessToFile( char const *accesstype, char const *fullpath, char const *options ) |
|
{ |
|
LOCAL_THREAD_LOCK(); |
|
|
|
if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES && !V_stristr( fullpath, "console.log" ) ) |
|
{ |
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS%s: %s %s (%.3f)\n", ThreadInMainThread() ? "" : "[a]", accesstype, fullpath, Plat_FloatTime() ); |
|
} |
|
|
|
int c = m_LogFuncs.Count(); |
|
if ( !c ) |
|
return; |
|
|
|
for ( int i = 0; i < c; ++i ) |
|
{ |
|
( m_LogFuncs[ i ] )( fullpath, options ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *filename - |
|
// *options - |
|
// Output : FILE |
|
//----------------------------------------------------------------------------- |
|
FILE *CBaseFileSystem::Trace_FOpen( const char *filenameT, const char *options, unsigned flags, int64 *size ) |
|
{ |
|
AUTOBLOCKREPORTER_FN( Trace_FOpen, this, true, filenameT, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_OPEN ); |
|
|
|
char filename[MAX_PATH]; |
|
|
|
FixUpPath ( filenameT, filename, sizeof( filename ) ); |
|
|
|
FILE *fp = FS_fopen( filename, options, flags, size ); |
|
|
|
if ( fp ) |
|
{ |
|
if ( options[0] == 'r' ) |
|
{ |
|
FS_setbufsize(fp, filesystem_buffer_size.GetInt() ); |
|
} |
|
else |
|
{ |
|
FS_setbufsize(fp, 32*1024 ); |
|
} |
|
|
|
AUTO_LOCK( m_OpenedFilesMutex ); |
|
COpenedFile file; |
|
|
|
file.SetName( filename ); |
|
file.m_pFile = fp; |
|
|
|
m_OpenedFiles.AddToTail( file ); |
|
|
|
LogAccessToFile( "open", filename, options ); |
|
} |
|
|
|
return fp; |
|
} |
|
|
|
void CBaseFileSystem::GetFileNameForHandle( FileHandle_t handle, char *buf, size_t buflen ) |
|
{ |
|
V_strncpy( buf, "Unknown", buflen ); |
|
/* |
|
CFileHandle *fh = ( CFileHandle *)handle; |
|
if ( !fh ) |
|
{ |
|
buf[ 0 ] = 0; |
|
return; |
|
} |
|
|
|
#if !defined( _RETAIL ) |
|
// Pack file filehandles store the underlying name for convenience |
|
if ( fh->IsPack() ) |
|
{ |
|
Q_strncpy( buf, fh->Name(), buflen ); |
|
return; |
|
} |
|
#endif |
|
|
|
AUTO_LOCK( m_OpenedFilesMutex ); |
|
|
|
COpenedFile file; |
|
file.m_pFile = fh->GetFileHandle(); |
|
|
|
int result = m_OpenedFiles.Find( file ); |
|
if ( result != -1 ) |
|
{ |
|
COpenedFile found = m_OpenedFiles[ result ]; |
|
Q_strncpy( buf, found.GetName(), buflen ); |
|
} |
|
else |
|
{ |
|
buf[ 0 ] = 0; |
|
} |
|
*/ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *fp - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::Trace_FClose( FILE *fp ) |
|
{ |
|
if ( fp ) |
|
{ |
|
m_OpenedFilesMutex.Lock(); |
|
|
|
COpenedFile file; |
|
file.m_pFile = fp; |
|
|
|
int result = m_OpenedFiles.Find( file ); |
|
if ( result != -1 /*m_OpenedFiles.InvalidIdx()*/ ) |
|
{ |
|
COpenedFile found = m_OpenedFiles[ result ]; |
|
if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES && !V_stristr( found.GetName(), "console.log" ) ) |
|
{ |
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES, "---FS%s: close %s %p %i (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), fp, m_OpenedFiles.Count(), Plat_FloatTime() ); |
|
} |
|
m_OpenedFiles.Remove( result ); |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
|
|
if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTALLACCESSES ) |
|
{ |
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES, "Tried to close unknown file pointer %p\n", fp ); |
|
} |
|
} |
|
|
|
m_OpenedFilesMutex.Unlock(); |
|
|
|
FS_fclose( fp ); |
|
} |
|
} |
|
|
|
|
|
void CBaseFileSystem::Trace_FRead( int size, FILE* fp ) |
|
{ |
|
if ( !fp || m_fwLevel < FILESYSTEM_WARNING_REPORTALLACCESSES_READ ) |
|
return; |
|
|
|
AUTO_LOCK( m_OpenedFilesMutex ); |
|
|
|
COpenedFile file; |
|
file.m_pFile = fp; |
|
|
|
int result = m_OpenedFiles.Find( file ); |
|
|
|
if( result != -1 ) |
|
{ |
|
COpenedFile found = m_OpenedFiles[ result ]; |
|
|
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "---FS%s: read %s %i %p (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp, Plat_FloatTime() ); |
|
} |
|
else |
|
{ |
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "Tried to read %i bytes from unknown file pointer %p\n", size, fp ); |
|
|
|
} |
|
} |
|
|
|
void CBaseFileSystem::Trace_FWrite( int size, FILE* fp ) |
|
{ |
|
if ( !fp || m_fwLevel < FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE ) |
|
return; |
|
|
|
COpenedFile file; |
|
file.m_pFile = fp; |
|
|
|
AUTO_LOCK( m_OpenedFilesMutex ); |
|
|
|
int result = m_OpenedFiles.Find( file ); |
|
|
|
if( result != -1 ) |
|
{ |
|
COpenedFile found = m_OpenedFiles[ result ]; |
|
|
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "---FS%s: write %s %i %p\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp ); |
|
} |
|
else |
|
{ |
|
Warning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "Tried to write %i bytes from unknown file pointer %p\n", size, fp ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::Trace_DumpUnclosedFiles( void ) |
|
{ |
|
/* |
|
AUTO_LOCK( m_OpenedFilesMutex ); |
|
for ( int i = 0 ; i < m_OpenedFiles.Count(); i++ ) |
|
{ |
|
COpenedFile *found = &m_OpenedFiles[ i ]; |
|
|
|
if ( m_fwLevel >= FILESYSTEM_WARNING_REPORTUNCLOSED ) |
|
{ |
|
// This warning can legitimately trigger because the log file is, by design, not |
|
// closed. It is not closed at shutdown in order to avoid race conditions. It is |
|
// not closed at each call to log information because the performance costs, |
|
// especially if Microsoft Security Essentials is running, are extreme. |
|
// In addition, when this warning triggers it can, due to the state of the process, |
|
// actually cause a crash. |
|
Warning( FILESYSTEM_WARNING_REPORTUNCLOSED, "File %s was never closed\n", found->GetName() ); |
|
} |
|
} |
|
*/ |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::PrintOpenedFiles( void ) |
|
{ |
|
FileWarningLevel_t saveLevel = m_fwLevel; |
|
m_fwLevel = FILESYSTEM_WARNING_REPORTUNCLOSED; |
|
Trace_DumpUnclosedFiles(); |
|
m_fwLevel = saveLevel; |
|
} |
|
|
|
#if defined( SUPPORT_PACKED_STORE ) |
|
|
|
CPackedStoreRefCount::CPackedStoreRefCount( char const *pFileBasename, char *pszFName, IBaseFileSystem *pFS ) |
|
: CPackedStore( pFileBasename, pszFName, pFS, false ) |
|
{ |
|
// If the VPK is signed, check the signature |
|
// |
|
// !FIXME! This code is simple a linchpin that a hacker could detour to bypass sv_pure |
|
#ifdef VPK_ENABLE_SIGNING |
|
CPackedStore::ESignatureCheckResult eSigResult = CPackedStore::CheckSignature( 0, NULL ); |
|
m_bSignatureValid = ( eSigResult == CPackedStore::eSignatureCheckResult_ValidSignature ); |
|
#else |
|
m_bSignatureValid = false; |
|
#endif |
|
} |
|
|
|
#endif |
|
|
|
void CBaseFileSystem::AddVPKFile( char const *pPath, const char *pPathID, SearchPathAdd_t addType ) |
|
{ |
|
#if defined( SUPPORT_PACKED_STORE ) |
|
char nameBuf[MAX_PATH]; |
|
|
|
Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pPath ); |
|
Q_FixSlashes( nameBuf ); |
|
|
|
CUtlSymbol pathIDSym = g_PathIDTable.AddString( pPathID ); |
|
|
|
// See if we already have this vpk file as a search path |
|
CPackedStoreRefCount *pVPK = NULL; |
|
for ( int i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
CPackedStoreRefCount *p = m_SearchPaths[i].GetPackedStore(); |
|
if ( p && V_stricmp( p->FullPathName(), nameBuf ) == 0 ) |
|
{ |
|
// Already present |
|
if ( m_SearchPaths[i].GetPath() == pathIDSym ) |
|
return; |
|
|
|
// Present, but for a different path |
|
pVPK = p; |
|
} |
|
} |
|
|
|
// Create a new VPK if we didn't don't already have it opened |
|
if ( pVPK == NULL ) |
|
{ |
|
char pszFName[MAX_PATH]; |
|
pVPK = new CPackedStoreRefCount( nameBuf, pszFName, this ); |
|
if ( pVPK->IsEmpty() ) |
|
{ |
|
delete pVPK; |
|
return; |
|
} |
|
pVPK->RegisterFileTracker( (IThreadedFileMD5Processor *)&m_FileTracker2 ); |
|
|
|
pVPK->m_PackFileID = m_FileTracker2.NotePackFileOpened( pVPK->FullPathName(), pPathID, 0 ); |
|
} |
|
else |
|
{ |
|
pVPK->AddRef(); |
|
} |
|
|
|
// Crete a search path for this |
|
CSearchPath *sp = &m_SearchPaths[ ( addType == PATH_ADD_TO_TAIL ) ? m_SearchPaths.AddToTail() : m_SearchPaths.AddToHead() ]; |
|
sp->SetPackedStore( pVPK ); |
|
sp->m_storeId = g_iNextSearchPathID++; |
|
sp->SetPath( pathIDSym ); |
|
sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 ); |
|
|
|
// Check if we're trusted or not |
|
SetSearchPathIsTrustedSource( sp ); |
|
#endif // SUPPORT_PACKED_STORE |
|
} |
|
|
|
|
|
bool CBaseFileSystem::RemoveVPKFile( const char *pPath, const char *pPathID ) |
|
{ |
|
#if defined( SUPPORT_PACKED_STORE ) |
|
char nameBuf[MAX_PATH]; |
|
|
|
Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pPath ); |
|
Q_FixSlashes( nameBuf ); |
|
|
|
CUtlSymbol pathIDSym = g_PathIDTable.AddString( pPathID ); |
|
|
|
// See if we already have this vpk file as a search path |
|
for ( int i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
CPackedStoreRefCount *p = m_SearchPaths[i].GetPackedStore(); |
|
if ( p && V_stricmp( p->FullPathName(), nameBuf ) == 0 ) |
|
{ |
|
// remove if we find one |
|
if ( m_SearchPaths[i].GetPath() == pathIDSym ) |
|
{ |
|
m_SearchPaths.Remove( i ); |
|
return true; |
|
} |
|
} |
|
} |
|
#endif // SUPPORT_PACKED_STORE |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds the specified pack file to the list |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::AddPackFile( const char *pFileName, const char *pathID ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
AsyncFinishAll(); |
|
return AddPackFileFromPath( "", pFileName, true, pathID ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds a pack file from the specified path |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::AddPackFileFromPath( const char *pPath, const char *pakfile, bool bCheckForAppendedPack, const char *pathID ) |
|
{ |
|
char fullpath[ MAX_PATH ]; |
|
_snprintf( fullpath, sizeof(fullpath), "%s%s", pPath, pakfile ); |
|
Q_FixSlashes( fullpath ); |
|
|
|
struct _stat buf; |
|
if ( FS_stat( fullpath, &buf ) == -1 ) |
|
return false; |
|
|
|
CPackFile *pf = new CZipPackFile( this ); |
|
pf->m_hPackFileHandleFS = Trace_FOpen( fullpath, "rb", 0, NULL ); |
|
if ( !pf->m_hPackFileHandleFS ) |
|
{ |
|
delete pf; |
|
return false; |
|
} |
|
|
|
// NOTE: Opening .bsp fiels inside of VPK files is not supported |
|
|
|
// Get the length of the pack file: |
|
FS_fseek( ( FILE * )pf->m_hPackFileHandleFS, 0, FILESYSTEM_SEEK_TAIL ); |
|
int64 len = FS_ftell( ( FILE * )pf->m_hPackFileHandleFS ); |
|
FS_fseek( ( FILE * )pf->m_hPackFileHandleFS, 0, FILESYSTEM_SEEK_HEAD ); |
|
|
|
if ( !pf->Prepare( len ) ) |
|
{ |
|
// Failed for some reason, ignore it |
|
Trace_FClose( pf->m_hPackFileHandleFS ); |
|
pf->m_hPackFileHandleFS = NULL; |
|
delete pf; |
|
|
|
return false; |
|
} |
|
|
|
// Add this pack file to the search path: |
|
CSearchPath *sp = &m_SearchPaths[ m_SearchPaths.AddToTail() ]; |
|
pf->SetPath( sp->GetPath() ); |
|
pf->m_lPackFileTime = GetFileTime( pakfile ); |
|
|
|
sp->SetPath( pPath ); |
|
sp->m_pPathIDInfo->SetPathID( pathID ); |
|
sp->SetPackFile( pf ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Search pPath for pak?.pak files and add to search path if found |
|
// Input : *pPath - |
|
//----------------------------------------------------------------------------- |
|
#if !defined( _X360 ) |
|
#define PACK_NAME_FORMAT "zip%i.zip" |
|
#else |
|
#define PACK_NAME_FORMAT "zip%i.360.zip" |
|
#define PACK_LOCALIZED_NAME_FORMAT "zip%i_%s.360.zip" |
|
#endif |
|
|
|
void CBaseFileSystem::AddPackFiles( const char *pPath, const CUtlSymbol &pathID, SearchPathAdd_t addType ) |
|
{ |
|
Assert( ThreadInMainThread() ); |
|
DISK_INTENSIVE(); |
|
|
|
CUtlVector< CUtlString > pakNames; |
|
CUtlVector< int64 > pakSizes; |
|
|
|
// determine pak files, [zip0..zipN] |
|
for ( int i = 0; ; i++ ) |
|
{ |
|
char pakfile[MAX_PATH]; |
|
char fullpath[MAX_PATH]; |
|
V_snprintf( pakfile, sizeof( pakfile ), PACK_NAME_FORMAT, i ); |
|
V_ComposeFileName( pPath, pakfile, fullpath, sizeof( fullpath ) ); |
|
|
|
struct _stat buf; |
|
if ( FS_stat( fullpath, &buf ) == -1 ) |
|
break; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
|
|
pakNames.AddToTail( pakfile ); |
|
pakSizes.AddToTail( (int64)((unsigned int)buf.st_size) ); |
|
} |
|
|
|
#if defined( _X360 ) |
|
// localized zips are added last to ensure they appear first in the search path construction |
|
// localized zips can only appear in the game or mod directories |
|
bool bUseEnglishAudio = XboxLaunch()->GetForceEnglish(); |
|
|
|
if ( XBX_IsLocalized() && ( bUseEnglishAudio == false ) && |
|
( V_stricmp( g_PathIDTable.String( pathID ), "game" ) == 0 || V_stricmp( g_PathIDTable.String( pathID ), "mod" ) == 0 ) ) |
|
{ |
|
// determine localized pak files, [zip0_language..zipN_language] |
|
for ( int i = 0; ; i++ ) |
|
{ |
|
char pakfile[MAX_PATH]; |
|
char fullpath[MAX_PATH]; |
|
V_snprintf( pakfile, sizeof( pakfile ), PACK_LOCALIZED_NAME_FORMAT, i, XBX_GetLanguageString() ); |
|
V_ComposeFileName( pPath, pakfile, fullpath, sizeof( fullpath ) ); |
|
|
|
struct _stat buf; |
|
if ( FS_stat( fullpath, &buf ) == -1 ) |
|
break; |
|
|
|
pakNames.AddToTail( pakfile ); |
|
pakSizes.AddToTail( (int64)((unsigned int)buf.st_size) ); |
|
} |
|
} |
|
#endif |
|
|
|
// Add any zip files in the format zip1.zip ... zip0.zip |
|
// Add them backwards so zip(N) is higher priority than zip(N-1), etc. |
|
int pakcount = pakSizes.Count(); |
|
int nCount = 0; |
|
for ( int i = pakcount-1; i >= 0; i-- ) |
|
{ |
|
char fullpath[MAX_PATH]; |
|
V_ComposeFileName( pPath, pakNames[i].Get(), fullpath, sizeof( fullpath ) ); |
|
|
|
int nIndex; |
|
if ( addType == PATH_ADD_TO_TAIL ) |
|
{ |
|
nIndex = m_SearchPaths.AddToTail(); |
|
} |
|
else |
|
{ |
|
nIndex = m_SearchPaths.InsertBefore( nCount ); |
|
++nCount; |
|
} |
|
|
|
CSearchPath *sp = &m_SearchPaths[ nIndex ]; |
|
|
|
sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathID, -1 ); |
|
sp->m_storeId = g_iNextSearchPathID++; |
|
sp->SetPath( g_PathIDTable.AddString( pPath ) ); |
|
|
|
CPackFile *pf = NULL; |
|
for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ ) |
|
{ |
|
if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), fullpath ) ) |
|
{ |
|
pf = m_ZipFiles[iPackFile]; |
|
sp->SetPackFile( pf ); |
|
pf->AddRef(); |
|
} |
|
} |
|
|
|
if ( !pf ) |
|
{ |
|
MEM_ALLOC_CREDIT(); |
|
|
|
pf = new CZipPackFile( this ); |
|
|
|
pf->SetPath( sp->GetPath() ); |
|
pf->m_ZipName = fullpath; |
|
|
|
m_ZipFiles.AddToTail( pf ); |
|
sp->SetPackFile( pf ); |
|
pf->m_lPackFileTime = GetFileTime( fullpath ); |
|
|
|
pf->m_hPackFileHandleFS = Trace_FOpen( fullpath, "rb", 0, NULL ); |
|
|
|
if ( pf->m_hPackFileHandleFS ) |
|
{ |
|
FS_setbufsize( pf->m_hPackFileHandleFS, 32*1024 ); // 32k buffer. |
|
|
|
if ( pf->Prepare( pakSizes[i] ) ) |
|
{ |
|
FS_setbufsize( pf->m_hPackFileHandleFS, filesystem_buffer_size.GetInt() ); |
|
} |
|
else |
|
{ |
|
// Failed for some reason, ignore it |
|
if ( pf->m_hPackFileHandleFS ) |
|
{ |
|
Trace_FClose( pf->m_hPackFileHandleFS ); |
|
pf->m_hPackFileHandleFS = NULL; |
|
} |
|
m_SearchPaths.Remove( nIndex ); |
|
} |
|
} |
|
else |
|
{ |
|
// !NOTE! Pack files inside of VPK not supported |
|
//pf->m_hPackFileHandleVPK = FindFileInVPKs( fullpath ); |
|
//if ( !pf->m_hPackFileHandleVPK || !pf->Prepare( pakSizes[i] ) ) |
|
{ |
|
m_SearchPaths.Remove( nIndex ); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Wipe all map (.bsp) pak file search paths |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::RemoveAllMapSearchPaths( void ) |
|
{ |
|
AsyncFinishAll(); |
|
|
|
int c = m_SearchPaths.Count(); |
|
for ( int i = c - 1; i >= 0; i-- ) |
|
{ |
|
if ( !( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) ) |
|
{ |
|
continue; |
|
} |
|
|
|
m_SearchPaths.Remove( i ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::AddMapPackFile( const char *pPath, const char *pPathID, SearchPathAdd_t addType ) |
|
{ |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pPath, pPathID, tempPathID ); |
|
|
|
// Security nightmares already, should not let things explicitly loading from e.g. "MOD" get surprise untrusted |
|
// files unless you really really know what you're doing. |
|
AssertMsg( V_strcasecmp( pPathID, "GAME" ) == 0, |
|
"Mounting map files anywhere besides GAME is asking for pain" ); |
|
|
|
char newPath[ MAX_FILEPATH ]; |
|
// +2 for '\0' and potential slash added at end. |
|
V_strncpy( newPath, pPath, sizeof( newPath ) ); |
|
#ifdef _WIN32 // don't do this on linux! |
|
V_strlower( newPath ); |
|
#endif |
|
V_FixSlashes( newPath ); |
|
|
|
// Open the .bsp and find the map lump |
|
char fullpath[ MAX_FILEPATH ]; |
|
if ( V_IsAbsolutePath( newPath ) ) // If it's an absolute path, just use that. |
|
{ |
|
V_strncpy( fullpath, newPath, sizeof(fullpath) ); |
|
} |
|
else |
|
{ |
|
if ( !GetLocalPath( newPath, fullpath, sizeof(fullpath) ) ) |
|
{ |
|
// Couldn't find that .bsp file!!! |
|
return; |
|
} |
|
} |
|
|
|
int c = m_SearchPaths.Count(); |
|
for ( int i = c - 1; i >= 0; i-- ) |
|
{ |
|
if ( !( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) ) |
|
continue; |
|
|
|
if ( V_stricmp( m_SearchPaths[i].GetPackFile()->m_ZipName.Get(), fullpath ) == 0 ) |
|
{ |
|
// Already set as map path |
|
return; |
|
} |
|
} |
|
|
|
RemoveAllMapSearchPaths(); |
|
|
|
CUtlSymbol pathSymbol = g_PathIDTable.AddString( newPath ); |
|
|
|
int iStoreId = CRC32_ProcessSingleBuffer( fullpath, V_strlen(fullpath) ) | 0x80000000; // set store ID based on .bsp filename, so if we remount the same map file, it will have the same storeid |
|
|
|
// Look through already-open packfiles in case we intentionally |
|
// preserved this ZIP across a map reload via refcount holding |
|
FOR_EACH_VEC( m_ZipFiles, i ) |
|
{ |
|
CPackFile* pf = m_ZipFiles[i]; |
|
if ( pf && pf->m_bIsMapPath && pf->GetPath() == pathSymbol && V_stricmp( pf->m_ZipName.Get(), fullpath ) == 0 ) |
|
{ |
|
CSearchPath *sp = &m_SearchPaths[ ( addType == PATH_ADD_TO_TAIL ) ? m_SearchPaths.AddToTail() : m_SearchPaths.AddToHead() ]; |
|
pf->AddRef(); |
|
sp->SetPackFile( pf ); |
|
sp->m_storeId = iStoreId; |
|
sp->SetPath( pathSymbol ); |
|
sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 ); |
|
if ( IsX360() && !V_strnicmp( newPath, "net:", 4 ) ) |
|
{ |
|
sp->m_bIsRemotePath = true; |
|
} |
|
SetSearchPathIsTrustedSource( sp ); |
|
return; |
|
} |
|
} |
|
|
|
{ |
|
FILE *fp = Trace_FOpen( fullpath, "rb", 0, NULL ); |
|
if ( !fp ) |
|
{ |
|
// Couldn't open it |
|
Warning( FILESYSTEM_WARNING, "Couldn't open .bsp %s for embedded pack file check\n", fullpath ); |
|
return; |
|
} |
|
|
|
// Get the .bsp file header |
|
dheader_t header; |
|
memset( &header, 0, sizeof(dheader_t) ); |
|
m_Stats.nBytesRead += FS_fread( &header, sizeof( header ), fp ); |
|
m_Stats.nReads++; |
|
|
|
if ( header.ident != IDBSPHEADER || header.version < MINBSPVERSION || header.version > BSPVERSION ) |
|
{ |
|
Trace_FClose( fp ); |
|
return; |
|
} |
|
|
|
// Find the LUMP_PAKFILE offset |
|
lump_t *packfile = &header.lumps[ LUMP_PAKFILE ]; |
|
if ( packfile->filelen <= sizeof( lump_t ) ) |
|
{ |
|
// It's empty or only contains a file header ( so there are no entries ), so don't add to search paths |
|
Trace_FClose( fp ); |
|
return; |
|
} |
|
|
|
// Seek to correct position |
|
FS_fseek( fp, packfile->fileofs, FILESYSTEM_SEEK_HEAD ); |
|
|
|
CPackFile *pf = new CZipPackFile( this ); |
|
|
|
pf->m_bIsMapPath = true; |
|
pf->m_hPackFileHandleFS = fp; |
|
|
|
MEM_ALLOC_CREDIT(); |
|
pf->m_ZipName = fullpath; |
|
|
|
if ( pf->Prepare( packfile->filelen, packfile->fileofs ) ) |
|
{ |
|
int nIndex; |
|
if ( addType == PATH_ADD_TO_TAIL ) |
|
{ |
|
nIndex = m_SearchPaths.AddToTail(); |
|
} |
|
else |
|
{ |
|
nIndex = m_SearchPaths.AddToHead(); |
|
} |
|
|
|
CSearchPath *sp = &m_SearchPaths[ nIndex ]; |
|
|
|
sp->SetPackFile( pf ); |
|
sp->m_storeId = iStoreId; |
|
sp->SetPath( pathSymbol ); |
|
sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 ); |
|
|
|
if ( IsX360() && !V_strnicmp( newPath, "net:", 4 ) ) |
|
{ |
|
sp->m_bIsRemotePath = true; |
|
} |
|
|
|
pf->SetPath( pathSymbol ); |
|
pf->m_lPackFileTime = GetFileTime( newPath ); |
|
|
|
Trace_FClose( pf->m_hPackFileHandleFS ); |
|
pf->m_hPackFileHandleFS = NULL; |
|
|
|
m_ZipFiles.AddToTail( pf ); |
|
|
|
SetSearchPathIsTrustedSource( sp ); |
|
} |
|
else |
|
{ |
|
delete pf; |
|
} |
|
} |
|
} |
|
|
|
|
|
void CBaseFileSystem::BeginMapAccess() |
|
{ |
|
if ( m_iMapLoad++ == 0 ) |
|
{ |
|
int c = m_SearchPaths.Count(); |
|
for( int i = 0; i < c; i++ ) |
|
{ |
|
CSearchPath *pSearchPath = &m_SearchPaths[i]; |
|
CPackFile *pPackFile = pSearchPath->GetPackFile(); |
|
|
|
if ( pPackFile && pPackFile->m_bIsMapPath ) |
|
{ |
|
pPackFile->AddRef(); |
|
pPackFile->m_mutex.Lock(); |
|
|
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( pPackFile->m_nOpenFiles == 0 && pPackFile->m_hPackFileHandleFS == NULL && !pPackFile->m_hPackFileHandleVPK ) |
|
#else |
|
if ( pPackFile->m_nOpenFiles == 0 && pPackFile->m_hPackFileHandleFS == NULL ) |
|
#endif |
|
{ |
|
// Try opening the file as a regular file |
|
pPackFile->m_hPackFileHandleFS = Trace_FOpen( pPackFile->m_ZipName, "rb", 0, NULL ); |
|
|
|
// !NOTE! Pack files inside of VPK not supported |
|
//#if defined( SUPPORT_PACKED_STORE ) |
|
// if ( !pPackFile->m_hPackFileHandleFS ) |
|
// { |
|
// pPackFile->m_hPackFileHandleVPK = FindFileInVPKs( pPackFile->m_ZipName ); |
|
// } |
|
//#endif |
|
} |
|
pPackFile->m_nOpenFiles++; |
|
pPackFile->m_mutex.Unlock(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
void CBaseFileSystem::EndMapAccess() |
|
{ |
|
if ( m_iMapLoad-- == 1 ) |
|
{ |
|
int c = m_SearchPaths.Count(); |
|
for( int i = 0; i < c; i++ ) |
|
{ |
|
CSearchPath *pSearchPath = &m_SearchPaths[i]; |
|
CPackFile *pPackFile = pSearchPath->GetPackFile(); |
|
|
|
if ( pPackFile && pPackFile->m_bIsMapPath ) |
|
{ |
|
pPackFile->m_mutex.Lock(); |
|
pPackFile->m_nOpenFiles--; |
|
if ( pPackFile->m_nOpenFiles == 0 ) |
|
{ |
|
if ( pPackFile->m_hPackFileHandleFS ) |
|
{ |
|
Trace_FClose( pPackFile->m_hPackFileHandleFS ); |
|
pPackFile->m_hPackFileHandleFS = NULL; |
|
} |
|
} |
|
pPackFile->m_mutex.Unlock(); |
|
pPackFile->Release(); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
void CBaseFileSystem::PrintSearchPaths( void ) |
|
{ |
|
Msg( "---------------\n" ); |
|
Msg( "Paths:\n" ); |
|
|
|
int c = m_SearchPaths.Count(); |
|
for( int i = 0; i < c; i++ ) |
|
{ |
|
CSearchPath *pSearchPath = &m_SearchPaths[i]; |
|
|
|
const char *pszPack = ""; |
|
const char *pszType = ""; |
|
if ( pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath ) |
|
{ |
|
pszType = "(map)"; |
|
} |
|
else if ( pSearchPath->GetPackFile() ) |
|
{ |
|
pszType = "(pack) "; |
|
pszPack = pSearchPath->GetPackFile()->m_ZipName; |
|
} |
|
#ifdef SUPPORT_PACKED_STORE |
|
else if ( pSearchPath->GetPackedStore() ) |
|
{ |
|
pszType = "(VPK)"; |
|
pszPack = pSearchPath->GetPackedStore()->FullPathName(); |
|
} |
|
#endif |
|
|
|
Msg( "\"%s\" \"%s\" %s%s\n", pSearchPath->GetPathString(), (const char *)pSearchPath->GetPathIDString(), pszType, pszPack ); |
|
} |
|
|
|
if ( IsX360() && m_ExcludePaths.Count() ) |
|
{ |
|
// dump current list |
|
Msg( "\nExclude:\n" ); |
|
char szPath[MAX_PATH]; |
|
for ( int i = 0; i < m_ExcludePaths.Count(); i++ ) |
|
{ |
|
if ( String( m_ExcludePaths[i], szPath, sizeof( szPath ) ) ) |
|
{ |
|
Msg( "\"%s\"\n", szPath ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is where search paths are created. map files are created at head of list (they occur after |
|
// file system paths have already been set ) so they get highest priority. Otherwise, we add the disk (non-packfile) |
|
// path and then the paks if they exist for the path |
|
// Input : *pPath - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::AddSearchPathInternal( const char *pPath, const char *pathID, SearchPathAdd_t addType, bool bAddPackFiles ) |
|
{ |
|
AsyncFinishAll(); |
|
|
|
Assert( ThreadInMainThread() ); |
|
|
|
// Map pak files have their own handler |
|
if ( V_stristr( pPath, ".bsp" ) ) |
|
{ |
|
AddMapPackFile( pPath, pathID, addType ); |
|
return; |
|
} |
|
|
|
// So do VPK files |
|
if ( V_stristr( pPath, ".vpk" ) ) |
|
{ |
|
AddVPKFile( pPath, pathID, addType ); |
|
return; |
|
} |
|
|
|
// Clean up the name |
|
char newPath[ MAX_FILEPATH ]; |
|
if ( pPath[0] == 0 ) |
|
{ |
|
newPath[0] = newPath[1] = 0; |
|
} |
|
else |
|
{ |
|
if ( IsX360() || Q_IsAbsolutePath( pPath ) ) |
|
{ |
|
Q_strncpy( newPath, pPath, sizeof( newPath ) ); |
|
} |
|
else |
|
{ |
|
Q_MakeAbsolutePath( newPath, sizeof(newPath), pPath ); |
|
} |
|
#ifdef _WIN32 |
|
Q_strlower( newPath ); |
|
#endif |
|
AddSeperatorAndFixPath( newPath ); |
|
} |
|
|
|
// Make sure that it doesn't already exist |
|
CUtlSymbol pathSym, pathIDSym; |
|
pathSym = g_PathIDTable.AddString( newPath ); |
|
pathIDSym = g_PathIDTable.AddString( pathID ); |
|
int i; |
|
int c = m_SearchPaths.Count(); |
|
int id = 0; |
|
for ( i = 0; i < c; i++ ) |
|
{ |
|
CSearchPath *pSearchPath = &m_SearchPaths[i]; |
|
if ( pSearchPath->GetPath() == pathSym && pSearchPath->GetPathID() == pathIDSym ) |
|
{ |
|
if ( ( addType == PATH_ADD_TO_HEAD && i == 0 ) || ( addType == PATH_ADD_TO_TAIL ) ) |
|
{ |
|
return; // this entry is already at the head |
|
} |
|
else |
|
{ |
|
m_SearchPaths.Remove(i); // remove it from its current position so we can add it back to the head |
|
i--; |
|
c--; |
|
} |
|
} |
|
if ( !id && pSearchPath->GetPath() == pathSym ) |
|
{ |
|
// get first found - all reference the same store |
|
id = pSearchPath->m_storeId; |
|
} |
|
} |
|
|
|
if (!id) |
|
{ |
|
id = g_iNextSearchPathID++; |
|
} |
|
|
|
if ( IsX360() && bAddPackFiles && ( !Q_stricmp( pathID, "DEFAULT_WRITE_PATH" ) || !Q_stricmp( pathID, "LOGDIR" ) ) ) |
|
{ |
|
// xbox can be assured that no zips would ever be loaded on its write path |
|
// otherwise xbox reloads zips because of mirrored drive mappings |
|
bAddPackFiles = false; |
|
} |
|
|
|
// Add to list |
|
bool bAdded = false; |
|
int nIndex = m_SearchPaths.Count(); |
|
if ( bAddPackFiles ) |
|
{ |
|
// Add pack files for this path next |
|
AddPackFiles( newPath, pathIDSym, addType ); |
|
bAdded = m_SearchPaths.Count() != nIndex; |
|
} |
|
if ( addType == PATH_ADD_TO_HEAD ) |
|
{ |
|
nIndex = m_SearchPaths.Count() - nIndex; |
|
Assert( nIndex >= 0 ); |
|
} |
|
|
|
if ( IsPC() || !bAddPackFiles || !bAdded ) |
|
{ |
|
// Grab last entry and set the path |
|
m_SearchPaths.InsertBefore( nIndex ); |
|
} |
|
else if ( IsX360() && bAddPackFiles && bAdded ) |
|
{ |
|
// 360 needs to find files (for the preload hit) in the zip first for fast loading |
|
// 360 always adds the non-pack search path *after* the pack file but respects the overall list ordering |
|
if ( addType == PATH_ADD_TO_HEAD ) |
|
{ |
|
m_SearchPaths.InsertBefore( nIndex ); |
|
} |
|
else |
|
{ |
|
nIndex = m_SearchPaths.Count() - 1; |
|
m_SearchPaths.InsertAfter( nIndex ); |
|
nIndex++; |
|
} |
|
} |
|
|
|
CSearchPath *sp = &m_SearchPaths[ nIndex ]; |
|
|
|
sp->SetPath( pathSym ); |
|
sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathIDSym, -1 ); |
|
|
|
// all matching paths have a reference to the same store |
|
sp->m_storeId = id; |
|
if ( IsX360() && !V_strnicmp( newPath, "net:", 4 ) ) |
|
{ |
|
sp->m_bIsRemotePath = true; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::CSearchPath *CBaseFileSystem::FindSearchPathByStoreId( int storeId ) |
|
{ |
|
FOR_EACH_VEC( m_SearchPaths, i ) |
|
{ |
|
if ( m_SearchPaths[i].m_storeId == storeId ) |
|
return &m_SearchPaths[i]; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Create the search path. |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::AddSearchPath( const char *pPath, const char *pathID, SearchPathAdd_t addType ) |
|
{ |
|
int currCount = m_SearchPaths.Count(); |
|
|
|
AddSearchPathInternal( pPath, pathID, addType, true ); |
|
|
|
if ( IsX360() && m_DVDMode == DVDMODE_DEV ) |
|
{ |
|
// dvd development mode clones a search path based on the remote path for fall through |
|
const char *pRemotePath = CommandLine()->ParmValue( "-remote" ); |
|
const char *pBasePath = CommandLine()->ParmValue( "-basedir" ); |
|
if ( pRemotePath && pBasePath && !V_stristr( pPath, ".bsp" ) ) |
|
{ |
|
// isolate the search path from the base path |
|
if ( !V_strnicmp( pPath, pBasePath, strlen( pBasePath ) ) ) |
|
{ |
|
// substitue the remote path |
|
char szRemotePath[MAX_PATH]; |
|
V_strncpy( szRemotePath, pRemotePath, sizeof( szRemotePath ) ); |
|
V_strncat( szRemotePath, pPath + strlen( pBasePath ), sizeof( szRemotePath ) ); |
|
|
|
// no pack files are allowed on the fall through remote path |
|
AddSearchPathInternal( szRemotePath, pathID, addType, false ); |
|
} |
|
} |
|
} |
|
|
|
if ( currCount != m_SearchPaths.Count() ) |
|
{ |
|
#if !defined( DEDICATED ) |
|
if ( IsDebug() ) |
|
{ |
|
// spew updated search paths |
|
// PrintSearchPaths(); |
|
} |
|
#endif |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the search path, each path is separated by ;s. Returns the length of the string returned |
|
// Pack search paths include the pack name, so that callers can still form absolute paths |
|
// and that absolute path can be sent to the filesystem, and mounted as a file inside a pack. |
|
//----------------------------------------------------------------------------- |
|
int CBaseFileSystem::GetSearchPath( const char *pathID, bool bGetPackFiles, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars ) |
|
{ |
|
AUTO_LOCK( m_SearchPathsMutex ); |
|
|
|
if ( maxLenInChars ) |
|
{ |
|
pDest[0] = 0; |
|
} |
|
|
|
// Build up result into string object |
|
CUtlString sResult; |
|
CSearchPathsIterator iter( this, pathID, bGetPackFiles ? FILTER_NONE : FILTER_CULLPACK ); |
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
if ( !sResult.IsEmpty() ) |
|
sResult += ";"; |
|
CUtlString sName; |
|
if ( pSearchPath->GetPackFile() ) |
|
{ |
|
sResult += pSearchPath->GetPackFile()->m_ZipName.String(); |
|
sResult += CORRECT_PATH_SEPARATOR_S; |
|
} |
|
#ifdef SUPPORT_PACKED_STORE |
|
else if ( pSearchPath->GetPackedStore() ) |
|
{ |
|
sResult += pSearchPath->GetPackedStore()->FullPathName(); |
|
} |
|
#endif |
|
else |
|
{ |
|
sResult += pSearchPath->GetPathString(); |
|
} |
|
} |
|
|
|
// Copy into user's buffer, possibly truncating |
|
if ( maxLenInChars ) |
|
{ |
|
V_strncpy( pDest, sResult.String(), maxLenInChars ); |
|
} |
|
|
|
// Return 1 extra for the NULL terminator |
|
return sResult.Length()+1; |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pPath - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::RemoveSearchPath( const char *pPath, const char *pathID ) |
|
{ |
|
AsyncFinishAll(); |
|
|
|
char newPath[ MAX_FILEPATH ]; |
|
newPath[ 0 ] = 0; |
|
|
|
if ( pPath ) |
|
{ |
|
// +2 for '\0' and potential slash added at end. |
|
Q_strncpy( newPath, pPath, sizeof( newPath ) ); |
|
#ifdef _WIN32 // don't do this on linux! |
|
Q_strlower( newPath ); |
|
#endif |
|
|
|
if ( V_stristr( newPath, ".bsp" ) ) |
|
{ |
|
Q_FixSlashes( newPath ); |
|
} |
|
else if ( V_stristr( newPath, ".vpk" ) ) |
|
{ |
|
return RemoveVPKFile( newPath, pathID ); |
|
} |
|
else |
|
{ |
|
AddSeperatorAndFixPath( newPath ); |
|
} |
|
} |
|
|
|
CUtlSymbol lookup = g_PathIDTable.AddString( newPath ); |
|
CUtlSymbol id = g_PathIDTable.AddString( pathID ); |
|
|
|
bool bret = false; |
|
|
|
// Count backward since we're possibly deleting one or more pack files, too |
|
int i; |
|
int c = m_SearchPaths.Count(); |
|
for( i = c - 1; i >= 0; i-- ) |
|
{ |
|
if ( newPath[0] && m_SearchPaths[i].GetPath() != lookup ) |
|
continue; |
|
|
|
if ( FilterByPathID( &m_SearchPaths[i], id ) ) |
|
continue; |
|
|
|
m_SearchPaths.Remove( i ); |
|
bret = true; |
|
} |
|
return bret; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes all search paths for a given pathID, such as all "GAME" paths. |
|
// Input : pathID - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::RemoveSearchPaths( const char *pathID ) |
|
{ |
|
AsyncFinishAll(); |
|
|
|
int nCount = m_SearchPaths.Count(); |
|
for (int i = nCount - 1; i >= 0; i--) |
|
{ |
|
if (!Q_stricmp(m_SearchPaths.Element(i).GetPathIDString(), pathID)) |
|
{ |
|
m_SearchPaths.FastRemove(i); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::CSearchPath *CBaseFileSystem::FindWritePath( const char *pFilename, const char *pathID ) |
|
{ |
|
CUtlSymbol lookup = g_PathIDTable.AddString( pathID ); |
|
|
|
AUTO_LOCK( m_SearchPathsMutex ); |
|
|
|
// a pathID has been specified, find the first match in the path list |
|
int c = m_SearchPaths.Count(); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
// pak files are not allowed to be written to... |
|
CSearchPath *pSearchPath = &m_SearchPaths[i]; |
|
if ( pSearchPath->GetPackFile() || pSearchPath->GetPackedStore() ) |
|
{ |
|
continue; |
|
} |
|
|
|
if ( IsX360() && ( m_DVDMode == DVDMODE_DEV ) && pFilename && !pSearchPath->m_bIsRemotePath ) |
|
{ |
|
bool bIgnorePath = false; |
|
char szExcludePath[MAX_PATH]; |
|
char szFilename[MAX_PATH]; |
|
V_ComposeFileName( pSearchPath->GetPathString(), pFilename, szFilename, sizeof( szFilename ) ); |
|
for ( int j = 0; j < m_ExcludePaths.Count(); j++ ) |
|
{ |
|
if ( g_pFullFileSystem->String( m_ExcludePaths[j], szExcludePath, sizeof( szExcludePath ) ) ) |
|
{ |
|
if ( !V_strnicmp( szFilename, szExcludePath, strlen( szExcludePath ) ) ) |
|
{ |
|
bIgnorePath = true; |
|
break; |
|
} |
|
} |
|
} |
|
if ( bIgnorePath ) |
|
{ |
|
// filename matches exclusion path, skip it |
|
// favoring the next path which should be the path fall through hit |
|
continue; |
|
} |
|
} |
|
|
|
if ( !pathID || ( pSearchPath->GetPathID() == lookup ) ) |
|
{ |
|
return pSearchPath; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Finds a search path that should be used for writing to, given a pathID. |
|
//----------------------------------------------------------------------------- |
|
const char *CBaseFileSystem::GetWritePath( const char *pFilename, const char *pathID ) |
|
{ |
|
CSearchPath *pSearchPath = NULL; |
|
if ( pathID && pathID[ 0 ] != '\0' ) |
|
{ |
|
|
|
// Check for "game_write" and "mod_write" |
|
if ( V_stricmp( pathID, "game" ) == 0 ) |
|
pSearchPath = FindWritePath( pFilename, "game_write" ); |
|
else if ( V_stricmp( pathID, "mod" ) == 0 ) |
|
pSearchPath = FindWritePath( pFilename, "mod_write" ); |
|
|
|
if ( pSearchPath == NULL ) |
|
pSearchPath = FindWritePath( pFilename, pathID ); |
|
|
|
if ( pSearchPath ) |
|
{ |
|
return pSearchPath->GetPathString(); |
|
} |
|
|
|
Warning( FILESYSTEM_WARNING, "Requested non-existent write path %s!\n", pathID ); |
|
} |
|
|
|
pSearchPath = FindWritePath( pFilename, "DEFAULT_WRITE_PATH" ); |
|
if ( pSearchPath ) |
|
{ |
|
return pSearchPath->GetPathString(); |
|
} |
|
|
|
pSearchPath = FindWritePath( pFilename, NULL ); // okay, just return the first search path added! |
|
if ( pSearchPath ) |
|
{ |
|
return pSearchPath->GetPathString(); |
|
} |
|
|
|
// Hope this is reasonable!! |
|
return ".\\"; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Reads/writes files to utlbuffers. Attempts alignment fixups for optimal read |
|
//----------------------------------------------------------------------------- |
|
CTHREADLOCAL(char *) g_pszReadFilename; |
|
bool CBaseFileSystem::ReadToBuffer( FileHandle_t fp, CUtlBuffer &buf, int nMaxBytes, FSAllocFunc_t pfnAlloc ) |
|
{ |
|
SetBufferSize( fp, 0 ); // TODO: what if it's a pack file? restore buffer size? |
|
|
|
int nBytesToRead = Size( fp ); |
|
if ( nBytesToRead == 0 ) |
|
{ |
|
// no data in file |
|
return true; |
|
} |
|
|
|
if ( nMaxBytes > 0 ) |
|
{ |
|
// can't read more than file has |
|
nBytesToRead = min( nMaxBytes, nBytesToRead ); |
|
} |
|
|
|
int nBytesRead = 0; |
|
int nBytesOffset = 0; |
|
|
|
int iStartPos = Tell( fp ); |
|
|
|
if ( nBytesToRead != 0 ) |
|
{ |
|
int nBytesDestBuffer = nBytesToRead; |
|
unsigned nSizeAlign = 0, nBufferAlign = 0, nOffsetAlign = 0; |
|
|
|
bool bBinary = !( buf.IsText() && !buf.ContainsCRLF() ); |
|
|
|
if ( bBinary && !IsLinux() && !buf.IsExternallyAllocated() && !pfnAlloc && |
|
( buf.TellPut() == 0 ) && ( buf.TellGet() == 0 ) && ( iStartPos % 4 == 0 ) && |
|
GetOptimalIOConstraints( fp, &nOffsetAlign, &nSizeAlign, &nBufferAlign ) ) |
|
{ |
|
// correct conditions to allow an optimal read |
|
if ( iStartPos % nOffsetAlign != 0 ) |
|
{ |
|
// move starting position back to nearest alignment |
|
nBytesOffset = ( iStartPos % nOffsetAlign ); |
|
Assert ( ( iStartPos - nBytesOffset ) % nOffsetAlign == 0 ); |
|
Seek( fp, -nBytesOffset, FILESYSTEM_SEEK_CURRENT ); |
|
|
|
// going to read from aligned start, increase target buffer size by offset alignment |
|
nBytesDestBuffer += nBytesOffset; |
|
} |
|
|
|
// snap target buffer size to its size alignment |
|
// add additional alignment slop for target pointer adjustment |
|
nBytesDestBuffer = AlignValue( nBytesDestBuffer, nSizeAlign ) + nBufferAlign; |
|
} |
|
|
|
if ( !pfnAlloc ) |
|
{ |
|
buf.EnsureCapacity( nBytesDestBuffer + buf.TellPut() ); |
|
} |
|
else |
|
{ |
|
// caller provided allocator |
|
void *pMemory = (*pfnAlloc)( g_pszReadFilename.Get(), nBytesDestBuffer ); |
|
buf.SetExternalBuffer( pMemory, nBytesDestBuffer, 0, buf.GetFlags() & ~CUtlBuffer::EXTERNAL_GROWABLE ); |
|
} |
|
|
|
int seekGet = -1; |
|
if ( nBytesDestBuffer != nBytesToRead ) |
|
{ |
|
// doing optimal read, align target pointer |
|
int nAlignedBase = AlignValue( (byte *)buf.Base(), nBufferAlign ) - (byte *)buf.Base(); |
|
buf.SeekPut( CUtlBuffer::SEEK_HEAD, nAlignedBase ); |
|
|
|
// the buffer read position is slid forward to ignore the addtional |
|
// starting offset alignment |
|
seekGet = nAlignedBase + nBytesOffset; |
|
} |
|
|
|
nBytesRead = ReadEx( buf.PeekPut(), buf.Size() - buf.TellPut(), nBytesToRead + nBytesOffset, fp ); |
|
buf.SeekPut( CUtlBuffer::SEEK_CURRENT, nBytesRead ); |
|
|
|
if ( seekGet != -1 ) |
|
{ |
|
// can only seek the get after data has been put, otherwise buffer sets overflow error |
|
buf.SeekGet( CUtlBuffer::SEEK_HEAD, seekGet ); |
|
} |
|
|
|
Seek( fp, iStartPos + ( nBytesRead - nBytesOffset ), FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
return (nBytesRead != 0); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Reads/writes files to utlbuffers |
|
// NOTE NOTE!! |
|
// If you change this implementation, copy it into CBaseVMPIFileSystem::ReadFile |
|
// in vmpi_filesystem.cpp |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::ReadFile( const char *pFileName, const char *pPath, CUtlBuffer &buf, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
bool bBinary = !( buf.IsText() && !buf.ContainsCRLF() ); |
|
|
|
FileHandle_t fp = Open( pFileName, ( bBinary ) ? "rb" : "rt", pPath ); |
|
if ( !fp ) |
|
return false; |
|
|
|
if ( nStartingByte != 0 ) |
|
{ |
|
Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
if ( pfnAlloc ) |
|
{ |
|
g_pszReadFilename.Set( (char *)pFileName ); |
|
} |
|
|
|
bool bSuccess = ReadToBuffer( fp, buf, nMaxBytes, pfnAlloc ); |
|
|
|
Close( fp ); |
|
|
|
return bSuccess; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// |
|
//----------------------------------------------------------------------------- |
|
int CBaseFileSystem::ReadFileEx( const char *pFileName, const char *pPath, void **ppBuf, bool bNullTerminate, bool bOptimalAlloc, int nMaxBytes, int nStartingByte, FSAllocFunc_t pfnAlloc ) |
|
{ |
|
FileHandle_t fp = Open( pFileName, "rb", pPath ); |
|
if ( !fp ) |
|
{ |
|
return 0; |
|
} |
|
|
|
if ( IsX360() ) |
|
{ |
|
// callers are sloppy, always want optimal |
|
bOptimalAlloc = true; |
|
} |
|
|
|
SetBufferSize( fp, 0 ); // TODO: what if it's a pack file? restore buffer size? |
|
|
|
int nBytesToRead = Size( fp ); |
|
int nBytesRead = 0; |
|
if ( nMaxBytes > 0 ) |
|
{ |
|
nBytesToRead = min( nMaxBytes, nBytesToRead ); |
|
if ( bNullTerminate ) |
|
{ |
|
nBytesToRead--; |
|
} |
|
} |
|
|
|
if ( nBytesToRead != 0 ) |
|
{ |
|
int nBytesBuf; |
|
if ( !*ppBuf ) |
|
{ |
|
nBytesBuf = nBytesToRead + ( ( bNullTerminate ) ? 1 : 0 ); |
|
|
|
if ( !pfnAlloc && !bOptimalAlloc ) |
|
{ |
|
// AllocOptimalReadBuffer does malloc... |
|
*ppBuf = malloc( nBytesBuf ); |
|
} |
|
else if ( !pfnAlloc ) |
|
{ |
|
*ppBuf = AllocOptimalReadBuffer( fp, nBytesBuf, 0 ); |
|
nBytesBuf = GetOptimalReadSize( fp, nBytesBuf ); |
|
} |
|
else |
|
{ |
|
*ppBuf = (*pfnAlloc)( pFileName, nBytesBuf ); |
|
} |
|
} |
|
else |
|
{ |
|
nBytesBuf = nMaxBytes; |
|
} |
|
|
|
if ( nStartingByte != 0 ) |
|
{ |
|
Seek( fp, nStartingByte, FILESYSTEM_SEEK_HEAD ); |
|
} |
|
|
|
nBytesRead = ReadEx( *ppBuf, nBytesBuf, nBytesToRead, fp ); |
|
|
|
if ( bNullTerminate ) |
|
{ |
|
((byte *)(*ppBuf))[nBytesToRead] = 0; |
|
} |
|
} |
|
|
|
Close( fp ); |
|
return nBytesRead; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// NOTE NOTE!! |
|
// If you change this implementation, copy it into CBaseVMPIFileSystem::WriteFile |
|
// in vmpi_filesystem.cpp |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::WriteFile( const char *pFileName, const char *pPath, CUtlBuffer &buf ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
const char *pWriteFlags = "wb"; |
|
if ( buf.IsText() && !buf.ContainsCRLF() ) |
|
{ |
|
pWriteFlags = "wt"; |
|
} |
|
|
|
FileHandle_t fp = Open( pFileName, pWriteFlags, pPath ); |
|
if ( !fp ) |
|
return false; |
|
|
|
int nBytesWritten = Write( buf.Base(), buf.TellPut(), fp ); |
|
|
|
Close( fp ); |
|
return (nBytesWritten == buf.TellPut()); |
|
} |
|
|
|
|
|
bool CBaseFileSystem::UnzipFile( const char *pFileName, const char *pPath, const char *pDestination ) |
|
{ |
|
IZip *pZip = IZip::CreateZip( NULL, true ); |
|
|
|
HANDLE hZipFile = pZip->ParseFromDisk( pFileName ); |
|
if ( !hZipFile ) |
|
{ |
|
Msg( "Bad or missing zip file, failed to open '%s'\n", pFileName ); |
|
return false; |
|
} |
|
|
|
int iZipIndex = -1; |
|
int iFileSize; |
|
char szFileName[MAX_PATH]; |
|
|
|
// Create Directories |
|
CreateDirHierarchy( pDestination, pPath ); |
|
|
|
while ( 1 ) |
|
{ |
|
// Get the next file in the zip |
|
szFileName[0] = '\0'; |
|
iFileSize = 0; |
|
iZipIndex = pZip->GetNextFilename( iZipIndex, szFileName, sizeof( szFileName ), iFileSize ); |
|
|
|
// If there aren't any more files then break out of this while |
|
if ( iZipIndex == -1 ) |
|
break; |
|
|
|
int iFileNameLength = Q_strlen( szFileName ); |
|
if ( szFileName[ iFileNameLength - 1 ] == '/' ) |
|
{ |
|
// Its a directory, so create it |
|
szFileName[ iFileNameLength - 1 ] = '\0'; |
|
|
|
char szFinalName[ MAX_PATH ]; |
|
Q_snprintf( szFinalName, sizeof( szFinalName ), "%s%c%s", pDestination, CORRECT_PATH_SEPARATOR, szFileName ); |
|
CreateDirHierarchy( szFinalName, pPath ); |
|
} |
|
} |
|
|
|
// Write Files |
|
while ( 1 ) |
|
{ |
|
szFileName[0] = '\0'; |
|
iFileSize = 0; |
|
iZipIndex = pZip->GetNextFilename( iZipIndex, szFileName, sizeof( szFileName ), iFileSize ); |
|
|
|
// If there aren't any more files then break out of this while |
|
if ( iZipIndex == -1 ) |
|
break; |
|
|
|
int iFileNameLength = Q_strlen( szFileName ); |
|
if ( szFileName[ iFileNameLength - 1 ] != '/' ) |
|
{ |
|
// It's not a directory, so write the file |
|
CUtlBuffer fileBuffer; |
|
fileBuffer.Purge(); |
|
|
|
if ( pZip->ReadFileFromZip( hZipFile, szFileName, false, fileBuffer ) ) |
|
{ |
|
char szFinalName[ MAX_PATH ]; |
|
Q_snprintf( szFinalName, sizeof( szFinalName ), "%s%c%s", pDestination, CORRECT_PATH_SEPARATOR, szFileName ); |
|
|
|
// Make sure the directory actually exists in case the ZIP doesn't list it (our zip utils create zips like this) |
|
char szFilePath[ MAX_PATH ]; |
|
Q_strncpy( szFilePath, szFinalName, sizeof(szFilePath) ); |
|
Q_StripFilename( szFilePath ); |
|
CreateDirHierarchy( szFilePath, pPath ); |
|
|
|
WriteFile( szFinalName, pPath, fileBuffer ); |
|
} |
|
} |
|
} |
|
|
|
#ifdef WIN32 |
|
::CloseHandle( hZipFile ); |
|
#else |
|
fclose( (FILE *)hZipFile ); |
|
#endif |
|
|
|
IZip::ReleaseZip( pZip ); |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::RemoveAllSearchPaths( void ) |
|
{ |
|
AUTO_LOCK( m_SearchPathsMutex ); |
|
m_SearchPaths.Purge(); |
|
//m_PackFileHandles.Purge(); |
|
} |
|
|
|
|
|
void CBaseFileSystem::LogFileAccess( const char *pFullFileName ) |
|
{ |
|
if( !m_pLogFile ) |
|
{ |
|
return; |
|
} |
|
char buf[1024]; |
|
#if BSPOUTPUT |
|
Q_snprintf( buf, sizeof( buf ), "%s\n%s\n", pShortFileName, pFullFileName); |
|
fprintf( m_pLogFile, "%s", buf ); // STEAM OK |
|
#else |
|
char cwd[MAX_FILEPATH]; |
|
getcwd( cwd, MAX_FILEPATH-1 ); |
|
Q_strcat( cwd, "\\", sizeof( cwd ) ); |
|
if( Q_strnicmp( cwd, pFullFileName, strlen( cwd ) ) == 0 ) |
|
{ |
|
const char *pFileNameWithoutExeDir = pFullFileName + strlen( cwd ); |
|
char targetPath[ MAX_FILEPATH ]; |
|
strcpy( targetPath, "%fs_target%\\" ); |
|
strcat( targetPath, pFileNameWithoutExeDir ); |
|
Q_snprintf( buf, sizeof( buf ), "copy \"%s\" \"%s\"\n", pFullFileName, targetPath ); |
|
char tmp[ MAX_FILEPATH ]; |
|
Q_strncpy( tmp, targetPath, sizeof( tmp ) ); |
|
Q_StripFilename( tmp ); |
|
fprintf( m_pLogFile, "mkdir \"%s\"\n", tmp ); // STEAM OK |
|
fprintf( m_pLogFile, "%s", buf ); // STEAM OK |
|
} |
|
else |
|
{ |
|
Assert( 0 ); |
|
} |
|
#endif |
|
} |
|
|
|
class CPackedStore; |
|
|
|
class CFileOpenInfo |
|
{ |
|
public: |
|
CFileOpenInfo( CBaseFileSystem *pFileSystem, const char *pFileName, const CBaseFileSystem::CSearchPath *path, const char *pOptions, int flags, char **ppszResolvedFilename ) : |
|
m_pFileSystem( pFileSystem ), m_pFileName( pFileName ), m_pSearchPath( path ), m_pOptions( pOptions ), m_Flags( flags ), m_ppszResolvedFilename( ppszResolvedFilename ) |
|
{ |
|
m_pFileHandle = NULL; |
|
m_ePureFileClass = ePureServerFileClass_Any; |
|
if ( m_pFileSystem->m_pPureServerWhitelist ) |
|
{ |
|
m_ePureFileClass = m_pFileSystem->m_pPureServerWhitelist->GetFileClass( pFileName ); |
|
} |
|
|
|
if ( m_ppszResolvedFilename ) |
|
*m_ppszResolvedFilename = NULL; |
|
m_pPackFile = NULL; |
|
m_pVPKFile = NULL; |
|
m_AbsolutePath[0] = '\0'; |
|
} |
|
|
|
~CFileOpenInfo() |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
void SetAbsolutePath( const char *pFormat, ... ) |
|
{ |
|
va_list marker; |
|
va_start( marker, pFormat ); |
|
V_vsnprintf( m_AbsolutePath, sizeof( m_AbsolutePath ), pFormat, marker ); |
|
va_end( marker ); |
|
|
|
V_FixSlashes( m_AbsolutePath ); |
|
} |
|
|
|
void SetResolvedFilename( const char *pStr ) |
|
{ |
|
if ( m_ppszResolvedFilename ) |
|
{ |
|
Assert( !( *m_ppszResolvedFilename ) ); |
|
*m_ppszResolvedFilename = strdup( pStr ); |
|
} |
|
} |
|
|
|
// Handles telling CFileTracker about the file we just opened so it can remember |
|
// where the file came from, and possibly calculate a CRC if necessary. |
|
void HandleFileCRCTracking( const char *pRelativeFileName ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( m_pFileSystem->m_WhitelistFileTrackingEnabled == 0 ) |
|
return; |
|
|
|
if ( m_pFileHandle && m_pSearchPath ) |
|
{ |
|
FILE *fp = m_pFileHandle->m_pFile; |
|
int64 nLength = m_pFileHandle->m_nLength; |
|
#ifdef SUPPORT_PACKED_STORE |
|
if ( m_pVPKFile ) |
|
{ |
|
m_pFileSystem->m_FileTracker2.NotePackFileAccess( pRelativeFileName, m_pSearchPath->GetPathIDString(), m_pSearchPath->m_storeId, m_pFileHandle->m_VPKHandle ); |
|
return; |
|
} |
|
#endif |
|
// we always record hashes of everything we load. we may filter later. |
|
m_pFileSystem->m_FileTracker2.NoteFileLoadedFromDisk( pRelativeFileName, m_pSearchPath->GetPathIDString(), m_pSearchPath->m_storeId, fp, nLength ); |
|
} |
|
} |
|
|
|
#ifdef SUPPORT_PACKED_STORE |
|
void SetFromPackedStoredFileHandle( const CPackedStoreFileHandle &fHandle, CBaseFileSystem *pFileSystem ) |
|
{ |
|
Assert( fHandle ); |
|
Assert( m_pFileHandle == NULL ); |
|
m_pFileHandle = new CFileHandle(pFileSystem); |
|
m_pFileHandle->m_VPKHandle = fHandle; |
|
m_pFileHandle->m_type = FT_NORMAL; |
|
m_pFileHandle->m_nLength = fHandle.m_nFileSize; |
|
} |
|
#endif |
|
|
|
public: |
|
CBaseFileSystem *m_pFileSystem; |
|
|
|
// These are output parameters. |
|
CFileHandle *m_pFileHandle; |
|
char **m_ppszResolvedFilename; |
|
|
|
CPackFile *m_pPackFile; |
|
CPackedStore *m_pVPKFile; |
|
|
|
const char *m_pFileName; |
|
const CBaseFileSystem::CSearchPath *m_pSearchPath; |
|
const char *m_pOptions; |
|
int m_Flags; |
|
|
|
EPureServerFileClass m_ePureFileClass; |
|
|
|
char m_AbsolutePath[MAX_FILEPATH]; // This is set |
|
}; |
|
|
|
|
|
void CBaseFileSystem::HandleOpenRegularFile( CFileOpenInfo &openInfo, bool bIsAbsolutePath ) |
|
{ |
|
openInfo.m_pFileHandle = NULL; |
|
|
|
int64 size; |
|
FILE *fp = Trace_FOpen( openInfo.m_AbsolutePath, openInfo.m_pOptions, openInfo.m_Flags, &size ); |
|
if ( fp ) |
|
{ |
|
if ( m_pLogFile ) |
|
{ |
|
LogFileAccess( openInfo.m_AbsolutePath ); |
|
} |
|
|
|
if ( m_bOutputDebugString ) |
|
{ |
|
#ifdef _WIN32 |
|
Plat_DebugString( "fs_debug: " ); |
|
Plat_DebugString( openInfo.m_AbsolutePath ); |
|
Plat_DebugString( "\n" ); |
|
#elif POSIX |
|
fprintf(stderr, "fs_debug: %s\n", openInfo.m_AbsolutePath ); |
|
#endif |
|
} |
|
|
|
openInfo.m_pFileHandle = new CFileHandle(this); |
|
openInfo.m_pFileHandle->m_pFile = fp; |
|
openInfo.m_pFileHandle->m_type = FT_NORMAL; |
|
openInfo.m_pFileHandle->m_nLength = size; |
|
|
|
openInfo.SetResolvedFilename( openInfo.m_AbsolutePath ); |
|
|
|
LogFileOpen( "Loose", openInfo.m_pFileName, openInfo.m_AbsolutePath ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: The base file search goes through here |
|
// Input : *path - |
|
// *pFileName - |
|
// *pOptions - |
|
// packfile - |
|
// *filetime - |
|
// Output : FileHandle_t |
|
//----------------------------------------------------------------------------- |
|
FileHandle_t CBaseFileSystem::FindFileInSearchPath( CFileOpenInfo &openInfo ) |
|
{ |
|
VPROF( "CBaseFileSystem::FindFile" ); |
|
|
|
Assert( openInfo.m_pSearchPath ); |
|
openInfo.m_pFileHandle = NULL; |
|
|
|
// Loading from pack file? |
|
CPackFile *pPackFile = openInfo.m_pSearchPath->GetPackFile(); |
|
if ( pPackFile ) |
|
{ |
|
openInfo.m_pFileHandle = pPackFile->OpenFile( openInfo.m_pFileName, openInfo.m_pOptions ); |
|
openInfo.m_pPackFile = pPackFile; |
|
if ( openInfo.m_pFileHandle ) |
|
{ |
|
char tempStr[MAX_PATH*2+2]; |
|
V_snprintf( tempStr, sizeof( tempStr ), "%s%c%s", pPackFile->m_ZipName.String(), CORRECT_PATH_SEPARATOR, openInfo.m_pFileName ); |
|
openInfo.SetResolvedFilename( tempStr ); |
|
} |
|
|
|
// If it's a BSP file, then the BSP file got CRC'd elsewhere so no need to verify stuff in there. |
|
return (FileHandle_t)openInfo.m_pFileHandle; |
|
} |
|
|
|
// Loading from VPK? |
|
#ifdef SUPPORT_PACKED_STORE |
|
CPackedStore *pVPK = openInfo.m_pSearchPath->GetPackedStore(); |
|
if ( pVPK ) |
|
{ |
|
CPackedStoreFileHandle fHandle = pVPK->OpenFile( openInfo.m_pFileName ); |
|
if ( fHandle ) |
|
{ |
|
openInfo.SetFromPackedStoredFileHandle( fHandle, this ); |
|
openInfo.SetResolvedFilename( openInfo.m_pFileName ); |
|
|
|
openInfo.m_pVPKFile = pVPK; |
|
LogFileOpen( "VPK", openInfo.m_pFileName, pVPK->BaseName() ); |
|
openInfo.HandleFileCRCTracking( openInfo.m_pFileName ); |
|
return ( FileHandle_t ) openInfo.m_pFileHandle; |
|
} |
|
return NULL; |
|
} |
|
#endif |
|
|
|
// Load loose file specified as relative filename |
|
// Convert filename to lowercase. All files in the |
|
// game logical filesystem must be accessed by lowercase name |
|
char szLowercaseFilename[ MAX_PATH ]; |
|
V_strcpy_safe( szLowercaseFilename, openInfo.m_pFileName ); |
|
V_strlower( szLowercaseFilename ); |
|
|
|
openInfo.SetAbsolutePath( "%s%s", openInfo.m_pSearchPath->GetPathString(), szLowercaseFilename ); |
|
|
|
// now have an absolute name |
|
HandleOpenRegularFile( openInfo, false ); |
|
return (FileHandle_t)openInfo.m_pFileHandle; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
FileHandle_t CBaseFileSystem::OpenForRead( const char *pFileNameT, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename ) |
|
{ |
|
VPROF( "CBaseFileSystem::OpenForRead" ); |
|
|
|
char pFileNameBuff[MAX_PATH]; |
|
const char *pFileName = pFileNameBuff; |
|
|
|
FixUpPath ( pFileNameT, pFileNameBuff, sizeof( pFileNameBuff ) ); |
|
|
|
// Try the memory cache for un-restricted searches or "GAME" items. |
|
if ( !pathID || Q_stricmp( pathID, "GAME" ) == 0 ) |
|
{ |
|
CMemoryFileBacking* pBacking = NULL; |
|
{ |
|
AUTO_LOCK( m_MemoryFileMutex ); |
|
CUtlHashtable< const char*, CMemoryFileBacking* >& table = m_MemoryFileHash; |
|
UtlHashHandle_t idx = table.Find( pFileName ); |
|
if ( idx != table.InvalidHandle() ) |
|
{ |
|
pBacking = table[idx]; |
|
pBacking->AddRef(); |
|
} |
|
} |
|
if ( pBacking ) |
|
{ |
|
if ( pBacking->m_nLength != -1 ) |
|
{ |
|
CFileHandle* pFile = new CMemoryFileHandle( this, pBacking ); |
|
pFile->m_type = strstr( pOptions, "b" ) ? FT_MEMORY_BINARY : FT_MEMORY_TEXT; |
|
return ( FileHandle_t )pFile; |
|
} |
|
else |
|
{ |
|
// length -1 == cached failure to read |
|
return ( FileHandle_t )NULL; |
|
} |
|
} |
|
else if ( ThreadInMainThread() && fs_report_sync_opens.GetInt() > 0 ) |
|
{ |
|
DevWarning("blocking load %s\n", pFileName); |
|
} |
|
} |
|
|
|
CFileOpenInfo openInfo( this, pFileName, NULL, pOptions, flags, ppszResolvedFilename ); |
|
|
|
// Already have an absolute path? |
|
// If so, don't bother iterating search paths. |
|
if ( V_IsAbsolutePath( pFileName ) ) |
|
{ |
|
openInfo.SetAbsolutePath( "%s", pFileName ); |
|
|
|
// Check if it's of the form C:/a/b/c/blah.zip/materials/blah.vtf |
|
// an absolute path can encode a zip pack file (i.e. caller wants to open the file from within the pack file) |
|
// format must strictly be ????.zip\???? |
|
// assuming a reasonable restriction that the zip must be a pre-existing search path zip |
|
char *pZipExt = V_stristr( openInfo.m_AbsolutePath, ".zip" CORRECT_PATH_SEPARATOR_S ); |
|
if ( !pZipExt ) |
|
pZipExt = V_stristr( openInfo.m_AbsolutePath, ".bsp" CORRECT_PATH_SEPARATOR_S ); |
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( !pZipExt ) |
|
pZipExt = V_stristr( openInfo.m_AbsolutePath, ".vpk" CORRECT_PATH_SEPARATOR_S ); |
|
#endif |
|
|
|
if ( pZipExt && pZipExt[5] ) |
|
{ |
|
// Cut string at the slash |
|
char *pSlash = pZipExt + 4; |
|
Assert( *pSlash == CORRECT_PATH_SEPARATOR ); |
|
*pSlash = '\0'; |
|
|
|
// want relative portion only, everything after the slash |
|
char *pRelativeFileName = pSlash + 1; |
|
|
|
// Find the zip or VPK in the search paths |
|
for ( int i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
|
|
// In VPK? |
|
#if defined( SUPPORT_PACKED_STORE ) |
|
CPackedStore *pVPK = m_SearchPaths[i].GetPackedStore(); |
|
if ( pVPK ) |
|
{ |
|
if ( V_stricmp( pVPK->FullPathName(), openInfo.m_AbsolutePath ) == 0 ) |
|
{ |
|
CPackedStoreFileHandle fHandle = pVPK->OpenFile( pRelativeFileName ); |
|
if ( fHandle ) |
|
{ |
|
openInfo.m_pSearchPath = &m_SearchPaths[i]; |
|
openInfo.SetFromPackedStoredFileHandle( fHandle, this ); |
|
} |
|
break; |
|
} |
|
continue; |
|
} |
|
#endif |
|
|
|
// In .zip? |
|
CPackFile *pPackFile = m_SearchPaths[i].GetPackFile(); |
|
if ( pPackFile ) |
|
{ |
|
if ( Q_stricmp( pPackFile->m_ZipName.Get(), openInfo.m_AbsolutePath ) == 0 ) |
|
{ |
|
openInfo.m_pSearchPath = &m_SearchPaths[i]; |
|
openInfo.m_pFileHandle = pPackFile->OpenFile( pRelativeFileName, openInfo.m_pOptions ); |
|
openInfo.m_pPackFile = pPackFile; |
|
break; |
|
} |
|
} |
|
} |
|
|
|
if ( openInfo.m_pFileHandle ) |
|
{ |
|
openInfo.SetResolvedFilename( openInfo.m_pFileName ); |
|
openInfo.HandleFileCRCTracking( pRelativeFileName ); |
|
} |
|
return (FileHandle_t)openInfo.m_pFileHandle; |
|
} |
|
|
|
// Otherwise, it must be a regular file, specified by absolute filename |
|
HandleOpenRegularFile( openInfo, true ); |
|
|
|
// !FIXME! We probably need to deal with CRC tracking, right? |
|
|
|
return (FileHandle_t)openInfo.m_pFileHandle; |
|
} |
|
|
|
// Run through all the search paths. |
|
PathTypeFilter_t pathFilter = FILTER_NONE; |
|
if ( IsX360() ) |
|
{ |
|
if ( flags & FSOPEN_NEVERINPACK ) |
|
{ |
|
pathFilter = FILTER_CULLPACK; |
|
} |
|
else if ( m_DVDMode == DVDMODE_STRICT ) |
|
{ |
|
// most all files on the dvd are expected to be in the pack |
|
// don't allow disk paths to be searched, which is very expensive on the dvd |
|
pathFilter = FILTER_CULLNONPACK; |
|
} |
|
} |
|
|
|
CSearchPathsIterator iter( this, &pFileName, pathID, pathFilter ); |
|
for ( openInfo.m_pSearchPath = iter.GetFirst(); openInfo.m_pSearchPath != NULL; openInfo.m_pSearchPath = iter.GetNext() ) |
|
{ |
|
FileHandle_t filehandle = FindFileInSearchPath( openInfo ); |
|
if ( filehandle ) |
|
{ |
|
// Check if search path is excluded due to pure server white list, |
|
// then we should make a note of this fact, and keep searching |
|
if ( !openInfo.m_pSearchPath->m_bIsTrustedForPureServer && openInfo.m_ePureFileClass == ePureServerFileClass_AnyTrusted ) |
|
{ |
|
#ifdef PURE_SERVER_DEBUG_SPEW |
|
Msg( "Ignoring %s from %s for pure server operation\n", openInfo.m_pFileName, openInfo.m_pSearchPath->GetDebugString() ); |
|
#endif |
|
|
|
m_FileTracker2.NoteFileIgnoredForPureServer( openInfo.m_pFileName, pathID, openInfo.m_pSearchPath->m_storeId ); |
|
Close( filehandle ); |
|
openInfo.m_pFileHandle = NULL; |
|
if ( ppszResolvedFilename && *ppszResolvedFilename ) |
|
{ |
|
free( *ppszResolvedFilename ); |
|
*ppszResolvedFilename = NULL; |
|
} |
|
continue; |
|
} |
|
|
|
// |
|
openInfo.HandleFileCRCTracking( openInfo.m_pFileName ); |
|
return filehandle; |
|
} |
|
} |
|
|
|
LogFileOpen( "[Failed]", pFileName, "" ); |
|
return ( FileHandle_t )0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
FileHandle_t CBaseFileSystem::OpenForWrite( const char *pFileName, const char *pOptions, const char *pathID ) |
|
{ |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pFileName, pathID, tempPathID ); |
|
|
|
if ( ThreadInMainThread() && fs_report_sync_opens.GetInt() ) |
|
{ |
|
DevWarning("blocking write %s\n", pFileName); |
|
} |
|
|
|
// Opening for write or append uses the write path |
|
// Unless an absolute path is specified... |
|
const char *pTmpFileName; |
|
char szScratchFileName[MAX_PATH]; |
|
if ( Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
pTmpFileName = pFileName; |
|
} |
|
else |
|
{ |
|
ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pFileName, pathID ); |
|
pTmpFileName = szScratchFileName; |
|
} |
|
|
|
int64 size; |
|
FILE *fp = Trace_FOpen( pTmpFileName, pOptions, 0, &size ); |
|
if ( !fp ) |
|
{ |
|
return ( FileHandle_t )0; |
|
} |
|
|
|
CFileHandle *fh = new CFileHandle( this ); |
|
fh->m_nLength = size; |
|
fh->m_type = FT_NORMAL; |
|
fh->m_pFile = fp; |
|
|
|
return ( FileHandle_t )fh; |
|
} |
|
|
|
|
|
// This looks for UNC-type filename specifiers, which should be used instead of |
|
// passing in path ID. So if it finds //mod/cfg/config.cfg, it translates |
|
// pFilename to "cfg/config.cfg" and pPathID to "mod" (mod is placed in tempPathID). |
|
void CBaseFileSystem::ParsePathID( const char* &pFilename, const char* &pPathID, char tempPathID[MAX_PATH] ) |
|
{ |
|
tempPathID[0] = 0; |
|
|
|
if ( !pFilename || pFilename[0] == 0 ) |
|
return; |
|
|
|
// FIXME: Pain! Backslashes are used to denote network drives, forward to denote path ids |
|
// HOORAY! We call FixSlashes everywhere. That will definitely not work |
|
// I'm not changing it yet though because I don't know how painful the bugs would be that would be generated |
|
bool bIsForwardSlash = ( pFilename[0] == '/' && pFilename[1] == '/' ); |
|
// bool bIsBackwardSlash = ( pFilename[0] == '\\' && pFilename[1] == '\\' ); |
|
if ( !bIsForwardSlash ) //&& !bIsBackwardSlash ) |
|
return; |
|
|
|
// They're specifying two path IDs. Ignore the one passed-in. |
|
if ( pPathID ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Specified two path IDs (%s, %s).\n", pFilename, pPathID ); |
|
} |
|
|
|
// Parse out the path ID. |
|
const char *pIn = &pFilename[2]; |
|
char *pOut = tempPathID; |
|
while ( *pIn && !PATHSEPARATOR( *pIn ) && (pOut - tempPathID) < (MAX_PATH-1) ) |
|
{ |
|
*pOut++ = *pIn++; |
|
} |
|
|
|
*pOut = 0; |
|
|
|
if ( tempPathID[0] == '*' ) |
|
{ |
|
// * means NULL. |
|
pPathID = NULL; |
|
} |
|
else |
|
{ |
|
pPathID = tempPathID; |
|
} |
|
|
|
// Move pFilename up past the part with the path ID. |
|
if ( *pIn == 0 ) |
|
pFilename = pIn; |
|
else |
|
pFilename = pIn + 1; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
FileHandle_t CBaseFileSystem::Open( const char *pFileName, const char *pOptions, const char *pathID ) |
|
{ |
|
return OpenEx( pFileName, pOptions, 0, pathID ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
FileHandle_t CBaseFileSystem::OpenEx( const char *pFileName, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s(%s, %s, %u %s )", __FUNCTION__, tmDynamicString( TELEMETRY_LEVEL0, pFileName ), tmDynamicString( TELEMETRY_LEVEL0, pOptions ), flags, tmDynamicString( TELEMETRY_LEVEL0, pathID ) ); |
|
|
|
VPROF_BUDGET( "CBaseFileSystem::Open", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
|
|
if ( !pFileName ) |
|
return (FileHandle_t)0; |
|
|
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
if ( ThreadInMainThread() && fs_report_sync_opens.GetInt() > 1 ) |
|
{ |
|
::Warning( "Open( %s )\n", pFileName ); |
|
} |
|
|
|
// Allow for UNC-type syntax to specify the path ID. |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pFileName, pathID, tempPathID ); |
|
|
|
|
|
// Try each of the search paths in succession |
|
// FIXME: call createdirhierarchy upon opening for write. |
|
if ( strstr( pOptions, "r" ) && !strstr( pOptions, "+" ) ) |
|
{ |
|
return OpenForRead( pFileName, pOptions, flags, pathID, ppszResolvedFilename ); |
|
} |
|
|
|
return OpenForWrite( pFileName, pOptions, pathID ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::Close( FileHandle_t file ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::Close", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
if ( !file ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Close NULL file handle!\n" ); |
|
return; |
|
} |
|
|
|
delete (CFileHandle*)file; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::Seek( FileHandle_t file, int pos, FileSystemSeek_t whence ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (pos=%d, whence=%d)", __FUNCTION__, pos, whence ); |
|
|
|
VPROF_BUDGET( "CBaseFileSystem::Seek", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "Tried to Seek NULL file handle!\n" ); |
|
return; |
|
} |
|
|
|
fh->Seek( pos, whence ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : file - |
|
// Output : unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CBaseFileSystem::Tell( FileHandle_t file ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::Tell", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
|
|
if ( !file ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Tell NULL file handle!\n" ); |
|
return 0; |
|
} |
|
|
|
|
|
// Pack files are relative |
|
return (( CFileHandle *)file)->Tell(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : file - |
|
// Output : unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CBaseFileSystem::Size( FileHandle_t file ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
|
|
VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
if ( !file ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Size NULL file handle!\n" ); |
|
return 0; |
|
} |
|
|
|
return ((CFileHandle *)file)->Size(); |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : file - |
|
// Output : unsigned int |
|
//----------------------------------------------------------------------------- |
|
unsigned int CBaseFileSystem::Size( const char* pFileName, const char *pPathID ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
// handle the case where no name passed... |
|
if ( !pFileName || !pFileName[0] ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Size NULL filename!\n" ); |
|
return 0; |
|
} |
|
|
|
// Ok, fall through to the fast path. |
|
unsigned result = 0; |
|
FileHandle_t h = Open( pFileName, "rb", pPathID ); |
|
if ( h ) |
|
{ |
|
result = Size( h ); |
|
Close(h); |
|
} |
|
|
|
return result; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *path - |
|
// *pFileName - |
|
// Output : long |
|
//----------------------------------------------------------------------------- |
|
time_t CBaseFileSystem::FastFileTime( const CSearchPath *path, const char *pFileName ) |
|
{ |
|
struct _stat buf; |
|
|
|
if ( path->GetPackFile() ) |
|
{ |
|
// If we found the file: |
|
if ( path->GetPackFile()->ContainsFile( pFileName ) ) |
|
{ |
|
return (path->GetPackFile()->m_lPackFileTime); |
|
} |
|
} |
|
#ifdef SUPPORT_PACKED_STORE |
|
else if ( path->GetPackedStore() ) |
|
{ |
|
// Hm, should we support this in some way? |
|
return 0L; |
|
} |
|
#endif |
|
else |
|
{ |
|
// Is it an absolute path? |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
|
|
if ( Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
Q_strncpy( pTmpFileName, pFileName, sizeof( pTmpFileName ) ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", path->GetPathString(), pFileName ); |
|
} |
|
|
|
Q_FixSlashes( pTmpFileName ); |
|
|
|
if( FS_stat( pTmpFileName, &buf ) != -1 ) |
|
{ |
|
return buf.st_mtime; |
|
} |
|
#if defined(LINUX) || defined(PLATFORM_BSD) |
|
char caseFixedName[ MAX_PATH ]; |
|
bool found = findFileInDirCaseInsensitive_safe( pTmpFileName, caseFixedName ); |
|
if ( found && FS_stat( caseFixedName, &buf ) != -1 ) |
|
{ |
|
return buf.st_mtime; |
|
} |
|
#endif |
|
} |
|
|
|
return ( 0L ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::EndOfFile( FileHandle_t file ) |
|
{ |
|
if ( !file ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to EndOfFile NULL file handle!\n" ); |
|
return true; |
|
} |
|
|
|
return ((CFileHandle *)file)->EndOfFile(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseFileSystem::Read( void *pOutput, int size, FileHandle_t file ) |
|
{ |
|
return ReadEx( pOutput, size, size, file ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseFileSystem::ReadEx( void *pOutput, int destSize, int size, FileHandle_t file ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s (%d bytes)", __FUNCTION__, size ); |
|
|
|
VPROF_BUDGET( "CBaseFileSystem::Read", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
if ( !file ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Read NULL file handle!\n" ); |
|
return 0; |
|
} |
|
if ( size < 0 ) |
|
{ |
|
return 0; |
|
} |
|
return ((CFileHandle*)file)->Read(pOutput, destSize, size ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Blow away current readers |
|
// Input : - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::UnloadCompiledKeyValues() |
|
{ |
|
#ifndef DEDICATED |
|
for ( int i = 0; i < IFileSystem::NUM_PRELOAD_TYPES; ++i ) |
|
{ |
|
delete m_PreloadData[ i ].m_pReader; |
|
m_PreloadData[ i ].m_pReader = NULL; |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Put data file into list of at specific slot, will be loaded when ::SetupPreloadData() gets called |
|
// Input : type - |
|
// *archiveFile - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::LoadCompiledKeyValues( KeyValuesPreloadType_t type, char const *archiveFile ) |
|
{ |
|
// Just add to list for appropriate loader |
|
Assert( type >= 0 && type < IFileSystem::NUM_PRELOAD_TYPES ); |
|
CompiledKeyValuesPreloaders_t& loader = m_PreloadData[ type ]; |
|
Assert( loader.m_CacheFile == 0 ); |
|
loader.m_CacheFile = FindOrAddFileName( archiveFile ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Takes a passed in KeyValues& head and fills in the precompiled keyvalues data into it. |
|
// Input : head - |
|
// type - |
|
// *filename - |
|
// *pPathID - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::LoadKeyValues( KeyValues& head, KeyValuesPreloadType_t type, char const *filename, char const *pPathID /*= 0*/ ) |
|
{ |
|
bool bret = true; |
|
|
|
#ifndef DEDICATED |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( filename, pPathID, tempPathID ); |
|
|
|
// FIXME: THIS STUFF DOESN'T TRACK pPathID AT ALL RIGHT NOW!!!!! |
|
if ( !m_PreloadData[ type ].m_pReader || !m_PreloadData[ type ].m_pReader->InstanceInPlace( head, filename ) ) |
|
{ |
|
bret = head.LoadFromFile( this, filename, pPathID ); |
|
} |
|
return bret; |
|
#else |
|
bret = head.LoadFromFile( this, filename, pPathID ); |
|
return bret; |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: If the "PreloadedData" hasn't been purged, then this'll try and instance the KeyValues using the fast path of |
|
/// compiled keyvalues loaded during startup. |
|
// Otherwise, it'll just fall through to the regular KeyValues loading routines |
|
// Input : type - |
|
// *filename - |
|
// *pPathID - |
|
// Output : KeyValues |
|
//----------------------------------------------------------------------------- |
|
KeyValues *CBaseFileSystem::LoadKeyValues( KeyValuesPreloadType_t type, char const *filename, char const *pPathID /*= 0*/ ) |
|
{ |
|
KeyValues *kv = NULL; |
|
|
|
if ( !m_PreloadData[ type ].m_pReader ) |
|
{ |
|
kv = new KeyValues( filename ); |
|
if ( kv ) |
|
{ |
|
kv->LoadFromFile( this, filename, pPathID ); |
|
} |
|
} |
|
else |
|
{ |
|
#ifndef DEDICATED |
|
// FIXME: THIS STUFF DOESN'T TRACK pPathID AT ALL RIGHT NOW!!!!! |
|
kv = m_PreloadData[ type ].m_pReader->Instance( filename ); |
|
if ( !kv ) |
|
{ |
|
kv = new KeyValues( filename ); |
|
if ( kv ) |
|
{ |
|
kv->LoadFromFile( this, filename, pPathID ); |
|
} |
|
} |
|
#endif |
|
} |
|
return kv; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: This is the fallback method of reading the name of the first key in the file |
|
// Input : *filename - |
|
// *pPathID - |
|
// *rootName - |
|
// bufsize - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::LookupKeyValuesRootKeyName( char const *filename, char const *pPathID, char *rootName, size_t bufsize ) |
|
{ |
|
if ( FileExists( filename, pPathID ) ) |
|
{ |
|
// open file and get shader name |
|
FileHandle_t hFile = Open( filename, "r", pPathID ); |
|
if ( hFile == FILESYSTEM_INVALID_HANDLE ) |
|
{ |
|
return false; |
|
} |
|
|
|
char buf[ 128 ]; |
|
ReadLine( buf, sizeof( buf ), hFile ); |
|
Close( hFile ); |
|
|
|
// The name will possibly come in as "foo"\n |
|
|
|
// So we need to strip the starting " character |
|
char *pStart = buf; |
|
if ( *pStart == '\"' ) |
|
{ |
|
++pStart; |
|
} |
|
// Then copy the rest of the string |
|
Q_strncpy( rootName, pStart, bufsize ); |
|
|
|
// And then strip off the \n and the " character at the end, in that order |
|
int len = Q_strlen( pStart ); |
|
while ( len > 0 && rootName[ len - 1 ] == '\n' ) |
|
{ |
|
rootName[ len - 1 ] = 0; |
|
--len; |
|
} |
|
while ( len > 0 && rootName[ len - 1 ] == '\"' ) |
|
{ |
|
rootName[ len - 1 ] = 0; |
|
--len; |
|
} |
|
} |
|
else |
|
{ |
|
return false; |
|
} |
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Tries to look up the name of the first key in the file from the compiled data |
|
// Input : type - |
|
// *outbuf - |
|
// bufsize - |
|
// *filename - |
|
// *pPathID - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::ExtractRootKeyName( KeyValuesPreloadType_t type, char *outbuf, size_t bufsize, char const *filename, char const *pPathID /*= 0*/ ) |
|
{ |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( filename, pPathID, tempPathID ); |
|
|
|
bool bret = true; |
|
|
|
if ( !m_PreloadData[ type ].m_pReader ) |
|
{ |
|
// Use fallback |
|
bret = LookupKeyValuesRootKeyName( filename, pPathID, outbuf, bufsize ); |
|
} |
|
else |
|
{ |
|
#ifndef DEDICATED |
|
// Try to use cache |
|
bret = m_PreloadData[ type ].m_pReader->LookupKeyValuesRootKeyName( filename, outbuf, bufsize ); |
|
if ( !bret ) |
|
{ |
|
// Not in cache, use fallback |
|
bret = LookupKeyValuesRootKeyName( filename, pPathID, outbuf, bufsize ); |
|
} |
|
#endif |
|
} |
|
return bret; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::SetupPreloadData() |
|
{ |
|
int i; |
|
|
|
for ( i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
CPackFile* pf = m_SearchPaths[i].GetPackFile(); |
|
if ( pf ) |
|
{ |
|
pf->SetupPreloadData(); |
|
} |
|
} |
|
|
|
#ifndef DEDICATED |
|
if ( !CommandLine()->FindParm( "-fs_nopreloaddata" ) ) |
|
{ |
|
// Loads in the precompiled keyvalues data for each type |
|
for ( i = 0; i < NUM_PRELOAD_TYPES; ++i ) |
|
{ |
|
CompiledKeyValuesPreloaders_t& preloader = m_PreloadData[ i ]; |
|
Assert( !preloader.m_pReader ); |
|
|
|
char fn[MAX_PATH]; |
|
if ( preloader.m_CacheFile != 0 && |
|
String( preloader.m_CacheFile, fn, sizeof( fn ) ) ) |
|
{ |
|
preloader.m_pReader = new CCompiledKeyValuesReader(); |
|
preloader.m_pReader->LoadFile( fn ); |
|
} |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::DiscardPreloadData() |
|
{ |
|
int i; |
|
for( i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
CPackFile* pf = m_SearchPaths[i].GetPackFile(); |
|
if ( pf ) |
|
{ |
|
pf->DiscardPreloadData(); |
|
} |
|
} |
|
|
|
UnloadCompiledKeyValues(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseFileSystem::Write( void const* pInput, int size, FileHandle_t file ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::Write", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
|
|
AUTOBLOCKREPORTER_FH( Write, this, true, file, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_WRITE ); |
|
|
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Write NULL file handle!\n" ); |
|
return 0; |
|
} |
|
return fh->Write( pInput, size ); |
|
|
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
int CBaseFileSystem::FPrintf( FileHandle_t file, const char *pFormat, ... ) |
|
{ |
|
va_list args; |
|
va_start( args, pFormat ); |
|
VPROF_BUDGET( "CBaseFileSystem::FPrintf", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to FPrintf NULL file handle!\n" ); |
|
return 0; |
|
} |
|
/* |
|
if ( !fh->GetFileHandle() ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to FPrintf NULL file pointer inside valid file handle!\n" ); |
|
return 0; |
|
} |
|
*/ |
|
|
|
|
|
char buffer[65535]; |
|
int len = vsnprintf( buffer, sizeof( buffer), pFormat, args ); |
|
len = fh->Write( buffer, len ); |
|
//int len = FS_vfprintf( fh->GetFileHandle() , pFormat, args ); |
|
va_end( args ); |
|
|
|
|
|
return len; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::SetBufferSize( FileHandle_t file, unsigned nBytes ) |
|
{ |
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to SetBufferSize NULL file handle!\n" ); |
|
return; |
|
} |
|
fh->SetBufferSize( nBytes ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::IsOk( FileHandle_t file ) |
|
{ |
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to IsOk NULL file handle!\n" ); |
|
return false; |
|
} |
|
|
|
return fh->IsOK(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::Flush( FileHandle_t file ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::Flush", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to Flush NULL file handle!\n" ); |
|
return; |
|
} |
|
|
|
fh->Flush(); |
|
|
|
} |
|
|
|
bool CBaseFileSystem::Precache( const char *pFileName, const char *pPathID) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
// Allow for UNC-type syntax to specify the path ID. |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pFileName, pPathID, tempPathID ); |
|
Assert( pPathID ); |
|
|
|
// Really simple, just open, the file, read it all in and close it. |
|
// We probably want to use file mapping to do this eventually. |
|
FileHandle_t f = Open( pFileName, "rb", pPathID ); |
|
if ( !f ) |
|
return false; |
|
|
|
// not for consoles, the read discard is a negative benefit, slow and clobbers small drive caches |
|
if ( IsPC() ) |
|
{ |
|
char buffer[16384]; |
|
while( sizeof(buffer) == Read(buffer,sizeof(buffer),f) ); |
|
} |
|
|
|
Close( f ); |
|
|
|
return true; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
char *CBaseFileSystem::ReadLine( char *pOutput, int maxChars, FileHandle_t file ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::ReadLine", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
CFileHandle *fh = ( CFileHandle *)file; |
|
if ( !fh ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "FS: Tried to ReadLine NULL file handle!\n" ); |
|
return NULL; |
|
} |
|
m_Stats.nReads++; |
|
|
|
int nRead = 0; |
|
|
|
// Read up to maxchars: |
|
while( nRead < ( maxChars - 1 ) ) |
|
{ |
|
// Are we at the end of the file? |
|
if( 1 != fh->Read( pOutput + nRead, 1 ) ) |
|
break; |
|
|
|
// Translate for text mode files: |
|
if( ( fh->m_type == FT_PACK_TEXT || fh->m_type == FT_MEMORY_TEXT ) && pOutput[nRead] == '\r' ) |
|
{ |
|
// Ignore \r |
|
continue; |
|
} |
|
|
|
// We're done when we hit a '\n' |
|
if( pOutput[nRead] == '\n' ) |
|
{ |
|
nRead++; |
|
break; |
|
} |
|
|
|
// Get outta here if we find a NULL. |
|
if( pOutput[nRead] == '\0' ) |
|
{ |
|
pOutput[nRead] = '\n'; |
|
nRead++; |
|
break; |
|
} |
|
|
|
nRead++; |
|
} |
|
|
|
if( nRead < maxChars ) |
|
pOutput[nRead] = '\0'; |
|
|
|
|
|
m_Stats.nBytesRead += nRead; |
|
return ( nRead ) ? pOutput : NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFileName - |
|
// Output : long |
|
//----------------------------------------------------------------------------- |
|
time_t CBaseFileSystem::GetFileTime( const char *pFileName, const char *pPathID ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::GetFileTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
|
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
CSearchPathsIterator iter( this, &pFileName, pPathID ); |
|
|
|
char tempFileName[MAX_PATH]; |
|
Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) ); |
|
Q_FixSlashes( tempFileName ); |
|
#ifdef _WIN32 |
|
Q_strlower( tempFileName ); |
|
#endif |
|
|
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
time_t ft = FastFileTime( pSearchPath, tempFileName ); |
|
if ( ft != 0L ) |
|
{ |
|
if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() ) |
|
{ |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
if ( strchr( tempFileName, ':' ) ) |
|
{ |
|
Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName ); |
|
} |
|
|
|
Q_FixSlashes( tempFileName ); |
|
|
|
LogAccessToFile( "filetime", pTmpFileName, "" ); |
|
} |
|
|
|
return ft; |
|
} |
|
} |
|
return (time_t)0L; |
|
} |
|
|
|
time_t CBaseFileSystem::GetPathTime( const char *pFileName, const char *pPathID ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::GetPathTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
|
|
CSearchPathsIterator iter( this, &pFileName, pPathID ); |
|
|
|
char tempFileName[MAX_PATH]; |
|
Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) ); |
|
Q_FixSlashes( tempFileName ); |
|
#ifdef _WIN32 |
|
Q_strlower( tempFileName ); |
|
#endif |
|
|
|
time_t pathTime = 0L; |
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
time_t ft = FastFileTime( pSearchPath, tempFileName ); |
|
if ( ft > pathTime ) |
|
pathTime = ft; |
|
if ( ft != 0L ) |
|
{ |
|
if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() ) |
|
{ |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
if ( strchr( tempFileName, ':' ) ) |
|
{ |
|
Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) ); |
|
} |
|
else |
|
{ |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName ); |
|
} |
|
|
|
Q_FixSlashes( tempFileName ); |
|
|
|
LogAccessToFile( "filetime", pTmpFileName, "" ); |
|
} |
|
} |
|
} |
|
return pathTime; |
|
} |
|
|
|
void CBaseFileSystem::MarkAllCRCsUnverified() |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
m_FileTracker2.MarkAllCRCsUnverified(); |
|
} |
|
|
|
|
|
void CBaseFileSystem::CacheFileCRCs( const char *pPathname, ECacheCRCType eType, IFileList *pFilter ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
} |
|
|
|
EFileCRCStatus CBaseFileSystem::CheckCachedFileHash( const char *pPathID, const char *pRelativeFilename, int nFileFraction, FileHash_t *pFileHash ) |
|
{ |
|
return m_FileTracker2.CheckCachedFileHash( pPathID, pRelativeFilename, nFileFraction, pFileHash ); |
|
} |
|
|
|
|
|
void CBaseFileSystem::EnableWhitelistFileTracking( bool bEnable, bool bCacheAllVPKHashes, bool bRecalculateAndCheckHashes ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
m_WhitelistFileTrackingEnabled = false; |
|
return; |
|
} |
|
|
|
if ( m_WhitelistFileTrackingEnabled != -1 ) |
|
{ |
|
Error( "CBaseFileSystem::EnableWhitelistFileTracking called more than once." ); |
|
} |
|
|
|
m_WhitelistFileTrackingEnabled = bEnable; |
|
if ( m_WhitelistFileTrackingEnabled && bCacheAllVPKHashes ) |
|
{ |
|
CacheAllVPKFileHashes( bCacheAllVPKHashes, bRecalculateAndCheckHashes ); |
|
} |
|
} |
|
|
|
|
|
void CBaseFileSystem::CacheAllVPKFileHashes( bool bCacheAllVPKHashes, bool bRecalculateAndCheckHashes ) |
|
{ |
|
#ifdef SUPPORT_PACKED_STORE |
|
for ( int i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
CPackedStore *pVPK = m_SearchPaths[i].GetPackedStore(); |
|
if ( pVPK == NULL ) |
|
continue; |
|
if ( !pVPK->BTestDirectoryHash() ) |
|
{ |
|
Msg( "VPK dir file hash does not match. File corrupted or modified.\n" ); |
|
} |
|
if ( !pVPK->BTestMasterChunkHash() ) |
|
{ |
|
Msg( "VPK chunk hash hash does not match. File corrupted or modified.\n" ); |
|
} |
|
|
|
CUtlVector<ChunkHashFraction_t> &vecChunkHash = pVPK->AccessPackFileHashes(); |
|
CPackedStoreFileHandle fhandle = pVPK->GetHandleForHashingFiles(); |
|
CUtlVector<ChunkHashFraction_t> vecChunkHashFractionCopy; |
|
if ( bRecalculateAndCheckHashes ) |
|
{ |
|
CUtlString sPackFileErrors; |
|
pVPK->GetPackFileLoadErrorSummary( sPackFileErrors ); |
|
|
|
if ( sPackFileErrors.Length() ) |
|
{ |
|
Msg( "Errors occured loading files.\n" ); |
|
Msg( "%s", sPackFileErrors.String() ); |
|
Msg( "Verify integrity of your game files, perform memory and disk diagnostics on your system.\n" ); |
|
} |
|
else |
|
Msg( "No VPK Errors occured loading files.\n" ); |
|
|
|
Msg( "Recomputing all VPK file hashes.\n" ); |
|
vecChunkHashFractionCopy.Swap( vecChunkHash ); |
|
} |
|
int cFailures = 0; |
|
if ( vecChunkHash.Count() == 0 ) |
|
{ |
|
if ( vecChunkHashFractionCopy.Count() == 0 ) |
|
Msg( "File hash information not found: Hashing all VPK files for pure server operation.\n" ); |
|
pVPK->HashAllChunkFiles(); |
|
if ( vecChunkHashFractionCopy.Count() != 0 ) |
|
{ |
|
if ( vecChunkHash.Count() != vecChunkHashFractionCopy.Count() ) |
|
{ |
|
Msg( "VPK hash count does not match. VPK content may be corrupt.\n" ); |
|
} |
|
else if ( Q_memcmp( vecChunkHash.Base(), vecChunkHashFractionCopy.Base(), vecChunkHash.Count()*sizeof(vecChunkHash[0])) != 0 ) |
|
{ |
|
Msg( "VPK hashes do not match. VPK content may be corrupt.\n" ); |
|
// find the actual mismatch |
|
FOR_EACH_VEC( vecChunkHashFractionCopy, iHash ) |
|
{ |
|
if ( Q_memcmp( vecChunkHashFractionCopy[iHash].m_md5contents.bits, vecChunkHash[iHash].m_md5contents.bits, sizeof( vecChunkHashFractionCopy[iHash].m_md5contents.bits ) ) != 0 ) |
|
{ |
|
Msg( "VPK hash for file %d failure at offset %x.\n", vecChunkHashFractionCopy[iHash].m_nPackFileNumber, vecChunkHashFractionCopy[iHash].m_nFileFraction ); |
|
cFailures++; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
if ( bCacheAllVPKHashes ) |
|
{ |
|
Msg( "Loaded %d VPK file hashes from %s for pure server operation.\n", vecChunkHash.Count(), pVPK->FullPathName() ); |
|
FOR_EACH_VEC( vecChunkHash, i ) |
|
{ |
|
m_FileTracker2.AddFileHashForVPKFile( vecChunkHash[i].m_nPackFileNumber, vecChunkHash[i].m_nFileFraction, vecChunkHash[i].m_cbChunkLen, vecChunkHash[i].m_md5contents, fhandle ); |
|
} |
|
} |
|
else |
|
{ |
|
if ( cFailures == 0 && vecChunkHash.Count() == vecChunkHashFractionCopy.Count() ) |
|
Msg( "File hashes checked. %d matches. no failures.\n", vecChunkHash.Count() ); |
|
else |
|
Msg( "File hashes checked. %d matches. %d failures.\n", vecChunkHash.Count(), cFailures ); |
|
} |
|
} |
|
#endif |
|
} |
|
|
|
|
|
bool CBaseFileSystem::CheckVPKFileHash( int PackFileID, int nPackFileNumber, int nFileFraction, MD5Value_t &md5Value ) |
|
{ |
|
#ifdef SUPPORT_PACKED_STORE |
|
for ( int i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
CPackedStore *pVPK = m_SearchPaths[i].GetPackedStore(); |
|
if ( pVPK == NULL || pVPK->m_PackFileID != PackFileID ) |
|
continue; |
|
ChunkHashFraction_t fileHashFraction; |
|
if ( pVPK->FindFileHashFraction( nPackFileNumber, nFileFraction, fileHashFraction ) ) |
|
{ |
|
CPackedStoreFileHandle fhandle = pVPK->GetHandleForHashingFiles(); |
|
fhandle.m_nFileNumber = nPackFileNumber; |
|
char szFileName[MAX_PATH]; |
|
|
|
pVPK->GetPackFileName( fhandle, szFileName, sizeof(szFileName) ); |
|
|
|
char hex[ 34 ]; |
|
Q_memset( hex, 0, sizeof( hex ) ); |
|
Q_binarytohex( (const byte *)md5Value.bits, sizeof( md5Value.bits ), hex, sizeof( hex ) ); |
|
|
|
char hex2[ 34 ]; |
|
Q_memset( hex2, 0, sizeof( hex2 ) ); |
|
Q_binarytohex( (const byte *)fileHashFraction.m_md5contents.bits, sizeof( fileHashFraction.m_md5contents.bits ), hex2, sizeof( hex2 ) ); |
|
|
|
if ( Q_memcmp( fileHashFraction.m_md5contents.bits, md5Value.bits, sizeof(md5Value.bits) ) != 0 ) |
|
{ |
|
Msg( "File %s offset %x hash %s does not match ( should be %s ) \n", szFileName, nFileFraction, hex, hex2 ); |
|
return false; |
|
} |
|
else |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
#else |
|
Error("CBaseFileSystem::CheckVPKFileHash should not be called, SUPPORT_PACKED_STORE not defined" ); |
|
#endif |
|
return false; |
|
} |
|
|
|
void CBaseFileSystem::RegisterFileWhitelist( IPureServerWhitelist *pWhiteList, IFileList **pFilesToReload ) |
|
{ |
|
if ( pFilesToReload ) |
|
*pFilesToReload = NULL; |
|
|
|
if ( IsX360() ) |
|
{ |
|
return; |
|
} |
|
|
|
if ( m_pPureServerWhitelist ) |
|
{ |
|
m_pPureServerWhitelist->Release(); |
|
m_pPureServerWhitelist = NULL; |
|
} |
|
if ( pWhiteList ) |
|
{ |
|
pWhiteList->AddRef(); |
|
m_pPureServerWhitelist = pWhiteList; |
|
} |
|
|
|
// update which search paths are considered trusted |
|
FOR_EACH_VEC( m_SearchPaths, i ) |
|
{ |
|
SetSearchPathIsTrustedSource( &m_SearchPaths[i] ); |
|
} |
|
|
|
// See if we need to reload any files |
|
if ( pFilesToReload ) |
|
*pFilesToReload = m_FileTracker2.GetFilesToUnloadForWhitelistChange( pWhiteList ); |
|
} |
|
|
|
void CBaseFileSystem::NotifyFileUnloaded( const char *pszFilename, const char *pPathId ) |
|
{ |
|
m_FileTracker2.NoteFileUnloaded( pszFilename, pPathId ); |
|
} |
|
|
|
void CBaseFileSystem::SetSearchPathIsTrustedSource( CSearchPath *pSearchPath ) |
|
{ |
|
#if 1 |
|
pSearchPath->m_bIsTrustedForPureServer = true; |
|
#else // Broken, I am lazy to fix this |
|
// Most paths are not considered trusted |
|
pSearchPath->m_bIsTrustedForPureServer = false; |
|
|
|
// If we don't have a pure server whitelist, we cannot say that any |
|
// particular files are trusted. (But then again, all files will be |
|
// accepted, so it won't really matter.) |
|
if ( m_pPureServerWhitelist == NULL ) |
|
return; |
|
|
|
// Treat map packs as trusted, because we will send the CRC of the map pack to the server |
|
if ( pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath ) |
|
{ |
|
#ifdef PURE_SERVER_DEBUG_SPEW |
|
Msg( "Setting map pack search path %s as trusted\n", pSearchPath->GetDebugString() ); |
|
#endif |
|
pSearchPath->m_bIsTrustedForPureServer = true; |
|
return; |
|
} |
|
|
|
#ifdef SUPPORT_PACKED_STORE |
|
// Only signed VPK's can be trusted |
|
CPackedStoreRefCount *pVPK = pSearchPath->GetPackedStore(); |
|
if ( pVPK == NULL ) |
|
{ |
|
#ifdef PURE_SERVER_DEBUG_SPEW |
|
Msg( "Setting %s as untrusted (loose files)\n", pSearchPath->GetDebugString() ); |
|
#endif |
|
return; |
|
} |
|
if ( !pVPK->m_bSignatureValid ) |
|
{ |
|
#ifdef PURE_SERVER_DEBUG_SPEW |
|
Msg( "Setting %s as untrusted (unsigned VPK)\n", pSearchPath->GetDebugString() ); |
|
#endif |
|
return; |
|
} |
|
const CUtlVector<uint8> &key = pVPK->GetSignaturePublicKey(); |
|
for ( int iKeyIndex = 0 ; iKeyIndex < m_pPureServerWhitelist->GetTrustedKeyCount() ; ++iKeyIndex ) |
|
{ |
|
int nKeySz = 0; |
|
const byte *pbKey = m_pPureServerWhitelist->GetTrustedKey( iKeyIndex, &nKeySz ); |
|
Assert( pbKey != NULL && nKeySz > 0 ); |
|
if ( key.Count() == nKeySz && V_memcmp( pbKey, key.Base(), nKeySz ) == 0 ) |
|
{ |
|
#ifdef PURE_SERVER_DEBUG_SPEW |
|
Msg( "Setting %s as untrusted\n", pSearchPath->GetDebugString() ); |
|
#endif |
|
pSearchPath->m_bIsTrustedForPureServer = true; |
|
return; |
|
} |
|
} |
|
|
|
#ifdef PURE_SERVER_DEBUG_SPEW |
|
Msg( "Setting %s as untrusted. (Key not in trusted key list)\n", pSearchPath->GetDebugString() ); |
|
#endif |
|
#endif |
|
#endif |
|
} |
|
|
|
|
|
int CBaseFileSystem::GetUnverifiedFileHashes( CUnverifiedFileHash *pFiles, int nMaxFiles ) |
|
{ |
|
return m_FileTracker2.GetUnverifiedFileHashes( pFiles, nMaxFiles ); |
|
} |
|
|
|
|
|
|
|
int CBaseFileSystem::GetWhitelistSpewFlags() |
|
{ |
|
return m_WhitelistSpewFlags; |
|
} |
|
|
|
|
|
void CBaseFileSystem::SetWhitelistSpewFlags( int flags ) |
|
{ |
|
m_WhitelistSpewFlags = flags; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pString - |
|
// maxCharsIncludingTerminator - |
|
// fileTime - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::FileTimeToString( char *pString, int maxCharsIncludingTerminator, time_t fileTime ) |
|
{ |
|
if ( IsX360() ) |
|
{ |
|
char szTemp[ 256 ]; |
|
|
|
time_t time = fileTime; |
|
V_strncpy( szTemp, ctime( &time ), sizeof( szTemp ) ); |
|
char *pFinalColon = Q_strrchr( szTemp, ':' ); |
|
if ( pFinalColon ) |
|
*pFinalColon = '\0'; |
|
|
|
// Clip off the day of the week |
|
V_strncpy( pString, szTemp + 4, maxCharsIncludingTerminator ); |
|
} |
|
else |
|
{ |
|
time_t time = fileTime; |
|
V_strncpy( pString, ctime( &time ), maxCharsIncludingTerminator ); |
|
|
|
// We see a linefeed at the end of these strings...if there is one, gobble it up |
|
int len = V_strlen( pString ); |
|
if ( pString[ len - 1 ] == '\n' ) |
|
{ |
|
pString[ len - 1 ] = '\0'; |
|
} |
|
|
|
pString[maxCharsIncludingTerminator-1] = '\0'; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFileName - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::FileExists( const char *pFileName, const char *pPathID ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::FileExists", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
|
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
FileHandle_t h = Open( pFileName, "rb", pPathID ); |
|
if ( h ) |
|
{ |
|
Close(h); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CBaseFileSystem::IsFileWritable( char const *pFileName, char const *pPathID /*=0*/ ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
struct _stat buf; |
|
|
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pFileName, pPathID, tempPathID ); |
|
|
|
if ( Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
if( FS_stat( pFileName, &buf ) != -1 ) |
|
{ |
|
#ifdef WIN32 |
|
if( buf.st_mode & _S_IWRITE ) |
|
#elif defined (LINUX) && !defined (ANDROID) |
|
if( buf.st_mode & S_IWRITE ) |
|
#elif ANDROID |
|
if( buf.st_mode & S_IWUSR ) |
|
#else |
|
if( buf.st_mode & S_IWRITE ) |
|
#endif |
|
{ |
|
return true; |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK ); |
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFileName ); |
|
Q_FixSlashes( pTmpFileName ); |
|
if ( FS_stat( pTmpFileName, &buf ) != -1 ) |
|
{ |
|
#ifdef WIN32 |
|
if ( buf.st_mode & _S_IWRITE ) |
|
#elif defined (LINUX) && !defined (ANDROID) |
|
if ( buf.st_mode & S_IWRITE ) |
|
#elif ANDROID |
|
if ( buf.st_mode & S_IWUSR ) |
|
#else |
|
if ( buf.st_mode & S_IWRITE ) |
|
#endif |
|
{ |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
bool CBaseFileSystem::SetFileWritable( char const *pFileName, bool writable, const char *pPathID /*= 0*/ ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
#ifdef _WIN32 |
|
int pmode = writable ? ( _S_IWRITE | _S_IREAD ) : ( _S_IREAD ); |
|
#elif ANDROID |
|
int pmode = writable ? ( S_IWUSR | S_IRUSR ) : ( S_IRUSR ); |
|
#else |
|
int pmode = writable ? ( S_IWRITE | S_IREAD ) : ( S_IREAD ); |
|
#endif |
|
|
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pFileName, pPathID, tempPathID ); |
|
|
|
if ( Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
return ( FS_chmod( pFileName, pmode ) == 0 ); |
|
} |
|
|
|
CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK ); |
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFileName ); |
|
Q_FixSlashes( pTmpFileName ); |
|
|
|
if ( FS_chmod( pTmpFileName, pmode ) == 0 ) |
|
{ |
|
return true; |
|
} |
|
} |
|
|
|
// Failure |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFileName - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::IsDirectory( const char *pFileName, const char *pathID ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
// Allow for UNC-type syntax to specify the path ID. |
|
struct _stat buf; |
|
|
|
char pTempBuf[MAX_PATH]; |
|
Q_strncpy( pTempBuf, pFileName, sizeof(pTempBuf) ); |
|
Q_StripTrailingSlash( pTempBuf ); |
|
pFileName = pTempBuf; |
|
|
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pFileName, pathID, tempPathID ); |
|
if ( Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
if ( FS_stat( pFileName, &buf ) != -1 ) |
|
{ |
|
if ( buf.st_mode & _S_IFDIR ) |
|
return true; |
|
} |
|
return false; |
|
} |
|
|
|
CSearchPathsIterator iter( this, &pFileName, pathID, FILTER_CULLPACK ); |
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
#ifdef SUPPORT_PACKED_STORE |
|
if ( pSearchPath->GetPackedStore() ) |
|
{ |
|
CUtlStringList outDir, outFile; |
|
pSearchPath->GetPackedStore()->GetFileAndDirLists( outDir, outFile, false ); |
|
FOR_EACH_VEC( outDir, i ) |
|
{ |
|
if ( !Q_stricmp( outDir[i], pFileName ) ) |
|
return true; |
|
} |
|
|
|
} |
|
else |
|
#endif // SUPPORT_PACKED_STORE |
|
{ |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFileName ); |
|
Q_FixSlashes( pTmpFileName ); |
|
if ( FS_stat( pTmpFileName, &buf ) != -1 ) |
|
{ |
|
if ( buf.st_mode & _S_IFDIR ) |
|
return true; |
|
} |
|
} |
|
} |
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *path - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::CreateDirHierarchy( const char *pRelativePathT, const char *pathID ) |
|
{ |
|
// Allow for UNC-type syntax to specify the path ID. |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID(pRelativePathT, pathID, tempPathID); // use the original path param to preserve "//" |
|
|
|
char pRelativePathBuff[ MAX_PATH ]; |
|
const char *pRelativePath = pRelativePathBuff; |
|
|
|
FixUpPath ( pRelativePathT, pRelativePathBuff, sizeof( pRelativePathBuff ) ); |
|
|
|
CHECK_DOUBLE_SLASHES( pRelativePath ); |
|
|
|
char szScratchFileName[MAX_PATH]; |
|
if ( !Q_IsAbsolutePath( pRelativePath ) ) |
|
{ |
|
Assert( pathID ); |
|
|
|
|
|
ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( szScratchFileName, pRelativePath, sizeof(szScratchFileName) ); |
|
} |
|
|
|
int len = strlen( szScratchFileName ) + 1; |
|
char *end = szScratchFileName + len; |
|
char *s = szScratchFileName; |
|
while( s < end ) |
|
{ |
|
if( *s == CORRECT_PATH_SEPARATOR && s != szScratchFileName && ( IsLinux() || *( s - 1 ) != ':' ) ) |
|
{ |
|
*s = '\0'; |
|
#if defined( _WIN32 ) |
|
_mkdir( szScratchFileName ); |
|
#elif defined( POSIX ) |
|
mkdir( szScratchFileName, S_IRWXU | S_IRGRP | S_IROTH );// owner has rwx, rest have r |
|
#endif |
|
*s = CORRECT_PATH_SEPARATOR; |
|
} |
|
s++; |
|
} |
|
|
|
#if defined( _WIN32 ) |
|
_mkdir( szScratchFileName ); |
|
#elif defined( POSIX ) |
|
mkdir( szScratchFileName, S_IRWXU | S_IRGRP | S_IROTH ); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pWildCard - |
|
// *pHandle - |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *CBaseFileSystem::FindFirstEx( const char *pWildCard, const char *pPathID, FileFindHandle_t *pHandle ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pWildCard ); |
|
|
|
return FindFirstHelper( pWildCard, pPathID, pHandle, NULL ); |
|
} |
|
|
|
|
|
const char *CBaseFileSystem::FindFirstHelper( const char *pWildCardT, const char *pPathID, FileFindHandle_t *pHandle, int *pFoundStoreID ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::FindFirst", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
Assert(pWildCardT); |
|
Assert(pHandle); |
|
|
|
FileFindHandle_t hTmpHandle = m_FindData.AddToTail(); |
|
FindData_t *pFindData = &m_FindData[hTmpHandle]; |
|
Assert( pFindData ); |
|
if ( pPathID ) |
|
{ |
|
pFindData->m_FilterPathID = g_PathIDTable.AddString( pPathID ); |
|
} |
|
|
|
char pWildCard[ MAX_PATH ]; |
|
|
|
FixUpPath ( pWildCardT, pWildCard, sizeof( pWildCard ) ); |
|
|
|
int maxlen = strlen( pWildCard ) + 1; |
|
pFindData->wildCardString.AddMultipleToTail( maxlen ); |
|
Q_strncpy( pFindData->wildCardString.Base(), pWildCard, maxlen ); |
|
pFindData->findHandle = INVALID_HANDLE_VALUE; |
|
|
|
if ( Q_IsAbsolutePath( pWildCard ) ) |
|
{ |
|
// Absolute path, cannot be VPK or Pak |
|
pFindData->findHandle = FS_FindFirstFile( pWildCard, &pFindData->findData ); |
|
pFindData->currentSearchPathID = -1; |
|
} |
|
else |
|
{ |
|
int c = m_SearchPaths.Count(); |
|
for( pFindData->currentSearchPathID = 0; |
|
pFindData->currentSearchPathID < c; |
|
pFindData->currentSearchPathID++ ) |
|
{ |
|
CSearchPath *pSearchPath = &m_SearchPaths[pFindData->currentSearchPathID]; |
|
|
|
if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) ) |
|
continue; |
|
|
|
// already visited this path? |
|
if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) ) |
|
continue; |
|
|
|
// If this is a VPK or Pak file, build list of matches now and use helper |
|
bool bIsVPKOrPak = false; |
|
if ( pSearchPath->GetPackFile() ) |
|
{ |
|
// XXX(johns) This support didn't exist for a long time, and I'm now worried about various things |
|
// looking for misc files suddenly finding them in the (untrusted) BSP and causing security |
|
// nightmares. For now, restricting FindFirst() support to BSPs only when the BSP search path |
|
// is explicitly requested, but this would otherwise work fine. |
|
if ( !pPathID || V_strcmp( pPathID, "BSP" ) != 0 ) |
|
{ |
|
continue; |
|
} |
|
|
|
Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 ); |
|
Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 ); |
|
pSearchPath->GetPackFile()->GetFileAndDirLists( pWildCard, pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true ); |
|
bIsVPKOrPak = true; |
|
} |
|
|
|
#ifdef SUPPORT_PACKED_STORE |
|
if ( pSearchPath->GetPackedStore() ) |
|
{ |
|
Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 ); |
|
Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 ); |
|
pSearchPath->GetPackedStore()->GetFileAndDirLists( pWildCard, pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true ); |
|
bIsVPKOrPak = true; |
|
} |
|
#endif |
|
|
|
if ( bIsVPKOrPak ) |
|
{ |
|
if ( FindNextFileInVPKOrPakHelper( pFindData ) ) |
|
{ |
|
// Remember that we visited this file already. |
|
pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 ); |
|
*pHandle = hTmpHandle; |
|
return pFindData->findData.cFileName; |
|
} |
|
continue; |
|
} |
|
|
|
// Otherwise, raw FS find for relative path |
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() ); |
|
Q_FixSlashes( pTmpFileName ); |
|
pFindData->findHandle = FS_FindFirstFile( pTmpFileName, &pFindData->findData ); |
|
pFindData->m_CurrentStoreID = pSearchPath->m_storeId; |
|
|
|
if( pFindData->findHandle != INVALID_HANDLE_VALUE ) |
|
break; |
|
} |
|
} |
|
|
|
// We have a result from the filesystem |
|
if( pFindData->findHandle != INVALID_HANDLE_VALUE ) |
|
{ |
|
// Remember that we visited this file already. |
|
pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 ); |
|
|
|
if ( pFoundStoreID ) |
|
*pFoundStoreID = pFindData->m_CurrentStoreID; |
|
|
|
*pHandle = hTmpHandle; |
|
return pFindData->findData.cFileName; |
|
} |
|
|
|
// Handle failure here |
|
pFindData = 0; |
|
m_FindData.Remove(hTmpHandle); |
|
*pHandle = -1; |
|
|
|
return NULL; |
|
} |
|
|
|
const char *CBaseFileSystem::FindFirst( const char *pWildCard, FileFindHandle_t *pHandle ) |
|
{ |
|
return FindFirstEx( pWildCard, NULL, pHandle ); |
|
} |
|
|
|
|
|
// Get the next file, trucking through the path. . don't check for duplicates. |
|
bool CBaseFileSystem::FindNextFileHelper( FindData_t *pFindData, int *pFoundStoreID ) |
|
{ |
|
// Try the same search path that we were already searching on. |
|
if( FS_FindNextFile( pFindData->findHandle, &pFindData->findData ) ) |
|
{ |
|
if ( pFoundStoreID ) |
|
*pFoundStoreID = pFindData->m_CurrentStoreID; |
|
|
|
return true; |
|
} |
|
|
|
if ( FindNextFileInVPKOrPakHelper( pFindData ) ) |
|
return true; |
|
|
|
// This happens when we searched a full path |
|
if ( pFindData->currentSearchPathID < 0 ) |
|
return false; |
|
|
|
pFindData->currentSearchPathID++; |
|
|
|
if ( pFindData->findHandle != INVALID_HANDLE_VALUE ) |
|
{ |
|
FS_FindClose( pFindData->findHandle ); |
|
} |
|
pFindData->findHandle = INVALID_HANDLE_VALUE; |
|
|
|
int c = m_SearchPaths.Count(); |
|
for( ; pFindData->currentSearchPathID < c; ++pFindData->currentSearchPathID ) |
|
{ |
|
CSearchPath *pSearchPath = &m_SearchPaths[pFindData->currentSearchPathID]; |
|
|
|
if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) ) |
|
continue; |
|
|
|
// already visited this path |
|
if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) ) |
|
continue; |
|
|
|
if ( pSearchPath->GetPackFile() ) |
|
{ |
|
// XXX(johns) This support didn't exist for a long time, and I'm now worried about various things |
|
// looking for misc files suddenly finding them in the (untrusted) BSP and causing security |
|
// nightmares. For now, restricting FindFirst() support to BSPs only when the BSP search path |
|
// is explicitly requested, but this would otherwise work fine. |
|
if ( !pFindData->m_FilterPathID || V_strcmp( g_PathIDTable.String( pFindData->m_FilterPathID ), "BSP" ) != 0 ) |
|
{ |
|
continue; |
|
} |
|
Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 ); |
|
Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 ); |
|
pSearchPath->GetPackFile()->GetFileAndDirLists( pFindData->wildCardString.Base(), pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true ); |
|
if ( FindNextFileInVPKOrPakHelper( pFindData ) ) |
|
return true; |
|
continue; |
|
} |
|
|
|
#ifdef SUPPORT_PACKED_STORE |
|
if ( pSearchPath->GetPackedStore() ) |
|
{ |
|
Assert( pFindData->m_dirMatchesFromVPKOrPak.Count() == 0 ); |
|
Assert( pFindData->m_fileMatchesFromVPKOrPak.Count() == 0 ); |
|
pSearchPath->GetPackedStore()->GetFileAndDirLists( pFindData->wildCardString.Base(), pFindData->m_dirMatchesFromVPKOrPak, pFindData->m_fileMatchesFromVPKOrPak, true ); |
|
if ( FindNextFileInVPKOrPakHelper( pFindData ) ) |
|
return true; |
|
continue; |
|
} |
|
#endif |
|
|
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() ); |
|
Q_FixSlashes( pTmpFileName ); |
|
pFindData->findHandle = FS_FindFirstFile( pTmpFileName, &pFindData->findData ); |
|
pFindData->m_CurrentStoreID = pSearchPath->m_storeId; |
|
if( pFindData->findHandle != INVALID_HANDLE_VALUE ) |
|
{ |
|
if ( pFoundStoreID ) |
|
*pFoundStoreID = pFindData->m_CurrentStoreID; |
|
|
|
return true; |
|
} |
|
} |
|
|
|
return false; |
|
} |
|
|
|
bool CBaseFileSystem::FindNextFileInVPKOrPakHelper( FindData_t *pFindData ) |
|
{ |
|
// Return the next one from the list of VPK matches if there is one |
|
if ( pFindData->m_fileMatchesFromVPKOrPak.Count() > 0 ) |
|
{ |
|
V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPKOrPak[0] ), sizeof( pFindData->findData.cFileName ) ); |
|
pFindData->findData.dwFileAttributes = 0; |
|
delete[] pFindData->m_fileMatchesFromVPKOrPak.Head(); |
|
pFindData->m_fileMatchesFromVPKOrPak.RemoveMultipleFromHead( 1 ); |
|
|
|
return true; |
|
} |
|
|
|
// Return the next one from the list of VPK matches if there is one |
|
if ( pFindData->m_dirMatchesFromVPKOrPak.Count() > 0 ) |
|
{ |
|
V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPKOrPak[0] ), sizeof( pFindData->findData.cFileName ) ); |
|
pFindData->findData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; |
|
delete pFindData->m_dirMatchesFromVPKOrPak.Head(); |
|
pFindData->m_dirMatchesFromVPKOrPak.RemoveMultipleFromHead( 1 ); |
|
|
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// Output : const char |
|
//----------------------------------------------------------------------------- |
|
const char *CBaseFileSystem::FindNext( FileFindHandle_t handle ) |
|
{ |
|
VPROF_BUDGET( "CBaseFileSystem::FindNext", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); |
|
FindData_t *pFindData = &m_FindData[handle]; |
|
|
|
while( 1 ) |
|
{ |
|
if( FindNextFileHelper( pFindData, NULL ) ) |
|
{ |
|
if ( pFindData->m_VisitedFiles.Find( pFindData->findData.cFileName ) == -1 ) |
|
{ |
|
pFindData->m_VisitedFiles.Insert( pFindData->findData.cFileName, 0 ); |
|
return pFindData->findData.cFileName; |
|
} |
|
} |
|
else |
|
{ |
|
return NULL; |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::FindIsDirectory( FileFindHandle_t handle ) |
|
{ |
|
FindData_t *pFindData = &m_FindData[handle]; |
|
return !!( pFindData->findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::FindClose( FileFindHandle_t handle ) |
|
{ |
|
if ( ( handle < 0 ) || ( !m_FindData.IsInList( handle ) ) ) |
|
return; |
|
|
|
FindData_t *pFindData = &m_FindData[handle]; |
|
Assert(pFindData); |
|
|
|
if ( pFindData->findHandle != INVALID_HANDLE_VALUE) |
|
{ |
|
FS_FindClose( pFindData->findHandle ); |
|
} |
|
pFindData->findHandle = INVALID_HANDLE_VALUE; |
|
|
|
pFindData->wildCardString.Purge(); |
|
pFindData->m_fileMatchesFromVPKOrPak.PurgeAndDeleteElements(); |
|
pFindData->m_dirMatchesFromVPKOrPak.PurgeAndDeleteElements(); |
|
m_FindData.Remove( handle ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFileName - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::GetLocalCopy( const char *pFileName ) |
|
{ |
|
// do nothing. . everything is local. |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fixes up Path names. Will fix up platform specific slashes, remove |
|
// any ../ or ./, fix //s, and lowercase anything under the directory |
|
// that the game is installed to. We expect all files to be lower cased |
|
// there - especially on Linux (where case sensitivity is the norm). |
|
// |
|
// Input : *pFileName - Original name to convert |
|
// *pFixedUpFileName - a buffer to put the converted filename into |
|
// sizeFixedUpFileName - the size of the above buffer in chars |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::FixUpPath( const char *pFileName, char *pFixedUpFileName, int sizeFixedUpFileName ) |
|
{ |
|
// If appropriate fixes up the filename to ensure that it's handled properly by the system. |
|
// |
|
V_strncpy( pFixedUpFileName, pFileName, sizeFixedUpFileName ); |
|
V_FixSlashes( pFixedUpFileName, CORRECT_PATH_SEPARATOR ); |
|
// V_RemoveDotSlashes( pFixedUpFileName, CORRECT_PATH_SEPARATOR, true ); |
|
V_FixDoubleSlashes( pFixedUpFileName ); |
|
|
|
if ( !V_IsAbsolutePath( pFixedUpFileName ) ) |
|
{ |
|
V_strlower( pFixedUpFileName ); |
|
} |
|
else |
|
{ |
|
// Get the BASE_PATH, skip past - if necessary, and lowercase the rest |
|
// Not just yet... |
|
|
|
|
|
int iBaseLength = 0; |
|
char pBaseDir[MAX_PATH]; |
|
|
|
// Need to get "BASE_PATH" from the filesystem paths, and then check this name against it. |
|
// |
|
iBaseLength = GetSearchPath( "BASE_PATH", true, pBaseDir, sizeof( pBaseDir ) ); |
|
if ( iBaseLength ) |
|
{ |
|
// If the first part of the pFixedUpFilename is pBaseDir |
|
// then lowercase the part after that. |
|
if ( *pBaseDir && (iBaseLength+1 < V_strlen( pFixedUpFileName ) ) && (0 != V_strncmp( pBaseDir, pFixedUpFileName, iBaseLength ) ) ) |
|
{ |
|
V_strlower( &pFixedUpFileName[iBaseLength-1] ); |
|
} |
|
} |
|
|
|
} |
|
|
|
// Msg("CBaseFileSystem::FixUpPath: Converted %s to %s\n", pFileName, pFixedUpFileName); // too noisy |
|
|
|
#ifdef NEVER // Useful if you're trying to see why your file may not be found (if you have a mixed case file) |
|
if (strncmp(pFixedUpFileName, pFileName, 256)) |
|
{ |
|
printf("FixUpPath->Converting %s to %s\n",pFileName, pFixedUpFileName); |
|
} |
|
#endif // NEVER |
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Converts a partial path into a full path |
|
// Relative paths that are pack based are returned as an absolute path .../zip?.zip/foo |
|
// A pack absolute path can be sent back in for opening, and the file will be properly |
|
// detected as pack based and mounted inside the pack. |
|
//----------------------------------------------------------------------------- |
|
const char *CBaseFileSystem::RelativePathToFullPath( const char *pFileName, const char *pPathID, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars, PathTypeFilter_t pathFilter, PathTypeQuery_t *pPathType ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
struct _stat buf; |
|
|
|
if ( pPathType ) |
|
{ |
|
*pPathType = PATH_IS_NORMAL; |
|
} |
|
|
|
// Convert filename to lowercase. All files in the |
|
// game logical filesystem must be accessed by lowercase name |
|
char szLowercaseFilename[ MAX_PATH ]; |
|
FixUpPath( pFileName, szLowercaseFilename, sizeof( szLowercaseFilename ) ); |
|
pFileName = szLowercaseFilename; |
|
|
|
// Fill in the default in case it's not found... |
|
V_strncpy( pDest, pFileName, maxLenInChars ); |
|
|
|
// @FD This is arbitrary and seems broken. If the caller needs this filter, they should |
|
// request it with the flag themselves. As it is, I cannot search all the file paths |
|
// for a file using this function because there is no option that says, "No, really, I |
|
// mean ALL SEARCH PATHS." The current problem I'm trying to fix is that sounds are not |
|
// working if they are in the BSP. I wrote code that assumed that I could just ask for |
|
// the absolute path of a file, since we are able to open files with these absolute |
|
// filenames, and that each particular filesystem call wouldn't have its own individual |
|
// quirks. |
|
// if ( IsPC() && pathFilter == FILTER_NONE ) |
|
// { |
|
// // X360TBD: PC legacy behavior never returned pack paths |
|
// // do legacy behavior to ensure naive callers don't break |
|
// pathFilter = FILTER_CULLPACK; |
|
// } |
|
|
|
|
|
CSearchPathsIterator iter( this, &pFileName, pPathID, pathFilter ); |
|
for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) |
|
{ |
|
|
|
CPackFile *pPack = pSearchPath->GetPackFile(); |
|
if ( pPack ) |
|
{ |
|
if ( pPack->ContainsFile( pFileName ) ) |
|
{ |
|
if ( pPathType ) |
|
{ |
|
if ( pPack->m_bIsMapPath ) |
|
{ |
|
*pPathType |= PATH_IS_MAPPACKFILE; |
|
} |
|
else |
|
{ |
|
*pPathType |= PATH_IS_PACKFILE; |
|
} |
|
if ( pSearchPath->m_bIsRemotePath ) |
|
{ |
|
*pPathType |= PATH_IS_REMOTE; |
|
} |
|
} |
|
|
|
// form an encoded absolute path that can be decoded by our FS as pak based |
|
const char *pszPackName = pPack->m_ZipName.String(); |
|
int len = V_strlen( pszPackName ); |
|
int nTotalLen = len + 1 + V_strlen( pFileName ); |
|
if ( nTotalLen >= maxLenInChars ) |
|
{ |
|
::Warning( "File %s was found in %s, but resulting abs filename won't fit in callers buffer of %d bytes\n", |
|
pFileName, pszPackName, maxLenInChars ); |
|
Assert( false ); |
|
return NULL; |
|
} |
|
|
|
V_strncpy( pDest, pszPackName, maxLenInChars ); |
|
V_AppendSlash( pDest, maxLenInChars ); |
|
V_strncat( pDest, pFileName, maxLenInChars ); |
|
Assert( V_strlen( pDest ) == nTotalLen ); |
|
return pDest; |
|
} |
|
|
|
continue; |
|
} |
|
|
|
// Found in VPK? |
|
#ifdef SUPPORT_PACKED_STORE |
|
CPackedStore *pVPK = pSearchPath->GetPackedStore(); |
|
if ( pVPK ) |
|
{ |
|
CPackedStoreFileHandle vpkHandle = pVPK->OpenFile( pFileName ); |
|
if ( vpkHandle ) |
|
{ |
|
const char *pszVpkName = vpkHandle.m_pOwner->FullPathName(); |
|
Assert( V_GetFileExtension( pszVpkName ) != NULL ); |
|
|
|
int len = V_strlen( pszVpkName ); |
|
int nTotalLen = len + 1 + V_strlen( pFileName ); |
|
if ( nTotalLen >= maxLenInChars ) |
|
{ |
|
::Warning( "File %s was found in %s, but resulting abs filename won't fit in callers buffer of %d bytes\n", |
|
pFileName, pszVpkName, maxLenInChars ); |
|
Assert( false ); |
|
return NULL; |
|
} |
|
|
|
V_strncpy( pDest, pszVpkName, maxLenInChars ); |
|
V_AppendSlash( pDest, maxLenInChars ); |
|
V_strncat( pDest, pFileName, maxLenInChars ); |
|
V_FixSlashes( pDest ); |
|
return pDest; |
|
} |
|
continue; |
|
} |
|
#endif |
|
|
|
char pTmpFileName[ MAX_FILEPATH ]; |
|
V_sprintf_safe( pTmpFileName, "%s%s", pSearchPath->GetPathString(), pFileName ); |
|
V_FixSlashes( pTmpFileName ); |
|
if ( FS_stat( pTmpFileName, &buf ) != -1 ) |
|
{ |
|
V_strncpy( pDest, pTmpFileName, maxLenInChars ); |
|
if ( pPathType && pSearchPath->m_bIsRemotePath ) |
|
{ |
|
*pPathType |= PATH_IS_REMOTE; |
|
} |
|
return pDest; |
|
} |
|
} |
|
|
|
// not found |
|
return NULL; |
|
} |
|
|
|
const char *CBaseFileSystem::GetLocalPath( const char *pFileName, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
|
|
return RelativePathToFullPath( pFileName, NULL, pDest, maxLenInChars ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true on success, otherwise false if it can't be resolved |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::FullPathToRelativePathEx( const char *pFullPath, const char *pPathId, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFullPath ); |
|
|
|
int nInlen = V_strlen( pFullPath ); |
|
if ( nInlen <= 0 ) |
|
{ |
|
pDest[ 0 ] = 0; |
|
return false; |
|
} |
|
|
|
V_strncpy( pDest, pFullPath, maxLenInChars ); |
|
|
|
char pInPath[ MAX_FILEPATH ]; |
|
V_strcpy_safe( pInPath, pFullPath ); |
|
#ifdef _WIN32 |
|
V_strlower( pInPath ); |
|
#endif |
|
V_FixSlashes( pInPath ); |
|
|
|
CUtlSymbol lookup; |
|
if ( pPathId ) |
|
{ |
|
lookup = g_PathIDTable.AddString( pPathId ); |
|
} |
|
|
|
int c = m_SearchPaths.Count(); |
|
for( int i = 0; i < c; i++ ) |
|
{ |
|
// FIXME: Should this work with embedded pak files? |
|
if ( m_SearchPaths[i].GetPackFile() && m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) |
|
continue; |
|
|
|
// Skip paths that are not on the specified search path |
|
if ( FilterByPathID( &m_SearchPaths[i], lookup ) ) |
|
continue; |
|
|
|
char pSearchBase[ MAX_FILEPATH ]; |
|
V_strncpy( pSearchBase, m_SearchPaths[i].GetPathString(), sizeof( pSearchBase ) ); |
|
#ifdef _WIN32 |
|
V_strlower( pSearchBase ); |
|
#endif |
|
V_FixSlashes( pSearchBase ); |
|
int nSearchLen = V_strlen( pSearchBase ); |
|
if ( V_strnicmp( pSearchBase, pInPath, nSearchLen ) ) |
|
continue; |
|
|
|
V_strncpy( pDest, &pInPath[ nSearchLen ], maxLenInChars ); |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Obsolete version |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::FullPathToRelativePath( const char *pFullPath, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars ) |
|
{ |
|
return FullPathToRelativePathEx( pFullPath, NULL, pDest, maxLenInChars ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns true on successfully retrieve case-sensitive full path, otherwise false |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::GetCaseCorrectFullPath_Ptr( const char *pFullPath, OUT_Z_CAP(maxLenInChars) char *pDest, int maxLenInChars ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFullPath ); |
|
|
|
#ifndef _WIN32 |
|
|
|
V_strncpy( pDest, pFullPath, maxLenInChars ); |
|
return true; |
|
|
|
#else |
|
|
|
char szCurrentDir[MAX_PATH]; |
|
V_strcpy_safe( szCurrentDir, pFullPath ); |
|
V_StripLastDir( szCurrentDir, sizeof( szCurrentDir ) ); |
|
|
|
CUtlString strSearchName = pFullPath; |
|
strSearchName.StripTrailingSlash(); |
|
strSearchName = strSearchName.Slice( V_strlen( szCurrentDir ), strSearchName.Length() ); |
|
|
|
CUtlString strSearchPath = szCurrentDir; |
|
strSearchPath += "*"; |
|
|
|
CUtlString strFoundCaseCorrectName; |
|
FileFindHandle_t findHandle; |
|
const char *pszCaseCorrectName = FindFirst( strSearchPath.Get(), &findHandle ); |
|
while ( pszCaseCorrectName ) |
|
{ |
|
if ( V_stricmp( strSearchName.String(), pszCaseCorrectName ) == 0 ) |
|
{ |
|
strFoundCaseCorrectName = pszCaseCorrectName; |
|
break; |
|
} |
|
pszCaseCorrectName = FindNext( findHandle ); |
|
} |
|
FindClose( findHandle ); |
|
|
|
// Not found |
|
if ( strFoundCaseCorrectName.IsEmpty() ) |
|
{ |
|
V_strncpy( pDest, pFullPath, maxLenInChars ); |
|
return false; |
|
} |
|
|
|
// If drive path, no need to recurse anymore. |
|
bool bResult = false; |
|
char szDir[MAX_PATH]; |
|
if ( !IsDirectory( szCurrentDir, NULL ) ) |
|
{ |
|
V_strupr( szCurrentDir ); |
|
V_strncpy( szDir, szCurrentDir, sizeof( szDir ) ); |
|
bResult = true; |
|
} |
|
else |
|
{ |
|
bResult = GetCaseCorrectFullPath( szCurrentDir, szDir ); |
|
} |
|
|
|
// connect the current path with the case-correct dir/file name |
|
V_MakeAbsolutePath( pDest, maxLenInChars, strFoundCaseCorrectName.String(), szDir ); |
|
|
|
return bResult; |
|
#endif // _WIN32 |
|
} |
|
|
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Deletes a file |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::RemoveFile( char const* pRelativePath, const char *pathID ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pRelativePath ); |
|
|
|
// Allow for UNC-type syntax to specify the path ID. |
|
char tempPathID[MAX_PATH]; |
|
ParsePathID( pRelativePath, pathID, tempPathID ); |
|
|
|
Assert( pathID || !IsX360() ); |
|
|
|
// Opening for write or append uses Write Path |
|
char szScratchFileName[MAX_PATH]; |
|
if ( Q_IsAbsolutePath( pRelativePath ) ) |
|
{ |
|
Q_strncpy( szScratchFileName, pRelativePath, sizeof( szScratchFileName ) ); |
|
} |
|
else |
|
{ |
|
ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID ); |
|
} |
|
int fail = unlink( szScratchFileName ); |
|
if ( fail != 0 ) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "Unable to remove %s!\n", szScratchFileName ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Renames a file |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::RenameFile( char const *pOldPath, char const *pNewPath, const char *pathID ) |
|
{ |
|
Assert( pOldPath && pNewPath ); |
|
|
|
CHECK_DOUBLE_SLASHES( pOldPath ); |
|
CHECK_DOUBLE_SLASHES( pNewPath ); |
|
|
|
// Allow for UNC-type syntax to specify the path ID. |
|
char pPathIdCopy[MAX_PATH]; |
|
const char *pOldPathId = pathID; |
|
if ( pathID ) |
|
{ |
|
Q_strncpy( pPathIdCopy, pathID, sizeof( pPathIdCopy ) ); |
|
pOldPathId = pPathIdCopy; |
|
} |
|
|
|
char tempOldPathID[MAX_PATH]; |
|
ParsePathID( pOldPath, pOldPathId, tempOldPathID ); |
|
Assert( pOldPathId ); |
|
|
|
// Allow for UNC-type syntax to specify the path ID. |
|
char tempNewPathID[MAX_PATH]; |
|
ParsePathID( pNewPath, pathID, tempNewPathID ); |
|
Assert( pathID ); |
|
|
|
char pNewFileName[ MAX_PATH ]; |
|
char szScratchFileName[MAX_PATH]; |
|
|
|
// The source file may be in a fallback directory, so just resolve the actual path, don't assume pathid... |
|
RelativePathToFullPath( pOldPath, pOldPathId, szScratchFileName, sizeof( szScratchFileName ) ); |
|
|
|
// Figure out the dest path |
|
if ( !Q_IsAbsolutePath( pNewPath ) ) |
|
{ |
|
ComputeFullWritePath( pNewFileName, sizeof( pNewFileName ), pNewPath, pathID ); |
|
} |
|
else |
|
{ |
|
Q_strncpy( pNewFileName, pNewPath, sizeof(pNewFileName) ); |
|
} |
|
|
|
// Make sure the directory exitsts, too |
|
char pPathOnly[ MAX_PATH ]; |
|
Q_strncpy( pPathOnly, pNewFileName, sizeof( pPathOnly ) ); |
|
Q_StripFilename( pPathOnly ); |
|
CreateDirHierarchy( pPathOnly, pathID ); |
|
|
|
// Now copy the file over |
|
int fail = rename( szScratchFileName, pNewFileName ); |
|
if (fail != 0) |
|
{ |
|
Warning( FILESYSTEM_WARNING, "Unable to rename %s to %s!\n", szScratchFileName, pNewFileName ); |
|
return false; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : **ppdir - |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::GetCurrentDirectory( char* pDirectory, int maxlen ) |
|
{ |
|
#if defined( _WIN32 ) && !defined( _X360 ) |
|
if ( !::GetCurrentDirectoryA( maxlen, pDirectory ) ) |
|
#elif defined( POSIX ) || defined( _X360 ) |
|
if ( !getcwd( pDirectory, maxlen ) ) |
|
#endif |
|
return false; |
|
|
|
Q_FixSlashes(pDirectory); |
|
|
|
// Strip the last slash |
|
int len = strlen(pDirectory); |
|
if ( pDirectory[ len-1 ] == CORRECT_PATH_SEPARATOR ) |
|
{ |
|
pDirectory[ len-1 ] = 0; |
|
} |
|
|
|
return true; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : pfnWarning - warning function callback |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::SetWarningFunc( void (*pfnWarning)( const char *fmt, ... ) ) |
|
{ |
|
m_pfnWarning = pfnWarning; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : level - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::SetWarningLevel( FileWarningLevel_t level ) |
|
{ |
|
m_fwLevel = level; |
|
} |
|
|
|
const FileSystemStatistics *CBaseFileSystem::GetFilesystemStatistics() |
|
{ |
|
return &m_Stats; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : level - |
|
// *fmt - |
|
// ... - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::Warning( FileWarningLevel_t level, const char *fmt, ... ) |
|
{ |
|
if ( level > m_fwLevel ) |
|
return; |
|
|
|
if ( ( fs_warning_mode.GetInt() == 1 && !ThreadInMainThread() ) || ( fs_warning_mode.GetInt() == 2 && ThreadInMainThread() ) ) |
|
return; |
|
|
|
va_list argptr; |
|
char warningtext[ 4096 ]; |
|
|
|
va_start( argptr, fmt ); |
|
Q_vsnprintf( warningtext, sizeof( warningtext ), fmt, argptr ); |
|
va_end( argptr ); |
|
|
|
// Dump to stdio |
|
printf( "%s", warningtext ); |
|
if ( m_pfnWarning ) |
|
{ |
|
(*m_pfnWarning)( warningtext ); |
|
} |
|
else |
|
{ |
|
#ifdef _WIN32 |
|
Plat_DebugString( warningtext ); |
|
#endif |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::COpenedFile::COpenedFile( void ) |
|
{ |
|
m_pFile = NULL; |
|
m_pName = NULL; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::COpenedFile::~COpenedFile( void ) |
|
{ |
|
delete[] m_pName; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : src - |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::COpenedFile::COpenedFile( const COpenedFile& src ) |
|
{ |
|
m_pFile = src.m_pFile; |
|
if ( src.m_pName ) |
|
{ |
|
int len = strlen( src.m_pName ) + 1; |
|
m_pName = new char[ len ]; |
|
Q_strncpy( m_pName, src.m_pName, len ); |
|
} |
|
else |
|
{ |
|
m_pName = NULL; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : src - |
|
// Output : Returns true on success, false on failure. |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::COpenedFile::operator==( const CBaseFileSystem::COpenedFile& src ) const |
|
{ |
|
return src.m_pFile == m_pFile; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *name - |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::COpenedFile::SetName( char const *name ) |
|
{ |
|
delete[] m_pName; |
|
int len = strlen( name ) + 1; |
|
m_pName = new char[ len ]; |
|
Q_strncpy( m_pName, name, len ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Output : char |
|
//----------------------------------------------------------------------------- |
|
char const *CBaseFileSystem::COpenedFile::GetName( void ) |
|
{ |
|
return m_pName ? m_pName : "???"; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::CSearchPath::CSearchPath( void ) |
|
{ |
|
m_Path = g_PathIDTable.AddString( "" ); |
|
m_pDebugPath = ""; |
|
|
|
m_storeId = 0; |
|
m_pPackFile = NULL; |
|
m_pPathIDInfo = NULL; |
|
m_bIsRemotePath = false; |
|
m_pPackedStore = NULL; |
|
m_bIsTrustedForPureServer = false; |
|
} |
|
|
|
const char *CBaseFileSystem::CSearchPath::GetDebugString() const |
|
{ |
|
if ( GetPackFile() ) |
|
{ |
|
return GetPackFile()->m_ZipName; |
|
} |
|
#ifdef SUPPORT_PACKED_STORE |
|
if ( GetPackedStore() ) |
|
{ |
|
return GetPackedStore()->FullPathName(); |
|
} |
|
#endif |
|
return GetPathString(); |
|
} |
|
|
|
bool CBaseFileSystem::CSearchPath::IsMapPath() const |
|
{ |
|
return GetPackFile()->m_bIsMapPath; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::CSearchPath::~CSearchPath( void ) |
|
{ |
|
if ( m_pPackFile ) |
|
{ |
|
m_pPackFile->Release(); |
|
} |
|
if ( m_pPackedStore ) |
|
{ |
|
m_pPackedStore->Release(); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetFirst() |
|
{ |
|
if ( m_SearchPaths.Count() ) |
|
{ |
|
m_visits.Reset(); |
|
m_iCurrent = -1; |
|
return GetNext(); |
|
} |
|
return &m_EmptySearchPath; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetNext() |
|
{ |
|
CSearchPath *pSearchPath = NULL; |
|
|
|
for ( m_iCurrent++; m_iCurrent < m_SearchPaths.Count(); m_iCurrent++ ) |
|
{ |
|
pSearchPath = &m_SearchPaths[m_iCurrent]; |
|
|
|
if ( m_PathTypeFilter == FILTER_CULLPACK && pSearchPath->GetPackFile() ) |
|
continue; |
|
|
|
if ( m_PathTypeFilter == FILTER_CULLNONPACK && !pSearchPath->GetPackFile() ) |
|
continue; |
|
|
|
if ( CBaseFileSystem::FilterByPathID( pSearchPath, m_pathID ) ) |
|
continue; |
|
|
|
// 360 can optionally ignore a local search path in dvddev mode |
|
// ignoring a local search path falls through to its cloned remote path |
|
// map paths are exempt from this exclusion logic |
|
if ( IsX360() && ( m_DVDMode == DVDMODE_DEV ) && m_Filename[0] && !pSearchPath->m_bIsRemotePath ) |
|
{ |
|
bool bIsMapPath = pSearchPath->GetPackFile() && pSearchPath->GetPackFile()->m_bIsMapPath; |
|
if ( !bIsMapPath ) |
|
{ |
|
bool bIgnorePath = false; |
|
char szExcludePath[MAX_PATH]; |
|
char szFilename[MAX_PATH]; |
|
V_ComposeFileName( pSearchPath->GetPathString(), m_Filename, szFilename, sizeof( szFilename ) ); |
|
for ( int i = 0; i < m_ExcludePaths.Count(); i++ ) |
|
{ |
|
if ( g_pFullFileSystem->String( m_ExcludePaths[i], szExcludePath, sizeof( szExcludePath ) ) ) |
|
{ |
|
if ( !V_strnicmp( szFilename, szExcludePath, strlen( szExcludePath ) ) ) |
|
{ |
|
bIgnorePath = true; |
|
break; |
|
} |
|
} |
|
} |
|
if ( bIgnorePath ) |
|
{ |
|
// filename matches exclusion path, skip it |
|
continue; |
|
} |
|
} |
|
} |
|
|
|
if ( !m_visits.MarkVisit( *pSearchPath ) ) |
|
break; |
|
} |
|
|
|
if ( m_iCurrent < m_SearchPaths.Count() ) |
|
{ |
|
return pSearchPath; |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
void CBaseFileSystem::CSearchPathsIterator::CopySearchPaths( const CUtlVector<CSearchPath> &searchPaths ) |
|
{ |
|
m_SearchPaths = searchPaths; |
|
for ( int i = 0; i < m_SearchPaths.Count(); i++ ) |
|
{ |
|
if ( m_SearchPaths[i].GetPackFile() ) |
|
{ |
|
m_SearchPaths[i].GetPackFile()->AddRef(); |
|
} |
|
else if ( m_SearchPaths[i].GetPackedStore() ) |
|
{ |
|
m_SearchPaths[i].GetPackedStore()->AddRef(); |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Load/unload a DLL |
|
//----------------------------------------------------------------------------- |
|
CSysModule *CBaseFileSystem::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly ) |
|
{ |
|
CHECK_DOUBLE_SLASHES( pFileName ); |
|
CSysModule *pModule = NULL; |
|
|
|
LogFileAccess( pFileName ); |
|
if ( !pPathID ) |
|
{ |
|
pPathID = "EXECUTABLE_PATH"; // default to the bin dir |
|
} |
|
|
|
char tempPathID[ MAX_PATH ]; |
|
ParsePathID( pFileName, pPathID, tempPathID ); |
|
|
|
CUtlSymbol lookup = g_PathIDTable.AddString( pPathID ); |
|
|
|
// a pathID has been specified, find the first match in the path list |
|
#ifndef ANDROID |
|
int c = m_SearchPaths.Count(); |
|
for ( int i = 0; i < c; i++ ) |
|
{ |
|
// pak files don't have modules |
|
if ( m_SearchPaths[i].GetPackFile() ) |
|
continue; |
|
|
|
if ( FilterByPathID( &m_SearchPaths[i], lookup ) ) |
|
continue; |
|
|
|
Q_snprintf( tempPathID, sizeof(tempPathID), "%s%s", m_SearchPaths[i].GetPathString(), pFileName ); // append the path to this dir. |
|
pModule = Sys_LoadModule( tempPathID ); |
|
if ( pModule ) |
|
{ |
|
// we found the binary in one of our search paths |
|
return pModule; |
|
} |
|
|
|
#ifdef POSIX |
|
Q_snprintf( tempPathID, sizeof(tempPathID), "%slib%s", m_SearchPaths[i].GetPathString(), pFileName ); // append the path to this dir. |
|
pModule = Sys_LoadModule( tempPathID ); |
|
if ( pModule ) |
|
return pModule; |
|
#endif |
|
} |
|
#endif |
|
|
|
if( !pModule ) |
|
pModule = Sys_LoadModule( pFileName ); |
|
|
|
return pModule; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::UnloadModule( CSysModule *pModule ) |
|
{ |
|
Sys_UnloadModule( pModule ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Adds a filesystem logging function |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::AddLoggingFunc( FileSystemLoggingFunc_t logFunc ) |
|
{ |
|
Assert(!m_LogFuncs.IsValidIndex(m_LogFuncs.Find(logFunc))); |
|
m_LogFuncs.AddToTail(logFunc); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Removes a filesystem logging function |
|
//----------------------------------------------------------------------------- |
|
void CBaseFileSystem::RemoveLoggingFunc( FileSystemLoggingFunc_t logFunc ) |
|
{ |
|
m_LogFuncs.FindAndRemove(logFunc); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Make sure that slashes are of the right kind and that there is a slash at the |
|
// end of the filename. |
|
// WARNING!!: assumes that you have an extra byte allocated in the case that you need |
|
// a slash at the end. |
|
//----------------------------------------------------------------------------- |
|
static void AddSeperatorAndFixPath( char *str ) |
|
{ |
|
char *lastChar = &str[strlen( str ) - 1]; |
|
if( *lastChar != CORRECT_PATH_SEPARATOR && *lastChar != INCORRECT_PATH_SEPARATOR ) |
|
{ |
|
lastChar[1] = CORRECT_PATH_SEPARATOR; |
|
lastChar[2] = '\0'; |
|
} |
|
Q_FixSlashes( str ); |
|
|
|
if ( IsX360() ) |
|
{ |
|
// 360 FS won't resolve any path with ../ |
|
V_RemoveDotSlashes( str ); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : *pFileName - |
|
// Output : FileNameHandle_t |
|
//----------------------------------------------------------------------------- |
|
FileNameHandle_t CBaseFileSystem::FindOrAddFileName( char const *pFileName ) |
|
{ |
|
return m_FileNames.FindOrAddFileName( pFileName ); |
|
} |
|
|
|
FileNameHandle_t CBaseFileSystem::FindFileName( char const *pFileName ) |
|
{ |
|
return m_FileNames.FindFileName( pFileName ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
// Input : handle - |
|
// Output : char const |
|
//----------------------------------------------------------------------------- |
|
bool CBaseFileSystem::String( const FileNameHandle_t& handle, char *buf, int buflen ) |
|
{ |
|
return m_FileNames.String( handle, buf, buflen ); |
|
} |
|
|
|
int CBaseFileSystem::GetPathIndex( const FileNameHandle_t &handle ) |
|
{ |
|
return m_FileNames.PathIndex(handle); |
|
} |
|
|
|
CBaseFileSystem::CPathIDInfo* CBaseFileSystem::FindOrAddPathIDInfo( const CUtlSymbol &id, int bByRequestOnly ) |
|
{ |
|
for ( int i=0; i < m_PathIDInfos.Count(); i++ ) |
|
{ |
|
CBaseFileSystem::CPathIDInfo *pInfo = m_PathIDInfos[i]; |
|
if ( pInfo->GetPathID() == id ) |
|
{ |
|
if ( bByRequestOnly != -1 ) |
|
{ |
|
pInfo->m_bByRequestOnly = (bByRequestOnly != 0); |
|
} |
|
return pInfo; |
|
} |
|
} |
|
|
|
// Add a new one. |
|
CBaseFileSystem::CPathIDInfo *pInfo = new CBaseFileSystem::CPathIDInfo; |
|
m_PathIDInfos.AddToTail( pInfo ); |
|
pInfo->SetPathID( id ); |
|
pInfo->m_bByRequestOnly = (bByRequestOnly == 1); |
|
return pInfo; |
|
} |
|
|
|
|
|
void CBaseFileSystem::MarkPathIDByRequestOnly( const char *pPathID, bool bRequestOnly ) |
|
{ |
|
FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), bRequestOnly ); |
|
} |
|
|
|
#if defined( TRACK_BLOCKING_IO ) |
|
|
|
void CBaseFileSystem::EnableBlockingFileAccessTracking( bool state ) |
|
{ |
|
m_bBlockingFileAccessReportingEnabled = state; |
|
} |
|
|
|
bool CBaseFileSystem::IsBlockingFileAccessEnabled() const |
|
{ |
|
return m_bBlockingFileAccessReportingEnabled; |
|
} |
|
|
|
IBlockingFileItemList *CBaseFileSystem::RetrieveBlockingFileAccessInfo() |
|
{ |
|
Assert( m_pBlockingItems ); |
|
return m_pBlockingItems; |
|
} |
|
|
|
void CBaseFileSystem::RecordBlockingFileAccess( bool synchronous, const FileBlockingItem& item ) |
|
{ |
|
AUTO_LOCK( m_BlockingFileMutex ); |
|
|
|
// Not tracking anything |
|
if ( !m_bBlockingFileAccessReportingEnabled ) |
|
return; |
|
|
|
if ( synchronous && !m_bAllowSynchronousLogging && ( item.m_ItemType == FILESYSTEM_BLOCKING_SYNCHRONOUS ) ) |
|
return; |
|
|
|
// Track it |
|
m_pBlockingItems->Add( item ); |
|
} |
|
|
|
bool CBaseFileSystem::SetAllowSynchronousLogging( bool state ) |
|
{ |
|
bool oldState = m_bAllowSynchronousLogging; |
|
m_bAllowSynchronousLogging = state; |
|
return oldState; |
|
} |
|
|
|
void CBaseFileSystem::BlockingFileAccess_EnterCriticalSection() |
|
{ |
|
m_BlockingFileMutex.Lock(); |
|
} |
|
|
|
void CBaseFileSystem::BlockingFileAccess_LeaveCriticalSection() |
|
{ |
|
m_BlockingFileMutex.Unlock(); |
|
} |
|
|
|
#endif // TRACK_BLOCKING_IO |
|
|
|
bool CBaseFileSystem::GetFileTypeForFullPath( char const *pFullPath, wchar_t *buf, size_t bufSizeInBytes ) |
|
{ |
|
#if !defined( _X360 ) && !defined( POSIX ) |
|
wchar_t wcharpath[512]; |
|
::MultiByteToWideChar( CP_UTF8, 0, pFullPath, -1, wcharpath, sizeof( wcharpath ) / sizeof(wchar_t) ); |
|
wcharpath[(sizeof( wcharpath ) / sizeof(wchar_t)) - 1] = L'\0'; |
|
|
|
SHFILEINFOW info = { 0 }; |
|
DWORD_PTR dwResult = SHGetFileInfoW( |
|
wcharpath, |
|
0, |
|
&info, |
|
sizeof( info ), |
|
SHGFI_TYPENAME |
|
); |
|
if ( dwResult ) |
|
{ |
|
wcsncpy( buf, info.szTypeName, ( bufSizeInBytes / sizeof( wchar_t ) ) ); |
|
buf[( bufSizeInBytes / sizeof( wchar_t ) ) - 1] = L'\0'; |
|
return true; |
|
} |
|
else |
|
#endif |
|
{ |
|
char ext[32]; |
|
Q_ExtractFileExtension( pFullPath, ext, sizeof( ext ) ); |
|
#ifdef POSIX |
|
_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L"%s File", V_strupr( ext ) ); // Matches what Windows does |
|
#else |
|
_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L".%S", ext ); |
|
#endif |
|
buf[( bufSizeInBytes / sizeof( wchar_t ) ) - 1] = L'\0'; |
|
} |
|
return false; |
|
} |
|
|
|
|
|
bool CBaseFileSystem::GetOptimalIOConstraints( FileHandle_t hFile, unsigned *pOffsetAlign, unsigned *pSizeAlign, unsigned *pBufferAlign ) |
|
{ |
|
if ( pOffsetAlign ) |
|
*pOffsetAlign = 1; |
|
if ( pSizeAlign ) |
|
*pSizeAlign = 1; |
|
if ( pBufferAlign ) |
|
*pBufferAlign = 1; |
|
return false; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
FileCacheHandle_t CBaseFileSystem::CreateFileCache( ) |
|
{ |
|
return new CFileCacheObject( this ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CBaseFileSystem::AddFilesToFileCache( FileCacheHandle_t cacheId, const char **ppFileNames, int nFileNames, const char *pPathID ) |
|
{ |
|
// For now, assuming that we're only used with GAME. |
|
Assert( pPathID && Q_strcasecmp( pPathID, "GAME" ) == 0 ); |
|
return static_cast< CFileCacheObject * >( cacheId )->AddFiles( ppFileNames, nFileNames ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CBaseFileSystem::IsFileCacheLoaded( FileCacheHandle_t cacheId ) |
|
{ |
|
return static_cast< CFileCacheObject * >( cacheId )->IsReady(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CBaseFileSystem::DestroyFileCache( FileCacheHandle_t cacheId ) |
|
{ |
|
delete static_cast< CFileCacheObject * >( cacheId ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CBaseFileSystem::IsFileCacheFileLoaded( FileCacheHandle_t cacheId, const char* pFileName ) |
|
{ |
|
#ifdef _DEBUG |
|
CFileCacheObject* pFileCache = static_cast< CFileCacheObject * >( cacheId ); |
|
bool bFileIsHeldByCache = false; |
|
{ |
|
AUTO_LOCK( pFileCache->m_InfosMutex ); |
|
for ( int i = 0; i < pFileCache->m_Infos.Count(); ++i ) |
|
{ |
|
if ( pFileCache->m_Infos[i]->pFileName && Q_strcmp( pFileCache->m_Infos[i]->pFileName, pFileName ) ) |
|
{ |
|
bFileIsHeldByCache = true; |
|
break; |
|
} |
|
} |
|
} |
|
Assert( bFileIsHeldByCache ); |
|
#endif |
|
|
|
AUTO_LOCK( m_MemoryFileMutex ); |
|
return m_MemoryFileHash.Find( pFileName ) != m_MemoryFileHash.InvalidHandle(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
bool CBaseFileSystem::RegisterMemoryFile( CMemoryFileBacking *pFile, CMemoryFileBacking **ppExistingFileWithRef ) |
|
{ |
|
Assert( pFile->m_pFS == static_cast< IFileSystem* >( this ) ); |
|
Assert( pFile->m_pFileName && pFile->m_pFileName[0] ); |
|
AUTO_LOCK( m_MemoryFileMutex ); |
|
|
|
CMemoryFileBacking *pInTable = m_MemoryFileHash[ m_MemoryFileHash.Insert( pFile->m_pFileName, pFile ) ]; |
|
pInTable->m_nRegistered++; |
|
pInTable->AddRef(); // either for table or for ppExistingFileWithRef return |
|
|
|
if ( pFile == pInTable ) |
|
{ |
|
Assert( pInTable->m_nRegistered == 1 ); |
|
*ppExistingFileWithRef = NULL; |
|
return true; |
|
} |
|
else |
|
{ |
|
Assert( pInTable->m_nRegistered > 1 ); |
|
*ppExistingFileWithRef = pInTable; |
|
return false; |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
void CBaseFileSystem::UnregisterMemoryFile( CMemoryFileBacking *pFile ) |
|
{ |
|
Assert( pFile->m_pFS == static_cast< IFileSystem* >( this ) && pFile->m_nRegistered > 0 ); |
|
bool bRelease = false; |
|
{ |
|
AUTO_LOCK( m_MemoryFileMutex ); |
|
pFile->m_nRegistered--; |
|
if ( pFile->m_nRegistered == 0 ) |
|
{ |
|
m_MemoryFileHash.Remove( pFile->m_pFileName ); |
|
bRelease = true; |
|
} |
|
} |
|
// Release potentially a complex op, prefer to perform it outside of mutex. |
|
if (bRelease) |
|
{ |
|
pFile->Release(); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructs a file handle |
|
// Input : base file system handle |
|
// Output : |
|
//----------------------------------------------------------------------------- |
|
CFileHandle::CFileHandle( CBaseFileSystem* fs ) |
|
{ |
|
Init( fs ); |
|
} |
|
|
|
CFileHandle::~CFileHandle() |
|
{ |
|
Assert( IsValid() ); |
|
#if !defined( _RETAIL ) |
|
delete[] m_pszTrueFileName; |
|
#endif |
|
|
|
if ( m_pPackFileHandle ) |
|
{ |
|
delete m_pPackFileHandle; |
|
m_pPackFileHandle = NULL; |
|
} |
|
|
|
if ( m_pFile ) |
|
{ |
|
m_fs->Trace_FClose( m_pFile ); |
|
m_pFile = NULL; |
|
} |
|
|
|
m_nMagic = FREE_MAGIC; |
|
} |
|
|
|
void CFileHandle::Init( CBaseFileSystem *fs ) |
|
{ |
|
m_nMagic = MAGIC; |
|
m_pFile = NULL; |
|
m_nLength = 0; |
|
m_type = FT_NORMAL; |
|
m_pPackFileHandle = NULL; |
|
|
|
m_fs = fs; |
|
|
|
#if !defined( _RETAIL ) |
|
m_pszTrueFileName = 0; |
|
#endif |
|
} |
|
|
|
bool CFileHandle::IsValid() |
|
{ |
|
return ( m_nMagic == MAGIC ); |
|
} |
|
|
|
int CFileHandle::GetSectorSize() |
|
{ |
|
Assert( IsValid() ); |
|
|
|
if ( m_pFile ) |
|
{ |
|
return m_fs->FS_GetSectorSize( m_pFile ); |
|
} |
|
else if ( m_pPackFileHandle ) |
|
{ |
|
return m_pPackFileHandle->GetSectorSize(); |
|
} |
|
else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT ) |
|
{ |
|
return 1; |
|
} |
|
else |
|
{ |
|
return -1; |
|
} |
|
} |
|
|
|
bool CFileHandle::IsOK() |
|
{ |
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( m_VPKHandle ) |
|
{ |
|
return true; |
|
} |
|
#endif |
|
if ( m_pFile ) |
|
{ |
|
return ( IsValid() && m_fs->FS_ferror( m_pFile ) == 0 ); |
|
} |
|
else if ( m_pPackFileHandle || m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT ) |
|
{ |
|
return IsValid(); |
|
} |
|
|
|
m_fs->Warning( FILESYSTEM_WARNING, "FS: Tried to IsOk NULL file pointer inside valid file handle!\n" ); |
|
return false; |
|
} |
|
|
|
void CFileHandle::Flush() |
|
{ |
|
Assert( IsValid() ); |
|
|
|
if ( m_pFile ) |
|
{ |
|
m_fs->FS_fflush( m_pFile ); |
|
} |
|
} |
|
|
|
void CFileHandle::SetBufferSize( int nBytes ) |
|
{ |
|
Assert( IsValid() ); |
|
|
|
if ( m_pFile ) |
|
{ |
|
m_fs->FS_setbufsize( m_pFile, nBytes ); |
|
} |
|
else if ( m_pPackFileHandle ) |
|
{ |
|
m_pPackFileHandle->SetBufferSize( nBytes ); |
|
} |
|
} |
|
|
|
int CFileHandle::Read( void* pBuffer, int nLength ) |
|
{ |
|
Assert( IsValid() ); |
|
return Read( pBuffer, -1, nLength ); |
|
} |
|
|
|
int CFileHandle::Read( void* pBuffer, int nDestSize, int nLength ) |
|
{ |
|
Assert( IsValid() ); |
|
|
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( m_VPKHandle ) |
|
{ |
|
if ( nDestSize >= 0 ) |
|
nLength = MIN( nLength, nDestSize ); |
|
return m_VPKHandle.Read( pBuffer, nLength ); |
|
} |
|
#endif |
|
// Is this a regular file or a pack file? |
|
if ( m_pFile ) |
|
{ |
|
return m_fs->FS_fread( pBuffer, nDestSize, nLength, m_pFile ); |
|
} |
|
else if ( m_pPackFileHandle ) |
|
{ |
|
// Pack file handle handles clamping all the reads: |
|
return m_pPackFileHandle->Read( pBuffer, nDestSize, nLength ); |
|
} |
|
else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT ) |
|
{ |
|
return static_cast< CMemoryFileHandle* >( this )->Read( pBuffer, nDestSize, nLength ); |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int CFileHandle::Write( const void* pBuffer, int nLength ) |
|
{ |
|
tmZone( TELEMETRY_LEVEL0, TMZF_NONE, "%s", __FUNCTION__ ); |
|
if( ThreadInMainThread() ) |
|
{ |
|
tmPlotI32( TELEMETRY_LEVEL0, TMPT_MEMORY, 0, nLength, "FileBytesWrite" ); |
|
} |
|
|
|
Assert( IsValid() ); |
|
|
|
if ( !m_pFile ) |
|
{ |
|
m_fs->Warning( FILESYSTEM_WARNING, "FS: Tried to Write NULL file pointer inside valid file handle!\n" ); |
|
return 0; |
|
} |
|
|
|
size_t nBytesWritten = m_fs->FS_fwrite( (void*)pBuffer, nLength, m_pFile ); |
|
|
|
m_fs->Trace_FWrite(nBytesWritten,m_pFile); |
|
|
|
return nBytesWritten; |
|
} |
|
|
|
int CFileHandle::Seek( int64 nOffset, int nWhence ) |
|
{ |
|
Assert( IsValid() ); |
|
|
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( m_VPKHandle ) |
|
{ |
|
return m_VPKHandle.Seek( nOffset, nWhence ); |
|
} |
|
#endif |
|
if ( m_pFile ) |
|
{ |
|
m_fs->FS_fseek( m_pFile, nOffset, nWhence ); |
|
// TODO - FS_fseek should return the resultant offset |
|
return 0; |
|
} |
|
else if ( m_pPackFileHandle ) |
|
{ |
|
return m_pPackFileHandle->Seek( nOffset, nWhence ); |
|
} |
|
else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT ) |
|
{ |
|
return static_cast< CMemoryFileHandle* >( this )->Seek( nOffset, nWhence ); |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
int CFileHandle::Tell() |
|
{ |
|
Assert( IsValid() ); |
|
|
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( m_VPKHandle ) |
|
{ |
|
return m_VPKHandle.Tell(); |
|
} |
|
#endif |
|
if ( m_pFile ) |
|
{ |
|
return m_fs->FS_ftell( m_pFile ); |
|
} |
|
else if ( m_pPackFileHandle ) |
|
{ |
|
return m_pPackFileHandle->Tell(); |
|
} |
|
else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT ) |
|
{ |
|
return static_cast< CMemoryFileHandle* >( this )->Tell(); |
|
} |
|
|
|
return -1; |
|
} |
|
|
|
int CFileHandle::Size() |
|
{ |
|
Assert( IsValid() ); |
|
|
|
int nReturnedSize = -1; |
|
|
|
#if defined( SUPPORT_PACKED_STORE ) |
|
if ( m_VPKHandle ) |
|
{ |
|
return m_VPKHandle.m_nFileSize; |
|
} |
|
#endif |
|
if ( m_pFile ) |
|
{ |
|
nReturnedSize = m_nLength; |
|
} |
|
else if ( m_pPackFileHandle ) |
|
{ |
|
nReturnedSize = m_pPackFileHandle->Size(); |
|
} |
|
else if ( m_type == FT_MEMORY_BINARY || m_type == FT_MEMORY_TEXT ) |
|
{ |
|
return static_cast< CMemoryFileHandle* >( this )->Size(); |
|
} |
|
|
|
return nReturnedSize; |
|
} |
|
|
|
int64 CFileHandle::AbsoluteBaseOffset() |
|
{ |
|
Assert( IsValid() ); |
|
|
|
if ( !m_pFile && m_pPackFileHandle ) |
|
{ |
|
return m_pPackFileHandle->AbsoluteBaseOffset(); |
|
} |
|
else |
|
{ |
|
return 0; |
|
} |
|
} |
|
|
|
bool CFileHandle::EndOfFile() |
|
{ |
|
Assert( IsValid() ); |
|
|
|
return ( Tell() >= Size() ); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
int CMemoryFileHandle::Read( void* pBuffer, int nDestSize, int nLength ) |
|
{ |
|
nLength = min( nLength, (int) m_nLength - m_nPosition ); |
|
if ( nLength > 0 ) |
|
{ |
|
Assert( m_nPosition >= 0 ); |
|
memcpy( pBuffer, m_pBacking->m_pData + m_nPosition, nLength ); |
|
m_nPosition += nLength; |
|
return nLength; |
|
} |
|
if ( m_nPosition >= (int) m_nLength ) |
|
{ |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
int CMemoryFileHandle::Seek( int64 nOffset64, int nWhence ) |
|
{ |
|
if ( nWhence == SEEK_SET ) |
|
{ |
|
m_nPosition = (int) clamp( nOffset64, 0ll, m_nLength ); |
|
} |
|
else if ( nWhence == SEEK_CUR ) |
|
{ |
|
m_nPosition += (int) clamp( nOffset64, -(int64)m_nPosition, m_nLength - m_nPosition ); |
|
} |
|
else if ( nWhence == SEEK_END ) |
|
{ |
|
m_nPosition = (int) m_nLength + (int) clamp( nOffset64, -m_nLength, 0ll ); |
|
} |
|
return m_nPosition; |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
|
|
CBaseFileSystem::CFileCacheObject::CFileCacheObject( CBaseFileSystem* pFS ) |
|
: m_pFS( pFS ) |
|
{ |
|
} |
|
|
|
void CBaseFileSystem::CFileCacheObject::AddFiles( const char **ppFileNames, int nFileNames ) |
|
{ |
|
CUtlVector< Info_t* > infos; |
|
infos.SetCount( nFileNames ); |
|
for ( int i = 0; i < nFileNames; ++i ) |
|
{ |
|
infos[i] = new Info_t; |
|
Info_t &info = *infos[i]; |
|
info.pFileName = strdup( ppFileNames[i] ); |
|
V_FixSlashes( (char*) info.pFileName ); |
|
#ifdef _WIN32 |
|
Q_strlower( (char*) info.pFileName ); |
|
#endif |
|
info.hIOAsync = NULL; |
|
info.pBacking = NULL; |
|
info.pOwner = NULL; |
|
} |
|
|
|
AUTO_LOCK( m_InfosMutex ); |
|
int offset = m_Infos.AddMultipleToTail( nFileNames, infos.Base() ); |
|
|
|
m_nPending += nFileNames; |
|
ProcessNewEntries(offset); |
|
} |
|
|
|
void CBaseFileSystem::CFileCacheObject::ProcessNewEntries( int start ) |
|
{ |
|
for ( int i = start; i < m_Infos.Count(); ++i ) |
|
{ |
|
Info_t &info = *m_Infos[i]; |
|
if ( !info.pOwner ) |
|
{ |
|
info.pOwner = this; |
|
|
|
// NOTE: currently only caching files with GAME pathID |
|
FileAsyncRequest_t request; |
|
request.pszFilename = info.pFileName; |
|
request.pszPathID = "GAME"; |
|
request.flags = FSASYNC_FLAGS_ALLOCNOFREE; |
|
request.pfnCallback = &IOCallback; |
|
request.pContext = &info; |
|
if ( m_pFS->AsyncRead( request, &info.hIOAsync ) != FSASYNC_OK ) |
|
{ |
|
--m_nPending; |
|
} |
|
} |
|
} |
|
} |
|
|
|
void CBaseFileSystem::CFileCacheObject::IOCallback( const FileAsyncRequest_t &request, int nBytesRead, FSAsyncStatus_t err ) |
|
{ |
|
Assert( request.pContext ); |
|
Info_t &info = *(Info_t *)request.pContext; |
|
|
|
CMemoryFileBacking *pBacking = new CMemoryFileBacking( info.pOwner->m_pFS ); |
|
pBacking->m_pData = NULL; |
|
pBacking->m_pFileName = info.pFileName; |
|
pBacking->m_nLength = ( err == FSASYNC_OK && nBytesRead == 0 ) ? 0 : -1; |
|
|
|
if ( request.pData ) |
|
{ |
|
if ( err == FSASYNC_OK && nBytesRead > 0 ) |
|
{ |
|
pBacking->m_pData = (const char*) request.pData; // transfer data ownership |
|
pBacking->m_nLength = nBytesRead; |
|
} |
|
else |
|
{ |
|
info.pOwner->m_pFS->FreeOptimalReadBuffer( request.pData ); |
|
} |
|
} |
|
|
|
CMemoryFileBacking *pExistingBacking = NULL; |
|
if ( !info.pOwner->m_pFS->RegisterMemoryFile( pBacking, &pExistingBacking ) ) |
|
{ |
|
// Someone already registered this file |
|
info.pFileName = pExistingBacking->m_pFileName; |
|
pBacking->Release(); |
|
pBacking = pExistingBacking; |
|
} |
|
|
|
//DevWarning("preload %s %d\n", request.pszFilename, pBacking->m_nLength); |
|
|
|
info.pBacking = pBacking; |
|
info.pOwner->m_nPending--; |
|
} |
|
|
|
CBaseFileSystem::CFileCacheObject::~CFileCacheObject() |
|
{ |
|
AUTO_LOCK( m_InfosMutex ); |
|
for ( int i = 0; i < m_Infos.Count(); ++i ) |
|
{ |
|
Info_t& info = *m_Infos[i]; |
|
if ( info.hIOAsync ) |
|
{ |
|
// job is guaranteed to not be running after abort |
|
m_pFS->AsyncAbort( info.hIOAsync ); |
|
m_pFS->AsyncRelease( info.hIOAsync ); |
|
} |
|
|
|
if ( info.pBacking ) |
|
{ |
|
m_pFS->UnregisterMemoryFile( info.pBacking ); |
|
info.pBacking->Release(); |
|
} |
|
else |
|
{ |
|
free( (char*) info.pFileName ); |
|
} |
|
|
|
delete m_Infos[i]; |
|
} |
|
Assert( m_nPending == 0 ); |
|
}
|
|
|