diff options
Diffstat (limited to 'replay/replay_reconstructor.cpp')
| -rw-r--r-- | replay/replay_reconstructor.cpp | 253 |
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 |