//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //===========================================================================// #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 "tier0/stacktools.h" #include "generichash.h" #include "tier1/utllinkedlist.h" #include "filesystem/IQueuedLoader.h" #include "filesystem/IXboxInstaller.h" #include "tier2/tier2.h" #include "tier1/lzmaDecoder.h" #include "vstdlib/vstrtools.h" #include "zip_utils.h" #include "fmtstr.h" #ifdef _X360 #include "xbox/xbox_launch.h" #include "xbox/xbox_console.h" #elif defined( _PS3 ) #include #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 #endif #if defined( _X360 ) #include "xbox\xbox_win32stubs.h" #undef GetCurrentDirectory #endif #ifdef _PS3 #include "ps3/ps3_core.h" #include "ps3_pathinfo.h" #include "tls_ps3.h" #include // extern bool g_bUseBdvdGameData; #ifndef PLATFORM_EXT #pragma message("PLATFORM_EXT define is missing, wtf?") #define PLATFORM_EXT ".ps3" #endif // ifndef PLATFORM_EXT void getcwd(...) { AssertMsg(false, "getcwd does not exist on PS3\n"); } bool SetupFios(); bool TeardownFios(); #endif // _PS3 #ifdef _X360 #define FS_DVDDEV_REMAP_ROOT "d:" #define FS_DVDDEV_ROOT "d:\\dvddev" #define FS_EXCLUDE_PATHS_FILENAME "xbox_exclude_paths.txt" #elif defined( _PS3 ) #define FS_DVDDEV_REMAP_ROOT g_pPS3PathInfo->GameImagePath() #define FS_DVDDEV_ROOT "/app_home/dvddev" #define FS_EXCLUDE_PATHS_FILENAME "ps3_exclude_paths.txt" #else #define FS_DVDDEV_REMAP_ROOT "" #define FS_DVDDEV_ROOT "dvddev???:::" #define FS_EXCLUDE_PATHS_FILENAME "allbad_exclude_paths.txt" #endif #ifdef _GAMECONSOLE static bool IsDvdDevPathString( char const *szPath ) { if ( IsGameConsole() && StringAfterPrefix( szPath, FS_DVDDEV_ROOT ) && szPath[ sizeof( FS_DVDDEV_ROOT ) - 1 ] == CORRECT_PATH_SEPARATOR ) { return true; } else if ( IsX360() ) { const char *pFirstDir = V_strstr( szPath, ":" ); if ( pFirstDir ) { // skip past colon/slash pFirstDir += 2; return ( V_strnicmp( pFirstDir, "dvddev", 6 ) == false ); } } return false; } #else #define IsDvdDevPathString( x ) false #endif // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #pragma warning( disable : 4355 ) // warning C4355: 'this' : used in base member initializer list ConVar fs_report_sync_opens( "fs_report_sync_opens", "0", FCVAR_RELEASE, "0:Off, 1:Always, 2:Not during map load" ); ConVar fs_report_sync_opens_callstack( "fs_report_sync_opens_callstack", "0", 0, "0 to not display the call-stack when we hit a fs_report_sync_opens warning. Set to 1 to display the call-stack." ); ConVar fs_report_long_reads( "fs_report_long_reads", "0", 0, "0:Off, 1:All (for tracking accumulated duplicate read times), >1:Microsecond threshold" ); ConVar fs_warning_mode( "fs_warning_mode", "0", 0, "0:Off, 1:Warn main thread, 2:Warn other threads" ); ConVar fs_monitor_read_from_pack( "fs_monitor_read_from_pack", "0", 0, "0:Off, 1:Any, 2:Sync only" ); #if IsPlatformPS3() ConVar fs_fios_spew_prefetches( "fs_fios_spew_prefetches", "0", 0, "Set this to 1 to output prefetch operations, otherwise set this to 0." ); ConVar fs_fios_enabled( "fs_fios_enabled", "0", 0, "Set this to 1 to enable FIOS, otherwise set this to 0." ); #endif #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; #if defined (_PS3) // Copied from zip_utils.cpp (we don't want to add the file to the project (for now)) BEGIN_BYTESWAP_DATADESC( ZIP_EndOfCentralDirRecord ) DEFINE_FIELD( signature, FIELD_INTEGER ), DEFINE_FIELD( numberOfThisDisk, FIELD_SHORT ), DEFINE_FIELD( numberOfTheDiskWithStartOfCentralDirectory, FIELD_SHORT ), DEFINE_FIELD( nCentralDirectoryEntries_ThisDisk, FIELD_SHORT ), DEFINE_FIELD( nCentralDirectoryEntries_Total, FIELD_SHORT ), DEFINE_FIELD( centralDirectorySize, FIELD_INTEGER ), DEFINE_FIELD( startOfCentralDirOffset, FIELD_INTEGER ), DEFINE_FIELD( commentLength, FIELD_SHORT ), END_BYTESWAP_DATADESC() BEGIN_BYTESWAP_DATADESC( ZIP_FileHeader ) DEFINE_FIELD( signature, FIELD_INTEGER ), DEFINE_FIELD( versionMadeBy, FIELD_SHORT ), DEFINE_FIELD( versionNeededToExtract, FIELD_SHORT ), DEFINE_FIELD( flags, FIELD_SHORT ), DEFINE_FIELD( compressionMethod, FIELD_SHORT ), DEFINE_FIELD( lastModifiedTime, FIELD_SHORT ), DEFINE_FIELD( lastModifiedDate, FIELD_SHORT ), DEFINE_FIELD( crc32, FIELD_INTEGER ), DEFINE_FIELD( compressedSize, FIELD_INTEGER ), DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ), DEFINE_FIELD( fileNameLength, FIELD_SHORT ), DEFINE_FIELD( extraFieldLength, FIELD_SHORT ), DEFINE_FIELD( fileCommentLength, FIELD_SHORT ), DEFINE_FIELD( diskNumberStart, FIELD_SHORT ), DEFINE_FIELD( internalFileAttribs, FIELD_SHORT ), DEFINE_FIELD( externalFileAttribs, FIELD_INTEGER ), DEFINE_FIELD( relativeOffsetOfLocalHeader, FIELD_INTEGER ), END_BYTESWAP_DATADESC() #endif void FixUpPathCaseForPS3(const char* pFilePath) { char* prev_ptr = NULL; char* last_ptr = NULL; // This is really bad but the EA code does it so let's give it a try for now char* pFilePathNonConst = const_cast< char * >( pFilePath ); char* ptr = pFilePathNonConst; //Convert all "\" to forward "/" and reformat relative paths while ( *ptr ) { if ( *ptr == '\\' || *ptr == '/' ) { *ptr='/'; while(ptr[1]=='\\' || ptr[1] == '/') //Get rid of multiple slashes { strcpy(ptr+1,ptr+2); } if(strncmp(ptr+1,"..",2)==0 && ptr[3]!=0 && ptr[4]!=0 && last_ptr) //Some relative paths are used at runtime in Team Fortress { //printf("Changing relative path %s to ... ", pFilePathNonConst); strcpy(last_ptr+1, ptr+4); //Remove relative path if(strncmp(last_ptr+1,"..",2)==0 && last_ptr[3]!=0 && last_ptr[4]!=0 && prev_ptr) //Sometimes get /../../ strings { strcpy(prev_ptr+1, last_ptr+4); if(strncmp(prev_ptr+1,"..",2)==0) { printf("Error: Can't process PS3 filenames containing /../../../\n"); Assert(0); } } //printf("%s\n", pFilePathNonConst); } prev_ptr = last_ptr; last_ptr = ptr; } ptr++; } // terrible, terrible cruft: savegames (*.HL?) are written with uppercase from a million // different places. For now, I'm just going to leave them alone here, rather than try // to find every single possible place that has a savegame go through it (as an alias // of a copy of an alias of a string that's impossible to track by grepping). Y-U-C-K. if ( V_strstr(pFilePath, ".HL") ) { return; } //PS3 file system is case sensitive (though this isn't enforced for /app_home/) if(pFilePathNonConst[0]=='/') { // if we're in the USRDIR directory, don't mess with paths up to that point char *pAfterUsrDir = V_strstr(pFilePathNonConst, "USRDIR"); if ( pAfterUsrDir ) { strlwr( pAfterUsrDir + 6 ); } else if ((strnicmp(pFilePathNonConst,"/app_home/",10)==0) || (strnicmp(pFilePathNonConst,"/dev_bdvd/",10)==0) || (strnicmp(pFilePathNonConst,"/host_root/",11)==0)) { strlwr(pFilePathNonConst+10); } else if (strnicmp(pFilePathNonConst,"/dev_hdd0/game/",15)==0) { strlwr(pFilePathNonConst+15); } else { //Lowercase everything after second "/" ptr=strchr(pFilePathNonConst,'/'); if (ptr) ptr=strchr(ptr+1,'/'); if (ptr) strlwr(ptr); } } else { //Lowercase everything strlwr(pFilePathNonConst); } } // Look for cases like materials\\blah.vmt. 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] == '\\') ) return true; } return false; } // // Format relative filename when used under a search path // allows "symlinking" official workshop locations into // official locations in shipping depots. // // Returns passed pFileName if no symlinking occurs, // or pointer to temp symlink buffer containing the symlink target. // static char const * V_FormatFilenameForSymlinking( char (&tempSymlinkBuffer)[MAX_PATH], char const *pFileName ) { if ( !pFileName ) return NULL; if ( !V_strnicmp( pFileName, "maps", 4 ) && ( ( pFileName[4] == CORRECT_PATH_SEPARATOR ) || ( pFileName[4] == INCORRECT_PATH_SEPARATOR ) ) && !V_strnicmp( pFileName + 5, "workshop", 8 ) && ( ( pFileName[13] == CORRECT_PATH_SEPARATOR ) || ( pFileName[13] == INCORRECT_PATH_SEPARATOR ) ) ) { // maps/workshop/ if ( ( false /** Removed for partner depot **/ ) && ( ( pFileName[23] == CORRECT_PATH_SEPARATOR ) || ( pFileName[23] == INCORRECT_PATH_SEPARATOR ) ) ) { Q_snprintf( tempSymlinkBuffer, ARRAYSIZE( tempSymlinkBuffer ), "maps%c%s", pFileName[4], pFileName + 24 ); return tempSymlinkBuffer; } } static bool bLoadBannedWords = ( !!CommandLine()->FindParm( "-usebanlist" ) ) || (!!CommandLine()->FindParm( "-perfectworld" ) ); if ( bLoadBannedWords ) { if ( !V_strnicmp( pFileName, "maps", 4 ) && ( ( pFileName[ 4 ] == CORRECT_PATH_SEPARATOR ) || ( pFileName[ 4 ] == INCORRECT_PATH_SEPARATOR ) ) && !V_strnicmp( pFileName + 5, "ar_monastery", 12 ) ) { // maps/ar_monastery -> maps/ar_shoots Q_snprintf( tempSymlinkBuffer, ARRAYSIZE( tempSymlinkBuffer ), "maps%car_shoots%s", pFileName[ 4 ], pFileName + 17 ); return tempSymlinkBuffer; } } return pFileName; // nothing symlinked here } // This can be used to easily fix a filename on the stack. #define CHECK_DOUBLE_SLASHES( x ) Assert( V_CheckDoubleSlashes(x) == false ); // Win32 dedicated.dll contains both filesystem_steam.cpp and filesystem_stdio.cpp, so it has two // CBaseFileSystem objects. We'll let it manage BaseFileSystem() itself. #if !( defined(_WIN32) && defined(DEDICATED) ) || defined( _PS3 ) static CBaseFileSystem *g_pBaseFileSystem; CBaseFileSystem *BaseFileSystem() { return g_pBaseFileSystem; } #endif ConVar filesystem_buffer_size( "filesystem_buffer_size", "0", 0, "Size of per file buffers. 0 for none" ); class CFileHandleTimer : public CFastTimer { public: FileHandle_t m_hFile; char m_szName[ MAX_PATH ]; }; struct FileOpenDuplicateTime_t { char m_szName[ MAX_PATH ]; int m_nLoadCount; float m_flAccumulatedMicroSeconds; FileOpenDuplicateTime_t() { m_szName[ 0 ] = '\0'; m_nLoadCount = 0; m_flAccumulatedMicroSeconds = 0.0f; } }; CUtlVector< FileOpenDuplicateTime_t* > g_FileOpenDuplicateTimes; // Used to debug approximate time spent reading files duplicate times CThreadFastMutex g_FileOpenDuplicateTimesMutex; #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; bool CBaseFileSystem::m_bFoundXboxImageInCache; bool CBaseFileSystem::m_bLaunchedFromXboxHDD; bool CBaseFileSystem::m_bDVDHosted; bool CBaseFileSystem::m_bAllowXboxInstall; bool CBaseFileSystem::m_bSearchPathsPatchedAfterInstall; CUtlVector< FileNameHandle_t > CBaseFileSystem::m_ExcludeFilePaths; CUtlSortVector< DLCContent_t, CDLCLess > CBaseFileSystem::m_DLCContents; CUtlVector< DLCCorrupt_t > CBaseFileSystem::m_CorruptDLC; CUtlBuffer g_UpdateZipBuffer; CUtlBuffer g_XLSPPatchZipBuffer; class CStoreIDEntry { public: CStoreIDEntry() {} CStoreIDEntry( const char *pPathIDStr, int storeID ) { m_PathIDString = pPathIDStr; m_StoreID = storeID; } public: CUtlSymbol m_PathIDString; int m_StoreID; }; //----------------------------------------------------------------------------- // CSimpleFileList (used by CFileCRCTracker). // Uses a dictionary to refer to the list of files. //----------------------------------------------------------------------------- class CFileSystemReloadFileList : public IFileList { public: CFileSystemReloadFileList( CBaseFileSystem *pFileSystem ) { m_pFileSystem = pFileSystem; } virtual void Release() { delete this; } // The engine is calling this for any files it wants to be pure. // Return true if this file should be reloaded based on its current state and the whitelist that we have now. virtual bool IsFileInList( const char *pFilename ) { bool bRet = m_pFileSystem->ShouldGameReloadFile( pFilename ); return bRet; } private: CBaseFileSystem *m_pFileSystem; }; static CStoreIDEntry* FindPrevFileByStoreID( CUtlDict< CUtlVector* ,int> &filesByStoreID, const char *pFilename, const char *pPathIDStr, int foundStoreID ) { int iEntry = filesByStoreID.Find( pFilename ); if ( iEntry == filesByStoreID.InvalidIndex() ) { CUtlVector *pList = new CUtlVector; 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 *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; } } //----------------------------------------------------------------------------- // IIOStats implementation //----------------------------------------------------------------------------- #ifndef _CERT class CIoStats : public IIoStats { public: CIoStats(); ~CIoStats(); virtual void OnFileSeek( int nTimeInMs ); virtual void OnFileRead( int nTimeInMs, int nBytesRead ); virtual void OnFileOpen( const char * pFileName ); virtual int GetNumberOfFileSeeks(); virtual int GetTimeInFileSeek(); virtual int GetNumberOfFileReads(); virtual int GetTimeInFileReads(); virtual int GetFileReadTotalSize(); virtual int GetNumberOfFileOpens(); void Reset(); private: CInterlockedInt m_nNumberOfFileSeeks; CInterlockedInt m_nTimeInFileSeek; CInterlockedInt m_nNumberOfFileReads; CInterlockedInt m_nTimeInFileRead; CInterlockedInt m_nFileReadTotalSize; CInterlockedInt m_nNumberOfFileOpens; }; static CIoStats s_IoStats; CIoStats::CIoStats() : m_nNumberOfFileSeeks( 0 ), m_nTimeInFileSeek( 0 ), m_nNumberOfFileReads( 0 ), m_nTimeInFileRead( 0 ), m_nFileReadTotalSize( 0 ), m_nNumberOfFileOpens( 0 ) { // Do nothing... } CIoStats::~CIoStats() { // Do nothing... } void CIoStats::OnFileSeek( int nTimeInMs ) { ++m_nNumberOfFileSeeks; m_nTimeInFileSeek += nTimeInMs; } void CIoStats::OnFileRead( int nTimeInMs, int nBytesRead ) { ++m_nNumberOfFileReads; m_nTimeInFileRead += nTimeInMs; m_nFileReadTotalSize += nBytesRead; } void CIoStats::OnFileOpen( const char * pFileName ) { ++m_nNumberOfFileOpens; } int CIoStats::GetNumberOfFileSeeks() { return m_nNumberOfFileSeeks; } int CIoStats::GetTimeInFileSeek() { return m_nTimeInFileSeek; } int CIoStats::GetNumberOfFileReads() { return m_nNumberOfFileReads; } int CIoStats::GetTimeInFileReads() { return m_nTimeInFileRead; } int CIoStats::GetFileReadTotalSize() { return m_nFileReadTotalSize; } int CIoStats::GetNumberOfFileOpens() { return m_nNumberOfFileOpens; } void CIoStats::Reset() { m_nNumberOfFileSeeks = 0; m_nTimeInFileSeek = 0; m_nNumberOfFileReads = 0; m_nTimeInFileRead = 0; m_nFileReadTotalSize = 0; m_nNumberOfFileOpens = 0; } #endif //----------------------------------------------------------------------------- // constructor //----------------------------------------------------------------------------- CBaseFileSystem::CBaseFileSystem() : m_FileTracker( this ), m_FileWhitelist( NULL ),m_FileTracker2( this ) { #if !( defined(_WIN32) && defined(DEDICATED) ) g_pBaseFileSystem = this; #endif g_pFullFileSystem = this; // Left in for non tier Apps, tools, etc... 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_pThreadPool = NULL; #if defined( TRACK_BLOCKING_IO ) m_pBlockingItems = new CBlockingFileItemList( this ); m_bBlockingFileAccessReportingEnabled = false; m_bAllowSynchronousLogging = true; #endif m_iMapLoad = 0; m_DVDMode = DVDMODE_STRICT; if ( IsGameConsole() ) { if ( CommandLine()->FindParm( "-dvddev" ) ) { // FIXME: Is there a way of determining the host is a vista machine without // needing to explicitly say so on the command line? bool bIsVista = ( CommandLine()->CheckParm( "-vista" ) != NULL ); if ( bIsVista ) { m_DVDMode = DVDMODE_DEV_VISTA; } else { m_DVDMode = DVDMODE_DEV; } } } #ifdef SUPPORT_IODELAY_MONITORING m_pDelayThread = NULL; m_flDelayLimit = 0; #endif } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseFileSystem::~CBaseFileSystem() { m_PathIDInfos.PurgeAndDeleteElements(); #if defined( TRACK_BLOCKING_IO ) delete m_pBlockingItems; #endif // Free the whitelist. RegisterFileWhitelist( NULL, 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; } #ifdef _PS3 // this is strictly a debug variable used to catch errors where we load and tear down more than one filesystem: static int s_PS3_libfs_ref_count = 0; #endif InitReturnVal_t CBaseFileSystem::Init() { m_FileTracker2.InitAsyncThread(); InitReturnVal_t nRetVal = BaseClass::Init(); if ( nRetVal != INIT_OK ) return nRetVal; #ifdef _PS3 // load the PS3's file system module to memory AssertMsg1( s_PS3_libfs_ref_count == 0, "%d CBaseFileSystems were instantiated!\n", s_PS3_libfs_ref_count+1 ); if ( cellSysmoduleLoadModule(CELL_SYSMODULE_FS) != CELL_OK ) { Error( "Could not load system libfs!\n" ); } else { s_PS3_libfs_ref_count += 1; } #endif // 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 ( IsGameConsole() ) { BuildExcludeList(); } #if defined( _X360 ) MEM_ALLOC_CREDIT(); #if defined( _DEMO ) // under demo conditions cannot allow install or use existing install // slam to expected state, do not override m_bLaunchedFromXboxHDD = false; m_bFoundXboxImageInCache = false; m_bAllowXboxInstall = false; m_bDVDHosted = true; #else // determine the type of system where we launched from // this allows other systems (like the installer) to conditionalize the install process // MS may very well auto-install for us at a later date DWORD dwDummyFlags; char szFileSystemName[MAX_PATH]; DWORD dwResult = GetVolumeInformation( "D:\\", NULL, 0, NULL, 0, &dwDummyFlags, szFileSystemName, sizeof( szFileSystemName ) ); if ( dwResult != 0 ) { m_bLaunchedFromXboxHDD = ( V_stricmp( szFileSystemName, "FATX" ) == 0 ); } if ( m_DVDMode == DVDMODE_STRICT ) { // must be in a strict dvd environment and not explicitly disabled if ( !CommandLine()->FindParm( "-noinstall" ) ) { // the install is allowed if we launched from anywhere but the HDD // or it can be tested from the HDD by forcing with command line options m_bAllowXboxInstall = ( m_bLaunchedFromXboxHDD == false ) || ( CommandLine()->FindParm( "-installer" ) != 0 ) || ( CommandLine()->FindParm( "-install" ) != 0 ); if ( m_bAllowXboxInstall ) { // install may have already occurred m_bFoundXboxImageInCache = IsAlreadyInstalledToXboxHDDCache(); if ( m_bFoundXboxImageInCache ) { // we are using the installed image // no further installer activity is ever allowed (as the targets will be opened) m_bAllowXboxInstall = false; } } } // The update zip is designed to be held resident to avoid MU yanking or other transient issues. // The zip is expected to be < 100K and is a special compressed format. const char *pszUpdatePath = "UPDATE:\\update\\update" PLATFORM_EXT ".zip"; if ( !IsCert() && !FileExists( pszUpdatePath ) ) { // allows us to fallback and test when it is in the image pszUpdatePath = "D:\\update\\update" PLATFORM_EXT ".zip"; } ReadFile( pszUpdatePath, NULL, g_UpdateZipBuffer, 0, 0 ); } // if we are in any way HDD based, we do not want the reduced DVD experience m_bDVDHosted = ( m_bAllowXboxInstall || CommandLine()->FindParm( "-dvdtest" ) || ( !m_bLaunchedFromXboxHDD && !m_bFoundXboxImageInCache ) ); #endif #elif defined( _PS3 ) m_bLaunchedFromXboxHDD = true; m_bFoundXboxImageInCache = false; m_bAllowXboxInstall = false; m_bDVDHosted = false; SetupFios(); #endif return INIT_OK; } void CBaseFileSystem::Shutdown() { ShutdownAsync(); m_FileTracker2.ShutdownAsync(); #if !defined( _X360 ) && !defined( _PS3 ) 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 RemoveAllSearchPaths(); Trace_DumpUnclosedFiles(); #if defined( _PS3 ) TeardownFios(); if ( --s_PS3_libfs_ref_count == 0 ) { cellSysmoduleUnloadModule(CELL_SYSMODULE_FS); } else { AssertMsg( false, "More than one CBaseFileSystem was instantiated! Failsafe triggered to refcount sysutil libfs.\n" ); } #endif BaseClass::Shutdown(); } void CBaseFileSystem::BuildExcludeList() { if ( !IsGameConsole() || m_DVDMode != DVDMODE_DEV ) { // xbox only return; } // clear prior results m_ExcludeFilePaths.Purge(); // exclude paths are valid only in dvddev mode // the optional exclude path file should exist in the dvddev cache // populate the exclusion list CUtlBuffer buf( 0, 0, CUtlBuffer::TEXT_BUFFER ); if ( ReadFile( FS_DVDDEV_ROOT "/" FS_EXCLUDE_PATHS_FILENAME, 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; #ifndef _CERT // At some point, we should also test that the corresponding file exist in the normal path // It is a bit more complicated on consoles due to the zip files. V_ComposeFileName( FS_DVDDEV_ROOT, pToken, szPath, sizeof( szPath ) ); bool bExist = FileExists( szPath ); if ( bExist == false) { Warning( "File '%s' does not exist. Won't be added to dvddev.\n", szPath ); continue; } #endif V_ComposeFileName( FS_DVDDEV_REMAP_ROOT, pToken, szPath, sizeof( szPath ) ); FileNameHandle_t hFileName = FindOrAddFileName( szPath ); if ( m_ExcludeFilePaths.Find( hFileName ) == -1 ) { m_ExcludeFilePaths.AddToTail( hFileName ); DevMsg( "File '%s' added to dvddev.\n", szPath ); } } } } //----------------------------------------------------------------------------- // 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 ) { FileSystemWarning( 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 *filename, const char *options, unsigned flags, int64 *size, CFileLoadInfo *pInfo ) { if ( m_NonexistingFilesExtensions.GetNumStrings() ) { if ( char const *pszExt = V_GetFileExtension( filename ) ) { AUTO_LOCK( m_OpenedFilesMutex ); UtlSymId_t symFound = m_NonexistingFilesExtensions.Find( pszExt ); if ( ( symFound != UTL_INVAL_SYMBOL ) && m_NonexistingFilesExtensions[ symFound ] ) { DevWarning( "Known VPK-only extension [%s], file {%s} declared missing. Run with -fullfsvalveds to search filesystem.\n", pszExt, filename ); return NULL; } } } #ifdef NONEXISTING_FILES_CACHE_SUPPORT bool bReadOnlyRequest = !strchr(options,'w') && !strchr(options,'a') && !strchr(options,'+'); static bool s_bNeverCheckFS = !CommandLine()->FindParm( "-alwayscheckfs" ); if ( s_bNeverCheckFS ) { AUTO_LOCK( m_OpenedFilesMutex ); UtlSymId_t symFound = m_NonexistingFilesCache.Find( filename ); if ( symFound != UTL_INVAL_SYMBOL ) { double &refCacheTime = m_NonexistingFilesCache[ symFound ]; if ( bReadOnlyRequest ) { if ( refCacheTime != 0.0 ) { Warning( "Trace_FOpen: duplicate request for missing file: %s [was missing %.3f sec ago]\n", filename, Plat_FloatTime() - refCacheTime ); return NULL; // we looked for this file already, it doesn't exist } else { // This file was previously missing, but a write request was made and could have created the file, so this read call should fall through } } else { // This is possibly a write request, so remove cached ENOENT record Warning( "Trace_FOpen: possibly write request for missing file: %s [was missing %.3f sec ago]\n", filename, Plat_FloatTime() - refCacheTime ); refCacheTime = 0.0f; } } else { // Nothing known about this file, fall through into syscall to fopen } } #endif AUTOBLOCKREPORTER_FN( Trace_FOpen, this, true, filename, FILESYSTEM_BLOCKING_SYNCHRONOUS, FileBlockingItem::FB_ACCESS_OPEN ); FILE *fp = FS_fopen( filename, options, flags, size, pInfo ); #ifdef NONEXISTING_FILES_CACHE_SUPPORT if ( s_bNeverCheckFS && !fp && bReadOnlyRequest ) { double dblNow = Plat_FloatTime(); AUTO_LOCK( m_OpenedFilesMutex ); m_NonexistingFilesCache[ filename ] = dblNow; Warning( "Trace_FOpen: missing file: %s [will never check again]\n", filename ); } #endif 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; } // Pack file filehandles store the underlying name for convenience if ( fh->IsPack() ) { Q_strncpy( buf, fh->Name(), buflen ); return; } 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 ) { FileSystemWarning( 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 ) { FileSystemWarning( 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 ]; FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES_READ, "---FS%s: read %s %i %p (%.3f)\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp, Plat_FloatTime() ); } else { FileSystemWarning( 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 ]; FileSystemWarning( FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE, "---FS%s: write %s %i %p\n", ThreadInMainThread() ? "" : "[a]", found.GetName(), size, fp ); } else { FileSystemWarning( 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 ) { FileSystemWarning( 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; } void CBaseFileSystem::AddVPKFile( char const *pBasename, SearchPathAdd_t addType ) { // Ensure that the passed in file name has a .vpk extension. Otherwise the check // for already having the .vpk file will always fail and the same file may get // added dozens of times, wasting hundreds of MB of memory. const char *pExtension = strrchr( pBasename, '.' ); Assert( pExtension && V_strcmp( pExtension, ".vpk" ) == 0 ); if ( !pExtension || V_strcmp( pExtension, ".vpk" ) ) { Warning( "Extensionless VPK file '%s' specified. Ignoring.\n", pBasename ); return; } #ifdef SUPPORT_VPK char nameBuf[MAX_PATH]; Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pBasename ); #ifdef _WIN32 Q_strlower( nameBuf ); #endif Q_FixSlashes( nameBuf ); // see if we already have this vpk file for( int i = 0; i < m_VPKFiles.Count(); i++ ) { if ( ! V_strcmp( m_VPKFiles[i]->FullPathName(), nameBuf ) ) { return; // already have this one } } char pszFName[MAX_PATH]; CPackedStore *pNew = new CPackedStore( nameBuf, pszFName, this ); pNew->RegisterFileTracker( (IThreadedFileMD5Processor *)&m_FileTracker2 ); if ( pNew->IsEmpty() ) { delete pNew; } else { if ( PATH_ADD_TO_TAIL == addType ) { m_VPKFiles.AddToTail( pNew ); } else { m_VPKFiles.AddToHead( pNew ); } char szRelativePathName[512]; Assert ( V_IsAbsolutePath( pNew->FullPathName() ) ); char szBasePath[MAX_PATH]; V_strncpy( szBasePath, pNew->FullPathName(), sizeof(szBasePath) ); V_StripFilename( szBasePath ); V_StripLastDir( szBasePath, sizeof(szBasePath) ); V_MakeRelativePath( pNew->FullPathName(), szBasePath, szRelativePathName, sizeof( szRelativePathName ) ); pNew->m_PackFileID = m_FileTracker2.NotePackFileOpened( pszFName, szRelativePathName, "GAME", 0 ); } #endif } void CBaseFileSystem::RemoveVPKFile( char const *pBasename ) { #ifdef SUPPORT_VPK char nameBuf[MAX_PATH]; Q_MakeAbsolutePath( nameBuf, sizeof( nameBuf ), pBasename ); Q_strlower( nameBuf ); Q_FixSlashes( nameBuf ); // see if we already have this vpk file for( int i = 0; i < m_VPKFiles.Count(); i++ ) { if ( ! V_strcmp( m_VPKFiles[i]->FullPathName(), nameBuf ) ) { delete m_VPKFiles[i]; m_VPKFiles.Remove( i ); break; // already have this one } } #endif } void CBaseFileSystem::GetVPKFileNames( CUtlVector &destVector ) { #ifdef SUPPORT_VPK for( int i = 0; i < m_VPKFiles.Count(); i++ ) { destVector.AddToTail( CUtlString( m_VPKFiles[i]->FullPathName() ) ); } #endif } //----------------------------------------------------------------------------- // 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 ) { // 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; } } #ifdef SUPPORT_VPK else { pf->m_hPackFileHandleVPK = FindFileInVPKs( fullpath ); if ( pf->m_hPackFileHandleVPK ) { // Get the length of the pack file: pf->m_hPackFileHandleVPK.Seek( 0, FILESYSTEM_SEEK_TAIL ); int64 len = pf->m_hPackFileHandleVPK.Tell(); pf->m_hPackFileHandleVPK.Seek( 0, FILESYSTEM_SEEK_HEAD ); if ( !pf->Prepare( len ) ) { // Failed for some reason, ignore it delete pf; return false; } } } #endif // 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; } // Read a bit of the file from the pack file: int CPackFileHandle::Read( void* pBuffer, int nDestSize, int nBytes ) { // Clamp nBytes to not go past the end of the file (async is still possible due to nDestSize) if ( nBytes + m_nFilePointer > m_nLength ) { nBytes = m_nLength - m_nFilePointer; } // Seek to the given file pointer and read int nBytesRead = m_pOwner->ReadFromPack( m_nIndex, pBuffer, nDestSize, nBytes, m_nBase + m_nFilePointer ); m_nFilePointer += nBytesRead; return nBytesRead; } // Seek around inside the pack: int CPackFileHandle::Seek( int nOffset, int nWhence ) { if ( nWhence == SEEK_SET ) { m_nFilePointer = nOffset; } else if ( nWhence == SEEK_CUR ) { m_nFilePointer += nOffset; } else if ( nWhence == SEEK_END ) { m_nFilePointer = m_nLength + nOffset; } // Clamp the file pointer to the actual bounds of the file: if ( m_nFilePointer > m_nLength ) { m_nFilePointer = m_nLength; } return m_nFilePointer; } //----------------------------------------------------------------------------- // Low Level I/O routine for reading from pack files. // Offsets all reads by the base of the pack file as needed. // Return bytes read. //----------------------------------------------------------------------------- int CPackFile::ReadFromPack( int nIndex, void* buffer, int nDestBytes, int nBytes, int64 nOffset ) { m_mutex.Lock(); if ( fs_monitor_read_from_pack.GetInt() == 1 || ( fs_monitor_read_from_pack.GetInt() == 2 && ThreadInMainThread() ) ) { // spew info about real i/o request char szName[MAX_PATH]; IndexToFilename( nIndex, szName, sizeof( szName ) ); Msg( "Read From Pack: Sync I/O: Requested:%7d, Offset:0x%16.16llx, %s\n", nBytes, m_nBaseOffset + nOffset, szName ); } int nBytesRead = 0; // Seek to the start of the read area and perform the read: TODO: CHANGE THIS INTO A CFileHandle if ( m_hPackFileHandleFS ) { m_fs->FS_fseek( m_hPackFileHandleFS, m_nBaseOffset + nOffset, SEEK_SET ); nBytesRead = m_fs->FS_fread( buffer, nDestBytes, nBytes, m_hPackFileHandleFS ); } #ifdef SUPPORT_VPK else if ( m_hPackFileHandleVPK ) { // We're a packfile embedded in a VPK m_hPackFileHandleVPK.Seek( m_nBaseOffset + nOffset, FILESYSTEM_SEEK_HEAD ); nBytesRead = m_hPackFileHandleVPK.Read( buffer, nBytes ); } #endif else { Error("Failure in CPackFile::ReadFromPack(): m_hPackFileHandleFS and/or m_hPackFileHandleVPK are uninitialized - The file open call(s) likely failed\n"); } m_mutex.Unlock(); return nBytesRead; } //----------------------------------------------------------------------------- // Open a file inside of a pack file. //----------------------------------------------------------------------------- CFileHandle *CPackFile::OpenFile( const char *pFileName, const char *pOptions ) { int nIndex, nLength; int64 nPosition; // find the file's location in the pack if ( FindFile( pFileName, nIndex, nPosition, nLength ) ) { m_mutex.Lock(); #ifdef SUPPORT_VPK if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL && !m_hPackFileHandleVPK ) #else if ( m_nOpenFiles == 0 && m_hPackFileHandleFS == NULL ) #endif { // Try to open it as a regular file first m_hPackFileHandleFS = m_fs->Trace_FOpen( m_ZipName, "rb", 0, NULL ); #ifdef SUPPORT_VPK // Try opening from a VPK if ( !m_hPackFileHandleFS ) { m_hPackFileHandleVPK = m_fs->FindFileInVPKs( pFileName ); } #endif } m_nOpenFiles++; m_mutex.Unlock(); CPackFileHandle* ph = new CPackFileHandle( this, nPosition, nLength, nIndex ); CFileHandle *fh = new CFileHandle( m_fs ); fh->m_pPackFileHandle = ph; fh->m_nLength = nLength; // The default mode for fopen is text, so require 'b' for binary if ( strstr( pOptions, "b" ) == NULL ) { fh->m_type = FT_PACK_TEXT; } else { fh->m_type = FT_PACK_BINARY; } fh->SetName( pFileName ); return fh; } return NULL; } //----------------------------------------------------------------------------- // Get a directory entry from a pack's preload section //----------------------------------------------------------------------------- ZIP_PreloadDirectoryEntry* CZipPackFile::GetPreloadEntry( int nEntryIndex ) { if ( !m_pPreloadHeader ) { return NULL; } // If this entry doesn't have a corresponding preload entry, fail. if ( m_PackFiles[nEntryIndex].m_nPreloadIdx == INVALID_PRELOAD_ENTRY ) { return NULL; } return &m_pPreloadDirectory[m_PackFiles[nEntryIndex].m_nPreloadIdx]; } //----------------------------------------------------------------------------- // Read a file from the pack //----------------------------------------------------------------------------- int CZipPackFile::ReadFromPack( int nEntryIndex, void* pBuffer, int nDestBytes, int nBytes, int64 nOffset ) { if ( nEntryIndex >= 0 ) { if ( nBytes <= 0 ) { return 0; } // X360TBD: This is screwy, it works because m_nBaseOffset is 0 for preload capable zips // It comes into play for files out of the embedded bsp zip, // this hackery is a pre-bias expecting ReadFromPack() do a symmetric post bias, yuck. nOffset -= m_nBaseOffset; // Attempt to satisfy request from possible preload section, otherwise fall through // A preload entry may be compressed ZIP_PreloadDirectoryEntry *pPreloadEntry = GetPreloadEntry( nEntryIndex ); if ( pPreloadEntry ) { // convert the absolute pack file position to a local file position int nLocalOffset = nOffset - m_PackFiles[nEntryIndex].m_nPosition; byte *pPreloadData = (byte*)m_pPreloadData + pPreloadEntry->DataOffset; CLZMA lzma; if ( lzma.IsCompressed( pPreloadData ) ) { unsigned int actualSize = lzma.GetActualSize( pPreloadData ); if ( nLocalOffset + nBytes <= (int)actualSize ) { // satisfy from compressed preload if ( fs_monitor_read_from_pack.GetInt() == 1 ) { char szName[MAX_PATH]; IndexToFilename( nEntryIndex, szName, sizeof( szName ) ); Msg( "Read From Pack: [Preload] Requested:%d, Compressed:%d, %s\n", nBytes, pPreloadEntry->Length, szName ); } if ( nLocalOffset == 0 && nDestBytes >= (int)actualSize && nBytes == (int)actualSize ) { // uncompress directly into caller's buffer lzma.Uncompress( (unsigned char *)pPreloadData, (unsigned char *)pBuffer ); return nBytes; } // uncompress into temporary memory CUtlMemory< byte > tempMemory; tempMemory.EnsureCapacity( actualSize ); lzma.Uncompress( pPreloadData, tempMemory.Base() ); // copy only what caller expects V_memcpy( pBuffer, (byte*)tempMemory.Base() + nLocalOffset, nBytes ); return nBytes; } } else if ( nLocalOffset + nBytes <= (int)pPreloadEntry->Length ) { // satisfy from uncompressed preload if ( fs_monitor_read_from_pack.GetInt() == 1 ) { char szName[MAX_PATH]; IndexToFilename( nEntryIndex, szName, sizeof( szName ) ); Msg( "Read From Pack: [Preload] Requested:%d, Total:%d, %s\n", nBytes, pPreloadEntry->Length, szName ); } V_memcpy( pBuffer, pPreloadData + nLocalOffset, nBytes ); return nBytes; } } } // fell through as a direct request from within the pack // intercept to possible embedded section if ( m_pSection ) { // a section is a special update zip that has no files, only preload // it has to be in the section V_memcpy( pBuffer, (byte*)m_pSection + nOffset, nBytes ); return nBytes; } return CPackFile::ReadFromPack( nEntryIndex, pBuffer, nDestBytes, nBytes, nOffset ); } //----------------------------------------------------------------------------- // Gets size, position, and index for a file in the pack. //----------------------------------------------------------------------------- bool CZipPackFile::GetOffsetAndLength( const char *pFileName, int &nBaseIndex, int64 &nFileOffset, int &nLength ) { CZipPackFile::CPackFileEntry lookup; lookup.m_HashName = HashStringCaselessConventional( pFileName ); int idx = m_PackFiles.Find( lookup ); if ( -1 != idx ) { nFileOffset = m_PackFiles[idx].m_nPosition; nLength = m_PackFiles[idx].m_nLength; nBaseIndex = idx; return true; } return false; } bool CZipPackFile::IndexToFilename( int nIndex, char *pBuffer, int nBufferSize ) { if ( nIndex >= 0 ) { m_fs->String( m_PackFiles[nIndex].m_hDebugFilename, pBuffer, nBufferSize ); return true; } Q_strncpy( pBuffer, "unknown", nBufferSize ); return false; } //----------------------------------------------------------------------------- // Find a file in the pack. //----------------------------------------------------------------------------- bool CZipPackFile::FindFile( const char *pFilename, int &nIndex, int64 &nOffset, int &nLength ) { char szCleanName[MAX_FILEPATH]; Q_strncpy( szCleanName, pFilename, sizeof( szCleanName ) ); #ifdef _WIN32 Q_strlower( szCleanName ); #endif Q_FixSlashes( szCleanName ); if ( !Q_RemoveDotSlashes( szCleanName ) ) { return false; } bool bFound = GetOffsetAndLength( szCleanName, nIndex, nOffset, nLength ); nOffset += m_nBaseOffset; return bFound; } //----------------------------------------------------------------------------- // Set up the preload section //----------------------------------------------------------------------------- void CZipPackFile::SetupPreloadData() { if ( m_pPreloadHeader || !m_nPreloadSectionSize ) { // already loaded or not availavble return; } MEM_ALLOC_CREDIT_( "xZip" ); void *pPreload; if ( m_pSection ) { pPreload = (byte*)m_pSection + m_nPreloadSectionOffset; } else { pPreload = malloc( m_nPreloadSectionSize ); if ( !pPreload ) { return; } if ( IsGameConsole() ) { // 360 XZips are always dvd aligned Assert( ( m_nPreloadSectionSize % XBOX_DVD_SECTORSIZE ) == 0 ); Assert( ( m_nPreloadSectionOffset % XBOX_DVD_SECTORSIZE ) == 0 ); } // preload data is loaded as a single unbuffered i/o operation ReadFromPack( -1, pPreload, -1, m_nPreloadSectionSize, m_nPreloadSectionOffset ); } // setup the header m_pPreloadHeader = (ZIP_PreloadHeader *)pPreload; // setup the preload directory m_pPreloadDirectory = (ZIP_PreloadDirectoryEntry *)((byte *)m_pPreloadHeader + sizeof( ZIP_PreloadHeader ) ); // setup the remap table m_pPreloadRemapTable = (unsigned short *)((byte *)m_pPreloadDirectory + m_pPreloadHeader->PreloadDirectoryEntries * sizeof( ZIP_PreloadDirectoryEntry ) ); // set the preload data base m_pPreloadData = (byte *)m_pPreloadRemapTable + m_pPreloadHeader->DirectoryEntries * sizeof( unsigned short ); } void CZipPackFile::DiscardPreloadData() { if ( !m_pPreloadHeader ) { // already discarded return; } // a section is an alias, the header becomes an alias, not owned memory if ( !m_pSection ) { free( m_pPreloadHeader ); } m_pPreloadHeader = NULL; } //----------------------------------------------------------------------------- // Parse the zip file to build the file directory and preload section //----------------------------------------------------------------------------- bool CZipPackFile::Prepare( int64 fileLen, int64 nFileOfs ) { if ( !fileLen || fileLen < sizeof( ZIP_EndOfCentralDirRecord ) ) { // nonsense zip return false; } // Pack files are always little-endian m_swap.ActivateByteSwapping( IsX360() || IsPS3()); m_FileLength = fileLen; m_nBaseOffset = nFileOfs; ZIP_EndOfCentralDirRecord rec = { 0 }; // Find and read the central header directory from its expected position at end of the file bool bCentralDirRecord = false; int64 offset = fileLen - sizeof( ZIP_EndOfCentralDirRecord ); // 360 can have an incompatible format bool bCompatibleFormat = true; if ( IsGameConsole() ) { // 360 has dependable exact zips, backup to handle possible xzip format if ( offset - XZIP_COMMENT_LENGTH >= 0 ) { offset -= XZIP_COMMENT_LENGTH; } // single i/o operation, scanning forward unsigned int nBytes = (unsigned int)( fileLen - offset ); char *pTemp = (char *)_alloca( nBytes ); ReadFromPack( -1, pTemp, -1, nBytes, offset ); while ( offset <= fileLen - sizeof( ZIP_EndOfCentralDirRecord ) ) { memcpy( &rec, pTemp, sizeof( ZIP_EndOfCentralDirRecord ) ); m_swap.SwapFieldsToTargetEndian( &rec ); if ( rec.signature == PKID( 5, 6 ) ) { bCentralDirRecord = true; if ( rec.commentLength >= 4 ) { char *pComment = pTemp + sizeof( ZIP_EndOfCentralDirRecord ); if ( !V_strnicmp( pComment, "XZP", 3 ) && pComment[3] != '1' ) { bCompatibleFormat = false; } } break; } offset++; pTemp++; } } else { // scan entire file from expected location for central dir for ( ; offset >= 0; offset-- ) { ReadFromPack( -1, (void*)&rec, -1, sizeof( rec ), offset ); m_swap.SwapFieldsToTargetEndian( &rec ); if ( rec.signature == PKID( 5, 6 ) ) { bCentralDirRecord = true; break; } } } Assert( bCentralDirRecord ); if ( !bCentralDirRecord ) { // no zip directory, bad zip return false; } int numFilesInZip = rec.nCentralDirectoryEntries_Total; if ( numFilesInZip <= 0 ) { // empty valid zip return true; } int firstFileIdx = 0; MEM_ALLOC_CREDIT(); // read central directory into memory and parse CUtlBuffer zipDirBuff( 0, rec.centralDirectorySize, 0 ); zipDirBuff.EnsureCapacity( rec.centralDirectorySize ); zipDirBuff.ActivateByteSwapping( IsX360() || IsPS3() ); ReadFromPack( -1, zipDirBuff.Base(), -1, rec.centralDirectorySize, rec.startOfCentralDirOffset ); zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize ); ZIP_FileHeader zipFileHeader; char filename[MAX_PATH]; // Check for a preload section, expected to be the first file in the zip zipDirBuff.GetObjects( &zipFileHeader ); zipDirBuff.Get( filename, zipFileHeader.fileNameLength ); filename[zipFileHeader.fileNameLength] = '\0'; if ( !V_stricmp( filename, PRELOAD_SECTION_NAME ) ) { m_nPreloadSectionSize = zipFileHeader.uncompressedSize; m_nPreloadSectionOffset = zipFileHeader.relativeOffsetOfLocalHeader + sizeof( ZIP_LocalFileHeader ) + zipFileHeader.fileNameLength + zipFileHeader.extraFieldLength; SetupPreloadData(); // Set up to extract the remaining files int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0; zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset ); firstFileIdx = 1; } else { if ( IsGameConsole() ) { // all 360 zip files are expected to have preload sections // only during development, maps are allowed to lack them, due to auto-conversion Warning( "Xbox Zip File '%s' missing preload section\n", m_ZipName.String() ); } // No preload section, reset buffer pointer zipDirBuff.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); } // Parse out central directory and determine absolute file positions of data. // Supports uncompressed zip files, with or without preload sections bool bSuccess = true; char tmpString[MAX_PATH]; CZipPackFile::CPackFileEntry lookup; m_PackFiles.EnsureCapacity( numFilesInZip ); for ( int i = firstFileIdx; i < numFilesInZip; ++i ) { zipDirBuff.GetObjects( &zipFileHeader ); if ( zipFileHeader.signature != PKID( 1, 2 ) || zipFileHeader.compressionMethod != 0 ) { Msg( "Incompatible pack file detected! %s\n", ( zipFileHeader.compressionMethod != 0 ) ? " File is compressed" : "" ); bSuccess = false; break; } Assert( zipFileHeader.fileNameLength < sizeof( tmpString ) ); zipDirBuff.Get( (void *)tmpString, zipFileHeader.fileNameLength ); tmpString[zipFileHeader.fileNameLength] = '\0'; Q_FixSlashes( tmpString ); lookup.m_hDebugFilename = m_fs->FindOrAddFileName( tmpString ); lookup.m_HashName = HashStringCaselessConventional( tmpString ); lookup.m_nLength = zipFileHeader.uncompressedSize; lookup.m_nPosition = zipFileHeader.relativeOffsetOfLocalHeader + sizeof( ZIP_LocalFileHeader ) + zipFileHeader.fileNameLength + zipFileHeader.extraFieldLength; // track the index to this file's possible preload directory entry if ( m_pPreloadRemapTable ) { lookup.m_nPreloadIdx = m_pPreloadRemapTable[i]; } else { lookup.m_nPreloadIdx = INVALID_PRELOAD_ENTRY; } m_PackFiles.InsertNoSort( lookup ); int nextOffset = bCompatibleFormat ? zipFileHeader.extraFieldLength + zipFileHeader.fileCommentLength : 0; zipDirBuff.SeekGet( CUtlBuffer::SEEK_CURRENT, nextOffset ); } m_PackFiles.RedoSort(); if ( IsGameConsole() ) { // mount optional kv pool int nIndex; int64 nOffset; int nLength; if ( FindFile( "kvpool" PLATFORM_EXT ".image", nIndex, nOffset, nLength ) ) { CUtlBuffer kvPoolBuffer; int nOptimalSize = AlignValue( nLength, XBOX_DVD_SECTORSIZE ); void *pBuffer = malloc( nOptimalSize ); kvPoolBuffer.SetExternalBuffer( pBuffer, nOptimalSize, nLength, CUtlBuffer::READ_ONLY ); ReadFromPack( nIndex, pBuffer, nOptimalSize, nLength, nOffset ); m_KVPoolKey = kvPoolBuffer.GetUnsignedInt(); bSuccess = m_KVStringPool.RestoreFromBuffer( kvPoolBuffer ); if ( !bSuccess ) { Msg( "Failed to unserialize compiled kvpool\n" ); m_KVStringPool.RemoveAll(); m_KVPoolKey = 0; } free( pBuffer ); } } return bSuccess; } CRC32_t CZipPackFile::GetKVPoolKey() { return m_KVPoolKey; } bool CZipPackFile::GetStringFromKVPool( unsigned int key, char *pOutBuff, int buflen ) { return m_KVStringPool.String( (FileNameHandle_t)key, pOutBuff, buflen ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- CZipPackFile::CZipPackFile( CBaseFileSystem* fs, void *pSection ) : m_PackFiles(), m_KVStringPool() { m_fs = fs; m_pPreloadDirectory = NULL; m_pPreloadData = NULL; m_pPreloadHeader = NULL; m_pPreloadRemapTable = NULL; m_nPreloadSectionOffset = 0; m_nPreloadSectionSize = 0; m_KVPoolKey = 0; #if defined( _GAMECONSOLE ) m_pSection = pSection; #else m_pSection = NULL; #endif } CZipPackFile::~CZipPackFile() { DiscardPreloadData(); m_KVStringPool.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: // Input : src1 - // src2 - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CZipPackFile::CPackFileLessFunc::Less( CZipPackFile::CPackFileEntry const& src1, CZipPackFile::CPackFileEntry const& src2, void *pCtx ) { return ( src1.m_HashName < src2.m_HashName ); } //----------------------------------------------------------------------------- // Purpose: Search pPath for pak?.pak files and add to search path if found // Input : *pPath - //----------------------------------------------------------------------------- #if defined( _GAMECONSOLE ) #define PACK_NAME_FORMAT "zip%i" PLATFORM_EXT ".zip" #define PACK_LOCALIZED_NAME_FORMAT "zip%i_%s" PLATFORM_EXT ".zip" #else #define PACK_NAME_FORMAT "zip%i.zip" #endif void CBaseFileSystem::AddPackFiles( const char *pPath, const CUtlSymbol &pathID, SearchPathAdd_t addType, int iForceInsertIndex ) { Assert( ThreadInMainThread() ); DISK_INTENSIVE(); // Xbox Update and DLC zips are purposely not using the ZipN decoration so as not to interfere with the // install process that wants to move zip0 to the cache partition. These zips also have other mounting // requirements that prevent the simpler ZipN discovery logic. #if defined( _GAMECONSOLE ) // hack prepend the update path during first time add only // only specific paths get the update override explicitly prepended const char *pPathIDString = g_PathIDTable.String( pathID ); if ( addType == PATH_ADD_TO_TAIL && iForceInsertIndex == 0 && ( !V_stricmp( pPathIDString, "PLATFORM" ) || !V_stricmp( pPathIDString, "GAME" ) || !V_stricmp( pPathIDString, "MOD" ) ) ) { // update search path gets added once per allowed pathID bool bFoundSearchPath = false; for ( int i = 0; i < m_SearchPaths.Count(); i++ ) { CSearchPath *pSearchPath = &m_SearchPaths[i]; if ( pSearchPath->GetPathID() == pathID && !V_stricmp( pSearchPath->GetPathString(), "u:\\update\\" ) ) { bFoundSearchPath = true; break; } } if ( !bFoundSearchPath && g_UpdateZipBuffer.TellPut() ) { // found update blob in executable int nIndex = m_SearchPaths.AddToTail(); CSearchPath *sp = &m_SearchPaths[ nIndex ]; // the path and filename are fake but reserved, they denote this binary resident blob // these names ensure they get ignored during post hdd install sp fixup const char *pFullpath = "u:\\update\\update" PLATFORM_EXT ".zip"; sp->m_pPathIDInfo = FindOrAddPathIDInfo( pathID, -1 ); sp->m_storeId = g_iNextSearchPathID++; sp->SetPath( g_PathIDTable.AddString( "u:\\update\\" ) ); // find and alias existing reference CPackFile *pf = NULL; for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ ) { if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), pFullpath ) ) { // found pf = m_ZipFiles[iPackFile]; sp->SetPackFile( pf ); pf->AddRef(); break; } } if ( !pf ) { // there is no 'file', point to the embedded section instead pf = new CZipPackFile( this, g_UpdateZipBuffer.Base() ); pf->SetPath( sp->GetPath() ); pf->m_bIsExcluded = false; pf->m_ZipName = pFullpath; m_ZipFiles.AddToTail( pf ); sp->SetPackFile( pf ); pf->m_lPackFileTime = 0; pf->m_hPackFileHandleFS = NULL; pf->Prepare( g_UpdateZipBuffer.TellPut() ); } } } #endif CUtlVector< CUtlString > pakPaths; 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(); pakPaths.AddToTail( pPath ); pakNames.AddToTail( pakfile ); pakSizes.AddToTail( (int64)((unsigned int)buf.st_size) ); } #if defined( _GAMECONSOLE ) // safety measure, ensure 360 dlc zip search path gets added ONLY once per allowed pathID // dlc paths have unique suffixes, _dlc1..._dlcN if ( m_DLCContents.Count() && V_stristr( pPath, "_dlc" ) ) { // per pathID, dlc zip should only occur once, but might have already been added bool bFoundSearchPath = false; for ( int i = 0; i < m_SearchPaths.Count(); i++ ) { CSearchPath *pSearchPath = &m_SearchPaths[i]; if ( pSearchPath->GetPathID() == pathID && V_stristr( pSearchPath->GetPathString(), pPath ) ) { bFoundSearchPath = true; break; } } if ( !bFoundSearchPath ) { char szFullPath[MAX_PATH]; V_ComposeFileName( pPath, "dlc" PLATFORM_EXT ".zip", szFullPath, sizeof( szFullPath ) ); struct _stat buf; if ( FS_stat( szFullPath, &buf ) != -1 ) { pakPaths.AddToTail( pPath ); pakNames.AddToTail( "dlc" PLATFORM_EXT ".zip" ); 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; if ( IsGameConsole() && addType == PATH_ADD_TO_TAIL_ATINDEX ) { // we will be inserting before this nCount = iForceInsertIndex; } for ( int i = pakcount-1; i >= 0; i-- ) { char fullpath[MAX_PATH]; V_ComposeFileName( pakPaths[i].Get(), 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( pakPaths[i].Get() ) ); 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(); break; } } 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 ); } } #ifdef SUPPORT_VPK else { pf->m_hPackFileHandleVPK = FindFileInVPKs( fullpath ); if ( !pf->m_hPackFileHandleVPK || !pf->Prepare( pakSizes[i] ) ) { m_SearchPaths.Remove( nIndex ); } } #endif } } } //----------------------------------------------------------------------------- // 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 ); char newPath[ MAX_FILEPATH ]; // +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 Q_FixSlashes( newPath ); // Open the .bsp and find the map lump char fullpath[ MAX_FILEPATH ]; if ( Q_IsAbsolutePath( newPath ) ) // If it's an absolute path, just use that. { Q_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 ( Q_stricmp( m_SearchPaths[i].GetPackFile()->m_ZipName.Get(), fullpath ) == 0 ) { // Already set as map path return; } } // Remove previous RemoveAllMapSearchPaths(); #ifdef SUPPORT_VPK CPackedStoreFileHandle psHandle = FindFileInVPKs( pPath ); if ( psHandle ) { // Get the .bsp file header BSPHeader_t header; memset( &header, 0, sizeof( header ) ); m_Stats.nBytesRead += psHandle.Read( &header, sizeof( header ) ); m_Stats.nReads++; if ( header.ident != IDBSPHEADER || header.m_nVersion < MINBSPVERSION || header.m_nVersion > BSPVERSION ) { 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 return; } // Seek to correct position psHandle.Seek( packfile->fileofs, FILESYSTEM_SEEK_HEAD ); CPackFile *pf = new CZipPackFile( this ); pf->m_bIsMapPath = true; pf->m_hPackFileHandleFS = NULL; pf->m_hPackFileHandleVPK = psHandle; 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 = g_iNextSearchPathID++; sp->SetPath( g_PathIDTable.AddString( newPath ) ); sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 ); if ( IsDvdDevPathString( newPath ) ) { sp->m_bIsDvdDevPath = true; } pf->SetPath( sp->GetPath() ); pf->m_lPackFileTime = GetFileTime( newPath ); //pf->m_PackFileID = m_FileTracker2.NotePackFileOpened( pPath, pPathID, packfile->filelen ); m_ZipFiles.AddToTail( pf ); } else { delete pf; } } else #endif { FILE *fp = Trace_FOpen( fullpath, "rb", 0, NULL ); if ( !fp ) { // Couldn't open it FileSystemWarning( FILESYSTEM_WARNING, "Couldn't open .bsp %s for embedded pack file check\n", fullpath ); return; } // Get the .bsp file header BSPHeader_t header; memset( &header, 0, sizeof( header ) ); m_Stats.nBytesRead += FS_fread( &header, sizeof( header ), fp ); m_Stats.nReads++; if ( header.ident != IDBSPHEADER || header.m_nVersion < MINBSPVERSION || header.m_nVersion > 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; #ifdef SUPPORT_VPK pf->m_hPackFileHandleVPK = psHandle; #endif 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 = g_iNextSearchPathID++; sp->SetPath( g_PathIDTable.AddString( newPath ) ); sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 ); if ( IsDvdDevPathString( newPath ) ) { sp->m_bIsDvdDevPath = true; } pf->SetPath( sp->GetPath() ); pf->m_lPackFileTime = GetFileTime( newPath ); Trace_FClose( pf->m_hPackFileHandleFS ); pf->m_hPackFileHandleFS = NULL; //pf->m_PackFileID = m_FileTracker2.NotePackFileOpened( pPath, pPathID, packfile->filelen ); m_ZipFiles.AddToTail( pf ); } else { delete pf; } } } void CBaseFileSystem::BeginMapAccess() { #if IsPlatformPS3() // SONY Fios library does not play well with QueuedLoader, so suspend it. // Adding a delay in the prefetch actually creates a soft-lock. SuspendPrefetches( "IFileSystem::BeginMapAccess" ); #endif 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(); #ifdef SUPPORT_VPK 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 ); #ifdef SUPPORT_VPK 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(); } } } #if IsPlatformPS3() ResumePrefetches( "IFileSystem::EndMapAccess" ); #endif } void CBaseFileSystem::PrintSearchPaths( void ) { int i; Msg( "---------------\n" ); Msg( "%-20s %s\n", "Path ID:", "File Path:" ); int c = m_SearchPaths.Count(); for( 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; } Msg( "%-20s \"%s\" %s%s\n", (const char *)pSearchPath->GetPathIDString(), pSearchPath->GetPathString(), pszType, pszPack ); } if ( IsGameConsole() && m_ExcludeFilePaths.Count() ) { // dump current list Msg( "\nExclude Paths:\n" ); char szPath[MAX_PATH]; for ( int i = 0; i < m_ExcludeFilePaths.Count(); i++ ) { if ( String( m_ExcludeFilePaths[i], szPath, sizeof( szPath ) ) ) { Msg( "\"%s\"\n", szPath ); } } } PrintDLCInfo(); } //----------------------------------------------------------------------------- // 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, int iForceInsertIndex ) { AsyncFinishAll(); Assert( ThreadInMainThread() ); // Map pak files have their own handler if ( V_stristr( pPath, ".bsp" ) ) { AddMapPackFile( pPath, pathID, addType ); return; } // Clean up the name char newPath[ MAX_FILEPATH ]; if ( pPath[0] == 0 ) { newPath[0] = newPath[1] = 0; } else { if ( IsGameConsole() || 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 ( IsGameConsole() && 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 ( IsGameConsole() && addType == PATH_ADD_TO_TAIL_ATINDEX ) { nIndex = iForceInsertIndex; } if ( bAddPackFiles ) { // Add pack files for this path next int lastCount = m_SearchPaths.Count(); AddPackFiles( newPath, pathIDSym, addType, iForceInsertIndex ); bAdded = m_SearchPaths.Count() != lastCount; } if ( IsGameConsole() && addType == PATH_ADD_TO_TAIL_ATINDEX ) { // isolated this specific pathadd hack behavior to not destablize existing state // this behavior is for install and dlc searchpath modifications if ( V_stristr( pPath, "_dlc" ) ) { if ( !bAdded ) { // dlc should have had a pack file, if it didn't mount shouldn't be adding in anything else return; } if ( !V_stristr( pPath, XBX_GetLanguageString() ) ) { // only dlc that is non-localized (language dlc doesn't have maps) gets a fallthrough directory due to map paths // every dlc thus far has had maps m_SearchPaths.InsertAfter( nIndex ); nIndex++; } } } else { 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 ( IsGameConsole() && bAddPackFiles && bAdded ) { bool bAddNonZipPath = true; if ( bAddNonZipPath ) { // 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 // fixup the indexes if ( addType == PATH_ADD_TO_HEAD ) { m_SearchPaths.InsertBefore( nIndex ); } else { nIndex = m_SearchPaths.Count() - 1; m_SearchPaths.InsertAfter( nIndex ); nIndex++; } } } } // setup the 'base' path 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; // classify the dvddev path if ( IsDvdDevPathString( newPath ) ) { sp->m_bIsDvdDevPath = true; } } //----------------------------------------------------------------------------- // Create the search path. //----------------------------------------------------------------------------- void CBaseFileSystem::AddSearchPath( const char *pPath, const char *pathID, SearchPathAdd_t addType ) { char tempSymlinkBuffer[MAX_PATH]; pPath = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pPath ); #if !defined( _X360 ) // The PC has no concept of update/dlc discovery, it explicitly adds them now // This layout matches the Xbox's search path layout, when the Xbox does late bind the DLC // any platform, game, or mod search paths get subverted in order to prepend the DLC path const char *pGameName = "csgo"; // CSGO compatibility VPKs if ( const char *pActualPathID = pathID ? StringAfterPrefix( pathID, "COMPAT:" ) : NULL ) { #ifdef SUPPORT_VPK // Check if we are running with VPKs? if ( m_VPKFiles.Count() > 0 ) { char newVPK[ MAX_PATH ] = {}; sprintf( newVPK, "%s/pakxv_%s.vpk", pGameName, pPath ); AddVPKFile( newVPK, addType ); return; } #endif if ( addType != PATH_ADD_TO_HEAD ) return; // in non-vpk mode compatibility paths can only be added to head // Build compatibility syntax path and proceed with adding pathID = pActualPathID; V_sprintf_safe( tempSymlinkBuffer, "csgo/compatibility/%s/", pPath ); pPath = tempSymlinkBuffer; } if ( V_stristr( pPath, pGameName ) && ( !Q_stricmp( pathID, "GAME" ) || !Q_stricmp( pathID, "MOD" ) || !Q_stricmp( pathID, "PLATFORM" ) ) ) { char szPathHead[MAX_PATH]; char szUpdatePath[MAX_PATH]; V_strncpy( szPathHead, pPath, sizeof( szPathHead ) ); V_StripLastDir( szPathHead, sizeof( szPathHead ) ); // xlsppatch trumps all V_ComposeFileName( szPathHead, "xlsppatch", szUpdatePath, sizeof( szUpdatePath ) ); struct _stat buf; if ( FS_stat( szUpdatePath, &buf ) != -1 ) { // found AddSearchPathInternal( szUpdatePath, pathID, addType, true ); } // followed by update V_ComposeFileName( szPathHead, "update", szUpdatePath, sizeof( szUpdatePath ) ); if ( FS_stat( szUpdatePath, &buf ) != -1 ) { // found AddSearchPathInternal( szUpdatePath, pathID, addType, true ); } // followed by dlc if ( !Q_stricmp( pathID, "GAME" ) || !Q_stricmp( pathID, "MOD" ) ) { // DS would have all DLC dirs // find highest DLC dir available int nHighestDLC = 1; for ( ;nHighestDLC <= 99; nHighestDLC++ ) { V_ComposeFileName( szPathHead, CFmtStr( "%s_dlc%d", pGameName, nHighestDLC ), szUpdatePath, sizeof( szUpdatePath ) ); if ( FS_stat( szUpdatePath, &buf ) == -1 ) { // does not exist, highest dlc available is previous nHighestDLC--; break; } V_ComposeFileName( szPathHead, CFmtStr( "%s_dlc%d/dlc_disabled.txt", pGameName, nHighestDLC ), szUpdatePath, sizeof( szUpdatePath ) ); if ( FS_stat( szUpdatePath, &buf ) != -1 ) { // disabled, highest dlc available is previous nHighestDLC--; break; } } // mount in correct order for ( ;nHighestDLC >= 1; nHighestDLC-- ) { V_ComposeFileName( szPathHead, CFmtStr( "%s_dlc%d", pGameName, nHighestDLC ), szUpdatePath, sizeof( szUpdatePath ) ); AddSearchPathInternal( szUpdatePath, pathID, addType, true ); #ifdef SUPPORT_VPK // scan for vpk's for( int i = 1 ; i < 99; i++ ) { char newVPK[MAX_PATH]; sprintf( newVPK, "%s/pak%02d_dir.vpk", szUpdatePath, i ); // we will fopen to bypass pathing, etc FILE *pstdiofile = fopen( newVPK, "rb" ); if ( pstdiofile ) { fclose( pstdiofile ); sprintf( newVPK, "%s/pak%02d.vpk", szUpdatePath, i ); AddVPKFile( newVPK ); } else { break; } } #endif } } } #endif int currCount = m_SearchPaths.Count(); AddSearchPathInternal( pPath, pathID, addType, true ); #ifdef SUPPORT_VPK // scan for vpk's for( int i = 1 ; i < 99; i++ ) { char newVPK[MAX_PATH]; sprintf( newVPK, "%s/pak%02d_dir.vpk", pPath, i ); // we will fopen to bypass pathing, etc FILE *pstdiofile = fopen( newVPK, "rb" ); if ( pstdiofile ) { fclose( pstdiofile ); sprintf( newVPK, "%s/pak%02d.vpk", pPath, i ); AddVPKFile( newVPK ); } else { break; } } #endif if ( IsGameConsole() && ( m_DVDMode == DVDMODE_DEV ) && ( !V_stricmp( pathID, "MOD" ) || !V_stricmp( pathID, "GAME" ) || !V_stricmp( pathID, "PLATFORM" ) ) ) { // dvd development mode clones a search path based on the remote path for fall through const char *pBasePath = CommandLine()->ParmValue( "-basedir" ); if ( pBasePath && !V_stristr( pPath, ".bsp" ) ) { // isolate the search sub path from the base path const char *pSearchSubPath = StringAfterPrefix( pPath, pBasePath ); if ( pSearchSubPath ) { if ( pSearchSubPath[0] == CORRECT_PATH_SEPARATOR ) { pSearchSubPath++; } // substitue the dvddev path head char szDvdDevPath[MAX_PATH]; V_ComposeFileName( FS_DVDDEV_ROOT, pSearchSubPath, szDvdDevPath, sizeof( szDvdDevPath ) ); // no pack files are allowed on the fall through dvddev path AddSearchPathInternal( szDvdDevPath, pathID, addType, false ); } } } if ( currCount != m_SearchPaths.Count() ) { #if !defined( DEDICATED ) && !defined( _CERT ) if ( IsGameConsole() ) { // spew updated search paths // PrintSearchPaths(); } #endif } } //----------------------------------------------------------------------------- // Patches the search path after install has finished. This is a hack until // a reboot occurs. This is really bad, but our system has no fundamental // way of manipulating search paths, so had to bury this here. This is designed // to be innvoked ONCE after the installer has completed to swap in the // cache based paths. The reboot causes a normal search path build out using // the proper paths. //----------------------------------------------------------------------------- bool CBaseFileSystem::FixupSearchPathsAfterInstall() { #if defined( _X360 ) if ( m_bSearchPathsPatchedAfterInstall ) { // do not want to ever call this twice return true; } AsyncFinishAll(); // this is incredibly hardcoded and fragile // after shipping need to revisit and generalize for installs // this assumes exact knowledge of how zips are mounted and the install footprint for ( int i = 0; i < m_SearchPaths.Count(); i++ ) { const char *pPathID = m_SearchPaths[i].GetPathIDString(); if ( V_stricmp( pPathID, "GAME" ) && V_stricmp( pPathID, "MOD" ) ) { // only consider these paths continue; } const char *pPath = m_SearchPaths[i].GetPathString(); const char *pColon = strchr( pPath, ':' ); if ( !pColon || !V_stristr( pPath, "csgo" ) || V_stristr( pPath, "_lv" ) || V_stristr( pPath, "_dlc" ) || V_stristr( pPath, "_tempcontent" ) ) { // ignore relative paths, can't patch those // ignore any non csgo path // ignore lv, dlc, tempcontent path, not installing those zips continue; } if ( !m_SearchPaths[i].GetPackFile() || m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) { // ignore non pack based paths // ignore bsps continue; } if ( !V_stristr( m_SearchPaths[i].GetPackFile()->m_ZipName.String(), "zip0" PLATFORM_EXT ".zip" ) ) { // only patching zip0 continue; } // Not installing localized data if ( m_SearchPaths[i].m_bIsLocalizedPath ) { continue; } char szNewPath[MAX_PATH]; V_snprintf( szNewPath, sizeof( szNewPath ), "%s%s", CACHE_PATH_CSTIKRE15, pColon+1 ); V_FixSlashes( szNewPath ); int lastCount = m_SearchPaths.Count(); AddSearchPathInternal( szNewPath, pPathID, PATH_ADD_TO_TAIL_ATINDEX, true, i ); int numNewPaths = m_SearchPaths.Count() - lastCount; if ( numNewPaths ) { // skip paths all the paths we just added i += numNewPaths; // this is really bad, skip past the zip we just considered, the next iteration will skip us to the next zip i++; } m_bSearchPathsPatchedAfterInstall = true; } if ( m_bSearchPathsPatchedAfterInstall ) { // cache paths got added // shutdown non cache paths // must do multiple passes until no removal occurs bool bRemoved; while ( 1 ) { bRemoved = false; for ( int i = 0; i < m_SearchPaths.Count(); i++ ) { const char *pPathID = m_SearchPaths[i].GetPathIDString(); if ( V_stricmp( pPathID, "GAME" ) && V_stricmp( pPathID, "MOD" ) ) { // only consider these paths continue; } const char *pPath = m_SearchPaths[i].GetPathString(); const char *pColon = strchr( pPath, ':' ); if ( !pColon || !V_stristr( pPath, "csgo" ) || V_stristr( pPath, "_lv" ) || V_stristr( pPath, "_dlc" ) || V_stristr( pPath, "_tempcontent" ) ) { // ignore relative paths, can't patch those // ignore any non csgo path // ignore lv, dlc, or tempcontent path, not installing those zips continue; } if ( !m_SearchPaths[i].GetPackFile() || m_SearchPaths[i].GetPackFile()->m_bIsMapPath ) { // ignore non pack based paths // ignore bsps continue; } // Not installing localized data if ( m_SearchPaths[i].m_bIsLocalizedPath ) { continue; } if ( V_stristr( pPath, "cache:" ) || !V_stristr( m_SearchPaths[i].GetPackFile()->m_ZipName.String(), "zip0" PLATFORM_EXT ".zip" ) ) { // ignore any cache oriented paths // only want to remove non-cache paths of zip0.360.zip continue; } m_SearchPaths.Remove( i ); bRemoved = true; break; } if ( !bRemoved ) { break; } } } #endif return m_bSearchPathsPatchedAfterInstall; } //----------------------------------------------------------------------------- // ConCommands can just blindly call this to ensure user's request to flush // data picks up any intended changes they may have done locally. //----------------------------------------------------------------------------- void CBaseFileSystem::SyncDvdDevCache() { if ( !IsGameConsole() || m_DVDMode != DVDMODE_DEV ) { // xbox dvddev only return; } #if defined( _X360 ) XBX_rSyncDvdDevCache(); #endif BuildExcludeList(); } //----------------------------------------------------------------------------- // 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, char *pPath, int nMaxLen ) { AUTO_LOCK( m_SearchPathsMutex ); int nLen = 0; if ( nMaxLen ) { pPath[0] = 0; } CSearchPathsIterator iter( this, pathID, bGetPackFiles ? FILTER_NONE : FILTER_CULLPACK ); for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { CPackFile *pPackFile = pSearchPath->GetPackFile(); if ( nLen >= nMaxLen ) { // Add 1 for the semicolon if our length is not 0 nLen += (nLen > 0) ? 1 : 0; if ( !pPackFile ) { nLen += Q_strlen( pSearchPath->GetPathString() ); } else { // full path and slash nLen += Q_strlen( pPackFile->m_ZipName.String() ) + 1; } continue; } if ( nLen != 0 ) { pPath[nLen++] = ';'; } if ( !pPackFile ) { Q_strncpy( &pPath[nLen], pSearchPath->GetPathString(), nMaxLen - nLen ); nLen += Q_strlen( pSearchPath->GetPathString() ); } else { // full path and slash Q_strncpy( &pPath[nLen], pPackFile->m_ZipName.String(), nMaxLen - nLen ); V_AppendSlash( &pPath[nLen], nMaxLen - nLen ); nLen += Q_strlen( pPackFile->m_ZipName.String() ) + 1; } } // Return 1 extra for the NULL terminator return nLen + 1; } //----------------------------------------------------------------------------- // Returns the search path IDs, each path is separated by ;s. Returns the length of the string returned //----------------------------------------------------------------------------- int CBaseFileSystem::GetSearchPathID( char *pPath, int nMaxLen ) { AUTO_LOCK( m_SearchPathsMutex ); if ( nMaxLen ) { pPath[0] = 0; } // determine unique PathIDs CUtlVector< CUtlSymbol > list; for ( int i = 0 ; i < m_SearchPaths.Count(); i++ ) { CUtlSymbol pathID = m_SearchPaths[i].GetPathID(); if ( pathID != UTL_INVAL_SYMBOL && list.Find( pathID ) == -1 ) { list.AddToTail( pathID ); V_strncat( pPath, m_SearchPaths[i].GetPathIDString(), nMaxLen ); V_strncat( pPath, ";", nMaxLen ); } } return strlen( pPath ) + 1; } //----------------------------------------------------------------------------- // Purpose: // Input : *pPath - // Output : Returns true on success, false on failure. //----------------------------------------------------------------------------- bool CBaseFileSystem::RemoveSearchPath( const char *pPath, const char *pathID ) { char tempSymlinkBuffer[MAX_PATH]; pPath = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pPath ); AsyncFinishAll(); char newPath[ MAX_FILEPATH ]; newPath[ 0 ] = 0; if ( const char *pActualPathID = pathID ? StringAfterPrefix( pathID, "COMPAT:" ) : NULL ) { #ifdef SUPPORT_VPK if ( m_VPKFiles.Count() > 0 ) { char newVPK[ MAX_PATH ] = {}; sprintf( newVPK, "pakxv_%s", pPath ); while ( m_VPKFiles.Count() > 0 ) { CPackedStore *pVPK = m_VPKFiles.Head(); char szExistingVPKBase[ MAX_PATH ] = {}; V_FileBase( pVPK->BaseName(), szExistingVPKBase, sizeof( szExistingVPKBase ) ); char const *szAfterRemovedPathVPK = StringAfterPrefix( szExistingVPKBase, newVPK ); if ( szAfterRemovedPathVPK && ( *szAfterRemovedPathVPK == 0 || *szAfterRemovedPathVPK == '_' ) ) { m_VPKFiles.RemoveMultipleFromHead( 1 ); delete pVPK; } else break; } return true; } #endif int c = m_SearchPaths.Count(); for ( int i = c - 1; i >= 0; i-- ) { char newCompatPath[ MAX_PATH ] = {}; sprintf( newCompatPath, "/csgo/compatibility/%s/", pPath ); Q_FixSlashes( newCompatPath ); if ( V_strstr( m_SearchPaths[ i ].GetPathString(), newCompatPath ) ) { m_SearchPaths.Remove( i ); return true; } } } 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 { AddSeperatorAndFixPath( newPath ); } } pPath = newPath; CUtlSymbol lookup = g_PathIDTable.AddString( pPath ); 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 && 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() ) { continue; } if ( pathID && ( pSearchPath->GetPathID() != lookup ) ) { // not the right pathID continue; } if ( IsGameConsole() && ( m_DVDMode == DVDMODE_DEV ) && pFilename && !pSearchPath->m_bIsDvdDevPath ) { bool bIgnorePath = false; char szExcludeFile[MAX_PATH]; char szFilename[MAX_PATH]; V_ComposeFileName( pSearchPath->GetPathString(), pFilename, szFilename, sizeof( szFilename ) ); for ( int j = 0; j < m_ExcludeFilePaths.Count(); j++ ) { if ( g_pFullFileSystem->String( m_ExcludeFilePaths[j], szExcludeFile, sizeof( szExcludeFile ) ) ) { if ( !V_stricmp( szFilename, szExcludeFile ) ) { bIgnorePath = true; break; } } } if ( bIgnorePath ) { // filename matches exclusion path, skip it // favoring the next path which should be the path fall through hit continue; } } 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' ) { pSearchPath = FindWritePath( pFilename, pathID ); if ( pSearchPath ) { return pSearchPath->GetPathString(); } FileSystemWarning( 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 //----------------------------------------------------------------------------- #ifdef _PS3 #define g_pszReadFilename GetTLSGlobals()->pFileSystemReadFilename #else CTHREADLOCALPTR(char) g_pszReadFilename; #endif 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 && !IsPosix() && !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, 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(), nBytesDestBuffer - nBufferAlign, 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 = (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 ( IsGameConsole() ) { // 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 ) { *ppBuf = new byte[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.TellMaxPut(), fp ); Close( fp ); return (nBytesWritten != 0); } bool CBaseFileSystem::UnzipFile( const char *pFileName, const char *pPath, const char *pDestination ) { #ifdef POSIX Error( " need to hook up zip for linux" ); #else 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\\%s", pDestination, 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\\%s", pDestination, szFileName ); WriteFile( szFinalName, pPath, fileBuffer ); } } } ::CloseHandle( hZipFile ); IZip::ReleaseZip( pZip ); #endif return true; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseFileSystem::RemoveAllSearchPaths( void ) { AUTO_LOCK( m_SearchPathsMutex ); // Sergiy: AaronS said it is a good idea to destroy these paths in reverse order while( m_SearchPaths.Count() ) { m_SearchPaths.Remove( m_SearchPaths.Count() - 1 ); } //m_PackFileHandles.Purge(); } void CBaseFileSystem::LogFileAccess( const char *pFullFileName ) { if( !m_pLogFile ) { return; } if ( IsPS3() ) { AssertMsg( false, "LogFileAccess broken on PS3\n" ); 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 CFileOpenInfo { public: CFileOpenInfo( CBaseFileSystem *pFileSystem, const char *pFileName, const CBaseFileSystem::CSearchPath *path, const char *pOptions, int flags, char **ppszResolvedFilename, bool bTrackCRCs ) : m_pFileSystem( pFileSystem ), m_pFileName( pFileName ), m_pPath( path ), m_pOptions( pOptions ), m_Flags( flags ), m_ppszResolvedFilename( ppszResolvedFilename ), m_bTrackCRCs( bTrackCRCs ) { // Multiple threads can access the whitelist simultaneously. // That's fine, but make sure it doesn't get freed by another thread. if ( IsPC() ) { m_pWhitelist = m_pFileSystem->m_FileWhitelist.AddRef(); } else { m_pWhitelist = NULL; } m_pFileHandle = NULL; m_bLoadedFromSteamCache = m_bSteamCacheOnly = false; if ( m_ppszResolvedFilename ) *m_ppszResolvedFilename = NULL; #ifdef SUPPORT_VPK m_pPackFile = NULL; m_pVPKFile = NULL; #endif } ~CFileOpenInfo() { if ( IsGameConsole() ) { return; } m_pFileSystem->m_FileWhitelist.ReleaseRef( m_pWhitelist ); } 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, bool bIsAbsolutePath ) { if ( IsGameConsole() ) { return; } if ( m_pFileSystem->m_WhitelistFileTrackingEnabled == 0 || !m_bTrackCRCs ) return; if ( m_pFileHandle ) { FILE *fp = m_pFileHandle->m_pFile; int64 nLength = m_pFileHandle->m_nLength; #ifdef SUPPORT_VPK if ( m_pVPKFile ) { m_pFileSystem->m_FileTracker2.NotePackFileAccess( pRelativeFileName, m_pPath->GetPathIDString(), 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_pPath->GetPathIDString(), fp, nLength ); } else if ( m_bSteamCacheOnly ) { // Remember that the file failed to load. We only need to do this in the case where we forced it to ignore files // on disk (in which case we'll want it to check the disk next time if sv_pure changed). m_pFileSystem->m_FileTracker.NoteFileFailedToLoad( pRelativeFileName, m_pPath->GetPathIDString() ); } } // Decides if the file must come from Steam or if it can be allowed to come off disk. void DetermineFileLoadInfoParameters( CFileLoadInfo &fileLoadInfo, bool bIsAbsolutePath ) { if ( IsGameConsole() ) { fileLoadInfo.m_bSteamCacheOnly = false; #ifdef _PS3 fileLoadInfo.m_ps3Filetype = PS3_FILETYPE_UNKNOWN; #endif return; } if ( m_bTrackCRCs && m_pWhitelist && m_pWhitelist->m_pAllowFromDiskList && !bIsAbsolutePath ) { Assert( !V_IsAbsolutePath( m_pFileName ) ); // (This is what bIsAbsolutePath is supposed to tell us..) // Ask the whitelist if this file must come from Steam. fileLoadInfo.m_bSteamCacheOnly = !m_pWhitelist->m_pAllowFromDiskList->IsFileInList( m_pFileName ); } else { fileLoadInfo.m_bSteamCacheOnly = false; } } public: CBaseFileSystem *m_pFileSystem; CWhitelistSpecs *m_pWhitelist; // These are output parameters. CFileHandle *m_pFileHandle; char **m_ppszResolvedFilename; #ifdef SUPPORT_VPK CPackFile *m_pPackFile; CPackedStore *m_pVPKFile; #endif const char *m_pFileName; const CBaseFileSystem::CSearchPath *m_pPath; const char *m_pOptions; int m_Flags; bool m_bTrackCRCs; // Stats about how the file was opened and how we asked the stdio/steam filesystem to open it. // Used to decide whether or not we need to generate and store CRCs. bool m_bLoadedFromSteamCache; // Did it get loaded out of the Steam cache? bool m_bSteamCacheOnly; // Are we asking that this file only come from Steam? char m_AbsolutePath[MAX_FILEPATH]; // This is set }; bool CBaseFileSystem::HandleOpenFromZipFile( CFileOpenInfo &openInfo ) { // 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" ); if ( !pZipExt ) { pZipExt = V_stristr( openInfo.m_AbsolutePath, ".bsp" ); } if ( pZipExt && pZipExt[4] == CORRECT_PATH_SEPARATOR && pZipExt[5] ) { // want full path to zip only, terminate at slash pZipExt[4] = '\0'; // want relative portion only, everything after the slash char *pRelativeFileName = pZipExt + 5; // find the zip for ( int i=0; i< m_ZipFiles.Count(); i++ ) { CPackFile *pPackFile = m_ZipFiles[i]; if ( Q_stricmp( pPackFile->m_ZipName.Get(), openInfo.m_AbsolutePath ) == 0 ) { openInfo.m_pFileHandle = pPackFile->OpenFile( pRelativeFileName, openInfo.m_pOptions ); #ifdef SUPPORT_VPK openInfo.m_pPackFile = pPackFile; #endif openInfo.HandleFileCRCTracking( pRelativeFileName, false ); break; } } if ( openInfo.m_pFileHandle ) openInfo.SetResolvedFilename( openInfo.m_pFileName ); return true; } else { return false; } } void CBaseFileSystem::HandleOpenFromPackFile( CPackFile *pPackFile, CFileOpenInfo &openInfo ) { openInfo.m_pFileHandle = pPackFile->OpenFile( openInfo.m_pFileName, openInfo.m_pOptions ); #ifdef SUPPORT_VPK openInfo.m_pPackFile = pPackFile; #endif // HACK! The bsp pack's paths may be different now that we've moved it to the workshop subdir... // If we're still failing to find it, remove the workshop/id subdir if ( !openInfo.m_pFileHandle ) { char szScratch[MAX_PATH]; V_strcpy_safe( szScratch, openInfo.m_pFileName ); CFmtStr workshopDir( "%cworkshop%c", CORRECT_PATH_SEPARATOR, CORRECT_PATH_SEPARATOR ); char *pStart = V_stristr( szScratch, workshopDir.Access() ); if ( pStart && pStart[0] && pStart[1] ) { *pStart = 0; // null terminate the first section of the path we're keeping pStart++; size_t len = strlen( pStart ); const char *pEnd = V_strnchr( pStart, CORRECT_PATH_SEPARATOR, len ); // skip past workshop path if ( pEnd && pEnd[0] && pEnd[1] ) { len = strlen( pEnd ); pEnd = V_strnchr( pEnd+1, CORRECT_PATH_SEPARATOR, len ); // skip past id path if ( pEnd ) { CFmtStr pathNoWorkshop( "%s%s", szScratch, pEnd ); openInfo.m_pFileHandle = pPackFile->OpenFile( pathNoWorkshop.Access(), openInfo.m_pOptions ); } } } } 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. if ( !pPackFile->m_bIsMapPath ) openInfo.HandleFileCRCTracking( openInfo.m_pFileName, false ); } void CBaseFileSystem::HandleOpenRegularFile( CFileOpenInfo &openInfo, bool bIsAbsolutePath ) { // Setup the parameters for the call (like to tell Steam to force the file to come out of the Steam caches or not). CFileLoadInfo fileLoadInfo; openInfo.DetermineFileLoadInfoParameters( fileLoadInfo, bIsAbsolutePath ); // xbox dvddev mode needs to convolve non-compliant fatx filenames // purposely placing this at this level, so only loose files pay the burden const char *pFilename = openInfo.m_AbsolutePath; bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( pFilename, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } int64 size; FILE *fp = Trace_FOpen( bFixed ? fixedFATXFilename : pFilename, openInfo.m_pOptions, openInfo.m_Flags, &size, &fileLoadInfo ); 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 ); // Remember what was returned by the Steam filesystem and track the CRC. openInfo.m_bLoadedFromSteamCache = fileLoadInfo.m_bLoadedFromSteamCache; openInfo.m_bSteamCacheOnly = fileLoadInfo.m_bSteamCacheOnly; openInfo.HandleFileCRCTracking( openInfo.m_pFileName, bIsAbsolutePath ); } } //----------------------------------------------------------------------------- // Purpose: The base file search goes through here // Input : *path - // *pFileName - // *pOptions - // packfile - // *filetime - // Output : FileHandle_t //----------------------------------------------------------------------------- FileHandle_t CBaseFileSystem::FindFile( const CSearchPath *path, const char *pFileName, const char *pOptions, unsigned flags, char **ppszResolvedFilename, bool bTrackCRCs ) { VPROF( "CBaseFileSystem::FindFile" ); char tempSymlinkBuffer[MAX_PATH]; pFileName = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pFileName ); CFileOpenInfo openInfo( this, pFileName, path, pOptions, flags, ppszResolvedFilename, bTrackCRCs ); bool bIsAbsolutePath = V_IsAbsolutePath( pFileName ); if ( bIsAbsolutePath ) { #ifdef SUPPORT_VPK if ( m_VPKFiles.Count() && ( ! V_stristr( pFileName, ".vpk" ) ) ) { // FileSystemWarning( FILESYSTEM_WARNING, "***VPK: FindFile Attempting to use full path with VPK file!\n\tFile: %s\n", pFileName ); } #endif openInfo.SetAbsolutePath( "%s", pFileName ); // Check if it's of the form C:/a/b/c/blah.zip/materials/blah.vtf if ( HandleOpenFromZipFile( openInfo ) ) { return (FileHandle_t)openInfo.m_pFileHandle; } } else { // check vpk file #ifdef SUPPORT_VPK for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { CPackedStoreFileHandle fHandle = m_VPKFiles[i]->OpenFile( pFileName ); if ( fHandle ) { openInfo.m_pFileHandle = new CFileHandle(this); openInfo.m_pFileHandle->m_VPKHandle = fHandle; openInfo.m_pFileHandle->m_type = FT_NORMAL; openInfo.m_pFileHandle->m_nLength = fHandle.m_nFileSize; openInfo.SetResolvedFilename( openInfo.m_AbsolutePath ); // Remember what was returned by the Steam filesystem and track the CRC. openInfo.m_bLoadedFromSteamCache = false; openInfo.m_bSteamCacheOnly = false; openInfo.m_pVPKFile = m_VPKFiles[i]; openInfo.HandleFileCRCTracking( openInfo.m_pFileName, false ); return ( FileHandle_t ) openInfo.m_pFileHandle; } } #endif // Caller provided a relative path if ( path->GetPackFile() ) { HandleOpenFromPackFile( path->GetPackFile(), openInfo ); return (FileHandle_t)openInfo.m_pFileHandle; } else { openInfo.SetAbsolutePath( "%s%s", path->GetPathString(), pFileName ); } } // now have an absolute name HandleOpenRegularFile( openInfo, bIsAbsolutePath ); return (FileHandle_t)openInfo.m_pFileHandle; } FileHandle_t CBaseFileSystem::FindFileInSearchPaths( const char *pFileName, const char *pOptions, const char *pathID, unsigned flags, char **ppszResolvedFilename, bool bTrackCRCs ) { // Run through all the search paths. PathTypeFilter_t pathFilter = FILTER_NONE; #if defined( _GAMECONSOLE ) && defined( _DEBUG ) // -pakfallbackfs will perform a filesystem search if the // requested file is not in pak zip (very expensive!) static enum PakFallback_t { PAK_FALLBACK_UNKNOWN, PAK_FALLBACK_ALLOW, PAK_FALLBACK_RETAIL } s_PakFallbackType = PAK_FALLBACK_UNKNOWN; if ( s_PakFallbackType == PAK_FALLBACK_UNKNOWN ) { s_PakFallbackType = CommandLine()->FindParm( "-pakfallbackfs" ) ? PAK_FALLBACK_ALLOW : PAK_FALLBACK_RETAIL; } #define IsPakStrictMode() ( s_PakFallbackType != PAK_FALLBACK_ALLOW ) #else #define IsPakStrictMode() true #endif if ( IsGameConsole() && IsPakStrictMode() ) { if ( flags & FSOPEN_NEVERINPACK ) { pathFilter = FILTER_CULLPACK; } else { // 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 ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { FileHandle_t filehandle = FindFile( pSearchPath, pFileName, pOptions, flags, ppszResolvedFilename, bTrackCRCs ); if ( filehandle ) return filehandle; } return ( FileHandle_t )0; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- FileHandle_t CBaseFileSystem::OpenForRead( const char *pFileName, const char *pOptions, unsigned flags, const char *pathID, char **ppszResolvedFilename ) { VPROF( "CBaseFileSystem::OpenForRead" ); return FindFileInSearchPaths( pFileName, pOptions, pathID, flags, ppszResolvedFilename, true ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- FileHandle_t CBaseFileSystem::OpenForWrite( const char *pFileName, const char *pOptions, const char *pathID ) { char tempPathID[MAX_PATH]; ParsePathID( pFileName, pathID, tempPathID ); // 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; // 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; // They're specifying two path IDs. Ignore the one passed-in. // AND only warn if they are inconsistent if ( pPathID && Q_stricmp( pPathID, tempPathID ) ) { FileSystemWarning( FILESYSTEM_WARNING, "FS: Specified two path IDs (%s, %s).\n", pFilename, pPathID ); } 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 ) { VPROF_BUDGET( "CBaseFileSystem::Open", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); if ( !pFileName ) return (FileHandle_t)0; #ifndef _CERT s_IoStats.OnFileOpen( pFileName ); #endif NoteIO(); CFileHandleTimer *pTimer = NULL; bool bReportLongLoads = ( fs_report_long_reads.GetInt() > 0 ); if ( bReportLongLoads ) { // When a file is opened we add it to the list and note the time pTimer = new CFileHandleTimer; if ( pTimer != NULL ) { // Need the lock only when adding to the vector, not during construction AUTO_LOCK( m_FileHandleTimersMutex ); m_FileHandleTimers.AddToTail( pTimer ); pTimer->Start(); } } CHECK_DOUBLE_SLASHES( pFileName ); if ( fs_report_sync_opens.GetInt() > 0 && ThreadInMainThread() && !bReportLongLoads ) // If we're reporting timings we have to delay this spew till after the file has been closed { Warning( "File::Open( %s ) on main thread.\n", pFileName ); #if !defined(_CERT) && !IsPS3() && !IsX360() if ( fs_report_sync_opens_callstack.GetInt() > 0 ) { // GetCallstack() does not work on PS3, it is using TLS which is not supported in cross-platform manner const int CALLSTACK_SIZE = 16; void * pAddresses[CALLSTACK_SIZE]; const int CALLSTACK_SKIP = 1; int nCount = GetCallStack( pAddresses, CALLSTACK_SIZE, CALLSTACK_SKIP ); if ( nCount != 0) { // Allocate dynamically instead of using the stack, this path is going to be very rarely used const int BUFFER_SIZE = 4096; char * pBuffer = new char[ BUFFER_SIZE ]; TranslateStackInfo( pAddresses, CALLSTACK_SIZE, pBuffer, BUFFER_SIZE, "\n", TSISTYLEFLAG_DEFAULT ); Msg( "%s\n", pBuffer ); delete[] pBuffer; } } #endif } // Allow for UNC-type syntax to specify the path ID. char tempPathID[MAX_PATH]; ParsePathID( pFileName, pathID, tempPathID ); char tempFileName[MAX_PATH]; Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) ); Q_FixSlashes( tempFileName ); #ifdef _WIN32 Q_strlower( tempFileName ); #endif FileHandle_t hFile; // Try each of the search paths in succession // FIXME: call createdirhierarchy upon opening for write. if ( strstr( pOptions, "r" ) && !strstr( pOptions, "+" ) ) { hFile = OpenForRead( tempFileName, pOptions, flags, pathID, ppszResolvedFilename ); } else { hFile = OpenForWrite( tempFileName, pOptions, pathID ); } if ( bReportLongLoads ) { // Save the file handle for ID when we close it if ( hFile && pTimer ) { pTimer->m_hFile = hFile; Q_strncpy( pTimer->m_szName, pFileName, sizeof( pTimer->m_szName ) ); // See if we've opened this file before so we can accumulate time spent rereading files FileOpenDuplicateTime_t *pFileOpenDuplicate = NULL; AUTO_LOCK( g_FileOpenDuplicateTimesMutex ); for ( int nFileOpenDuplicate = g_FileOpenDuplicateTimes.Count() - 1; nFileOpenDuplicate >= 0; --nFileOpenDuplicate ) { FileOpenDuplicateTime_t *pTempFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ]; if ( Q_stricmp( pFileName, pTempFileOpenDuplicate->m_szName ) == 0 ) { // Found it! pFileOpenDuplicate = pTempFileOpenDuplicate; break; } } if ( pFileOpenDuplicate == NULL ) { // We haven't opened this file before, so add it to the list pFileOpenDuplicate = new FileOpenDuplicateTime_t; if ( pFileOpenDuplicate != NULL ) { g_FileOpenDuplicateTimes.AddToTail( pFileOpenDuplicate ); Q_strncpy( pFileOpenDuplicate->m_szName, pFileName, sizeof( pTimer->m_szName ) ); } } // Increment the number of times we've opened this file if ( pFileOpenDuplicate != NULL ) { pFileOpenDuplicate->m_nLoadCount++; } } else { // File didn't open, pop it off the list if ( pTimer != NULL ) { // We need the lock only when removing from the vector, deleting the timer does not need it AUTO_LOCK( m_FileHandleTimersMutex ); for ( int nTimer = m_FileHandleTimers.Count() - 1; nTimer >= 0; --nTimer ) { CFileHandleTimer *pLocalTimer = m_FileHandleTimers[ nTimer ]; if ( pLocalTimer == pTimer ) { m_FileHandleTimers.Remove( nTimer ); break; } } delete pTimer; } } } return hFile; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseFileSystem::Close( FileHandle_t file ) { VPROF_BUDGET( "CBaseFileSystem::Close", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); if ( !file ) { FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Close NULL file handle!\n" ); return; } unsigned long ulLongLoadThreshold = fs_report_long_reads.GetInt(); if ( ulLongLoadThreshold > 0 ) { // Let's find the nTimer that matches the file (we assume that we only have to close once CFileHandleTimer *pTimer = NULL; { AUTO_LOCK( m_FileHandleTimersMutex ); // Still do from the end to the beginning for consistency with previous code (and make access to Count() only once). for ( int nTimer = m_FileHandleTimers.Count() - 1; nTimer >= 0; --nTimer ) { CFileHandleTimer *pLocalTimer = m_FileHandleTimers[ nTimer ]; if ( pLocalTimer && pLocalTimer->m_hFile == file ) { pTimer = pLocalTimer; m_FileHandleTimers.Remove( nTimer ); break; } } } // m_FileHandleTimers is not locked here (but we can still access pTimer) if ( pTimer != NULL ) { // Found the file, report the time between opening and closing pTimer->End(); unsigned long ulMicroseconds = pTimer->GetDuration().GetMicroseconds(); if ( ulLongLoadThreshold <= ulMicroseconds ) { Warning( "Open( %lu microsecs, %s )\n", ulMicroseconds, pTimer->m_szName ); } // Accumulate time spent if this file has been opened at least twice { AUTO_LOCK( g_FileOpenDuplicateTimesMutex ); for ( int nFileOpenDuplicate = 0; nFileOpenDuplicate < g_FileOpenDuplicateTimes.Count(); nFileOpenDuplicate++ ) { FileOpenDuplicateTime_t *pFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ]; if ( Q_stricmp( pTimer->m_szName, pFileOpenDuplicate->m_szName ) == 0 ) { if ( pFileOpenDuplicate->m_nLoadCount > 1 ) { pFileOpenDuplicate->m_flAccumulatedMicroSeconds += pTimer->GetDuration().GetMicrosecondsF(); } break; } } } delete pTimer; } } delete (CFileHandle*)file; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseFileSystem::Seek( FileHandle_t file, int pos, FileSystemSeek_t whence ) { VPROF_BUDGET( "CBaseFileSystem::Seek", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); CFileHandle *fh = ( CFileHandle *)file; if ( !fh ) { FileSystemWarning( FILESYSTEM_WARNING, "Tried to Seek NULL file handle!\n" ); return; } #ifndef _CERT int nTimeStart = Plat_MSTime(); #endif fh->Seek( pos, whence ); #ifndef _CERT s_IoStats.OnFileSeek( Plat_MSTime() - nTimeStart ); #endif } //----------------------------------------------------------------------------- // Purpose: // Input : file - // Output : unsigned int //----------------------------------------------------------------------------- unsigned int CBaseFileSystem::Tell( FileHandle_t file ) { VPROF_BUDGET( "CBaseFileSystem::Tell", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); if ( !file ) { FileSystemWarning( 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 ) { VPROF_BUDGET( "CBaseFileSystem::Size", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); if ( !file ) { FileSystemWarning( 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] ) { FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Size NULL filename!\n" ); return 0; } if ( IsPC() ) { // If we have a whitelist and it's forcing the file to load from Steam instead of from disk, // then do this the slow way, otherwise we'll get the wrong file size (i.e. the size of the file on disk). CWhitelistSpecs *pWhitelist = m_FileWhitelist.AddRef(); if ( pWhitelist ) { bool bAllowFromDisk = pWhitelist->m_pAllowFromDiskList->IsFileInList( pFileName ); m_FileWhitelist.ReleaseRef( pWhitelist ); if ( !bAllowFromDisk ) { FileHandle_t fh = Open( pFileName, "rb", pPathID ); if ( fh ) { unsigned int ret = Size( fh ); Close( fh ); return ret; } else { return 0; } } } } // Ok, fall through to the fast path. int iSize = 0; CSearchPathsIterator iter( this, &pFileName, pPathID ); for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { iSize = FastFindFile( pSearchPath, pFileName ); if ( iSize >= 0 ) { break; } } return iSize; } //----------------------------------------------------------------------------- // Purpose: // Input : *path - // *pFileName - // Output : long //----------------------------------------------------------------------------- long CBaseFileSystem::FastFileTime( const CSearchPath *path, const char *pFileName ) { struct _stat buf; char tempSymlinkBuffer[MAX_PATH]; pFileName = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pFileName ); if ( path->GetPackFile() ) { int nIndex, nLength; int64 nPosition; // If we found the file: if ( path->GetPackFile()->FindFile( pFileName, nIndex, nPosition, nLength ) ) { return (path->GetPackFile()->m_lPackFileTime); } } else { // Is it an absolute path? char tempFileName[ MAX_FILEPATH ]; if ( Q_IsAbsolutePath( pFileName ) ) { Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) ); #ifdef SUPPORT_VPK if ( m_VPKFiles.Count() ) { #ifdef _DEBUG FileSystemWarning( FILESYSTEM_WARNING, "***VPK: FastFileTime Attempting to use full path with VPK file!\n\tFile: %s\n", pFileName ); #endif } #endif } else { bool bFileInVpk = false; #ifdef SUPPORT_VPK if ( m_VPKFiles.Count() ) { Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) ); Q_FixSlashes( tempFileName ); // check vpk file for ( int i = 0; i < m_VPKFiles.Count(); i++ ) { CPackedStoreFileHandle fHandle = m_VPKFiles[i]->OpenFile( tempFileName ); if ( fHandle ) { // File found in VPK - return file time of the VPK m_VPKFiles[i]->GetPackFileName( fHandle, tempFileName, sizeof( tempFileName ) ); bFileInVpk = true; break; } } } #endif if ( !bFileInVpk ) { Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", path->GetPathString(), pFileName ); } } Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 ) { return buf.st_mtime; } #ifdef LINUX // Support Linux and its case sensitive file system char realName[MAX_PATH]; const char *pRealName = findFileInDirCaseInsensitive( tempFileName, realName ); if ( pRealName && FS_stat( pRealName, &buf ) != -1 ) { return buf.st_mtime; } #endif } return ( 0L ); } int CBaseFileSystem::FastFindFile( const CSearchPath *path, const char *pFileName ) { struct _stat buf; char tempSymlinkBuffer[MAX_PATH]; pFileName = V_FormatFilenameForSymlinking( tempSymlinkBuffer, pFileName ); if ( path->GetPackFile() ) { int nIndexResult; int64 nOffsetResult; int nLengthResult; if ( path->GetPackFile()->FindFile( pFileName, nIndexResult, nOffsetResult, nLengthResult ) ) { return nLengthResult; } else { return -1; } } char tempFileName[ MAX_FILEPATH ]; // Is it an absolute path? bool bRelativePath = !Q_IsAbsolutePath( pFileName ); #ifdef SUPPORT_VPK // NOTE: Pack files need relative paths if ( !bRelativePath && m_VPKFiles.Count() ) { #ifdef _DEBUG FileSystemWarning( FILESYSTEM_WARNING, "***VPK: FastFindFile Attempting to use full path with VPK file!\n\tFile: %s\n", pFileName ); #endif } if ( bRelativePath ) { Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) ); Q_FixSlashes( tempFileName ); // check vpk file for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { CPackedStoreFileHandle fHandle = m_VPKFiles[i]->OpenFile( tempFileName ); if ( fHandle ) { return fHandle.m_nFileSize; } } } #endif if ( !bRelativePath ) { Q_strncpy( tempFileName, pFileName, sizeof( tempFileName ) ); } else { Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", path->GetPathString(), pFileName ); } Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } #if defined(_PS3) FixUpPathCaseForPS3(tempFileName); #endif if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 ) { LogAccessToFile( "stat", tempFileName, "" ); return buf.st_size; } #ifdef LINUX // Support Linux and its case sensitive file system char realName[MAX_PATH]; if ( findFileInDirCaseInsensitive( tempFileName, realName ) && FS_stat( realName, &buf ) != -1 ) { return buf.st_size; } #endif return ( -1 ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CBaseFileSystem::EndOfFile( FileHandle_t file ) { if ( !file ) { FileSystemWarning( 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 ) { VPROF_BUDGET( "CBaseFileSystem::Read", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); NoteIO(); if ( !file ) { FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to Read NULL file handle!\n" ); return 0; } if ( size < 0 ) { return 0; } #ifndef _CERT int nTimeStart = Plat_MSTime(); #endif int nRet = ((CFileHandle*)file)->Read(pOutput, destSize, size ); #ifndef _CERT s_IoStats.OnFileRead( Plat_MSTime() - nTimeStart, size ); #endif return nRet; } //----------------------------------------------------------------------------- // 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 ); bret = head.LoadFromFile( this, filename, pPathID ); #else bret = head.LoadFromFile( this, filename, pPathID ); #endif return bret; } //----------------------------------------------------------------------------- // 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 = new KeyValues( filename ); if ( kv ) { kv->LoadFromFile( this, filename, pPathID ); } 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: //----------------------------------------------------------------------------- void CBaseFileSystem::SetupPreloadData() { int i; for ( i = 0; i < m_SearchPaths.Count(); i++ ) { CPackFile* pPF = m_SearchPaths[i].GetPackFile(); if ( pPF ) { pPF->SetupPreloadData(); } } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseFileSystem::DiscardPreloadData() { int i; for( i = 0; i < m_SearchPaths.Count(); i++ ) { CPackFile* pf = m_SearchPaths[i].GetPackFile(); if ( pf ) { pf->DiscardPreloadData(); } } } //----------------------------------------------------------------------------- // 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 ) { FileSystemWarning( 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 ) { FileSystemWarning( FILESYSTEM_WARNING, "FS: Tried to FPrintf NULL file handle!\n" ); return 0; } /* if ( !fh->GetFileHandle() ) { FileSystemWarning( 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 ) { FileSystemWarning( 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 ) { FileSystemWarning( 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 ) { FileSystemWarning( 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 ) { FileSystemWarning( 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 && pOutput[nRead] == '\r' ) { // Ignore \r continue; } // We're done when we hit a '\n' if( pOutput[nRead] == '\n' ) { nRead++; break; } // Get outta here if we find a NULL. if( pOutput[nRead] == '\0' ) { pOutput[nRead] = '\n'; nRead++; break; } nRead++; } if( nRead < maxChars ) pOutput[nRead] = '\0'; m_Stats.nBytesRead += nRead; return ( nRead ) ? pOutput : NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : *pFileName - // Output : long //----------------------------------------------------------------------------- long CBaseFileSystem::GetFileTime( const char *pFileName, const char *pPathID ) { VPROF_BUDGET( "CBaseFileSystem::GetFileTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); CHECK_DOUBLE_SLASHES( pFileName ); CSearchPathsIterator iter( this, &pFileName, pPathID ); char tempFileName[MAX_PATH]; Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) ); Q_FixSlashes( tempFileName ); #ifdef _WIN32 Q_strlower( tempFileName ); #endif for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { long ft = FastFileTime( pSearchPath, tempFileName ); if ( ft != 0L ) { if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() ) { char pTmpFileName[ MAX_FILEPATH ]; if ( strchr( tempFileName, ':' ) ) // { Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) ); } else { Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName ); } Q_FixSlashes( pTmpFileName ); LogAccessToFile( "filetime", pTmpFileName, "" ); } return ft; } } return 0L; } long CBaseFileSystem::GetPathTime( const char *pFileName, const char *pPathID ) { VPROF_BUDGET( "CBaseFileSystem::GetFileTime", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); CSearchPathsIterator iter( this, &pFileName, pPathID ); char tempFileName[MAX_PATH]; Q_strncpy( tempFileName, pFileName, sizeof(tempFileName) ); Q_FixSlashes( tempFileName ); #ifdef _WIN32 Q_strlower( tempFileName ); #endif long pathTime = 0L; for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { long ft = FastFileTime( pSearchPath, tempFileName ); if ( ft > pathTime ) pathTime = ft; if ( ft != 0L ) { if ( !pSearchPath->GetPackFile() && m_LogFuncs.Count() ) { char pTmpFileName[ MAX_FILEPATH ]; if ( strchr( tempFileName, ':' ) ) { Q_strncpy( pTmpFileName, tempFileName, sizeof( pTmpFileName ) ); } else { Q_snprintf( pTmpFileName, sizeof( pTmpFileName ), "%s%s", pSearchPath->GetPathString(), tempFileName ); } Q_FixSlashes( pTmpFileName ); LogAccessToFile( "filetime", pTmpFileName, "" ); } } } return pathTime; } bool CBaseFileSystem::ShouldGameReloadFile( const char *pFilename ) { if ( IsGameConsole() ) { return false; } if ( V_IsAbsolutePath( pFilename ) ) { if ( m_WhitelistSpewFlags & WHITELIST_SPEW_RELOAD_FILES ) { Msg( "Whitelist - reload (absolute path) %s\n", pFilename ); } // They should be checking with relative filenames, but this is easy to remedy if we need to. // Easy enough to remedy if we want.. just strip off the path ID prefixes until we find the file. Assert( false ); return true; } CFileInfo *fileInfos[256]; int nFileInfos = m_FileTracker.GetFileInfos( fileInfos, ARRAYSIZE( fileInfos ), pFilename ); if ( nFileInfos == 0 ) { // Ain't heard of this file. It probably came from a BSP or a pak file. if ( m_WhitelistSpewFlags & WHITELIST_SPEW_DONT_RELOAD_FILES ) { Msg( "Whitelist - don't reload (unheard-of-file) %s\n", pFilename ); } return false; } // Note: This might be null, in which case all files are allowed to come off disk. bool bFileAllowedToComeFromDisk = true; CWhitelistSpecs *pWhitelist = m_FileWhitelist.GetInMainThread(); if ( pWhitelist ) bFileAllowedToComeFromDisk = pWhitelist->m_pAllowFromDiskList->IsFileInList( pFilename ); // Since we don't require the game to specify which path ID it's interested in here, there's a small amount // of ambiguity here (because 2 files with the same name could have been loaded from different path IDs). // This case should be extremely rare, and we error on the side of simplicity (don't require the game to // remember which path ID its files were opened from). // // In the case where there are multiple files, we error on the side of reloading the file - if any of the // files here would require a reload, then we tell the game to reload this file even if it might not // have strictly needed to reload it. bool bRet = false; for ( int i=0; i < nFileInfos; i++ ) { CFileInfo *pFileInfo = fileInfos[i]; // See comments above k_eFileFlagsFailedToLoadLastTime for info about this case. if ( bFileAllowedToComeFromDisk && (pFileInfo->m_Flags & k_eFileFlagsFailedToLoadLastTime) ) { bRet = true; break; } if ( pFileInfo->m_Flags & k_eFileFlagsLoadedFromSteam ) { if ( (pFileInfo->m_Flags & k_eFileFlagsForcedLoadFromSteam) && bFileAllowedToComeFromDisk ) { // So.. the last time we loaded this file, we forced it to come from Steam, but the new whitelist says it's ok if this // file is loaded off disk. So reload it. // // TODO: we could optimize this by checking if there even IS a file on disk that would override the Steam one, // and in that case, don't tell the game to reload it. // // We could also optimize it if we remembered whether we told Steam to allow loads off disk or not. // If we did allow loads and Steam still got the file from Steam, then we know that there isn't // a file on disk here, and therefore we wouldn't have to reload it now. bRet = true; break; } else { // So we loaded the file from Steam last time, and the new whitelist only allows it to come from Steam, so we're ok. //return false; } } else { if ( bFileAllowedToComeFromDisk ) { // No need to reload. The new whitelist says this file can come off disk, and the last time we loaded it, it was off disk. // The client will still verify the CRC of the file with the server to make sure its file is legit.. //return false; } else { // The file was loaded off disk but the server won't allow that now. bRet = true; break; } } } if ( (m_WhitelistSpewFlags & WHITELIST_SPEW_RELOAD_FILES) && bRet ) Msg( "Whitelist - reload %s\n", pFilename ); if ( (m_WhitelistSpewFlags & WHITELIST_SPEW_DONT_RELOAD_FILES) && !bRet ) Msg( "Whitelist - don't reload %s\n", pFilename ); return bRet; } void CBaseFileSystem::MarkAllCRCsUnverified() { if ( IsGameConsole() ) { return; } m_FileTracker2.MarkAllCRCsUnverified(); } void CBaseFileSystem::CacheFileCRCs( const char *pPathname, ECacheCRCType eType, IFileList *pFilter ) { if ( IsGameConsole() ) { return; } // Get a list of the unique search path names (mod, game, platform, etc). CUtlDict searchPathNames; m_SearchPathsMutex.Lock(); for ( int i = 0; i < m_SearchPaths.Count(); i++ ) { CSearchPath *pSearchPath = &m_SearchPaths[i]; if ( searchPathNames.Find( pSearchPath->GetPathIDString() ) == searchPathNames.InvalidIndex() ) searchPathNames.Insert( pSearchPath->GetPathIDString() ); } m_SearchPathsMutex.Unlock(); CacheFileCRCs_R( pPathname, eType, pFilter, searchPathNames ); } void CBaseFileSystem::CacheFileCRCs_R( const char *pPathname, ECacheCRCType eType, IFileList *pFilter, CUtlDict &searchPathNames ) { if ( IsGameConsole() ) { return; } char searchStr[MAX_PATH]; bool bRecursive = false; if ( eType == k_eCacheCRCType_SingleFile ) { V_snprintf( searchStr, sizeof( searchStr ), "%s", pPathname ); } else if ( eType == k_eCacheCRCType_Directory ) { V_ComposeFileName( pPathname, "*.*", searchStr, sizeof( searchStr ) ); } else if ( eType == k_eCacheCRCType_Directory_Recursive ) { V_ComposeFileName( pPathname, "*.*", searchStr, sizeof( searchStr ) ); bRecursive = true; } // Get the path we're searching in. char pathDirectory[MAX_PATH]; V_strncpy( pathDirectory, searchStr, sizeof( pathDirectory ) ); V_StripLastDir( pathDirectory, sizeof( pathDirectory ) ); /* Note: This is tricky because the client could check different path IDs with the same filename and we'd either have the same file or a different file depending on these two cases: a) they have one file : hl2\blah.txt (in this case, checking the GAME and MOD path IDs for blah.txt return the same file CRC) b) they have two files: hl2\blah.txt AND hl2mp\blah.txt (in this case, checking the GAME and MOD path IDs for blah.txt return different file CRCs) */ CUtlDict< CUtlVector* ,int> filesByStoreID; // key=filename, value=list of store IDs this filename was found in for ( int i=searchPathNames.First(); i != searchPathNames.InvalidIndex(); i = searchPathNames.Next( i ) ) { // Now find all the files.. int foundStoreID; const char *pPathIDStr = searchPathNames.GetElementName( i ); FileFindHandle_t findHandle; const char *pFilename = FindFirstHelper( searchStr, pPathIDStr, &findHandle, &foundStoreID ); while ( pFilename ) { if ( pFilename[0] != '.' ) { char relativeName[MAX_PATH]; V_ComposeFileName( pathDirectory, pFilename, relativeName, sizeof( relativeName ) ); if ( FindIsDirectory( findHandle ) ) { if ( bRecursive ) CacheFileCRCs_R( relativeName, eType, pFilter, searchPathNames ); } else { if ( pFilter->IsFileInList( relativeName ) ) { CStoreIDEntry *pPrevRecord = FindPrevFileByStoreID( filesByStoreID, pFilename, pPathIDStr, foundStoreID ); if ( pPrevRecord ) { // Ok, we already found this file in an earlier search path with the same storeID (i.e. the exact same disk path) // so rather than recalculate the CRC redundantly, just copy the CRC from the previous one into a record with the new path ID string. // This saves a lot of redundant CRC calculations since logdir, default_write_path, game, and mod all share directories. m_FileTracker.CacheFileCRC_Copy( pPathIDStr, relativeName, pPrevRecord->m_PathIDString.String() ); } else { // Ok, we want the CRC for this file. m_FileTracker.CacheFileCRC( pPathIDStr, relativeName ); } } } } FindData_t *pFindData = &m_FindData[findHandle]; if ( !FindNextFileHelper( pFindData, &foundStoreID ) ) break; pFilename = pFindData->findData.cFileName; } FindClose( findHandle ); } filesByStoreID.PurgeAndDeleteElements(); } 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 ( IsGameConsole() ) { 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_VPK for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { if ( !m_VPKFiles[i]->BTestDirectoryHash() ) { Msg( "VPK dir file hash does not match. File corrupted or modified.\n" ); } if ( !m_VPKFiles[i]->BTestMasterChunkHash() ) { Msg( "VPK chunk hash hash does not match. File corrupted or modified.\n" ); } CUtlVector &vecChunkHash = m_VPKFiles[i]->AccessPackFileHashes(); CPackedStoreFileHandle fhandle = m_VPKFiles[i]->GetHandleForHashingFiles(); CUtlVector vecChunkHashFractionCopy; if ( bRecalculateAndCheckHashes ) { CUtlString sPackFileErrors; m_VPKFiles[i]->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" ); m_VPKFiles[i]->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( "Loading VPK file hashes for pure server operation.\n" ); 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_VPK for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { if ( m_VPKFiles[i]->m_PackFileID == PackFileID ) { ChunkHashFraction_t fileHashFraction; if ( m_VPKFiles[i]->FindFileHashFraction( nPackFileNumber, nFileFraction, fileHashFraction ) ) { CPackedStoreFileHandle fhandle = m_VPKFiles[i]->GetHandleForHashingFiles(); fhandle.m_nFileNumber = nPackFileNumber; char szFileName[MAX_PATH]; m_VPKFiles[i]->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; } } } } return false; #endif } void CBaseFileSystem::RegisterFileWhitelist( IFileList *pWantCRCList, IFileList *pAllowFromDiskList, IFileList **pFilesToReload ) { if ( IsGameConsole() ) { return; } CWhitelistSpecs *pOldList = m_FileWhitelist.GetInMainThread(); if ( pOldList ) { m_FileWhitelist.ReleaseRef( pOldList ); // Get rid of our reference to it so it can be freed. m_FileWhitelist.ResetWhenNoRemainingReferences( NULL ); // Wait for everyone else to stop hanging onto it, then free it. // Free the old ones (other threads shouldn't have access to these anymore because pOldList->m_pAllowFromDiskList->Release(); pOldList->m_pWantCRCList->Release(); } if ( pAllowFromDiskList && pWantCRCList ) { CWhitelistSpecs *pNewList = new CWhitelistSpecs; pNewList->m_pAllowFromDiskList = pAllowFromDiskList; pNewList->m_pWantCRCList = pWantCRCList; m_FileWhitelist.Init( pNewList ); } } int CBaseFileSystem::GetUnverifiedFileHashes( CUnverifiedFileHash *pFiles, int nMaxFiles ) { return m_FileTracker2.GetUnverifiedFileHashes( pFiles, nMaxFiles ); } int CBaseFileSystem::GetWhitelistSpewFlags() { return m_WhitelistSpewFlags; } void CBaseFileSystem::SetWhitelistSpewFlags( int flags ) { m_WhitelistSpewFlags = flags; } //----------------------------------------------------------------------------- // Purpose: // Input : *pString - // maxCharsIncludingTerminator - // fileTime - //----------------------------------------------------------------------------- void CBaseFileSystem::FileTimeToString( char *pString, int maxCharsIncludingTerminator, long fileTime ) { if ( IsGameConsole() ) { 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 ); NoteIO(); CHECK_DOUBLE_SLASHES( pFileName ); CSearchPathsIterator iter( this, &pFileName, pPathID ); for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { int size = FastFindFile( pSearchPath, pFileName ); if ( size >= 0 ) { 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 ) ) { bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( pFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } if ( FS_stat( bFixed ? fixedFATXFilename : pFileName, &buf ) != -1 ) { #ifdef WIN32 if ( buf.st_mode & _S_IWRITE ) #elif defined( _PS3 ) if( buf.st_mode & S_IWUSR ) #elif POSIX if ( buf.st_mode & S_IWRITE ) #else if ( buf.st_mode & S_IWRITE ) #endif { return true; } } return false; } CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK ); for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { char tempFileName[ MAX_FILEPATH ]; Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFileName ); Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 ) { #ifdef WIN32 if ( buf.st_mode & _S_IWRITE ) #elif defined( _PS3 ) if( buf.st_mode & S_IWUSR ) #elif POSIX if ( buf.st_mode & S_IWRITE ) #else if ( buf.st_mode & S_IWRITE ) #endif { return true; } } } return false; } bool CBaseFileSystem::SetFileWritable( char const *pFileName, bool writable, const char *pPathID /*= 0*/ ) { CHECK_DOUBLE_SLASHES( pFileName ); #ifdef _WIN32 int pmode = writable ? ( _S_IWRITE | _S_IREAD ) : ( _S_IREAD ); #elif defined( _PS3 ) 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 ) ) { bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( pFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } return ( FS_chmod( bFixed ? fixedFATXFilename : pFileName, pmode ) == 0 ); } CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLPACK ); for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { char tempFilename[ MAX_FILEPATH ]; Q_snprintf( tempFilename, sizeof( tempFilename ), "%s%s", pSearchPath->GetPathString(), pFileName ); Q_FixSlashes( tempFilename ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFilename, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } if ( FS_chmod( bFixed ? fixedFATXFilename : tempFilename, 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 ) ) { bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( pFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } if ( FS_stat( bFixed ? fixedFATXFilename : 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() ) { char tempFileName[ MAX_FILEPATH ]; Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFileName ); Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } if ( FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1 ) { if ( buf.st_mode & _S_IFDIR ) return true; } } #ifdef SUPPORT_VPK // // Let's see if the directory exists in the VPK file structure // if ( 0 == m_VPKDirectories.Count() ) { // Populate the directory list CUtlStringList dirNames; CUtlStringList fileNames; for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { m_VPKFiles[i]->GetFileAndDirLists( dirNames, fileNames, true ); } FOR_EACH_VEC( dirNames, j ) { m_VPKDirectories.Insert( dirNames[j], 0 ); } } // If the dir isn't part of the VPK structure then game over char szPathWithCorrectSlashes[MAX_PATH]; V_strncpy( szPathWithCorrectSlashes, pFileName, sizeof( szPathWithCorrectSlashes ) ); V_FixSlashes( szPathWithCorrectSlashes, '/' ); return ( -1 != m_VPKDirectories.Find( szPathWithCorrectSlashes ) ); #else return ( false ); #endif } //----------------------------------------------------------------------------- // Purpose: // Input : *path - //----------------------------------------------------------------------------- void CBaseFileSystem::CreateDirHierarchy( const char *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 ); char szScratchFileName[MAX_PATH]; if ( !Q_IsAbsolutePath( pRelativePath ) ) { Assert( pathID ); ComputeFullWritePath( szScratchFileName, sizeof( szScratchFileName ), pRelativePath, pathID ); } else { Q_strncpy( szScratchFileName, pRelativePath, sizeof(szScratchFileName) ); } Q_FixSlashes( szScratchFileName ); int len = strlen( szScratchFileName ) + 1; char *end = szScratchFileName + len; char *s = szScratchFileName; while( s < end ) { if ( PATHSEPARATOR( *s ) && s != szScratchFileName && ( IsLinux() || IsPS3() || *( s - 1 ) != ':' ) ) { char save = *s; *s = '\0'; #if defined( _WIN32 ) _mkdir( szScratchFileName ); #elif defined( _PS3 ) CellFsStat status; //Only create is the path doesn't exist already - Jawad. if ( cellFsStat( szScratchFileName, &status ) != CELL_FS_SUCCEEDED ) cellFsMkdir( szScratchFileName, CELL_FS_DEFAULT_CREATE_MODE_1 ); #elif defined( POSIX ) mkdir( szScratchFileName, S_IRWXU | S_IRGRP | S_IROTH );// owner has rwx, rest have r #endif *s = save; } s++; } #if defined( _WIN32 ) _mkdir( szScratchFileName ); #elif defined( _PS3 ) CellFsStat status; if ( cellFsStat( szScratchFileName, &status ) != CELL_FS_SUCCEEDED ) cellFsMkdir( szScratchFileName, CELL_FS_DEFAULT_CREATE_MODE_1 ); #elif defined( POSIX ) mkdir( szScratchFileName, S_IRWXU | S_IRGRP | S_IROTH ); #endif } //----------------------------------------------------------------------------- // Purpose: Given an absolute path, do a find first find next on it and build // a list of files. Physical file system only //----------------------------------------------------------------------------- void CBaseFileSystem::FindFileAbsoluteListHelper( CUtlVector< CUtlString > &outAbsolutePathNames, FindData_t &findData, const char *pAbsoluteFindName ) { // TODO: figure out what PS3 does without VPKs #ifndef _PS3 bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( pAbsoluteFindName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } char path[MAX_PATH]; V_strncpy( path, pAbsoluteFindName, sizeof(path) ); V_StripFilename( path ); findData.findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : pAbsoluteFindName, &findData.findData ); while ( findData.findHandle != INVALID_HANDLE_VALUE ) { char result[MAX_PATH]; V_ComposeFileName( path, findData.findData.cFileName, result, sizeof(result) ); outAbsolutePathNames.AddToTail( result ); if ( !FS_FindNextFile( findData.findHandle, &findData.findData ) ) { FS_FindClose( findData.findHandle ); findData.findHandle = INVALID_HANDLE_VALUE; } } #else Error( "Not implemented!\n" ); #endif } //----------------------------------------------------------------------------- // Purpose: Searches for a file in all paths and results absolute path names // for the file, works in pack files (zip and vpk) too. Lets you search for // something like sound/sound.cache and get a list of every sound cache //----------------------------------------------------------------------------- void CBaseFileSystem::FindFileAbsoluteList( CUtlVector< CUtlString > &outAbsolutePathNames, const char *pWildCard, const char *pPathID ) { // TODO: figure out what PS3 does without VPKs #ifndef _PS3 VPROF_BUDGET( "CBaseFileSystem::FindFileAbsoluteList", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); outAbsolutePathNames.Purge(); FindData_t findData; if ( pPathID ) { findData.m_FilterPathID = g_PathIDTable.AddString( pPathID ); } int maxlen = strlen( pWildCard ) + 1; findData.wildCardString.AddMultipleToTail( maxlen ); Q_strncpy( findData.wildCardString.Base(), pWildCard, maxlen ); Q_FixSlashes( findData.wildCardString.Base() ); findData.findHandle = INVALID_HANDLE_VALUE; if ( Q_IsAbsolutePath( pWildCard ) ) { FindFileAbsoluteListHelper( outAbsolutePathNames, findData, pWildCard ); } else { int c = m_SearchPaths.Count(); for ( findData.currentSearchPathID = 0; findData.currentSearchPathID < c; findData.currentSearchPathID++ ) { CSearchPath *pSearchPath = &m_SearchPaths[findData.currentSearchPathID]; if ( pSearchPath->GetPackFile() ) // We're going to search pack files second continue; if ( FilterByPathID( pSearchPath, findData.m_FilterPathID ) ) continue; // already visited this path if ( findData.m_VisitedSearchPaths.MarkVisit( *pSearchPath ) ) continue; char tempFileName[ MAX_FILEPATH ]; Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), findData.wildCardString.Base() ); Q_FixSlashes( tempFileName ); FindFileAbsoluteListHelper( outAbsolutePathNames, findData, tempFileName ); } } //TODO: zips! #if defined( SUPPORT_VPK ) // // Now that we have searched the filesystem let's look in the VPK files // FOR_EACH_VEC( m_VPKFiles, i ) { CUtlStringList dirMatchesFromVPK, fileMatchesFromVPK; m_VPKFiles[i]->GetFileAndDirLists( pWildCard, dirMatchesFromVPK, fileMatchesFromVPK, true ); FOR_EACH_VEC( dirMatchesFromVPK, j ) { char result[MAX_PATH]; V_ComposeFileName( m_VPKFiles[i]->FullPathName(), dirMatchesFromVPK[j], result, sizeof(result) ); outAbsolutePathNames.AddToTail( result ); } FOR_EACH_VEC( fileMatchesFromVPK, j ) { char result[MAX_PATH]; V_ComposeFileName( m_VPKFiles[i]->FullPathName(), fileMatchesFromVPK[j], result, sizeof(result) ); outAbsolutePathNames.AddToTail( result ); } } #endif #else Error( "Not implemented!\n" ); #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 *pWildCard, const char *pPathID, FileFindHandle_t *pHandle, int *pFoundStoreID ) { VPROF_BUDGET( "CBaseFileSystem::FindFirst", VPROF_BUDGETGROUP_OTHER_FILESYSTEM ); Assert( pWildCard ); 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 ); } int maxlen = strlen( pWildCard ) + 1; pFindData->wildCardString.AddMultipleToTail( maxlen ); Q_strncpy( pFindData->wildCardString.Base(), pWildCard, maxlen ); Q_FixSlashes( pFindData->wildCardString.Base() ); pFindData->findHandle = INVALID_HANDLE_VALUE; if ( Q_IsAbsolutePath( pWildCard ) ) { bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( pWildCard, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } pFindData->findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : 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]; // FIXME: Should findfirst/next work with pak files? if ( pSearchPath->GetPackFile() ) continue; if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) ) continue; // already visited this path if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) ) continue; char tempFileName[ MAX_FILEPATH ]; Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() ); Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } pFindData->findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : tempFileName, &pFindData->findData ); pFindData->m_CurrentStoreID = pSearchPath->m_storeId; if ( pFindData->findHandle != INVALID_HANDLE_VALUE ) break; } } #ifdef SUPPORT_VPK // // Now that we have searched the filesystem let's look in the VPK files // for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { m_VPKFiles[i]->GetFileAndDirLists( pWildCard, pFindData->m_dirMatchesFromVPK, pFindData->m_fileMatchesFromVPK, true ); } #endif // 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; } #ifdef SUPPORT_VPK // We have a file result from the VPK file but not the filesystem else if ( pFindData->m_fileMatchesFromVPK.Count() > 0 ) { // Remember that we visited this file already. pFindData->m_VisitedFiles.Insert( V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPK[0] ), 0 ); *pHandle = hTmpHandle; V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) ); pFindData->findData.dwFileAttributes = 0; delete pFindData->m_fileMatchesFromVPK.Head(); pFindData->m_fileMatchesFromVPK.RemoveMultipleFromHead( 1 ); return pFindData->findData.cFileName; } // We have a dir result from the VPK file but not the filesystem else if ( pFindData->m_dirMatchesFromVPK.Count() > 0 ) { // Remember that we visited this file already. pFindData->m_VisitedFiles.Insert( V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPK[0] ), 0 ); *pHandle = hTmpHandle; V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) ); pFindData->findData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; delete pFindData->m_dirMatchesFromVPK.Head(); pFindData->m_dirMatchesFromVPK.RemoveMultipleFromHead( 1 ); return pFindData->findData.cFileName; } #endif // 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 ) { // PAK files??? // 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; } // 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]; // FIXME: Should this work with PAK files? if ( pSearchPath->GetPackFile() ) continue; if ( FilterByPathID( pSearchPath, pFindData->m_FilterPathID ) ) continue; // already visited this path if ( pFindData->m_VisitedSearchPaths.MarkVisit( *pSearchPath ) ) continue; char tempFileName[ MAX_FILEPATH ]; Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFindData->wildCardString.Base() ); Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } pFindData->findHandle = FS_FindFirstFile( bFixed ? fixedFATXFilename : tempFileName, &pFindData->findData ); pFindData->m_CurrentStoreID = pSearchPath->m_storeId; if ( pFindData->findHandle != INVALID_HANDLE_VALUE ) { if ( pFoundStoreID ) *pFoundStoreID = pFindData->m_CurrentStoreID; return true; } } #ifdef SUPPORT_VPK // Return the next one from the list of VPK matches if there is one if ( pFindData->m_fileMatchesFromVPK.Count() > 0 ) { V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_fileMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) ); pFindData->findData.dwFileAttributes = 0; delete pFindData->m_fileMatchesFromVPK.Head(); pFindData->m_fileMatchesFromVPK.RemoveMultipleFromHead( 1 ); return true; } // Return the next one from the list of VPK matches if there is one if ( pFindData->m_dirMatchesFromVPK.Count() > 0 ) { V_strncpy( pFindData->findData.cFileName, V_UnqualifiedFileName( pFindData->m_dirMatchesFromVPK[0] ), sizeof( pFindData->findData.cFileName ) ); pFindData->findData.dwFileAttributes = FILE_ATTRIBUTE_DIRECTORY; delete pFindData->m_dirMatchesFromVPK.Head(); pFindData->m_dirMatchesFromVPK.RemoveMultipleFromHead( 1 ); return true; } #endif 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(); #ifdef SUPPORT_VPK pFindData->m_fileMatchesFromVPK.PurgeAndDeleteElements(); pFindData->m_dirMatchesFromVPK.PurgeAndDeleteElements(); #endif m_FindData.Remove( handle ); } //----------------------------------------------------------------------------- // Purpose: // Input : *pFileName - //----------------------------------------------------------------------------- void CBaseFileSystem::GetLocalCopy( const char *pFileName ) { // do nothing. . everything is local. } #ifdef SUPPORT_VPK //----------------------------------------------------------------------------- // Purpose: // Input : *pFileName - //----------------------------------------------------------------------------- CPackedStoreFileHandle CBaseFileSystem::FindFileInVPKs( const char *pFileName ) { CPackedStoreFileHandle retVal; // Try to find the path in the mounted VPK files for( int i = 0 ; i < m_VPKFiles.Count(); i++ ) { retVal = m_VPKFiles[i]->OpenFile( pFileName ); if ( retVal ) { break; } } return retVal; } #endif //----------------------------------------------------------------------------- // 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, char *pFullPath, int fullPathBufferSize, PathTypeFilter_t pathFilter, PathTypeQuery_t *pPathType ) { CHECK_DOUBLE_SLASHES( pFileName ); struct _stat buf; if ( pPathType ) { *pPathType = PATH_IS_NORMAL; } #ifdef _PS3 // crush the filename to lowercase char lowercasedname[256]; V_strncpy( lowercasedname, pFileName, 255 ); V_strnlwr( lowercasedname, 255 ); pFileName = lowercasedname; #endif // Fill in the default in case it's not found... Q_strncpy( pFullPath, pFileName, fullPathBufferSize ); 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() ) { int dummy; int64 dummy64; CPackFile *pPack = pSearchPath->GetPackFile(); if ( pPack ) { if ( pPack->FindFile( pFileName, dummy, dummy64, dummy ) ) { if ( pPathType ) { if ( pPack->m_bIsMapPath ) { *pPathType |= PATH_IS_MAPPACKFILE; } else { *pPathType |= PATH_IS_PACKFILE; } if ( pSearchPath->m_bIsDvdDevPath ) { *pPathType |= PATH_IS_DVDDEV; } } // form an encoded absolute path that can be decoded by our FS as pak based int len; V_strncpy( pFullPath, pPack->m_ZipName.String(), fullPathBufferSize ); len = strlen( pFullPath ); V_AppendSlash( pFullPath, fullPathBufferSize - len ); len = strlen( pFullPath ); V_strncpy( pFullPath + len, pFileName, fullPathBufferSize - len ); return pFullPath; } continue; } char tempFileName[ MAX_FILEPATH ]; Q_snprintf( tempFileName, sizeof( tempFileName ), "%s%s", pSearchPath->GetPathString(), pFileName ); Q_FixSlashes( tempFileName ); bool bFixed = false; char fixedFATXFilename[MAX_PATH]; if ( IsX360() ) { // do not expose naming convolution to callers // callers don't expect the filename to change, only that the path get filled out bFixed = FixupFATXFilename( tempFileName, fixedFATXFilename, sizeof( fixedFATXFilename ) ); } bool bFound = FS_stat( bFixed ? fixedFATXFilename : tempFileName, &buf ) != -1; if ( bFound ) { Q_strncpy( pFullPath, tempFileName, fullPathBufferSize ); if ( pPathType && pSearchPath->m_bIsDvdDevPath ) { *pPathType |= PATH_IS_DVDDEV; } return pFullPath; } } #ifdef SUPPORT_VPK // Try to find the path in the mounted VPK files if ( FindFileInVPKs( pFileName ) ) { char pModPath[MAX_PATH]; GetSearchPath( "MOD", false, pModPath, sizeof( pModPath ) ); V_snprintf( pFullPath, fullPathBufferSize, "%s%s", pModPath, pFileName ); return pFullPath; } #endif // not found return NULL; } #if IsGameConsole() bool CBaseFileSystem::GetPackFileInfoFromRelativePath( const char *pFileName, const char *pPathID, char *pPackPath, int nPackPathBufferSize, int64 &nPosition, int64 &nLength ) { CHECK_DOUBLE_SLASHES( pFileName ); #ifdef _PS3 // crush the filename to lowercase char lowercasedname[256]; V_strncpy( lowercasedname, pFileName, 255 ); V_strnlwr( lowercasedname, 255 ); pFileName = lowercasedname; #endif CSearchPathsIterator iter( this, &pFileName, pPathID, FILTER_CULLNONPACK ); for ( CSearchPath *pSearchPath = iter.GetFirst(); pSearchPath != NULL; pSearchPath = iter.GetNext() ) { int nIndex; int nLength32; CPackFile *pPack = pSearchPath->GetPackFile(); if ( pPack ) { if ( pPack->FindFile( pFileName, nIndex, nPosition, nLength32 ) ) { // Support all types as long as it is in a pack file. // Note that if it is in dvddev, prefetching will not really help (and may not be initialized correctly anyway). V_strncpy( pPackPath, pPack->m_ZipName.String(), nPackPathBufferSize ); nLength = nLength32; return true; } } } // not found pPackPath[0] = '\0'; nPosition = -1; nLength = -1; return false; } #endif const char *CBaseFileSystem::GetLocalPath( const char *pFileName, char *pLocalPath, int localPathBufferSize ) { CHECK_DOUBLE_SLASHES( pFileName ); return RelativePathToFullPath( pFileName, NULL, pLocalPath, localPathBufferSize ); } //----------------------------------------------------------------------------- // Returns true on success, otherwise false if it can't be resolved //----------------------------------------------------------------------------- bool CBaseFileSystem::FullPathToRelativePathEx( const char *pFullPath, const char *pPathId, char *pRelative, int nMaxLen ) { CHECK_DOUBLE_SLASHES( pFullPath ); int nInlen = Q_strlen( pFullPath ); if ( nInlen <= 0 ) { pRelative[ 0 ] = 0; return false; } Q_strncpy( pRelative, pFullPath, nMaxLen ); char pInPath[ MAX_FILEPATH ]; Q_strncpy( pInPath, pFullPath, sizeof( pInPath ) ); #ifdef _WIN32 Q_strlower( pInPath ); #endif Q_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 ]; Q_strncpy( pSearchBase, m_SearchPaths[i].GetPathString(), sizeof( pSearchBase ) ); #ifdef _WIN32 Q_strlower( pSearchBase ); #endif Q_FixSlashes( pSearchBase ); int nSearchLen = Q_strlen( pSearchBase ); if ( Q_strnicmp( pSearchBase, pInPath, nSearchLen ) ) continue; Q_strncpy( pRelative, &pInPath[ nSearchLen ], nMaxLen ); return true; } return false; } //----------------------------------------------------------------------------- // Obsolete version //----------------------------------------------------------------------------- bool CBaseFileSystem::FullPathToRelativePath( const char *pFullPath, char *pRelative, int nMaxLen ) { return FullPathToRelativePathEx( pFullPath, NULL, pRelative, nMaxLen ); } //----------------------------------------------------------------------------- // 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 || !IsGameConsole() ); // 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 ) { FileSystemWarning( FILESYSTEM_WARNING, "Unable to remove %s! (errno %x)\n", szScratchFileName, errno ); } } //----------------------------------------------------------------------------- // 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) { FileSystemWarning( 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( _PS3 ) ) || 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: Get VPK file IO stats for OGS reporting //----------------------------------------------------------------------------- void CBaseFileSystem::GetVPKFileStatisticsKV( KeyValues *pKV ) { if ( pKV && !V_strcmp( pKV->GetName(), "ForceVpkOnlyExtensions" ) ) { AUTO_LOCK( m_OpenedFilesMutex ); // this code will force VPK only extensions FOR_EACH_SUBKEY( pKV, kvExt ) { m_NonexistingFilesExtensions[ kvExt->GetName() ] = kvExt->GetBool(); } return; } for( int i = 0; i < m_VPKFiles.Count(); i++ ) { m_VPKFiles[i]->GetPackFileLoadErrorSummaryKV( pKV ); } } //----------------------------------------------------------------------------- // Purpose: // Input : level - // *fmt - // ... - //----------------------------------------------------------------------------- void CBaseFileSystem::FileSystemWarning( FileWarningLevel_t level, const char *fmt, ... ) { #ifdef _CERT return; #endif 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_bIsDvdDevPath = false; m_bIsLocalizedPath = false; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseFileSystem::CSearchPath::~CSearchPath( void ) { if ( m_pPackFile ) { m_pPackFile->Release(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetFirst() { if ( m_SearchPaths.Count() ) { m_visits.Reset(); m_iCurrent = -1; m_bExcluded = false; return GetNext(); } return &m_EmptySearchPath; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CBaseFileSystem::CSearchPath *CBaseFileSystem::CSearchPathsIterator::GetNext() { CSearchPath *pSearchPath = NULL; // PURPOSELY!!! split the 360 dvddev logic from the nominal (shipping) case // the logic is permuted slightly to do the right kind of filtering in dvddev or strict mode // 360 can optionally ignore and exclude a local search path in dvddev mode // excluding a local search path falls through to its cloned dvddev cache path // map paths are exempt from this exclusion logic if ( IsGameConsole() && ( m_DVDMode == DVDMODE_DEV || m_DVDMode == DVDMODE_DEV_VISTA ) && m_Filename[0] ) { 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_CULLLOCALIZED || m_PathTypeFilter == FILTER_CULLLOCALIZED_ANY ) && pSearchPath->m_bIsLocalizedPath ) { continue; } if ( pSearchPath->m_bIsDvdDevPath && !m_bExcluded ) { // the dvddev cache path is ignored until an exclusion has been matched continue; } else if ( !pSearchPath->m_bIsDvdDevPath && m_bExcluded ) { // an excluded file falls all the way through and can only be in the dvddev cache continue; } if ( CBaseFileSystem::FilterByPathID( pSearchPath, m_pathID ) ) continue; // prevent a duplicate costly check for exclusion if ( m_visits.MarkVisit( *pSearchPath ) ) continue; // a path gets ignored/skipped if it matches a dvddev exclusion, thus falling through to the dvddev cache path bool bIgnorePath = false; if ( !pSearchPath->m_bIsDvdDevPath ) { if ( !pSearchPath->GetPackFile() || !pSearchPath->GetPackFile()->m_bIsMapPath ) { char szExcludeFile[MAX_PATH]; char szFilename[MAX_PATH]; V_ComposeFileName( pSearchPath->GetPathString(), m_Filename, szFilename, sizeof( szFilename ) ); for ( int i = 0; i < m_ExcludeFilePaths.Count(); i++ ) { if ( g_pFullFileSystem->String( m_ExcludeFilePaths[i], szExcludeFile, sizeof( szExcludeFile ) ) ) { if ( !V_stricmp( szFilename, szExcludeFile ) ) { // the file was excluded and should not be fetched from this search path // the fallthrough will hit the dvddev cache bIgnorePath = true; m_bExcluded = true; break; } } } // only an exclusion match is allowed to inhibit the filtering on non-zip paths, i.e. loose files // ignoring loose files is critical to preventing pointless slow presence i/o, i.e. 99% files are expected to be in a zip if ( !pSearchPath->GetPackFile() && m_PathTypeFilter == FILTER_CULLNONPACK && !bIgnorePath ) { continue; } } } if ( !bIgnorePath ) { break; } } } else { // nominal behavior 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 || m_PathTypeFilter == FILTER_CULLLOCALIZED ) && !pSearchPath->GetPackFile() ) continue; if ( ( m_PathTypeFilter == FILTER_CULLLOCALIZED || m_PathTypeFilter == FILTER_CULLLOCALIZED_ANY ) && pSearchPath->m_bIsLocalizedPath ) { continue; } if ( CBaseFileSystem::FilterByPathID( pSearchPath, m_pathID ) ) continue; if ( !m_visits.MarkVisit( *pSearchPath ) ) break; } } if ( m_iCurrent < m_SearchPaths.Count() ) { return pSearchPath; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Load/unload a DLL //----------------------------------------------------------------------------- CSysModule *CBaseFileSystem::LoadModule( const char *pFileName, const char *pPathID, bool bValidatedDllOnly ) { CHECK_DOUBLE_SLASHES( pFileName ); bool bPathIsGameBin = false; bPathIsGameBin; // Touch the var for !Win64 build compiler warnings. LogFileAccess( pFileName ); if ( !pPathID ) { pPathID = "EXECUTABLE_PATH"; // default to the bin dir } else if ( IsPlatformWindowsPC64() ) { bPathIsGameBin = V_strcmp( "GAMEBIN", pPathID ) == 0; } #if defined(POSIX) && defined(PLATFORM_64BITS) bPathIsGameBin = V_strcmp( "GAMEBIN", pPathID ) == 0; #endif char tempPathID[ MAX_PATH ]; ParsePathID( pFileName, pPathID, tempPathID ); CUtlSymbol lookup = g_PathIDTable.AddString( pPathID ); // a pathID has been specified, find the first match in the path list int c = m_SearchPaths.Count(); for ( int i = 0; i < c; i++ ) { // pak files don't have modules if ( m_SearchPaths[i].GetPackFile() ) continue; if ( FilterByPathID( &m_SearchPaths[i], lookup ) ) continue; Q_snprintf( tempPathID, sizeof(tempPathID), "%s%s", m_SearchPaths[i].GetPathString(), pFileName ); // append the path to this dir. CSysModule *pModule = Sys_LoadModule( tempPathID ); if ( pModule ) { // we found the binary in one of our search paths return pModule; } else if ( IsPlatformWindowsPC64() && bPathIsGameBin ) { Q_snprintf( tempPathID, sizeof( tempPathID ), "%s%s%s%s", m_SearchPaths[ i ].GetPathString(), "x64", CORRECT_PATH_SEPARATOR_S, pFileName ); // append the path to this dir. pModule = Sys_LoadModule( tempPathID ); if ( pModule ) { // we found the binary in a 64-bit location. return pModule; } } #if defined(POSIX) && defined(PLATFORM_64BITS) else if ( bPathIsGameBin ) { #if defined(LINUX) const char* plat_dir = "linux64"; #else const char* plat_dir = "osx64"; #endif Q_snprintf( tempPathID, sizeof( tempPathID ), "%s%s%s%s", m_SearchPaths[ i ].GetPathString(), plat_dir, CORRECT_PATH_SEPARATOR_S, pFileName ); // append the path to this dir. pModule = Sys_LoadModule( tempPathID ); if ( pModule ) { // we found the binary in a 64-bit location. return pModule; } } #endif } // couldn't load it from any of the search paths, let LoadLibrary try return Sys_LoadModule( pFileName ); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CBaseFileSystem::UnloadModule( CSysModule *pModule ) { Sys_UnloadModule( pModule ); } //----------------------------------------------------------------------------- // Purpose: Adds a filesystem logging function //----------------------------------------------------------------------------- void CBaseFileSystem::AddLoggingFunc( FileSystemLoggingFunc_t logFunc ) { Assert(!m_LogFuncs.IsValidIndex(m_LogFuncs.Find(logFunc))); m_LogFuncs.AddToTail(logFunc); } //----------------------------------------------------------------------------- // Purpose: Removes a filesystem logging function //----------------------------------------------------------------------------- void CBaseFileSystem::RemoveLoggingFunc( FileSystemLoggingFunc_t logFunc ) { m_LogFuncs.FindAndRemove(logFunc); } //----------------------------------------------------------------------------- // Make sure that slashes are of the right kind and that there is a slash at the // end of the filename. // WARNING!!: assumes that you have an extra byte allocated in the case that you need // a slash at the end. //----------------------------------------------------------------------------- static void AddSeperatorAndFixPath( char *str ) { char *lastChar = &str[strlen( str ) - 1]; if( *lastChar != CORRECT_PATH_SEPARATOR && *lastChar != INCORRECT_PATH_SEPARATOR ) { lastChar[1] = CORRECT_PATH_SEPARATOR; lastChar[2] = '\0'; } Q_FixSlashes( str ); if ( IsGameConsole() ) { // 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 ); } bool CBaseFileSystem::IsFileInReadOnlySearchPath(const char *pPath, const char *pathID) { //TODO: implementme! return false; } #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 V_snwprintf( buf, ( bufSizeInBytes / sizeof( wchar_t ) ) - 1, L"%s File", V_strupr( ext ) ); // Matches what Windows does #else V_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; } //----------------------------------------------------------------------------- // This is a DVDDEV misery that needs to convolve loose filenames that exceed the 42 character limit. // This is not for files inside zip files or in any other context. //----------------------------------------------------------------------------- bool CBaseFileSystem::FixupFATXFilename( const char *pFilename, char *pOutFilename, int nOutSize ) { if ( !IsX360() || m_DVDMode != DVDMODE_DEV ) { // xbox dvdev mode only return false; } // back up to isolate the filename const char *pFilenameStart = V_UnqualifiedFileName( pFilename ); int nFilenameLen = strlen( pFilenameStart ); if ( nFilenameLen <= 42 ) { // already compliant return false; } // only files within the dvddev cache or shadercache would be convolved (via vxconsole) if ( !V_stristr( pFilename, "dvddev" ) ) { // not in any of the vxconsole caches, ignore return false; } // deterministically generate a unique suffix // this is the same operation that vxconsole does before pushing the files // good enough to ensure similar excessive filenames in the same dir won't collide char szFixedFilename[MAX_PATH]; V_strncpy( szFixedFilename, pFilenameStart, sizeof( szFixedFilename ) ); strlwr( szFixedFilename ); CRC32_t crc32 = CRC32_ProcessSingleBuffer( szFixedFilename, nFilenameLen ); crc32 %= 10000; char szUnique[32]; sprintf( szUnique, "~%u", ( uint ) crc32 ); szFixedFilename[42 - strlen( szUnique )] = '\0'; V_strncat( szFixedFilename, szUnique, sizeof( szFixedFilename ) ); if ( pFilenameStart != pFilename ) { // get the initial path (and the slash) V_strncpy( pOutFilename, pFilename, MIN( nOutSize, pFilenameStart - pFilename + 1 ) ); } // tack on the fixed filename V_strncat( pOutFilename, szFixedFilename, nOutSize ); return true; } bool CBaseFileSystem::GetStringFromKVPool( CRC32_t poolKey, unsigned int key, char *pOutBuff, int buflen ) { // xbox only if ( !IsGameConsole() ) { Assert( 0 ); return false; } AUTO_LOCK( m_SearchPathsMutex ); 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 ) { if ( poolKey == pPackFile->GetKVPoolKey() ) { return pPackFile->GetStringFromKVPool( key, pOutBuff, buflen ); } } } return false; } //----------------------------------------------------------------------------- // Purpose: Constructs a file handle // Input : base file system handle // Output : //----------------------------------------------------------------------------- CFileHandle::CFileHandle( CBaseFileSystem* fs ) { Init( fs ); } CFileHandle::~CFileHandle() { Assert( IsValid() ); delete[] m_pszTrueFileName; 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; m_pszTrueFileName = 0; } 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 { return -1; } } bool CFileHandle::IsOK() { #ifdef SUPPORT_VPK if ( m_VPKHandle ) return true; #endif if ( m_pFile ) { return ( IsValid() && m_fs->FS_ferror( m_pFile ) == 0 ); } else if ( m_pPackFileHandle ) { return IsValid(); } m_fs->FileSystemWarning( 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() ); #ifdef SUPPORT_VPK if ( m_VPKHandle ) { if ( nDestSize >= 0 ) nLength = MIN( nLength, nDestSize ); int nLengthRead = m_VPKHandle.Read( pBuffer, nLength ); m_fs->m_FileTracker2.NotePackFileRead( m_VPKHandle, pBuffer, nLength ); return nLengthRead; } #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 ); } return 0; } int CFileHandle::Write( const void* pBuffer, int nLength ) { Assert( IsValid() ); if ( !m_pFile ) { m_fs->FileSystemWarning( 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() ); #ifdef SUPPORT_VPK 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( (int)nOffset, nWhence ); } return -1; } int CFileHandle::Tell() { Assert( IsValid() ); #ifdef SUPPORT_VPK 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(); } return -1; } int CFileHandle::Size() { Assert( IsValid() ); int nReturnedSize = -1; #ifdef SUPPORT_VPK if ( m_VPKHandle ) { return m_VPKHandle.m_nFileSize; } #endif if ( m_pFile ) { nReturnedSize = m_nLength; } else if ( m_pPackFileHandle ) { nReturnedSize = m_pPackFileHandle->Size(); } return nReturnedSize; } int64 CFileHandle::AbsoluteBaseOffset() { Assert( IsValid() ); if ( m_pFile ) { return 0; } else { return m_pPackFileHandle->AbsoluteBaseOffset(); } } bool CFileHandle::EndOfFile() { Assert( IsValid() ); return ( Tell() >= Size() ); } #ifdef _GAMECONSOLE static int s_DLC_Numeric_Supported[] = { 0, 0, 0, 0, 0, // 5 0, 0, 0, 0, 0, // 10 0, 0, 0, 0, 0, // 15 0, 0, 0, 0, 20, // 20 0, 0, 0, 0, 0, // 25 0, 0, 0, 0, 0, // 30 }; static bool IsDlcNumericSupported( int iDLC ) { #if defined( CSTRIKE15 ) return ( iDLC >= 1 && iDLC < 31 ); #else // CSTRIKE15 for ( int k = 0; k < ARRAYSIZE( s_DLC_Numeric_Supported ); ++ k ) if ( s_DLC_Numeric_Supported[k] == iDLC ) return true; return false; #endif // CSTRIKE15 } #else static bool IsDlcNumericSupported( int iDLC ) { return false; } #endif bool CBaseFileSystem::DiscoverDLC( int iController ) { #if !defined( _X360 ) return false; #else DevMsg( "Discovering DLC...\n" ); // clear prior corrupt results m_CorruptDLC.Purge(); CUtlSortVector< DLCContent_t, CDLCLess > dlcResults; // development path supports locally mounting the dlc when not using XLAST or XBL // this is development only, retail runtime would never have command line dictated DLC // command line trumps ANY discovery so we can have a desired exact DLC testing state bool bUsingCommandLineDLC = false; const char *pCmdLine = CommandLine()->GetCmdLine(); while( pCmdLine ) { pCmdLine = V_stristr( pCmdLine, "-dlc" ); if ( !pCmdLine ) break; bUsingCommandLineDLC = true; int nDLC = atoi( pCmdLine + 4 ); if ( nDLC == 0 ) { // malformed command line dev args DevWarning( "Bad argument: %s\n", pCmdLine ); break; } // get the required dlcflags -dlc 0x // user must supply as the lower word identifies control bits char const *szDlcNflags = CommandLine()->ParmValue( CFmtStr( "-dlc%d", nDLC ), "0x0" ); unsigned int nDLCFlags = 0; if ( 1 != sscanf( szDlcNflags, "0x%x", &nDLCFlags ) ) { DevWarning( "Bad DLC flags: -dlc%d %s\n", nDLC, szDlcNflags ); break; } // form the license mask // identify development DLCN as N.0 in the upper MSW, the retail version is at least N.1 // the LSW are the control flags unsigned int nLicenseMask = ( ( nDLC & 0xFF ) << 24 ) | ( nDLCFlags & 0xFFFF ); DLCContent_t dlcContent; // should be part of test image // the real DLC encodes descriptive data we can only spoof V_strcpy( dlcContent.m_szVolume, "D" ); V_strtowcs( CFmtStr( "DLC%d Dev Name (0x%8.8x)", nDLC, nLicenseMask ), -1, dlcContent.m_ContentData.szDisplayName, sizeof( dlcContent.m_ContentData.szDisplayName ) ); dlcContent.m_nController = 0; dlcContent.m_LicenseMask = nLicenseMask; dlcContent.m_bMounted = ( nLicenseMask & DLCFLAGS_PRESENCE_ONLY ) != 0; dlcResults.Insert( dlcContent ); // next arg pCmdLine += 4; } if ( bUsingCommandLineDLC ) { for ( int i = 0; i < dlcResults.Count(); i++ ) { // only care about new unique occurring DLC // skip over any DLC that we have already discovered bool bFound = false; for ( int j = 0; j < m_DLCContents.Count() && !bFound; j++ ) { bFound = ( DLC_LICENSE_ID( m_DLCContents[j].m_LicenseMask ) == DLC_LICENSE_ID( dlcResults[i].m_LicenseMask ) ); } if ( !bFound ) { m_DLCContents.Insert( dlcResults[i] ); } } } else { CUtlMemory< BYTE > buffer; DWORD nNumItems = 0; BYTE *pBuffer = NULL; // find additional content // must have a signed in user, otherwise cannot find enhanced content DWORD nBufferSize; HANDLE hEnumerator; if ( XContentCreateEnumerator( iController, XCONTENTDEVICE_ANY, XCONTENTTYPE_MARKETPLACE, 0, 100, &nBufferSize, &hEnumerator ) == ERROR_SUCCESS ) { if ( nBufferSize ) { // get a buffer to capture enumeration results buffer.EnsureCapacity( nBufferSize ); pBuffer = buffer.Base(); if ( XEnumerate( hEnumerator, pBuffer, nBufferSize, &nNumItems, NULL ) != ERROR_SUCCESS ) { nNumItems = 0; } } } ::CloseHandle( hEnumerator ); if ( !nNumItems ) { return false; } char szFilename[XCONTENT_MAX_FILENAME_LENGTH+1]; szFilename[XCONTENT_MAX_FILENAME_LENGTH] = 0; XCONTENT_DATA *pContentData; // determine all our dlc content for ( unsigned int i = 0; i < nNumItems; i++ ) { // filenames are encoded encryptions, useless to anything but the system pContentData = (XCONTENT_DATA *)pBuffer + i; V_memcpy( szFilename, pContentData->szFileName, XCONTENT_MAX_FILENAME_LENGTH ); // must mount to get license mask // license mask is ONLY available for content downloaded through XBL (not mounted locally) DWORD licenseMask = 0; DWORD dwStatus = XContentCreate( iController, "DLC", pContentData, XCONTENTFLAG_OPENEXISTING, NULL, &licenseMask, NULL ); if ( dwStatus != ERROR_SUCCESS ) { // assume corrupt DLCCorrupt_t dlcCorrupt; dlcCorrupt.m_ContentData = *pContentData; m_CorruptDLC.AddToTail( dlcCorrupt ); continue; } // always unmount, highest version will get re-mounted // as we might rev the DLC without a TU XContentClose( "DLC", NULL ); // DLC N int nDlcNumericId = DLC_LICENSE_ID( licenseMask ); bool bDlcIsSupported = IsDlcNumericSupported( nDlcNumericId ); // only consider DLC with a valid license mask // we DONT/CANT support install-test-locally DLC because DLC lacks license mask to decode if ( nDlcNumericId && bDlcIsSupported ) { // insert into ascending sorted list, ensures dlc1..dlcN order DLCContent_t dlcContent; dlcContent.m_ContentData = *pContentData; dlcContent.m_LicenseMask = licenseMask; dlcContent.m_nController = iController; dlcContent.m_szVolume[0] = '\0'; dlcContent.m_bMounted = false; dlcResults.Insert( dlcContent ); } else { // assume corrupt DLCCorrupt_t dlcCorrupt; dlcCorrupt.m_ContentData = *pContentData; m_CorruptDLC.AddToTail( dlcCorrupt ); continue; } } // mount the highest version of each type // sorted results order guarantees ascending type/version order for ( int i = 0; i < dlcResults.Count(); ) { // iterate ascending list determine highest version of matching type DWORD dlcType = DLC_LICENSE_ID( dlcResults[i].m_LicenseMask ); int nBest = i; for ( int j = i+1; j < dlcResults.Count(); j++ ) { if ( dlcType != DLC_LICENSE_ID( dlcResults[j].m_LicenseMask ) ) { // wrong one, due to sort order, no more of this type break; } nBest = j; } // only care about unique DLC types, can't handle newly discovered sub versions of the same type // iterate for a match bool bFound = false; for ( int j = 0; j < m_DLCContents.Count() && !bFound; j++ ) { bFound = ( DLC_LICENSE_ID( m_DLCContents[j].m_LicenseMask ) == DLC_LICENSE_ID( dlcResults[nBest].m_LicenseMask ) ); } if ( !bFound ) { // mount the highest version of each type only DLCContent_t dlcContent = dlcResults[nBest]; V_strcpy( dlcContent.m_szVolume, CFmtStr( "DLC%d", DLC_LICENSE_ID( dlcContent.m_LicenseMask ) ) ); DWORD dwResults; if ( dlcContent.m_LicenseMask & DLCFLAGS_PRESENCE_ONLY ) { // we have it, that's all that is required // what's inside it is never acessed dwResults = ERROR_SUCCESS; dlcContent.m_bMounted = true; } else { dwResults = XContentCreate( iController, dlcContent.m_szVolume, &dlcContent.m_ContentData, XCONTENTFLAG_OPENEXISTING, NULL, NULL, NULL ); } if ( dwResults == ERROR_SUCCESS ) { // already handled corrupt errors, so expecting success m_DLCContents.Insert( dlcContent ); } } // continue with next dlc type i = nBest + 1; } } PrintDLCInfo(); return ( m_DLCContents.Count() != 0 ); #endif } // Returns the number of DLC components found int CBaseFileSystem::IsAnyDLCPresent( bool *pbDLCSearchPathMounted ) { if ( !IsX360() ) { return 0; } if ( pbDLCSearchPathMounted ) { // discovered DLC may have added new DLC that have not mounted their search path *pbDLCSearchPathMounted = true; for ( int i = 0; i < m_DLCContents.Count(); i++ ) { if ( !m_DLCContents[i].m_bMounted ) { // set caller's query to false so they know to trigger the AddDLCSearchPaths() *pbDLCSearchPathMounted = false; break; } } } return m_DLCContents.Count(); } bool CBaseFileSystem::GetAnyDLCInfo( int iDLC, unsigned int *pLicenseMask, wchar_t *pTitleBuff, int nOutTitleSize ) { if ( !IsX360() ) { return false; } if ( !m_DLCContents.IsValidIndex( iDLC ) ) { return false; } if ( pLicenseMask ) { *pLicenseMask = m_DLCContents[iDLC].m_LicenseMask; } if ( pTitleBuff ) { V_wcsncpy( pTitleBuff, m_DLCContents[iDLC].m_ContentData.szDisplayName, nOutTitleSize ); } return true; } int CBaseFileSystem::IsAnyCorruptDLC() { if ( !IsX360() ) { return 0; } return m_CorruptDLC.Count(); } bool CBaseFileSystem::GetAnyCorruptDLCInfo( int iCorruptDLC, wchar_t *pTitleBuff, int nOutTitleSize ) { if ( !IsX360() ) { return false; } if ( !m_CorruptDLC.IsValidIndex( iCorruptDLC ) ) { return false; } if ( pTitleBuff ) { V_wcsncpy( pTitleBuff, m_CorruptDLC[iCorruptDLC].m_ContentData.szDisplayName, nOutTitleSize ); } return true; } bool CBaseFileSystem::IsSpecificDLCPresent( unsigned int nDLCPackage ) { for( int i = 0; i < m_DLCContents.Count(); i++ ) { if ( m_DLCContents[i].m_bMounted && ( DLC_LICENSE_ID( m_DLCContents[i].m_LicenseMask ) == nDLCPackage ) ) { return true; } } return false; } bool CBaseFileSystem::AddDLCSearchPaths() { if ( !IsX360() ) { return false; } if ( !IsAnyDLCPresent() ) { return false; } AsyncFinishAll(); // have to add the DLC to achieve desired SP order DLCN..DLC1 for ( int iDLC = 0; iDLC < m_DLCContents.Count(); iDLC++ ) { if ( m_DLCContents[iDLC].m_bMounted ) { // already procesed this on a prior add, skip now // only care about newly discovered unique DLC continue; } unsigned int nDLCType = DLC_LICENSE_ID( m_DLCContents[iDLC].m_LicenseMask ); char szDLCPath[MAX_PATH]; V_snprintf( szDLCPath, sizeof( szDLCPath ), "%s:\\csgo_dlc%d", m_DLCContents[iDLC].m_szVolume, nDLCType ); char szDLCLanguagePath[MAX_PATH]; const char *pLanguageString = NULL; if ( XBX_IsAudioLocalized() ) { pLanguageString = XBX_GetLanguageString(); // paranoid check if ( !V_stricmp( pLanguageString, "english" ) ) { // bad, system is confused pLanguageString = NULL; } } if ( pLanguageString ) { V_strncpy( szDLCLanguagePath, CFmtStr( "%s_%s", szDLCPath, pLanguageString ), sizeof( szDLCLanguagePath ) ); } const char *pathTargets[] = { "GAME", "MOD" }; for ( int i = 0; i < ARRAYSIZE( pathTargets ); i++ ) { // inject dlc path for each target path id, once at top for ( int j = 0; j < m_SearchPaths.Count(); j++ ) { if ( m_SearchPaths[j].GetPackFile() && m_SearchPaths[j].GetPackFile()->m_bIsMapPath ) { // skip over any map based search paths continue; } if ( V_stricmp( m_SearchPaths[j].GetPathIDString(), pathTargets[i] ) ) { // skip over any path IDs that we are not interested in continue; } // always after update if present int iIndex = j; if ( V_stristr( m_SearchPaths[j].GetPathString(), "update" ) ) { // add after update iIndex++; } // scan to find any existing DLC, need to be either before or after bool bNoAdd = false; for ( int k = iIndex; k < m_SearchPaths.Count(); k++ ) { const char *pCurrentPathString = m_SearchPaths[k].GetPathString(); const char *pDLCSuffix = V_stristr( pCurrentPathString, "_dlc" ); if ( !pDLCSuffix ) { break; } else { unsigned int nExistingDLC = atoi( pDLCSuffix + 4 ); if ( !nExistingDLC ) { // uh-oh, can't determine what DLC this is, can't add any other DevWarning( "ERROR! Skipping DLC Mount, malformed DLC search path found: %s\n", pCurrentPathString ); bNoAdd = true; break; } if ( nDLCType > nExistingDLC ) { // stop, we need to be prior to this one break; } else if ( nDLCType == nExistingDLC ) { // uh-oh, we don't add DLCs of the same type bNoAdd = true; break; } else { // the DLC we want to add is smaller, it needs to at least go after this one iIndex = k + 1; } } } if ( bNoAdd ) { // not adding this DLC break; } if ( pLanguageString ) { // only add dlc language search path, if DLC has it struct _stat buf; if ( FS_stat( szDLCLanguagePath, &buf ) != -1 ) { AddSearchPathInternal( szDLCLanguagePath, pathTargets[i], PATH_ADD_TO_TAIL_ATINDEX, true, iIndex ); iIndex++; } } AddSearchPathInternal( szDLCPath, pathTargets[i], PATH_ADD_TO_TAIL_ATINDEX, true, iIndex ); break; } } // never multiply mount or re-mount again m_DLCContents[iDLC].m_bMounted = true; } PrintSearchPaths(); return true; } void CBaseFileSystem::PrintDLCInfo() { if ( IsX360() ) { if ( m_DLCContents.Count() ) { Msg( "\nDLC:\n" ); for ( int i = 0; i < m_DLCContents.Count(); i++ ) { char szTitle[MAX_PATH]; V_UnicodeToUTF8( m_DLCContents[i].m_ContentData.szDisplayName, szTitle, sizeof( szTitle ) ); Msg( "DLC Found: '%s' License: 0x%8.8x\n", szTitle, m_DLCContents[i].m_LicenseMask ); } } if ( m_CorruptDLC.Count() ) { Msg( "\nCorrupt DLC:\n" ); for ( int i = 0; i < m_CorruptDLC.Count(); i++ ) { char szTitle[MAX_PATH]; V_UnicodeToUTF8( m_CorruptDLC[i].m_ContentData.szDisplayName, szTitle, sizeof( szTitle ) ); Msg( "Corrupt: '%s'\n", szTitle ); } } } } bool CBaseFileSystem::AddXLSPUpdateSearchPath( const void *pData, int nSize ) { if ( IsPC() ) { return false; } const char *targetPathIDs[] = { "PLATFORM", "GAME", "MOD" }; for ( int i = 0; i GetPathIDString(), pPathIDString ) && !V_stricmp( pSearchPath->GetPathString(), "u:\\xlsppatch\\" ) ) { bFoundSearchPath = true; break; } } if ( !bFoundSearchPath && g_XLSPPatchZipBuffer.TellPut() ) { // find first matching pathID, xlsppatch will be placed before it int nIndex = 0; for ( int j = 0; j < m_SearchPaths.Count(); j++ ) { CSearchPath *pSearchPath = &m_SearchPaths[j]; if ( !V_stricmp( pSearchPath->GetPathIDString(), pPathIDString ) ) { nIndex = j; break; } } // have xlsppatch blob nIndex = m_SearchPaths.InsertBefore( nIndex ); CSearchPath *sp = &m_SearchPaths[ nIndex ]; // the path and filename are fake but reserved, they denote this binary resident blob // these names ensure they get ignored during post hdd install sp fixup const char *pFullpath = "u:\\xlsppatch\\xlsppatch" PLATFORM_EXT ".zip"; sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathIDString ), -1 ); sp->m_storeId = g_iNextSearchPathID++; sp->SetPath( g_PathIDTable.AddString( "u:\\xlsppatch\\" ) ); // find and alias existing reference CPackFile *pf = NULL; for ( int iPackFile = 0; iPackFile < m_ZipFiles.Count(); iPackFile++ ) { if ( !Q_stricmp( m_ZipFiles[iPackFile]->m_ZipName.Get(), pFullpath ) ) { // found pf = m_ZipFiles[iPackFile]; sp->SetPackFile( pf ); pf->AddRef(); break; } } if ( !pf ) { // there is no 'file', point to the embedded section instead pf = new CZipPackFile( this, g_XLSPPatchZipBuffer.Base() ); pf->SetPath( sp->GetPath() ); pf->m_bIsExcluded = false; pf->m_ZipName = pFullpath; m_ZipFiles.AddToTail( pf ); sp->SetPackFile( pf ); pf->m_lPackFileTime = 0; pf->m_hPackFileHandleFS = NULL; if ( !pf->Prepare( g_XLSPPatchZipBuffer.TellPut() ) ) { bXLSPPatchValid = false; break; } } } } return bXLSPPatchValid; } void CBaseFileSystem::MarkLocalizedPath( CSearchPath *sp ) { // game console only for now #ifdef _GAMECONSOLE const char *pPath = g_PathIDTable.String( sp->GetPath() ); if ( !pPath || !*pPath ) return; if ( !XBX_IsAudioLocalized() ) { return; } const char *pLanguage = XBX_GetLanguageString(); if ( !pLanguage || !V_stricmp( pLanguage, "english" ) ) { return; } int languagelen = V_strlen( pLanguage ); int pathlen = V_strlen( pPath ); // ignore trailing slash if ( pPath[ pathlen - 1 ] == '\\' || pPath[ pathlen - 1 ] == '/' ) { --pathlen; } if ( pathlen > languagelen && V_strnicmp( pPath + pathlen - languagelen, pLanguage, languagelen ) == 0 ) { sp->m_bIsLocalizedPath = true; } #endif } #ifdef SUPPORT_IODELAY_MONITORING class CIODelayAlarmThread : public CThread { public: CIODelayAlarmThread( CBaseFileSystem *pFileSystem ); void WakeUp( void ); CBaseFileSystem *m_pFileSystem; CThreadEvent m_hThreadEvent; volatile bool m_bThreadShouldExit; // CThread Overrides virtual int Run( void ); }; CIODelayAlarmThread::CIODelayAlarmThread( CBaseFileSystem *pFileSystem ) { m_pFileSystem = pFileSystem; m_bThreadShouldExit = false; } void CIODelayAlarmThread::WakeUp( void ) { m_hThreadEvent.Set(); } int CIODelayAlarmThread::Run( void ) { while( ! m_bThreadShouldExit ) { uint32 nWaitTime = 1000; float flCurTimeout = m_pFileSystem->m_flDelayLimit; if ( flCurTimeout > 0. ) { nWaitTime = ( uint32 )( 1000.0 * flCurTimeout ); m_hThreadEvent.Wait( nWaitTime ); } // check for overflow float flCurTime = Plat_FloatTime(); if ( flCurTime - m_pFileSystem->m_flLastIOTime > m_pFileSystem->m_flDelayLimit ) { Warning( " %f elapsed w/o i/o\n", flCurTime - m_pFileSystem->m_flLastIOTime ); DebuggerBreakIfDebugging(); m_pFileSystem->m_flLastIOTime = MAX( Plat_FloatTime(), m_pFileSystem->m_flLastIOTime ); } } return 0; } #endif // SUPPORT_IODELAY_MONITORING void CBaseFileSystem::SetIODelayAlarm( float flTime ) { #ifdef SUPPORT_IODELAY_MONITORING m_flDelayLimit = flTime; if ( m_flDelayLimit > 0. ) { m_flDelayLimit = flTime; if ( ! m_pDelayThread ) { m_pDelayThread = new CIODelayAlarmThread( this ); m_pDelayThread->Start(); } m_pDelayThread->WakeUp(); } #endif } IIoStats *CBaseFileSystem::GetIoStats() { #ifndef _CERT return &s_IoStats; #else return NULL; #endif } CON_COMMAND( fs_dump_open_duplicate_times, "Set fs_report_long_reads 1 before loading to use this. Prints a list of files that were opened more than once and ~how long was spent reading from them." ) { float flTotalTime = 0.0f, flAccumulatedMilliseconds; AUTO_LOCK( g_FileOpenDuplicateTimesMutex ); for ( int nFileOpenDuplicate = 0; nFileOpenDuplicate< g_FileOpenDuplicateTimes.Count(); nFileOpenDuplicate++ ) { FileOpenDuplicateTime_t *pFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ]; if ( pFileOpenDuplicate ) { if ( pFileOpenDuplicate->m_nLoadCount > 1 ) { flTotalTime += pFileOpenDuplicate->m_flAccumulatedMicroSeconds; flAccumulatedMilliseconds = pFileOpenDuplicate->m_flAccumulatedMicroSeconds / 1000.0f; DevMsg( "Times Opened: %3i\t\tAccumulated Time: %10.5fms\t\tAverage Time per Open: %13.8fms\t\tFile: %s\n", pFileOpenDuplicate->m_nLoadCount, flAccumulatedMilliseconds, ( flAccumulatedMilliseconds / pFileOpenDuplicate->m_nLoadCount ), pFileOpenDuplicate->m_szName ); } } } DevMsg( "Total Seconds: %.5f\n", flTotalTime / 1000000.0f ); } CON_COMMAND( fs_clear_open_duplicate_times, "Clear the list of files that have been opened." ) { AUTO_LOCK( g_FileOpenDuplicateTimesMutex ); for ( int nFileOpenDuplicate = 0; nFileOpenDuplicate< g_FileOpenDuplicateTimes.Count(); nFileOpenDuplicate++ ) { FileOpenDuplicateTime_t *pFileOpenDuplicate = g_FileOpenDuplicateTimes[ nFileOpenDuplicate ]; delete pFileOpenDuplicate; g_FileOpenDuplicateTimes[ nFileOpenDuplicate ] = NULL; } g_FileOpenDuplicateTimes.RemoveAll(); } #if IsPlatformPS3() class CFiosAllocator : public cell::fios::allocator { public: void* Allocate(uint32_t size, uint32_t flags, const char* pFile, int line) { (void) pFile; (void) line; return memalign(FIOS_ALIGNMENT_FROM_MEMFLAGS(flags), size); } void Deallocate(void* pMemory, uint32_t flags, const char* pFile, int line) { (void) flags; (void) pFile; (void) line; free(pMemory); } void* Reallocate(void* pMemory, uint32_t newSize, uint32_t flags, const char* pFile, int line) { (void) pMemory; (void) newSize; (void) flags; (void) pFile; (void) line; return NULL; /* fios does not use Reallocate */ } }; // Configure FIOS, allows prefetching of whole files of files within pack files. class CFiosConfiguration { public: CFiosConfiguration(); ~CFiosConfiguration(); bool Setup(); bool Teardown(); void FlushCache(); // These methods are not thread safe. bool PrefetchFile( const char * pFileName, int nPriority, bool bPersist ); bool PrefetchFile( const char * pFileName, int nPriority, bool bPersist, int64 nOffset, int64 nSize ); void SuspendPrefetches( const char *pWhy ); void ResumePrefetches( const char *pWhy ); void PrintPrefetches(); void PrintPrefetch( int nSlot ); int ClearFinishedPrefetches(); const char * GetPathCached() const; void CancelAllPrefetches(); bool IsPrefetchingDone(); private: void CancelAndRespawnPrefetches( bool bPersistent ); int ResumePrefetchesToZero(); void SuspendPrefetchesToN( int nNumberOfSuspends ); CFiosAllocator m_Allocator; cell::fios::media * m_SysCacheMedia; cell::fios::scheduler * m_SchedulerForHDD; cell::fios::schedulercache * m_SchedulerCache; cell::fios::media * m_DevBdvdMedia; cell::fios::scheduler * m_MainScheduler; class CPrefetchInfo { public: CPrefetchInfo( cell::fios::op * pOp, const char * pFileName, int64 nOffset, int64 nSize, const cell::fios::opattr_t & opAttributes, bool bPersistent ) : m_OpAttributes( opAttributes ), m_nOffset( nOffset ), m_nSize( nSize ), m_pOp( pOp ), m_bPersitent( bPersistent ) { Assert( pOp != NULL ); V_strncpy( m_FileName, pFileName, sizeof( m_FileName ) ); } // Clone but use a different cell::fios::op *. CPrefetchInfo( cell::fios::op * pOp, const CPrefetchInfo & other ) : m_OpAttributes( other.m_OpAttributes ), m_nOffset( other.m_nOffset ), m_nSize( other.m_nSize ), m_pOp( pOp ), m_bPersitent( other.m_bPersitent ) { Assert( pOp != NULL ); V_strncpy( m_FileName, other.m_FileName, sizeof( m_FileName ) ); } cell::fios::op * GetOp() const { return m_pOp; } const char * GetFileName() const { return m_FileName; } int64 GetOffset() const { return m_nOffset; } int64 GetSize() const { return m_nSize; } const cell::fios::opattr_t & GetOpAttributes() const { return m_OpAttributes; } bool IsPersistent() const { return m_bPersitent; } private: char m_FileName[256]; cell::fios::opattr_t m_OpAttributes; int64 m_nOffset; int64 m_nSize; cell::fios::op * m_pOp; bool m_bPersitent; }; CUtlVector< CPrefetchInfo * > m_Prefetches; }; CFiosConfiguration g_FiosConfiguration; bool SetupFios() { if ( fs_fios_enabled.GetBool() ) { return g_FiosConfiguration.Setup(); } else { return false; } } bool TeardownFios() { if ( fs_fios_enabled.GetBool() ) { return g_FiosConfiguration.Teardown(); } else { return false; } } const char * CFiosConfiguration::GetPathCached() const { return g_pPS3PathInfo->GameImagePath(); } CFiosConfiguration::CFiosConfiguration() : m_Allocator(), m_SysCacheMedia( NULL ), m_SchedulerForHDD( NULL ), m_SchedulerCache( NULL ), m_DevBdvdMedia( NULL ), m_MainScheduler( NULL ), m_Prefetches( 4, 32 ) // 32 allows us to prefetch 16 high priority prefetches at the same time as one low priority prefetch // With the low priority being canceled and restarted every time. { // Do nothing... } CFiosConfiguration::~CFiosConfiguration() { // Do nothing... Assert( m_Prefetches.Count() == 0 ); } // SONY overrides new with the types ps3media and schedulercache. #include "memdbgoff.h" bool CFiosConfiguration::Setup() { cell::fios::fios_parameters parameters = FIOS_PARAMETERS_INITIALIZER; parameters.pAllocator = &m_Allocator; parameters.pLargeMemcpy = 0; // Use memcpy parameters.pVprintf = 0; // Use vprintf // Enable this only to see if the cache is working as expected //parameters.profiling = cell::fios::kProfileOps | cell::fios::kProfileCache; cell::fios::FIOSInit( ¶meters ); m_DevBdvdMedia = new cell::fios::ps3media( GetPathCached() ); if ( m_DevBdvdMedia == NULL ) { Warning( "[CFiosConfiguration::Setup] Can't allocate dev bdvd media.\n" ); return false; } m_SysCacheMedia = new cell::fios::ps3media( g_pPS3PathInfo->SystemCachePath() ); if ( m_SysCacheMedia == NULL ) { Warning( "[CFiosConfiguration::Setup] Can't allocate system cache media.\n" ); return false; } m_SchedulerForHDD = cell::fios::scheduler::createSchedulerForMedia( m_SysCacheMedia ); if ( m_SchedulerForHDD == NULL ) { Warning( "[CFiosConfiguration::Setup] Can't create scheduler for media.\n" ); return false; } // Use SONY-like configuration: const int BLOCK_SIZE = 1024 * 1024; // 1 Mb blocks. const int NUMBER_OF_BLOCKS = 1800; // 1800 blocks mean that the cache is going to be 1.8 Gb big. // We leave 237 MB for the other usages. // In theory, without counting other systems, we can go up to 2037 blocks. // To remove one potential cause for a CERT issue, let's remove the usage of this flag (#if 0) // This is a speculative fix for crashes that would not exist for the end user (although SONY states that we should not ship with this setting turned on). #if defined(_CERT) && 0 const bool bCheckModification = false; // No need to scan for modifications at release time (faster) #else const bool bCheckModification = true; // But we want to double-check during development to avoid stale data #endif m_SchedulerCache = new cell::fios::schedulercache( m_DevBdvdMedia, m_SchedulerForHDD, "FILECACHE", // Cache directory 0x0123456789ABCDEFll, // diskId. For one disk only title, we can hard-code it true, // Use single-file as it is faster. bCheckModification, NUMBER_OF_BLOCKS, BLOCK_SIZE); if ( m_SchedulerCache == NULL ) { Warning( "[CFiosConfiguration::Setup] Can't allocate m_SchedulerCache.\n" ); return false; } m_MainScheduler = cell::fios::scheduler::createSchedulerForMedia( m_SchedulerCache ); if (m_MainScheduler == NULL ) { Warning( "[CFiosConfiguration::Setup] Can't allocate m_MainScheduler.\n" ); return false; } // Starts the prefetches 0.5 second after no disk-usage. // This should make the IO faster for normal cases, prefetch will start when sounds / animations have not been needed IO for a while. // Except it creates huge IO issues. These functions are not reliable at all, created deadlocks, etc... Re-enable it on the PS4. // m_MainScheduler->setPrefetchDelay( cell::fios::FIOSMillisecondsToAbstime( 500 ) ); // m_MainScheduler->setPrefetchThrottle( cell::fios::FIOSMillisecondsToAbstime( 100 ) ); // One prefetch command every 3 game frames // This will make the prefetches two times slower. m_MainScheduler->setDefault(); // Currently we are not creating a RAM cache (this could speed up some small reads). return true; } bool CFiosConfiguration::Teardown() { // Tearing down, we need to make sure suspend state is reset (FIOS limitation). ResumePrefetchesToZero(); // Ops are taken cared of when we destroy the scheduler, but here we want to make sure PrefetchInfo are also deallocated correctly CancelAllPrefetches(); // We cancel all outstanding operations m_MainScheduler->cancelAllOps(); // Wait for the main scheduler to be idle, so all the operations are effectively canceled (otherwise FIOS may crash in shutdownAndCancelOps). while ( m_MainScheduler->isIdle() == false ) { ThreadSleep( 1 ); } // sometimes, files aren't closed by file system, and this will prevent the scheduler from shutting down m_MainScheduler->closeAllFiles(); m_MainScheduler->shutdownAndCancelOps(); cell::fios::scheduler::destroyScheduler( m_MainScheduler ); m_MainScheduler = NULL; delete m_SchedulerCache; m_SchedulerCache = NULL; m_SchedulerForHDD->shutdownAndCancelOps(); cell::fios::scheduler::destroyScheduler( m_SchedulerForHDD ); m_SchedulerForHDD = NULL; delete m_SysCacheMedia; m_SysCacheMedia = NULL; delete m_DevBdvdMedia; m_DevBdvdMedia = NULL; cell::fios::FIOSTerminate(); return true; } // Turn it back on #include "memdbgon.h" void CFiosConfiguration::FlushCache() { m_SchedulerCache->flush(); } bool CFiosConfiguration::PrefetchFile( const char * pFileName, int nPriority, bool bPersist ) { #if 0 // Because prefetching a big file is going to block all the other prefetches (and mess up the priorities if we need some prefetch done ASAP) // We are going to split a big prefetch in smaller pieces. // This is not necessary anymore: // If we cancel a prefetch in the middle and restart it, all the file portion that have been already prefetched before the cancellation // will be directly accounted for. I.e. there is not a lot of overhead to cancel and restart a prefetch. int64 nFileSize; cell::fios::err_t err = cell::fios::scheduler::getDefaultScheduler()->getFileSizeSync( NULL, pFileName, &nFileSize ); if ( err < 0 ) { Warning( "Can't retrieve size of file '%s'.\n", pFileName ); return false; } const int MAX_PREFETCH_BLOCK_SIZE = 16 * 1024 * 1024; // We prefetch 16 Mb at a time max // This is around 2 seconds of prefetching at the BluRay speed. // A 1.2 Gb file (like the sound zip file on Portal 2) will use 75 prefetch commands. // And will take a little bit more than 2 minutes to be prefetched. int64 nOffset = 0; int64 nRemainingSize = nFileSize; while ( nRemainingSize != 0 ) { int64 nPrefetchSize = MIN( nRemainingSize, MAX_PREFETCH_BLOCK_SIZE ); if ( PrefetchFile( pFileName, nPriority, bPersist, nOffset, nPrefetchSize ) == false) { return false; } nRemainingSize -= nPrefetchSize; nOffset += nPrefetchSize; } return true; #else return PrefetchFile( pFileName, nPriority, bPersist, 0, FIOS_OFF_T_MAX ); #endif } bool CFiosConfiguration::PrefetchFile( const char * pFileName, int nPriority, bool bPersist, int64 nOffset, int64 nSize ) { // Before we prefetch more, let's clear the old prefetches ClearFinishedPrefetches(); cell::fios::opattr_t opattr = FIOS_OPATTR_INITIALIZER; opattr.deadline = bPersist ? kDEADLINE_LATER : kDEADLINE_ASAP; // If not persistent, assume that we need this sooner rather than later // ASAP maybe a bit high (but still lower than NOW) // This is to handle the case where non-persistent files (usually for a given map) // are prefetched before persistent files (usually for the game in general). // Note that FIOS doe not seem to care about the deadline for prefetches (or the priority). :( opattr.priority = nPriority; opattr.pCallback = NULL; opattr.opflags = bPersist ? cell::fios::kOPF_CACHEPERSIST : 0; opattr.pLicense = 0; cell::fios::op * pOp = cell::fios::scheduler::getDefaultScheduler()->prefetchFile( &opattr, pFileName, nOffset, nSize ); if ( pOp == NULL ) { Warning( "FIOS error: Can't prefetch the file '%s'.\n", pFileName ); return false; } CPrefetchInfo * pPrefetchInfo = new CPrefetchInfo( pOp, pFileName, nOffset, nSize, opattr, bPersist ); m_Prefetches.AddToTail( pPrefetchInfo ); if ( bPersist == false ) { // If the prefetch is not persistent, it is deemed higher priority than persistent prefetch // (as they are map specific prefetching, so need to happen sooner rather than later). // Due to incorrect priorities in the FIOS prefetching engine, we are going to cancel persistent prefetches // And recreate them (they will be at the end of the list and recreated just after, as FIOS seems to execute them in order :(). // This is a workaround that we hope will work. // We can't suspend / resume individual op. We can suspend / resume all prefetches but that will not help us here. CancelAndRespawnPrefetches( true ); } return true; } void CFiosConfiguration::SuspendPrefetches( const char *pWhy ) { if ( fs_fios_spew_prefetches.GetBool() ) { Msg( "[Fios] Suspend prefetches. %s\n", pWhy ); } cell::fios::scheduler::getDefaultScheduler()->suspendPrefetch(); } void CFiosConfiguration::ResumePrefetches( const char *pWhy ) { if ( fs_fios_spew_prefetches.GetBool() ) { Msg( "[Fios] Resume prefetches. %s\n", pWhy ); } cell::fios::scheduler::getDefaultScheduler()->resumePrefetch(); } void CFiosConfiguration::CancelAndRespawnPrefetches( bool bPersistent ) { // This code will add new prefetches, but we are only interested in the prefetch already there // Note that this code is pretty inefficient if called several times in a row // (like when we prefetch non-persistent file one after the other). // But it should not happen often enough to be a big performance issue (we are talking about microseconds, not milliseconds). int nSize = m_Prefetches.Count(); for ( int i = 0 ; i < nSize ; ++i ) { CPrefetchInfo * pPrefetchInfo = m_Prefetches[i]; if ( pPrefetchInfo->IsPersistent() == false ) { // We want to keep these. continue; } cell::fios::op * pOp = pPrefetchInfo->GetOp(); if ( pOp->isDone() || pOp->isCancelled() ) { // Already done or canceled, nothing to cancel... continue; } // We cancel it pOp->cancel(); // And recreate it, it will be pretty much pushed at the end of the FIOS stack. // If the previous op happened to have finished earlier, re-prefetching it will not do another I/O operation. cell::fios::op * pNewOp = cell::fios::scheduler::getDefaultScheduler()->prefetchFile( &pPrefetchInfo->GetOpAttributes(), pPrefetchInfo->GetFileName(), pPrefetchInfo->GetOffset(), pPrefetchInfo->GetSize() ); if ( pNewOp == NULL ) { Warning( "FIOS error: Can't prefetch the file '%s'.\n", pPrefetchInfo->GetFileName() ); continue; // Not restarting the prefetch is not critical but could reduce the game experience // Continue with the other files } CPrefetchInfo * pNewPrefetchInfo = new CPrefetchInfo( pNewOp, *pPrefetchInfo ); m_Prefetches.AddToTail( pNewPrefetchInfo ); } // In case the canceled ops are done by now (this will also clear the old CPrefetchInfo). ClearFinishedPrefetches(); } void CFiosConfiguration::PrintPrefetches() { int nSize = m_Prefetches.Count(); if ( nSize != 0 ) { for (int i = 0 ; i < nSize ; ++i ) { PrintPrefetch( i ); } } else { Msg( "No prefetch in progress.\n" ); } } void CFiosConfiguration::PrintPrefetch( int nSlot ) { CPrefetchInfo * pPrefetchInfo = m_Prefetches[nSlot]; cell::fios::op * pOp = pPrefetchInfo->GetOp(); int nPriority = pOp->getPriority(); const float ONE_MEGABYTE = 1024.0f * 1024.0f; int64_t nRequestedSize = pOp->getRequestedSize(); // Note that if the file is 1.2 Gb (like the sound zip file in Portal2), the reported size will be incorrect. if ( nRequestedSize == FIOS_OFF_T_MAX ) { // We only get the size when we print it (not in normal process). int64 nFileSize; cell::fios::err_t err = cell::fios::scheduler::getDefaultScheduler()->getFileSizeSync( NULL, pPrefetchInfo->GetFileName(), &nFileSize ); nRequestedSize = nFileSize - pPrefetchInfo->GetOffset(); } float fRequestedSize = ( nRequestedSize >= 0 ) ? ( ( float )( nRequestedSize ) / ONE_MEGABYTE ) : -1.f; // Sometimes the size is unknown, use -1 in this case float fFullfilledSize = ( float )( pOp->getFulfilledSize() ) / ONE_MEGABYTE; if ( pOp->isDone() || pOp->isCancelled() ) { cell::fios::err_t nError = pOp->getError(); if ( nError < 0 ) { Msg( "Slot[%d] File: '%s' - Priority: %d - Requested size: %0.1f Mb - Fulfilled size: %0.1f Mb - Error: 0x%08X.\n", nSlot, pPrefetchInfo->GetFileName(), nPriority, fRequestedSize, fFullfilledSize, nError ); } else { Msg( "Slot[%d] File: '%s' - Priority: %d - Requested size: %0.1f Mb - Fulfilled size: %0.1f Mb - Done.\n", nSlot, pPrefetchInfo->GetFileName(), nPriority, fRequestedSize, fFullfilledSize ); } } else { int millisecondsToCompletion = cell::fios::FIOSAbstimeToMilliseconds( pOp->getEstimatedCompletion() ); float fSecondsToCompletion = (float)millisecondsToCompletion / 1000.0f; Msg( "Slot[%d] File: '%s' - Priority: %d - Requested size: %0.1f Mb - Fulfilled size: %0.1f Mb - Estimated completion: %0.3f seconds.\n", nSlot, pPrefetchInfo->GetFileName(), nPriority, fRequestedSize, fFullfilledSize, fSecondsToCompletion ); } } int CFiosConfiguration::ClearFinishedPrefetches() { int nCleared = 0; int nSize = m_Prefetches.Count(); // From end to beginning to indices are preserved during the scan for ( int i = nSize - 1 ; i >= 0 ; --i ) { CPrefetchInfo * pPrefetchInfo = m_Prefetches[i]; cell::fios::op * pOp = pPrefetchInfo->GetOp(); if ( pOp->isDone() || pOp->isCancelled() ) { // Remove it from the list delete pPrefetchInfo; ++nCleared; cell::fios::scheduler::getDefaultScheduler()->deleteOp( pOp ); m_Prefetches.Remove( i ); } } return nCleared; } void CFiosConfiguration::CancelAllPrefetches() { int nSize = m_Prefetches.Count(); for ( int i = 0 ; i < nSize ; ++i ) { CPrefetchInfo * pPrefetchInfo = m_Prefetches[i]; cell::fios::op * pOp = pPrefetchInfo->GetOp(); if ( pOp->isDone() || pOp->isCancelled() ) { continue; } pOp->cancel(); } // The prefetches may actually have been suspended (like during map loading). // cancel() may not do anything in this suspended state, so let's resume so they can complete. int nNumberOfSuspends = ResumePrefetchesToZero(); // This is going to be a blocking call until all the ops are considered done (after cancellation). // Ops are canceled very quickly though. while ( m_Prefetches.Count() != 0 ) { ClearFinishedPrefetches(); } // Once it is over, we re-suspend the same number of times the prefetches (so the state is the same as when we entered the function) SuspendPrefetchesToN( nNumberOfSuspends ); } int CFiosConfiguration::ResumePrefetchesToZero() { uint32 nNumberOfSuspends = cell::fios::scheduler::getDefaultScheduler()->getPrefetchSuspendCount(); for ( int i = 0 ; i < nNumberOfSuspends ; ++i ) { cell::fios::scheduler::getDefaultScheduler()->resumePrefetch( "Resume so CancelAllPrefetches() can complete." ); } Assert( cell::fios::scheduler::getDefaultScheduler()->getPrefetchSuspendCount() == 0 ); return ( int )nNumberOfSuspends; } void CFiosConfiguration::SuspendPrefetchesToN( int nNumberOfSuspends ) { for ( int i = 0 ; i < nNumberOfSuspends ; ++i ) { cell::fios::scheduler::getDefaultScheduler()->suspendPrefetch( "Suspends restored after CancelAllPrefetches() has completed." ); } Assert( cell::fios::scheduler::getDefaultScheduler()->getPrefetchSuspendCount() == nNumberOfSuspends ); } bool CFiosConfiguration::IsPrefetchingDone() { int nSize = m_Prefetches.Count(); for ( int i = 0 ; i < nSize ; ++i ) { CPrefetchInfo * pPrefetchInfo = m_Prefetches[i]; cell::fios::op * pOp = pPrefetchInfo->GetOp(); if ( ( pOp->isDone() || pOp->isCancelled() ) == false ) { return false; } } return true; // All are done or canceled } bool CBaseFileSystem::PrefetchFile( const char *pFileName, int nPriority, bool bPersist ) { if ( fs_fios_enabled.GetBool() ) { return g_FiosConfiguration.PrefetchFile( pFileName, nPriority, bPersist ); } else { return false; } } bool CBaseFileSystem::PrefetchFile( const char *pFileName, int nPriority, bool bPersist, int64 nOffset, int64 nSize ) { if ( fs_fios_enabled.GetBool() ) { return g_FiosConfiguration.PrefetchFile( pFileName, nPriority, bPersist, nOffset, nSize ); } else { return false; } } void CBaseFileSystem::FlushCache() { if ( fs_fios_enabled.GetBool() ) { g_FiosConfiguration.FlushCache(); } } void CBaseFileSystem::SuspendPrefetches( const char *pWhy ) { if ( fs_fios_enabled.GetBool() ) { g_FiosConfiguration.SuspendPrefetches( pWhy ); } } bool g_bUseFiosHddCache = true; void CBaseFileSystem::OnSaveStateChanged( bool bSaving ) { static CInterlockedInt nPrefetchesDueToSaving = 0; if ( bSaving ) { // If we are saving, we want to reduce the HDD access as much as possible // That way, normal IO and saving IO don't compete for the HDD usage. // Normal IO will not be slowed down by the saving (we will use the BluRay instead), // And hopefully the saving will be faster too. SuspendPrefetches( "Saving" ); ++nPrefetchesDueToSaving; g_bUseFiosHddCache = false; // Let's stop using the HDD cache (read and write) } else { // In case, OnSaveStateChanged( false ) is not called as many times as OnSaveStateChanged( true ), // let's restore the prefetch state (until nPrefetchesDueToSaving == 0). // This is a paranoid code, in case we don't have expected parity. for ( ; ; ) { int nResult = --nPrefetchesDueToSaving; if ( nResult < 0 ) { // We decremented too far ++nPrefetchesDueToSaving; // Put back the value of 0 break; // We are done with the resume prefetches related to the save } ResumePrefetches( "Save finished" ); // Let's continue until nPrefetchesDueToSaving == 0 } g_bUseFiosHddCache = true; // Safe to re-use the cache again } } bool CBaseFileSystem::IsPrefetchingDone() { return g_FiosConfiguration.IsPrefetchingDone( ); } void CBaseFileSystem::ResumePrefetches( const char *pWhy ) { if ( fs_fios_enabled.GetBool() ) { g_FiosConfiguration.ResumePrefetches( pWhy ); } } CON_COMMAND( fs_fios_flush_cache, "Flushes the FIOS HDD cache." ) { if ( fs_fios_enabled.GetBool() ) { g_FiosConfiguration.FlushCache(); Msg( "FIOS cache flushed.\n" ); } } CON_COMMAND( fs_fios_print_prefetches, "Displays all the prefetches currently in progress." ) { if ( fs_fios_enabled.GetBool() ) { g_FiosConfiguration.PrintPrefetches(); // Clear it after displaying the list so the user has a chance to view the one finished int nCleared = g_FiosConfiguration.ClearFinishedPrefetches(); if ( nCleared != 0 ) { Msg( "%d prefetch(s) finished and removed from the list.\n", nCleared ); } } } CON_COMMAND( fs_fios_prefetch_file, "Prefetches a file: .\nThe preftech is medium priority and persistent." ) { if ( fs_fios_enabled.GetBool() ) { if ( args.ArgC() == 2 ) { // Minimum priority and persistent for full zip file // As the assumption is all the data in the zip files have been organized to stay prefetched (like for sounds). // We want it persistent so we know after a while it will always stay there, but it is a lower priority prefetch // as it may not be used before a while. (short term / non-persistent preftech have a more immediate use). bool bSucceeded = g_FiosConfiguration.PrefetchFile( args.Arg( 1 ), -128, true ); if ( bSucceeded == false ) { Warning( "Prefetch failed. Check if there are other prefetches going on with the command 'fs_fios_print_prefetches'.\n"); } } else { Warning( "Incorrect parameter for the command 'fs_fios_prefetch_file'. Please use a file name (like \"/PS3_GAME/USRDIR/filename.bin\".\n" ); } } } CON_COMMAND( fs_fios_prefetch_file_in_pack, "Prefetches a file in a pack: .\nThe preftech is medium priority and non-persistent.") { if ( fs_fios_enabled.GetBool() ) { if ( args.ArgC() == 2 ) { // Higher priority and non-persistent for single file (we don't want the file to pollute the cache, it is just used for short term optimization, like in the next map). // But it has to be higher priority than the long term persistent prefetches as there is more chance the prefetch is going to help shortly. const char * pFileInPack = args.Arg( 1 ); char packFileName[256]; int64 nPosition = 0, nLength = 0; if ( g_pBaseFileSystem->GetPackFileInfoFromRelativePath( pFileInPack, "GAME", packFileName, sizeof(packFileName), nPosition, nLength ) ) { const char * pPathCached = g_FiosConfiguration.GetPathCached(); const int nPathCachedLength = strlen( pPathCached ); if ( memcmp( packFileName, pPathCached, nPathCachedLength ) == 0 ) { Msg( "Prefetching file: '%s' - Pack file: '%s' - Position: %lld - Size: %lld.\n", pFileInPack, packFileName, nPosition, nLength ); // We have to skip the path cached as FIOS expects a path relative to that. // Priority is set higher than average, just to differentiate with persistent prefetches. bool bSucceeded = g_FiosConfiguration.PrefetchFile( packFileName + nPathCachedLength, -127, false, nPosition, nLength ); if ( bSucceeded == false ) { Warning( "Prefetch failed. Check if there are other prefetches going on with the command 'fs_fios_print_prefetches'.\n" ); } } else { Warning( "Can't prefetch file '%s' in the pack file '%s' as the pack file is not a under the cached path '%s'.\n", pFileInPack, packFileName, pPathCached ); } } else { Warning( "Can't find the corresponding pack file for '%s'.\n", pFileInPack ); } } else { Warning( "Incorrect parameter for the command 'fs_fios_prefetch_file_in_pack'. Please use a file name (like \"portal2/models/container_ride/fineDebris_part5.ani\".\n" ); } } } CON_COMMAND( fs_fios_cancel_prefetches, "Cancels all the prefetches in progress." ) { if ( fs_fios_enabled.GetBool() ) { g_FiosConfiguration.CancelAllPrefetches(); } } #else // Fake commands for other platforms... CON_COMMAND( fs_fios_flush_cache, "Flushes the FIOS HDD cache." ) { } CON_COMMAND( fs_fios_print_prefetches, "Displays all the prefetches currently in progress." ) { } CON_COMMAND( fs_fios_prefetch_file, "Prefetches a file: .\nThe preftech is medium priority and persistent." ) { } CON_COMMAND( fs_fios_prefetch_file_in_pack, "Prefetches a file in a pack: .\nThe preftech is medium priority and non-persistent.") { } CON_COMMAND( fs_fios_cancel_prefetches, "Cancels all the prefetches in progress." ) { } #endif