Example #1
0
        internal static DwgObject Parse(BitReader reader, DwgObjectCache objectCache, DwgVersionId version)
        {
            reader.StartCrcCheck();
            var size     = reader.Read_MS();
            var crcStart = reader.Offset + size;
            var typeCode = reader.Read_BS();

            if (!Enum.IsDefined(typeof(DwgObjectType), typeCode))
            {
                // unsupported
                return(null);
            }

            var type = (DwgObjectType)typeCode;
            var obj  = CreateObject(type);

            obj.ReadCommonDataStart(reader);
            obj.ParseSpecific(reader, version);
            obj.ReadCommonDataEnd(reader);

            // ensure there's no extra data
            reader.AlignToByte();
            reader.SkipBytes(Math.Max(0, crcStart - reader.Offset));

            reader.ValidateCrc(initialValue: DwgHeaderVariables.InitialCrcValue);
            obj.ValidateCommonValues();
            obj.OnAfterObjectRead(reader, objectCache);
            return(obj);
        }
        private static byte[] ReadSpecialBlob(BitReader bitReader, int numBitsForLength)
        {
            var stringLength = bitReader.Read(numBitsForLength);

            bitReader.AlignToByte();
            bitReader.ReadBytes(2);
            return(bitReader.ReadBytes((int)stringLength));
        }
        /// <summary> Parses the replay.server.battlelobby file in a replay file. </summary>
        /// <param name="replay"> The replay file to apply the parsed data to. </param>
        /// <param name="buffer"> The buffer containing the replay.server.battlelobby file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
            {
                var bitReader = new BitReader(stream);

                // 52124 and 52381 are non-tested ptr builds
                if (replay.ReplayBuild < 47479 ||
                    replay.ReplayBuild < 47903 || replay.ReplayBuild == 52124 || replay.ReplayBuild == 52381 ||
                    replay.GameMode == GameMode.Unknown)
                {
                    GetBattleTags(replay, bitReader);
                    return;
                }

                uint dependenciesLength = bitReader.Read(6);
                for (int i = 0; i < dependenciesLength; i++)
                {
                    bitReader.ReadBlobPrecededWithLength(10);
                }

                // s2ma cache handles
                uint s2maCacheHandlesLength = bitReader.Read(6);
                for (int i = 0; i < s2maCacheHandlesLength; i++)
                {
                    bitReader.AlignToByte();
                    if (bitReader.ReadString(4) != "s2ma")
                    {
                        throw new DetailedParsedException($"s2ma cache");
                    }

                    bitReader.ReadBytes(36);
                }

                DetailedParse(bitReader, replay, s2maCacheHandlesLength);
            }
        }
Example #4
0
        public static List <IGameEvent> Parse(Replay replay, byte[] buffer)
        {
            // The GameEvent file changes significantly after 16561.
            // This is sometime around the first patch after release. Since
            // parsing replays this old should be extremely rare, I don't believe
            // its worth the effort to try to support both. If it is, it should be
            // done in another method.
            //
            // Still a bitstream, but stuff's moved around and event codes are different.
            if (replay.ReplayBuild < 16561)
            {
                throw new NotSupportedException(
                          "Replay builds under 16561 are not supported for parsing GameEvent log.");
            }

            // Initialize Ability and Unit data.
            var effectiveBuild = BuildData.GetInstance().GetEffectiveBuild(replay.ReplayBuild);

            if (effectiveBuild == 0)
            {
                throw new NotSupportedException(
                          String.Format("Replay build {0} is not supported by the current event database", replay.ReplayBuild));
            }
            var abilityData = new AbilityData(effectiveBuild);
            var unitData    = new UnitData(effectiveBuild);

            var events = new List <IGameEvent>();

            // Keep a reference to know the game length.
            var ticksElapsed = 0;

            using (var stream = new MemoryStream(buffer))
            {
                var bitReader = new BitReader(stream);

                var playersGone = new bool[0x10];

                while (!bitReader.EndOfStream)
                {
                    var intervalLength = 6 + (bitReader.Read(2) << 3);
                    var interval       = bitReader.Read(intervalLength);
                    ticksElapsed += (int)interval;
                    var    playerIndex = (int)bitReader.Read(5);
                    Player player;
                    if (playerIndex < 0x10)
                    {
                        player = replay.GetPlayerById(playerIndex);
                    }
                    else
                    {
                        player = Player.Global;
                    }

                    var        eventType = bitReader.Read(7);
                    IGameEvent gameEvent;
                    switch (eventType)
                    {
                    case 0x05:     // Game start
                        gameEvent = new GameStartEvent();
                        break;

                    case 0x0b:
                    case 0x0c:     // Join game
                        gameEvent = new PlayerJoinEvent(bitReader, replay, playerIndex);
                        break;

                    case 0x19:     // Leave game
                        gameEvent = new PlayerLeftEvent(player);
                        playersGone[playerIndex] = true;
                        DetectWinners(playersGone, replay);
                        break;

                    case 0x1b:     // Ability
                        gameEvent = new AbilityEvent(bitReader, replay, player, abilityData, unitData);
                        break;

                    case 0x1c:     // Selection
                        gameEvent = new SelectionEvent(bitReader, replay, player, unitData);
                        break;

                    case 0x1d:     // Control groups
                        gameEvent = new HotkeyEvent(bitReader, replay, player);
                        break;

                    case 0x1f:     // Send resources
                        gameEvent = new SendResourcesEvent(bitReader, replay);
                        break;

                    case 0x23:     // ??
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        bitReader.Read(8);
                        break;

                    case 0x26:     // ??
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        bitReader.Read(32);
                        bitReader.Read(32);
                        break;

                    case 0x27:     // Target critter - special
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Selection;
                        var unitId = bitReader.Read(32);
                        break;

                    case 0x31:     // Camera
                        gameEvent = new CameraEvent(bitReader, replay);
                        break;

                    case 0x37:     // UI Event
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Other;
                        bitReader.Read(32);
                        bitReader.Read(32);
                        break;

                    case 0x38:     // weird sync event
                    {
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Other;
                        for (var j = 0; j < 2; j++)
                        {
                            var length = bitReader.Read(8);
                            for (var i = 0; i < length; i++)
                            {
                                bitReader.Read(32);
                            }
                        }
                        break;
                    }

                    case 0x3c:     // ???
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        bitReader.Read(16);
                        break;

                    case 0x46:     // Request resources
                        gameEvent = new RequestResourcesEvent(bitReader, replay);
                        break;

                    case 0x47:     // ?? -- associated with send minerals
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        bitReader.Read(32);
                        break;

                    case 0x48:     // ?? -- sync event
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        bitReader.Read(32);
                        break;

                    case 0x4C:     // ?? -- seen with spectator
                        bitReader.Read(4);
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        break;

                    case 0x59:     // ?? -- sync flags maybe?
                        bitReader.Read(32);
                        gameEvent           = new GameEventBase();
                        gameEvent.EventType = GameEventType.Inactive;
                        break;

                    default:     // debug
                        throw new InvalidOperationException(String.Format(
                                                                "Unknown event type {0:x} at {1:x} in replay.game.events",
                                                                eventType, bitReader.Cursor));
                    }

                    gameEvent.Player = player;
                    gameEvent.Time   = Timestamp.Create(ticksElapsed);
                    events.Add(gameEvent);

                    bitReader.AlignToByte();
                }
            }

            replay.GameLength = Timestamp.Create(ticksElapsed).TimeSpan;

            return(events);
        }
        private static void DetailedParse(BitReader bitReader, Replay replay, uint s2maCacheHandlesLength)
        {
            bitReader.AlignToByte();
            for (; ;)
            {
                // we're just going to skip all the way down to the s2mh
                if (bitReader.ReadString(4) == "s2mh")
                {
                    bitReader.stream.Position -= 4;
                    break;
                }
                else
                {
                    bitReader.stream.Position -= 3;
                }
            }

            // bitReader.Read(???); // this depends on previous data (not byte aligned)

            // s2mh cache handles
            // uint s2mhCacheHandlesLength = bitReader.Read(6);
            // for (int i = 0; i < s2mhCacheHandlesLength; i++)
            for (var i = 0; i < s2maCacheHandlesLength; i++)
            {
                bitReader.AlignToByte();
                if (bitReader.ReadString(4) != "s2mh")
                {
                    throw new Exception($"s2mh cache");
                }

                bitReader.ReadBytes(36);
            }

            // Player collections - starting with HOTS 2.0 (live build 52860)
            // strings gone starting with build (ptr) 55929
            // --------------------------------------------------------------
            var playerCollection = new List <string>();

            int collectionSize = replay.ReplayBuild >= 48027 ? bitReader.ReadInt16() : bitReader.ReadInt32();

            if (collectionSize > 8000)
            {
                throw new DetailedParsedException("collectionSize is an unusually large number");
            }

            for (var i = 0; i < collectionSize; i++)
            {
                if (replay.ReplayBuild >= 55929)
                {
                    bitReader.ReadBytes(8); // most likey an identifier for the item; first six bytes are 0x00
                }
                else
                {
                    playerCollection.Add(bitReader.ReadString(bitReader.ReadByte()));
                }
            }

            // use to determine if the collection item is usable by the player (owns/free to play/internet cafe)
            if (bitReader.ReadInt32() != collectionSize)
            {
                throw new DetailedParsedException("skinArrayLength not equal");
            }

            for (var i = 0; i < collectionSize; i++)
            {
                for (var j = 0; j < 16; j++) // 16 is total player slots
                {
                    bitReader.ReadByte();

                    var num = bitReader.Read(8);

                    if (replay.ReplayBuild < 55929)
                    {
                        if (replay.ClientListByUserID[j] != null)
                        {
                            if (num > 0)
                            {
                                replay.ClientListByUserID[j].PlayerCollectionDictionary.Add(playerCollection[i], true);
                            }
                            else if (num == 0)
                            {
                                replay.ClientListByUserID[j].PlayerCollectionDictionary.Add(playerCollection[i], false);
                            }
                            else
                            {
                                throw new NotImplementedException();
                            }
                        }
                    }
                }
            }

            if (replay.ReplayBuild >= 85027)
            {
                bitReader.ReadByte(); // could contain m_disabledHeroList
            }
            // Player info
            // ------------------------

            // m_randomSeed, set it if it hasn't been set
            if (replay.RandomValue == 0)
            {
                replay.RandomValue = (uint)bitReader.ReadInt32();
            }
            else
            {
                bitReader.ReadInt32();
            }

            bitReader.ReadBytes(4);

            uint playerListLength = bitReader.Read(5);

            for (uint i = 0; i < playerListLength; i++)
            {
                bitReader.Read(32);

                bitReader.Read(5); // player index

                // toon
                bitReader.Read(8);                                    // m_region
                if (bitReader.ReadStringFromBits(32, true) != "Hero") // m_programId
                {
                    throw new DetailedParsedException("Not Hero");
                }
                bitReader.Read(32);     // m_realm
                bitReader.ReadLong(64); // m_id

                // internal toon
                bitReader.Read(8);                                    // m_region
                if (bitReader.ReadStringFromBits(32, true) != "Hero") // m_programId
                {
                    throw new DetailedParsedException("Not Hero");
                }
                bitReader.Read(32); // m_realm

                int idLength = (int)bitReader.Read(7) + 2;
                bitReader.AlignToByte();
                replay.ClientListByUserID[i].BattleNetTId = bitReader.ReadString(idLength);

                bitReader.Read(6);

                if (replay.ReplayBuild <= 47479)
                {
                    // internal toon repeat
                    bitReader.Read(8);                                    // m_region
                    if (bitReader.ReadStringFromBits(32, true) != "Hero") // m_programId
                    {
                        throw new DetailedParsedException("Not Hero");
                    }
                    bitReader.Read(32); // m_realm

                    idLength = (int)bitReader.Read(7) + 2;
                    bitReader.AlignToByte();
                    if (replay.ClientListByUserID[i].BattleNetTId != bitReader.ReadString(idLength))
                    {
                        throw new DetailedParsedException("Duplicate internal id does not match");
                    }
                }

                bitReader.Read(2);
                bitReader.ReadBytes(25);
                bitReader.Read(24);

                if (replay.GameMode == GameMode.Cooperative)
                {
                    bitReader.ReadBytes(8); // ai games have 8 more bytes somewhere around here
                }
                bitReader.Read(7);

                if (!bitReader.ReadBoolean())
                {
                    // repeat of the collection section above
                    if (replay.ReplayBuild >= 51609 || replay.ReplayBuild == 47903 || replay.ReplayBuild == 47479)
                    {
                        bitReader.ReadBitArray(bitReader.Read(12));
                    }
                    else if (replay.ReplayBuild > 47219)
                    {
                        // each byte has a max value of 0x7F (127)
                        bitReader.ReadBytes((int)bitReader.Read(15) * 2);
                    }
                    else
                    {
                        bitReader.ReadBitArray(bitReader.Read(9));
                    }

                    bitReader.ReadBoolean();
                }

                bitReader.ReadBoolean(); // m_hasSilencePenalty

                if (replay.ReplayBuild >= 61718)
                {
                    bitReader.ReadBoolean();
                    bitReader.ReadBoolean(); // m_hasVoiceSilencePenalty
                }

                if (replay.ReplayBuild >= 66977)
                {
                    bitReader.ReadBoolean();                                                                 // m_isBlizzardStaff
                }
                if (bitReader.ReadBoolean())                                                                 // is player in party
                {
                    replay.ClientListByUserID[i].PartyValue = bitReader.ReadInt32() + bitReader.ReadInt32(); // players in same party will have the same exact 8 bytes of data
                }
                bitReader.ReadBoolean();                                                                     // has battletag?

                var battleTag = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(7)).Split('#'); // battleTag <name>#xxxxx

                if (battleTag[0] != replay.ClientListByUserID[i].Name)
                {
                    throw new DetailedParsedException("Couldn't find BattleTag");
                }

                if (battleTag.Length == 2)
                {
                    replay.ClientListByUserID[i].BattleTag = int.Parse(battleTag[1]);
                }

                if (replay.ReplayBuild >= 52860 || (replay.ReplayVersionMajor == 2 && replay.ReplayBuild >= 51978))
                {
                    replay.ClientListByUserID[i].AccountLevel = (int)bitReader.Read(32);  // in custom games, this is a 0
                }
                if (replay.ReplayBuild >= 69947)
                {
                    bitReader.ReadBoolean(); // m_hasActiveBoost
                }
            }

            // some more data after this
            // there is also a CSTM string down here, if it exists, the game is a custom game
        }
        /// <summary> Parses the replay.initdata file in a replay file. </summary>
        /// <param name="replay"> The replay file to apply the parsed data to. </param>
        /// <param name="buffer"> The buffer containing the replay.initdata file. </param>
        public static void Parse(Replay replay, byte[] buffer)
        {
            using (var stream = new MemoryStream(buffer))
            {
                var reader = new BitReader(stream);

                var i = reader.ReadByte();

                var playerList = new string[i];
                for (int j = 0; j < i; j++)
                {
                    var nameLength = reader.ReadByte();
                    reader.AlignToByte();
                    var str = reader.ReadString(nameLength);

                    playerList[j] = str;

                    if (replay.ReplayBuild < 24764)
                    {
                        reader.ReadBytes(5);
                    }
                    else
                    {
                        if (reader.ReadBoolean())
                        {
                            var strLength = reader.ReadByte();
                            reader.AlignToByte();
                            var clanTag = reader.ReadString(strLength);
                        }

                        if (reader.ReadBoolean())
                        {
                            reader.ReadByte(); // Highest league
                        }

                        if (reader.ReadBoolean())
                        {
                            var swarmLevel = reader.ReadInt32(); // Swarm level
                        }

                        reader.ReadInt32(); // Random seed

                        if (reader.ReadBoolean())
                        {
                            reader.ReadByte(); // Race Preference
                        }

                        if (reader.ReadBoolean())
                        {
                            reader.ReadByte(); // Team Preference
                        }

                        reader.ReadBoolean(); //test map
                        reader.ReadBoolean(); //test auto
                        reader.ReadBoolean(); //examine
                        reader.ReadBoolean(); //custom interface
                        reader.Read(2);       //observer
                    }
                }

                if (replay.ReplayBuild < 24764)
                {
                    using (var binaryReader = new BinaryReader(stream))
                    {
                        // Save the full list of clients.
                        // This is no longer necessary since we get the client list elsewhere.
                        //// replay.ClientList = playerList;
                        if (PositionAfter(binaryReader, new byte[] { 115, 50, 109, 97 }))
                        {
                            reader.ReadBytes(2);
                            var gatewayStr = reader.ReadBytes(2);

                            var gateway = Encoding.UTF8.GetString(gatewayStr);
                            replay.Gateway = gateway;
                        }
                        else
                        {
                            replay.GameType = GameType.SinglePlayer;
                        }
                    }
                }
            }
        }
Example #7
0
        public static void Parse(StormReplay replay, ReadOnlySpan <byte> source)
        {
            BitReader bitReader = new BitReader(source, EndianType.BigEndian);

            uint dependenciesLength = bitReader.ReadBits(6);

            for (int i = 0; i < dependenciesLength; i++)
            {
                bitReader.ReadBlobAsString(10);
            }

            // s2ma cache handles
            uint s2maCacheHandlesLength = bitReader.ReadBits(6);

            for (int i = 0; i < s2maCacheHandlesLength; i++)
            {
                if (bitReader.ReadStringFromBytes(4) != "s2ma")
                {
                    throw new StormParseException($"{_exceptionHeader}: s2ma cache");
                }

                bitReader.ReadAlignedBytes(36);
            }

            /*source.ReadAlignedBytes(94);
             * if (source.ReadStringFromBytes(4) != "Clsd")
             * throw new StormParseException($"{ExceptionHeader}: Clsd"); */

            // we're just going to skip all the way down to the s2mh

            bitReader.AlignToByte();

            /*for (; ;)
             * {
             *  if (source.ReadStringFromBytes(4) == "s2mh")
             *  {
             *      BitReader.Index -= 4;
             *      break;
             *  }
             *  else
             *  {
             *      BitReader.Index -= 3;
             *  }
             * }*/

            /*
             * for (; ; )
             * {
             *  if (source.ReadStringFromBytes(4) == "s2mv")
             *  {
             *      BitReader.Index -= 4;
             *      break;
             *  }
             *  else
             *  {
             *      BitReader.Index -= 3;
             *  }
             * }*/

            // first hit

            /*
             * BitReader.Index -= 1;
             *
             * uint s2mvCacheHandlesLength = source.ReadBits(8);
             *
             * for (int i = 0; i < s2mvCacheHandlesLength; i++)
             * {
             *  if (source.ReadStringFromBytes(4) != "s2mv")
             *      throw new StormParseException($"{ExceptionHeader}: s2mv cache");
             *
             *  source.ReadAlignedBytes(36);
             * }
             *
             * uint localeCount = source.ReadBits(5);
             *
             * for (int i = 0; i < localeCount; i++)
             * {
             *  source.ReadStringFromBits(32); // locale
             *
             *  uint s2mlCacheHandlesLength = source.ReadBits(6);
             *
             *  for (int j = 0; j < s2mlCacheHandlesLength; j++)
             *  {
             *      if (source.ReadStringFromBytes(4) != "s2ml")
             *          throw new StormParseException($"{ExceptionHeader}: s2ml cache");
             *
             *      source.ReadAlignedBytes(36);
             *  }
             * }
             *
             * source.ReadAlignedBytes(16);
             * uint sm2vCacheBlizzLength = source.ReadBits(8);
             *
             * for (int i = 0; i < sm2vCacheBlizzLength; i++)
             * {
             *
             * }
             */

            // second s2mv hit

            /*
             * for (; ; )
             * {
             *  if (source.ReadStringFromBytes(4) == "s2mv")
             *  {
             *      BitReader.Index -= 4;
             *      break;
             *  }
             *  else
             *  {
             *      BitReader.Index -= 3;
             *  }
             * }
             * for (int i = 0; i < 2; i++)
             * {
             *  if (source.ReadStringFromBytes(4) != "s2mv")
             *      throw new StormParseException($"{ExceptionHeader}: s2mv cache");
             *
             *  source.ReadAlignedBytes(36);
             * }
             *
             * source.ReadBits(1);
             *
             * uint region = source.ReadBits(8); // m_region
             * if (source.ReadStringFromBits(32) != "Hero") // m_programId
             *  throw new StormParseException($"{ExceptionHeader}: Not Hero");
             * source.ReadBits(32); // m_realm
             *
             * int blizzIdLength = (int)source.ReadBits(7);
             *
             * if (region >= 90)
             * {
             *  if (source.ReadStringFromBytes(2) != "T:")
             *      throw new StormParseException($"{ExceptionHeader}: Not blizz T:");
             *  source.ReadStringFromBytes(blizzIdLength);
             * }
             * else
             * {
             *  source.ReadStringFromBytes(blizzIdLength);
             *  source.ReadStringFromBytes(2);
             * }
             *
             * source.ReadBits(8); // m_region
             * if (source.ReadStringFromBits(32) != "Hero") // m_programId
             *  throw new StormParseException($"{ExceptionHeader}: Not Hero");
             * source.ReadBits(32); // m_realm
             * source.ReadLongBits(64); // m_id
             *
             *
             * int klj = (int)source.ReadBits(12);
             *
             * int sdfad = (int)source.ReadBits(12);
             *
             *
             * source.ReadBits(1); //temp
             *
             */

            for (; ;)
            {
                if (bitReader.ReadStringFromBytes(4) == "s2mh")
                {
                    bitReader.Index -= 4;
                    break;
                }
                else
                {
                    bitReader.Index -= 3;
                }
            }

            // source.ReadBits(???); // this depends on previous data (not byte aligned)

            // s2mh cache handles
            // uint s2mhCacheHandlesLength = source.ReadBits(6);
            // for (int i = 0; i < s2mhCacheHandlesLength; i++)
            for (int i = 0; i < s2maCacheHandlesLength; i++)
            {
                if (bitReader.ReadStringFromBytes(4) != "s2mh")
                {
                    throw new StormParseException($"{_exceptionHeader}: s2mh cache");
                }

                bitReader.ReadAlignedBytes(36);
            }

            // player collections

            uint collectionSize;

            // strings gone starting with build (ptr) 55929
            if (replay.ReplayBuild >= 48027)
            {
                collectionSize = bitReader.ReadBits(16);
            }
            else
            {
                collectionSize = bitReader.ReadBits(32);
            }

            for (uint i = 0; i < collectionSize; i++)
            {
                if (replay.ReplayBuild >= 55929)
                {
                    bitReader.ReadAlignedBytes(8); // most likey an identifier for the item; first six bytes are 0x00
                }
                else
                {
                    bitReader.ReadStringFromBytes(bitReader.ReadAlignedByte());
                }
            }

            // use to determine if the collection item is usable by the player (owns/free to play/internet cafe)
            if (bitReader.ReadBits(32) != collectionSize)
            {
                throw new StormParseException($"{_exceptionHeader}: collection difference");
            }

            for (int i = 0; i < collectionSize; i++)
            {
                // 16 is total player slots
                for (int j = 0; j < 16; j++)
                {
                    bitReader.ReadAlignedByte();
                    bitReader.ReadAlignedByte(); // more likely a boolean to get the value

                    if (replay.ReplayBuild < 55929)
                    {
                        // when the identifier is a string, set the value to the appropriate array index
                    }
                }
            }

            // Player info

            if (replay.ReplayBuild <= 47479 || replay.ReplayBuild == 47801 || replay.ReplayBuild == 47903)
            {
                // Builds that are not yet supported for detailed parsing
                // build 47801 is a ptr build that had new data in the battletag section, the data was changed in 47944 (patch for 47801)
                // GetBattleTags(replay, source);
                return;
            }

            replay.RandomValue = bitReader.ReadBits(32); // m_randomSeed

            bitReader.ReadAlignedBytes(4);

            uint playerListLength = bitReader.ReadBits(5);

            if (replay.PlayersWithObserversCount != playerListLength)
            {
                throw new StormParseException($"{_exceptionHeader}: mismatch on player list length - {playerListLength} to {replay.PlayersWithObserversCount}");
            }

            for (uint i = 0; i < playerListLength; i++)
            {
                bitReader.ReadBits(32);

                uint playerIndex = bitReader.ReadBits(5); // player index

                StormPlayer player = replay.ClientListByUserID[playerIndex];

                // toon handle
                uint playerRegion = bitReader.ReadBits(8); // m_region

                bitReader.EndianType = EndianType.LittleEndian;
                if (bitReader.ReadBits(32) != 1869768008) // m_programId
                {
                    throw new StormParseException($"{_exceptionHeader}: Not Hero");
                }
                bitReader.EndianType = EndianType.BigEndian;

                uint playerRealm = bitReader.ReadBits(32);     // m_realm
                long playerId    = bitReader.ReadLongBits(64); // m_id

                if (player.PlayerType == PlayerType.Human)
                {
                    if (player.ToonHandle !.Region != playerRegion)
                    {
                        throw new StormParseException($"{_exceptionHeader}: Mismatch on player region");
                    }
                    if (player.ToonHandle.Realm != playerRealm)
                    {
                        throw new StormParseException($"{_exceptionHeader}: Mismatch on player realm");
                    }
                    if (player.ToonHandle.Id != playerId)
                    {
                        throw new StormParseException($"{_exceptionHeader}: Mismatch on player id");
                    }
                }
                else if (player.PlayerType == PlayerType.Observer || player.PlayerType == PlayerType.Unknown)
                {
                    // observers don't have the information carried over to the details file and sometimes not the initdata file
                    player.ToonHandle ??= new ToonHandle();

                    player.ToonHandle.Region    = (int)playerRegion;
                    player.ToonHandle.ProgramId = 1869768008;
                    player.ToonHandle.Realm     = (int)playerRealm;
                    player.ToonHandle.Id        = (int)playerId;
                    player.PlayerType           = PlayerType.Observer;
                    player.Team = StormTeam.Observer;
                }

                // toon handle again but with T_ shortcut
                bitReader.ReadBits(8); // m_region

                bitReader.EndianType = EndianType.LittleEndian;
                if (bitReader.ReadBits(32) != 1869768008) // m_programId (Hero)
                {
                    throw new StormParseException($"{_exceptionHeader}: Not Hero");
                }
                bitReader.EndianType = EndianType.BigEndian;

                bitReader.ReadBits(32); // m_realm

                int idLength = (int)bitReader.ReadBits(7) + 2;

                player.ToonHandle ??= new ToonHandle();
                player.ToonHandle.ShortcutId = bitReader.ReadStringFromBytes(idLength);

                bitReader.ReadBits(6);

                if (replay.ReplayBuild <= 47479)
                {
                    // toon handle repeat again with T_ shortcut
                    bitReader.ReadBits(8);                          // m_region
                    if (bitReader.ReadStringFromBits(32) != "Hero") // m_programId
                    {
                        throw new StormParseException($"{_exceptionHeader}: Not Hero");
                    }
                    bitReader.ReadBits(32); // m_realm

                    idLength = (int)bitReader.ReadBits(7) + 2;
                    if (player.ToonHandle.ShortcutId != bitReader.ReadStringFromBytes(idLength))
                    {
                        throw new StormParseException($"{_exceptionHeader}: Duplicate shortcut id does not match");
                    }

                    bitReader.ReadBits(6);
                }

                bitReader.ReadBits(2);
                bitReader.ReadUnalignedBytes(25);
                bitReader.ReadBits(24);

                // ai games have 8 more bytes somewhere around here
                if (replay.GameMode == StormGameMode.Cooperative)
                {
                    bitReader.ReadUnalignedBytes(8);
                }

                bitReader.ReadBits(7);

                if (!bitReader.ReadBoolean())
                {
                    // repeat of the collection section above
                    if (replay.ReplayBuild > 51609 || replay.ReplayBuild == 47903 || replay.ReplayBuild == 47479)
                    {
                        bitReader.ReadBitArray(bitReader.ReadBits(12));
                    }
                    else if (replay.ReplayBuild > 47219)
                    {
                        // each byte has a max value of 0x7F (127)
                        bitReader.ReadUnalignedBytes((int)bitReader.ReadBits(15) * 2);
                    }
                    else
                    {
                        bitReader.ReadBitArray(bitReader.ReadBits(9));
                    }

                    bitReader.ReadBoolean();
                }

                bool isSilenced = bitReader.ReadBoolean(); // m_hasSilencePenalty
                if (player.PlayerType == PlayerType.Observer)
                {
                    player.IsSilenced = isSilenced;
                }

                if (replay.ReplayBuild >= 61718)
                {
                    bitReader.ReadBoolean();
                    bool isVoiceSilenced = bitReader.ReadBoolean(); // m_hasVoiceSilencePenalty
                    if (player.PlayerType == PlayerType.Observer)
                    {
                        player.IsVoiceSilenced = isVoiceSilenced;
                    }
                }

                if (replay.ReplayBuild >= 66977)
                {
                    bool isBlizzardStaff = bitReader.ReadBoolean(); // m_isBlizzardStaff
                    if (player.PlayerType == PlayerType.Observer)
                    {
                        player.IsBlizzardStaff = isBlizzardStaff;
                    }
                }

                if (bitReader.ReadBoolean())                        // is player in party
                {
                    player.PartyValue = bitReader.ReadLongBits(64); // players in same party will have the same exact 8 bytes of data
                }
                bitReader.ReadBoolean();

                string battleTagName = bitReader.ReadBlobAsString(7);
                int    poundIndex    = battleTagName.IndexOf('#');

                if (!string.IsNullOrEmpty(battleTagName) && poundIndex < 0)
                {
                    throw new StormParseException($"{_exceptionHeader}: Invalid battletag");
                }

                ReadOnlySpan <char> namePart = battleTagName.AsSpan().Slice(0, poundIndex);
                if (!namePart.SequenceEqual(player.Name))
                {
                    throw new StormParseException($"{_exceptionHeader}: Mismatch on battletag name with player name");
                }

                player.BattleTagName = battleTagName;

                if (replay.ReplayBuild >= 52860 || (replay.ReplayVersion.Major == 2 && replay.ReplayBuild >= 51978))
                {
                    player.AccountLevel = (int)bitReader.ReadBits(32);  // in custom games, this is a 0
                }
                if (replay.ReplayBuild >= 69947)
                {
                    bool hasActiveBoost = bitReader.ReadBoolean(); // m_hasActiveBoost
                    if (player.PlayerType == PlayerType.Observer)
                    {
                        player.HasActiveBoost = hasActiveBoost;
                    }
                }
            }
        }
Example #8
0
        public static void Parse(StormReplay replay, ReadOnlySpan <byte> source)
        {
            if (source.Length <= 1)
            {
                return;
            }

            BitReader bitReader = new BitReader(source, EndianType.BigEndian);

            uint ticksElapsed = 0;

            while (bitReader.Index < source.Length)
            {
                ticksElapsed += bitReader.ReadBits(6 + ((int)bitReader.ReadBits(2) << 3));
                TimeSpan timeStamp = TimeSpan.FromSeconds(ticksElapsed / 16.0);

                int playerIndex = (int)bitReader.ReadBits(5);

                StormMessageEventType messageEventType = (StormMessageEventType)bitReader.ReadBits(4);

                StormMessage?message = null;

                switch (messageEventType)
                {
                case StormMessageEventType.SChatMessage:
                    ChatMessage chatMessage = new ChatMessage
                    {
                        MessageTarget = (StormMessageTarget)bitReader.ReadBits(3), // m_recipient (the target)
                        Message       = bitReader.ReadBlobAsString(11),            // m_string
                    };

                    message = new StormMessage(chatMessage);

                    break;

                case StormMessageEventType.SPingMessage:
                    PingMessage pingMessage = new PingMessage()
                    {
                        MessageTarget = (StormMessageTarget)bitReader.ReadBits(3),                                                                                                   // m_recipient (the target)
                        Point         = new Point((double)(bitReader.ReadInt32Unaligned() - (-2147483648)) / 4096, ((double)bitReader.ReadInt32Unaligned() - (-2147483648)) / 4096), // m_point x and m_point y
                    };

                    message = new StormMessage(pingMessage);

                    break;

                case StormMessageEventType.SLoadingProgressMessage:
                    LoadingProgressMessage loadingProgressMessage = new LoadingProgressMessage()
                    {
                        LoadingProgress = bitReader.ReadInt32Unaligned() - (-2147483648),     // m_progress
                    };

                    message = new StormMessage(loadingProgressMessage);

                    break;

                case StormMessageEventType.SServerPingMessage:
                    break;

                case StormMessageEventType.SReconnectNotifyMessage:
                    bitReader.ReadBits(2);     // m_status; is either a 1 or a 2
                    break;

                case StormMessageEventType.SPlayerAnnounceMessage:
                    PlayerAnnounceMessage playerAnnounceMessage = new PlayerAnnounceMessage()
                    {
                        AnnouncementType = (AnnouncementType)bitReader.ReadBits(2),
                    };

                    switch (playerAnnounceMessage.AnnouncementType)
                    {
                    case AnnouncementType.None:
                        break;

                    case AnnouncementType.Ability:
                        int abilityLink  = bitReader.ReadInt16Unaligned();     // m_abilLink
                        int abilityIndex = (int)bitReader.ReadBits(5);         // m_abilCmdIndex
                        int buttonLink   = bitReader.ReadInt16Unaligned();     // m_buttonLink
                        playerAnnounceMessage.AbilityAnnouncement = new AbilityAnnouncement(abilityIndex, abilityLink, buttonLink);

                        break;

                    case AnnouncementType.Behavior:
                        bitReader.ReadInt16Unaligned();         // m_behaviorLink
                        bitReader.ReadInt16Unaligned();         // m_buttonLink
                        break;

                    case AnnouncementType.Vitals:
                        playerAnnounceMessage.VitalAnnouncement = new VitalAnnouncement((VitalType)(bitReader.ReadInt16Unaligned() - (-32768)));
                        break;

                    default:
                        throw new NotImplementedException();
                    }

                    if (replay.ReplayBuild > 45635)
                    {
                        // m_announceLink
                        bitReader.ReadInt16Unaligned();
                    }

                    bitReader.ReadInt32Unaligned();     // m_otherUnitTag
                    bitReader.ReadInt32Unaligned();     // m_unitTag

                    message = new StormMessage(playerAnnounceMessage);
                    break;

                default:
                    throw new NotImplementedException();
                }

                if (message != null)
                {
                    message.MessageEventType = messageEventType;
                    message.Timestamp        = timeStamp;

                    if (playerIndex != 16)
                    {
                        message.MessageSender = replay.ClientListByUserID[playerIndex];
                    }

                    replay.MessagesInternal.Add(message);
                }

                bitReader.AlignToByte();
            }
        }
        /// <summary> Parses the Replay.Messages.Events file. </summary>
        /// <param name="buffer"> Buffer containing the contents of the replay.messages.events file. </param>
        /// <returns> A list of messages parsed from the buffer. </returns>
        public static void Parse(Replay replay, byte[] buffer)
        {
            if (buffer.Length <= 1)
            {
                // Chat has been removed from this replay
                return;
            }

            var ticksElapsed = 0;

            using (var stream = new MemoryStream(buffer))
            {
                var bitReader = new BitReader(stream);

                while (!bitReader.EndOfStream)
                {
                    var message = new Message();

                    ticksElapsed     += (int)bitReader.Read(6 + (bitReader.Read(2) << 3));
                    message.Timestamp = new TimeSpan(0, 0, (int)Math.Round(ticksElapsed / 16.0));

                    var playerIndex = (int)bitReader.Read(5);
                    if (playerIndex != 16)
                    {
                        message.MessageSender = replay.ClientListByUserID[playerIndex];
                        message.PlayerIndex   = playerIndex;
                    }

                    message.MessageEventType = (MessageEventType)bitReader.Read(4);
                    switch (message.MessageEventType)
                    {
                    case MessageEventType.SChatMessage:
                    {
                        var chatMessage = new ChatMessage
                        {
                            MessageTarget = (MessageTarget)bitReader.Read(3),                                 // m_recipient (the target)
                            Message       = Encoding.UTF8.GetString(bitReader.ReadBlobPrecededWithLength(11)) // m_string
                        };

                        message.ChatMessage = chatMessage;
                        replay.Messages.Add(message);
                        break;
                    }

                    case MessageEventType.SPingMessage:
                    {
                        var pingMessage = new PingMessage
                        {
                            MessageTarget = (MessageTarget)bitReader.Read(3),               // m_recipient (the target)
                            XCoordinate   = bitReader.ReadInt32() - (-2147483648),          // m_point x
                            YCoordinate   = bitReader.ReadInt32() - (-2147483648)           // m_point y
                        };

                        message.PingMessage = pingMessage;
                        replay.Messages.Add(message);
                        break;
                    }

                    case MessageEventType.SLoadingProgressMessage:
                    {
                        // can be used to keep track of how fast/slow players are loading
                        // also includes players who are reloading the game
                        var progress = bitReader.ReadInt32() - (-2147483648);         // m_progress
                        break;
                    }

                    case MessageEventType.SServerPingMessage:
                    {
                        break;
                    }

                    case MessageEventType.SReconnectNotifyMessage:
                    {
                        bitReader.Read(2);         // m_status; is either a 1 or a 2
                        break;
                    }

                    case MessageEventType.SPlayerAnnounceMessage:
                    {
                        var announceMessage = new PlayerAnnounceMessage {
                            AnnouncementType = (AnnouncementType)bitReader.Read(2)
                        };

                        switch (announceMessage.AnnouncementType)
                        {
                        case AnnouncementType.None:
                        {
                            break;
                        }

                        case AnnouncementType.Ability:
                        {
                            var ability = new AbilityAnnouncment {
                                AbilityLink  = bitReader.ReadInt16(),                  // m_abilLink
                                AbilityIndex = (int)bitReader.Read(5),                 // m_abilCmdIndex
                                ButtonLink   = bitReader.ReadInt16()                   // m_buttonLink
                            };

                            announceMessage.AbilityAnnouncement = ability;
                            break;
                        }

                        case AnnouncementType.Behavior:            // no idea what triggers this
                        {
                            bitReader.ReadInt16();                 // m_behaviorLink
                            bitReader.ReadInt16();                 // m_buttonLink
                            break;
                        }

                        case AnnouncementType.Vitals:
                        {
                            var vital = new VitalAnnouncment {
                                VitalType = (VitalType)(bitReader.ReadInt16() - (-32768))
                            };

                            announceMessage.VitalAnnouncement = vital;
                            break;
                        }

                        default:
                            throw new NotImplementedException();
                        }

                        if (replay.ReplayBuild > 45635)
                        {
                            // m_announceLink
                            bitReader.ReadInt16();
                        }

                        bitReader.ReadInt32();         // m_otherUnitTag
                        bitReader.ReadInt32();         // m_unitTag

                        message.PlayerAnnounceMessage = announceMessage;
                        replay.Messages.Add(message);
                        break;
                    }

                    default:
                        throw new NotImplementedException();
                    }

                    bitReader.AlignToByte();
                }
            }
        }