//========= 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 ); } //----------------------------------------------------------------------------------------