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.
326 lines
8.9 KiB
326 lines
8.9 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
//=======================================================================================// |
|
|
|
#include "sv_replaycontext.h" |
|
#include "replay/shared_defs.h" // BUILD_CURL defined here |
|
#include "sv_sessionrecorder.h" |
|
#include "sv_fileservercleanup.h" |
|
#include "sv_recordingsession.h" |
|
#include "sv_publishtest.h" |
|
#include "replaysystem.h" |
|
#include "icommandline.h" |
|
|
|
#if BUILD_CURL |
|
#include "curl/curl.h" |
|
#endif |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include "tier0/memdbgon.h" |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
#undef CreateEvent |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
CServerReplayContext::CServerReplayContext() |
|
: m_pSessionRecorder( NULL ), |
|
m_pFileserverCleaner( NULL ), |
|
m_bShouldAbortRecording( false ), |
|
m_flConVarSanityCheckTime( 0.0f ) |
|
{ |
|
} |
|
|
|
CServerReplayContext::~CServerReplayContext() |
|
{ |
|
delete m_pSessionRecorder; |
|
delete m_pFileserverCleaner; |
|
} |
|
|
|
bool CServerReplayContext::Init( CreateInterfaceFn fnFactory ) |
|
{ |
|
#if BUILD_CURL |
|
// Initialize cURL - in windows, this will init the winsock stuff. |
|
curl_global_init( CURL_GLOBAL_ALL ); |
|
#endif |
|
|
|
m_pShared = new CSharedReplayContext( this ); |
|
|
|
m_pShared->m_strSubDir = GetServerSubDirName(); |
|
m_pShared->m_pRecordingSessionManager = new CServerRecordingSessionManager( this ); |
|
m_pShared->m_pRecordingSessionBlockManager = new CServerRecordingSessionBlockManager( this ); |
|
m_pShared->m_pErrorSystem = new CErrorSystem( this ); |
|
|
|
m_pShared->Init( fnFactory ); |
|
|
|
// Create directory for temp files |
|
CFmtStr fmtTmpDir( "%s%s", SV_GetBasePath(), SUBDIR_TMP ); |
|
g_pFullFileSystem->CreateDirHierarchy( fmtTmpDir.Access() ); |
|
|
|
// Remove any extraneous files from the temp directory |
|
CleanTmpDir(); |
|
|
|
m_pSessionRecorder = new CSessionRecorder(); |
|
m_pSessionRecorder->Init(); |
|
|
|
m_pFileserverCleaner = new CFileserverCleaner(); |
|
|
|
return true; |
|
} |
|
|
|
void CServerReplayContext::CleanTmpDir() |
|
{ |
|
int nFilesRemoved = 0; |
|
|
|
Log( "Cleaning files from temp dir, \"%s\" ...", SV_GetTmpDir() ); |
|
|
|
FileFindHandle_t hFind; |
|
CFmtStr fmtPath( "%s*", SV_GetTmpDir() ); |
|
const char *pFilename = g_pFullFileSystem->FindFirst( fmtPath.Access(), &hFind ); |
|
while ( pFilename ) |
|
{ |
|
if ( pFilename[0] != '.' ) |
|
{ |
|
// Remove the file |
|
CFmtStr fmtFullFilename( "%s%s", SV_GetTmpDir(), pFilename ); |
|
g_pFullFileSystem->RemoveFile( fmtFullFilename.Access() ); |
|
|
|
++nFilesRemoved; |
|
} |
|
|
|
// Get next file |
|
pFilename = g_pFullFileSystem->FindNext( hFind ); |
|
} |
|
|
|
if ( nFilesRemoved ) |
|
{ |
|
Log( "%i %s removed.\n", nFilesRemoved, nFilesRemoved == 1 ? "file" : "files" ); |
|
} |
|
else |
|
{ |
|
Log( "no files removed.\n" ); |
|
} |
|
} |
|
|
|
void CServerReplayContext::Shutdown() |
|
{ |
|
m_pShared->Shutdown(); |
|
|
|
#if BUILD_CURL |
|
// Shutdown cURL |
|
curl_global_cleanup(); |
|
#endif |
|
} |
|
|
|
void CServerReplayContext::Think() |
|
{ |
|
ConVarSanityThink(); |
|
|
|
if ( !g_pReplay->IsReplayEnabled() ) |
|
return; |
|
|
|
if ( m_bShouldAbortRecording ) |
|
{ |
|
g_pBlockSpewer->PrintBlockStart(); |
|
g_pBlockSpewer->PrintMsg( "Replay recording shutting down due to publishing error! Recording will begin" ); |
|
g_pBlockSpewer->PrintMsg( "at the beginning of the next round, but may fail again." ); |
|
g_pBlockSpewer->PrintBlockEnd(); |
|
|
|
// Shutdown recording |
|
m_pSessionRecorder->AbortCurrentSessionRecording(); |
|
|
|
m_bShouldAbortRecording = false; |
|
} |
|
|
|
m_pShared->Think(); |
|
} |
|
|
|
void CServerReplayContext::ConVarSanityThink() |
|
{ |
|
if ( m_flConVarSanityCheckTime == 0.0f ) |
|
return; |
|
|
|
DoSanityCheckNow(); |
|
} |
|
|
|
void CServerReplayContext::UpdateFileserverIPFromHostname( const char *pHostname ) |
|
{ |
|
if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, m_szFileserverIP, sizeof( m_szFileserverIP ) ) ) |
|
{ |
|
V_strcpy( m_szFileserverIP, "0.0.0.0" ); |
|
Log( "ERROR: Could not resolve fileserver hostname \"%s\" !\n", pHostname ); |
|
return; |
|
} |
|
|
|
Log( "Cached resolved fileserver hostname to IP address: \"%s\" -> \"%s\"\n", pHostname, m_szFileserverIP ); |
|
} |
|
|
|
void CServerReplayContext::UpdateFileserverProxyIPFromHostname( const char *pHostname ) |
|
{ |
|
if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, m_szFileserverProxyIP, sizeof( m_szFileserverProxyIP ) ) ) |
|
{ |
|
V_strcpy( m_szFileserverProxyIP, "0.0.0.0" ); |
|
Log( "ERROR: Could not resolve fileserver proxy hostname \"%s\" !\n", pHostname ); |
|
return; |
|
} |
|
|
|
Log( "Cached resolved fileserver proxy hostname to IP address: \"%s\" -> \"%s\"\n", pHostname, m_szFileserverProxyIP ); |
|
} |
|
|
|
void CServerReplayContext::DoSanityCheckNow() |
|
{ |
|
// Check now? |
|
if ( m_flConVarSanityCheckTime <= g_pEngine->GetHostTime() ) |
|
{ |
|
// Reset |
|
m_flConVarSanityCheckTime = 0.0f; |
|
|
|
g_pBlockSpewer->PrintBlockStart(); |
|
|
|
extern ConVar replay_enable; |
|
if ( replay_enable.GetBool() ) |
|
{ |
|
// Test publish |
|
const bool bPublishResult = SV_DoTestPublish(); |
|
|
|
g_pBlockSpewer->PrintEmptyLine(); |
|
|
|
if ( bPublishResult ) |
|
{ |
|
g_pBlockSpewer->PrintEmptyLine(); |
|
g_pBlockSpewer->PrintEmptyLine(); |
|
g_pBlockSpewer->PrintMsg( "SUCCESS - REPLAY IS ENABLED!" ); |
|
g_pBlockSpewer->PrintEmptyLine(); |
|
g_pBlockSpewer->PrintMsg( "A 'changelevel' or 'map' is required - recording will" ); |
|
g_pBlockSpewer->PrintMsg( "begin at the start of the next round." ); |
|
g_pBlockSpewer->PrintEmptyLine(); |
|
} |
|
else |
|
{ |
|
replay_enable.SetValue( 0 ); |
|
|
|
g_pBlockSpewer->PrintEmptyLine(); |
|
g_pBlockSpewer->PrintMsg( "FAILURE - REPLAY DISABLED! \"replay_enable\" is now 0." ); |
|
g_pBlockSpewer->PrintEmptyLine(); |
|
g_pBlockSpewer->PrintEmptyLine(); |
|
g_pBlockSpewer->PrintMsg( "Address any failures above and re-exec replay.cfg." ); |
|
} |
|
} |
|
|
|
g_pBlockSpewer->PrintBlockEnd(); |
|
} |
|
} |
|
|
|
void CServerReplayContext::FlagForConVarSanityCheck() |
|
{ |
|
m_flConVarSanityCheckTime = g_pEngine->GetHostTime() + 0.2f; |
|
} |
|
|
|
IGameEvent *CServerReplayContext::CreateReplaySessionInfoEvent() |
|
{ |
|
IGameEvent *pEvent = g_pGameEventManager->CreateEvent( "replay_sessioninfo", true ); |
|
if ( !pEvent ) |
|
return NULL; |
|
|
|
extern ConVar replay_block_dump_interval; |
|
|
|
// Fill event |
|
pEvent->SetString( "sn", m_pShared->m_pRecordingSessionManager->GetCurrentSessionName() ); |
|
pEvent->SetInt( "di", replay_block_dump_interval.GetInt() ); |
|
pEvent->SetInt( "cb", m_pShared->m_pRecordingSessionManager->GetCurrentSessionBlockIndex() ); |
|
pEvent->SetInt( "st", m_pSessionRecorder->GetCurrentRecordingStartTick() ); |
|
|
|
return pEvent; |
|
} |
|
|
|
IReplaySessionRecorder *CServerReplayContext::GetSessionRecorder() |
|
{ |
|
return g_pServerReplayContext->m_pSessionRecorder; |
|
} |
|
|
|
const char *CServerReplayContext::GetLocalFileServerPath() const |
|
{ |
|
static char s_szBuf[MAX_OSPATH]; |
|
extern ConVar replay_local_fileserver_path; |
|
|
|
// Fix up the path name - NOTE: We intentionally avoid calling V_FixupPathName(), which |
|
// pushes the entire output string to lower case. |
|
V_strncpy( s_szBuf, replay_local_fileserver_path.GetString(), sizeof( s_szBuf ) ); |
|
V_FixSlashes( s_szBuf ); |
|
V_RemoveDotSlashes( s_szBuf ); |
|
V_FixDoubleSlashes( s_szBuf ); |
|
|
|
V_StripTrailingSlash( s_szBuf ); |
|
V_AppendSlash( s_szBuf, sizeof( s_szBuf ) ); |
|
return s_szBuf; |
|
} |
|
|
|
void CServerReplayContext::CreateSessionOnClient( int nClientSlot ) |
|
{ |
|
// If we have a session (i.e. if we're recording) |
|
if ( SV_GetRecordingSessionInProgress() ) |
|
{ |
|
// Create the session on the client |
|
IGameEvent *pSessionInfoEvent = CreateReplaySessionInfoEvent(); |
|
g_pReplay->SV_SendReplayEvent( pSessionInfoEvent, nClientSlot ); |
|
} |
|
} |
|
|
|
const char *CServerReplayContext::GetServerSubDirName() const |
|
{ |
|
const char *pSubDirName = CommandLine()->ParmValue( "-replayserverdir" ); |
|
if ( !pSubDirName || !pSubDirName[0] ) |
|
{ |
|
Msg( "No '-replayserverdir' parameter found - using default replay folder.\n" ); |
|
return SUBDIR_SERVER; |
|
} |
|
|
|
Msg( "\n** Using custom replay dir name: \"%s%c%s\"\n\n", SUBDIR_REPLAY, CORRECT_PATH_SEPARATOR, pSubDirName ); |
|
|
|
return pSubDirName; |
|
} |
|
|
|
void CServerReplayContext::ReportErrorsToUser( wchar_t *pErrorText ) |
|
{ |
|
char szErrorText[4096]; |
|
g_pVGuiLocalize->ConvertUnicodeToANSI( pErrorText, szErrorText, sizeof( szErrorText ) ); |
|
|
|
static Color s_clrRed( 255, 0, 0 ); |
|
Warning( "\n-----------------------------------------------\n" ); |
|
Warning( "%s", szErrorText ); |
|
Warning( "-----------------------------------------------\n\n" ); |
|
} |
|
|
|
void CServerReplayContext::OnPublishFailed() |
|
{ |
|
// Don't report publish failure and shutdown publishing more than once per session. |
|
if ( !m_pSessionRecorder->RecordingAborted() ) |
|
{ |
|
m_bShouldAbortRecording = true; |
|
} |
|
} |
|
|
|
//---------------------------------------------------------------------------------------- |
|
|
|
CServerRecordingSession *SV_GetRecordingSessionInProgress() |
|
{ |
|
return SV_CastSession( SV_GetRecordingSessionManager()->GetRecordingSessionInProgress() ); |
|
} |
|
|
|
const char *SV_GetTmpDir() |
|
{ |
|
return Replay_va( "%s%s%c", SV_GetBasePath(), SUBDIR_TMP, CORRECT_PATH_SEPARATOR ); |
|
} |
|
|
|
bool SV_IsOffloadingEnabled() |
|
{ |
|
return false; |
|
} |
|
|
|
bool SV_RunJobToCompletion( CJob *pJob ) |
|
{ |
|
return RunJobToCompletion( SV_GetThreadPool(), pJob ); |
|
} |
|
|
|
//----------------------------------------------------------------------------------------
|
|
|