Example #1
0
void CReplay :: BuildReplay( string gameName, string statString, uint32_t war3Version, uint16_t buildNumber )
{
	m_War3Version = war3Version;
	m_BuildNumber = buildNumber;
	m_Flags = 32768;

	CONSOLE_Print( "[REPLAY] building replay" );

	uint32_t LanguageID = 0x0012F8B0;

	BYTEARRAY Replay;
	Replay.push_back( 16 );															// Unknown (4.0)
	Replay.push_back( 1 );															// Unknown (4.0)
	Replay.push_back( 0 );															// Unknown (4.0)
	Replay.push_back( 0 );															// Unknown (4.0)
	Replay.push_back( 0 );															// Host RecordID (4.1)
	Replay.push_back( m_HostPID );													// Host PlayerID (4.1)
	UTIL_AppendByteArrayFast( Replay, m_HostName );									// Host PlayerName (4.1)
	Replay.push_back( 1 );															// Host AdditionalSize (4.1)
	Replay.push_back( 0 );															// Host AdditionalData (4.1)
	UTIL_AppendByteArrayFast( Replay, gameName );									// GameName (4.2)
	Replay.push_back( 0 );															// Null (4.0)
	UTIL_AppendByteArrayFast( Replay, statString );									// StatString (4.3)
	UTIL_AppendByteArray( Replay, (uint32_t)m_Slots.size( ), false );				// PlayerCount (4.6)
	Replay.push_back( m_MapGameType );												// GameType (4.7)
	Replay.push_back( 32 );															// GameType (4.7)
	Replay.push_back( 73 );															// GameType (4.7)
	Replay.push_back( 0 );															// GameType (4.7)
	UTIL_AppendByteArray( Replay, LanguageID, false );								// LanguageID (4.8)

	// PlayerList (4.9)

	for( vector<PIDPlayer> :: iterator i = m_Players.begin( ); i != m_Players.end( ); i++ )
	{
		if( (*i).first != m_HostPID )
		{
			Replay.push_back( 22 );													// Player RecordID (4.1)
			Replay.push_back( (*i).first );											// Player PlayerID (4.1)
			UTIL_AppendByteArrayFast( Replay, (*i).second );						// Player PlayerName (4.1)
			Replay.push_back( 1 );													// Player AdditionalSize (4.1)
			Replay.push_back( 0 );													// Player AdditionalData (4.1)
			UTIL_AppendByteArray( Replay, (uint32_t)0, false );						// Unknown
		}
	}

	// GameStartRecord (4.10)

	Replay.push_back( 25 );															// RecordID (4.10)
	UTIL_AppendByteArray( Replay, (uint16_t)( 7 + m_Slots.size( ) * 9 ), false );	// Size (4.10)
	Replay.push_back( m_Slots.size( ) );											// NumSlots (4.10)

	for( unsigned char i = 0; i < m_Slots.size( ); i++ )
		UTIL_AppendByteArray( Replay, m_Slots[i].GetByteArray( ) );

	UTIL_AppendByteArray( Replay, m_RandomSeed, false );							// RandomSeed (4.10)
	Replay.push_back( m_SelectMode );												// SelectMode (4.10)
	Replay.push_back( m_StartSpotCount );											// StartSpotCount (4.10)

	// ReplayData (5.0)

	Replay.push_back( REPLAY_FIRSTSTARTBLOCK );
	UTIL_AppendByteArray( Replay, (uint32_t)1, false );
	Replay.push_back( REPLAY_SECONDSTARTBLOCK );
	UTIL_AppendByteArray( Replay, (uint32_t)1, false );

	// leavers during loading need to be stored between the second and third start blocks

	while( !m_LoadingBlocks.empty( ) )
	{
		UTIL_AppendByteArray( Replay, m_LoadingBlocks.front( ) );
		m_LoadingBlocks.pop( );
	}

	Replay.push_back( REPLAY_THIRDSTARTBLOCK );
	UTIL_AppendByteArray( Replay, (uint32_t)1, false );

	// initialize replay length to zero
	// we'll accumulate the replay length as we iterate through the timeslots
	// this is necessary because we might be discarding some timeslots due to not enough checksums and the replay length needs to be accurate

	m_ReplayLength = 0;
	uint32_t TimeSlotsDiscarded = 0;
	bool EndOfTimeSlots = false;

	while( !m_Blocks.empty( ) )
	{
		BYTEARRAY Block = m_Blocks.front( );
		m_Blocks.pop( );

		if( Block.size( ) >= 5 && Block[0] == REPLAY_TIMESLOT )
		{
			uint16_t TimeIncrement = UTIL_ByteArrayToUInt16( Block, false, 3 );

			if( TimeIncrement != 0 && m_CheckSums.empty( ) )
				EndOfTimeSlots = true;

			if( EndOfTimeSlots )
			{
				TimeSlotsDiscarded++;
				continue;
			}

			// append timeslot

			UTIL_AppendByteArrayFast( Replay, Block );

			// append checksum
			// todotodo: after experimenting, Strilanc discovered that checksums are NOT required in replays
			// we could optimize saving of replays by building a complete stream without waiting for checksums as the game progresses
			// alternatively, we could build that stream as the checksums were added if we wanted to keep them
			// rather than building it in one go right now, which might take a few hundred ms and cause a spike in other games

			if( TimeIncrement != 0 )
			{
				BYTEARRAY CheckSum;
				CheckSum.reserve( 6 );
				CheckSum.push_back( REPLAY_CHECKSUM );
				CheckSum.push_back( 4 );
				UTIL_AppendByteArray( CheckSum, m_CheckSums.front( ), false );
				m_CheckSums.pop( );
				UTIL_AppendByteArrayFast( Replay, CheckSum );
			}

			// accumulate replay length

			m_ReplayLength += TimeIncrement;
		}
		else
			UTIL_AppendByteArrayFast( Replay, Block );
	}

	if( TimeSlotsDiscarded > 0 )
		CONSOLE_Print( "[REPLAY] ran out of checksums, discarded " + UTIL_ToString( TimeSlotsDiscarded ) + " timeslots" );

	// done

	m_Decompressed = string( Replay.begin( ), Replay.end( ) );
}