Team Fortress 2 Source Code as on 22/4/2020
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.

522 lines
14 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "replaysystem.h"
  5. #include "tier2/tier2.h"
  6. #include "iserver.h"
  7. #include "iclient.h"
  8. #include "icliententitylist.h"
  9. #include "igameevents.h"
  10. #include "replay/ireplaymovierenderer.h"
  11. #include "replay/ireplayscreenshotsystem.h"
  12. #include "replay/replayutils.h"
  13. #include "replay/replaylib.h"
  14. #include "sv_sessionrecorder.h"
  15. #include "sv_recordingsession.h"
  16. #include "cl_screenshotmanager.h"
  17. #include "netmessages.h"
  18. #include "thinkmanager.h"
  19. #include "managertest.h"
  20. #include "vprof.h"
  21. #include "sv_fileservercleanup.h"
  22. #if !defined( _X360 )
  23. #include "winlite.h"
  24. #include "xbox/xboxstubs.h"
  25. #endif
  26. // TODO: Deal with linux build includes
  27. #ifdef IS_WINDOWS_PC
  28. #include <winsock.h>
  29. #endif
  30. // memdbgon must be the last include file in a .cpp file!!!
  31. #include "tier0/memdbgon.h"
  32. //----------------------------------------------------------------------------------------
  33. #undef CreateEvent // Can't call IGameEventManager2::CreateEvent() without this
  34. //----------------------------------------------------------------------------------------
  35. #if !defined( DEDICATED )
  36. IEngineClientReplay *g_pEngineClient = NULL;
  37. CClientReplayContext *g_pClientReplayContextInternal = NULL;
  38. IVDebugOverlay *g_pDebugOverlay = NULL;
  39. IDownloadSystem *g_pDownloadSystem = NULL;
  40. #endif
  41. vgui::ILocalize *g_pVGuiLocalize = NULL;
  42. CServerReplayContext *g_pServerReplayContext = NULL;
  43. IClientReplay *g_pClient = NULL;
  44. IServerReplay *g_pServer = NULL;
  45. IEngineReplay *g_pEngine = NULL;
  46. IGameEventManager2 *g_pGameEventManager = NULL;
  47. IEngineTrace *g_pEngineTraceClient = NULL;
  48. IReplayDemoPlayer *g_pReplayDemoPlayer = NULL;
  49. IClientEntityList *entitylist = NULL; // icliententitylist.h forces the use of this name by externing in the header
  50. //----------------------------------------------------------------------------------------
  51. #define REPLAY_INIT( exp_ ) \
  52. if ( !( exp_ ) ) \
  53. { \
  54. Warning( "CReplaySystem::Connect() failed on: \"%s\"!\n", #exp_ ); \
  55. return false; \
  56. }
  57. //----------------------------------------------------------------------------------------
  58. class CReplaySystem : public CTier2AppSystem< IReplaySystem >
  59. {
  60. typedef CTier2AppSystem< IReplaySystem > BaseClass;
  61. public:
  62. virtual bool Connect( CreateInterfaceFn fnFactory )
  63. {
  64. REPLAY_INIT( fnFactory );
  65. REPLAY_INIT( BaseClass::Connect( fnFactory ) );
  66. ConVar_Register( FCVAR_CLIENTDLL );
  67. REPLAY_INIT( g_pFullFileSystem );
  68. g_pEngine = (IEngineReplay *)fnFactory( ENGINE_REPLAY_INTERFACE_VERSION, NULL );
  69. REPLAY_INIT( g_pEngine );
  70. REPLAY_INIT( g_pEngine->IsSupportedModAndPlatform() );
  71. #if !defined( DEDICATED )
  72. g_pEngineClient = (IEngineClientReplay *)fnFactory( ENGINE_REPLAY_CLIENT_INTERFACE_VERSION, NULL );
  73. REPLAY_INIT( g_pEngineClient );
  74. g_pEngineTraceClient = (IEngineTrace *)fnFactory( INTERFACEVERSION_ENGINETRACE_CLIENT, NULL );
  75. REPLAY_INIT( g_pEngineTraceClient );
  76. g_pReplayDemoPlayer = (IReplayDemoPlayer *)fnFactory( INTERFACEVERSION_REPLAYDEMOPLAYER, NULL );
  77. REPLAY_INIT( g_pReplayDemoPlayer );
  78. g_pVGuiLocalize = (vgui::ILocalize *)fnFactory( VGUI_LOCALIZE_INTERFACE_VERSION, NULL );
  79. REPLAY_INIT( g_pVGuiLocalize );
  80. g_pDebugOverlay = ( IVDebugOverlay * )fnFactory( VDEBUG_OVERLAY_INTERFACE_VERSION, NULL );
  81. REPLAY_INIT( g_pDebugOverlay );
  82. #endif
  83. g_pGameEventManager = (IGameEventManager2 *)fnFactory( INTERFACEVERSION_GAMEEVENTSMANAGER2, NULL );
  84. REPLAY_INIT( g_pGameEventManager );
  85. #if !defined( DEDICATED )
  86. g_pDownloadSystem = (IDownloadSystem *)fnFactory( INTERFACEVERSION_DOWNLOADSYSTEM, NULL );
  87. REPLAY_INIT( g_pDownloadSystem );
  88. // Create client context now if not running a dedicated server
  89. if ( !g_pEngine->IsDedicated() )
  90. {
  91. g_pClientReplayContextInternal = new CClientReplayContext();
  92. }
  93. // ...and create server replay context if we are
  94. else
  95. #endif
  96. {
  97. g_pServerReplayContext = new CServerReplayContext();
  98. }
  99. #if defined( DEDICATED )
  100. REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), NULL ) ) // Init without the client replay context
  101. #else
  102. REPLAY_INIT( ReplayLib_Init( g_pEngine->GetGameDir(), g_pClientReplayContextInternal ) );
  103. #endif
  104. Test();
  105. return true;
  106. }
  107. virtual void Disconnect()
  108. {
  109. BaseClass::Disconnect();
  110. }
  111. virtual InitReturnVal_t Init()
  112. {
  113. InitReturnVal_t nRetVal = BaseClass::Init();
  114. if ( nRetVal != INIT_OK )
  115. return nRetVal;
  116. return INIT_OK;
  117. }
  118. virtual void Shutdown()
  119. {
  120. BaseClass::Shutdown();
  121. #if !defined( DEDICATED )
  122. delete g_pClientReplayContextInternal;
  123. g_pClientReplayContextInternal = NULL;
  124. #endif
  125. delete g_pServerReplayContext;
  126. g_pServerReplayContext = NULL;
  127. }
  128. virtual void Think()
  129. {
  130. VPROF_BUDGET( "CReplaySystem::Think", VPROF_BUDGETGROUP_REPLAY );
  131. g_pThinkManager->Think();
  132. }
  133. virtual bool IsReplayEnabled()
  134. {
  135. extern ConVar replay_enable;
  136. return replay_enable.GetInt() != 0;
  137. }
  138. virtual bool IsRecording()
  139. {
  140. // NOTE: demoplayer->IsPlayingBack() needs to be checked here, as "replay_enable" and
  141. // "replay_recording" will inevitably get stored with signon data in any playing demo.
  142. // If the !demoplayer->IsPlayingBack() line is omitted below, Replay_IsRecording()
  143. // becomes useless during demo playback and will always return true.
  144. extern ConVar replay_recording;
  145. #if !defined( DEDICATED )
  146. return IsReplayEnabled() &&
  147. replay_recording.GetInt() &&
  148. !g_pEngineClient->IsDemoPlayingBack();
  149. #else
  150. return IsReplayEnabled() &&
  151. replay_recording.GetInt();
  152. #endif
  153. }
  154. //----------------------------------------------------------------------------------------
  155. // Client-specific implementation:
  156. //----------------------------------------------------------------------------------------
  157. virtual bool CL_Init( CreateInterfaceFn fnClientFactory )
  158. {
  159. #if !defined( DEDICATED )
  160. g_pClient = (IClientReplay *)fnClientFactory( CLIENT_REPLAY_INTERFACE_VERSION, NULL );
  161. if ( !g_pClient )
  162. return false;
  163. entitylist = (IClientEntityList *)fnClientFactory( VCLIENTENTITYLIST_INTERFACE_VERSION, NULL );
  164. if ( !entitylist )
  165. return false;
  166. if ( !g_pClientReplayContextInternal->Init( fnClientFactory ) )
  167. return false;
  168. return true;
  169. #else
  170. return false;
  171. #endif
  172. }
  173. virtual void CL_Shutdown()
  174. {
  175. #if !defined( DEDICATED )
  176. if ( g_pClientReplayContextInternal && g_pClientReplayContextInternal->IsInitialized() )
  177. {
  178. g_pClientReplayContextInternal->Shutdown();
  179. }
  180. #endif
  181. }
  182. virtual void CL_Render()
  183. {
  184. #if !defined( DEDICATED )
  185. // If the replay system wants to take a screenshot, do it now
  186. if ( g_pClientReplayContextInternal->m_pScreenshotManager->ShouldCaptureScreenshot() )
  187. {
  188. g_pClientReplayContextInternal->m_pScreenshotManager->DoCaptureScreenshot();
  189. return;
  190. }
  191. // Currently rendering? NOTE: GetMovieRenderer() only returns a valid ptr during rendering
  192. IReplayMovieRenderer *pReplayMovieRenderer = g_pClientReplayContextInternal->GetMovieRenderer();
  193. if ( !pReplayMovieRenderer )
  194. return;
  195. pReplayMovieRenderer->RenderVideo();
  196. #endif
  197. }
  198. virtual IClientReplayContext *CL_GetContext()
  199. {
  200. #if !defined( DEDICATED )
  201. return g_pClientReplayContextInternal;
  202. #else
  203. return NULL;
  204. #endif
  205. }
  206. //----------------------------------------------------------------------------------------
  207. // Server-specific implementation:
  208. //----------------------------------------------------------------------------------------
  209. virtual bool SV_Init( CreateInterfaceFn fnServerFactory )
  210. {
  211. if ( !g_pEngine->IsDedicated() || !g_pServerReplayContext )
  212. return false;
  213. g_pServer = (IServerReplay *)fnServerFactory( SERVER_REPLAY_INTERFACE_VERSION, NULL );
  214. if ( !g_pServer )
  215. return false;
  216. Assert( !ReplayServer() );
  217. return g_pServerReplayContext->Init( fnServerFactory );
  218. }
  219. virtual void SV_Shutdown()
  220. {
  221. if ( g_pServerReplayContext && g_pServerReplayContext->IsInitialized() )
  222. {
  223. g_pServerReplayContext->Shutdown();
  224. }
  225. }
  226. virtual IServerReplayContext *SV_GetContext()
  227. {
  228. return g_pServerReplayContext;
  229. }
  230. virtual bool SV_ShouldBeginRecording( bool bIsInWaitingForPlayers )
  231. {
  232. extern ConVar replay_enable;
  233. return !bIsInWaitingForPlayers &&
  234. #if !defined( DEDICATED )
  235. !g_pEngineClient->IsPlayingReplayDemo() &&
  236. #endif
  237. replay_enable.GetBool();
  238. }
  239. virtual void SV_NotifyReplayRequested()
  240. {
  241. if ( !g_pEngine->IsSupportedModAndPlatform() )
  242. return;
  243. CServerRecordingSession *pSession = SV_GetRecordingSessionInProgress();
  244. if ( !pSession )
  245. return;
  246. // A replay was requested - notify the session so we don't throw it away at the end of the round
  247. pSession->NotifyReplayRequested();
  248. }
  249. virtual void SV_SendReplayEvent( const char *pEventName, int nClientSlot )
  250. {
  251. // Attempt to create the event
  252. IGameEvent *pEvent = g_pGameEventManager->CreateEvent( pEventName, true );
  253. if ( !pEvent )
  254. return;
  255. SV_SendReplayEvent( pEvent, nClientSlot );
  256. }
  257. virtual void SV_SendReplayEvent( IGameEvent *pEvent, int nClientSlot/*=-1*/ )
  258. {
  259. IServer *pGameServer = g_pEngine->GetGameServer();
  260. if ( !pEvent )
  261. return;
  262. // Write event info to SVC_GameEvent msg
  263. char buffer_data[MAX_EVENT_BYTES];
  264. SVC_GameEvent msg;
  265. msg.SetReliable( false );
  266. msg.m_DataOut.StartWriting( buffer_data, sizeof( buffer_data ) );
  267. if ( !g_pGameEventManager->SerializeEvent( pEvent, &msg.m_DataOut ) )
  268. {
  269. DevMsg( "Replay_SendReplayEvent(): failed to serialize event '%s'.\n", pEvent->GetName() );
  270. goto free_event;
  271. }
  272. // Send to all clients?
  273. if ( nClientSlot == -1 )
  274. {
  275. for ( int i = 0; i < pGameServer->GetClientCount(); ++i )
  276. {
  277. IClient *pClient = pGameServer->GetClient( i );
  278. if ( pClient )
  279. {
  280. // Send the message
  281. pClient->SendNetMsg( msg );
  282. }
  283. }
  284. }
  285. else // Send to just the one client?
  286. {
  287. IClient *pClient = pGameServer->GetClient( nClientSlot );
  288. if ( pClient )
  289. {
  290. // Send the message
  291. pClient->SendNetMsg( msg );
  292. }
  293. }
  294. free_event:
  295. g_pGameEventManager->FreeEvent( pEvent );
  296. }
  297. virtual void SV_EndRecordingSession( bool bForceSynchronousPublish/*=false*/ )
  298. {
  299. if ( !g_pEngine->IsSupportedModAndPlatform() )
  300. return;
  301. if ( !ReplayServer() )
  302. return;
  303. SV_GetSessionRecorder()->StopRecording( false );
  304. if ( bForceSynchronousPublish )
  305. {
  306. // Publish all files
  307. SV_GetSessionRecorder()->PublishAllSynchronous();
  308. // This should unlock all sessions
  309. SV_GetSessionRecorder()->UpdateSessionLocks();
  310. // Let the session manager do any cleanup - this will remove files associated with a ditched
  311. // session. For example, if the server was shut down in the middle of a round where no one
  312. // saved a replay, the files will be published synchronously above and then cleaned up
  313. // synchronously here.
  314. SV_GetRecordingSessionManager()->DoSessionCleanup();
  315. // Since the recording session manager will kick off a cleanup job, we need to wait for it
  316. // here since we're shutting down.
  317. SV_GetFileserverCleaner()->BlockForCompletion();
  318. }
  319. }
  320. void Test()
  321. {
  322. #if !defined( DEDICATED ) && _DEBUG
  323. // This gets called after interfaces are hooked up, and before any of the
  324. // internal replay systems get init'd.
  325. CTestManager::Test();
  326. #endif
  327. }
  328. };
  329. //----------------------------------------------------------------------------------------
  330. static CReplaySystem s_Replay;
  331. IReplaySystem *g_pReplay = &s_Replay;
  332. //----------------------------------------------------------------------------------------
  333. EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CReplaySystem, IReplaySystem, REPLAY_INTERFACE_VERSION,
  334. s_Replay );
  335. //----------------------------------------------------------------------------------------
  336. void Replay_MsgBox( const char *pText )
  337. {
  338. g_pClient->DisplayReplayMessage( pText, false, true, NULL );
  339. }
  340. void Replay_MsgBox( const wchar_t *pText )
  341. {
  342. g_pClient->DisplayReplayMessage( pText, false, true, NULL );
  343. }
  344. const char *Replay_GetDownloadURLPath()
  345. {
  346. static char s_szDownloadURLPath[MAX_OSPATH];
  347. extern ConVar replay_fileserver_path; // NOTE: replicated
  348. V_strcpy_safe( s_szDownloadURLPath, replay_fileserver_path.GetString() );
  349. V_StripTrailingSlash( s_szDownloadURLPath );
  350. V_FixSlashes( s_szDownloadURLPath, '/' );
  351. // Get rid of starting slash
  352. if ( s_szDownloadURLPath[0] == '/' )
  353. return &s_szDownloadURLPath[1];
  354. return s_szDownloadURLPath;
  355. }
  356. const char *Replay_GetDownloadURL()
  357. {
  358. #if 0
  359. // Get the local host name
  360. char szHostname[MAX_OSPATH];
  361. if ( gethostname( szHostname, sizeof( szHostname ) ) == -1 )
  362. {
  363. Error( "Failed to send to Replay to client - couldn't get local IP.\n" );
  364. return "";
  365. }
  366. #endif
  367. // Construct the URL based on replicated cvars
  368. static char s_szFileURL[ MAX_OSPATH ];
  369. extern ConVar replay_fileserver_protocol;
  370. extern ConVar replay_fileserver_host;
  371. extern ConVar replay_fileserver_port;
  372. V_snprintf(
  373. s_szFileURL, sizeof( s_szFileURL ),
  374. "%s://%s:%i/%s/",
  375. replay_fileserver_protocol.GetString(),
  376. replay_fileserver_host.GetString(),
  377. replay_fileserver_port.GetInt(),
  378. Replay_GetDownloadURLPath()
  379. );
  380. // Cleanup
  381. V_FixDoubleSlashes( s_szFileURL + V_strlen("http://") );
  382. return s_szFileURL;
  383. }
  384. //----------------------------------------------------------------------------------------
  385. // Purpose: (client/server) Crack a URL into a base and a path
  386. // NOTE: the URL *must contain a port* !
  387. //
  388. // Example: http://some.base.url:8080/a/path/here.txt cracks into:
  389. // pBaseURL = "http://some.base.url:8080"
  390. // pURLPath = "/a/path/here.txt"
  391. //----------------------------------------------------------------------------------------
  392. void Replay_CrackURL( const char *pURL, char *pBaseURLOut, char *pURLPathOut )
  393. {
  394. const char *pColon;
  395. const char *pURLPath;
  396. // Must at least have "http://"
  397. if ( V_strlen(pURL) < 6 )
  398. goto fail;
  399. // Skip protocol ':' (eg http://)
  400. pColon = V_strstr( pURL, ":" );
  401. if ( !pColon )
  402. goto fail;
  403. // Find next colon
  404. pColon = V_strstr( pColon + 1, ":" );
  405. if ( !pColon )
  406. goto fail;
  407. // Copies "http[s]://<address>:<port>
  408. pURLPath = V_strstr( pColon, "/" );
  409. V_strncpy( pBaseURLOut, pURL, pURLPath - pURL + 1 );
  410. V_strcpy( pURLPathOut, pURLPath );
  411. return;
  412. fail:
  413. AssertMsg( 0, "Replay_CrackURL() was passed an invalid URL and has failed. This should never happen." );
  414. }
  415. #ifndef DEDICATED
  416. void Replay_HudMsg( const char *pText, const char *pSound, bool bUrgent )
  417. {
  418. g_pClient->DisplayReplayMessage( pText, bUrgent, false, pSound );
  419. }
  420. #endif
  421. //----------------------------------------------------------------------------------------