/// <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); } // 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"); } // seems to be in all replays bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + 19859; // next section is language libraries? // --------------------------------------- bitReader.Read(8); bitReader.Read(8); 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++) { 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.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(14); if (replay.ReplayBuild >= 53548 || replay.ReplayBuild == 53270) { bitReader.ReadBytes(232); bitReader.Read(1); } else if (replay.ReplayBuild >= 52860) { bitReader.ReadBytes(228); bitReader.Read(7); } else if (replay.ReplayVersionMajor == 2 && replay.ReplayBuild >= 52561) { bitReader.ReadBytes(227); bitReader.Read(4); } else if (replay.ReplayVersionMajor == 2 && replay.ReplayBuild >= 51978) { bitReader.ReadBytes(224); bitReader.Read(0); } else if (replay.ReplayBuild >= 52124) { bitReader.ReadBytes(59); bitReader.Read(4); } else if (replay.ReplayBuild >= 51609) { bitReader.ReadBytes(58); bitReader.Read(7); } else { bitReader.ReadBytes(12); // each byte has a max value of 0x7F (127) if (replay.ReplayBuild >= 48027) { bitReader.ReadInt16(); } else { bitReader.ReadInt32(); } // this data is a repeat of the usable skins section above // bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + (skinArrayLength * 2); for (int i = 0; i < collectionSize; i++) { // each byte has a max value of 0x7F (127) int value = 0; int x = (int)bitReader.Read(8); if (x > 0) { value += x + 127; } value += (int)bitReader.Read(8); } 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(25); bitReader.Read(7); bool noCollection = bitReader.ReadBoolean(); // repeat of the collection section above if (replay.ReplayBuild >= 51609 && !noCollection) { 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); bitReader.ReadBoolean(); } // else if not equal, then data isn't available, most likely an observer } else if (!noCollection) { 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); } bitReader.ReadBoolean(); // m_hasSilencePenalty if (replay.ReplayBuild >= 61718) { bitReader.ReadBoolean(); bitReader.ReadBoolean(); // m_hasVoiceSilencePenalty } if (replay.ReplayBuild >= 66977) { bitReader.ReadBoolean(); // m_isBlizzardStaff } 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.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); if (replay.ReplayBuild < 38793) { 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); } // 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"); } // seems to be in all replays bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + 19859; // next section is language libraries? // --------------------------------------- bitReader.Read(8); bitReader.Read(8); 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); } // All the Heroes, skins, mounts, effects, some other weird stuff (Cocoon, ArtifactSlot2, TestMountRideSurf, etc...) // -------------------------------------------------------------- int skinArrayLength = bitReader.ReadInt32(); for (int i = 0; i < skinArrayLength; i++) { bitReader.ReadString(bitReader.ReadByte()); // the name of the "skin" } // this next part is just a whole bunch of 0x00 and 0x01 // use to determine if the heroes, skins, mounts are usable by the player (owns/free to play/internet cafe) if (bitReader.ReadInt32() != skinArrayLength) { throw new Exception("skinArrayLength not equal"); } for (int i = 0; i < skinArrayLength; i++) { for (int j = 0; j < 16; j++) // 16 is total player slots { ReadByte0x00(bitReader); var num = bitReader.Read(8); if (num == 1) { } // true; else if (num == 0) { } // false; else { throw new NotImplementedException(); } } } // Player info // ------------------------ if (replay.ReplayBuild <= 43259) { // Builds that are not yet supported for detailed parsing GetBattleTags(replay, bitReader); return; } bitReader.ReadInt32(); bitReader.ReadBytes(33); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.ReadByte(); // why 0x19? if (replay.ReplayBuild < 47479) { ExtendedBattleTagParsingOld(replay, bitReader); return; } else if (replay.ReplayBuild == 47479) { // build 47479, after November 2, 2016 around 7pm CDT the data in this section changed slightly // there is no longer a duplicated TId if (!DetectBattleTagChangeBuild47479(replay, bitReader)) { ExtendedBattleTagParsingOld(replay, bitReader); return; } } // for builds after 47479 for (int i = 0; i < replay.ClientListByUserID.Length; i++) { if (replay.ClientListByUserID[i] == null) { break; } string TId; // 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); } else { ReadByte0x00(bitReader); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.Read(6); // get XXXXXXXX#YYY TId = Encoding.UTF8.GetString(ReadSpecialBlob(bitReader, 8)); } // next 31 bytes bitReader.ReadBytes(4); // same for all players bitReader.ReadBytes(14); bitReader.ReadBytes(13); // same for all players bitReader.ReadBytes(40); 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) bitReader.ReadBytes(8); } 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 // -------------------------------- } }
/// <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); if (replay.ReplayBuild < 38793) { 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); } // 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"); } // seems to be in all replays bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + 19859; // next section is language libraries? // --------------------------------------- bitReader.Read(8); bitReader.Read(8); 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); } // All the Heroes, skins, mounts, effects, some other weird stuff (Cocoon, ArtifactSlot2, TestMountRideSurf, etc...) // -------------------------------------------------------------- List <string> skins = new List <string>(); int skinArrayLength = 0; if (replay.ReplayBuild >= 48027) { skinArrayLength = bitReader.ReadInt16(); } else { skinArrayLength = bitReader.ReadInt32(); } if (skinArrayLength > 1000) { throw new Exception("skinArrayLength is an unusually large number"); } for (int i = 0; i < skinArrayLength; i++) { skins.Add(bitReader.ReadString(bitReader.ReadByte())); } // use to determine if the heroes, skins, mounts are usable by the player (owns/free to play/internet cafe) if (bitReader.ReadInt32() != skinArrayLength) { throw new Exception("skinArrayLength not equal"); } for (int i = 0; i < skinArrayLength; i++) { for (int j = 0; j < 16; j++) // 16 is total player slots { bitReader.ReadByte(); // new values beginning on ptr 47801 // 0xC3 = free to play? // more values: 0xC1, 0x02, 0x83 var num = bitReader.Read(8); if (replay.ClientListByUserID[j] != null) { if (num > 0) { // usable } else if (num == 0) { // not usable } 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(); bitReader.ReadBytes(33); ReadByte0x00(bitReader); ReadByte0x00(bitReader); bitReader.ReadByte(); // 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)); } // next 30 bytes bitReader.ReadBytes(4); // same for all players bitReader.ReadBytes(14); bitReader.ReadBytes(12); // same for all players // these were important in ptr build 47801, not sure what it's used for now // each byte has a max value of 0x7F (127) if (replay.ReplayBuild >= 48027) { bitReader.ReadInt16(); } else { bitReader.ReadInt32(); } // this data is a repeat of the usable skins section above //bitReader.stream.Position = bitReader.stream.Position = bitReader.stream.Position + (skinArrayLength * 2); for (int i = 0; i < skinArrayLength; i++) { // each byte has a max value of 0x7F (127) int value = 0; int x = (int)bitReader.Read(8); if (x > 0) { value += x + 127; } value += (int)bitReader.Read(8); } 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) bitReader.ReadBytes(8); } 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]); // these similar bytes don't occur for last player bitReader.ReadBytes(27); } // some more data after this } }