//===== Copyright (c) Valve Corporation, All rights reserved. ======// // // Purpose: // // $NoKeywords: $ //==================================================================// #if defined( _WIN32 ) && !defined( _X360 ) #undef PROTECTED_THINGS_ENABLE #include #endif #include "quakedef.h" // for max_ospath #include #include #include "filesystem.h" #include "bitmap/tgawriter.h" #include #include "filesystem_init.h" #include "keyvalues.h" #include "host.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" #define ADDONLIST_FILENAME "addonlist.txt" #define ADDONS_DIRNAME "addons" IFileSystem *g_pFileSystem = NULL; // This comes is in filesystem_init.cpp extern KeyValues* ReadKeyValuesFile( const char *pFilename ); void fs_whitelist_spew_flags_changefn( IConVar *pConVar, const char *pOldValue, float flOldValue ) { if ( g_pFileSystem ) { ConVarRef var( pConVar ); g_pFileSystem->SetWhitelistSpewFlags( var.GetInt() ); } } #if defined( _DEBUG ) ConVar fs_whitelist_spew_flags( "fs_whitelist_spew_flags", "0", 0, "Set whitelist spew flags to a combination of these values:\n" " 0x0001 - list files as they are added to the CRC tracker\n" " 0x0002 - show files the filesystem is telling the engine to reload\n" " 0x0004 - show files the filesystem is NOT telling the engine to reload", fs_whitelist_spew_flags_changefn ); #endif CON_COMMAND( path, "Show the engine filesystem path." ) { if( g_pFileSystem ) { g_pFileSystem->PrintSearchPaths(); } } CON_COMMAND( fs_printopenfiles, "Show all files currently opened by the engine." ) { if( g_pFileSystem ) { g_pFileSystem->PrintOpenedFiles(); } } CON_COMMAND( fs_warning_level, "Set the filesystem warning level." ) { if( args.ArgC() != 2 ) { Warning( "\"fs_warning_level n\" where n is one of:\n" ); Warning( "\t0:\tFILESYSTEM_WARNING_QUIET\n" ); Warning( "\t1:\tFILESYSTEM_WARNING_REPORTUNCLOSED\n" ); Warning( "\t2:\tFILESYSTEM_WARNING_REPORTUSAGE\n" ); Warning( "\t3:\tFILESYSTEM_WARNING_REPORTALLACCESSES\n" ); Warning( "\t4:\tFILESYSTEM_WARNING_REPORTALLACCESSES_READ\n" ); Warning( "\t5:\tFILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE\n" ); Warning( "\t6:\tFILESYSTEM_WARNING_REPORTALLACCESSES_ASYNC\n" ); return; } int level = atoi( args[ 1 ] ); switch( level ) { case FILESYSTEM_WARNING_QUIET: Warning( "fs_warning_level = FILESYSTEM_WARNING_QUIET\n" ); break; case FILESYSTEM_WARNING_REPORTUNCLOSED: Warning( "fs_warning_level = FILESYSTEM_WARNING_REPORTUNCLOSED\n" ); break; case FILESYSTEM_WARNING_REPORTUSAGE: Warning( "fs_warning_level = FILESYSTEM_WARNING_REPORTUSAGE\n" ); break; case FILESYSTEM_WARNING_REPORTALLACCESSES: Warning( "fs_warning_level = FILESYSTEM_WARNING_REPORTALLACCESSES\n" ); break; case FILESYSTEM_WARNING_REPORTALLACCESSES_READ: Warning( "fs_warning_level = FILESYSTEM_WARNING_REPORTALLACCESSES_READ\n" ); break; case FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE: Warning( "fs_warning_level = FILESYSTEM_WARNING_REPORTALLACCESSES_READWRITE\n" ); break; case FILESYSTEM_WARNING_REPORTALLACCESSES_ASYNC: Warning( "fs_warning_level = FILESYSTEM_WARNING_REPORTALLACCESSES_ASYNC\n" ); break; default: Warning( "fs_warning_level = UNKNOWN!!!!!!!\n" ); return; break; } g_pFileSystem->SetWarningLevel( ( FileWarningLevel_t )level ); } //----------------------------------------------------------------------------- // Purpose: Wrap Sys_LoadModule() with a filesystem GetLocalCopy() call to // ensure have the file to load when running Steam. //----------------------------------------------------------------------------- CSysModule *FileSystem_LoadModule(const char *path) { if ( g_pFileSystem ) return g_pFileSystem->LoadModule( path ); else return Sys_LoadModule(path); } //----------------------------------------------------------------------------- // Purpose: Provided for symmetry sake with FileSystem_LoadModule()... //----------------------------------------------------------------------------- void FileSystem_UnloadModule(CSysModule *pModule) { Sys_UnloadModule(pModule); } void FileSystem_SetWhitelistSpewFlags() { #if defined( _DEBUG ) if ( !g_pFileSystem ) { Assert( !"FileSystem_InitSpewFlags - no filesystem." ); return; } g_pFileSystem->SetWhitelistSpewFlags( fs_whitelist_spew_flags.GetInt() ); #endif } CON_COMMAND( fs_syncdvddevcache, "Force the 360 to get updated files that are in your p4 changelist(s) from the host PC when running with -dvddev." ) { if( g_pFileSystem ) { g_pFileSystem->SyncDvdDevCache(); } } //--------------------------------------------------------------------------------------------------------------------- // Loads the optional addonlist.txt file which lives in the same location as gameinfo.txt and defines additional search // paths for content add-ons to mods. //--------------------------------------------------------------------------------------------------------------------- bool LoadAddonListFile( const char *pDirectoryName, KeyValues *&pAddons ) { char addoninfoFilename[MAX_PATH]; V_snprintf( addoninfoFilename, sizeof( addoninfoFilename), "%s%s", pDirectoryName, ADDONLIST_FILENAME ); pAddons = ReadKeyValuesFile( addoninfoFilename ); return ( pAddons != NULL ); } //--------------------------------------------------------------------------------------------------------------------- // Copies any addons staged under \steamapps\SourceMods\addons\ to the addons directory //--------------------------------------------------------------------------------------------------------------------- void CopyStagedAddons( IFileSystem *pFileSystem, const char *pModPath ) { #if (defined( PLATFORM_WINDOWS ) && !defined( _X360 ) ) || defined( PLATFORM_OSX ) #ifdef IS_WINDOWS_PC HKEY hKey; // Find the Steam installation path in the registry if ( ERROR_SUCCESS == RegOpenKeyEx( HKEY_CURRENT_USER, "Software\\Valve\\Steam", 0, KEY_READ, &hKey) ) { DWORD nReadLength = MAX_PATH; char szAddonInstallPath[MAX_PATH]; if ( ERROR_SUCCESS == RegQueryValueEx( hKey, "SourceModInstallPath", NULL, NULL, (LPBYTE)szAddonInstallPath, &nReadLength ) ) { #else { { char szAddonInstallPath[MAX_PATH]; char *pszHomeDir = getenv("HOME"); V_snprintf( szAddonInstallPath, sizeof(szAddonInstallPath), "%s/Library/Application Support/Steam/SteamApps/sourcemods", pszHomeDir ); #endif char szAddonsWildcard[MAX_PATH]; FileFindHandle_t findHandleDir; // // Loop through the .vpk files in the staged location // CUtlVector< CUtlString > vecAddonVPKs; V_snprintf( szAddonsWildcard, sizeof( szAddonsWildcard ), "%s%c%s%c%i%c%s", szAddonInstallPath, CORRECT_PATH_SEPARATOR, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, GetSteamAppID(), CORRECT_PATH_SEPARATOR, "*.vpk" ); const char *pFileName = pFileSystem->FindFirst( szAddonsWildcard, &findHandleDir ); while ( pFileName ) { char szSrcVPKPath[MAX_PATH]; V_snprintf( szSrcVPKPath, sizeof( szSrcVPKPath), "%s%c%s%c%i%c%s", szAddonInstallPath, CORRECT_PATH_SEPARATOR, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, GetSteamAppID(), CORRECT_PATH_SEPARATOR, pFileName); vecAddonVPKs.AddToTail( CUtlString( szSrcVPKPath ) ); pFileName = pFileSystem->FindNext( findHandleDir ); } pFileSystem->FindClose( findHandleDir ); // // Copy each of the VPKs to the addons directory // FOR_EACH_VEC( vecAddonVPKs, i ) { char szDestPath[MAX_PATH]; V_snprintf( szDestPath, sizeof( szDestPath ),"%s%s%c%s", pModPath, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, V_UnqualifiedFileName( vecAddonVPKs[i] ) ); pFileSystem->RemoveFile( szDestPath ); pFileSystem->RenameFile( vecAddonVPKs[i], szDestPath ); } } #ifdef IS_WINDOWS_PC RegCloseKey( hKey ); #endif } #endif } //--------------------------------------------------------------------------------------------------------------------- // Reconciles the contents of the addonlist.txt file with the addon folders located under /addons. If the // contains directory names that no longer exist they are removed. If there are directories present that are not in // the file they are added. Added directories default to the disabled state in the file. //--------------------------------------------------------------------------------------------------------------------- void ReconcileAddonListFile( IFileSystem *pFileSystem, const char *pModPath ) { KeyValues *pAddonList; // Load the existing addonlist.txt file LoadAddonListFile( pModPath, pAddonList ); // If there is no addonlist.txt then create an empty KeyValues if ( !pAddonList ) { pAddonList = new KeyValues( "AddonList" ); } // Get the list of subdirectories of addons char addonsWildcard[MAX_PATH]; FileFindHandle_t findHandleDir; // // Loop through the .vpk files // CUtlVector< CUtlString > vecAddonVPKs; V_snprintf( addonsWildcard, sizeof( addonsWildcard ), "%s%s%c%s", pModPath, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, "*.vpk" ); const char *pFileName = pFileSystem->FindFirst( addonsWildcard, &findHandleDir ); while ( pFileName ) { vecAddonVPKs.AddToTail( CUtlString( pFileName ) ); pFileName = pFileSystem->FindNext( findHandleDir ); } pFileSystem->FindClose( findHandleDir ); // // Loop through the loose directories // CUtlVector< CUtlString > vecAddonDirs; V_snprintf( addonsWildcard, sizeof( addonsWildcard ), "%s%s%c%s", pModPath, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, "*.*" ); pFileName = pFileSystem->FindFirst( addonsWildcard, &findHandleDir ); while ( pFileName ) { // We only want directories that is not already represented by a .vpk and that contains a valid addoninfo.txt if ( pFileSystem->FindIsDirectory( findHandleDir ) && ( pFileName[0] != '.' ) ) { char szVPKized[MAX_PATH]; V_snprintf( szVPKized, sizeof( szVPKized ), "%s.vpk", pFileName ); if ( !vecAddonVPKs.IsValidIndex( vecAddonVPKs.Find( CUtlString( szVPKized ) ) ) ) { char addonsInfoFile[MAX_PATH]; FileFindHandle_t findHandleConfig; V_snprintf( addonsInfoFile, sizeof( addonsInfoFile ), "%s%s%c%s%c%s", pModPath, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, pFileName, CORRECT_PATH_SEPARATOR, "addoninfo.txt" ); if ( pFileSystem->FindFirst( addonsInfoFile, &findHandleConfig ) ) { vecAddonDirs.AddToTail( CUtlString( pFileName ) ); } pFileSystem->FindClose( findHandleConfig ); } } pFileName = pFileSystem->FindNext( findHandleDir ); } pFileSystem->FindClose( findHandleDir ); // Add missing, existing directories to the KeyValues FOR_EACH_VEC( vecAddonDirs, i ) { // We found a directory that wasn't included in the file - add it if ( !pAddonList->FindKey( vecAddonDirs[i] ) ) { pAddonList->SetInt( vecAddonDirs[i], 1 ); } } // Add missing, existing VPKs to the KeyValues FOR_EACH_VEC( vecAddonVPKs, i ) { // We found a VPK that wasn't included in the file - add it if ( !pAddonList->FindKey( vecAddonVPKs[i] ) ) { pAddonList->SetInt( vecAddonVPKs[i], 1 ); } } // Remove any non-existent directories from the KeyValues KeyValues* pIter = pAddonList->GetFirstSubKey(); CUtlVector vecDoomedSubkeys; while( pIter ) { if ( !vecAddonDirs.IsValidIndex( vecAddonDirs.Find( CUtlString( pIter->GetName() ) ) ) && !vecAddonVPKs.IsValidIndex( vecAddonVPKs.Find( CUtlString( pIter->GetName() ) ) ) ) { vecDoomedSubkeys.AddToTail( pIter ); } pIter = pIter->GetNextKey(); } // Now actually delete the missing directories FOR_EACH_VEC( vecDoomedSubkeys, j ) { pAddonList->RemoveSubKey( vecDoomedSubkeys[j] ); vecDoomedSubkeys[j]->deleteThis(); } // Persist and dispose char addoninfoFilename[MAX_PATH]; V_snprintf( addoninfoFilename, sizeof( addoninfoFilename), "%s%s", pModPath, ADDONLIST_FILENAME ); if ( pAddonList->GetFirstSubKey() ) { pAddonList->SaveToFile( pFileSystem, addoninfoFilename ); } else { if ( pFileSystem->FileExists( addoninfoFilename ) ) { pFileSystem->RemoveFile( addoninfoFilename ); } } pAddonList->deleteThis(); } //--------------------------------------------------------------------------------------------------------------------- // Adds enabled addons to the GAME search path after removing any existing addons from the GAME path. //--------------------------------------------------------------------------------------------------------------------- void FileSystem_UpdateAddonSearchPaths( IFileSystem *pFileSystem ) { // Get the path to the mod dir char modPath[MAX_PATH]; pFileSystem->GetSearchPath( "MOD", false, modPath, sizeof( modPath ) ); // // Remove any existing addons from the search path // char gameSearchPath[10*MAX_PATH]; char addonSearchString[MAX_PATH]; CUtlStringList gameSearchPathList; // Construct the search string for determining whether the search path component is an add-on V_snprintf( addonSearchString, sizeof( addonSearchString ), "%s%s", modPath, ADDONS_DIRNAME ); pFileSystem->GetSearchPath( "GAME", false, gameSearchPath, sizeof( gameSearchPath ) ); V_SplitString(gameSearchPath, ";", gameSearchPathList ); FOR_EACH_VEC( gameSearchPathList, i ) { if ( V_stristr( gameSearchPathList[i], addonSearchString ) ) { pFileSystem->RemoveSearchPath( gameSearchPathList[i], "GAME" ); } } // Unmount any VPK addons CUtlVector loadedVPKs; pFileSystem->GetVPKFileNames( loadedVPKs ); FOR_EACH_VEC( loadedVPKs, i ) { if ( V_stristr( loadedVPKs[i], addonSearchString ) ) { pFileSystem->RemoveVPKFile( loadedVPKs[i] ); } } // // Copy over any addons that were staged by the addon installer // CopyStagedAddons( pFileSystem, modPath ); // // Reconcile the addons file and add any newly added ones to the list // ReconcileAddonListFile( pFileSystem, modPath ); // // Add any enabled addons to the GAME search path // KeyValues *pAddonList; if ( LoadAddonListFile( modPath, pAddonList ) ) { for ( KeyValues *pCur=pAddonList->GetFirstValue(); pCur; pCur=pCur->GetNextValue() ) { const char *pszAddonName = pCur->GetName(); const bool bAddonActivated = pCur->GetInt() != 0; if ( bAddonActivated ) { char addOnPath[MAX_PATH]; V_snprintf( addOnPath, sizeof( addOnPath ), "%s%s%c%s", modPath, ADDONS_DIRNAME, CORRECT_PATH_SEPARATOR, pszAddonName ); if ( V_stristr( pszAddonName, ".vpk" ) ) { pFileSystem->AddVPKFile( addOnPath, PATH_ADD_TO_TAIL ); } else { pFileSystem->AddSearchPath( addOnPath, "GAME", PATH_ADD_TO_TAIL ); } } } pAddonList->deleteThis(); } modelloader->Studio_ReloadModels( IModelLoader::RELOAD_EVERYTHING ); materials->UncacheAllMaterials(); } CON_COMMAND( update_addon_paths, "Reloads the search paths for game addons." ) { if( g_pFileSystem ) { FileSystem_UpdateAddonSearchPaths( g_pFileSystem ); } } CON_COMMAND( unload_all_addons, "Reloads the search paths for game addons." ) { // // Unmount any VPK addons // if( g_pFileSystem ) { char addonSearchString[MAX_PATH]; char modPath[MAX_PATH]; CUtlVector loadedVPKs; // Get the path to the mod dir g_pFileSystem->GetSearchPath( "MOD", false, modPath, sizeof( modPath ) ); // Construct the search string for determining whether the search path component is an add-on V_snprintf( addonSearchString, sizeof( addonSearchString ), "%s%s", modPath, ADDONS_DIRNAME ); g_pFileSystem->GetVPKFileNames( loadedVPKs ); FOR_EACH_VEC( loadedVPKs, i ) { if ( V_stristr( loadedVPKs[i], addonSearchString ) ) { g_pFileSystem->RemoveVPKFile( loadedVPKs[i] ); } } } }