diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/replaydemo.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'engine/replaydemo.cpp')
| -rw-r--r-- | engine/replaydemo.cpp | 488 |
1 files changed, 488 insertions, 0 deletions
diff --git a/engine/replaydemo.cpp b/engine/replaydemo.cpp new file mode 100644 index 0000000..bc1610e --- /dev/null +++ b/engine/replaydemo.cpp @@ -0,0 +1,488 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#if defined( REPLAY_ENABLED ) + +#include <tier1/strtools.h> +#include <eiface.h> +#include <bitbuf.h> +#include <time.h> +#include "replaydemo.h" +#include "replayserver.h" +#include "demo.h" +#include "host_cmd.h" +#include "proto_version.h" +#include "demofile/demoformat.h" +#include "filesystem_engine.h" +#include "net.h" +#include "networkstringtable.h" +#include "dt_common_eng.h" +#include "host.h" +#include "server.h" +#include "networkstringtableclient.h" +#include "replay_internal.h" +#include "GameEventManager.h" +#include "replay/ireplaysystem.h" +#include "replay/ireplaysessionrecorder.h" +#include "replay/shared_defs.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +extern CNetworkStringTableContainer *networkStringTableContainerServer; +extern CGlobalVars g_ServerGlobalVariables; +extern IServerReplayContext *g_pServerReplayContext; + +static ConVar *replay_record_voice = NULL; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CReplayDemoRecorder::CReplayDemoRecorder( CReplayServer* pServer ) +{ + m_bIsRecording = false; + + Assert( pServer ); + m_pReplayServer = pServer; + + m_nStartTick = -1; +} + +CReplayDemoRecorder::~CReplayDemoRecorder() +{ + StopRecording(); +} + +void CReplayDemoRecorder::GetUniqueDemoFilename( char *pOut, int nLength ) +{ + Assert( pOut ); + tm today; VCRHook_LocalTime( &today ); + Q_snprintf( pOut, nLength, "%04i%02i%02i-%02i%02i%02i-%s.dem", + 1900 + today.tm_year, today.tm_mon+1, today.tm_mday, + today.tm_hour, today.tm_min, today.tm_sec, m_pReplayServer->GetMapName() ); +} + +void CReplayDemoRecorder::StartRecording() +{ + // Get a proper filename and cache it for later + GetUniqueDemoFilename( m_szDumpFilename, sizeof( m_szDumpFilename ) ); + + // Start recording to the temporary location in the game dir + StartRecording( TMP_REPLAY_FILENAME, false ); +} + +const char *CReplayDemoRecorder::GetDemoFilename() +{ + static char s_szDemoFilename[ MAX_OSPATH ]; + const char *pFilename = replay->m_DemoRecorder.GetRecordingFilename(); Assert( pFilename && pFilename[0] ); + V_strcpy( s_szDemoFilename, pFilename ); + return s_szDemoFilename; +} + +void CReplayDemoRecorder::StartRecording( const char *pFilename, bool bContinuously ) +{ + SETUP_CVAR_REF( replay_recording ); + + StopRecording(); // stop if we're already recording + + // Attempt to "open" the demo file + ConVarRef replay_buffersize( "replay_buffersize" ); + const int nBufferSize = 1024 * 1024 * ( replay_buffersize.IsValid() ? replay_buffersize.GetInt() : 16 ); + if ( !m_DemoFile.Open( NULL, false, true, nBufferSize, false ) ) + { + Warning( "Failed to start recording - couldn't open demo file %s.\n", pFilename ); + return; + } + + // Using this tickcount allows us to sync up client-side recorded ragdolls later with replay demos on clients + m_nStartTick = g_ServerGlobalVariables.tickcount; + + demoheader_t *dh = &m_DemoFile.m_DemoHeader; + + // open demo header file containing sigon data + Q_memset( dh, 0, sizeof(demoheader_t) ); + + Q_strncpy( dh->demofilestamp, DEMO_HEADER_ID, sizeof(dh->demofilestamp) ); + dh->demoprotocol = DEMO_PROTOCOL; + dh->networkprotocol = PROTOCOL_VERSION; + + Q_strncpy( dh->mapname, m_pReplayServer->GetMapName(), sizeof( dh->mapname ) ); + + char szGameDir[MAX_OSPATH]; + Q_strncpy(szGameDir, com_gamedir, sizeof( szGameDir ) ); + Q_FileBase ( szGameDir, dh->gamedirectory, sizeof( dh->gamedirectory ) ); + + Q_strncpy( dh->servername, host_name.GetString(), sizeof( dh->servername ) ); + + Q_strncpy( dh->clientname, "Replay Demo", sizeof( dh->servername ) ); + + // write demo file header info + m_DemoFile.WriteDemoHeader(); + + dh->signonlength = WriteSignonData(); // demoheader will be written when demo is closed + + m_nFrameCount = 0; + + // Demo playback should read this as an incoming message. + // Write the client's realtime value out so we can synchronize the reads. + m_DemoFile.WriteCmdHeader( dem_synctick, 0 ); + + m_bIsRecording = true; + + m_SequenceInfo = 1; + m_nDeltaTick = -1; + + replay_recording.SetValue( 1 ); + + extern ConVar replay_debug; + if ( replay_debug.GetBool() ) ConMsg( "%f: Recording Replay...\n", host_time ); + + g_pServerReplayContext->GetSessionRecorder()->SetCurrentRecordingStartTick( m_nStartTick ); +} + +bool CReplayDemoRecorder::IsRecording() +{ + return m_bIsRecording; +} + +void CReplayDemoRecorder::StopRecording() +{ + if ( !IsRecording() ) + return; + + // Wipe the demo (does not write to disk) + m_DemoFile.Close(); + + // Set recording flag + m_bIsRecording = false; + + // clear writing data buffer + if ( m_MessageData.GetBasePointer() ) + { + delete [] m_MessageData.GetBasePointer(); + m_MessageData.StartWriting( NULL, 0 ); + } + + // replay_stoprecording gets set to 0 from within the replay session recorder, but only if we aren't starting to record a new round +} + +CDemoFile *CReplayDemoRecorder::GetDemoFile() +{ + return &m_DemoFile; +} + +int CReplayDemoRecorder::GetRecordingTick() +{ + return g_ServerGlobalVariables.tickcount - m_nStartTick; +} + +void CReplayDemoRecorder::WriteServerInfo() +{ + ALIGN4 byte buffer[ NET_MAX_PAYLOAD ] ALIGN4_POST; + bf_write msg( "CReplayDemoRecorder::WriteServerInfo", buffer, sizeof( buffer ) ); + + SVC_ServerInfo serverinfo; // create serverinfo message + + // on the master demos are using sv object, on relays replay + CBaseServer *pServer = (CBaseServer*)&sv; + + m_pReplayServer->FillServerInfo( serverinfo ); // fill rest of info message + + serverinfo.WriteToBuffer( msg ); + + // send first tick + NET_Tick signonTick( m_nSignonTick, 0, 0 ); + signonTick.WriteToBuffer( msg ); + + // Write replicated ConVars to non-listen server clients only + NET_SetConVar convars; + // build a list of all replicated convars + Host_BuildConVarUpdateMessage( &convars, FCVAR_REPLICATED, true ); + + // write convars to demo + convars.WriteToBuffer( msg ); + + // write stringtable baselines +#ifndef SHARED_NET_STRING_TABLES + m_pReplayServer->m_StringTables->WriteBaselines( msg ); +#endif + + // send signon state + NET_SignonState signonMsg( SIGNONSTATE_NEW, pServer->GetSpawnCount() ); + signonMsg.WriteToBuffer( msg ); + + WriteMessages( dem_signon, msg ); +} + +void CReplayDemoRecorder::RecordCommand( const char *cmdstring ) +{ + if ( !IsRecording() ) + return; + + if ( !cmdstring || !cmdstring[0] ) + return; + + GET_REPLAY_DBG_REF(); + if ( replay_debug.GetBool() ) Msg( "recording command, \"%s\"\n", cmdstring ); + + m_DemoFile.WriteConsoleCommand( cmdstring, GetRecordingTick() ); +} + +void CReplayDemoRecorder::RecordServerClasses( ServerClass *pClasses ) +{ + MEM_ALLOC_CREDIT(); + + char *pBigBuffer; + CUtlBuffer bigBuff; + + int buffSize = 256*1024; + if ( !IsX360() ) + { + pBigBuffer = (char*)stackalloc( buffSize ); + } + else + { + // keep temp large allocations off of stack + bigBuff.EnsureCapacity( buffSize ); + pBigBuffer = (char*)bigBuff.Base(); + } + + bf_write buf( pBigBuffer, buffSize ); + + // Send SendTable info. + DataTable_WriteSendTablesBuffer( pClasses, &buf ); + + // Send class descriptions. + DataTable_WriteClassInfosBuffer( pClasses, &buf ); + + // Now write the buffer into the demo file + m_DemoFile.WriteNetworkDataTables( &buf, GetRecordingTick() ); +} + +void CReplayDemoRecorder::RecordStringTables() +{ + // !KLUDGE! It would be nice if the bit buffer could write into a stream + // with the power to grow itself. But it can't. Hence this really bad + // kludge + void *data = NULL; + int dataLen = 512 * 1024; + while ( dataLen <= DEMO_FILE_MAX_STRINGTABLE_SIZE ) + { + data = realloc( data, dataLen ); + bf_write buf( data, dataLen ); + buf.SetDebugName("CReplayDemoRecorder::RecordStringTables"); + buf.SetAssertOnOverflow( false ); // Doesn't turn off all the spew / asserts, but turns off one + networkStringTableContainerServer->WriteStringTables( buf ); + + // Did we fit? + if ( !buf.IsOverflowed() ) + { + + // Now write the buffer into the demo file + m_DemoFile.WriteStringTables( &buf, GetRecordingTick() ); + break; + } + + // Didn't fit. Try doubling the size of the buffer + dataLen *= 2; + } + + if ( dataLen > DEMO_FILE_MAX_STRINGTABLE_SIZE ) + { + Warning( "Failed to RecordStringTables. Trying to record string table that's bigger than max string table size\n" ); + } + + free(data); +} + +int CReplayDemoRecorder::WriteSignonData() +{ + int start = m_DemoFile.GetCurPos( false ); + + // on the master demos are using sv object, on relays replay + CBaseServer *pServer = (CBaseServer*)&sv; + + m_nSignonTick = pServer->m_nTickCount; + + WriteServerInfo(); + + RecordServerClasses( serverGameDLL->GetAllServerClasses() ); + RecordStringTables(); + + ALIGN4 byte buffer[ NET_MAX_PAYLOAD ] ALIGN4_POST; + bf_write msg( "CReplayDemo::WriteSignonData", buffer, sizeof( buffer ) ); + + // use your class infos, CRC is correct + SVC_ClassInfo classmsg( true, pServer->serverclasses ); + classmsg.WriteToBuffer( msg ); + + // Write the regular signon now + msg.WriteBits( m_pReplayServer->m_Signon.GetData(), m_pReplayServer->m_Signon.GetNumBitsWritten() ); + + // write new state + NET_SignonState signonMsg1( SIGNONSTATE_PRESPAWN, pServer->GetSpawnCount() ); + signonMsg1.WriteToBuffer( msg ); + + WriteMessages( dem_signon, msg ); + msg.Reset(); + + // set view entity + SVC_SetView viewent( m_pReplayServer->m_nViewEntity ); + viewent.WriteToBuffer( msg ); + + // Spawned into server, not fully active, though + NET_SignonState signonMsg2( SIGNONSTATE_SPAWN, pServer->GetSpawnCount() ); + signonMsg2.WriteToBuffer( msg ); + + WriteMessages( dem_signon, msg ); + + return m_DemoFile.GetCurPos( false ) - start; +} + + +void CReplayDemoRecorder::WriteFrame( CReplayFrame *pFrame ) +{ + ALIGN4 byte buffer[ NET_MAX_PAYLOAD ] ALIGN4_POST; + bf_write msg( "CReplayDemo::RecordFrame", buffer, sizeof( buffer ) ); + + //first write reliable data + bf_write *data = &pFrame->m_Messages[REPLAY_BUFFER_RELIABLE]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + + //now send snapshot data + + // send tick time + NET_Tick tickmsg( pFrame->tick_count, host_frametime_unbounded, host_frametime_stddeviation ); + tickmsg.WriteToBuffer( msg ); + + +#ifndef SHARED_NET_STRING_TABLES + // Update shared client/server string tables. Must be done before sending entities + sv.m_StringTables->WriteUpdateMessage( NULL, MAX( m_nSignonTick, m_nDeltaTick ), msg ); +#endif + + // get delta frame + CClientFrame *deltaFrame = m_pReplayServer->GetClientFrame( m_nDeltaTick ); // NULL if m_nDeltaTick is not found or -1 + + // send entity update, delta compressed if deltaFrame != NULL + sv.WriteDeltaEntities( m_pReplayServer->m_MasterClient, pFrame, deltaFrame, msg ); + + // send all unreliable temp ents between last and current frame + CFrameSnapshot * fromSnapshot = deltaFrame?deltaFrame->GetSnapshot():NULL; + sv.WriteTempEntities( m_pReplayServer->m_MasterClient, pFrame->GetSnapshot(), fromSnapshot, msg, 255 ); + + // write sound data + data = &pFrame->m_Messages[REPLAY_BUFFER_SOUNDS]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + + // write voice data + if ( replay_record_voice == NULL ) + { + replay_record_voice = g_pCVar->FindVar( "replay_record_voice" ); + Assert( replay_record_voice != NULL ); + } + + if ( replay_record_voice && replay_record_voice->GetBool() ) + { + data = &pFrame->m_Messages[REPLAY_BUFFER_VOICE]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + } + + // last write unreliable data + data = &pFrame->m_Messages[REPLAY_BUFFER_UNRELIABLE]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + + // update delta tick just like fake clients do + m_nDeltaTick = pFrame->tick_count; + + // write packet to demo file + WriteMessages( dem_packet, msg ); +} + +void CReplayDemoRecorder::WriteMessages( unsigned char cmd, bf_write &message ) +{ + int len = message.GetNumBytesWritten(); + + if (len <= 0) + return; + + // fill last bits in last byte with NOP if necessary + int nRemainingBits = message.GetNumBitsWritten() % 8; + if ( nRemainingBits > 0 && nRemainingBits <= (8-NETMSG_TYPE_BITS) ) + { + message.WriteUBitLong( net_NOP, NETMSG_TYPE_BITS ); + } + + Assert( len < NET_MAX_MESSAGE ); + + // if signondata read as fast as possible, no rewind + // and wait for packet time + // byte cmd = (m_pDemoFileHeader != NULL) ? dem_signon : dem_packet; + + if ( cmd == dem_packet ) + { + m_nFrameCount++; + } + + // write command & time + m_DemoFile.WriteCmdHeader( cmd, GetRecordingTick() ); + + // write NULL democmdinfo just to keep same format as client demos + democmdinfo_t info; + Q_memset( &info, 0, sizeof( info ) ); + m_DemoFile.WriteCmdInfo( info ); + + // write continously increasing sequence numbers + m_DemoFile.WriteSequenceInfo( m_SequenceInfo, m_SequenceInfo ); + m_SequenceInfo++; + + // Output the buffer. Skip the network packet stuff. + m_DemoFile.WriteRawData( (char*)message.GetBasePointer(), len ); +} + +void CReplayDemoRecorder::RecordMessages(bf_read &data, int bits) +{ + // create buffer if not there yet + if ( m_MessageData.GetBasePointer() == NULL ) + { + m_MessageData.StartWriting( new unsigned char[NET_MAX_PAYLOAD], NET_MAX_PAYLOAD ); + } + + if ( bits>0 ) + { + m_MessageData.WriteBitsFromBuffer( &data, bits ); + Assert( !m_MessageData.IsOverflowed() ); + } +} + +void CReplayDemoRecorder::RecordPacket() +{ + Assert( !"Does this ever get called? I can't find anywhere where it does." ); + if( m_MessageData.GetBasePointer() ) + { + WriteMessages( dem_packet, m_MessageData ); + m_MessageData.Reset(); // clear message buffer + } +} + +const char *CReplayDemoRecorder::GetRecordingFilename() +{ + AssertMsg( 0, "Do we ever call this? " ); + if ( !IsRecording() ) + { + Assert( 0 ); + return NULL; + } + + return m_szDumpFilename; +} + +#endif |