// used for builds <= 47479 and 47903 private static void ExtendedBattleTagParsingOld(Replay replay, BitReader bitReader) { bool changed47479 = false; if (replay.ReplayBuild == 47479 && DetectBattleTagChangeBuild47479(replay, bitReader)) { changed47479 = true; } for (int i = 0; i < replay.ClientListByUserID.Length; i++) { if (replay.ClientListByUserID[i] == null) { break; } string TId; string TId_2; // this first one is weird, nothing to indicate the length of the string if (i == 0) { var offset = bitReader.ReadByte(); bitReader.ReadString(2); // T: TId = bitReader.ReadString(12 + offset); if (replay.ReplayBuild <= 47479 && !changed47479) { bitReader.ReadBytes(6); ReadByte0x00(bitReader); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.Read(6); // get T: again TId_2 = Encoding.UTF8.GetString(ReadSpecialBlob(bitReader, 8)); if (TId != TId_2) { throw new Exception("TID dup not equal"); } } } else { ReadByte0x00(bitReader); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.Read(6); // get XXXXXXXX#YYY TId = Encoding.UTF8.GetString(ReadSpecialBlob(bitReader, 8)); if (replay.ReplayBuild <= 47479 && !changed47479) { bitReader.ReadBytes(6); ReadByte0x00(bitReader); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.Read(6); // get T: again TId_2 = Encoding.UTF8.GetString(ReadSpecialBlob(bitReader, 8)); if (TId != TId_2) { throw new Exception("TID dup not equal"); } } } replay.ClientListByUserID[i].BattleNetTId = TId; // next 31 bytes bitReader.ReadBytes(4); // same for all players bitReader.ReadByte(); bitReader.ReadBytes(8); // same for all players bitReader.ReadBytes(4); bitReader.ReadBytes(14); // same for all players if (replay.ReplayBuild >= 47903 || changed47479) { bitReader.ReadBytes(40); } else if (replay.ReplayBuild >= 47219 || replay.ReplayBuild == 47024) { bitReader.ReadBytes(39); } else if (replay.ReplayBuild >= 45889) { bitReader.ReadBytes(38); } else if (replay.ReplayBuild >= 45228) { bitReader.ReadBytes(37); } else if (replay.ReplayBuild >= 44468) { bitReader.ReadBytes(36); } else { bitReader.ReadBytes(35); } if (replay.ReplayBuild >= 47903 || changed47479) { bitReader.Read(1); } else if (replay.ReplayBuild >= 47219 || replay.ReplayBuild == 47024) { bitReader.Read(6); } else if (replay.ReplayBuild >= 46690 || replay.ReplayBuild == 46416) { bitReader.Read(5); } else if (replay.ReplayBuild >= 45889) { bitReader.Read(2); } else if (replay.ReplayBuild >= 45228) { bitReader.Read(3); } else { bitReader.Read(5); } if (bitReader.ReadBoolean()) { // use this to determine who is in a party // those in the same party will have the same exact 8 bytes of data // the party leader is the first one (in the order of the client list) replay.ClientListByUserID[i].PartyValue = bitReader.ReadInt32() + bitReader.ReadInt32(); } bitReader.Read(1); var battleTag = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(7)).Split('#'); // battleTag <name>#xxxxx if (battleTag.Length != 2 || battleTag[0] != replay.ClientListByUserID[i].Name) { throw new Exception("Couldn't find BattleTag"); } replay.ClientListByUserID[i].BattleTag = int.Parse(battleTag[1]); // these similar bytes don't occur for last player bitReader.ReadBytes(27); } // some more bytes after (at least 700) // theres some HeroICONs and other repetitive stuff }
private static void GetBattleTags(Replay replay, BitReader reader) { // Search for the BattleTag for each player var battleTagDigits = new List <char>(); for (var playerNum = 0; playerNum < replay.Players.Length; playerNum++) { var player = replay.Players[playerNum]; if (player == null) { continue; } // Find each player's name, and then their associated BattleTag battleTagDigits.Clear(); var playerNameBytes = Encoding.UTF8.GetBytes(player.Name); while (!reader.EndOfStream) { var isFound = true; for (var i = 0; i < playerNameBytes.Length + 1; i++) { if ((i == playerNameBytes.Length && reader.ReadByte() != 35 /* '#' Character */) || (i < playerNameBytes.Length && reader.ReadByte() != playerNameBytes[i])) { isFound = false; break; } } if (isFound) { break; } } // Get the digits from the BattleTag while (!reader.EndOfStream) { var currentCharacter = (char)reader.ReadByte(); if (playerNum == 9 && (currentCharacter == 'z' || currentCharacter == 'Ø')) { // If player is in slot 9, there's a chance that an extra digit could be appended to the BattleTag battleTagDigits.RemoveAt(battleTagDigits.Count - 1); break; } else if (char.IsDigit(currentCharacter)) { battleTagDigits.Add(currentCharacter); } else { break; } } if (reader.EndOfStream) { break; } else if (battleTagDigits.Count == 0) { continue; } player.BattleTag = int.Parse(string.Join("", battleTagDigits)); } }
/// <summary> Parses the replay.server.battlelobby file in a replay file. </summary> /// <param name="replay"> The replay file to apply the parsed data to. </param> /// <param name="buffer"> The buffer containing the replay.server.battlelobby file. </param> public static void Parse(Replay replay, byte[] buffer) { using (var stream = new MemoryStream(buffer)) { var bitReader = new BitReader(stream); // 52124 and 52381 are non-tested ptr builds if (replay.ReplayBuild < 38793 || replay.ReplayBuild == 52124 || replay.ReplayBuild == 52381 || replay.GameMode == GameMode.Unknown) { GetBattleTags(replay, bitReader); return; } int s2mArrayLength = bitReader.ReadByte(); int stringLength = bitReader.ReadByte(); bitReader.ReadString(stringLength); for (var i = 1; i < s2mArrayLength; i++) { bitReader.Read(16); bitReader.ReadString(stringLength); } if (bitReader.ReadByte() != s2mArrayLength) { throw new Exception("s2ArrayLength not equal"); } for (var i = 0; i < s2mArrayLength; i++) { bitReader.ReadString(4); // s2m bitReader.ReadBytes(2); // 0x00 0x00 bitReader.ReadString(2); // Realm bitReader.ReadBytes(32); } if (replay.ReplayBuild < 55929) { // seems to be in all replays bitReader.ReadInt16(); bitReader.stream.Position = bitReader.stream.Position + 684; // seems to be in all replays bitReader.ReadInt16(); bitReader.stream.Position = bitReader.stream.Position + 1944; if (bitReader.ReadString(8) != "HumnComp") { throw new Exception("Not HumnComp"); } } bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + 19859; //// next section is language libraries? //// --------------------------------------- //for (int i = 0; ; i++) // no idea how to determine the count //{ // if (bitReader.ReadString(4).Substring(0, 2) != "s2") // s2mv; not sure if its going to be 'mv' all the time // { // bitReader.stream.Position = bitReader.stream.Position - 4; // break; // } // bitReader.ReadBytes(2); // 0x00 0x00 // bitReader.ReadString(2); // Realm // bitReader.ReadBytes(32); //} //bitReader.Read(32); //bitReader.Read(8); //bitReader.ReadByte(); //for (int i = 0; ; i++) // no idea how to determine the count //{ // if (bitReader.ReadString(4).Substring(0, 2) != "s2") // s2ml // { // bitReader.stream.Position = bitReader.stream.Position - 4; // break; // } // bitReader.ReadBytes(2); // 0x00 0x00 // bitReader.ReadString(2); // Realm // bitReader.ReadBytes(32); //} //for (int k = 0; k < 11; k++) //{ // // ruRU, zhCN, plPL, esMX, frFR, esES // // ptBR, itIT, enUs, deDe, koKR // bitReader.ReadString(4); // bitReader.ReadByte(); // for (int i = 0; ; i++) // { // if (bitReader.ReadString(4).Substring(0, 2) != "s2") // s2ml // { // bitReader.stream.Position = bitReader.stream.Position - 4; // break; // } // bitReader.ReadString(4); // s2ml // bitReader.ReadBytes(2); // 0x00 0x00 // bitReader.ReadString(2); // Realm // bitReader.ReadBytes(32); // } //} // new section, can't find a pattern // has blizzmaps#1, Hero, s2mv // -------------------- //bitReader.ReadBytes(8); // all 0x00 for (;;) { // we're just going to skip all the way down to the s2mh if (bitReader.ReadString(4) == "s2mh") { bitReader.stream.Position = bitReader.stream.Position - 4; break; } else { bitReader.stream.Position = bitReader.stream.Position - 3; } } for (var i = 0; i < s2mArrayLength; i++) { bitReader.ReadString(4); // s2mh bitReader.ReadBytes(2); // 0x00 0x00 bitReader.ReadString(2); // Realm bitReader.ReadBytes(32); } // Player collections - starting with HOTS 2.0 (live build 52860) // -------------------------------------------------------------- List <string> playerCollection = new List <string>(); int collectionSize = 0; if (replay.ReplayBuild >= 48027) { collectionSize = bitReader.ReadInt16(); } else { collectionSize = bitReader.ReadInt32(); } if (collectionSize > 5000) { throw new Exception("collectionSize is an unusually large number"); } for (int i = 0; i < collectionSize; i++) { if (replay.ReplayBuild >= 55929) { bitReader.ReadBytes(8); } else { playerCollection.Add(bitReader.ReadString(bitReader.ReadByte())); } } // use to determine if the collection item is usable by the player (owns/free to play/internet cafe) if (bitReader.ReadInt32() != collectionSize) { throw new Exception("skinArrayLength not equal"); } for (int i = 0; i < collectionSize; i++) { for (int j = 0; j < 16; j++) // 16 is total player slots { bitReader.ReadByte(); var num = bitReader.Read(8); if (replay.ReplayBuild < 55929) { if (replay.ClientListByUserID[j] != null) { if (num > 0) { replay.ClientListByUserID[j].PlayerCollectionDictionary.Add(playerCollection[i], true); } else if (num == 0) { replay.ClientListByUserID[j].PlayerCollectionDictionary.Add(playerCollection[i], false); } else { throw new NotImplementedException(); } } } } } // Player info // ------------------------ if (replay.ReplayBuild <= 43259 || replay.ReplayBuild == 47801) { // Builds that are not yet supported for detailed parsing // build 47801 is a ptr build that had new data in the battletag section, the data was changed in 47944 (patch for 47801) GetBattleTags(replay, bitReader); return; } bitReader.ReadInt32(); // m_randomSeed bitReader.ReadBytes(32); bitReader.ReadInt32(); // 0x19 if (replay.ReplayBuild <= 47479 || replay.ReplayBuild == 47903) { ExtendedBattleTagParsingOld(replay, bitReader); return; } for (int player = 0; player < replay.ClientListByUserID.Length; player++) { if (replay.ClientListByUserID[player] == null) { break; } string TId; if (player == 0) { var offset = bitReader.ReadByte(); bitReader.ReadString(2); // T: TId = bitReader.ReadString(12 + offset); } else { ReadByte0x00(bitReader); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.Read(6); // get XXXXXXXX#YYY TId = Encoding.UTF8.GetString(ReadSpecialBlob(bitReader, 8)); } replay.ClientListByUserID[player].BattleNetTId = TId; // next 18 bytes bitReader.ReadBytes(4); // same for all players bitReader.ReadBytes(26); // repeat of the collection section above if (replay.ReplayBuild >= 51609) { int size = (int)bitReader.Read(12); // 3 bytes max 4095 if (size != collectionSize) { throw new Exception("size and collectionSize not equal"); } int bytesSize = collectionSize / 8; int bitsSize = (collectionSize % 8) + 2; // two additional unknown bits bitReader.ReadBytes(bytesSize); bitReader.Read(bitsSize); } else { if (replay.ReplayBuild >= 48027) { bitReader.ReadInt16(); } else { bitReader.ReadInt32(); } // each byte has a max value of 0x7F (127) bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + (collectionSize * 2); bitReader.Read(1); } if (bitReader.ReadBoolean()) { // use this to determine who is in a party // those in the same party will have the same exact 8 bytes of data // the party leader is the first one (in the order of the client list) replay.ClientListByUserID[player].PartyValue = bitReader.ReadInt32() + bitReader.ReadInt32(); } bitReader.Read(1); var battleTag = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(7)).Split('#'); // battleTag <name>#xxxxx if (battleTag.Length != 2 || battleTag[0] != replay.ClientListByUserID[player].Name) { throw new Exception("Couldn't find BattleTag"); } replay.ClientListByUserID[player].BattleTag = int.Parse(battleTag[1]); if (replay.ReplayBuild >= 52860 || (replay.ReplayVersionMajor == 2 && replay.ReplayBuild >= 51978)) { replay.ClientListByUserID[player].AccountLevel = bitReader.ReadInt32(); // player's account level, not available in custom games } bitReader.ReadBytes(27); // these similar bytes don't occur for last player } // some more data after this } }
internal static void DetailedParse(BitReader bitReader, Replay replay, int s2mArrayLength) { bitReader.AlignToByte(); for (; ;) { // we're just going to skip all the way down to the s2mh if (bitReader.ReadString(4) == "s2mh") { bitReader.stream.Position = bitReader.stream.Position - 4; break; } else { bitReader.stream.Position = bitReader.stream.Position - 3; } } for (var i = 0; i < s2mArrayLength; i++) { bitReader.ReadString(4); // s2mh bitReader.ReadBytes(2); // 0x00 0x00 bitReader.ReadString(2); // Realm bitReader.ReadBytes(32); } // Player collections - starting with HOTS 2.0 (live build 52860) // strings gone starting with build (ptr) 55929 // -------------------------------------------------------------- List <string> playerCollection = new List <string>(); int collectionSize = 0; if (replay.ReplayBuild >= 48027) { collectionSize = bitReader.ReadInt16(); } else { collectionSize = bitReader.ReadInt32(); } if (collectionSize > 8000) { throw new DetailedParsedException("collectionSize is an unusually large number"); } for (int i = 0; i < collectionSize; i++) { if (replay.ReplayBuild >= 55929) { bitReader.ReadBytes(8); // most likey an identifier for the item; first six bytes are 0x00 } else { playerCollection.Add(bitReader.ReadString(bitReader.ReadByte())); } } // use to determine if the collection item is usable by the player (owns/free to play/internet cafe) if (bitReader.ReadInt32() != collectionSize) { throw new DetailedParsedException("skinArrayLength not equal"); } for (int i = 0; i < collectionSize; i++) { for (int j = 0; j < 16; j++) // 16 is total player slots { bitReader.ReadByte(); var num = bitReader.Read(8); if (replay.ReplayBuild < 55929) { if (replay.ClientListByUserID[j] != null) { if (num > 0) { replay.ClientListByUserID[j].PlayerCollectionDictionary.Add(playerCollection[i], true); } else if (num == 0) { replay.ClientListByUserID[j].PlayerCollectionDictionary.Add(playerCollection[i], false); } else { throw new NotImplementedException(); } } } } } // Player info // ------------------------ if (replay.ReplayBuild <= 43259 || replay.ReplayBuild == 47801) { // Builds that are not yet supported for detailed parsing // build 47801 is a ptr build that had new data in the battletag section, the data was changed in 47944 (patch for 47801) GetBattleTags(replay, bitReader); return; } // m_randomSeed, set it if it hasn't been set if (replay.RandomValue == 0) { replay.RandomValue = (uint)bitReader.ReadInt32(); } else { bitReader.ReadInt32(); } bitReader.ReadBytes(32); bitReader.ReadInt32(); // 0x19 if (replay.ReplayBuild <= 47479 || replay.ReplayBuild == 47903) { ExtendedBattleTagParsingOld(replay, bitReader); return; } for (int player = 0; player < replay.ClientListByUserID.Length; player++) { if (replay.ClientListByUserID[player] == null) { break; } if (player == 0) { var offset = bitReader.ReadByte(); bitReader.ReadString(2); // T: replay.ClientListByUserID[player].BattleNetTId = bitReader.ReadString(12 + offset); // TId } else { ReadByte0x00(bitReader); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.Read(6); // get XXXXXXXX#YYY replay.ClientListByUserID[player].BattleNetTId = Encoding.UTF8.GetString(ReadSpecialBlob(bitReader, 8)); // TId } // next 30 bytes bitReader.ReadBytes(4); // same for all players bitReader.ReadBytes(26); // repeat of the collection section above if (replay.ReplayBuild >= 51609) { int size = (int)bitReader.Read(12); // 3 bytes if (size == collectionSize) { int bytesSize = collectionSize / 8; int bitsSize = (collectionSize % 8); bitReader.ReadBytes(bytesSize); bitReader.Read(bitsSize); } // else if not equal, then data isn't available, most likely an observer } else { if (replay.ReplayBuild >= 48027) { bitReader.ReadInt16(); } else { bitReader.ReadInt32(); } // each byte has a max value of 0x7F (127) bitReader.stream.Position = bitReader.stream.Position + (collectionSize * 2); } if (replay.ReplayBuild >= 51609) { bitReader.ReadBoolean(); } bitReader.ReadBoolean(); // m_hasSilencePenalty if (replay.ReplayBuild >= 61718) { bitReader.ReadBoolean(); bitReader.ReadBoolean(); // m_hasVoiceSilencePenalty } if (bitReader.ReadBoolean()) // is player in party { replay.ClientListByUserID[player].PartyValue = bitReader.ReadInt32() + bitReader.ReadInt32(); // players in same party will have the same exact 8 bytes of data } bitReader.ReadBoolean(); var battleTag = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(7)).Split('#'); // battleTag <name>#xxxxx if (battleTag.Length != 2 || battleTag[0] != replay.ClientListByUserID[player].Name) { throw new DetailedParsedException("Couldn't find BattleTag"); } replay.ClientListByUserID[player].BattleTag = int.Parse(battleTag[1]); if (replay.ReplayBuild >= 52860 || (replay.ReplayVersionMajor == 2 && replay.ReplayBuild >= 51978)) { replay.ClientListByUserID[player].AccountLevel = bitReader.ReadInt32(); // player's account level, not available in custom games } bitReader.ReadBytes(27); // these similar bytes don't occur for last player } // some more data after this // there is also a CSTM string down here, if it exists, the game is a custom game }
/// <summary> Parses the replay.initdata file in a replay file. </summary> /// <param name="replay"> The replay file to apply the parsed data to. </param> /// <param name="buffer"> The buffer containing the replay.initdata file. </param> public static void Parse(Replay replay, byte[] buffer) { using (var stream = new MemoryStream(buffer)) { var reader = new BitReader(stream); var i = reader.ReadByte(); var playerList = new string[i]; for (int j = 0; j < i; j++) { playerList[j] = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(8)); if (reader.ReadBoolean()) { var clanTag = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(8)); // Console.WriteLine(clanTag); } if (reader.ReadBoolean()) { // Clan Logo reader.ReadBlobPrecededWithLength(40); } if (reader.ReadBoolean()) { var highestLeague = reader.Read(8); // Console.WriteLine(highestLeague); } if (reader.ReadBoolean()) { var combinedRaceLevels = reader.ReadInt32(); // Console.WriteLine(combinedRaceLevels); } reader.ReadInt32(); // Random seed (So far, always 0 in Heroes) if (reader.ReadBoolean()) { reader.Read(8); // Race Preference } if (reader.ReadBoolean()) { reader.Read(8); // Team Preference } reader.ReadBoolean(); //test map reader.ReadBoolean(); //test auto reader.ReadBoolean(); //examine reader.ReadBoolean(); //custom interface var unknown1 = reader.ReadInt32(); reader.Read(2); //observer var unknown2 = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); var unknown3 = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); var unknown4 = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); var unknown5 = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); var unknown6 = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); var unknown7 = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); // Console.WriteLine(unknown1 + unknown2 + unknown3 + unknown4 + unknown5 + unknown6 + unknown7); } // Marked as 'Random Value', so I will use as seed replay.RandomValue = (uint)reader.ReadInt32(); reader.ReadBlobPrecededWithLength(10); // Dflt reader.ReadBoolean(); // Lock Teams reader.ReadBoolean(); // Teams Together reader.ReadBoolean(); // Advanced Shared Control reader.ReadBoolean(); // Random Races reader.ReadBoolean(); // BattleNet reader.ReadBoolean(); // AMM reader.ReadBoolean(); // Competitive reader.ReadBoolean(); // No Victory Or Defeat reader.ReadBoolean(); // Unknown 0 reader.ReadBoolean(); // Unknown 1 reader.ReadBoolean(); // Unknown 2 reader.Read(2); // Fog reader.Read(2); // Observers reader.Read(2); // User Difficulty reader.ReadInt32(); reader.ReadInt32(); // 64 bit int: Client Debug Flags reader.Read(3); // Game Speed // Not sure what this 'Game Type' is reader.Read(3); var maxUsers = reader.Read(5); if (maxUsers != 10) // Max Players { replay.GameMode = GameMode.TryMe; } reader.Read(5); // Max Observers reader.Read(5); // Max Players reader.Read(4); // + 1 = Max Teams reader.Read(6); // Max Colors reader.Read(8); // + 1 = Max Races reader.Read(8); // Max Controls replay.MapSize = new Point { X = (int)reader.Read(8), Y = (int)reader.Read(8) }; if (replay.MapSize.Y == 1) { replay.MapSize.Y = replay.MapSize.X; } else if (replay.MapSize.X == 0) { replay.MapSize.X = replay.MapSize.Y; } // About 1000 bytes from here is a list of characters, character skins, character mounts, artifact selections, and other data } }
/// <summary> Parses the replay.initdata file in a replay file. </summary> /// <param name="replay"> The replay file to apply the parsed data to. </param> /// <param name="buffer"> The buffer containing the replay.initdata file. </param> public static void Parse(Replay replay, byte[] buffer) { using (var stream = new MemoryStream(buffer)) { var reader = new BitReader(stream); var playerListLength = reader.Read(5); for (var i = 0; i < playerListLength; i++) { var playerName = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(8)); // Player name // Populate the client list if (playerName != "") { if (i < replay.Players.Length && replay.Players[i].Name == playerName) { // 99.9% of matches have 10 players and 10 clients replay.ClientList[i] = replay.Players[i]; } else { // Some Custom games with Observers may have the client list in a different order than player list // Hopefully in these rare cases, nobody will be sharing the same name :) replay.ClientList[i] = replay.Players.SingleOrDefault(j => j.Name == playerName); } if (replay.ClientList[i] == null) { replay.ClientList[i] = new Player { Name = playerName } } ; } if (reader.ReadBoolean()) { reader.ReadBlobPrecededWithLength(8); // clanTag } if (reader.ReadBoolean()) { reader.ReadBlobPrecededWithLength(40); // Clan Logo } if (reader.ReadBoolean()) { reader.Read(8); // highestLeague } if (reader.ReadBoolean()) { reader.ReadInt32(); // combinedRaceLevels } reader.ReadInt32(); // Random seed (So far, always 0 in Heroes) if (reader.ReadBoolean()) { reader.Read(8); // Race Preference } if (reader.ReadBoolean()) { reader.Read(8); // Team Preference } reader.ReadBoolean(); //test map reader.ReadBoolean(); //test auto reader.ReadBoolean(); //examine reader.ReadBoolean(); //custom interface reader.ReadInt32(); // m_testType reader.Read(2); //observer Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_hero - Currently Empty String Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_skin - Currently Empty String Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_mount - Currently Empty String Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); // m_toonHandle - Currently Empty String } // Marked as 'Random Value', so I will use as seed replay.RandomValue = (uint)reader.ReadInt32(); reader.ReadBlobPrecededWithLength(10); // m_gameCacheName - "Dflt" reader.ReadBoolean(); // Lock Teams reader.ReadBoolean(); // Teams Together reader.ReadBoolean(); // Advanced Shared Control reader.ReadBoolean(); // Random Races reader.ReadBoolean(); // BattleNet reader.ReadBoolean(); // AMM reader.ReadBoolean(); // Competitive reader.ReadBoolean(); // m_practice reader.ReadBoolean(); // m_cooperative reader.ReadBoolean(); // m_noVictoryOrDefeat reader.ReadBoolean(); // m_heroDuplicatesAllowed reader.Read(2); // Fog reader.Read(2); // Observers reader.Read(2); // User Difficulty reader.ReadInt32(); reader.ReadInt32(); // 64 bit int: Client Debug Flags reader.Read(3); // Game Speed // Not sure what this 'Game Type' is reader.Read(3); var maxUsers = reader.Read(5); if (maxUsers != 10) // Max Players { replay.GameMode = GameMode.TryMe; } reader.Read(5); // Max Observers reader.Read(5); // Max Players reader.Read(4); // + 1 = Max Teams reader.Read(6); // Max Colors reader.Read(8); // + 1 = Max Races reader.Read(8); // Max Controls replay.MapSize = new Point { X = (int)reader.Read(8), Y = (int)reader.Read(8) }; if (replay.MapSize.Y == 1) { replay.MapSize.Y = replay.MapSize.X; } else if (replay.MapSize.X == 0) { replay.MapSize.X = replay.MapSize.Y; } // I haven't tested the following code on replays before build 39595 (End of 2015) if (replay.ReplayBuild < 39595) { return; } reader.Read(32); // m_mapFileSyncChecksum reader.ReadBlobPrecededWithLength(11); // m_mapFileName reader.ReadBlobPrecededWithLength(8); // m_mapAuthorName reader.Read(32); // m_modFileSyncChecksum // m_slotDescriptions var slotDescriptionLength = reader.Read(5); for (var i = 0; i < slotDescriptionLength; i++) { reader.ReadBitArray(reader.Read(6)); // m_allowedColors reader.ReadBitArray(reader.Read(8)); // m_allowedRaces reader.ReadBitArray(reader.Read(6)); // m_allowedDifficulty reader.ReadBitArray(reader.Read(8)); // m_allowedControls reader.ReadBitArray(reader.Read(2)); // m_allowedObserveTypes reader.ReadBitArray(reader.Read(7)); // m_allowedAIBuilds } reader.Read(6); // m_defaultDifficulty reader.Read(7); // m_defaultAIBuild // m_cacheHandles var cacheHandlesLength = reader.Read(6); for (var i = 0; i < cacheHandlesLength; i++) { reader.ReadBytes(40); } reader.ReadBoolean(); // m_hasExtensionMod reader.ReadBoolean(); // m_isBlizzardMap reader.ReadBoolean(); // m_isPremadeFFA reader.ReadBoolean(); // m_isCoopMode #region m_lobbyState reader.Read(3); // m_phase reader.Read(5); // m_maxUsers reader.Read(5); // m_maxObservers // m_slots var slotsLength = reader.Read(5); for (var i = 0; i < slotsLength; i++) { int?clientListIndex = null; reader.Read(8); // m_control if (reader.ReadBoolean()) { clientListIndex = (int)reader.Read(4); // m_userId } reader.Read(4); // m_teamId if (reader.ReadBoolean()) { reader.Read(5); // m_colorPref } if (reader.ReadBoolean()) { reader.Read(8); // m_racePref } reader.Read(6); // m_difficulty reader.Read(7); // m_aiBuild reader.Read(7); // m_handicap // m_observe var observerStatus = reader.Read(2); if (observerStatus == 2 && clientListIndex.HasValue) { replay.ClientList[clientListIndex.Value].PlayerType = PlayerType.Spectator; } reader.Read(32); // m_logoIndex reader.ReadBlobPrecededWithLength(9); // m_hero var skinAndSkinTint = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_skin if (skinAndSkinTint == "") { skinAndSkinTint = null; } if (clientListIndex.HasValue && replay.ClientList[clientListIndex.Value] != null) { replay.ClientList[clientListIndex.Value].SkinAndSkinTint = skinAndSkinTint; } var mountAndMountTint = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_mount if (mountAndMountTint == "") { mountAndMountTint = null; } if (clientListIndex.HasValue && replay.ClientList[clientListIndex.Value] != null) { replay.ClientList[clientListIndex.Value].MountAndMountTint = mountAndMountTint; } // m_artifacts var artifactsLength = reader.Read(4); for (var j = 0; j < artifactsLength; j++) { reader.ReadBlobPrecededWithLength(9); } if (reader.ReadBoolean()) { reader.Read(8); // m_workingSetSlotId } // m_rewards var rewardsLength = reader.Read(17); for (var j = 0; j < rewardsLength; j++) { reader.Read(32); } reader.ReadBlobPrecededWithLength(7); // m_toonHandle // m_licenses var licensesLength = reader.Read(9); for (var j = 0; j < licensesLength; j++) { reader.Read(32); } if (reader.ReadBoolean()) { reader.Read(4); // m_tandemLeaderUserId } reader.ReadBlobPrecededWithLength(9); // m_commander - Empty string reader.Read(32); // m_commanderLevel - So far, always 0 if (reader.ReadBoolean() && clientListIndex.HasValue && replay.ClientList[clientListIndex.Value] != null) // m_hasSilencePenalty { replay.ClientList[clientListIndex.Value].IsSilenced = true; } } if (reader.Read(32) != replay.RandomValue) // m_randomSeed { throw new Exception("Replay Random Seed Values in Replay Init Data did not match"); } if (reader.ReadBoolean()) { reader.Read(4); // m_hostUserId } reader.ReadBoolean(); // m_isSinglePlayer reader.Read(8); // m_pickedMapTag - So far, always 0 reader.Read(32); // m_gameDuration - So far, always 0 reader.Read(6); // m_defaultDifficulty reader.Read(7); // m_defaultAIBuild #endregion } } }
/// <summary> Parses the replay.initdata file in a replay file. </summary> /// <param name="replay"> The replay file to apply the parsed data to. </param> /// <param name="buffer"> The buffer containing the replay.initdata file. </param> public static void Parse(Replay replay, byte[] buffer) { using (var stream = new MemoryStream(buffer)) { var reader = new BitReader(stream); var playerListLength = reader.Read(5); for (var i = 0; i < playerListLength; i++) { var playerName = Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(8)); // Player name // Populate the name for each client in the client list by UserID if (playerName != "") { replay.ClientListByUserID[i] = new Player { Name = playerName } } ; if (reader.ReadBoolean()) { reader.ReadBlobPrecededWithLength(8); // clanTag } if (reader.ReadBoolean()) { reader.ReadBlobPrecededWithLength(40); // Clan Logo } if (reader.ReadBoolean()) { reader.Read(8); // highestLeague } if (reader.ReadBoolean()) { reader.ReadInt32(); // combinedRaceLevels } reader.ReadInt32(); // Random seed (So far, always 0 in Heroes) if (reader.ReadBoolean()) { reader.Read(8); // Race Preference } if (reader.ReadBoolean()) { reader.Read(8); // Team Preference } reader.ReadBoolean(); //test map reader.ReadBoolean(); //test auto reader.ReadBoolean(); //examine reader.ReadBoolean(); //custom interface reader.ReadInt32(); // m_testType reader.Read(2); //observer Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_hero - Currently Empty String Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_skin - Currently Empty String Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_mount - Currently Empty String if (replay.ReplayVersionMajor >= 2) { Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_banner - Currently Empty String Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_spray - Currently Empty String } Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(7)); // m_toonHandle - Currently Empty String } // Marked as 'Random Value', so I will use as seed replay.RandomValue = (uint)reader.ReadInt32(); reader.ReadBlobPrecededWithLength(10); // m_gameCacheName - "Dflt" reader.ReadBoolean(); // Lock Teams reader.ReadBoolean(); // Teams Together reader.ReadBoolean(); // Advanced Shared Control reader.ReadBoolean(); // Random Races reader.ReadBoolean(); // BattleNet reader.ReadBoolean(); // AMM reader.ReadBoolean(); // Competitive reader.ReadBoolean(); // m_practice reader.ReadBoolean(); // m_cooperative reader.ReadBoolean(); // m_noVictoryOrDefeat reader.ReadBoolean(); // m_heroDuplicatesAllowed reader.Read(2); // Fog reader.Read(2); // Observers reader.Read(2); // User Difficulty reader.ReadInt32(); reader.ReadInt32(); // 64 bit int: Client Debug Flags // m_ammId if (replay.ReplayBuild >= 43905 && reader.ReadBoolean()) { switch (reader.ReadInt32()) { case 50021: // Versus AI (Cooperative) case 50041: // Practice break; case 50001: replay.GameMode = GameMode.QuickMatch; break; case 50031: replay.GameMode = GameMode.Brawl; break; case 50051: replay.GameMode = GameMode.UnrankedDraft; break; case 50061: replay.GameMode = GameMode.HeroLeague; break; case 50071: replay.GameMode = GameMode.TeamLeague; break; default: replay.GameMode = GameMode.Unknown; break; } } reader.Read(3); // Game Speed // Not sure what this 'Game Type' is reader.Read(3); var maxUsers = reader.Read(5); if (maxUsers != 10 && replay.GameMode != GameMode.Brawl) // Max Players { replay.GameMode = GameMode.TryMe; } reader.Read(5); // Max Observers reader.Read(5); // Max Players reader.Read(4); // + 1 = Max Teams reader.Read(6); // Max Colors reader.Read(8); // + 1 = Max Races // Max Controls if (replay.ReplayBuild < 59279) { reader.Read(8); } else { reader.Read(4); } replay.MapSize = new Point { X = (int)reader.Read(8), Y = (int)reader.Read(8) }; if (replay.MapSize.Y == 1) { replay.MapSize.Y = replay.MapSize.X; } else if (replay.MapSize.X == 0) { replay.MapSize.X = replay.MapSize.Y; } // I haven't tested the following code on replays before build 39595 (End of 2015) if (replay.ReplayBuild < 39595) { return; } reader.Read(32); // m_mapFileSyncChecksum reader.ReadBlobPrecededWithLength(11); // m_mapFileName reader.ReadBlobPrecededWithLength(8); // m_mapAuthorName reader.Read(32); // m_modFileSyncChecksum // m_slotDescriptions var slotDescriptionLength = reader.Read(5); for (var i = 0; i < slotDescriptionLength; i++) { reader.ReadBitArray(reader.Read(6)); // m_allowedColors reader.ReadBitArray(reader.Read(8)); // m_allowedRaces reader.ReadBitArray(reader.Read(6)); // m_allowedDifficulty // m_allowedControls if (replay.ReplayBuild < 59279) { reader.ReadBitArray(reader.Read(8)); } else { reader.ReadBitArray(reader.Read(4)); } reader.ReadBitArray(reader.Read(2)); // m_allowedObserveTypes reader.ReadBitArray(reader.Read(7)); // m_allowedAIBuilds } reader.Read(6); // m_defaultDifficulty reader.Read(7); // m_defaultAIBuild // m_cacheHandles var cacheHandlesLength = reader.Read(6); for (var i = 0; i < cacheHandlesLength; i++) { reader.ReadBytes(40); } reader.ReadBoolean(); // m_hasExtensionMod reader.ReadBoolean(); // m_isBlizzardMap reader.ReadBoolean(); // m_isPremadeFFA reader.ReadBoolean(); // m_isCoopMode #region m_lobbyState reader.Read(3); // m_phase reader.Read(5); // m_maxUsers reader.Read(5); // m_maxObservers // m_slots var slotsLength = reader.Read(5); for (var i = 0; i < slotsLength; i++) { int?userID = null; reader.Read(8); // m_control if (reader.ReadBoolean()) { userID = (int)reader.Read(4); // m_userId } reader.Read(4); // m_teamId if (reader.ReadBoolean()) { reader.Read(5); // m_colorPref } if (reader.ReadBoolean()) { reader.Read(8); // m_racePref } reader.Read(6); // m_difficulty reader.Read(7); // m_aiBuild reader.Read(7); // m_handicap // m_observe var observerStatus = reader.Read(2); reader.Read(32); // m_logoIndex reader.ReadBlobPrecededWithLength(9); // m_hero var skinAndSkinTint = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_skin if (skinAndSkinTint == "") { skinAndSkinTint = null; } var mountAndMountTint = Encoding.ASCII.GetString(reader.ReadBlobPrecededWithLength(9)); // m_mount if (mountAndMountTint == "") { mountAndMountTint = null; } // m_artifacts if (replay.ReplayBuild < 65579 || replay.ReplayBuild == 65617 || replay.ReplayBuild == 65654) { var artifactsLength = reader.Read(4); for (var j = 0; j < artifactsLength; j++) { reader.ReadBlobPrecededWithLength(9); } } int?workingSetSlotID = null; if (reader.ReadBoolean()) { workingSetSlotID = (int)reader.Read(8); // m_workingSetSlotId } if (userID.HasValue && workingSetSlotID.HasValue) { if (replay.ClientListByWorkingSetSlotID[workingSetSlotID.Value] != null) { replay.ClientListByUserID[userID.Value] = replay.ClientListByWorkingSetSlotID[workingSetSlotID.Value]; } if (observerStatus == 2) { replay.ClientListByUserID[userID.Value].PlayerType = PlayerType.Spectator; } replay.ClientListByUserID[userID.Value].SkinAndSkinTint = skinAndSkinTint; replay.ClientListByUserID[userID.Value].MountAndMountTint = mountAndMountTint; } // m_rewards var rewardsLength = reader.Read(17); for (var j = 0; j < rewardsLength; j++) { reader.Read(32); } reader.ReadBlobPrecededWithLength(7); // m_toonHandle // m_licenses if (replay.ReplayBuild < 49582 || replay.ReplayBuild == 49838) { var licensesLength = reader.Read(9); for (var j = 0; j < licensesLength; j++) { reader.Read(32); } } if (reader.ReadBoolean()) { reader.Read(4); // m_tandemLeaderUserId } if (replay.ReplayBuild <= 41504) { reader.ReadBlobPrecededWithLength(9); // m_commander - Empty string reader.Read(32); // m_commanderLevel - So far, always 0 } if (reader.ReadBoolean() && userID.HasValue) // m_hasSilencePenalty { replay.ClientListByUserID[userID.Value].IsSilenced = true; } if (replay.ReplayBuild >= 61718 && reader.ReadBoolean() && userID.HasValue) // m_hasVoiceSilencePenalty { replay.ClientListByUserID[userID.Value].IsVoiceSilence = true; } if (replay.ReplayBuild >= 66977 && reader.ReadBoolean() && userID.HasValue) // m_isBlizzardStaff { replay.ClientListByUserID[userID.Value].IsBlizzardStaff = true; } if (replay.ReplayVersionMajor >= 2) { Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_banner Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_spray Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_announcerPack Encoding.UTF8.GetString(reader.ReadBlobPrecededWithLength(9)); // m_voiceLine // m_heroMasteryTiers if (replay.ReplayBuild >= 52561) { var heroMasteryTiersLength = reader.Read(10); for (var j = 0; j < heroMasteryTiersLength; j++) { reader.Read(32); // m_hero reader.Read(8); // m_tier } } } } if (reader.Read(32) != replay.RandomValue) // m_randomSeed { throw new Exception("Replay Random Seed Values in Replay Init Data did not match"); } if (reader.ReadBoolean()) { reader.Read(4); // m_hostUserId } reader.ReadBoolean(); // m_isSinglePlayer reader.Read(8); // m_pickedMapTag - So far, always 0 reader.Read(32); // m_gameDuration - So far, always 0 reader.Read(6); // m_defaultDifficulty reader.Read(7); // m_defaultAIBuild #endregion } } }