internal static DwgObject Parse(BitReader reader, DwgObjectCache objectCache, DwgVersionId version) { reader.StartCrcCheck(); var size = reader.Read_MS(); var crcStart = reader.Offset + size; var typeCode = reader.Read_BS(); if (!Enum.IsDefined(typeof(DwgObjectType), typeCode)) { // unsupported return(null); } var type = (DwgObjectType)typeCode; var obj = CreateObject(type); obj.ReadCommonDataStart(reader); obj.ParseSpecific(reader, version); obj.ReadCommonDataEnd(reader); // ensure there's no extra data reader.AlignToByte(); reader.SkipBytes(Math.Max(0, crcStart - reader.Offset)); reader.ValidateCrc(initialValue: DwgHeaderVariables.InitialCrcValue); obj.ValidateCommonValues(); obj.OnAfterObjectRead(reader, objectCache); return(obj); }
private static byte[] ReadSpecialBlob(BitReader bitReader, int numBitsForLength) { var stringLength = bitReader.Read(numBitsForLength); bitReader.AlignToByte(); bitReader.ReadBytes(2); return(bitReader.ReadBytes((int)stringLength)); }
/// <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 < 47479 || replay.ReplayBuild < 47903 || replay.ReplayBuild == 52124 || replay.ReplayBuild == 52381 || replay.GameMode == GameMode.Unknown) { GetBattleTags(replay, bitReader); return; } uint dependenciesLength = bitReader.Read(6); for (int i = 0; i < dependenciesLength; i++) { bitReader.ReadBlobPrecededWithLength(10); } // s2ma cache handles uint s2maCacheHandlesLength = bitReader.Read(6); for (int i = 0; i < s2maCacheHandlesLength; i++) { bitReader.AlignToByte(); if (bitReader.ReadString(4) != "s2ma") { throw new DetailedParsedException($"s2ma cache"); } bitReader.ReadBytes(36); } DetailedParse(bitReader, replay, s2maCacheHandlesLength); } }
public static List <IGameEvent> Parse(Replay replay, byte[] buffer) { // The GameEvent file changes significantly after 16561. // This is sometime around the first patch after release. Since // parsing replays this old should be extremely rare, I don't believe // its worth the effort to try to support both. If it is, it should be // done in another method. // // Still a bitstream, but stuff's moved around and event codes are different. if (replay.ReplayBuild < 16561) { throw new NotSupportedException( "Replay builds under 16561 are not supported for parsing GameEvent log."); } // Initialize Ability and Unit data. var effectiveBuild = BuildData.GetInstance().GetEffectiveBuild(replay.ReplayBuild); if (effectiveBuild == 0) { throw new NotSupportedException( String.Format("Replay build {0} is not supported by the current event database", replay.ReplayBuild)); } var abilityData = new AbilityData(effectiveBuild); var unitData = new UnitData(effectiveBuild); var events = new List <IGameEvent>(); // Keep a reference to know the game length. var ticksElapsed = 0; using (var stream = new MemoryStream(buffer)) { var bitReader = new BitReader(stream); var playersGone = new bool[0x10]; while (!bitReader.EndOfStream) { var intervalLength = 6 + (bitReader.Read(2) << 3); var interval = bitReader.Read(intervalLength); ticksElapsed += (int)interval; var playerIndex = (int)bitReader.Read(5); Player player; if (playerIndex < 0x10) { player = replay.GetPlayerById(playerIndex); } else { player = Player.Global; } var eventType = bitReader.Read(7); IGameEvent gameEvent; switch (eventType) { case 0x05: // Game start gameEvent = new GameStartEvent(); break; case 0x0b: case 0x0c: // Join game gameEvent = new PlayerJoinEvent(bitReader, replay, playerIndex); break; case 0x19: // Leave game gameEvent = new PlayerLeftEvent(player); playersGone[playerIndex] = true; DetectWinners(playersGone, replay); break; case 0x1b: // Ability gameEvent = new AbilityEvent(bitReader, replay, player, abilityData, unitData); break; case 0x1c: // Selection gameEvent = new SelectionEvent(bitReader, replay, player, unitData); break; case 0x1d: // Control groups gameEvent = new HotkeyEvent(bitReader, replay, player); break; case 0x1f: // Send resources gameEvent = new SendResourcesEvent(bitReader, replay); break; case 0x23: // ?? gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; bitReader.Read(8); break; case 0x26: // ?? gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; bitReader.Read(32); bitReader.Read(32); break; case 0x27: // Target critter - special gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Selection; var unitId = bitReader.Read(32); break; case 0x31: // Camera gameEvent = new CameraEvent(bitReader, replay); break; case 0x37: // UI Event gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Other; bitReader.Read(32); bitReader.Read(32); break; case 0x38: // weird sync event { gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Other; for (var j = 0; j < 2; j++) { var length = bitReader.Read(8); for (var i = 0; i < length; i++) { bitReader.Read(32); } } break; } case 0x3c: // ??? gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; bitReader.Read(16); break; case 0x46: // Request resources gameEvent = new RequestResourcesEvent(bitReader, replay); break; case 0x47: // ?? -- associated with send minerals gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; bitReader.Read(32); break; case 0x48: // ?? -- sync event gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; bitReader.Read(32); break; case 0x4C: // ?? -- seen with spectator bitReader.Read(4); gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; break; case 0x59: // ?? -- sync flags maybe? bitReader.Read(32); gameEvent = new GameEventBase(); gameEvent.EventType = GameEventType.Inactive; break; default: // debug throw new InvalidOperationException(String.Format( "Unknown event type {0:x} at {1:x} in replay.game.events", eventType, bitReader.Cursor)); } gameEvent.Player = player; gameEvent.Time = Timestamp.Create(ticksElapsed); events.Add(gameEvent); bitReader.AlignToByte(); } } replay.GameLength = Timestamp.Create(ticksElapsed).TimeSpan; return(events); }
private static void DetailedParse(BitReader bitReader, Replay replay, uint s2maCacheHandlesLength) { bitReader.AlignToByte(); for (; ;) { // we're just going to skip all the way down to the s2mh if (bitReader.ReadString(4) == "s2mh") { bitReader.stream.Position -= 4; break; } else { bitReader.stream.Position -= 3; } } // bitReader.Read(???); // this depends on previous data (not byte aligned) // s2mh cache handles // uint s2mhCacheHandlesLength = bitReader.Read(6); // for (int i = 0; i < s2mhCacheHandlesLength; i++) for (var i = 0; i < s2maCacheHandlesLength; i++) { bitReader.AlignToByte(); if (bitReader.ReadString(4) != "s2mh") { throw new Exception($"s2mh cache"); } bitReader.ReadBytes(36); } // Player collections - starting with HOTS 2.0 (live build 52860) // strings gone starting with build (ptr) 55929 // -------------------------------------------------------------- var playerCollection = new List <string>(); int collectionSize = replay.ReplayBuild >= 48027 ? bitReader.ReadInt16() : bitReader.ReadInt32(); if (collectionSize > 8000) { throw new DetailedParsedException("collectionSize is an unusually large number"); } for (var 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 (var i = 0; i < collectionSize; i++) { for (var 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(); } } } } } if (replay.ReplayBuild >= 85027) { bitReader.ReadByte(); // could contain m_disabledHeroList } // Player info // ------------------------ // m_randomSeed, set it if it hasn't been set if (replay.RandomValue == 0) { replay.RandomValue = (uint)bitReader.ReadInt32(); } else { bitReader.ReadInt32(); } bitReader.ReadBytes(4); uint playerListLength = bitReader.Read(5); for (uint i = 0; i < playerListLength; i++) { bitReader.Read(32); bitReader.Read(5); // player index // toon bitReader.Read(8); // m_region if (bitReader.ReadStringFromBits(32, true) != "Hero") // m_programId { throw new DetailedParsedException("Not Hero"); } bitReader.Read(32); // m_realm bitReader.ReadLong(64); // m_id // internal toon bitReader.Read(8); // m_region if (bitReader.ReadStringFromBits(32, true) != "Hero") // m_programId { throw new DetailedParsedException("Not Hero"); } bitReader.Read(32); // m_realm int idLength = (int)bitReader.Read(7) + 2; bitReader.AlignToByte(); replay.ClientListByUserID[i].BattleNetTId = bitReader.ReadString(idLength); bitReader.Read(6); if (replay.ReplayBuild <= 47479) { // internal toon repeat bitReader.Read(8); // m_region if (bitReader.ReadStringFromBits(32, true) != "Hero") // m_programId { throw new DetailedParsedException("Not Hero"); } bitReader.Read(32); // m_realm idLength = (int)bitReader.Read(7) + 2; bitReader.AlignToByte(); if (replay.ClientListByUserID[i].BattleNetTId != bitReader.ReadString(idLength)) { throw new DetailedParsedException("Duplicate internal id does not match"); } } bitReader.Read(2); bitReader.ReadBytes(25); bitReader.Read(24); if (replay.GameMode == GameMode.Cooperative) { bitReader.ReadBytes(8); // ai games have 8 more bytes somewhere around here } bitReader.Read(7); if (!bitReader.ReadBoolean()) { // repeat of the collection section above if (replay.ReplayBuild >= 51609 || replay.ReplayBuild == 47903 || replay.ReplayBuild == 47479) { bitReader.ReadBitArray(bitReader.Read(12)); } else if (replay.ReplayBuild > 47219) { // each byte has a max value of 0x7F (127) bitReader.ReadBytes((int)bitReader.Read(15) * 2); } else { bitReader.ReadBitArray(bitReader.Read(9)); } bitReader.ReadBoolean(); } 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[i].PartyValue = bitReader.ReadInt32() + bitReader.ReadInt32(); // players in same party will have the same exact 8 bytes of data } bitReader.ReadBoolean(); // has battletag? var battleTag = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(7)).Split('#'); // battleTag <name>#xxxxx if (battleTag[0] != replay.ClientListByUserID[i].Name) { throw new DetailedParsedException("Couldn't find BattleTag"); } if (battleTag.Length == 2) { replay.ClientListByUserID[i].BattleTag = int.Parse(battleTag[1]); } if (replay.ReplayBuild >= 52860 || (replay.ReplayVersionMajor == 2 && replay.ReplayBuild >= 51978)) { replay.ClientListByUserID[i].AccountLevel = (int)bitReader.Read(32); // in custom games, this is a 0 } if (replay.ReplayBuild >= 69947) { bitReader.ReadBoolean(); // m_hasActiveBoost } } // 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++) { var nameLength = reader.ReadByte(); reader.AlignToByte(); var str = reader.ReadString(nameLength); playerList[j] = str; if (replay.ReplayBuild < 24764) { reader.ReadBytes(5); } else { if (reader.ReadBoolean()) { var strLength = reader.ReadByte(); reader.AlignToByte(); var clanTag = reader.ReadString(strLength); } if (reader.ReadBoolean()) { reader.ReadByte(); // Highest league } if (reader.ReadBoolean()) { var swarmLevel = reader.ReadInt32(); // Swarm level } reader.ReadInt32(); // Random seed if (reader.ReadBoolean()) { reader.ReadByte(); // Race Preference } if (reader.ReadBoolean()) { reader.ReadByte(); // Team Preference } reader.ReadBoolean(); //test map reader.ReadBoolean(); //test auto reader.ReadBoolean(); //examine reader.ReadBoolean(); //custom interface reader.Read(2); //observer } } if (replay.ReplayBuild < 24764) { using (var binaryReader = new BinaryReader(stream)) { // Save the full list of clients. // This is no longer necessary since we get the client list elsewhere. //// replay.ClientList = playerList; if (PositionAfter(binaryReader, new byte[] { 115, 50, 109, 97 })) { reader.ReadBytes(2); var gatewayStr = reader.ReadBytes(2); var gateway = Encoding.UTF8.GetString(gatewayStr); replay.Gateway = gateway; } else { replay.GameType = GameType.SinglePlayer; } } } } }
public static void Parse(StormReplay replay, ReadOnlySpan <byte> source) { BitReader bitReader = new BitReader(source, EndianType.BigEndian); uint dependenciesLength = bitReader.ReadBits(6); for (int i = 0; i < dependenciesLength; i++) { bitReader.ReadBlobAsString(10); } // s2ma cache handles uint s2maCacheHandlesLength = bitReader.ReadBits(6); for (int i = 0; i < s2maCacheHandlesLength; i++) { if (bitReader.ReadStringFromBytes(4) != "s2ma") { throw new StormParseException($"{_exceptionHeader}: s2ma cache"); } bitReader.ReadAlignedBytes(36); } /*source.ReadAlignedBytes(94); * if (source.ReadStringFromBytes(4) != "Clsd") * throw new StormParseException($"{ExceptionHeader}: Clsd"); */ // we're just going to skip all the way down to the s2mh bitReader.AlignToByte(); /*for (; ;) * { * if (source.ReadStringFromBytes(4) == "s2mh") * { * BitReader.Index -= 4; * break; * } * else * { * BitReader.Index -= 3; * } * }*/ /* * for (; ; ) * { * if (source.ReadStringFromBytes(4) == "s2mv") * { * BitReader.Index -= 4; * break; * } * else * { * BitReader.Index -= 3; * } * }*/ // first hit /* * BitReader.Index -= 1; * * uint s2mvCacheHandlesLength = source.ReadBits(8); * * for (int i = 0; i < s2mvCacheHandlesLength; i++) * { * if (source.ReadStringFromBytes(4) != "s2mv") * throw new StormParseException($"{ExceptionHeader}: s2mv cache"); * * source.ReadAlignedBytes(36); * } * * uint localeCount = source.ReadBits(5); * * for (int i = 0; i < localeCount; i++) * { * source.ReadStringFromBits(32); // locale * * uint s2mlCacheHandlesLength = source.ReadBits(6); * * for (int j = 0; j < s2mlCacheHandlesLength; j++) * { * if (source.ReadStringFromBytes(4) != "s2ml") * throw new StormParseException($"{ExceptionHeader}: s2ml cache"); * * source.ReadAlignedBytes(36); * } * } * * source.ReadAlignedBytes(16); * uint sm2vCacheBlizzLength = source.ReadBits(8); * * for (int i = 0; i < sm2vCacheBlizzLength; i++) * { * * } */ // second s2mv hit /* * for (; ; ) * { * if (source.ReadStringFromBytes(4) == "s2mv") * { * BitReader.Index -= 4; * break; * } * else * { * BitReader.Index -= 3; * } * } * for (int i = 0; i < 2; i++) * { * if (source.ReadStringFromBytes(4) != "s2mv") * throw new StormParseException($"{ExceptionHeader}: s2mv cache"); * * source.ReadAlignedBytes(36); * } * * source.ReadBits(1); * * uint region = source.ReadBits(8); // m_region * if (source.ReadStringFromBits(32) != "Hero") // m_programId * throw new StormParseException($"{ExceptionHeader}: Not Hero"); * source.ReadBits(32); // m_realm * * int blizzIdLength = (int)source.ReadBits(7); * * if (region >= 90) * { * if (source.ReadStringFromBytes(2) != "T:") * throw new StormParseException($"{ExceptionHeader}: Not blizz T:"); * source.ReadStringFromBytes(blizzIdLength); * } * else * { * source.ReadStringFromBytes(blizzIdLength); * source.ReadStringFromBytes(2); * } * * source.ReadBits(8); // m_region * if (source.ReadStringFromBits(32) != "Hero") // m_programId * throw new StormParseException($"{ExceptionHeader}: Not Hero"); * source.ReadBits(32); // m_realm * source.ReadLongBits(64); // m_id * * * int klj = (int)source.ReadBits(12); * * int sdfad = (int)source.ReadBits(12); * * * source.ReadBits(1); //temp * */ for (; ;) { if (bitReader.ReadStringFromBytes(4) == "s2mh") { bitReader.Index -= 4; break; } else { bitReader.Index -= 3; } } // source.ReadBits(???); // this depends on previous data (not byte aligned) // s2mh cache handles // uint s2mhCacheHandlesLength = source.ReadBits(6); // for (int i = 0; i < s2mhCacheHandlesLength; i++) for (int i = 0; i < s2maCacheHandlesLength; i++) { if (bitReader.ReadStringFromBytes(4) != "s2mh") { throw new StormParseException($"{_exceptionHeader}: s2mh cache"); } bitReader.ReadAlignedBytes(36); } // player collections uint collectionSize; // strings gone starting with build (ptr) 55929 if (replay.ReplayBuild >= 48027) { collectionSize = bitReader.ReadBits(16); } else { collectionSize = bitReader.ReadBits(32); } for (uint i = 0; i < collectionSize; i++) { if (replay.ReplayBuild >= 55929) { bitReader.ReadAlignedBytes(8); // most likey an identifier for the item; first six bytes are 0x00 } else { bitReader.ReadStringFromBytes(bitReader.ReadAlignedByte()); } } // use to determine if the collection item is usable by the player (owns/free to play/internet cafe) if (bitReader.ReadBits(32) != collectionSize) { throw new StormParseException($"{_exceptionHeader}: collection difference"); } for (int i = 0; i < collectionSize; i++) { // 16 is total player slots for (int j = 0; j < 16; j++) { bitReader.ReadAlignedByte(); bitReader.ReadAlignedByte(); // more likely a boolean to get the value if (replay.ReplayBuild < 55929) { // when the identifier is a string, set the value to the appropriate array index } } } // Player info if (replay.ReplayBuild <= 47479 || replay.ReplayBuild == 47801 || replay.ReplayBuild == 47903) { // 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, source); return; } replay.RandomValue = bitReader.ReadBits(32); // m_randomSeed bitReader.ReadAlignedBytes(4); uint playerListLength = bitReader.ReadBits(5); if (replay.PlayersWithObserversCount != playerListLength) { throw new StormParseException($"{_exceptionHeader}: mismatch on player list length - {playerListLength} to {replay.PlayersWithObserversCount}"); } for (uint i = 0; i < playerListLength; i++) { bitReader.ReadBits(32); uint playerIndex = bitReader.ReadBits(5); // player index StormPlayer player = replay.ClientListByUserID[playerIndex]; // toon handle uint playerRegion = bitReader.ReadBits(8); // m_region bitReader.EndianType = EndianType.LittleEndian; if (bitReader.ReadBits(32) != 1869768008) // m_programId { throw new StormParseException($"{_exceptionHeader}: Not Hero"); } bitReader.EndianType = EndianType.BigEndian; uint playerRealm = bitReader.ReadBits(32); // m_realm long playerId = bitReader.ReadLongBits(64); // m_id if (player.PlayerType == PlayerType.Human) { if (player.ToonHandle !.Region != playerRegion) { throw new StormParseException($"{_exceptionHeader}: Mismatch on player region"); } if (player.ToonHandle.Realm != playerRealm) { throw new StormParseException($"{_exceptionHeader}: Mismatch on player realm"); } if (player.ToonHandle.Id != playerId) { throw new StormParseException($"{_exceptionHeader}: Mismatch on player id"); } } else if (player.PlayerType == PlayerType.Observer || player.PlayerType == PlayerType.Unknown) { // observers don't have the information carried over to the details file and sometimes not the initdata file player.ToonHandle ??= new ToonHandle(); player.ToonHandle.Region = (int)playerRegion; player.ToonHandle.ProgramId = 1869768008; player.ToonHandle.Realm = (int)playerRealm; player.ToonHandle.Id = (int)playerId; player.PlayerType = PlayerType.Observer; player.Team = StormTeam.Observer; } // toon handle again but with T_ shortcut bitReader.ReadBits(8); // m_region bitReader.EndianType = EndianType.LittleEndian; if (bitReader.ReadBits(32) != 1869768008) // m_programId (Hero) { throw new StormParseException($"{_exceptionHeader}: Not Hero"); } bitReader.EndianType = EndianType.BigEndian; bitReader.ReadBits(32); // m_realm int idLength = (int)bitReader.ReadBits(7) + 2; player.ToonHandle ??= new ToonHandle(); player.ToonHandle.ShortcutId = bitReader.ReadStringFromBytes(idLength); bitReader.ReadBits(6); if (replay.ReplayBuild <= 47479) { // toon handle repeat again with T_ shortcut bitReader.ReadBits(8); // m_region if (bitReader.ReadStringFromBits(32) != "Hero") // m_programId { throw new StormParseException($"{_exceptionHeader}: Not Hero"); } bitReader.ReadBits(32); // m_realm idLength = (int)bitReader.ReadBits(7) + 2; if (player.ToonHandle.ShortcutId != bitReader.ReadStringFromBytes(idLength)) { throw new StormParseException($"{_exceptionHeader}: Duplicate shortcut id does not match"); } bitReader.ReadBits(6); } bitReader.ReadBits(2); bitReader.ReadUnalignedBytes(25); bitReader.ReadBits(24); // ai games have 8 more bytes somewhere around here if (replay.GameMode == StormGameMode.Cooperative) { bitReader.ReadUnalignedBytes(8); } bitReader.ReadBits(7); if (!bitReader.ReadBoolean()) { // repeat of the collection section above if (replay.ReplayBuild > 51609 || replay.ReplayBuild == 47903 || replay.ReplayBuild == 47479) { bitReader.ReadBitArray(bitReader.ReadBits(12)); } else if (replay.ReplayBuild > 47219) { // each byte has a max value of 0x7F (127) bitReader.ReadUnalignedBytes((int)bitReader.ReadBits(15) * 2); } else { bitReader.ReadBitArray(bitReader.ReadBits(9)); } bitReader.ReadBoolean(); } bool isSilenced = bitReader.ReadBoolean(); // m_hasSilencePenalty if (player.PlayerType == PlayerType.Observer) { player.IsSilenced = isSilenced; } if (replay.ReplayBuild >= 61718) { bitReader.ReadBoolean(); bool isVoiceSilenced = bitReader.ReadBoolean(); // m_hasVoiceSilencePenalty if (player.PlayerType == PlayerType.Observer) { player.IsVoiceSilenced = isVoiceSilenced; } } if (replay.ReplayBuild >= 66977) { bool isBlizzardStaff = bitReader.ReadBoolean(); // m_isBlizzardStaff if (player.PlayerType == PlayerType.Observer) { player.IsBlizzardStaff = isBlizzardStaff; } } if (bitReader.ReadBoolean()) // is player in party { player.PartyValue = bitReader.ReadLongBits(64); // players in same party will have the same exact 8 bytes of data } bitReader.ReadBoolean(); string battleTagName = bitReader.ReadBlobAsString(7); int poundIndex = battleTagName.IndexOf('#'); if (!string.IsNullOrEmpty(battleTagName) && poundIndex < 0) { throw new StormParseException($"{_exceptionHeader}: Invalid battletag"); } ReadOnlySpan <char> namePart = battleTagName.AsSpan().Slice(0, poundIndex); if (!namePart.SequenceEqual(player.Name)) { throw new StormParseException($"{_exceptionHeader}: Mismatch on battletag name with player name"); } player.BattleTagName = battleTagName; if (replay.ReplayBuild >= 52860 || (replay.ReplayVersion.Major == 2 && replay.ReplayBuild >= 51978)) { player.AccountLevel = (int)bitReader.ReadBits(32); // in custom games, this is a 0 } if (replay.ReplayBuild >= 69947) { bool hasActiveBoost = bitReader.ReadBoolean(); // m_hasActiveBoost if (player.PlayerType == PlayerType.Observer) { player.HasActiveBoost = hasActiveBoost; } } } }
public static void Parse(StormReplay replay, ReadOnlySpan <byte> source) { if (source.Length <= 1) { return; } BitReader bitReader = new BitReader(source, EndianType.BigEndian); uint ticksElapsed = 0; while (bitReader.Index < source.Length) { ticksElapsed += bitReader.ReadBits(6 + ((int)bitReader.ReadBits(2) << 3)); TimeSpan timeStamp = TimeSpan.FromSeconds(ticksElapsed / 16.0); int playerIndex = (int)bitReader.ReadBits(5); StormMessageEventType messageEventType = (StormMessageEventType)bitReader.ReadBits(4); StormMessage?message = null; switch (messageEventType) { case StormMessageEventType.SChatMessage: ChatMessage chatMessage = new ChatMessage { MessageTarget = (StormMessageTarget)bitReader.ReadBits(3), // m_recipient (the target) Message = bitReader.ReadBlobAsString(11), // m_string }; message = new StormMessage(chatMessage); break; case StormMessageEventType.SPingMessage: PingMessage pingMessage = new PingMessage() { MessageTarget = (StormMessageTarget)bitReader.ReadBits(3), // m_recipient (the target) Point = new Point((double)(bitReader.ReadInt32Unaligned() - (-2147483648)) / 4096, ((double)bitReader.ReadInt32Unaligned() - (-2147483648)) / 4096), // m_point x and m_point y }; message = new StormMessage(pingMessage); break; case StormMessageEventType.SLoadingProgressMessage: LoadingProgressMessage loadingProgressMessage = new LoadingProgressMessage() { LoadingProgress = bitReader.ReadInt32Unaligned() - (-2147483648), // m_progress }; message = new StormMessage(loadingProgressMessage); break; case StormMessageEventType.SServerPingMessage: break; case StormMessageEventType.SReconnectNotifyMessage: bitReader.ReadBits(2); // m_status; is either a 1 or a 2 break; case StormMessageEventType.SPlayerAnnounceMessage: PlayerAnnounceMessage playerAnnounceMessage = new PlayerAnnounceMessage() { AnnouncementType = (AnnouncementType)bitReader.ReadBits(2), }; switch (playerAnnounceMessage.AnnouncementType) { case AnnouncementType.None: break; case AnnouncementType.Ability: int abilityLink = bitReader.ReadInt16Unaligned(); // m_abilLink int abilityIndex = (int)bitReader.ReadBits(5); // m_abilCmdIndex int buttonLink = bitReader.ReadInt16Unaligned(); // m_buttonLink playerAnnounceMessage.AbilityAnnouncement = new AbilityAnnouncement(abilityIndex, abilityLink, buttonLink); break; case AnnouncementType.Behavior: bitReader.ReadInt16Unaligned(); // m_behaviorLink bitReader.ReadInt16Unaligned(); // m_buttonLink break; case AnnouncementType.Vitals: playerAnnounceMessage.VitalAnnouncement = new VitalAnnouncement((VitalType)(bitReader.ReadInt16Unaligned() - (-32768))); break; default: throw new NotImplementedException(); } if (replay.ReplayBuild > 45635) { // m_announceLink bitReader.ReadInt16Unaligned(); } bitReader.ReadInt32Unaligned(); // m_otherUnitTag bitReader.ReadInt32Unaligned(); // m_unitTag message = new StormMessage(playerAnnounceMessage); break; default: throw new NotImplementedException(); } if (message != null) { message.MessageEventType = messageEventType; message.Timestamp = timeStamp; if (playerIndex != 16) { message.MessageSender = replay.ClientListByUserID[playerIndex]; } replay.MessagesInternal.Add(message); } bitReader.AlignToByte(); } }
/// <summary> Parses the Replay.Messages.Events file. </summary> /// <param name="buffer"> Buffer containing the contents of the replay.messages.events file. </param> /// <returns> A list of messages parsed from the buffer. </returns> public static void Parse(Replay replay, byte[] buffer) { if (buffer.Length <= 1) { // Chat has been removed from this replay return; } var ticksElapsed = 0; using (var stream = new MemoryStream(buffer)) { var bitReader = new BitReader(stream); while (!bitReader.EndOfStream) { var message = new Message(); ticksElapsed += (int)bitReader.Read(6 + (bitReader.Read(2) << 3)); message.Timestamp = new TimeSpan(0, 0, (int)Math.Round(ticksElapsed / 16.0)); var playerIndex = (int)bitReader.Read(5); if (playerIndex != 16) { message.MessageSender = replay.ClientListByUserID[playerIndex]; message.PlayerIndex = playerIndex; } message.MessageEventType = (MessageEventType)bitReader.Read(4); switch (message.MessageEventType) { case MessageEventType.SChatMessage: { var chatMessage = new ChatMessage { MessageTarget = (MessageTarget)bitReader.Read(3), // m_recipient (the target) Message = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(11)) // m_string }; message.ChatMessage = chatMessage; replay.Messages.Add(message); break; } case MessageEventType.SPingMessage: { var pingMessage = new PingMessage { MessageTarget = (MessageTarget)bitReader.Read(3), // m_recipient (the target) XCoordinate = bitReader.ReadInt32() - (-2147483648), // m_point x YCoordinate = bitReader.ReadInt32() - (-2147483648) // m_point y }; message.PingMessage = pingMessage; replay.Messages.Add(message); break; } case MessageEventType.SLoadingProgressMessage: { // can be used to keep track of how fast/slow players are loading // also includes players who are reloading the game var progress = bitReader.ReadInt32() - (-2147483648); // m_progress break; } case MessageEventType.SServerPingMessage: { break; } case MessageEventType.SReconnectNotifyMessage: { bitReader.Read(2); // m_status; is either a 1 or a 2 break; } case MessageEventType.SPlayerAnnounceMessage: { var announceMessage = new PlayerAnnounceMessage { AnnouncementType = (AnnouncementType)bitReader.Read(2) }; switch (announceMessage.AnnouncementType) { case AnnouncementType.None: { break; } case AnnouncementType.Ability: { var ability = new AbilityAnnouncment { AbilityLink = bitReader.ReadInt16(), // m_abilLink AbilityIndex = (int)bitReader.Read(5), // m_abilCmdIndex ButtonLink = bitReader.ReadInt16() // m_buttonLink }; announceMessage.AbilityAnnouncement = ability; break; } case AnnouncementType.Behavior: // no idea what triggers this { bitReader.ReadInt16(); // m_behaviorLink bitReader.ReadInt16(); // m_buttonLink break; } case AnnouncementType.Vitals: { var vital = new VitalAnnouncment { VitalType = (VitalType)(bitReader.ReadInt16() - (-32768)) }; announceMessage.VitalAnnouncement = vital; break; } default: throw new NotImplementedException(); } if (replay.ReplayBuild > 45635) { // m_announceLink bitReader.ReadInt16(); } bitReader.ReadInt32(); // m_otherUnitTag bitReader.ReadInt32(); // m_unitTag message.PlayerAnnounceMessage = announceMessage; replay.Messages.Add(message); break; } default: throw new NotImplementedException(); } bitReader.AlignToByte(); } } }