public static void Parse(Replay replay, byte[] buffer) { // Referenced from https://raw.githubusercontent.com/Blizzard/heroprotocol/master/protocol39445.py var gameEvents = new List <GameEvent>(); var ticksElapsed = 0; using (var stream = new MemoryStream(buffer)) { var bitReader = new Streams.BitReader(stream); while (!bitReader.EndOfStream) { var gameEvent = new GameEvent(); ticksElapsed += (int)bitReader.Read(6 + (bitReader.Read(2) << 3)); gameEvent.ticksElapsed = ticksElapsed; var playerIndex = (int)bitReader.Read(5); if (playerIndex == 16) { gameEvent.isGlobal = true; } else { gameEvent.player = replay.ClientList[playerIndex]; } gameEvent.eventType = (GameEventType)bitReader.Read(7); switch (gameEvent.eventType) { case GameEventType.CStartGameEvent: break; case GameEventType.CUserFinishedLoadingSyncEvent: break; case GameEventType.CUserOptionsEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_gameFullyDownloaded new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_developmentCheatsEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_testCheatsEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_multiplayerCheatsEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_syncChecksummingEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_isMapToMapTransition new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_debugPauseEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_useGalaxyAsserts new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_platformMac new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_cameraFollow new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_baseBuildNum new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_buildNum new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_versionFlags new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(9) } /* m_hotkeyProfile */ } }; break; case GameEventType.CBankFileEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) }; break; case GameEventType.CBankSectionEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(6) }; break; case GameEventType.CBankKeyEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(6) }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) } } }; break; case GameEventType.CBankSignatureEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, array = new TrackerEventStructure[bitReader.Read(5)] }; for (var i = 0; i < gameEvent.data.array.Length; i++) { gameEvent.data.array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(8) } } ; gameEvent.data.blob = bitReader.ReadBlobPrecededWithLength(7); break; case GameEventType.CCameraSaveEvent: bitReader.Read(3); // m_which bitReader.Read(16); // x bitReader.Read(16); // y break; case GameEventType.CCommandManagerResetEvent: bitReader.Read(32); // m_sequence break; case GameEventType.CCmdEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[5] }; // m_cmdFlags if (replay.ReplayBuild < 33684) { gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[22] } } ; else if (replay.ReplayBuild < 37117) { gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[23] } } ; else if (replay.ReplayBuild < 38236) { gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[24] } } ; else { gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[25] } }; for (var i = 0; i < gameEvent.data.array[0].array.Length; i++) { gameEvent.data.array[0].array[i] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(1) } } ; // m_abil if (bitReader.ReadBoolean()) { gameEvent.data.array[1] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_abilLink new TrackerEventStructure { unsignedInt = bitReader.Read(5) }, // m_abilCmdIndex new TrackerEventStructure() } }; if (bitReader.ReadBoolean()) { // m_abilCmdData gameEvent.data.array[1].array[2].unsignedInt = bitReader.Read(8); } } // m_data switch (bitReader.Read(2)) { case 0: // None break; case 1: // TargetPoint gameEvent.data.array[2] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }; break; case 2: // TargetUnit gameEvent.data.array[2] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_targetUnitFlags new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_timer new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_tag new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_snapshotUnitLink new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure(), } }; if (bitReader.ReadBoolean()) { // m_snapshotControlPlayerId gameEvent.data.array[2].array[4].unsignedInt = bitReader.Read(4); } if (bitReader.ReadBoolean()) { // m_snapshotUpkeepPlayerId gameEvent.data.array[2].array[5].unsignedInt = bitReader.Read(4); } // m_snapshotPoint gameEvent.data.array[2].array[6].array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } }; break; case 3: // Data gameEvent.data.array[2] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; } if (replay.ReplayBuild >= 33684) { bitReader.Read(32); // m_sequence } if (bitReader.ReadBoolean()) { gameEvent.data.array[3] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } ; // m_otherUnit if (bitReader.ReadBoolean()) { gameEvent.data.array[4] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } ; // m_unitGroup break; case GameEventType.CSelectionDeltaEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(4) }, // m_controlGroupId // m_delta new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(9) }, // m_subgroupIndex new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure() } } } }; // m_removeMask switch (bitReader.Read(2)) { case 0: // None break; case 1: // Mask bitReader.Read(bitReader.Read(9)); break; case 2: // OneIndices case 3: // ZeroIndices gameEvent.data.array[1].array[1] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[1].array.Length; i++) { gameEvent.data.array[1].array[1].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(9) } } ; break; } // m_addSubgroups gameEvent.data.array[1].array[2] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[2].array.Length; i++) { gameEvent.data.array[1].array[2].array[i] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_unitLink new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_subgroupPriority new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_intraSubgroupPriority new TrackerEventStructure { unsignedInt = bitReader.Read(9) } } } } ; // m_count // m_addUnitTags gameEvent.data.array[1].array[3] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[3].array.Length; i++) { gameEvent.data.array[1].array[3].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } ; break; case GameEventType.CControlGroupUpdateEvent: bitReader.Read(4); // m_controlGroupIndex // m_controlGroupUpdate if (replay.ReplayBuild < 36359) // Not sure exactly when this change happened - roughly around here. This primarily affected 'The Lost Vikings' hero { bitReader.Read(2); } else { bitReader.Read(3); } // m_mask switch (bitReader.Read(2)) { case 0: // None break; case 1: // Mask bitReader.Read(bitReader.Read(9)); break; case 2: // OneIndices case 3: // ZeroIndices gameEvent.data.array[1].array[1] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[1].array.Length; i++) { gameEvent.data.array[1].array[1].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(9) } } ; break; } break; case GameEventType.CResourceTradeEvent: bitReader.Read(4); // m_recipientId bitReader.Read(32); // m_resources, should be offset -2147483648 bitReader.Read(32); // m_resources, should be offset -2147483648 bitReader.Read(32); // m_resources, should be offset -2147483648 break; case GameEventType.CTriggerChatMessageEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(10) }; break; case GameEventType.CTriggerPingEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }; break; case GameEventType.CUnitClickEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // m_unitTag break; case GameEventType.CTriggerSkippedEvent: break; case GameEventType.CTriggerSoundLengthQueryEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } }; break; case GameEventType.CTriggerSoundOffsetEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; case GameEventType.CTriggerTransmissionOffsetEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } }; break; case GameEventType.CTriggerTransmissionCompleteEvent: gameEvent.data = new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }; break; case GameEventType.CCameraUpdateEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[6] }; if (bitReader.ReadBoolean()) { // m_target, x/y gameEvent.data.array[0] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, new TrackerEventStructure { unsignedInt = bitReader.Read(16) } } } } ; if (bitReader.ReadBoolean()) { // m_distance gameEvent.data.array[1] = new TrackerEventStructure { unsignedInt = bitReader.Read(16) } } ; if (bitReader.ReadBoolean()) { // m_pitch gameEvent.data.array[2] = new TrackerEventStructure { unsignedInt = bitReader.Read(16) } } ; if (bitReader.ReadBoolean()) { // m_yaw gameEvent.data.array[3] = new TrackerEventStructure { unsignedInt = bitReader.Read(16) } } ; if (bitReader.ReadBoolean()) { // m_reason gameEvent.data.array[4] = new TrackerEventStructure { vInt = bitReader.Read(8) - 128 } } ; // m_follow gameEvent.data.array[5] = new TrackerEventStructure { unsignedInt = bitReader.Read(1) }; break; case GameEventType.CTriggerPlanetMissionLaunchedEvent: bitReader.Read(32); // m_difficultyLevel, offset -2147483648 break; case GameEventType.CTriggerDialogControlEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) /* Actually signed - not handled correctly */ }, new TrackerEventStructure { vInt = bitReader.Read(32) /* Actually signed - not handled correctly */ }, new TrackerEventStructure() } }; switch (bitReader.Read(3)) { case 0: // None break; case 1: // Checked gameEvent.data.array[2].unsignedInt = bitReader.Read(1); break; case 2: // ValueChanged gameEvent.data.array[2].unsignedInt = bitReader.Read(32); break; case 3: // SelectionChanged gameEvent.data.array[2].vInt = bitReader.Read(32); /* Actually signed - not handled correctly */ break; case 4: // TextChanged gameEvent.data.array[2].DataType = 2; gameEvent.data.array[2].blob = bitReader.ReadBlobPrecededWithLength(11); break; case 5: // MouseButton gameEvent.data.array[2].unsignedInt = bitReader.Read(32); break; } break; case GameEventType.CTriggerSoundLengthSyncEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[2] }; gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(7)] }; for (var i = 0; i < gameEvent.data.array[0].array.Length; i++) { gameEvent.data.array[0].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } ; gameEvent.data.array[1] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(7)] }; for (var i = 0; i < gameEvent.data.array[1].array.Length; i++) { gameEvent.data.array[1].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } ; break; case GameEventType.CTriggerConversationSkippedEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(1) }; break; case GameEventType.CTriggerMouseClickedEvent: bitReader.Read(32); // m_button bitReader.ReadBoolean(); // m_down bitReader.Read(11); // m_posUI X bitReader.Read(11); // m_posUI Y bitReader.Read(20); // m_posWorld X bitReader.Read(20); // m_posWorld Y bitReader.Read(32); // m_posWorld Z (Offset -2147483648) bitReader.Read(8); // m_flags (-128) break; case GameEventType.CTriggerMouseMovedEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(11) }, new TrackerEventStructure { unsignedInt = bitReader.Read(11) }, new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }, new TrackerEventStructure { vInt = bitReader.Read(8) - 128 } } }; break; case GameEventType.CTriggerHotkeyPressedEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // May be missing an offset value break; case GameEventType.CTriggerTargetModeUpdateEvent: bitReader.Read(16); // m_abilLink bitReader.Read(5); // m_abilCmdIndex bitReader.Read(8); // m_state (-128) break; case GameEventType.CTriggerSoundtrackDoneEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; case GameEventType.CTriggerKeyPressedEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(8) - 128 }, new TrackerEventStructure { vInt = bitReader.Read(8) - 128 } } }; break; case GameEventType.CTriggerCutsceneBookmarkFiredEvent: // m_cutsceneId, m_bookmarkName gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) } } }; break; case GameEventType.CTriggerCutsceneEndSceneFiredEvent: // m_cutsceneId gameEvent.data = new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }; break; case GameEventType.CGameUserLeaveEvent: break; case GameEventType.CGameUserJoinEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[5] }; gameEvent.data.array[0] = new TrackerEventStructure { unsignedInt = bitReader.Read(2) }; gameEvent.data.array[1] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(8) }; if (bitReader.ReadBoolean()) { gameEvent.data.array[2] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) } } ; if (bitReader.ReadBoolean()) { gameEvent.data.array[3] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(8) } } ; if (bitReader.ReadBoolean()) { gameEvent.data.array[4] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBytes(40) } } ; break; case GameEventType.CCommandManagerStateEvent: gameEvent.data = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(2) }; // m_state if (replay.ReplayBuild >= 33684) { if (bitReader.ReadBoolean()) { // m_sequence gameEvent.data.array = new[] { new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(8) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(8) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(16) } } } } ; break; case GameEventType.CCmdUpdateTargetPointEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(32) - 2147483648 } } }; break; case GameEventType.CCmdUpdateTargetUnitEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[7] }; gameEvent.data.array[0] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(16) }; // m_targetUnitFlags gameEvent.data.array[1] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(8) }; // m_timer gameEvent.data.array[2] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(32) }; // m_tag gameEvent.data.array[3] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(16) }; // m_snapshotUnitLink if (bitReader.ReadBoolean()) { gameEvent.data.array[4] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(4) } } ; // m_snapshotControlPlayerId if (bitReader.ReadBoolean()) { gameEvent.data.array[5] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(4) } } ; // m_snapshotUpkeepPlayerId gameEvent.data.array[6] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(32) - 2147483648 } } }; // m_snapshotPoint (x, y, z) break; case GameEventType.CHeroTalentSelectedEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // m_index break; case GameEventType.CHeroTalentTreeSelectionPanelToggled: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(1) }; // m_shown break; default: throw new NotImplementedException(); } bitReader.AlignToByte(); gameEvents.Add(gameEvent); } } replay.GameEvents = gameEvents; // Gather talent selections var talentGameEventsDictionary = replay.GameEvents .Where(i => i.eventType == GameEventType.CHeroTalentSelectedEvent) .GroupBy(i => i.player) .ToDictionary( i => i.Key, i => i.Select(j => new Tuple <int, TimeSpan>((int)j.data.unsignedInt.Value, j.TimeSpan)).OrderBy(j => j.Item2).ToArray()); foreach (var player in talentGameEventsDictionary.Keys) { player.Talents = talentGameEventsDictionary[player]; } // Gather Team Level Milestones (From talent choices: 1 / 4 / 7 / 10 / 13 / 16 / 20) for (var currentTeam = 0; currentTeam < replay.TeamLevelMilestones.Length; currentTeam++) { var maxTalentChoices = replay.Players.Where(i => i.Team == currentTeam).Select(i => i.Talents.Length).Max(); replay.TeamLevelMilestones[currentTeam] = new TimeSpan[maxTalentChoices]; var appropriatePlayers = replay.Players.Where(j => j.Team == currentTeam && j.Talents.Length == maxTalentChoices); for (var i = 0; i < replay.TeamLevelMilestones[currentTeam].Length; i++) { replay.TeamLevelMilestones[currentTeam][i] = appropriatePlayers.Select(j => j.Talents[i].Item2).Min(); } } // Uncomment this to write out all replay.game.events to individual text files in the 'C:\HOTSLogs\' folder /* var eventGroups = replay.GameEvents.GroupBy(i => i.eventType).Select(i => new { EventType = i.Key, EventCount = i.Count(), Events = i.OrderBy(j => j.TimeSpan) }); * string eventGroupData = ""; * foreach (var eventGroup in eventGroups) * { * foreach (var eventData in eventGroup.Events) * eventGroupData += eventData.TimeSpan + ": " + eventData.player + ": " + eventData + "\r\n"; * File.WriteAllText(@"C:\HOTSLogs\" + (int)eventGroup.EventType + " " + eventGroup.EventType + @".txt", eventGroupData); * eventGroupData = ""; * } */ } }
public static List<GameEvent> Parse(byte[] buffer, Player[] clientList, int replayBuild) { // Referenced from https://raw.githubusercontent.com/Blizzard/heroprotocol/master/protocol39445.py var gameEvents = new List<GameEvent>(); var ticksElapsed = 0; using (var stream = new MemoryStream(buffer)) { var bitReader = new Streams.BitReader(stream); while (!bitReader.EndOfStream) { var gameEvent = new GameEvent(); ticksElapsed += (int)bitReader.Read(6 + (bitReader.Read(2) << 3)); gameEvent.ticksElapsed = ticksElapsed; var playerIndex = (int)bitReader.Read(5); if (playerIndex == 16) gameEvent.isGlobal = true; else gameEvent.player = clientList[playerIndex]; gameEvent.eventType = (GameEventType)bitReader.Read(7); switch (gameEvent.eventType) { case GameEventType.CStartGameEvent: break; case GameEventType.CUserFinishedLoadingSyncEvent: break; case GameEventType.CUserOptionsEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_gameFullyDownloaded new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_developmentCheatsEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_testCheatsEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_multiplayerCheatsEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_syncChecksummingEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_isMapToMapTransition new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_debugPauseEnabled new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_useGalaxyAsserts new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_platformMac new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, // m_cameraFollow new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_baseBuildNum new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_buildNum new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_versionFlags new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(9) } /* m_hotkeyProfile */ } }; break; case GameEventType.CBankFileEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) }; break; case GameEventType.CBankSectionEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(6) }; break; case GameEventType.CBankKeyEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(6) }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) } } }; break; case GameEventType.CBankSignatureEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, array = new TrackerEventStructure[bitReader.Read(5)] }; for (var i = 0; i < gameEvent.data.array.Length; i++) gameEvent.data.array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(8) }; gameEvent.data.blob = bitReader.ReadBlobPrecededWithLength(7); break; case GameEventType.CCameraSaveEvent: bitReader.Read(3); // m_which bitReader.Read(16); // x bitReader.Read(16); // y break; case GameEventType.CCommandManagerResetEvent: bitReader.Read(32); // m_sequence break; case GameEventType.CGameCheatEvent: // m_target gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[4] }; switch (bitReader.Read(2)) { case 0: // None break; case 1: // TargetPoint gameEvent.data.array[0] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }; break; case 2: // TargetUnit gameEvent.data.array[0] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_targetUnitFlags new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_timer new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_tag new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_snapshotUnitLink new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure(), } }; if (bitReader.ReadBoolean()) // m_snapshotControlPlayerId gameEvent.data.array[0].array[4].unsignedInt = bitReader.Read(4); if (bitReader.ReadBoolean()) // m_snapshotUpkeepPlayerId gameEvent.data.array[0].array[5].unsignedInt = bitReader.Read(4); // m_snapshotPoint gameEvent.data.array[0].array[6].array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } }; break; } bitReader.Read(32); // m_time Encoding.ASCII.GetString(bitReader.ReadBlobPrecededWithLength(10)); // m_verb Encoding.ASCII.GetString(bitReader.ReadBlobPrecededWithLength(10)); // m_arguments break; case GameEventType.CCmdEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[5] }; // m_cmdFlags if (replayBuild < 33684) gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[22] }; else if (replayBuild < 37117) gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[23] }; else if (replayBuild < 38236) gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[24] }; else gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[25] }; for (var i = 0; i < gameEvent.data.array[0].array.Length; i++) gameEvent.data.array[0].array[i] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(1) }; // m_abil if (bitReader.ReadBoolean()) { gameEvent.data.array[1] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_abilLink new TrackerEventStructure { unsignedInt = bitReader.Read(5) }, // m_abilCmdIndex new TrackerEventStructure() } }; if (bitReader.ReadBoolean()) // m_abilCmdData gameEvent.data.array[1].array[2].unsignedInt = bitReader.Read(8); } // m_data switch (bitReader.Read(2)) { case 0: // None break; case 1: // TargetPoint gameEvent.data.array[2] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }; break; case 2: // TargetUnit gameEvent.data.array[2] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_targetUnitFlags new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_timer new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, // m_tag new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_snapshotUnitLink new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure(), } }; if (bitReader.ReadBoolean()) // m_snapshotControlPlayerId gameEvent.data.array[2].array[4].unsignedInt = bitReader.Read(4); if (bitReader.ReadBoolean()) // m_snapshotUpkeepPlayerId gameEvent.data.array[2].array[5].unsignedInt = bitReader.Read(4); // m_snapshotPoint gameEvent.data.array[2].array[6].array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } }; break; case 3: // Data gameEvent.data.array[2] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; } if (replayBuild >= 33684) bitReader.Read(32); // m_sequence if (bitReader.ReadBoolean()) gameEvent.data.array[3] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // m_otherUnit if (bitReader.ReadBoolean()) gameEvent.data.array[4] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // m_unitGroup break; case GameEventType.CSelectionDeltaEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(4) }, // m_controlGroupId // m_delta new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(9) }, // m_subgroupIndex new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure(), new TrackerEventStructure() } } } }; // m_removeMask switch (bitReader.Read(2)) { case 0: // None break; case 1: // Mask bitReader.Read(bitReader.Read(9)); break; case 2: // OneIndices case 3: // ZeroIndices gameEvent.data.array[1].array[1] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[1].array.Length; i++) gameEvent.data.array[1].array[1].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(9) }; break; } // m_addSubgroups gameEvent.data.array[1].array[2] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[2].array.Length; i++) gameEvent.data.array[1].array[2].array[i] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, // m_unitLink new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_subgroupPriority new TrackerEventStructure { unsignedInt = bitReader.Read(8) }, // m_intraSubgroupPriority new TrackerEventStructure { unsignedInt = bitReader.Read(9) } } }; // m_count // m_addUnitTags gameEvent.data.array[1].array[3] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[3].array.Length; i++) gameEvent.data.array[1].array[3].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; case GameEventType.CControlGroupUpdateEvent: bitReader.Read(4); // m_controlGroupIndex // m_controlGroupUpdate if (replayBuild < 36359) // Not sure exactly when this change happened - roughly around here. This primarily affected 'The Lost Vikings' hero bitReader.Read(2); else bitReader.Read(3); // m_mask switch (bitReader.Read(2)) { case 0: // None break; case 1: // Mask bitReader.Read(bitReader.Read(9)); break; case 2: // OneIndices case 3: // ZeroIndices gameEvent.data.array[1].array[1] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(9)] }; for (var i = 0; i < gameEvent.data.array[1].array[1].array.Length; i++) gameEvent.data.array[1].array[1].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(9) }; break; } break; case GameEventType.CSelectionSyncCheckEvent: bitReader.Read(4); // m_controlGroupId // m_selectionSyncData bitReader.Read(9); // m_count bitReader.Read(9); // m_subgroupCount bitReader.Read(9); // m_activeSubgroupIndex bitReader.Read(32); // m_unitTagsChecksum bitReader.Read(32); // m_subgroupIndicesChecksum bitReader.Read(32); // m_subgroupsChecksum break; case GameEventType.CResourceTradeEvent: bitReader.Read(4); // m_recipientId bitReader.Read(32); // m_resources, should be offset -2147483648 bitReader.Read(32); // m_resources, should be offset -2147483648 bitReader.Read(32); // m_resources, should be offset -2147483648 break; case GameEventType.CTriggerChatMessageEvent: gameEvent.data = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(10) }; break; case GameEventType.CTriggerPingEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, new TrackerEventStructure { unsignedInt = bitReader.Read(1) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }; break; case GameEventType.CUnitClickEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // m_unitTag break; case GameEventType.CTriggerSkippedEvent: break; case GameEventType.CTriggerSoundLengthQueryEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(32) }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } }; break; case GameEventType.CTriggerSoundOffsetEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; case GameEventType.CTriggerTransmissionOffsetEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { unsignedInt = bitReader.Read(32) } } }; break; case GameEventType.CTriggerTransmissionCompleteEvent: gameEvent.data = new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }; break; case GameEventType.CCameraUpdateEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[6] }; if (bitReader.ReadBoolean()) // m_target, x/y gameEvent.data.array[0] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(16) }, new TrackerEventStructure { unsignedInt = bitReader.Read(16) } } }; if (bitReader.ReadBoolean()) // m_distance gameEvent.data.array[1] = new TrackerEventStructure { unsignedInt = bitReader.Read(16) }; if (bitReader.ReadBoolean()) // m_pitch gameEvent.data.array[2] = new TrackerEventStructure { unsignedInt = bitReader.Read(16) }; if (bitReader.ReadBoolean()) // m_yaw gameEvent.data.array[3] = new TrackerEventStructure { unsignedInt = bitReader.Read(16) }; if (bitReader.ReadBoolean()) // m_reason gameEvent.data.array[4] = new TrackerEventStructure { vInt = bitReader.Read(8) - 128 }; // m_follow gameEvent.data.array[5] = new TrackerEventStructure { unsignedInt = bitReader.Read(1) }; break; case GameEventType.CTriggerPlanetMissionLaunchedEvent: bitReader.Read(32); // m_difficultyLevel, offset -2147483648 break; case GameEventType.CTriggerDialogControlEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) /* Actually signed - not handled correctly */ }, new TrackerEventStructure { vInt = bitReader.Read(32) /* Actually signed - not handled correctly */ }, new TrackerEventStructure() } }; switch (bitReader.Read(3)) { case 0: // None break; case 1: // Checked gameEvent.data.array[2].unsignedInt = bitReader.Read(1); break; case 2: // ValueChanged gameEvent.data.array[2].unsignedInt = bitReader.Read(32); break; case 3: // SelectionChanged gameEvent.data.array[2].vInt = bitReader.Read(32); /* Actually signed - not handled correctly */ break; case 4: // TextChanged gameEvent.data.array[2].DataType = 2; gameEvent.data.array[2].blob = bitReader.ReadBlobPrecededWithLength(11); break; case 5: // MouseButton gameEvent.data.array[2].unsignedInt = bitReader.Read(32); break; } break; case GameEventType.CTriggerSoundLengthSyncEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[2] }; gameEvent.data.array[0] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(7)] }; for (var i = 0; i < gameEvent.data.array[0].array.Length; i++) gameEvent.data.array[0].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; gameEvent.data.array[1] = new TrackerEventStructure { array = new TrackerEventStructure[bitReader.Read(7)] }; for (var i = 0; i < gameEvent.data.array[1].array.Length; i++) gameEvent.data.array[1].array[i] = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; case GameEventType.CTriggerConversationSkippedEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(1) }; break; case GameEventType.CTriggerMouseClickedEvent: bitReader.Read(32); // m_button bitReader.ReadBoolean(); // m_down bitReader.Read(11); // m_posUI X bitReader.Read(11); // m_posUI Y bitReader.Read(20); // m_posWorld X bitReader.Read(20); // m_posWorld Y bitReader.Read(32); // m_posWorld Z (Offset -2147483648) bitReader.Read(8); // m_flags (-128) break; case GameEventType.CTriggerMouseMovedEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(11) }, new TrackerEventStructure { unsignedInt = bitReader.Read(11) }, new TrackerEventStructure { array = new[] { new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 } } }, new TrackerEventStructure { vInt = bitReader.Read(8) - 128 } } }; break; case GameEventType.CTriggerHotkeyPressedEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // May be missing an offset value break; case GameEventType.CTriggerTargetModeUpdateEvent: bitReader.Read(16); // m_abilLink bitReader.Read(5); // m_abilCmdIndex bitReader.Read(8); // m_state (-128) break; case GameEventType.CTriggerSoundtrackDoneEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; break; case GameEventType.CTriggerKeyPressedEvent: gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(8) - 128 }, new TrackerEventStructure { vInt = bitReader.Read(8) - 128 } } }; break; case GameEventType.CTriggerCutsceneBookmarkFiredEvent: // m_cutsceneId, m_bookmarkName gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }, new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) } } }; break; case GameEventType.CTriggerCutsceneEndSceneFiredEvent: // m_cutsceneId gameEvent.data = new TrackerEventStructure { vInt = bitReader.Read(32) - 2147483648 }; break; case GameEventType.CGameUserLeaveEvent: break; case GameEventType.CGameUserJoinEvent: gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[5] }; gameEvent.data.array[0] = new TrackerEventStructure { unsignedInt = bitReader.Read(2) }; gameEvent.data.array[1] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(8) }; if (bitReader.ReadBoolean()) gameEvent.data.array[2] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(7) }; if (bitReader.ReadBoolean()) gameEvent.data.array[3] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBlobPrecededWithLength(8) }; if (bitReader.ReadBoolean()) gameEvent.data.array[4] = new TrackerEventStructure { DataType = 2, blob = bitReader.ReadBytes(40) }; break; case GameEventType.CCommandManagerStateEvent: gameEvent.data = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(2) }; // m_state if (replayBuild >= 33684) if (bitReader.ReadBoolean()) // m_sequence gameEvent.data.array = new[] { new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(8) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(8) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(16) } }; break; case GameEventType.CCmdUpdateTargetPointEvent: if (replayBuild >= 40336 && bitReader.ReadBoolean()) bitReader.Read(32); gameEvent.data = new TrackerEventStructure { array = new[] { new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(32) - 2147483648 } } }; break; case GameEventType.CCmdUpdateTargetUnitEvent: if (replayBuild >= 40336 && bitReader.ReadBoolean()) bitReader.Read(32); gameEvent.data = new TrackerEventStructure { array = new TrackerEventStructure[7] }; gameEvent.data.array[0] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(16) }; // m_targetUnitFlags gameEvent.data.array[1] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(8) }; // m_timer gameEvent.data.array[2] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(32) }; // m_tag gameEvent.data.array[3] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(16) }; // m_snapshotUnitLink if (bitReader.ReadBoolean()) gameEvent.data.array[4] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(4) }; // m_snapshotControlPlayerId if (bitReader.ReadBoolean()) gameEvent.data.array[5] = new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(4) }; // m_snapshotUpkeepPlayerId gameEvent.data.array[6] = new TrackerEventStructure { array = new[] { new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 7, unsignedInt = bitReader.Read(20) }, new TrackerEventStructure { DataType = 9, vInt = bitReader.Read(32) - 2147483648 } } }; // m_snapshotPoint (x, y, z) break; case GameEventType.CHeroTalentSelectedEvent: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(32) }; // m_index break; case GameEventType.CHeroTalentTreeSelectionPanelToggled: gameEvent.data = new TrackerEventStructure { unsignedInt = bitReader.Read(1) }; // m_shown break; default: throw new NotImplementedException(); } bitReader.AlignToByte(); gameEvents.Add(gameEvent); } } return gameEvents; // Uncomment this to write out all replay.game.events to individual text files in the 'C:\HOTSLogs\' folder /* var eventGroups = replay.GameEvents.GroupBy(i => i.eventType).Select(i => new { EventType = i.Key, EventCount = i.Count(), Events = i.OrderBy(j => j.TimeSpan) }); string eventGroupData = ""; foreach (var eventGroup in eventGroups) { foreach (var eventData in eventGroup.Events) eventGroupData += eventData.TimeSpan + ": " + eventData.player + ": " + eventData + "\r\n"; File.WriteAllText(@"C:\HOTSLogs\" + (int)eventGroup.EventType + " " + eventGroup.EventType + @".txt", eventGroupData); eventGroupData = ""; } */ }
/// <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 Streams.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.MessageEventType = (MessageEventType)bitReader.Read(4); switch (message.MessageEventType) { case MessageEventType.SChatMessage: { ChatMessage chatMessage = new ChatMessage(); chatMessage.MessageTarget = (MessageTarget)bitReader.Read(3); // m_recipient (the target) chatMessage.Message = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(11)); // m_string message.ChatMessage = chatMessage; replay.Messages.Add(message); break; } case MessageEventType.SPingMessage: { PingMessage pingMessage = new PingMessage(); pingMessage.MessageTarget = (MessageTarget)bitReader.Read(3); // m_recipient (the target) pingMessage.XCoordinate = bitReader.ReadInt32() - (-2147483648); // m_point x pingMessage.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: { PlayerAnnounceMessage announceMessage = new PlayerAnnounceMessage(); announceMessage.AnnouncementType = (AnnouncementType)bitReader.Read(2); switch (announceMessage.AnnouncementType) { case AnnouncementType.None: { break; } case AnnouncementType.Ability: { AbilityAnnouncment ability = new AbilityAnnouncment(); ability.AbilityLink = bitReader.ReadInt16(); // m_abilLink ability.AbilityIndex = (int)bitReader.Read(5); // m_abilCmdIndex ability.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: { VitalAnnouncment vital = new VitalAnnouncment(); vital.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(); } } }