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/hltvdemo.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'engine/hltvdemo.cpp')
| -rw-r--r-- | engine/hltvdemo.cpp | 456 |
1 files changed, 456 insertions, 0 deletions
diff --git a/engine/hltvdemo.cpp b/engine/hltvdemo.cpp new file mode 100644 index 0000000..5dc25b4 --- /dev/null +++ b/engine/hltvdemo.cpp @@ -0,0 +1,456 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + + +#include <tier1/strtools.h> +#include <eiface.h> +#include <bitbuf.h> +#include <time.h> +#include "hltvdemo.h" +#include "hltvserver.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" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +extern CNetworkStringTableContainer *networkStringTableContainerServer; + +////////////////////////////////////////////////////////////////////// +// Construction/Destruction +////////////////////////////////////////////////////////////////////// + +CHLTVDemoRecorder::CHLTVDemoRecorder() +{ + m_bIsRecording = false; +} + +CHLTVDemoRecorder::~CHLTVDemoRecorder() +{ + StopRecording(); +} + +void CHLTVDemoRecorder::StartAutoRecording() +{ + char fileName[MAX_OSPATH]; + + tm today; VCRHook_LocalTime( &today ); + + Q_snprintf( fileName, sizeof(fileName), "auto-%04i%02i%02i-%02i%02i-%s.dem", + 1900 + today.tm_year, today.tm_mon+1, today.tm_mday, + today.tm_hour, today.tm_min, hltv->GetMapName() ); + + StartRecording( fileName, false ); +} + + +void CHLTVDemoRecorder::StartRecording( const char *filename, bool bContinuously ) +{ + StopRecording(); // stop if we're already recording + + if ( !m_DemoFile.Open( filename, false ) ) + { + ConMsg ("StartRecording: couldn't open demo file %s.\n", filename ); + return; + } + + ConMsg ("Recording SourceTV demo to %s...\n", filename); + + demoheader_t *dh = &m_DemoFile.m_DemoHeader; + + // open demo header file containing sigondata + 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, hltv->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, "SourceTV 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; + + m_nStartTick = host_tickcount; + + // 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; +} + +bool CHLTVDemoRecorder::IsRecording() +{ + return m_bIsRecording; +} + +void CHLTVDemoRecorder::StopRecording() +{ + if ( !m_bIsRecording ) + return; + + // Demo playback should read this as an incoming message. + m_DemoFile.WriteCmdHeader( dem_stop, GetRecordingTick() ); + + // update demo header info + m_DemoFile.m_DemoHeader.playback_ticks = GetRecordingTick(); + m_DemoFile.m_DemoHeader.playback_time = host_state.interval_per_tick * GetRecordingTick(); + m_DemoFile.m_DemoHeader.playback_frames = m_nFrameCount; + + // write updated version + m_DemoFile.WriteDemoHeader(); + + m_DemoFile.Close(); + + m_bIsRecording = false; + + // clear writing data buffer + if ( m_MessageData.GetBasePointer() ) + { + delete [] m_MessageData.GetBasePointer(); + m_MessageData.StartWriting( NULL, 0 ); + } + + ConMsg("Completed SourceTV demo \"%s\", recording time %.1f\n", + m_DemoFile.m_szFileName, + m_DemoFile.m_DemoHeader.playback_time ); +} + +CDemoFile *CHLTVDemoRecorder::GetDemoFile() +{ + return &m_DemoFile; +} + + +int CHLTVDemoRecorder::GetRecordingTick( void ) +{ + return host_tickcount - m_nStartTick; +} + + + +void CHLTVDemoRecorder::WriteServerInfo() +{ + ALIGN4 byte buffer[ NET_MAX_PAYLOAD ] ALIGN4_POST; + bf_write msg( "CHLTVDemoRecorder::WriteServerInfo", buffer, sizeof( buffer ) ); + + SVC_ServerInfo serverinfo; // create serverinfo message + + // on the master demos are using sv object, on relays hltv + CBaseServer *pServer = hltv->IsMasterProxy()?(CBaseServer*)(&sv):(CBaseServer*)(hltv); + + hltv->FillServerInfo( serverinfo ); // fill rest of info message + + serverinfo.WriteToBuffer( msg ); + + // send first tick + NET_Tick signonTick( m_nSignonTick, 0, 0 ); + signonTick.WriteToBuffer( msg ); + + // write stringtable baselines + +#ifndef SHARED_NET_STRING_TABLES + pServer->m_StringTables->WriteBaselines( msg ); +#endif + + // 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 ); + + if ( hltv->IsMasterProxy() ) + { + // for SourceTV server demos write set "tv_transmitall 1" even + // if it's off for the real broadcast + NET_SetConVar::cvar_t acvar; + Q_strncpy( acvar.name, "tv_transmitall", MAX_OSPATH ); + Q_strncpy( acvar.value, "1", MAX_OSPATH ); + convars.m_ConVars.AddToTail( acvar ); + } + + // write convars to demo + convars.WriteToBuffer( msg ); + + // send signon state + NET_SignonState signonMsg( SIGNONSTATE_NEW, pServer->GetSpawnCount() ); + signonMsg.WriteToBuffer( msg ); + + WriteMessages( dem_signon, msg ); +} + +void CHLTVDemoRecorder::RecordCommand( const char *cmdstring ) +{ + if ( !IsRecording() ) + return; + + if ( !cmdstring || !cmdstring[0] ) + return; + + m_DemoFile.WriteConsoleCommand( cmdstring, GetRecordingTick() ); +} + +void CHLTVDemoRecorder::RecordServerClasses( ServerClass *pClasses ) +{ + 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 CHLTVDemoRecorder::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("CHLTVDemoRecorder_StringTables"); + 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 CHLTVDemoRecorder::WriteSignonData() +{ + int start = m_DemoFile.GetCurPos( false ); + + // on the master demos are using sv object, on relays hltv + CBaseServer *pServer = hltv->IsMasterProxy()?(CBaseServer*)(&sv):(CBaseServer*)(hltv); + + m_nSignonTick = pServer->m_nTickCount; + + WriteServerInfo(); + + RecordServerClasses( serverGameDLL->GetAllServerClasses() ); + RecordStringTables(); + + ALIGN4 byte buffer[ NET_MAX_PAYLOAD ] ALIGN4_POST; + bf_write msg( "CHLTVDemo::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( hltv->m_Signon.GetData(), hltv->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( hltv->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 CHLTVDemoRecorder::WriteFrame( CHLTVFrame *pFrame ) +{ + ALIGN4 byte buffer[ NET_MAX_PAYLOAD ] ALIGN4_POST; + bf_write msg( "CHLTVDemo::RecordFrame", buffer, sizeof( buffer ) ); + + assert( hltv->IsMasterProxy() ); // this works only on the master since we use sv. + + //first write reliable data + bf_write *data = &pFrame->m_Messages[HLTV_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 = hltv->GetClientFrame( m_nDeltaTick ); // NULL if delta_tick is not found or -1 + + // send entity update, delta compressed if deltaFrame != NULL + sv.WriteDeltaEntities( hltv->m_MasterClient, pFrame, deltaFrame, msg ); + + // send all unreliable temp ents between last and current frame + CFrameSnapshot * fromSnapshot = deltaFrame?deltaFrame->GetSnapshot():NULL; + sv.WriteTempEntities( hltv->m_MasterClient, pFrame->GetSnapshot(), fromSnapshot, msg, 255 ); + + // write sound data + data = &pFrame->m_Messages[HLTV_BUFFER_SOUNDS]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + + // write voice data + data = &pFrame->m_Messages[HLTV_BUFFER_VOICE]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + + // last write unreliable data + data = &pFrame->m_Messages[HLTV_BUFFER_UNRELIABLE]; + if ( data->GetNumBitsWritten() ) + msg.WriteBits( data->GetBasePointer(), data->GetNumBitsWritten() ); + + // update delta tick just like fakeclients do + m_nDeltaTick = pFrame->tick_count; + + // write packet to demo file + WriteMessages( dem_packet, msg ); +} + +void CHLTVDemoRecorder::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 ); + + if ( tv_debug.GetInt() > 1 ) + { + Msg( "Writing SourceTV demo message %i bytes at file pos %i\n", len, m_DemoFile.GetCurPos( false ) ); + } +} + +void CHLTVDemoRecorder::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 CHLTVDemoRecorder::RecordPacket() +{ + if( m_MessageData.GetBasePointer() ) + { + WriteMessages( dem_packet, m_MessageData ); + m_MessageData.Reset(); // clear message buffer + } +} |