You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1010 lines
28 KiB
1010 lines
28 KiB
//========= 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 ); |
|
} |
|
|
|
|
|
|