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( ) ); }