summaryrefslogtreecommitdiff
path: root/replay/replaysystem.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /replay/replaysystem.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'replay/replaysystem.cpp')
-rw-r--r--replay/replaysystem.cpp522
1 files changed, 522 insertions, 0 deletions
diff --git a/replay/replaysystem.cpp b/replay/replaysystem.cpp
new file mode 100644
index 0000000..c268773
--- /dev/null
+++ b/replay/replaysystem.cpp
@@ -0,0 +1,522 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "replaysystem.h"
+#include "tier2/tier2.h"
+#include "iserver.h"
+#include "iclient.h"
+#include "icliententitylist.h"
+#include "igameevents.h"
+#include "replay/ireplaymovierenderer.h"
+#include "replay/ireplayscreenshotsystem.h"
+#include "replay/replayutils.h"
+#include "replay/replaylib.h"
+#include "sv_sessionrecorder.h"
+#include "sv_recordingsession.h"
+#include "cl_screenshotmanager.h"
+#include "netmessages.h"
+#include "thinkmanager.h"
+#include "managertest.h"
+#include "vprof.h"
+#include "sv_fileservercleanup.h"
+
+#if !defined( _X360 )
+#include "winlite.h"
+#include "xbox/xboxstubs.h"
+#endif
+
+// TODO: Deal with linux build includes
+#ifdef IS_WINDOWS_PC
+#include <winsock.h>
+#endif
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//----------------------------------------------------------------------------------------
+
+#undef CreateEvent // Can't call IGameEventManager2::CreateEvent() without this
+
+//----------------------------------------------------------------------------------------
+
+#if !defined( DEDICATED )
+IEngineClientReplay *g_pEngineClient = NULL;
+CClientReplayContext *g_pClientReplayContextInternal = NULL;
+IVDebugOverlay *g_pDebugOverlay = NULL;
+IDownloadSystem *g_pDownloadSystem = NULL;
+#endif
+
+vgui::ILocalize *g_pVGuiLocalize = NULL;
+CServerReplayContext *g_pServerReplayContext = NULL;
+IClientReplay *g_pClient = NULL;
+IServerReplay *g_pServer = NULL;
+IEngineReplay *g_pEngine = NULL;
+IGameEventManager2 *g_pGameEventManager = NULL;
+IEngineTrace *g_pEngineTraceClient = NULL;
+IReplayDemoPlayer *g_pReplayDemoPlayer = NULL;
+IClientEntityList *entitylist = NULL; // icliententitylist.h forces the use of this name by externing in the header
+
+//----------------------------------------------------------------------------------------
+
+#define REPLAY_INIT( exp_ ) \
+ if ( !( exp_ ) ) \
+ { \
+ Warning( "CReplaySystem::Connect() failed on: \"%s\"!\n", #exp_ ); \
+ return false; \
+ }
+
+//----------------------------------------------------------------------------------------
+
+class CReplaySystem : public CTier2AppSystem< IReplaySystem >
+{
+ typedef CTier2AppSystem< IReplaySystem > BaseClass;
+
+public:
+ virtual bool Connect( CreateInterfaceFn fnFactory )
+ {
+ REPLAY_INIT( fnFactory );
+ REPLAY_INIT( BaseClass::Connect( fnFactory ) );
+
+ ConVar_Register( FCVAR_CLIENTDLL );
+
+ REPLAY_INIT( g_pFullFileSystem );
+
+ g_pEngine = (IEngineReplay *)fnFactory( ENGINE_REPLAY_INTERFACE_VERSION, NULL );
+ REPLAY_INIT( g_pEngine );
+
+ REPLAY_INIT( g_pEngine->IsSupportedModAndPlatform() );
+
+#if !defined( DEDICATED )
+ g_pEngineClient = (IEngineClientReplay *)fnFactory( ENGINE_REPLAY_CLIENT_INTERFACE_VERSION, NULL );
+ REPLAY_INIT( g_pEngineClient );
+
+ g_pEngineTraceClient = (IEngineTrace *)fnFactory( INTERFACEVERSION_ENGINETRACE_CLIENT, NULL );
+ REPLAY_INIT( g_pEngineTraceClient );
+
+ g_pReplayDemoPlayer = (IReplayDemoPlayer *)fnFactory( INTERFACEVERSION_REPLAYDEMOPLAYER, NULL );
+ REPLAY_INIT( g_pReplayDemoPlayer );
+
+ g_pVGuiLocalize = (vgui::ILocalize *)fnFactory( VGUI_LOCALIZE_INTERFACE_VERSION, NULL );
+ REPLAY_INIT( g_pVGuiLocalize );
+
+ g_pDebugOverlay = ( IVDebugOverlay * )fnFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL );
+ REPLAY_INIT( g_pDebugOverlay );
+#endif
+
+ g_pGameEventManager = (IGameEventManager2 *)fnFactory( INTERFACEVERSION_GAMEEVENTSMANAGER2, NULL );
+ REPLAY_INIT( g_pGameEventManager );
+
+#if !defined( DEDICATED )
+ g_pDownloadSystem = (IDownloadSystem *)fnFactory( INTERFACEVERSION_DOWNLOADSYSTEM, NULL );
+ REPLAY_INIT( g_pDownloadSystem );
+
+ // Create client context now if not running a dedicated server
+ if ( !g_pEngine->IsDedicated() )
+ {
+ g_pClientReplayContextInternal = new CClientReplayContext();
+ }
+
+ // ...and create server replay context if we are
+ else
+#endif
+ {
+ g_pServerReplayContext = new CServerReplayContext();
+ }
+
+#if defined( DEDICATED )
+ REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), NULL ) ) // Init without the client replay context
+#else
+ REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), g_pClientReplayContextInternal ) );
+#endif
+
+ Test();
+
+ return true;
+ }
+
+ virtual void Disconnect()
+ {
+ BaseClass::Disconnect();
+ }
+
+ virtual InitReturnVal_t Init()
+ {
+ InitReturnVal_t nRetVal = BaseClass::Init();
+ if ( nRetVal != INIT_OK )
+ return nRetVal;
+
+ return INIT_OK;
+ }
+
+ virtual void Shutdown()
+ {
+ BaseClass::Shutdown();
+
+#if !defined( DEDICATED )
+ delete g_pClientReplayContextInternal;
+ g_pClientReplayContextInternal = NULL;
+#endif
+
+ delete g_pServerReplayContext;
+ g_pServerReplayContext = NULL;
+ }
+
+ virtual void Think()
+ {
+ VPROF_BUDGET( "CReplaySystem::Think", VPROF_BUDGETGROUP_REPLAY );
+
+ g_pThinkManager->Think();
+ }
+
+ virtual bool IsReplayEnabled()
+ {
+ extern ConVar replay_enable;
+ return replay_enable.GetInt() != 0;
+ }
+
+ virtual bool IsRecording()
+ {
+ // NOTE: demoplayer->IsPlayingBack() needs to be checked here, as "replay_enable" and
+ // "replay_recording" will inevitably get stored with signon data in any playing demo.
+ // If the !demoplayer->IsPlayingBack() line is omitted below, Replay_IsRecording()
+ // becomes useless during demo playback and will always return true.
+ extern ConVar replay_recording;
+#if !defined( DEDICATED )
+ return IsReplayEnabled() &&
+ replay_recording.GetInt() &&
+ !g_pEngineClient->IsDemoPlayingBack();
+#else
+ return IsReplayEnabled() &&
+ replay_recording.GetInt();
+#endif
+ }
+
+ //----------------------------------------------------------------------------------------
+ // Client-specific implementation:
+ //----------------------------------------------------------------------------------------
+
+ virtual bool CL_Init( CreateInterfaceFn fnClientFactory )
+ {
+#if !defined( DEDICATED )
+ g_pClient = (IClientReplay *)fnClientFactory( CLIENT_REPLAY_INTERFACE_VERSION, NULL );
+ if ( !g_pClient )
+ return false;
+
+ entitylist = (IClientEntityList *)fnClientFactory( VCLIENTENTITYLIST_INTERFACE_VERSION, NULL );
+ if ( !entitylist )
+ return false;
+
+ if ( !g_pClientReplayContextInternal->Init( fnClientFactory ) )
+ return false;
+
+ return true;
+#else
+ return false;
+#endif
+ }
+
+ virtual void CL_Shutdown()
+ {
+#if !defined( DEDICATED )
+ if ( g_pClientReplayContextInternal && g_pClientReplayContextInternal->IsInitialized() )
+ {
+ g_pClientReplayContextInternal->Shutdown();
+ }
+#endif
+ }
+
+ virtual void CL_Render()
+ {
+#if !defined( DEDICATED )
+ // If the replay system wants to take a screenshot, do it now
+ if ( g_pClientReplayContextInternal->m_pScreenshotManager->ShouldCaptureScreenshot() )
+ {
+ g_pClientReplayContextInternal->m_pScreenshotManager->DoCaptureScreenshot();
+ return;
+ }
+
+ // Currently rendering? NOTE: GetMovieRenderer() only returns a valid ptr during rendering
+ IReplayMovieRenderer *pReplayMovieRenderer = g_pClientReplayContextInternal->GetMovieRenderer();
+ if ( !pReplayMovieRenderer )
+ return;
+
+ pReplayMovieRenderer->RenderVideo();
+#endif
+ }
+
+ virtual IClientReplayContext *CL_GetContext()
+ {
+#if !defined( DEDICATED )
+ return g_pClientReplayContextInternal;
+#else
+ return NULL;
+#endif
+ }
+
+ //----------------------------------------------------------------------------------------
+ // Server-specific implementation:
+ //----------------------------------------------------------------------------------------
+
+ virtual bool SV_Init( CreateInterfaceFn fnServerFactory )
+ {
+ if ( !g_pEngine->IsDedicated() || !g_pServerReplayContext )
+ return false;
+
+ g_pServer = (IServerReplay *)fnServerFactory( SERVER_REPLAY_INTERFACE_VERSION, NULL );
+ if ( !g_pServer )
+ return false;
+
+ Assert( !ReplayServer() );
+
+ return g_pServerReplayContext->Init( fnServerFactory );
+ }
+
+ virtual void SV_Shutdown()
+ {
+ if ( g_pServerReplayContext && g_pServerReplayContext->IsInitialized() )
+ {
+ g_pServerReplayContext->Shutdown();
+ }
+ }
+
+ virtual IServerReplayContext *SV_GetContext()
+ {
+ return g_pServerReplayContext;
+ }
+
+ virtual bool SV_ShouldBeginRecording( bool bIsInWaitingForPlayers )
+ {
+ extern ConVar replay_enable;
+
+ return !bIsInWaitingForPlayers &&
+#if !defined( DEDICATED )
+ !g_pEngineClient->IsPlayingReplayDemo() &&
+#endif
+ replay_enable.GetBool();
+ }
+
+ virtual void SV_NotifyReplayRequested()
+ {
+ if ( !g_pEngine->IsSupportedModAndPlatform() )
+ return;
+
+ CServerRecordingSession *pSession = SV_GetRecordingSessionInProgress();
+ if ( !pSession )
+ return;
+
+ // A replay was requested - notify the session so we don't throw it away at the end of the round
+ pSession->NotifyReplayRequested();
+ }
+
+ virtual void SV_SendReplayEvent( const char *pEventName, int nClientSlot )
+ {
+ // Attempt to create the event
+ IGameEvent *pEvent = g_pGameEventManager->CreateEvent( pEventName, true );
+ if ( !pEvent )
+ return;
+
+ SV_SendReplayEvent( pEvent, nClientSlot );
+ }
+
+ virtual void SV_SendReplayEvent( IGameEvent *pEvent, int nClientSlot/*=-1*/ )
+ {
+ IServer *pGameServer = g_pEngine->GetGameServer();
+
+ if ( !pEvent )
+ return;
+
+ // Write event info to SVC_GameEvent msg
+ char buffer_data[MAX_EVENT_BYTES];
+ SVC_GameEvent msg;
+ msg.SetReliable( false );
+ msg.m_DataOut.StartWriting( buffer_data, sizeof( buffer_data ) );
+ if ( !g_pGameEventManager->SerializeEvent( pEvent, &msg.m_DataOut ) )
+ {
+ DevMsg( "Replay_SendReplayEvent(): failed to serialize event '%s'.\n", pEvent->GetName() );
+ goto free_event;
+ }
+
+ // Send to all clients?
+ if ( nClientSlot == -1 )
+ {
+ for ( int i = 0; i < pGameServer->GetClientCount(); ++i )
+ {
+ IClient *pClient = pGameServer->GetClient( i );
+ if ( pClient )
+ {
+ // Send the message
+ pClient->SendNetMsg( msg );
+ }
+ }
+ }
+ else // Send to just the one client?
+ {
+ IClient *pClient = pGameServer->GetClient( nClientSlot );
+ if ( pClient )
+ {
+ // Send the message
+ pClient->SendNetMsg( msg );
+ }
+ }
+
+ free_event:
+ g_pGameEventManager->FreeEvent( pEvent );
+ }
+
+ virtual void SV_EndRecordingSession( bool bForceSynchronousPublish/*=false*/ )
+ {
+ if ( !g_pEngine->IsSupportedModAndPlatform() )
+ return;
+
+ if ( !ReplayServer() )
+ return;
+
+ SV_GetSessionRecorder()->StopRecording( false );
+
+ if ( bForceSynchronousPublish )
+ {
+ // Publish all files
+ SV_GetSessionRecorder()->PublishAllSynchronous();
+
+ // This should unlock all sessions
+ SV_GetSessionRecorder()->UpdateSessionLocks();
+
+ // Let the session manager do any cleanup - this will remove files associated with a ditched
+ // session. For example, if the server was shut down in the middle of a round where no one
+ // saved a replay, the files will be published synchronously above and then cleaned up
+ // synchronously here.
+ SV_GetRecordingSessionManager()->DoSessionCleanup();
+
+ // Since the recording session manager will kick off a cleanup job, we need to wait for it
+ // here since we're shutting down.
+ SV_GetFileserverCleaner()->BlockForCompletion();
+ }
+ }
+
+ void Test()
+ {
+#if !defined( DEDICATED ) && _DEBUG
+ // This gets called after interfaces are hooked up, and before any of the
+ // internal replay systems get init'd.
+ CTestManager::Test();
+#endif
+ }
+};
+
+//----------------------------------------------------------------------------------------
+
+static CReplaySystem s_Replay;
+IReplaySystem *g_pReplay = &s_Replay;
+
+//----------------------------------------------------------------------------------------
+
+EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CReplaySystem, IReplaySystem, REPLAY_INTERFACE_VERSION,
+ s_Replay );
+
+//----------------------------------------------------------------------------------------
+
+void Replay_MsgBox( const char *pText )
+{
+ g_pClient->DisplayReplayMessage( pText, false, true, NULL );
+}
+
+void Replay_MsgBox( const wchar_t *pText )
+{
+ g_pClient->DisplayReplayMessage( pText, false, true, NULL );
+}
+
+const char *Replay_GetDownloadURLPath()
+{
+ static char s_szDownloadURLPath[MAX_OSPATH];
+ extern ConVar replay_fileserver_path; // NOTE: replicated
+
+ V_strcpy_safe( s_szDownloadURLPath, replay_fileserver_path.GetString() );
+ V_StripTrailingSlash( s_szDownloadURLPath );
+ V_FixSlashes( s_szDownloadURLPath, '/' );
+
+ // Get rid of starting slash
+ if ( s_szDownloadURLPath[0] == '/' )
+ return &s_szDownloadURLPath[1];
+
+ return s_szDownloadURLPath;
+}
+
+const char *Replay_GetDownloadURL()
+{
+#if 0
+ // Get the local host name
+ char szHostname[MAX_OSPATH];
+ if ( gethostname( szHostname, sizeof( szHostname ) ) == -1 )
+ {
+ Error( "Failed to send to Replay to client - couldn't get local IP.\n" );
+ return "";
+ }
+#endif
+
+ // Construct the URL based on replicated cvars
+ static char s_szFileURL[ MAX_OSPATH ];
+ extern ConVar replay_fileserver_protocol;
+ extern ConVar replay_fileserver_host;
+ extern ConVar replay_fileserver_port;
+ V_snprintf(
+ s_szFileURL, sizeof( s_szFileURL ),
+ "%s://%s:%i/%s/",
+ replay_fileserver_protocol.GetString(),
+ replay_fileserver_host.GetString(),
+ replay_fileserver_port.GetInt(),
+ Replay_GetDownloadURLPath()
+ );
+
+ // Cleanup
+ V_FixDoubleSlashes( s_szFileURL + V_strlen("http://") );
+
+ return s_szFileURL;
+}
+
+//----------------------------------------------------------------------------------------
+// Purpose: (client/server) Crack a URL into a base and a path
+// NOTE: the URL *must contain a port* !
+//
+// Example: http://some.base.url:8080/a/path/here.txt cracks into:
+// pBaseURL = "http://some.base.url:8080"
+// pURLPath = "/a/path/here.txt"
+//----------------------------------------------------------------------------------------
+void Replay_CrackURL( const char *pURL, char *pBaseURLOut, char *pURLPathOut )
+{
+ const char *pColon;
+ const char *pURLPath;
+
+ // Must at least have "http://"
+ if ( V_strlen(pURL) < 6 )
+ goto fail;
+
+ // Skip protocol ':' (eg http://)
+ pColon = V_strstr( pURL, ":" );
+ if ( !pColon )
+ goto fail;
+
+ // Find next colon
+ pColon = V_strstr( pColon + 1, ":" );
+ if ( !pColon )
+ goto fail;
+
+ // Copies "http[s]://<address>:<port>
+ pURLPath = V_strstr( pColon, "/" );
+ V_strncpy( pBaseURLOut, pURL, pURLPath - pURL + 1 );
+ V_strcpy( pURLPathOut, pURLPath );
+
+ return;
+
+fail:
+ AssertMsg( 0, "Replay_CrackURL() was passed an invalid URL and has failed. This should never happen." );
+}
+
+#ifndef DEDICATED
+void Replay_HudMsg( const char *pText, const char *pSound, bool bUrgent )
+{
+ g_pClient->DisplayReplayMessage( pText, bUrgent, false, pSound );
+}
+#endif
+
+//----------------------------------------------------------------------------------------