public PlayerJoinEvent(BitReader bitReader, Replay replay, int playerIndex) { this.EventType = GameEventType.Inactive; // This should probably be a series of {shl; or} on .Read(1) // to make it version-independent if (replay.ReplayBuild < 22612) { this.JoinFlags = (int)bitReader.Read(4); } else { this.JoinFlags = (int)bitReader.Read(12); // unknown } // Initialize player if not exists (true for observers) Player player = replay.GetPlayerById(playerIndex); if (player == null) { var p = new Player { PlayerType = PlayerType.Spectator }; replay.ClientList[playerIndex] = player = p; } // Initialize wireframe player.Wireframe = new List<Unit>(); player.WireframeSubgroup = 0; // Initialize control groups player.Hotkeys = new List<Unit>[10]; }
/// <summary> Parses the replay.details file, applying it to a Replay object. </summary> /// <param name="replay"> The replay object to apply the parsed information to. </param> /// <param name="stream"> The stream containing the replay.details file. </param> public static void Parse(Replay replay, Stream stream) { using (var reader = new BinaryReader(stream)) { reader.ReadBytes(6); var playerCount = reader.ReadByte() >> 1; // Parsing Player Info var players = new Player[playerCount]; for (int i = 0; i < playerCount; i++) { players[i] = PlayerDetails.Parse(reader); } replay.Players = players; var mapNameLength = KeyValueStruct.Parse(reader).Value; var mapBytes = reader.ReadBytes(mapNameLength); replay.Map = Encoding.UTF8.GetString(mapBytes); var stringLength = KeyValueStruct.Parse(reader).Value; // This is typically an empty string, no need to decode. var unknownString = reader.ReadBytes(stringLength); reader.ReadBytes(3); var mapPreviewNameLength = KeyValueStruct.Parse(reader).Value; var mapPreviewNameBytes = reader.ReadBytes(mapPreviewNameLength); // What have I learned: // While I can get the name of the map preview file, apparently MPQLib.dll will not // Support exporting the file since its not in the file list. I tried, and it threw an error... // Maybe my buffer wasn't big enough, but it was some temporary file error, so I don't think so. var mapPreviewName = Encoding.UTF8.GetString(mapPreviewNameBytes); reader.ReadBytes(3); var saveTime = KeyValueLongStruct.Parse(reader).Value; var saveTimeZone = KeyValueLongStruct.Parse(reader).Value; var time = DateTime.FromFileTime(saveTime); // Subtract the timezone to get the appropriate UTC time. time = time.Subtract(new TimeSpan(saveTimeZone)); // We create a new timestamp so we can properly set this as UTC time. replay.Timestamp = new DateTime(time.Ticks, DateTimeKind.Utc); reader.Close(); } }
public PlayerLeftEvent(Player player, Timestamp time) : base(player, time) { this.EventType = GameEventType.Inactive; }
/// <summary> Parses the replay.details file, applying it to a Replay object. </summary> /// <param name="replay"> The replay object to apply the parsed information to. </param> /// <param name="stream"> The stream containing the replay.details file. </param> public static void Parse(Replay replay, Stream stream) { using (var reader = new BinaryReader(stream)) { reader.ReadBytes(6); var playerCount = reader.ReadByte() >> 1; var players = new Player[playerCount]; // Parsing Player Info for (int i = 0; i < playerCount; i++) { var parsedPlayer = PlayerDetails.Parse(reader); // The references between both of these classes are the same on purpose. // We want updates to one to propogate to the other. players[i] = parsedPlayer; replay.ClientList[i + 1] = parsedPlayer; } replay.Players = players; var mapNameLength = KeyValueStruct.Parse(reader).Value; var mapBytes = reader.ReadBytes(mapNameLength); replay.Map = Encoding.UTF8.GetString(mapBytes); var stringLength = KeyValueStruct.Parse(reader).Value; // This is typically an empty string, no need to decode. var unknownString = reader.ReadBytes(stringLength); reader.ReadBytes(3); var mapPreviewNameLength = KeyValueStruct.Parse(reader).Value; var mapPreviewNameBytes = reader.ReadBytes(mapPreviewNameLength); replay.MapPreviewName = Encoding.UTF8.GetString(mapPreviewNameBytes); reader.ReadBytes(3); var saveTime = KeyValueLongStruct.Parse(reader).Value; var saveTimeZone = KeyValueLongStruct.Parse(reader).Value; var time = DateTime.FromFileTime(saveTime); // Subtract the timezone to get the appropriate UTC time. time = time.Subtract(new TimeSpan(saveTimeZone)); // We create a new timestamp so we can properly set this as UTC time. replay.Timestamp = new DateTime(time.Ticks, DateTimeKind.Utc); // don't know what the next 14 bytes are for, so we skip them reader.ReadBytes(14); var resources = new List<ResourceInfo>(); reader.ReadBytes(2); // there are 2 bytes before each "s2ma" string var s2ma = Encoding.UTF8.GetString(reader.ReadBytes(4)); while(s2ma == "s2ma") { reader.ReadBytes(2); // 0x00, 0x00 resources.Add(new ResourceInfo { Gateway = Encoding.UTF8.GetString(reader.ReadBytes(2)), Hash = reader.ReadBytes(32), }); reader.ReadBytes(2); s2ma = Encoding.UTF8.GetString(reader.ReadBytes(4)); } var map = resources.Last(); replay.MapGateway = map.Gateway; replay.MapHash = map.Hash; reader.Close(); } }
public GameEventBase(Player player, Timestamp time) { this.Player = player; this.Time = time; }
/// <summary> Initializes a new instance of the <see cref = "Replay" /> class. </summary> internal Replay() { GameUnits = new Dictionary<int, Unit>(); ClientList = new Player[0x10]; }
private static void TrackLeavingPlayer(List<Player> remainingPlayers, Player player) { if (remainingPlayers.Contains(player)) { remainingPlayers.Remove(player); } }
public AbilityEvent(BitReader bitReader, Replay replay, Player player, AbilityData abilityData, UnitData unitData) { uint flags; // 1.3.3 patch notes: // - Fixed an issue where the APM statistic could be artificially increased. // This adds the "failed" flag, which is triggered usually by holding down a // hotkey, leading to key repeat spamming the event throughout a single tick. if (replay.ReplayBuild < 18574) // < 1.3.3 { flags = bitReader.Read(17); } else if (replay.ReplayBuild < 22612) // < 1.5.0 { flags = bitReader.Read(18); } else { flags = bitReader.Read(20); } Queued = (flags & 2) != 0; RightClick = (flags & 8) != 0; WireframeClick = (flags & 0x20) != 0; ToggleAbility = (flags & 0x40) != 0; EnableAutoCast = (flags & 0x80) != 0; AbilityUsed = (flags & 0x100) != 0; WireframeUnload = (flags & 0x200) != 0; WireframeCancel = (flags & 0x400) != 0; MinimapClick = (flags & 0x10000) != 0; AbilityFailed = (flags & 0x20000) != 0; // flags & 0xf815 -> Debug for unknown flags // Never found any across all test data. DefaultAbility = (bitReader.Read(1) == 0); DefaultActor = true; if (!DefaultAbility) { AbilityType = abilityData.GetAbilityType( (int)bitReader.Read(16), (int)bitReader.Read(5)); DefaultActor = (bitReader.Read(1) == 0); if (!DefaultActor) { // I'm thinking this would be an array type... but I can't // find anything that causes this bit to be set. throw new InvalidOperationException("Unsupported: non-default actor"); } } if (DefaultActor) { // Deep copy the current wireframe as the actor list // ----- // If a user wants to deal with subgroups to get a more // concise actor list, the data is all here. We're not // going to bother, though, because there are several // exceptions to account for in determining event actors. Actors = new List<Unit>(player.Wireframe.Count); foreach (var unit in player.Wireframe) { Actors.Add(new Unit(unit)); } } var targetType = bitReader.Read(2); if (targetType == 1) // Location target { var targetX = bitReader.Read(20); var targetY = bitReader.Read(20); var targetZ = bitReader.Read(32); TargetLocation = Location.FromEventFormat(targetX, targetY, targetZ); } else if (targetType == 2) // Unit + Location target { TargetFlags = (int)bitReader.Read(8); WireframeIndex = (int)bitReader.Read(8); var unitId = (int)bitReader.Read(32); var unit = replay.GetUnitById(unitId); var unitTypeId = (int)bitReader.Read(16); if (unit == null) { var unitType = unitData.GetUnitType(unitTypeId); unit = new Unit(unitId, unitType); unit.typeId = unitTypeId; replay.GameUnits.Add(unitId, unit); } TargetUnit = unit; var targetHasPlayer = bitReader.Read(1) == 1; if (targetHasPlayer) { TargetPlayer = (int)bitReader.Read(4); } // 1.4.0 -- Don't really know what this was meant to fix if (replay.ReplayBuild >= 19595) { var targetHasTeam = bitReader.Read(1) == 1; if (targetHasTeam) { TargetTeam = (int)bitReader.Read(4); } } var targetX = bitReader.Read(20); var targetY = bitReader.Read(20); var targetZ = bitReader.Read(32); TargetLocation = Location.FromEventFormat(targetX, targetY, targetZ); } else if (targetType == 3) // Unit target { var id = bitReader.Read(32); // Again, if the user wants to determine exactly which // queue item is canceled in the case of a queue cancel // event (the most common case of this target specifier's // occurence), they can; however, it requires an additional // data structure that I don't want to bother with; however, // all the underlying data is available in the events list. TargetId = id; } var lastBit = bitReader.Read(1); // Should be 0; if not, misalignment is likely if (!AbilityFailed) { if (RightClick) { this.EventType = GameEventType.RightClick; } else { this.EventType = EventData.GetInstance().GetEventType(this.AbilityType); } } else { this.EventType = GameEventType.Inactive; } }
/// <summary> Update the wireframe for a player using the data in the event </summary> void UpdateWireframe(Player player) { List<Unit> affectedWireframe; if (WireframeIndex == 10) { affectedWireframe = player.Wireframe; } else { affectedWireframe = player.Hotkeys[WireframeIndex]; } if (!ClearSelection) { // Remove removed units, add added units, sort by unit id (top 14 bits) foreach (Unit unit in RemovedUnits) { affectedWireframe.Remove(unit); } foreach (Unit unit in AddedUnits) { affectedWireframe.Add(unit); } affectedWireframe.Sort((m, n) => m.Id - n.Id); } else { // Copy added units only to wireframe if (WireframeIndex == 10) { player.Wireframe = new List<Unit>(AddedUnits); player.Wireframe.Sort((m, n) => m.Id - n.Id); } else { player.Hotkeys[WireframeIndex] = new List<Unit>(AddedUnits); player.Hotkeys[WireframeIndex].Sort((m, n) => m.Id - n.Id); } } }
public SelectionEvent(BitReader bitReader, Replay replay, Player player, UnitData data) { int wireframeLength = 8; if (replay.ReplayBuild >= 22612) { wireframeLength = 9; // Maximum selection size has been increased to 500, up from 255. } // Parse select event and update player wireframe accordingly WireframeIndex = (int)bitReader.Read(4); player.WireframeSubgroup = SubgroupIndex = (int)bitReader.Read(wireframeLength); if (WireframeIndex == 10) { this.EventType = GameEventType.Selection; } else // This is a control group update, likely from a CAbilMorph { this.EventType = GameEventType.Inactive; } List<Unit> affectedWireframe; if (WireframeIndex == 10) { affectedWireframe = player.Wireframe; } else { affectedWireframe = player.Hotkeys[WireframeIndex]; } RemovedUnits = new List<Unit>(); var updateFlags = (int)bitReader.Read(2); ClearSelection = false; if (updateFlags == 1) { var numBits = (int)bitReader.Read(wireframeLength); var unitsRemoved = new bool[numBits]; var wireframeIndex = 0; while (numBits >= 8) { numBits -= 8; var flags = bitReader.Read(8); for (int i = 0; i < 8; i++) { unitsRemoved[wireframeIndex + i] = (flags & (1 << i)) != 0; } wireframeIndex += 8; } if (numBits != 0) { var flags = bitReader.Read(numBits); for (int i = 0; i < numBits; i++) { unitsRemoved[wireframeIndex + i] = (flags & (1 << i)) != 0; } wireframeIndex += numBits; } for (int i = 0; i < wireframeIndex; i++) { if (unitsRemoved[i]) { RemovedUnits.Add(affectedWireframe[i]); } } } else if (updateFlags == 2) { var indexArrayLength = (int)bitReader.Read(wireframeLength); if (indexArrayLength > 0) { for (int i = 0; i < indexArrayLength; i++) { RemovedUnits.Add(affectedWireframe[(int)bitReader.Read(wireframeLength)]); } } } else if (updateFlags == 3) { var indexArrayLength = (int)bitReader.Read(wireframeLength); if (indexArrayLength > 0) { AddedUnits = new List<Unit>(indexArrayLength); for (int i = 0; i < indexArrayLength; i++) { AddedUnits.Add(affectedWireframe[(int)bitReader.Read(wireframeLength)]); } } ClearSelection = true; } // Build removed unit types RemovedUnitTypes = new Dictionary<UnitType, int>(); foreach (var unit in RemovedUnits) { if (!RemovedUnitTypes.ContainsKey(unit.Type)) { RemovedUnitTypes.Add(unit.Type, 1); } else { RemovedUnitTypes[unit.Type]++; } } HandleUnitArrays(bitReader, replay, data); // Now, update the player wireframe. UpdateWireframe(player); // Check for Morph update if (AddedUnits.SequenceEqual(RemovedUnits)) { this.EventType = GameEventType.Inactive; } }
public HotkeyEvent(BitReader bitReader, Replay replay, Player player) { int wireframeLength = 8; if (replay.ReplayBuild >= 22612) { wireframeLength = 9; // Maximum selection size has been increased to 500, up from 255. } this.EventType = GameEventType.Selection; ControlGroup = (int)bitReader.Read(4); // throws ActionType = (HotkeyActionType)(int)bitReader.Read(2); var updateType = (int)bitReader.Read(2); // This is an internal update that is somewhat asynchronous to // the main wireframe. var unitsRemovedList = new List<Unit>(); if (updateType == 1) // Remove by flags { var numBits = (int)bitReader.Read(wireframeLength); var unitsRemoved = new bool[numBits]; var wireframeIndex = 0; while (numBits >= 8) { numBits -= 8; var flags = bitReader.Read(8); for (int i = 0; i < 8; i++) { unitsRemoved[wireframeIndex + i] = (flags & (1 << i)) != 0; } wireframeIndex += 8; } if (numBits != 0) { var flags = bitReader.Read(numBits); for (int i = 0; i < numBits; i++) { unitsRemoved[wireframeIndex + i] = (flags & (1 << i)) != 0; } wireframeIndex += numBits; } for (int i = 0; i < wireframeIndex; i++) { if (unitsRemoved[i]) { unitsRemovedList.Add(player.Hotkeys[ControlGroup][i]); } } } else if (updateType == 2) { var numIndices = (int)bitReader.Read(wireframeLength); for (int i = 0; i < numIndices; i++) { unitsRemovedList.Add(player.Hotkeys[ControlGroup][(int)bitReader.Read(wireframeLength)]); } } else if (updateType == 3) // Replace control group with portion of control group { // This happens fairly rarely, so I'll just invert the output unitsRemovedList = new List<Unit>(player.Hotkeys[ControlGroup]); var numIndices = (int)bitReader.Read(wireframeLength); for (int i = 0; i < numIndices; i++) { unitsRemovedList.Remove(player.Hotkeys[ControlGroup][(int)bitReader.Read(wireframeLength)]); } } if (ActionType == HotkeyActionType.AddToControlGroup) { var oldControlgroup = player.Hotkeys[ControlGroup]; List<Unit> newControlgroup; if (oldControlgroup != null) { newControlgroup = new List<Unit>(player.Wireframe.Count + oldControlgroup.Count); foreach (Unit unit in oldControlgroup) { newControlgroup.Add(unit); } } else { newControlgroup = new List<Unit>(player.Wireframe.Count); } foreach (Unit unit in player.Wireframe) { if (oldControlgroup == null || !oldControlgroup.Contains(unit)) { newControlgroup.Add(unit); } } newControlgroup.Sort((m, n) => m.Id - n.Id); player.Hotkeys[ControlGroup] = newControlgroup; } else if (ActionType == HotkeyActionType.SelectControlGroup) { player.Wireframe = new List<Unit>(player.Hotkeys[ControlGroup]); // Only see these two together because of the nature of it foreach (Unit unit in unitsRemovedList) { player.Wireframe.Remove(unit); } } else if (ActionType == HotkeyActionType.SetControlGroup) { player.Hotkeys[ControlGroup] = new List<Unit>(player.Wireframe); } // Copy ref list to property. Idk if this is a great idea, but it's likely // never more than 30 ish dwords per event? Can't be more than another meg // or three per replay. i.e. Can't be more than lolJava. ControlGroupUnits = new List<Unit>(player.Hotkeys[ControlGroup]); }
public HotkeyEvent(Player player, Timestamp time) : base(player, time) { this.EventType = GameEventType.Other; }