summaryrefslogtreecommitdiff
path: root/replay/replay_reconstructor.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/replay_reconstructor.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'replay/replay_reconstructor.cpp')
-rw-r--r--replay/replay_reconstructor.cpp253
1 files changed, 253 insertions, 0 deletions
diff --git a/replay/replay_reconstructor.cpp b/replay/replay_reconstructor.cpp
new file mode 100644
index 0000000..dee7385
--- /dev/null
+++ b/replay/replay_reconstructor.cpp
@@ -0,0 +1,253 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+//=======================================================================================//
+
+#include "replay_reconstructor.h"
+#include "replay/replay.h"
+#include "cl_recordingsessionblock.h"
+#include "cl_recordingsession.h"
+#include "cl_replaycontext.h"
+#include "cl_replaymanager.h"
+#include "UtlSortVector.h"
+#include "demofile/demoformat.h"
+#include "lzss.h"
+#include "compression.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+//----------------------------------------------------------------------------------------
+
+class CReplay_LessFunc
+{
+public:
+ bool Less( const CClientRecordingSessionBlock *pBlock1, const CClientRecordingSessionBlock *pBlock2, void *pCtx )
+ {
+ return ( pBlock1->m_iReconstruction < pBlock2->m_iReconstruction );
+ }
+};
+
+//----------------------------------------------------------------------------------------
+
+bool Replay_Reconstruct( CReplay *pReplay, bool bDeleteBlocks/*=true*/ )
+{
+ // Get the session for the given replay
+ CClientRecordingSession *pSession = CL_CastSession( CL_GetRecordingSessionManager()->FindSession( pReplay->m_hSession ) );
+ if ( !pSession )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_BadSession" );
+ return false;
+ }
+
+ // Dynamically load blocks for the session
+ pSession->LoadBlocksForSession();
+
+ // How many blocks needed
+ const int nNumBlocksNeeded = pReplay->m_iMaxSessionBlockRequired + 1;
+
+ // Enough blocks to proceed?
+ const CBaseRecordingSession::BlockContainer_t &vecAllBlocks = pSession->GetBlocks();
+ if ( vecAllBlocks.Count() < nNumBlocksNeeded )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_NotEnoughBlocksForReconstruction" );
+ return false;
+ }
+
+ // Add needed blocks in sorted order
+ CUtlSortVector< const CClientRecordingSessionBlock *, CReplay_LessFunc > vecReplayBlocks;
+ FOR_EACH_VEC( vecAllBlocks, i )
+ {
+ const CClientRecordingSessionBlock *pCurBlock = CL_CastBlock( vecAllBlocks[ i ] );
+
+ // Don't add more blocks than are needed
+ if ( pCurBlock->m_iReconstruction >= nNumBlocksNeeded )
+ continue;
+
+ // Sorted insert
+ vecReplayBlocks.Insert( pCurBlock );
+ }
+
+ // Now we need to do an integrity check on all blocks
+ int iLastReconstructionIndex = 0; // All replay reconstruction will start with block 0
+ FOR_EACH_VEC( vecReplayBlocks, i )
+ {
+ const CClientRecordingSessionBlock *pCurBlock = vecReplayBlocks[ i ];
+
+ // Haven't downloaded yet or failed to download for some reason?
+ if ( !pCurBlock->DownloadedSuccessfully() )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_BlocksNotDLd" );
+ return false;
+ }
+
+ // Check against reconstruction indices and make sure the list is continuous
+ if ( pCurBlock->m_iReconstruction - iLastReconstructionIndex > 1 )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_NonContinuous" );
+ return false;
+ }
+
+ // Cache for next iteration
+ iLastReconstructionIndex = pCurBlock->m_iReconstruction;
+ }
+
+ // Open the target, reconstruction file - "<session_name>_<replay handle>.dem"
+ CUtlString strReconstructedFileFilename;
+ strReconstructedFileFilename.Format( "%s%s_%i.dem", CL_GetReplayManager()->GetIndexPath(), pSession->m_strName.Get(), pReplay->GetHandle() );
+ FileHandle_t hReconstructedFile = g_pFullFileSystem->Open( strReconstructedFileFilename.Get(), "wb" );
+ if ( hReconstructedFile == FILESYSTEM_INVALID_HANDLE )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_OpenOutFile" );
+ return false;
+ }
+
+ // Now that we have an ordered list of replays to reconstruct, create the mother file
+ bool bFailed = false;
+ FOR_EACH_VEC( vecReplayBlocks, i )
+ {
+ const CClientRecordingSessionBlock *pCurBlock = vecReplayBlocks[ i ];
+
+ // Open the partial file for the current replay
+ const char *pFilename = pCurBlock->m_szFullFilename;
+
+ FileHandle_t hBlockFile = g_pFullFileSystem->Open( pFilename, "rb" );
+ if ( hBlockFile == FILESYSTEM_INVALID_HANDLE )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_BlockDNE" );
+ bFailed = true;
+ break;
+ }
+
+ int nSize = g_pFullFileSystem->Size( hBlockFile );
+ if ( nSize == 0 )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_ZeroLengthBlock" );
+ bFailed = true;
+ break;
+ }
+
+ char *pBuffer = (char *)new char[ nSize ];
+ if ( !pBuffer )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_OutOfMemory" );
+ bFailed = true;
+ }
+ else
+ {
+ // Read the file
+ if ( nSize != g_pFullFileSystem->Read( pBuffer, nSize, hBlockFile ) )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_FailedToRead" );
+ bFailed = true;
+ }
+ else
+ {
+ // Decompress if necessary
+ CompressorType_t nCompressorType = (CompressorType_t)pCurBlock->m_nCompressorType;
+ if ( nCompressorType != COMPRESSORTYPE_INVALID )
+ {
+ ICompressor *pCompressor = CreateCompressor( nCompressorType );
+
+ if ( !pCompressor )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_DecompressorCreate" );
+ bFailed = true;
+ }
+ else
+ {
+ const unsigned int nCompressedSize = nSize;
+ unsigned int nUncompressedSize = pCurBlock->m_uUncompressedSize;
+ char *pUncompressedBuffer = new char[ nUncompressedSize ];
+
+ if ( !pUncompressedBuffer )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_Alloc" );
+ bFailed = true;
+ }
+ else
+ {
+ if ( !nUncompressedSize )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_UncompressedSizeIsZero" );
+ bFailed = true;
+ }
+ else if ( !pCompressor->Decompress( pUncompressedBuffer, &nUncompressedSize, pBuffer, nCompressedSize ) )
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_Decompression" );
+ bFailed = true;
+ }
+ }
+
+ if ( bFailed )
+ {
+ delete [] pUncompressedBuffer;
+ }
+ else
+ {
+ // Overwrite buffer pointer and buffer size
+ pBuffer = pUncompressedBuffer;
+ nSize = nUncompressedSize;
+ }
+
+ // Free compressor
+ delete pCompressor;
+ }
+ }
+
+ // Append the read data to the mother file
+ if ( g_pFullFileSystem->Write( pBuffer, nSize, hReconstructedFile ) == nSize )
+ {
+ g_pFullFileSystem->Close( hBlockFile );
+ }
+ else
+ {
+ CL_GetErrorSystem()->AddErrorFromTokenName( "#Replay_Err_Recon_FailedToWrite" );
+ bFailed = true;
+ }
+ }
+ }
+
+ // Free
+ delete [] pBuffer;
+ }
+
+ if ( !bFailed )
+ {
+ // Add dem_stop - embed the calculated end tick
+ const int nLengthInTicks = g_pEngine->TimeToTicks( pReplay->m_flLength );
+ int nLastTick = 0;
+ if ( nLengthInTicks > 0 )
+ {
+ nLastTick = pReplay->m_nSpawnTick + nLengthInTicks;
+ }
+ unsigned char szEndTickBuf[4];
+ *( (int32 *)szEndTickBuf ) = nLastTick;
+ unsigned char szStopBuf[] = { dem_stop, szEndTickBuf[0], szEndTickBuf[1], szEndTickBuf[2], szEndTickBuf[3], 0 };
+ int nStopSize = sizeof( szStopBuf );
+ if ( g_pFullFileSystem->Write( szStopBuf, nStopSize, hReconstructedFile ) != nStopSize )
+ {
+ Warning( "Replay: Failed to write stop bits to reconstructed replay file \"%s\"\n", strReconstructedFileFilename.Get() );
+ // Should still run fine
+ }
+
+ // Save reconstructed filename, which will serve to indicate whether the
+ // replay's been successfully reconstructed or not.
+ pReplay->m_strReconstructedFilename = strReconstructedFileFilename;
+
+ // Mark the replay for flush
+ CL_GetReplayManager()->FlagForFlush( pReplay, true );
+
+ // Delete blocks - removes from session, session block manager, and from disk if no other replays are depending on them.
+ if ( bDeleteBlocks )
+ {
+ pSession->DeleteBlocks();
+ }
+ }
+
+ // Close reconstructed file
+ g_pFullFileSystem->Close( hReconstructedFile );
+
+ return true;
+}
+
+//---------------------------------------------------------------------------------------- \ No newline at end of file