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.

326 lines
8.9 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "sv_replaycontext.h"
  5. #include "replay/shared_defs.h" // BUILD_CURL defined here
  6. #include "sv_sessionrecorder.h"
  7. #include "sv_fileservercleanup.h"
  8. #include "sv_recordingsession.h"
  9. #include "sv_publishtest.h"
  10. #include "replaysystem.h"
  11. #include "icommandline.h"
  12. #if BUILD_CURL
  13. #include "curl/curl.h"
  14. #endif
  15. // memdbgon must be the last include file in a .cpp file!!!
  16. #include "tier0/memdbgon.h"
  17. //----------------------------------------------------------------------------------------
  18. #undef CreateEvent
  19. //----------------------------------------------------------------------------------------
  20. CServerReplayContext::CServerReplayContext()
  21. : m_pSessionRecorder( NULL ),
  22. m_pFileserverCleaner( NULL ),
  23. m_bShouldAbortRecording( false ),
  24. m_flConVarSanityCheckTime( 0.0f )
  25. {
  26. }
  27. CServerReplayContext::~CServerReplayContext()
  28. {
  29. delete m_pSessionRecorder;
  30. delete m_pFileserverCleaner;
  31. }
  32. bool CServerReplayContext::Init( CreateInterfaceFn fnFactory )
  33. {
  34. #if BUILD_CURL
  35. // Initialize cURL - in windows, this will init the winsock stuff.
  36. curl_global_init( CURL_GLOBAL_ALL );
  37. #endif
  38. m_pShared = new CSharedReplayContext( this );
  39. m_pShared->m_strSubDir = GetServerSubDirName();
  40. m_pShared->m_pRecordingSessionManager = new CServerRecordingSessionManager( this );
  41. m_pShared->m_pRecordingSessionBlockManager = new CServerRecordingSessionBlockManager( this );
  42. m_pShared->m_pErrorSystem = new CErrorSystem( this );
  43. m_pShared->Init( fnFactory );
  44. // Create directory for temp files
  45. CFmtStr fmtTmpDir( "%s%s", SV_GetBasePath(), SUBDIR_TMP );
  46. g_pFullFileSystem->CreateDirHierarchy( fmtTmpDir.Access() );
  47. // Remove any extraneous files from the temp directory
  48. CleanTmpDir();
  49. m_pSessionRecorder = new CSessionRecorder();
  50. m_pSessionRecorder->Init();
  51. m_pFileserverCleaner = new CFileserverCleaner();
  52. return true;
  53. }
  54. void CServerReplayContext::CleanTmpDir()
  55. {
  56. int nFilesRemoved = 0;
  57. Log( "Cleaning files from temp dir, \"%s\" ...", SV_GetTmpDir() );
  58. FileFindHandle_t hFind;
  59. CFmtStr fmtPath( "%s*", SV_GetTmpDir() );
  60. const char *pFilename = g_pFullFileSystem->FindFirst( fmtPath.Access(), &hFind );
  61. while ( pFilename )
  62. {
  63. if ( pFilename[0] != '.' )
  64. {
  65. // Remove the file
  66. CFmtStr fmtFullFilename( "%s%s", SV_GetTmpDir(), pFilename );
  67. g_pFullFileSystem->RemoveFile( fmtFullFilename.Access() );
  68. ++nFilesRemoved;
  69. }
  70. // Get next file
  71. pFilename = g_pFullFileSystem->FindNext( hFind );
  72. }
  73. if ( nFilesRemoved )
  74. {
  75. Log( "%i %s removed.\n", nFilesRemoved, nFilesRemoved == 1 ? "file" : "files" );
  76. }
  77. else
  78. {
  79. Log( "no files removed.\n" );
  80. }
  81. }
  82. void CServerReplayContext::Shutdown()
  83. {
  84. m_pShared->Shutdown();
  85. #if BUILD_CURL
  86. // Shutdown cURL
  87. curl_global_cleanup();
  88. #endif
  89. }
  90. void CServerReplayContext::Think()
  91. {
  92. ConVarSanityThink();
  93. if ( !g_pReplay->IsReplayEnabled() )
  94. return;
  95. if ( m_bShouldAbortRecording )
  96. {
  97. g_pBlockSpewer->PrintBlockStart();
  98. g_pBlockSpewer->PrintMsg( "Replay recording shutting down due to publishing error! Recording will begin" );
  99. g_pBlockSpewer->PrintMsg( "at the beginning of the next round, but may fail again." );
  100. g_pBlockSpewer->PrintBlockEnd();
  101. // Shutdown recording
  102. m_pSessionRecorder->AbortCurrentSessionRecording();
  103. m_bShouldAbortRecording = false;
  104. }
  105. m_pShared->Think();
  106. }
  107. void CServerReplayContext::ConVarSanityThink()
  108. {
  109. if ( m_flConVarSanityCheckTime == 0.0f )
  110. return;
  111. DoSanityCheckNow();
  112. }
  113. void CServerReplayContext::UpdateFileserverIPFromHostname( const char *pHostname )
  114. {
  115. if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, m_szFileserverIP, sizeof( m_szFileserverIP ) ) )
  116. {
  117. V_strcpy( m_szFileserverIP, "0.0.0.0" );
  118. Log( "ERROR: Could not resolve fileserver hostname \"%s\" !\n", pHostname );
  119. return;
  120. }
  121. Log( "Cached resolved fileserver hostname to IP address: \"%s\" -> \"%s\"\n", pHostname, m_szFileserverIP );
  122. }
  123. void CServerReplayContext::UpdateFileserverProxyIPFromHostname( const char *pHostname )
  124. {
  125. if ( !g_pEngine->NET_GetHostnameAsIP( pHostname, m_szFileserverProxyIP, sizeof( m_szFileserverProxyIP ) ) )
  126. {
  127. V_strcpy( m_szFileserverProxyIP, "0.0.0.0" );
  128. Log( "ERROR: Could not resolve fileserver proxy hostname \"%s\" !\n", pHostname );
  129. return;
  130. }
  131. Log( "Cached resolved fileserver proxy hostname to IP address: \"%s\" -> \"%s\"\n", pHostname, m_szFileserverProxyIP );
  132. }
  133. void CServerReplayContext::DoSanityCheckNow()
  134. {
  135. // Check now?
  136. if ( m_flConVarSanityCheckTime <= g_pEngine->GetHostTime() )
  137. {
  138. // Reset
  139. m_flConVarSanityCheckTime = 0.0f;
  140. g_pBlockSpewer->PrintBlockStart();
  141. extern ConVar replay_enable;
  142. if ( replay_enable.GetBool() )
  143. {
  144. // Test publish
  145. const bool bPublishResult = SV_DoTestPublish();
  146. g_pBlockSpewer->PrintEmptyLine();
  147. if ( bPublishResult )
  148. {
  149. g_pBlockSpewer->PrintEmptyLine();
  150. g_pBlockSpewer->PrintEmptyLine();
  151. g_pBlockSpewer->PrintMsg( "SUCCESS - REPLAY IS ENABLED!" );
  152. g_pBlockSpewer->PrintEmptyLine();
  153. g_pBlockSpewer->PrintMsg( "A 'changelevel' or 'map' is required - recording will" );
  154. g_pBlockSpewer->PrintMsg( "begin at the start of the next round." );
  155. g_pBlockSpewer->PrintEmptyLine();
  156. }
  157. else
  158. {
  159. replay_enable.SetValue( 0 );
  160. g_pBlockSpewer->PrintEmptyLine();
  161. g_pBlockSpewer->PrintMsg( "FAILURE - REPLAY DISABLED! \"replay_enable\" is now 0." );
  162. g_pBlockSpewer->PrintEmptyLine();
  163. g_pBlockSpewer->PrintEmptyLine();
  164. g_pBlockSpewer->PrintMsg( "Address any failures above and re-exec replay.cfg." );
  165. }
  166. }
  167. g_pBlockSpewer->PrintBlockEnd();
  168. }
  169. }
  170. void CServerReplayContext::FlagForConVarSanityCheck()
  171. {
  172. m_flConVarSanityCheckTime = g_pEngine->GetHostTime() + 0.2f;
  173. }
  174. IGameEvent *CServerReplayContext::CreateReplaySessionInfoEvent()
  175. {
  176. IGameEvent *pEvent = g_pGameEventManager->CreateEvent( "replay_sessioninfo", true );
  177. if ( !pEvent )
  178. return NULL;
  179. extern ConVar replay_block_dump_interval;
  180. // Fill event
  181. pEvent->SetString( "sn", m_pShared->m_pRecordingSessionManager->GetCurrentSessionName() );
  182. pEvent->SetInt( "di", replay_block_dump_interval.GetInt() );
  183. pEvent->SetInt( "cb", m_pShared->m_pRecordingSessionManager->GetCurrentSessionBlockIndex() );
  184. pEvent->SetInt( "st", m_pSessionRecorder->GetCurrentRecordingStartTick() );
  185. return pEvent;
  186. }
  187. IReplaySessionRecorder *CServerReplayContext::GetSessionRecorder()
  188. {
  189. return g_pServerReplayContext->m_pSessionRecorder;
  190. }
  191. const char *CServerReplayContext::GetLocalFileServerPath() const
  192. {
  193. static char s_szBuf[MAX_OSPATH];
  194. extern ConVar replay_local_fileserver_path;
  195. // Fix up the path name - NOTE: We intentionally avoid calling V_FixupPathName(), which
  196. // pushes the entire output string to lower case.
  197. V_strncpy( s_szBuf, replay_local_fileserver_path.GetString(), sizeof( s_szBuf ) );
  198. V_FixSlashes( s_szBuf );
  199. V_RemoveDotSlashes( s_szBuf );
  200. V_FixDoubleSlashes( s_szBuf );
  201. V_StripTrailingSlash( s_szBuf );
  202. V_AppendSlash( s_szBuf, sizeof( s_szBuf ) );
  203. return s_szBuf;
  204. }
  205. void CServerReplayContext::CreateSessionOnClient( int nClientSlot )
  206. {
  207. // If we have a session (i.e. if we're recording)
  208. if ( SV_GetRecordingSessionInProgress() )
  209. {
  210. // Create the session on the client
  211. IGameEvent *pSessionInfoEvent = CreateReplaySessionInfoEvent();
  212. g_pReplay->SV_SendReplayEvent( pSessionInfoEvent, nClientSlot );
  213. }
  214. }
  215. const char *CServerReplayContext::GetServerSubDirName() const
  216. {
  217. const char *pSubDirName = CommandLine()->ParmValue( "-replayserverdir" );
  218. if ( !pSubDirName || !pSubDirName[0] )
  219. {
  220. Msg( "No '-replayserverdir' parameter found - using default replay folder.\n" );
  221. return SUBDIR_SERVER;
  222. }
  223. Msg( "\n** Using custom replay dir name: \"%s%c%s\"\n\n", SUBDIR_REPLAY, CORRECT_PATH_SEPARATOR, pSubDirName );
  224. return pSubDirName;
  225. }
  226. void CServerReplayContext::ReportErrorsToUser( wchar_t *pErrorText )
  227. {
  228. char szErrorText[4096];
  229. g_pVGuiLocalize->ConvertUnicodeToANSI( pErrorText, szErrorText, sizeof( szErrorText ) );
  230. static Color s_clrRed( 255, 0, 0 );
  231. Warning( "\n-----------------------------------------------\n" );
  232. Warning( "%s", szErrorText );
  233. Warning( "-----------------------------------------------\n\n" );
  234. }
  235. void CServerReplayContext::OnPublishFailed()
  236. {
  237. // Don't report publish failure and shutdown publishing more than once per session.
  238. if ( !m_pSessionRecorder->RecordingAborted() )
  239. {
  240. m_bShouldAbortRecording = true;
  241. }
  242. }
  243. //----------------------------------------------------------------------------------------
  244. CServerRecordingSession *SV_GetRecordingSessionInProgress()
  245. {
  246. return SV_CastSession( SV_GetRecordingSessionManager()->GetRecordingSessionInProgress() );
  247. }
  248. const char *SV_GetTmpDir()
  249. {
  250. return Replay_va( "%s%s%c", SV_GetBasePath(), SUBDIR_TMP, CORRECT_PATH_SEPARATOR );
  251. }
  252. bool SV_IsOffloadingEnabled()
  253. {
  254. return false;
  255. }
  256. bool SV_RunJobToCompletion( CJob *pJob )
  257. {
  258. return RunJobToCompletion( SV_GetThreadPool(), pJob );
  259. }
  260. //----------------------------------------------------------------------------------------