コード例 #1
0
        /// <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="buffer"> The buffer containing the replay.details file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                {
                    var replayDetailsStructure = new TrackerEventStructure(reader);
                    var playerId = -1;
                    replay.Players = replayDetailsStructure.dictionary[0].optionalData.array.Select(i => new Player
                    {   
                        PlayerId = ++playerId,
                        Name = i.dictionary[0].blobText,
                        BattleNetRegionId = (int)i.dictionary[1].dictionary[0].vInt.Value,
                        BattleNetSubId = (int)i.dictionary[1].dictionary[2].vInt.Value,
                        BattleNetId = (int)i.dictionary[1].dictionary[4].vInt.Value,
                        // [2] = Race (SC2 Remnant, Always Empty String in Heroes of the Storm)
                        Color = i.dictionary[3].dictionary.Keys.OrderBy(j => j).Select(j => (int)i.dictionary[3].dictionary[j].vInt.Value).ToArray(),
                        // [4] = Player Type (2 = Human, 3 = Computer (Practice, Try Me, or Coop)) - This is more accurately gathered in replay.attributes.events
                        Team = (int)i.dictionary[5].vInt.Value,
                        Handicap = (int)i.dictionary[6].vInt.Value,
                        // [7] = VInt, Default 0
                        IsWinner = i.dictionary[8].vInt.Value == 1,
                        // [9] = Sometimes player index in ClientList array; usually 0-9, but can be higher if there are observers. I don't fully understand this, as this was incorrect in at least one Custom game, where this said ClientList[8] was null
                        Character = i.dictionary[10].blobText
                    }).ToArray();

                    if (replay.Players.Length != 10 || replay.Players.Count(i => i.IsWinner) != 5)
                        // Try Me Mode, or something strange
                        return;

                    replay.Map = replayDetailsStructure.dictionary[1].blobText;
                    // [2] - This is typically an empty string, no need to decode.
                    // [3] - Blob: "Minimap.tga" or "CustomMiniMap.tga"
                    // [4] - Uint, Default 1

                    // [5] - Utc Timestamp
                    replay.Timestamp = DateTime.FromFileTimeUtc(replayDetailsStructure.dictionary[5].vInt.Value);

                    // There was a bug during the below builds where timestamps were buggy for the Mac build of Heroes of the Storm
                    // The replay, as well as viewing these replays in the game client, showed years such as 1970, 1999, etc
                    // I couldn't find a way to get the correct timestamp, so I am just estimating based on when these builds were live
                    if (replay.ReplayBuild == 34053 && replay.Timestamp < new DateTime(2015, 2, 8))
                        replay.Timestamp = new DateTime(2015, 2, 13);
                    else if (replay.ReplayBuild == 34190 && replay.Timestamp < new DateTime(2015, 2, 15))
                        replay.Timestamp = new DateTime(2015, 2, 20);

                    // [6] - Windows replays, this is Utc offset.  Mac replays, this is actually the entire Local Timestamp
                    // var potentialUtcOffset = new TimeSpan(replayDetailsStructure.dictionary[6].vInt.Value);

                    // [7] - Blob, Empty String
                    // [8] - Blob, Empty String
                    // [9] - Blob, Empty String
                    // [10] - Optional, Array: 0 - Blob, "s2ma"
                    // [11] - UInt, Default 0
                    // [12] - VInt, Default 4
                    // [13] - VInt, Default 1 or 7
                    // [14] - Optional, Null
                    // [15] - VInt, Default 0
                    // [16] - Optional, UInt, Default 0
                }
        }
コード例 #2
0
        private static void ParseHeader(Replay replay, BinaryReader reader)
        {
            reader.ReadBytes(3);                          // 'Magic'
            reader.ReadByte();                            // Format
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Data Max Size
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Header Offset
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // User Data Header Size

            var headerStructure = new TrackerEventStructure(reader);

            // [0] = Blob, "Heroes of the Storm replay 11" - Strange backward arrow before 11 as well.  I don't think the '11' will change, as I believe it was also always '11' in Starcraft 2 replays.

            replay.ReplayVersion = string.Format("{0}.{1}.{2}.{3}", headerStructure.dictionary[1].dictionary[0].vInt.Value, headerStructure.dictionary[1].dictionary[1].vInt.Value, headerStructure.dictionary[1].dictionary[2].vInt.Value, headerStructure.dictionary[1].dictionary[3].vInt.Value);

            replay.ReplayBuild = (int)headerStructure.dictionary[1].dictionary[4].vInt.Value;

            if (replay.ReplayBuild >= 39951)
            {
                // 'm_dataBuildNum' may have always been incremented for these smaller 'hotfix' patches, but build 39951 is the first time I've noticed where a Hero's available talent selection has changed in one of these smaller patches
                // We probably want to use this as the most precise build number from now on
                replay.ReplayBuild = (int)headerStructure.dictionary[6].vInt.Value;
            }

            // [2] = VInt, Default 2 - m_type

            replay.Frames = (int)headerStructure.dictionary[3].vInt.Value; // m_elapsedGameLoops

            // [4] = VInt, Default 0 - m_useScaledTime
            // [5] = Depending on replay build, either Blob with gibberish, or array of 16 bytes (basically a Blob), also with gibberish.  Of ~770 pre-wipe replays, there were only 11 distinct blobs, so this is likely a map version hash or something - 'm_ngdpRootKey'
            // [6] = Replay Build (Usually the same as what is in [1], but can be incremented by itself for smaller 'hotfix' patches) - m_dataBuildNum
            // [7] = m_fixedFileHash
        }
コード例 #3
0
        /// <summary> Parses the replay.tracker.events file, applying it to a Replay object. </summary>
        /// <param name="replay"> The replay object to apply the parsed information to. </param>
        /// <param name="buffer"> The buffer containing the replay.tracker.events file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            replay.TrackerEvents = new List <TrackerEvent>();

            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                    while (stream.Position < stream.Length)
                    {
                        var intro = reader.ReadBytes(3); // Always 03 00 09 (Edit: Middle digit seems to have at least two possible values)
                        if (intro[0] != 3 || /* intro[1] != 0 || */ intro[2] != 9)
                        {
                            throw new Exception("Unexpected data in tracker event");
                        }

                        var trackerEvent = new TrackerEvent();
                        trackerEvent.FramesSinceLastEvent = (int)TrackerEventStructure.read_vint(reader);

                        intro = reader.ReadBytes(1); // Always 09
                        if (intro[0] != 9)
                        {
                            throw new Exception("Unexpected data in tracker event");
                        }

                        trackerEvent.TrackerEventType = (TrackerEventType)TrackerEventStructure.read_vint(reader);
                        trackerEvent.Data             = new TrackerEventStructure(reader);
                        replay.TrackerEvents.Add(trackerEvent);
                    }

            replay.Frames       = replay.TrackerEvents.Sum(i => i.FramesSinceLastEvent);
            replay.ReplayLength = new TimeSpan(0, 0, (int)(replay.Frames / 16.0));

            /* var trackerEventGroupBy = replay.TrackerEvents.GroupBy(i => i.TrackerEventType).OrderBy(i => i.Key);
             * Console.WriteLine(trackerEventGroupBy.Count()); */
        }
コード例 #4
0
        private static void ParseHeader(Replay replay, BinaryReader reader)
        {
            reader.ReadBytes(3); // 'Magic'
            reader.ReadByte(); // Format
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Data Max Size
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Header Offset
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // User Data Header Size

            var headerStructure = new TrackerEventStructure(reader);

            // [0] = Blob, "Heroes of the Storm replay 11" - Strange backward arrow before 11 as well.  I don't think the '11' will change, as I believe it was also always '11' in Starcraft 2 replays.

            replay.ReplayVersion = string.Format("{0}.{1}.{2}.{3}", headerStructure.dictionary[1].dictionary[0].vInt.Value, headerStructure.dictionary[1].dictionary[1].vInt.Value, headerStructure.dictionary[1].dictionary[2].vInt.Value, headerStructure.dictionary[1].dictionary[3].vInt.Value);
            
            replay.ReplayBuild = (int)headerStructure.dictionary[1].dictionary[4].vInt.Value;

            if (replay.ReplayBuild >= 39951)
                // 'm_dataBuildNum' may have always been incremented for these smaller 'hotfix' patches, but build 39951 is the first time I've noticed where a Hero's available talent selection has changed in one of these smaller patches
                // We probably want to use this as the most precise build number from now on
                replay.ReplayBuild = (int)headerStructure.dictionary[6].vInt.Value;

            // [2] = VInt, Default 2 - m_type

            replay.Frames = (int)headerStructure.dictionary[3].vInt.Value; // m_elapsedGameLoops

            // [4] = VInt, Default 0 - m_useScaledTime
            // [5] = Depending on replay build, either Blob with gibberish, or array of 16 bytes (basically a Blob), also with gibberish.  Of ~770 pre-wipe replays, there were only 11 distinct blobs, so this is likely a map version hash or something - 'm_ngdpRootKey'
            // [6] = Replay Build (Usually the same as what is in [1], but can be incremented by itself for smaller 'hotfix' patches) - m_dataBuildNum
            // [7] = m_fixedFileHash
        }
コード例 #5
0
        /// <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="buffer"> The buffer containing the replay.details file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                {
                    var replayDetailsStructure = new TrackerEventStructure(reader);
                    replay.Players = replayDetailsStructure.dictionary[0].optionalData.array.Select(i => new Player
                    {
                        Name = i.dictionary[0].blobText,
                        BattleNetRegionId = (int)i.dictionary[1].dictionary[0].vInt.Value,
                        BattleNetSubId = (int)i.dictionary[1].dictionary[2].vInt.Value,
                        BattleNetId = (int)i.dictionary[1].dictionary[4].vInt.Value,
                        // [2] = Race (SC2 Remnant, Always Empty String in Heroes of the Storm)
                        Color = i.dictionary[3].dictionary.Keys.OrderBy(j => j).Select(j => (int)i.dictionary[3].dictionary[j].vInt.Value).ToArray(),
                        // [4] = Player Type (2 = Human, 3 = Computer (Practice, Try Me, or Coop)) - This is more accurately gathered in replay.attributes.events
                        Team = (int)i.dictionary[5].vInt.Value,
                        Handicap = (int)i.dictionary[6].vInt.Value,
                        // [7] = VInt, Default 0
                        IsWinner = i.dictionary[8].vInt.Value == 1,
                        // [9] = Sometimes player index in ClientList array; usually 0-9, but can be higher if there are observers. I don't fully understand this, as this was incorrect in at least one Custom game, where this said ClientList[8] was null
                        Character = i.dictionary[10].blobText
                    }).ToArray();

                    if (replay.Players.Length != 10 || replay.Players.Count(i => i.IsWinner) != 5)
                        // Try Me Mode, or something strange
                        return;

                    replay.Map = replayDetailsStructure.dictionary[1].blobText;
                    // [2] - m_difficulty
                    // [3] - m_thumbnail - "Minimap.tga", "CustomMiniMap.tga", etc
                    // [4] - m_isBlizzardMap
                    
                    replay.Timestamp = DateTime.FromFileTimeUtc(replayDetailsStructure.dictionary[5].vInt.Value); // m_timeUTC

                    // There was a bug during the below builds where timestamps were buggy for the Mac build of Heroes of the Storm
                    // The replay, as well as viewing these replays in the game client, showed years such as 1970, 1999, etc
                    // I couldn't find a way to get the correct timestamp, so I am just estimating based on when these builds were live
                    if (replay.ReplayBuild == 34053 && replay.Timestamp < new DateTime(2015, 2, 8))
                        replay.Timestamp = new DateTime(2015, 2, 13);
                    else if (replay.ReplayBuild == 34190 && replay.Timestamp < new DateTime(2015, 2, 15))
                        replay.Timestamp = new DateTime(2015, 2, 20);

                    // [6] - m_timeLocalOffset - For Windows replays, this is Utc offset.  For Mac replays, this is actually the entire Local Timestamp
                    // [7] - m_description - Empty String
                    // [8] - m_imageFilePath - Empty String
                    // [9] - m_mapFileName - Empty String
                    // [10] - m_cacheHandles - "s2ma"
                    // [11] - m_miniSave - 0
                    // [12] - m_gameSpeed - 4
                    // [13] - m_defaultDifficulty - Usually 1 or 7
                    // [14] - m_modPaths - Null
                    // [15] - m_campaignIndex - 0
                    // [16] - m_restartAsTransitionMap - 0
                }
        }
コード例 #6
0
        /// <summary> Parses the replay.tracker.events file, applying it to a Replay object. </summary>
        /// <param name="replay"> The replay object to apply the parsed information to. </param>
        /// <param name="buffer"> The buffer containing the replay.tracker.events file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            replay.TrackerEvents = new List <TrackerEvent>();

            var currentFrameCount = 0;

            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                    while (stream.Position < stream.Length)
                    {
                        var intro = reader.ReadBytes(3); // Always 03 00 09 (Edit: Middle digit seems to have at least two possible values)
                        if (intro[0] != 3 || /* intro[1] != 0 || */ intro[2] != 9)
                        {
                            throw new Exception("Unexpected data in tracker event");
                        }

                        currentFrameCount += (int)TrackerEventStructure.read_vint(reader);

                        var trackerEvent = new TrackerEvent {
                            TimeSpan = new TimeSpan(0, 0, (int)(currentFrameCount / 16.0))
                        };

                        intro = reader.ReadBytes(1); // Always 09
                        if (intro[0] != 9)
                        {
                            throw new Exception("Unexpected data in tracker event");
                        }

                        trackerEvent.TrackerEventType = (TrackerEventType)TrackerEventStructure.read_vint(reader);
                        trackerEvent.Data             = new TrackerEventStructure(reader);
                        replay.TrackerEvents.Add(trackerEvent);
                    }

            replay.Frames       = currentFrameCount;
            replay.ReplayLength = replay.TrackerEvents.Last().TimeSpan;

            // Need to verify the player ID in the below code - particularly Custom Games where observers can take up spots in the client list
            replay.TimelineEvents.AddRange(replay.TrackerEvents.Where(i =>
                                                                      i.TrackerEventType == TrackerEventType.CreepColor &&
                                                                      i.Data.dictionary[1].blobText == "VehicleDragonUpgrade")
                                           .Select(i => new TimelineEvent {
                TimeSpan          = i.TimeSpan,
                TimelineEventType = TimelineEventType.MapMechanicDragonShireDragon,
                PlayerID          = (int)i.Data.dictionary[0].vInt.Value,
                Value             = 1
            }));

            /* var trackerEventGroupBy = replay.TrackerEvents.GroupBy(i => i.TrackerEventType).OrderBy(i => i.Key);
             * Console.WriteLine(trackerEventGroupBy.Count()); */
        }
コード例 #7
0
        /// <summary> Parses the replay.tracker.events file, applying it to a Replay object. </summary>
        /// <param name="replay"> The replay object to apply the parsed information to. </param>
        /// <param name="buffer"> The buffer containing the replay.tracker.events file. </param>
        /// <param name="parseUnitData"> Determines whether or not to parse unit data </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            replay.TrackerEvents = new List <TrackerEvent>();

            var currentFrameCount = 0;

            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                    while (stream.Position < stream.Length)
                    {
                        var intro = reader.ReadBytes(3); // Always 03 00 09 (Edit: Middle digit seems to have at least two possible values)
                        if (intro[0] != 3 || /* intro[1] != 0 || */ intro[2] != 9)
                        {
                            throw new Exception("Unexpected data in tracker event");
                        }

                        currentFrameCount += (int)TrackerEventStructure.read_vint(reader);

                        var trackerEvent = new TrackerEvent {
                            TimeSpan = new TimeSpan(0, 0, (int)(currentFrameCount / 16.0))
                        };

                        intro = reader.ReadBytes(1); // Always 09
                        if (intro[0] != 9)
                        {
                            throw new Exception("Unexpected data in tracker event");
                        }

                        trackerEvent.TrackerEventType = (TrackerEventType)TrackerEventStructure.read_vint(reader);
                        trackerEvent.Data             = new TrackerEventStructure(reader);
                        replay.TrackerEvents.Add(trackerEvent);
                    }

            replay.Frames       = currentFrameCount;
            replay.ReplayLength = replay.TrackerEvents.Last().TimeSpan;

            // Populate the client list using player indexes
            var playerIndexes = replay.TrackerEvents.Where(i => i.TrackerEventType == ReplayTrackerEvents.TrackerEventType.PlayerSetupEvent && i.Data.dictionary[2].optionalData != null).Select(i => i.Data.dictionary[2].optionalData.vInt.Value).Distinct().OrderBy(i => i).ToArray();

            for (var i = 0; i < playerIndexes.Length; i++)
            {
                // The references between both of these classes are the same on purpose.
                // We want updates to one to propogate to the other.
                replay.ClientList[playerIndexes[i]] = replay.Players[i];
            }
        }
コード例 #8
0
        private static void ParseHeader(Replay replay, BinaryReader reader)
        {
            reader.ReadBytes(3);                          // 'Magic'
            reader.ReadByte();                            // Format
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Data Max Size
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Header Offset
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // User Data Header Size

            var headerStructure = new TrackerEventStructure(reader);

            // [0] = Blob, "Heroes of the Storm replay 11" - Strange backward arrow before 11 as well.  I don't think the '11' will change, as I believe it was also always '11' in Starcraft 2 replays.

            replay.ReplayVersion = string.Format("{0}.{1}.{2}.{3}", headerStructure.dictionary[1].dictionary[0].vInt.Value, headerStructure.dictionary[1].dictionary[1].vInt.Value, headerStructure.dictionary[1].dictionary[2].vInt.Value, headerStructure.dictionary[1].dictionary[3].vInt.Value);
            replay.ReplayBuild   = (int)headerStructure.dictionary[1].dictionary[4].vInt.Value;

            // [2] = VInt, Default 2
            // [3] = VInt, Frame Count (Very similar, though slightly different, than frame count from tracker event frame delta sum)
            // [4] = VInt, Default 0
            // [5] = Depending on replay build, either Blob with gibberish, or array of 16 bytes (basically a Blob), also with gibberish.  Of ~770 pre-wipe replays, there were only 11 distinct blobs, so this is likely a map version hash or something
            // [6] = Replay Build (Same as what is in [1])
        }
コード例 #9
0
        private static void ParseHeader(Replay replay, BinaryReader reader)
        {
            reader.ReadBytes(3); // 'Magic'
            reader.ReadByte(); // Format
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Data Max Size
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // Header Offset
            BitConverter.ToInt32(reader.ReadBytes(4), 0); // User Data Header Size

            var headerStructure = new TrackerEventStructure(reader);

            // [0] = Blob, "Heroes of the Storm replay 11" - Strange backward arrow before 11 as well.  I don't think the '11' will change, as I believe it was also always '11' in Starcraft 2 replays.

            replay.ReplayVersion = string.Format("{0}.{1}.{2}.{3}", headerStructure.dictionary[1].dictionary[0].vInt.Value, headerStructure.dictionary[1].dictionary[1].vInt.Value, headerStructure.dictionary[1].dictionary[2].vInt.Value, headerStructure.dictionary[1].dictionary[3].vInt.Value);
            replay.ReplayBuild = (int)headerStructure.dictionary[1].dictionary[4].vInt.Value;

            // [2] = VInt, Default 2
            // [3] = VInt, Frame Count (Very similar, though slightly different, than frame count from tracker event frame delta sum)
            // [4] = VInt, Default 0
            // [5] = Depending on replay build, either Blob with gibberish, or array of 16 bytes (basically a Blob), also with gibberish.  Of ~770 pre-wipe replays, there were only 11 distinct blobs, so this is likely a map version hash or something
            // [6] = Replay Build (Same as what is in [1])
        }
コード例 #10
0
        /// <summary> Parses the replay.tracker.events file, applying it to a Replay object. </summary>
        /// <param name="replay"> The replay object to apply the parsed information to. </param>
        /// <param name="buffer"> The buffer containing the replay.tracker.events file. </param>
        /// <param name="onlyParsePlayerSetupEvents"> If true, speeds up parsing by skipping Unit data, which is most of this file </param>
        public static void Parse(Replay replay, byte[] buffer, bool onlyParsePlayerSetupEvents = false)
        {
            replay.TrackerEvents = new List <TrackerEvent>();

            var currentFrameCount = 0;

            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                    while (stream.Position < stream.Length)
                    {
                        var intro = reader.ReadBytes(3); // Always 03 ?? 09; Middle digit seems to have at least two possible values

                        currentFrameCount += (int)TrackerEventStructure.read_vint(reader);

                        var trackerEvent = new TrackerEvent {
                            TimeSpan = new TimeSpan(0, 0, (int)(currentFrameCount / 16.0))
                        };

                        intro = reader.ReadBytes(1); // Always 09

                        trackerEvent.TrackerEventType = (TrackerEventType)TrackerEventStructure.read_vint(reader);
                        trackerEvent.Data             = new TrackerEventStructure(reader);
                        replay.TrackerEvents.Add(trackerEvent);

                        if (onlyParsePlayerSetupEvents && trackerEvent.TrackerEventType != TrackerEventType.PlayerSetupEvent)
                        {
                            break;
                        }
                    }

            // Populate the client list using player indexes
            var playerIndexes = replay.TrackerEvents.Where(i => i.TrackerEventType == TrackerEventType.PlayerSetupEvent && i.Data.dictionary[2].optionalData != null).Select(i => i.Data.dictionary[2].optionalData.vInt.Value).Distinct().OrderBy(i => i).ToArray();

            for (var i = 0; i < playerIndexes.Length; i++)
            {
                // The references between both of these classes are the same on purpose.
                // We want updates to one to propogate to the other.
                replay.ClientList[playerIndexes[i]] = replay.Players[i];
            }
        }
コード例 #11
0
        /// <summary> Parses the replay.tracker.events file </summary>
        /// <param name="buffer"> The buffer containing the replay.tracker.events file. </param>
        public static List <TrackerEvent> Parse(byte[] buffer)
        {
            var trackerEvents = new List <TrackerEvent>();

            var currentFrameCount = 0;

            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                    while (stream.Position < stream.Length)
                    {
                        reader.ReadBytes(3); // Always 03 ?? 09; Middle digit seems to have at least two possible values

                        currentFrameCount += (int)TrackerEventStructure.read_vint(reader);

                        var trackerEvent = new TrackerEvent {
                            TimeSpan = TimeSpan.FromSeconds((int)(currentFrameCount / 16.0))
                        };

                        reader.ReadBytes(1); // Always 09

                        trackerEvent.TrackerEventType = (TrackerEventType)TrackerEventStructure.read_vint(reader);
                        trackerEvent.Data             = new TrackerEventStructure(reader);

                        if (trackerEvent.TrackerEventType == TrackerEventType.StatGameEvent && trackerEvent.Data.dictionary[3].optionalData != null)
                        {
                            // m_fixedData is stored in fixed point 20.12 format
                            foreach (var trackerEventArrayItem in trackerEvent.Data.dictionary[3].optionalData.array)
                            {
                                trackerEventArrayItem.dictionary[1].vInt = trackerEventArrayItem.dictionary[1].vInt.Value / 4096;
                            }
                        }

                        trackerEvents.Add(trackerEvent);
                    }

            return(trackerEvents);
        }
コード例 #12
0
        public TrackerEventStructure(BinaryReader reader)
        {
            DataType = reader.ReadByte();
            switch (DataType)
            {
            case 0x00:     // array
                array = new TrackerEventStructure[read_vint(reader)];
                for (var i = 0; i < array.Length; i++)
                {
                    array[i] = new TrackerEventStructure(reader);
                }
                break;

            case 0x01:     // bitarray, weird alignment requirements - haven't seen it used yet so not spending time on it
                /*  bits = self.read_vint()
                 *  data = self.read_bits(bits) */
                throw new NotImplementedException();

            case 0x02:     // blob
                blob = reader.ReadBytes((int)read_vint(reader));
                break;

            case 0x03:     // choice
                choiceFlag = (int)read_vint(reader);
                choiceData = new TrackerEventStructure(reader);
                break;

            case 0x04:     // optional
                if (reader.ReadByte() != 0)
                {
                    optionalData = new TrackerEventStructure(reader);
                }
                break;

            case 0x05:     // struct
                dictionary = new Dictionary <int, TrackerEventStructure>();
                var dictionarySize = read_vint(reader);
                for (var i = 0; i < dictionarySize; i++)
                {
                    dictionary[(int)read_vint(reader)] = new TrackerEventStructure(reader);
                }
                break;

            case 0x06:     // u8
                unsignedInt = reader.ReadByte();
                break;

            case 0x07:     // u32
                unsignedInt = reader.ReadUInt32();
                break;

            case 0x08:     // u64
                unsignedInt = reader.ReadUInt64();
                break;

            case 0x09:     // vint
                vInt = read_vint(reader);
                break;

            default:
                throw new NotImplementedException();
            }
        }
コード例 #13
0
 public TrackerEventStructure(BinaryReader reader)
 {
     DataType = reader.ReadByte();
     switch (DataType)
     {
         case 0x00: // array
             array = new TrackerEventStructure[read_vint(reader)];
             for (var i = 0; i < array.Length; i++)
                 array[i] = new TrackerEventStructure(reader);
             break;
         case 0x01: // bitarray, weird alignment requirements - haven't seen it used yet so not spending time on it
             /*  bits = self.read_vint()
                 data = self.read_bits(bits) */
             throw new NotImplementedException();
         case 0x02: // blob
             blob = reader.ReadBytes((int) read_vint(reader));
             break;
         case 0x03: // choice
             choiceFlag = (int) read_vint(reader);
             choiceData = new TrackerEventStructure(reader);
             break;
         case 0x04: // optional
             if (reader.ReadByte() != 0)
                 optionalData = new TrackerEventStructure(reader);
             break;
         case 0x05: // struct
             dictionary = new Dictionary<int, TrackerEventStructure>();
             var dictionarySize = read_vint(reader);
             for (var i = 0; i < dictionarySize; i++)
                 dictionary[(int) read_vint(reader)] = new TrackerEventStructure(reader);
             break;
         case 0x06: // u8
             unsignedInt = reader.ReadByte();
             break;
         case 0x07: // u32
             unsignedInt = reader.ReadUInt32();
             break;
         case 0x08: // u64
             unsignedInt = reader.ReadUInt64();
             break;
         case 0x09: // vint
             vInt = read_vint(reader);
             break;
         default:
             throw new NotImplementedException();
     }
 }
コード例 #14
0
        /// <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="buffer"> The buffer containing the replay.details file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                {
                    var replayDetailsStructure = new TrackerEventStructure(reader);

                    replay.Players = replayDetailsStructure.dictionary[0].optionalData.array.Select(i => new Player
                    {
                        Name = i.dictionary[0].blobText,
                        BattleNetRegionId = (int)i.dictionary[1].dictionary[0].vInt.Value,
                        BattleNetSubId    = (int)i.dictionary[1].dictionary[2].vInt.Value,
                        BattleNetId       = (int)i.dictionary[1].dictionary[4].vInt.Value,
                        // [2] = Race (SC2 Remnant, Always Empty String in Heroes of the Storm)
                        Color = i.dictionary[3].dictionary.Keys.OrderBy(j => j).Select(j => (int)i.dictionary[3].dictionary[j].vInt.Value).ToArray(),
                        // [4] = Player Type (2 = Human, 3 = Computer (Practice, Try Me, or Coop)) - This is more accurately gathered in replay.attributes.events
                        Team     = (int)i.dictionary[5].vInt.Value,
                        Handicap = (int)i.dictionary[6].vInt.Value,
                        // [7] = VInt, Default 0
                        IsWinner = i.dictionary[8].vInt.Value == 1,
                        // [9] = Player Number (0 - 9)
                        Character = i.dictionary[10].blobText
                    }).ToArray();

                    if (replay.Players.Length != 10)
                    {
                        // Try Me Mode, or something strange
                        return;
                    }

                    var playerIndexes = replay.TrackerEvents.Where(i => i.TrackerEventType == ReplayTrackerEvents.TrackerEventType.PlayerSetupEvent && i.Data.dictionary[2].optionalData != null).Select(i => i.Data.dictionary[2].optionalData.vInt.Value).OrderBy(i => i).ToArray();
                    for (var i = 0; i < playerIndexes.Length; i++)
                    {
                        // The references between both of these classes are the same on purpose.
                        // We want updates to one to propogate to the other.
                        replay.ClientList[playerIndexes[i]] = replay.Players[i];
                    }

                    replay.Map = replayDetailsStructure.dictionary[1].blobText;
                    // [2] - This is typically an empty string, no need to decode.
                    // [3] - Blob: "Minimap.tga" or "CustomMiniMap.tga"
                    // [4] - Uint, Default 1

                    // [5] - Utc Timestamp
                    replay.Timestamp = DateTime.FromFileTimeUtc(replayDetailsStructure.dictionary[5].vInt.Value);

                    // [6] - Windows replays, this is Utc offset.  Mac replays, this is actually the entire Local Timestamp
                    // var potentialUtcOffset = new TimeSpan(replayDetailsStructure.dictionary[6].vInt.Value);
                    // Console.WriteLine(potentialUtcOffset.ToString());

                    // [7] - Blob, Empty String
                    // [8] - Blob, Empty String
                    // [9] - Blob, Empty String
                    // [10] - Optional, Array: 0 - Blob, "s2ma"
                    // [11] - UInt, Default 0
                    // [12] - VInt, Default 4
                    // [13] - VInt, Default 1 or 7
                    // [14] - Optional, Null
                    // [15] - VInt, Default 0
                    // [16] - Optional, UInt, Default 0
                }
        }
コード例 #15
0
        /// <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="buffer"> The buffer containing the replay.details file. </param>
        public static void Parse(Replay replay, byte[] buffer, bool ignoreErrors = false)
        {
            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                {
                    var replayDetailsStructure = new TrackerEventStructure(reader);
                    replay.Players = replayDetailsStructure.dictionary[0].optionalData.array.Select(i => new Player {
                        Name = i.dictionary[0].blobText,
                        BattleNetRegionId = (int)i.dictionary[1].dictionary[0].vInt.Value,
                        BattleNetSubId    = (int)i.dictionary[1].dictionary[2].vInt.Value,
                        BattleNetId       = (int)i.dictionary[1].dictionary[4].vInt.Value,
                        // [2] = Race (SC2 Remnant, Always Empty String in Heroes of the Storm)
                        Color = i.dictionary[3].dictionary.Keys.OrderBy(j => j).Select(j => (int)i.dictionary[3].dictionary[j].vInt.Value).ToArray(),
                        // [4] = Player Type (2 = Human, 3 = Computer (Practice, Try Me, or Cooperative)) - This is more accurately gathered in replay.attributes.events
                        Team     = (int)i.dictionary[5].vInt.Value,
                        Handicap = (int)i.dictionary[6].vInt.Value,
                        // [7] = VInt, Default 0 - 'm_observe'
                        IsWinner = i.dictionary[8].vInt.Value == 1,
                        // [9] = 'm_workingSetSlotId'
                        Character = i.dictionary[10].blobText
                    }).ToArray();

                    if (!ignoreErrors && (replay.Players.Length != 10 || replay.Players.Count(i => i.IsWinner) != 5))
                    {
                        // Try Me Mode, or something strange
                        return;
                    }

                    for (var i = 0; i < replay.Players.Length; i++)
                    {
                        replay.ClientListByWorkingSetSlotID[replayDetailsStructure.dictionary[0].optionalData.array[i].dictionary[9].optionalData.vInt.Value] = replay.Players[i];
                    }

                    replay.Map = replayDetailsStructure.dictionary[1].blobText;
                    // [2] - m_difficulty
                    // [3] - m_thumbnail - "Minimap.tga", "CustomMiniMap.tga", etc
                    // [4] - m_isBlizzardMap

                    replay.Timestamp = DateTime.FromFileTimeUtc(replayDetailsStructure.dictionary[5].vInt.Value); // m_timeUTC

                    // There was a bug during the below builds where timestamps were buggy for the Mac build of Heroes of the Storm
                    // The replay, as well as viewing these replays in the game client, showed years such as 1970, 1999, etc
                    // I couldn't find a way to get the correct timestamp, so I am just estimating based on when these builds were live
                    if (replay.ReplayBuild == 34053 && replay.Timestamp < new DateTime(2015, 2, 8))
                    {
                        replay.Timestamp = new DateTime(2015, 2, 13);
                    }
                    else if (replay.ReplayBuild == 34190 && replay.Timestamp < new DateTime(2015, 2, 15))
                    {
                        replay.Timestamp = new DateTime(2015, 2, 20);
                    }

                    // [6] - m_timeLocalOffset - For Windows replays, this is Utc offset.  For Mac replays, this is actually the entire Local Timestamp
                    // [7] - m_description - Empty String
                    // [8] - m_imageFilePath - Empty String
                    // [9] - m_mapFileName - Empty String
                    // [10] - m_cacheHandles - "s2ma"
                    // [11] - m_miniSave - 0
                    // [12] - m_gameSpeed - 4
                    // [13] - m_defaultDifficulty - Usually 1 or 7
                    // [14] - m_modPaths - Null
                    // [15] - m_campaignIndex - 0
                    // [16] - m_restartAsTransitionMap - 0
                }
        }
コード例 #16
0
        /// <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="buffer"> The buffer containing the replay.details file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
                using (var reader = new BinaryReader(stream))
                {
                    var replayDetailsStructure = new TrackerEventStructure(reader);
                    replay.Players = replayDetailsStructure.dictionary[0].optionalData.array.Select(i => new Player
                    {
                        Name = i.dictionary[0].blobText,
                        BattleNetRegionId = (int)i.dictionary[1].dictionary[0].vInt.Value,
                        BattleNetSubId    = (int)i.dictionary[1].dictionary[2].vInt.Value,
                        BattleNetId       = (int)i.dictionary[1].dictionary[4].vInt.Value,
                        // [2] = Race (SC2 Remnant, Always Empty String in Heroes of the Storm)
                        Color = i.dictionary[3].dictionary.Keys.OrderBy(j => j).Select(j => (int)i.dictionary[3].dictionary[j].vInt.Value).ToArray(),
                        // [4] = Player Type (2 = Human, 3 = Computer (Practice, Try Me, or Coop)) - This is more accurately gathered in replay.attributes.events
                        Team     = (int)i.dictionary[5].vInt.Value,
                        Handicap = (int)i.dictionary[6].vInt.Value,
                        // [7] = VInt, Default 0
                        IsWinner = i.dictionary[8].vInt.Value == 1,
                        // [9] = Sometimes player index in ClientList array; usually 0-9, but can be higher if there are observers. I don't fully understand this, as this was incorrect in at least one Custom game, where this said ClientList[8] was null
                        Character = i.dictionary[10].blobText
                    }).ToArray();

                    if (replay.Players.Length != 10 || replay.Players.Count(i => i.IsWinner) != 5)
                    {
                        // Try Me Mode, or something strange
                        return;
                    }

                    replay.Map = replayDetailsStructure.dictionary[1].blobText;
                    // [2] - This is typically an empty string, no need to decode.
                    // [3] - Blob: "Minimap.tga" or "CustomMiniMap.tga"
                    // [4] - Uint, Default 1

                    // [5] - Utc Timestamp
                    replay.Timestamp = DateTime.FromFileTimeUtc(replayDetailsStructure.dictionary[5].vInt.Value);

                    // There was a bug during the below builds where timestamps were buggy for the Mac build of Heroes of the Storm
                    // The replay, as well as viewing these replays in the game client, showed years such as 1970, 1999, etc
                    // I couldn't find a way to get the correct timestamp, so I am just estimating based on when these builds were live
                    if (replay.ReplayBuild == 34053 && replay.Timestamp < new DateTime(2015, 2, 8))
                    {
                        replay.Timestamp = new DateTime(2015, 2, 13);
                    }
                    else if (replay.ReplayBuild == 34190 && replay.Timestamp < new DateTime(2015, 2, 15))
                    {
                        replay.Timestamp = new DateTime(2015, 2, 20);
                    }

                    // [6] - Windows replays, this is Utc offset.  Mac replays, this is actually the entire Local Timestamp
                    // var potentialUtcOffset = new TimeSpan(replayDetailsStructure.dictionary[6].vInt.Value);

                    // [7] - Blob, Empty String
                    // [8] - Blob, Empty String
                    // [9] - Blob, Empty String
                    // [10] - Optional, Array: 0 - Blob, "s2ma"
                    // [11] - UInt, Default 0
                    // [12] - VInt, Default 4
                    // [13] - VInt, Default 1 or 7
                    // [14] - Optional, Null
                    // [15] - VInt, Default 0
                    // [16] - Optional, UInt, Default 0
                }
        }