summaryrefslogtreecommitdiff
path: root/engine/MapReslistGenerator.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engine/MapReslistGenerator.cpp')
-rw-r--r--engine/MapReslistGenerator.cpp1010
1 files changed, 1010 insertions, 0 deletions
diff --git a/engine/MapReslistGenerator.cpp b/engine/MapReslistGenerator.cpp
new file mode 100644
index 0000000..cc729a6
--- /dev/null
+++ b/engine/MapReslistGenerator.cpp
@@ -0,0 +1,1010 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//===========================================================================//
+
+
+#include "MapReslistGenerator.h"
+#include "filesystem.h"
+#include "filesystem_engine.h"
+#include "sys.h"
+#include "cmd.h"
+#include "common.h"
+#include "quakedef.h"
+#include "vengineserver_impl.h"
+#include "tier1/strtools.h"
+#include "tier0/icommandline.h"
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <tier0/dbg.h>
+#include "host.h"
+#include "host_state.h"
+#include "utlbuffer.h"
+#include "characterset.h"
+#include "tier1/fmtstr.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+#define PAUSE_FRAMES_BETWEEN_MAPS 300
+#define PAUSE_TIME_BETWEEN_MAPS 2.0f
+
+extern engineparms_t host_parms;
+
+#define ENGINE_RESLIST_FILE "engine.lst"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void MapReslistGenerator_Usage()
+{
+ Msg( "-makereslists usage:\n" );
+ Msg( " [ -makereslists <optionalscriptfile> ] -- script file to control more complex makereslists operations (multiple passes, etc.)\n" );
+ Msg( " [ -usereslistfile filename ] -- get map list from specified file, default is to build for maps/*.bsp\n" );
+ Msg( " [ -startmap mapname ] -- restart generation at specified map (after crash, implies resume)\n" );
+ Msg( " [ -condebug ] -- prepend console.log entries with mapname or engine if not in a map\n" );
+ Msg( " [ +map mapname ] -- generate reslists for specified map and exit after that map\n" );
+ Msg( " [ -rebuildaudio ] -- force rebuild of _other_rebuild.cache (metacache) file at exit\n" );
+ Msg( " [ -forever ] -- when you get to the end of the maplist, start over from the top\n" );
+ Msg( " [ -reslistdir ] -- default is 'reslists', use this to override\n" );
+ Msg( " [ -startstage nnn ] -- when running from script file, this starts at specified stage\n" );
+ Msg( " [ -collate ] -- skip everything, just merge the reslist from temp folders to the final folder again\n" );
+}
+
+void MapReslistGenerator_Init()
+{
+ // check for reslist generation
+ if ( CommandLine()->FindParm("-makereslists") )
+ {
+ bool usemaplistfile = false;
+ if ( CommandLine()->FindParm("-usereslistfile") )
+ {
+ usemaplistfile = true;
+ }
+ MapReslistGenerator().EnableReslistGeneration( usemaplistfile );
+ }
+ else if ( CommandLine()->FindParm( "-rebuildaudio" ) )
+ {
+ MapReslistGenerator().SetAutoQuit( true );
+ }
+
+ if ( CommandLine()->FindParm( "-trackdeletions" ) )
+ {
+ MapReslistGenerator().EnableDeletionsTracking();
+ }
+}
+
+void MapReslistGenerator_Shutdown()
+{
+ MapReslistGenerator().Shutdown();
+}
+
+void MapReslistGenerator_BuildMapList()
+{
+ MapReslistGenerator().BuildMapList();
+}
+
+CMapReslistGenerator g_MapReslistGenerator;
+CMapReslistGenerator &MapReslistGenerator()
+{
+ return g_MapReslistGenerator;
+}
+
+static bool ReslistLogLessFunc( CUtlString const &pLHS, CUtlString const &pRHS )
+{
+ return CaselessStringLessThan( pLHS.Get(), pRHS.Get() );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor
+//-----------------------------------------------------------------------------
+CMapReslistGenerator::CMapReslistGenerator() :
+ m_AlreadyWrittenFileNames( 0, 0, true ),
+ m_DeletionListWarnings( 0, 0, DefLessFunc( CUtlSymbol ) ),
+ m_EngineLog( 0, 0, ReslistLogLessFunc ),
+ m_MapLog( 0, 0, ReslistLogLessFunc ),
+ m_bAutoQuit( false )
+{
+ MEM_ALLOC_CREDIT_CLASS();
+
+ m_bUsingMapList = false;
+ m_bTrackingDeletions = false;
+ m_bLoggingEnabled = false;
+ m_iCurrentMap = 0;
+ m_flNextMapRunTime = 0.0f;
+ m_iFrameCountdownToRunningNextMap = 0;
+ m_szPrefix[0] = '\0';
+ m_szLevelName[0] = '\0';
+ m_iPauseTimeBetweenMaps = PAUSE_TIME_BETWEEN_MAPS;
+ m_iPauseFramesBetweenMaps = PAUSE_FRAMES_BETWEEN_MAPS;
+ m_bRestartOnTransition = false;
+ m_bLogToEngineList = true;
+ m_sResListDir = "reslists";
+}
+
+void CMapReslistGenerator::SetAutoQuit( bool bState )
+{
+ m_bAutoQuit = bState;
+}
+
+void CMapReslistGenerator::BuildMapList()
+{
+ if ( !IsEnabled() )
+ return;
+
+ MapReslistGenerator_Usage();
+
+ // Get the maplist file, if any
+ const char *pMapFile = NULL;
+ CommandLine()->CheckParm( "-usereslistfile", &pMapFile );
+
+ // +map argument precludes using a maplist file
+ bool bUseMap = CommandLine()->FindParm("+map") != 0;
+ bool bUseMapListFile = bUseMap ? false : CommandLine()->FindParm("-usereslistfile") != 0;
+
+ // Build the map list
+ if ( !BuildGeneralMapList( &m_Maps, bUseMapListFile, pMapFile, "reslists", &m_iCurrentMap ) )
+ {
+ m_bLoggingEnabled = false;
+ }
+}
+
+bool BuildGeneralMapList( CUtlVector<maplist_map_t> *aMaps, bool bUseMapListFile, const char *pMapFile, char *pSystemMsg, int *iCurrentMap )
+{
+ if ( !bUseMapListFile )
+ {
+ // If the user passed in a +map parameter, just use that single map
+ char const *pMapName = NULL;
+ if ( CommandLine()->CheckParm( "+map", &pMapName ) && pMapName )
+ {
+ // ensure validity
+ char szMapFile[64] = { 0 };
+ V_snprintf( szMapFile, sizeof( szMapFile ), "maps/%s.bsp", pMapName );
+ if (g_pVEngineServer->IsMapValid( szMapFile ))
+ {
+ // add to list
+ maplist_map_t newMap;
+ Q_strncpy(newMap.name, pMapName, sizeof(newMap.name));
+ aMaps->AddToTail( newMap );
+ }
+
+ CommandLine()->RemoveParm( "+map" );
+ }
+ else
+ {
+ // build the list of all the levels to scan
+ // Search the directory structure.
+ const char *mapwild = "maps/*.bsp";
+ char const *findfn = Sys_FindFirst( mapwild, NULL, 0 );
+ while ( findfn )
+ {
+ // make sure that it's in the mod filesystem
+ if ( !g_pFileSystem->FileExists( va("maps/%s", findfn), "MOD" ) )
+ {
+ findfn = Sys_FindNext( NULL, 0 );
+ continue;
+ }
+
+ // strip extension
+ char sz[ MAX_PATH ];
+ Q_strncpy( sz, findfn, sizeof( sz ) );
+ char *ext = strchr( sz, '.' );
+ if (ext)
+ {
+ ext[0] = 0;
+ }
+
+ // move to next item
+ findfn = Sys_FindNext( NULL, 0 );
+
+ // ensure validity
+ char szMapFile[64] = { 0 };
+ V_snprintf( szMapFile, sizeof( szMapFile ), "maps/%s.bsp", sz );
+ if (!g_pVEngineServer->IsMapValid( szMapFile ))
+ continue;
+
+ // add to list
+ maplist_map_t newMap;
+ Q_strncpy(newMap.name, sz, sizeof(newMap.name));
+ aMaps->AddToTail( newMap );
+ }
+
+ Sys_FindClose();
+ }
+ }
+ else
+ {
+ // Read from file
+ if ( pMapFile )
+ {
+ // Load them in
+ FileHandle_t resfilehandle;
+ resfilehandle = g_pFileSystem->Open( pMapFile, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != resfilehandle )
+ {
+ // Read in and parse mapcycle.txt
+ int length = g_pFileSystem->Size(resfilehandle);
+ if ( length > 0 )
+ {
+ char *pStart = (char *)new char[ length + 1 ];
+ if ( pStart && ( length == g_pFileSystem->Read(pStart, length, resfilehandle) ) )
+ {
+ pStart[ length ] = 0;
+ const char *pFileList = pStart;
+
+ while ( 1 )
+ {
+ char szMap[ MAX_OSPATH ];
+
+ pFileList = COM_Parse( pFileList );
+ if ( strlen( com_token ) <= 0 )
+ break;
+
+ Q_strncpy(szMap, com_token, sizeof(szMap));
+
+ // ensure validity
+ char szMapFile[64] = { 0 };
+ V_snprintf( szMapFile, sizeof( szMapFile ), "maps/%s.bsp", szMap );
+ if (!g_pVEngineServer->IsMapValid( szMapFile ))
+ continue;
+
+ // Any more tokens on this line?
+ while ( COM_TokenWaiting( pFileList ) )
+ {
+ pFileList = COM_Parse( pFileList );
+ }
+
+ maplist_map_t newMap;
+ Q_strncpy(newMap.name, szMap, sizeof(newMap.name));
+ aMaps->AddToTail( newMap );
+ }
+ }
+ delete[] pStart;
+ }
+
+ g_pFileSystem->Close(resfilehandle);
+ }
+ else
+ {
+ Error( "Unable to load %s maplist file: %s\n", pSystemMsg, pMapFile );
+ return false;
+ }
+
+ }
+ else
+ {
+ Error( "Unable to find %s maplist filename\n", pSystemMsg );
+ return false;
+ }
+ }
+
+ int c = aMaps->Count();
+ if ( c == 0 )
+ {
+ Msg( "%s: No maps found\n", pSystemMsg );
+ return false;
+ }
+
+ Msg( "%s: Creating for:\n", pSystemMsg );
+
+ // Determine the current map (-startmap allows starts mid-maplist)
+ *iCurrentMap = 0;
+ char const *startmap = NULL;
+ if ( CommandLine()->CheckParm( "-startmap", &startmap ) && startmap )
+ {
+ for ( int i = 0 ; i < c; ++i )
+ {
+ if ( !Q_stricmp( aMaps->Element(i).name, startmap ) )
+ {
+ *iCurrentMap = i;
+ }
+ }
+ }
+
+ for ( int i = 0 ; i < c; ++i )
+ {
+ if ( i < *iCurrentMap )
+ {
+ Msg( "- %s\n", aMaps->Element(i).name );
+ }
+ else
+ {
+ Msg( "+ %s\n", aMaps->Element(i).name );
+ }
+ }
+
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Reconstructs engine log dictionary from existing engine reslist.
+// This is used to restore state after a restart, otherwise the engine log
+// would aggregate duplicate files.
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::BuildEngineLogFromReslist()
+{
+ m_EngineLog.RemoveAll();
+
+ CUtlBuffer buffer( 0, 0, CUtlBuffer::TEXT_BUFFER );
+ if ( !g_pFileSystem->ReadFile( CFmtStr( "%s\\%s", m_sResListDir.String(), ENGINE_RESLIST_FILE ), "DEFAULT_WRITE_PATH", buffer ) )
+ {
+ // does not exist
+ return;
+ }
+
+ characterset_t breakSet;
+ CharacterSetBuild( &breakSet, "" );
+
+ // parse reslist
+ char szToken[MAX_PATH];
+ for ( ;; )
+ {
+ int nTokenSize = buffer.ParseToken( &breakSet, szToken, sizeof( szToken ) );
+ if ( nTokenSize <= 0 )
+ {
+ break;
+ }
+
+ int idx = m_EngineLog.Find( szToken );
+ if ( idx == m_EngineLog.InvalidIndex() )
+ {
+ m_EngineLog.Insert( szToken );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Appends specified line to the engine reslist.
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::LogToEngineReslist( char const *pLine )
+{
+ // prevent unecessary duplication due to file appending
+ int idx = m_EngineLog.Find( pLine );
+ if ( idx != m_EngineLog.InvalidIndex() )
+ {
+ // already logged
+ return;
+ }
+
+ m_EngineLog.Insert( pLine );
+
+ // Open for append, write data, close.
+ FileHandle_t fh = g_pFileSystem->Open( CFmtStr( "%s\\%s", m_sResListDir.String(), ENGINE_RESLIST_FILE ), "at", "DEFAULT_WRITE_PATH" );
+ if ( fh != FILESYSTEM_INVALID_HANDLE )
+ {
+ g_pFileSystem->Write( "\"", 1, fh );
+ g_pFileSystem->Write( pLine, Q_strlen( pLine ), fh );
+ g_pFileSystem->Write( "\"\n", 2, fh );
+ g_pFileSystem->Close( fh );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: initializes the object to enable reslist generation
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::EnableReslistGeneration( bool usemaplistfile )
+{
+ //hackhack !!!! This is a work-around until CS precaches things on level start, not player spawn
+ if ( !Q_stricmp( "cstrike", GetCurrentMod() ))
+ {
+ m_iPauseTimeBetweenMaps = PAUSE_TIME_BETWEEN_MAPS * 3;
+ m_iPauseFramesBetweenMaps = PAUSE_FRAMES_BETWEEN_MAPS * 3;
+ }
+
+ m_bUsingMapList = usemaplistfile;
+
+ m_bLoggingEnabled = true;
+
+ char const *pszDir = NULL;
+ if ( CommandLine()->CheckParm( "-reslistdir", &pszDir ) && pszDir )
+ {
+ char szDir[ MAX_PATH ];
+ Q_strncpy( szDir, pszDir, sizeof( szDir ) );
+ Q_StripTrailingSlash( szDir );
+ Q_strlower( szDir );
+ Q_FixSlashes( szDir );
+
+ if ( Q_strlen( szDir ) > 0 )
+ {
+ m_sResListDir = szDir;
+ }
+ }
+
+ // create file to dump out to
+ g_pFileSystem->CreateDirHierarchy( m_sResListDir.String() , "DEFAULT_WRITE_PATH" );
+
+ // Leave the existing one if resuming from a specific map, otherwise, blow it away
+ if ( !CommandLine()->FindParm( "-startmap" ) )
+ {
+ g_pFileSystem->RemoveFile( CFmtStr( "%s\\%s", m_sResListDir.String(), ENGINE_RESLIST_FILE ), "DEFAULT_WRITE_PATH" );
+ m_EngineLog.RemoveAll();
+ }
+ else
+ {
+ BuildEngineLogFromReslist();
+ }
+
+ // add logging function
+ g_pFileSystem->AddLoggingFunc(&FileSystemLoggingFunc);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: starts the first map
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::StartReslistGeneration()
+{
+ m_iCurrentMap = 0;
+ m_iFrameCountdownToRunningNextMap = m_iPauseFramesBetweenMaps;
+ m_flNextMapRunTime = Sys_FloatTime() + m_iPauseTimeBetweenMaps;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *mapname -
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::SetPrefix( char const *mapname )
+{
+ Q_snprintf( m_szPrefix, sizeof( m_szPrefix ), "%s: ", mapname );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : char const
+//-----------------------------------------------------------------------------
+char const *CMapReslistGenerator::LogPrefix()
+{
+ // If not recording stuff to file, then use the "default" prefix.
+ if ( m_bLogToEngineList )
+ {
+ return "engine: ";
+ }
+
+ return m_szPrefix;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: call to mark level load/end
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::OnLevelLoadStart(const char *levelName)
+{
+ if ( !IsEnabled() )
+ return;
+
+ // reset the duplication list
+ m_AlreadyWrittenFileNames.RemoveAll();
+
+ // prepare for map logging
+ m_bLogToEngineList = false;
+ m_MapLog.RemoveAll();
+ V_strncpy( m_szLevelName, levelName, sizeof( m_szLevelName ) );
+
+ // add in the bsp file to the list, and its node graph
+ char path[MAX_PATH];
+ Q_snprintf( path, sizeof( path ), "maps\\%s.bsp", levelName );
+ OnResourcePrecached( path );
+
+ bool useNodeGraph = true;
+ KeyValues *modinfo = new KeyValues("ModInfo");
+ if ( modinfo->LoadFromFile( g_pFileSystem, "gameinfo.txt" ) )
+ {
+ useNodeGraph = modinfo->GetInt( "nodegraph", 1 ) != 0;
+ }
+ modinfo->deleteThis();
+
+ if ( useNodeGraph )
+ {
+ Q_snprintf(path, sizeof(path), "maps\\graphs\\%s.ain", levelName);
+ OnResourcePrecached(path);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: call to mark level load/end
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::OnLevelLoadEnd()
+{
+}
+
+void CMapReslistGenerator::OnPlayerSpawn()
+{
+ if ( !IsEnabled() )
+ return;
+
+ // initiate the next level
+ m_iFrameCountdownToRunningNextMap = m_iPauseFramesBetweenMaps;
+ m_flNextMapRunTime = Sys_FloatTime() + m_iPauseTimeBetweenMaps;
+}
+
+bool CMapReslistGenerator::ShouldRebuildCaches()
+{
+ if ( !IsEnabled() )
+ {
+ return CommandLine()->FindParm( "-rebuildaudio" ) != 0;
+ }
+
+ if ( !CommandLine()->FindParm( "-norebuildaudio" ) )
+ return true;
+ return false;
+}
+
+char const *CMapReslistGenerator::GetResListDirectory() const
+{
+ return m_sResListDir.String();
+}
+
+void CMapReslistGenerator::DoQuit()
+{
+ Cbuf_AddText( "quit\n" );
+ // remove the logging
+ g_pFileSystem->RemoveLoggingFunc(&FileSystemLoggingFunc);
+ m_bLogToEngineList = true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: call every frame if we're enabled, just so that the next map can be triggered at the right time
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::RunFrame()
+{
+ if ( !IsEnabled() )
+ {
+ if ( m_bAutoQuit )
+ {
+ m_bAutoQuit = false;
+ DoQuit();
+ }
+ return;
+ }
+
+ if ( --m_iFrameCountdownToRunningNextMap > 0 )
+ return;
+
+ if ( m_flNextMapRunTime && m_flNextMapRunTime < Sys_FloatTime() )
+ {
+ // about to transition or terminate, emit the current map log
+ WriteMapLog();
+
+ if ( m_Maps.IsValidIndex( m_iCurrentMap ) )
+ {
+ m_flNextMapRunTime = 0.0f;
+ m_iFrameCountdownToRunningNextMap = 0;
+
+ if ( !m_bRestartOnTransition )
+ {
+ Cbuf_AddText( va( "map %s\n", m_Maps[m_iCurrentMap].name ) );
+
+ SetPrefix( m_Maps[m_iCurrentMap].name );
+
+ ++m_iCurrentMap;
+ if ( m_Maps.IsValidIndex( m_iCurrentMap ) )
+ {
+ // cause a full engine restart on the transition to the next map
+ // ensure that one-time init code logs correctly to each map reslist
+ m_bRestartOnTransition = true;
+ }
+ }
+ else
+ {
+ // restart at specified map
+ CommandLine()->RemoveParm( "-startmap" );
+ CommandLine()->AppendParm( "-startmap", m_Maps[m_iCurrentMap].name );
+ HostState_Restart();
+ }
+ }
+ else
+ {
+ // no more levels, just quit
+ if ( !CommandLine()->FindParm( "-forever" ) )
+ {
+ DoQuit();
+ }
+ else
+ {
+ StartReslistGeneration();
+ m_bRestartOnTransition = true;
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: logs and handles mdl files being precaches
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::OnModelPrecached(const char *relativePathFileName)
+{
+ if ( !IsEnabled() )
+ return;
+
+ if (strstr(relativePathFileName, ".vmt"))
+ {
+ // it's a materials file, make sure that it starts in the materials directory, and we get the .vtf
+ char file[_MAX_PATH];
+
+ if (!Q_strnicmp(relativePathFileName, "materials", strlen("materials")))
+ {
+ Q_strncpy(file, relativePathFileName, sizeof(file));
+ }
+ else
+ {
+ // prepend the materials directory
+ Q_snprintf(file, sizeof(file), "materials\\%s", relativePathFileName);
+ }
+ OnResourcePrecached(file);
+
+ // get the matching vtf file
+ char *ext = strstr(file, ".vmt");
+ if (ext)
+ {
+ Q_strncpy(ext, ".vtf", 5);
+ OnResourcePrecached(file);
+ }
+ }
+ else
+ {
+ OnResourcePrecached(relativePathFileName);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: logs sound file access
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::OnSoundPrecached(const char *relativePathFileName)
+{
+ // skip any special characters
+ if (!V_isalnum(relativePathFileName[0]))
+ {
+ ++relativePathFileName;
+ }
+
+ // prepend the sound/ directory if necessary
+ char file[_MAX_PATH];
+ if (!Q_strnicmp(relativePathFileName, "sound", strlen("sound")))
+ {
+ Q_strncpy(file, relativePathFileName, sizeof(file));
+ }
+ else
+ {
+ // prepend the sound directory
+ Q_snprintf(file, sizeof(file), "sound\\%s", relativePathFileName);
+ }
+
+ OnResourcePrecached(file);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: logs the precache as a file access
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::OnResourcePrecached(const char *relativePathFileName)
+{
+ if ( !IsEnabled() )
+ return;
+
+ // ignore empty string
+ if (relativePathFileName[0] == 0)
+ return;
+
+ // ignore files that start with '*' since they signify special models
+ if (relativePathFileName[0] == '*')
+ return;
+
+ char fullPath[_MAX_PATH];
+ if (g_pFileSystem->GetLocalPath(relativePathFileName, fullPath, sizeof(fullPath)))
+ {
+ OnResourcePrecachedFullPath(fullPath);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Logs out file access to a file
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::OnResourcePrecachedFullPath(const char *fullPathFileName)
+{
+ char fixed[ MAX_PATH ];
+ Q_strncpy( fixed, fullPathFileName, sizeof( fixed ) );
+ Q_strlower( fixed );
+ Q_FixSlashes( fixed );
+
+ // make sure the filename hasn't already been written
+ UtlSymId_t filename = m_AlreadyWrittenFileNames.Find( fixed );
+ if ( filename != UTL_INVAL_SYMBOL )
+ return;
+
+ // record in list, so we don't write it again
+ m_AlreadyWrittenFileNames.AddString( fixed );
+
+ // add extras for mdl's
+ if (strstr(fixed, ".mdl"))
+ {
+ // it's a model, get it's other files as well
+ char file[_MAX_PATH];
+ Q_strncpy(file, fixed, sizeof(file) - 10);
+ char *ext = strstr(file, ".mdl");
+
+ Q_strncpy(ext, ".vvd", 10);
+ OnResourcePrecachedFullPath(file);
+ Q_strncpy(ext, ".ani", 10);
+ OnResourcePrecachedFullPath(file);
+ Q_strncpy(ext, ".dx80.vtx", 10);
+ OnResourcePrecachedFullPath(file);
+ Q_strncpy(ext, ".dx90.vtx", 10);
+ OnResourcePrecachedFullPath(file);
+ Q_strncpy(ext, ".sw.vtx", 10);
+ OnResourcePrecachedFullPath(file);
+ Q_strncpy(ext, ".phy", 10);
+ OnResourcePrecachedFullPath(file);
+ Q_strncpy(ext, ".jpg", 10);
+ OnResourcePrecachedFullPath(file);
+ }
+
+ // strip it down relative to the root directory of the game (for steam)
+ char const *relativeFileName = Q_stristr( fixed, GetBaseDirectory() );
+ if ( relativeFileName )
+ {
+ // Skip the basedir and slash
+ relativeFileName += ( Q_strlen( GetBaseDirectory() ) + 1 );
+ }
+
+ if ( !relativeFileName )
+ {
+ return;
+ }
+
+ if ( m_bLogToEngineList )
+ {
+ LogToEngineReslist( relativeFileName );
+ }
+ else
+ {
+ // find or add to sorted tree
+ int idx = m_MapLog.Find( relativeFileName );
+ if ( idx == m_MapLog.InvalidIndex() )
+ {
+ m_MapLog.Insert( relativeFileName );
+ }
+ }
+}
+
+void CMapReslistGenerator::WriteMapLog()
+{
+ if ( !m_szLevelName[0] )
+ {
+ // log has not been established yet
+ return;
+ }
+
+ // write the sorted map log, allows for easier diffs between revisions
+ char path[_MAX_PATH];
+ Q_snprintf( path, sizeof( path ), "%s\\%s.lst", m_sResListDir.String(), m_szLevelName );
+ FileHandle_t fh = g_pFileSystem->Open( path, "wt", "DEFAULT_WRITE_PATH" );
+ for ( int i = m_MapLog.FirstInorder(); i != m_MapLog.InvalidIndex(); i = m_MapLog.NextInorder( i ) )
+ {
+ const char *pLine = m_MapLog[i].String();
+ g_pFileSystem->Write( "\"", 1, fh );
+ g_pFileSystem->Write( pLine, Q_strlen( pLine ), fh );
+ g_pFileSystem->Write( "\"\n", 2, fh );
+ }
+ g_pFileSystem->Close( fh );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: callback function from filesystem
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::FileSystemLoggingFunc(const char *fullPathFileName, const char *options)
+{
+ g_MapReslistGenerator.OnResourcePrecachedFullPath(fullPathFileName);
+}
+
+#define DELETIONS_BATCH_FILE "deletions.bat"
+#define DELETIONS_WARNINGS_FILE "undelete.lst"
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::EnableDeletionsTracking()
+{
+ unsigned int deletions = 0;
+ unsigned int warnings = 0;
+
+ // Load deletions file and build dictionary
+ m_bTrackingDeletions = true;;
+
+ // Open up deletions.bat and parse out all filenames
+ // Load them in
+ FileHandle_t deletionsfile;
+ deletionsfile = g_pFileSystem->Open( DELETIONS_BATCH_FILE, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != deletionsfile )
+ {
+ // Read in and parse mapcycle.txt
+ int length = g_pFileSystem->Size(deletionsfile);
+ if ( length > 0 )
+ {
+ char *pStart = (char *)new char[ length + 1 ];
+ if ( pStart && ( length == g_pFileSystem->Read(pStart, length, deletionsfile) ) )
+ {
+ pStart[ length ] = 0;
+ const char *pFileList = pStart;
+
+ while ( 1 )
+ {
+ char filename[ MAX_OSPATH ];
+
+ pFileList = COM_Parse( pFileList );
+ if ( strlen( com_token ) <= 0 )
+ break;
+
+ if ( !Q_stricmp( com_token, "del" ) )
+ continue;
+
+ Q_snprintf(filename, sizeof( filename ), "%s/%s", com_gamedir, com_token );
+
+ // Any more tokens on this line?
+ while ( COM_TokenWaiting( pFileList ) )
+ {
+ pFileList = COM_Parse( pFileList );
+ }
+
+ Q_FixSlashes( filename );
+ Q_strlower( filename );
+
+ m_DeletionList.AddString( filename );
+
+ ++deletions;
+ }
+ }
+ delete[] pStart;
+ }
+
+ g_pFileSystem->Close(deletionsfile);
+ }
+ else
+ {
+ Warning( "Unable to load deletions.bat file %s\n", DELETIONS_BATCH_FILE );
+ m_bTrackingDeletions = false;
+ return;
+ }
+
+ FileHandle_t warningsfile = g_pFileSystem->Open( DELETIONS_WARNINGS_FILE, "rb" );
+ if ( FILESYSTEM_INVALID_HANDLE != warningsfile )
+ {
+ // Read in and parse mapcycle.txt
+ int length = g_pFileSystem->Size(warningsfile);
+ if ( length > 0 )
+ {
+ char *pStart = (char *)new char[ length + 1 ];
+ if ( pStart && ( length == g_pFileSystem->Read(pStart, length, warningsfile) ) )
+ {
+ pStart[ length ] = 0;
+ const char *pFileList = pStart;
+
+ while ( 1 )
+ {
+ pFileList = COM_Parse( pFileList );
+ if ( strlen( com_token ) <= 0 )
+ break;
+
+ Q_FixSlashes( com_token );
+ Q_strlower( com_token );
+
+ CUtlSymbol sym = m_DeletionListWarningsSymbols.AddString( com_token );
+ int idx = m_DeletionListWarnings.Find( sym );
+ if ( idx == m_DeletionListWarnings.InvalidIndex() )
+ {
+ m_DeletionListWarnings.Insert( sym );
+ ++warnings;
+ }
+ }
+ }
+ delete[] pStart;
+ }
+
+ g_pFileSystem->Close(warningsfile);
+ }
+
+ // Hook up logging function
+ g_pFileSystem->AddLoggingFunc( &TrackDeletionsLoggingFunc );
+
+ Msg( "Tracking deletions (%u files in deletion list in '%s', %u previous warnings loaded from '%s'\n",
+ deletions, DELETIONS_BATCH_FILE, warnings, DELETIONS_WARNINGS_FILE );
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *fullPathFileName -
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::TrackDeletions( const char *fullPathFileName )
+{
+ Assert( m_bTrackingDeletions );
+
+ char test[ _MAX_PATH ];
+ Q_strncpy( test, fullPathFileName, sizeof( test ) );
+ Q_FixSlashes( test );
+ Q_strlower( test );
+
+ CUtlSymbol sym = m_DeletionList.Find( test );
+ if ( UTL_INVAL_SYMBOL != sym )
+ {
+ CUtlSymbol warningSymbol = m_DeletionListWarningsSymbols.AddString( test );
+
+ uint idx = m_DeletionListWarnings.Find( warningSymbol );
+ if ( idx == m_DeletionListWarnings.InvalidIndex() )
+ {
+ Msg( "--> Referenced file marked for deletion \"%s\"\n", test );
+ m_DeletionListWarnings.Insert( warningSymbol );
+ }
+ }
+
+ // add extras for mdl's
+ if (strstr(test, ".mdl"))
+ {
+ // it's a model, get it's other files as well
+ char file[_MAX_PATH];
+ Q_strncpy(file, test, sizeof(file) - 10);
+ char *ext = strstr(file, ".mdl");
+
+ Q_strncpy(ext, ".vvd", 10);
+ TrackDeletions(file);
+ Q_strncpy(ext, ".ani", 10);
+ TrackDeletions(file);
+ Q_strncpy(ext, ".dx80.vtx", 10);
+ TrackDeletions(file);
+ Q_strncpy(ext, ".dx90.vtx", 10);
+ TrackDeletions(file);
+ Q_strncpy(ext, ".sw.vtx", 10);
+ TrackDeletions(file);
+ Q_strncpy(ext, ".phy", 10);
+ TrackDeletions(file);
+ Q_strncpy(ext, ".jpg", 10);
+ TrackDeletions(file);
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: callback function from filesystem
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::TrackDeletionsLoggingFunc(const char *fullPathFileName, const char *options)
+{
+ g_MapReslistGenerator.TrackDeletions(fullPathFileName);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMapReslistGenerator::Shutdown()
+{
+ if ( m_bTrackingDeletions )
+ {
+ SpewTrackedDeletionsLog();
+
+ g_pFileSystem->RemoveLoggingFunc( &TrackDeletionsLoggingFunc );
+ m_DeletionList.RemoveAll();
+ m_DeletionListWarnings.RemoveAll();
+ m_DeletionListWarningsSymbols.RemoveAll();
+ m_bTrackingDeletions = NULL;
+ }
+}
+
+void CMapReslistGenerator::SpewTrackedDeletionsLog()
+{
+ if ( !m_bTrackingDeletions )
+ return;
+
+ FileHandle_t hUndeleteFile = g_pFileSystem->Open( DELETIONS_WARNINGS_FILE, "wt", "DEFAULT_WRITE_PATH" );
+ if ( FILESYSTEM_INVALID_HANDLE == hUndeleteFile )
+ {
+ return;
+ }
+
+ for ( int i = m_DeletionListWarnings.FirstInorder(); i != m_DeletionListWarnings.InvalidIndex() ; i = m_DeletionListWarnings.NextInorder( i ) )
+ {
+ char const *filename = m_DeletionListWarningsSymbols.String( m_DeletionListWarnings[ i ] );
+
+ g_pFileSystem->Write("\"", 1, hUndeleteFile);
+ g_pFileSystem->Write(filename, Q_strlen(filename), hUndeleteFile);
+ g_pFileSystem->Write("\"\n", 2, hUndeleteFile);
+ }
+
+ g_pFileSystem->Close( hUndeleteFile );
+}
+
+