summaryrefslogtreecommitdiff
path: root/engine/replaydemo.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 /engine/replaydemo.cpp
downloadarchived-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.cpp488
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