/// <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.initdata file. </param> public static void Parse(Replay replay, byte[] buffer) { using (var stream = new MemoryStream(buffer)) { var reader = new BitReader(stream); int arrayLength = reader.ReadByte(); var stringLength = reader.ReadByte(); for (var i = 0; i < arrayLength; i++) { reader.ReadString(stringLength); reader.ReadBytes(2); // Unknown } // This is not always here; we can't blindly wait for 's2mh' /* while (!reader.EndOfStream) if (reader.ReadString(1) == "s" && reader.ReadString(1) == "2" && reader.ReadString(1) == "m" && reader.ReadString(1) == "h") { reader.stream.Position -= 4; break; } if (reader.EndOfStream) return; for (var j = 0; j < arrayLength; j++) { reader.ReadString(4); // s2mh reader.ReadBytes(2); // 0x00 0x00 reader.ReadBytes(2); // 'Realm' reader.ReadBytes(32); // 'DepHash' } reader.ReadBytes(2); // 0x00 0x00 // Different Skins / Artifacts / Characters - I think this is what users mouse over in the UI before the game arrayLength = reader.ReadInt16(); for (var j = 0; j < arrayLength; j++) reader.ReadString(reader.ReadByte()); reader.ReadBytes(2); // 0x00 0x00 reader.ReadInt16(); do arrayLength = reader.ReadByte(); while (!reader.EndOfStream && (arrayLength == 0 || arrayLength == 1)); if (reader.EndOfStream) return; */ // Now get the BattleTag for each player var battleTagDigits = new List<char>(); foreach (var player in replay.Players.Where(i => i != null)) { // 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 numbers from the BattleTag while (!reader.EndOfStream) { var currentCharacter = (char)reader.ReadByte(); if (char.IsDigit(currentCharacter)) battleTagDigits.Add(currentCharacter); else break; } if (reader.EndOfStream) break; player.BattleTag = int.Parse(string.Join("", battleTagDigits)); } } }
/// <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 } }