private static Sc2Replay ParseReplay(IMpqArchive archive) { if (archive == null) throw new ArgumentNullException("archive"); var result = new Sc2Replay(); ParseUserDataHeader(archive, result); dynamic details = archive.ReadSerializedData("replay.details", false); result.MapName = Encoding.UTF8.GetString(details[1]); // parse the date, subtract the time zone to get UTC time var timeZoneOffset = new TimeSpan((long)details[6]); var date = DateTime.FromFileTime((long)details[5]); var adjustedDate = date.Subtract(timeZoneOffset); result.DatePlayed = DateTime.SpecifyKind(adjustedDate, DateTimeKind.Utc); result.Players = ParsePlayers(details); ParseMapInformation(details, result); // hack: we don't actually parse the replay gateway since I // haven't been able to find it in the replay file yet. // for now, we just assume that it's the same as the gateway // of the map file resource. result.Gateway = result.MapGateway; result.Attributes = ParseAttributes(archive); ApplyAttributes(result); return result; }
private static void ApplyAttributes(Sc2Replay replay) { foreach (var attribute in replay.Attributes) { var stringValue = attribute.StringValue; Sc2ReplayPlayer player = null; // player indices are 1-based, and 0 means that the attribute doesn't relate to a player if (attribute.PlayerIndex > 0 && attribute.PlayerIndex <= replay.Players.Length) player = replay.Players[attribute.PlayerIndex - 1]; switch (attribute.Type) { case Sc2ReplayAttributeType.PlayerType: if (stringValue.Equals("comp", StringComparison.OrdinalIgnoreCase)) player.Type = Sc2ReplayPlayerType.Computer; else if (stringValue.Equals("humn", StringComparison.OrdinalIgnoreCase)) player.Type = Sc2ReplayPlayerType.Human; break; case Sc2ReplayAttributeType.TeamSize: replay.TeamSize = stringValue.Trim('\0'); break; case Sc2ReplayAttributeType.PlayerTeam_1v1: case Sc2ReplayAttributeType.PlayerTeam_2v2: case Sc2ReplayAttributeType.PlayerTeam_3v3: case Sc2ReplayAttributeType.PlayerTeam_4v4: case Sc2ReplayAttributeType.PlayerTeam_FFA: var team = stringValue.Trim('\0', 'T'); player.Team = Convert.ToInt32(team); break; case Sc2ReplayAttributeType.PlayerRace: player.SelectedRace = RaceParser.Parse(stringValue); break; case Sc2ReplayAttributeType.GameType: if (stringValue.Equals("priv", StringComparison.OrdinalIgnoreCase)) replay.GameType = Sc2GameType.Custom; else if (stringValue.Trim('\0').Equals("amm", StringComparison.OrdinalIgnoreCase)) replay.GameType = Sc2GameType.Matchmaking; break; } } }
private static void ParseMapInformation(dynamic details, Sc2Replay result) { var resources = (object[]) details[10]; var mapData = (byte[]) resources.Last(); if (mapData[0] != 's' || mapData[1] != '2' || mapData[2] != 'm' || mapData[3] != 'a' || mapData[4] != 0 || mapData[5] != 0) throw new Sc2ReplayParsingException("Replay file format invalid (Error parsing map gateway and hash)"); result.MapGateway = Encoding.UTF8.GetString(mapData, 6, 2); var mapHashBytes = new byte[32]; Array.Copy(mapData, 8, mapHashBytes, 0, 32); result.MapHash = string.Join("", mapHashBytes.Select(b => b.ToString("X2"))).ToLowerInvariant(); }
private static void ParseUserDataHeader(IMpqArchive replay, Sc2Replay result) { dynamic data = Starcraft2SerializedData.Deserialize(replay.UserDataHeader.UserData, true); var starcraft2 = (string)data[0]; if (!starcraft2.StartsWith("StarCraft II replay")) // todo: this is kind of a hack, for some reason there are extra funky characters after "replay". Why? throw new InvalidOperationException( String.Format( "This does not appear to be a SC2 replay file. (Unexpected user header string: '{0}')", starcraft2)); var buildAndVersion = (IDictionary<long, object>)data[1]; result.Version = new[] { (long)buildAndVersion[0], (long)buildAndVersion[1], (long)buildAndVersion[2], (long)buildAndVersion[3] }; result.Build = new[] { (long)buildAndVersion[4], (long)buildAndVersion[5] }; ValidateBuild(result.Build); var lengthInTicks = (long)data[3]; result.Length = TimeSpan.FromSeconds(lengthInTicks / 16d); }