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.

252 lines
7.8 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "replay_reconstructor.h"
  5. #include "replay/replay.h"
  6. #include "cl_recordingsessionblock.h"
  7. #include "cl_recordingsession.h"
  8. #include "cl_replaycontext.h"
  9. #include "cl_replaymanager.h"
  10. #include "UtlSortVector.h"
  11. #include "demofile/demoformat.h"
  12. #include "lzss.h"
  13. #include "compression.h"
  14. // memdbgon must be the last include file in a .cpp file!!!
  15. #include "tier0/memdbgon.h"
  16. //----------------------------------------------------------------------------------------
  17. class CReplay_LessFunc
  18. {
  19. public:
  20. bool Less( const CClientRecordingSessionBlock *pBlock1, const CClientRecordingSessionBlock *pBlock2, void *pCtx )
  21. {
  22. return ( pBlock1->m_iReconstruction < pBlock2->m_iReconstruction );
  23. }
  24. };
  25. //----------------------------------------------------------------------------------------
  26. bool Replay_Reconstruct( CReplay *pReplay, bool bDeleteBlocks/*=true*/ )
  27. {
  28. // Get the session for the given replay
  29. CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pReplay->m_hSession ) );
  30. if ( !pSession )
  31. {
  32. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_BadSession" );
  33. return false;
  34. }
  35. // Dynamically load blocks for the session
  36. pSession->LoadBlocksForSession();
  37. // How many blocks needed
  38. const int nNumBlocksNeeded = pReplay->m_iMaxSessionBlockRequired + 1;
  39. // Enough blocks to proceed?
  40. const CBaseRecordingSession::BlockContainer_t &vecAllBlocks = pSession->GetBlocks();
  41. if ( vecAllBlocks.Count() < nNumBlocksNeeded )
  42. {
  43. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_NotEnoughBlocksForReconstruction" );
  44. return false;
  45. }
  46. // Add needed blocks in sorted order
  47. CUtlSortVector< const CClientRecordingSessionBlock *, CReplay_LessFunc > vecReplayBlocks;
  48. FOR_EACH_VEC( vecAllBlocks, i )
  49. {
  50. const CClientRecordingSessionBlock *pCurBlock = CL_CastBlock( vecAllBlocks[ i ] );
  51. // Don't add more blocks than are needed
  52. if ( pCurBlock->m_iReconstruction >= nNumBlocksNeeded )
  53. continue;
  54. // Sorted insert
  55. vecReplayBlocks.Insert( pCurBlock );
  56. }
  57. // Now we need to do an integrity check on all blocks
  58. int iLastReconstructionIndex = 0; // All replay reconstruction will start with block 0
  59. FOR_EACH_VEC( vecReplayBlocks, i )
  60. {
  61. const CClientRecordingSessionBlock *pCurBlock = vecReplayBlocks[ i ];
  62. // Haven't downloaded yet or failed to download for some reason?
  63. if ( !pCurBlock->DownloadedSuccessfully() )
  64. {
  65. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_BlocksNotDLd" );
  66. return false;
  67. }
  68. // Check against reconstruction indices and make sure the list is continuous
  69. if ( pCurBlock->m_iReconstruction - iLastReconstructionIndex > 1 )
  70. {
  71. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_NonContinuous" );
  72. return false;
  73. }
  74. // Cache for next iteration
  75. iLastReconstructionIndex = pCurBlock->m_iReconstruction;
  76. }
  77. // Open the target, reconstruction file - "<session_name>_<replay handle>.dem"
  78. CUtlString strReconstructedFileFilename;
  79. strReconstructedFileFilename.Format( "%s%s_%i.dem", CL_GetReplayManager()->GetIndexPath(), pSession->m_strName.Get(), pReplay->GetHandle() );
  80. FileHandle_t hReconstructedFile = g_pFullFileSystem->Open( strReconstructedFileFilename.Get(), "wb" );
  81. if ( hReconstructedFile == FILESYSTEM_INVALID_HANDLE )
  82. {
  83. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_OpenOutFile" );
  84. return false;
  85. }
  86. // Now that we have an ordered list of replays to reconstruct, create the mother file
  87. bool bFailed = false;
  88. FOR_EACH_VEC( vecReplayBlocks, i )
  89. {
  90. const CClientRecordingSessionBlock *pCurBlock = vecReplayBlocks[ i ];
  91. // Open the partial file for the current replay
  92. const char *pFilename = pCurBlock->m_szFullFilename;
  93. FileHandle_t hBlockFile = g_pFullFileSystem->Open( pFilename, "rb" );
  94. if ( hBlockFile == FILESYSTEM_INVALID_HANDLE )
  95. {
  96. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_BlockDNE" );
  97. bFailed = true;
  98. break;
  99. }
  100. int nSize = g_pFullFileSystem->Size( hBlockFile );
  101. if ( nSize == 0 )
  102. {
  103. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_ZeroLengthBlock" );
  104. bFailed = true;
  105. break;
  106. }
  107. char *pBuffer = (char *)new char[ nSize ];
  108. if ( !pBuffer )
  109. {
  110. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_OutOfMemory" );
  111. bFailed = true;
  112. }
  113. else
  114. {
  115. // Read the file
  116. if ( nSize != g_pFullFileSystem->Read( pBuffer, nSize, hBlockFile ) )
  117. {
  118. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_FailedToRead" );
  119. bFailed = true;
  120. }
  121. else
  122. {
  123. // Decompress if necessary
  124. CompressorType_t nCompressorType = (CompressorType_t)pCurBlock->m_nCompressorType;
  125. if ( nCompressorType != COMPRESSORTYPE_INVALID )
  126. {
  127. ICompressor *pCompressor = CreateCompressor( nCompressorType );
  128. if ( !pCompressor )
  129. {
  130. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_DecompressorCreate" );
  131. bFailed = true;
  132. }
  133. else
  134. {
  135. const unsigned int nCompressedSize = nSize;
  136. unsigned int nUncompressedSize = pCurBlock->m_uUncompressedSize;
  137. char *pUncompressedBuffer = new char[ nUncompressedSize ];
  138. if ( !pUncompressedBuffer )
  139. {
  140. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_Alloc" );
  141. bFailed = true;
  142. }
  143. else
  144. {
  145. if ( !nUncompressedSize )
  146. {
  147. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_UncompressedSizeIsZero" );
  148. bFailed = true;
  149. }
  150. else if ( !pCompressor->Decompress( pUncompressedBuffer, &nUncompressedSize, pBuffer, nCompressedSize ) )
  151. {
  152. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_Decompression" );
  153. bFailed = true;
  154. }
  155. }
  156. if ( bFailed )
  157. {
  158. delete [] pUncompressedBuffer;
  159. }
  160. else
  161. {
  162. // Overwrite buffer pointer and buffer size
  163. pBuffer = pUncompressedBuffer;
  164. nSize = nUncompressedSize;
  165. }
  166. // Free compressor
  167. delete pCompressor;
  168. }
  169. }
  170. // Append the read data to the mother file
  171. if ( g_pFullFileSystem->Write( pBuffer, nSize, hReconstructedFile ) == nSize )
  172. {
  173. g_pFullFileSystem->Close( hBlockFile );
  174. }
  175. else
  176. {
  177. CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_FailedToWrite" );
  178. bFailed = true;
  179. }
  180. }
  181. }
  182. // Free
  183. delete [] pBuffer;
  184. }
  185. if ( !bFailed )
  186. {
  187. // Add dem_stop - embed the calculated end tick
  188. const int nLengthInTicks = g_pEngine->TimeToTicks( pReplay->m_flLength );
  189. int nLastTick = 0;
  190. if ( nLengthInTicks > 0 )
  191. {
  192. nLastTick = pReplay->m_nSpawnTick + nLengthInTicks;
  193. }
  194. unsigned char szEndTickBuf[4];
  195. *( (int32 *)szEndTickBuf ) = nLastTick;
  196. unsigned char szStopBuf[] = { dem_stop, szEndTickBuf[0], szEndTickBuf[1], szEndTickBuf[2], szEndTickBuf[3], 0 };
  197. int nStopSize = sizeof( szStopBuf );
  198. if ( g_pFullFileSystem->Write( szStopBuf, nStopSize, hReconstructedFile ) != nStopSize )
  199. {
  200. Warning( "Replay: Failed to write stop bits to reconstructed replay file \"%s\"\n", strReconstructedFileFilename.Get() );
  201. // Should still run fine
  202. }
  203. // Save reconstructed filename, which will serve to indicate whether the
  204. // replay's been successfully reconstructed or not.
  205. pReplay->m_strReconstructedFilename = strReconstructedFileFilename;
  206. // Mark the replay for flush
  207. CL_GetReplayManager()->FlagForFlush( pReplay, true );
  208. // Delete blocks - removes from session, session block manager, and from disk if no other replays are depending on them.
  209. if ( bDeleteBlocks )
  210. {
  211. pSession->DeleteBlocks();
  212. }
  213. }
  214. // Close reconstructed file
  215. g_pFullFileSystem->Close( hReconstructedFile );
  216. return true;
  217. }
  218. //----------------------------------------------------------------------------------------