Example #1
0
        public ReplayParamsBlock(BinaryReader br, MajorVersion gameMajorVersion)
        {
            this.startFrame = br.ReadUInt32();
            this.endFrame   = br.ReadUInt32();

            this.probablyExpectedDurationMs = br.ReadInt64();
            Debug.Assert(this.probablyExpectedDurationMs == 12000);

            this.startMs = br.ReadInt64();
            this.endMs   = br.ReadInt64();

            // Matches are unlikely to last longer than an hour so this should be a reasonable assert
            Debug.Assert(this.startFrame <= 3600 * 60);
            Debug.Assert(this.endFrame <= 3600 * 60);
            Debug.Assert(this.startMs <= 3600 * 1000);
            Debug.Assert(this.endMs <= 3600 * 1000);

            int numHeroes = br.ReadInt32();

            //Debug.Assert(numHeroes <= 1); // this assert fails on endless junkenstein events

            this.heroesWithUnlockables = new HeroWithUnlockables[numHeroes];
            for (int i = 0; i < numHeroes; ++i)
            {
                this.heroesWithUnlockables[i] = new HeroWithUnlockables(br, gameMajorVersion);
            }
        }
        public Replay(BinaryReader br, MajorVersion gameMajorVersion)
        {
            uint magic = br.ReadUInt24();

            Debug.Assert(magic == MAGIC_CONSTANT);

            byte formatVersion = br.ReadByte();

            Debug.Assert(formatVersion == 3 || formatVersion == 4 || formatVersion == 5 || formatVersion == 7);

            this.buildNumber = new BuildNumber(br);
            Debug.Assert(this.buildNumber.IsKnownByTool(), $"Build number {buildNumber} is not known by tool");

            this.map = br.ReadMap64();

            this.gameMode = br.ReadGameMode64();

            byte unknown1;

            if (formatVersion >= 5)
            {
                byte[] unknownBytes = br.ReadBytes(0x28);

                if (formatVersion >= 7)
                {
                    // seems to be per-match, might be a match ID?
                    ulong unknown2 = br.ReadUInt64();
                    Debug.Assert((unknown2 >> 56) == 0x00);
                }

                unknown1 = br.ReadByte();
                Debug.Assert(unknown1 == 0xB || unknown1 == 0xF);
            }
            else
            {
                unknown1 = br.ReadByte();
                Debug.Assert(unknown1 == 0xB || unknown1 == 0xF);

                uint unknown2 = br.ReadUInt32();
                Debug.Assert(unknown2 == 0x10 || unknown2 == 0x30);

                uint unknown3 = br.ReadUInt32();
                Debug.Assert(unknown3 == 0x10 || unknown3 == 0x30);
            }

            this.mapChecksum = new Checksum(br);
            Debug.Assert(MapChecksumDB.IsValidChecksumForMap(map, mapChecksum));

            int paramsBlockLength = br.ReadInt32();

            using (DebugBlockLength dbl = new DebugBlockLength(paramsBlockLength, br))
            {
                this.paramsBlock = new ReplayParamsBlock(br, gameMajorVersion);
            }

            if ((unknown1 & 0x4) != 0)
            {
                int highlightInfoLength = br.ReadInt32();
                using (DebugBlockLength dbl = new DebugBlockLength(highlightInfoLength, br))
                {
                    this.highlightInfo = new HighlightInfo(br, gameMajorVersion);
                }
            }

            byte[] compressedBuffer   = br.ReadBytes((int)(br.BaseStream.Length - br.BaseStream.Position));
            byte[] decompressedBuffer = Decompress(compressedBuffer);

#if DEBUG_OUTPUT_DECOMPRESSED_REPLAYDATA
            if (formatVersion >= 7)
            {
                string filename = br.GetFilename();
                File.WriteAllBytes("decompressed/" + filename + ".bin", decompressedBuffer);
            }
#endif

            bool readExtraPrefixBit = formatVersion >= 7;
            using (BinaryReader br2 = decompressedBuffer.CreateBinaryReader())
            {
                this.replayFrames = new List <ReplayFrame>();
                for (int frameIndex = 0; br2.BaseStream.Position < br2.BaseStream.Length; ++frameIndex)
                {
                    this.replayFrames.Add(new ReplayFrame(br2, mapChecksum, readExtraPrefixBit));
                }
            }

            int diffMs           = (int)(this.paramsBlock.endMs - this.paramsBlock.startMs);
            int durationNoFrame0 = (int)Math.Round(this.TotalDurationWithoutFirstFrame() * 1000);
            Debug.Assert(Math.Abs(durationNoFrame0 - diffMs) <= 1);

            if (buildNumber < 40407)             // TODO: work out why this assertion fails on some halloween replays
            {
                Debug.Assert(this.paramsBlock.startFrame == (this.replayFrames[0].ticker1 & 0x7fffffff) - 2);
                Debug.Assert(this.paramsBlock.endFrame == this.replayFrames[this.replayFrames.Count - 1].ticker1 - 2);
            }
            for (int i = 1; i < this.replayFrames.Count; ++i)
            {
                var lastFrame = this.replayFrames[i - 1];
                var thisFrame = this.replayFrames[i];
                Debug.Assert((thisFrame.ticker1 & 0x7fffffff) - (lastFrame.ticker1 & 0x7fffffff) <= 3);
                Debug.Assert(
                    thisFrame.ticker2 - lastFrame.ticker2 <= 5 &&
                    thisFrame.ticker2 - lastFrame.ticker2 >= 0);
            }
        }
Example #3
0
        public HighlightInfo(BinaryReader br, MajorVersion gameMajorVersion)
        {
            // player name of the highlight protagonist (will differ for other people's POTGs)
            this.playerName = br.ReadNullPaddedUTF8();

            // wheeeeeeee
            if (gameMajorVersion >= new MajorVersion(1, 16, VersionBranch.None))
            {
                byte unknown1 = br.ReadByte();
                Debug.Assert(unknown1 == 0);
            }

            // 1 for potg, 0 for top5 highlight, 4 for manual highlight
            this.typeFlags = (HighlightTypeFlag)br.ReadByte();
            Debug.Assert(Extensions.AreAllFlagsDefined(typeFlags));

            this.unknown2 = br.ReadUInt32();
            Debug.Assert((unknown2 & 0x80000000u) == 0x80000000u);
            Debug.Assert((unknown2 & 0x7FFFFFFFu) <= 0x0000FFFFu);

            this.unknown3 = br.ReadUInt32();
            Debug.Assert((unknown3 & 0x80000000u) == 0x80000000u);
            Debug.Assert((unknown3 & 0x7FFFFFFFu) <= 0x0000FFFFu);

            Debug.Assert(unknown3 >= unknown2);

            this.unknown4 = br.ReadSingle();
            Debug.Assert(!float.IsInfinity(unknown4) && !float.IsNaN(unknown4));

            this.unknown5 = br.ReadSingle();
            Debug.Assert(!float.IsInfinity(unknown5) && !float.IsNaN(unknown5));

            // seems to be nonzero if the player starts the highlight in the air - elevation?
            this.unknown6 = br.ReadUInt32();

            if (gameMajorVersion >= new MajorVersion(1, 17, VersionBranch.None))
            {
                // 0 for defense team, 1 for attack team. not sure how this handles oasis/nepal/ilios/lijiangtower yet
                this.unknown7 = br.ReadUInt32();
                Debug.Assert(unknown7 == 0 || unknown7 == 1 || unknown7 == 4);
            }

            this.highlightIntroPosition = br.ReadVec3();
            Debug.Assert(highlightIntroPosition.IsFinite());

            this.highlightIntroDirection = br.ReadVec3();
            Debug.Assert(highlightIntroDirection.IsFinite());
            Debug.Assert(highlightIntroDirection.IsUnitVector());

            this.upVector = br.ReadVec3();
            Debug.Assert(upVector.IsFinite());
            Debug.Assert(upVector.IsUnitVector());
            Debug.Assert(Math.Round(upVector.x) == 0);
            Debug.Assert(Math.Round(upVector.y) == 1);
            Debug.Assert(Math.Round(upVector.z) == 0);

            this.hero       = br.ReadHero64();
            this.skin       = br.ReadSkin64();
            this.weaponSkin = br.ReadWeaponSkin64();

            // at a hazy guess these might be to do with the new country-specific skins for the world cup?
            if (gameMajorVersion >= new MajorVersion(1, 17, VersionBranch.None))
            {
                this.unknown8 = br.ReadUInt64();
                Debug.Assert(unknown8 == 0);
                this.unknown9 = br.ReadUInt64();
                Debug.Assert(unknown9 == 0);
            }

            this.highlightIntro = br.ReadHighlightIntro64();

            this.category = br.ReadHighlightCategory64();

            this.timestamp = br.ReadUInt64();
            this.uuid      = new HighlightUUID(br);

            UnlockValidator.RunForHeroWithUnlocks(hero, skin, weaponSkin, highlightIntro);

            Debug.Assert(this.unknown3 >= this.unknown2);
        }
        public Highlight(BinaryReader br)
        {
            var filename = br.GetFilename();

            uint magic = br.ReadUInt24();

            Debug.Assert(magic == MAGIC_CONSTANT);

            byte formatVersion = br.ReadByte();

            Debug.Assert(formatVersion == 3 || formatVersion == 4);

            this.checksum = new Checksum(br);

            int dataLength = br.ReadInt32();

            Debug.Assert(br.BaseStream.Position + dataLength == br.BaseStream.Length);

            if (Checksum.CanCompute)
            {
                long   pos           = br.BaseStream.Position;
                byte[] checksumInput = br.ReadBytes(dataLength);
                br.BaseStream.Position = pos;
                Checksum computedChecksum = Checksum.Compute(checksumInput);
                Debug.Assert(this.checksum == computedChecksum);
            }

            using (DebugBlockLength dbl = new DebugBlockLength(dataLength, br))
            {
                uint unknown1 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown1 == 0);

                uint unknown2 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown2 == 0);

                uint unknown3 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown3 == 0);

                uint unknown4 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown4 == 0);

                uint unknown5 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown5 == 0);

                uint unknown6 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown6 == 0);

                uint unknown7 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown7 == 0);

                uint unknown8 = br.ReadUInt32();                    // 0?
                Debug.Assert(unknown8 == 0);

                this.majorVersion = new MajorVersion(br);
                Debug.Assert(this.majorVersion.IsKnownByTool(), $"Unknown major version {majorVersion}");

                this.buildNumber = new BuildNumber(br);
                Debug.Assert(buildNumber.IsKnownByTool(), $"Unknown build number {buildNumber}");

                this.playerId = br.ReadUInt32();                    // player id of the logged in user

                uint unknown12 = br.ReadUInt32();                   // 0?
                Debug.Assert(unknown12 == 0);

                this.uiFlags = (UIFlags)br.ReadUInt32();
                Debug.Assert(((uint)uiFlags & 0xFFFFFFF0u) == 0);                                                 // assume only the bottom four bits will be set...
                Debug.Assert(uiFlags.HasFlag(UIFlags.ManualHighlight) != uiFlags.HasFlag(UIFlags.Top5Highlight)); // exactly one of these will be set

                this.map = br.ReadMap64();

                this.gameMode = br.ReadGameMode64();

                if (formatVersion >= 4)
                {
                    this.v4_unknown1 = br.ReadUInt32();
                    Debug.Assert(this.v4_unknown1 == 0);

                    this.v4_unknown2 = br.ReadUInt32();
                    Debug.Assert(this.v4_unknown2 == 0);
                }

                // 2 entries will be a POTG, 1 entry will be either a highlight or a POTG against bots, I think...
                int numHighlightInfos = br.ReadInt32();
                Debug.Assert(numHighlightInfos == 1 || numHighlightInfos == 2);

                this.highlightInfos = new HighlightInfo[numHighlightInfos];
                for (int i = 0; i < numHighlightInfos; ++i)
                {
                    highlightInfos[i] = new HighlightInfo(br, this.majorVersion);
                }
                Debug.Assert(br.GetFilename().StartsWith(highlightInfos[0].uuid.ToString()));

                int numHeroes = br.ReadInt32();

                this.heroesWithUnlockables = new HeroWithUnlockables[numHeroes];
                for (int i = 0; i < numHeroes; ++i)
                {
                    heroesWithUnlockables[i] = new HeroWithUnlockables(br, majorVersion);
                }

                // I have absolutely no idea what this section is for but it seems to be entirely predictable *shrug*
                {
                    this.unknown60 = br.ReadUInt32();
                    Debug.Assert(unknown60 == 0);

                    this.unknown61 = br.ReadUInt32();
                    Debug.Assert(unknown61 == 0);

                    byte unknown62 = br.ReadByte();

                    /*Debug.Assert(
                     *      unknown62 == 0x00 ||
                     *      unknown62 == 0x01 ||
                     *      unknown62 == 0x07 ||
                     *      unknown62 == 0x0a ||
                     *      unknown62 == 0x11 ||
                     *      unknown62 == 0x1c ||
                     *      unknown62 == 0x27 ||
                     *      unknown62 == 0x2a ||
                     *      unknown62 == 0x36 ||
                     *      unknown62 == 0x4e ||
                     *      unknown62 == 0x61 ||
                     *      unknown62 == 0x80 ||
                     *      unknown62 == 0xc1 ||
                     *      unknown62 == 0xc7 ||
                     *      unknown62 == 0xf7 ||
                     *      unknown62 == 0xff);*/

                    int fillerCount = (unknown62 & 1);
                    this.fillerStructs = new FillerStruct[fillerCount];
                    for (int i = 0; i < fillerCount; ++i)
                    {
                        this.fillerStructs[i] = new FillerStruct(br);
                    }
                }

                int replayDataLength = br.ReadInt32();
                Debug.Assert(br.BaseStream.Position + replayDataLength == br.BaseStream.Length);

                replayBlock = new Replay(br, this.majorVersion);
            }

            Debug.Assert(
                replayBlock.buildNumber == this.buildNumber ||
                (this.buildNumber == 38044 && replayBlock.buildNumber == 38024) ||
                (this.buildNumber == 38459 && replayBlock.buildNumber == 38510) ||
                (this.buildNumber == 38459 && replayBlock.buildNumber == 38679) ||
                (this.buildNumber == 38765 && replayBlock.buildNumber == 38679) ||
                (this.buildNumber == 38459 && replayBlock.buildNumber == 38765) ||
                (this.buildNumber == 39023 && replayBlock.buildNumber == 38882) ||
                (this.buildNumber == 39023 && replayBlock.buildNumber == 39221) ||
                (this.buildNumber == 39358 && replayBlock.buildNumber == 39221) ||
                (this.buildNumber == 39484 && replayBlock.buildNumber == 39425) ||
                (this.buildNumber == 39484 && replayBlock.buildNumber == 39572) ||
                (this.buildNumber == 39484 && replayBlock.buildNumber == 39775) ||
                (this.buildNumber == 39935 && replayBlock.buildNumber == 39823) ||
                (this.buildNumber == 40048 && replayBlock.buildNumber == 39974) ||
                (this.buildNumber == 40133 && replayBlock.buildNumber == 39974) ||
                (this.buildNumber == 40990 && replayBlock.buildNumber == 40763) ||
                (this.buildNumber == 41714 && replayBlock.buildNumber == 41350) ||
                (this.buildNumber == 41713 && replayBlock.buildNumber == 41835) ||
                (this.buildNumber == 42665 && replayBlock.buildNumber == 42563) ||
                (this.buildNumber == 45752 && replayBlock.buildNumber == 45876) ||
                (this.buildNumber == 51948 && replayBlock.buildNumber == 51830)
                // I've no idea what's up with all these weird permutations...
                );
            Debug.Assert(replayBlock.map == this.map);
            Debug.Assert(replayBlock.gameMode == this.gameMode);
            if (replayBlock.highlightInfo != null)
            {
                //Debug.Assert(replayBlock.highlightInfo == highlightInfos[0]);
                Debug.Assert(HighlightInfo.EqualWithTypeMasking(replayBlock.highlightInfo, highlightInfos[0]));
            }

            if (this.highlightInfos[0].typeFlags.HasFlag(HighlightInfo.HighlightTypeFlag.Manual))
            {
                Debug.Assert(this.highlightInfos[0].unknown4 == 0);
            }
            else
            {
                Debug.Assert(this.highlightInfos[0].unknown4 != 0);
                if (this.highlightInfos.Length > 1)
                {
                    Debug.Assert(this.highlightInfos[0].unknown4 >= this.highlightInfos[1].unknown4);
                }
            }
            Debug.Assert(this.highlightInfos[0].unknown5 > this.replayBlock.paramsBlock.startMs / 1000.0f);
            Debug.Assert(this.highlightInfos[0].unknown5 < this.replayBlock.paramsBlock.endMs / 1000.0f);

            if (!this.highlightInfos[0].typeFlags.HasFlag(HighlightInfo.HighlightTypeFlag.POTG))
            {
                if (uiFlags.HasFlag(UIFlags.ManualHighlight))
                {
                    Debug.Assert(this.highlightInfos[0].typeFlags.HasFlag(HighlightInfo.HighlightTypeFlag.Manual));
                }
                else
                {
                    Debug.Assert(
                        this.highlightInfos[0].typeFlags.HasFlag(HighlightInfo.HighlightTypeFlag.Top5) ||
                        this.highlightInfos[0].typeFlags.HasFlag(HighlightInfo.HighlightTypeFlag.Unknown_10)
                        );
                }
            }


            if (this.highlightInfos[0].typeFlags.HasFlag(HighlightInfo.HighlightTypeFlag.Manual))
            {
                Debug.Assert(this.highlightInfos[0].category == HighlightCategory.None);
            }
            else
            {
                Debug.Assert(
                    this.highlightInfos[0].category == HighlightCategory.HighScore ||
                    this.highlightInfos[0].category == HighlightCategory.Lifesaver ||
                    this.highlightInfos[0].category == HighlightCategory.Sharpshooter ||
                    this.highlightInfos[0].category == HighlightCategory.Shutdown
                    );
            }
        }
        public HeroWithUnlockables(BinaryReader br, MajorVersion gameMajorVersion)
        {
            this.skin       = br.ReadSkin32();
            this.weaponSkin = br.ReadWeaponSkin32();

            this.highlightIntro = br.ReadHighlightIntro32();

            int numSprays = br.ReadInt32();

            this.sprays = br.ReadSpray32s(numSprays);

            int numVoiceLines = br.ReadInt32();

            this.voiceLines = br.ReadVoiceLine32s(numVoiceLines);

            int numEmotes = br.ReadInt32();

            this.emotes = br.ReadEmote32s(numEmotes);

            // might be to do with team affiliation? seems to be either 0 or 1 depending on team - not sure about -1??
            if (gameMajorVersion >= new MajorVersion(1, 17, VersionBranch.None))
            {
                this.unknownInV17 = br.ReadInt32();
                Debug.Assert(unknownInV17 == -1 || unknownInV17 == 0 || unknownInV17 == 1 || unknownInV17 == 4);
            }

            this.hero = br.ReadHero64();

            // AI players in custom matches have no HighlightIntro, no unlocks, and always use classic skins
            // Bots in the Junkenstein/Uprising modes sometimes use non-classic skins.
            if (highlightIntro == HighlightIntro.None)
            {
                Debug.Assert(this.sprays.Length == 0);
                Debug.Assert(this.voiceLines.Length == 0);
                Debug.Assert(this.emotes.Length == 0);
                Debug.Assert(
                    ($"{skin}" == $"{hero}_Classic") ||
                    (hero == Hero.Junkrat && skin == Skin.Junkrat_DrJunkenstein) ||
                    (hero == Hero.Mercy && skin == Skin.Mercy_Witch) ||
                    (hero == Hero.Reaper && skin == Skin.Reaper_Dracula) ||
                    (hero == Hero.Roadhog && skin == Skin.Roadhog_JunkensteinsMonster) ||
                    (hero == Hero.Orisa && skin == Skin.Orisa_NullSector) ||
                    (hero == Hero.UprisingEvent_00000173 && skin == Skin.UprisingEvent_00000173_Classic) ||
                    (hero == Hero.UprisingEvent_00000178 && skin == Skin.UprisingEvent_00000178_Classic) ||
                    (hero == Hero.UprisingEvent_00000179 && skin == Skin.UprisingEvent_00000179_Classic) ||
                    (hero == Hero.RetributionEvent_000001AC && skin == Skin.RetributionEvent_000001AC_Classic) ||
                    (hero == Hero.RetributionEvent_000001B8 && skin == Skin.RetributionEvent_000001B8_Classic) ||
                    (hero == Hero.RetributionEvent_000001BA && skin == Skin.RetributionEvent_000001BA_Classic) ||
                    (hero == Hero.RetributionEvent_000001BB && skin == Skin.RetributionEvent_000001BB_Classic) ||
                    (hero == Hero.RetributionEvent_000001CE && skin == Skin.RetributionEvent_000001CE_Classic)
                    );
                Debug.Assert(
                    ($"{weaponSkin}" == $"{hero}_Classic") ||
                    (hero == Hero.Junkrat && weaponSkin == WeaponSkin.Junkrat_DrJunkenstein) ||
                    (hero == Hero.Mercy && weaponSkin == WeaponSkin.Mercy_Witch) ||
                    (hero == Hero.Reaper && weaponSkin == WeaponSkin.Reaper_Dracula) ||
                    (hero == Hero.Roadhog && weaponSkin == WeaponSkin.Roadhog_JunkensteinsMonster) ||
                    (hero == Hero.Orisa && weaponSkin == WeaponSkin.Orisa_NullSector) ||
                    (hero == Hero.UprisingEvent_00000173 && weaponSkin == WeaponSkin.UprisingEvent_00000173_Classic) ||
                    (hero == Hero.UprisingEvent_00000178 && weaponSkin == WeaponSkin.UprisingEvent_00000178_Classic) ||
                    (hero == Hero.UprisingEvent_00000179 && weaponSkin == WeaponSkin.UprisingEvent_00000179_Classic) ||
                    (hero == Hero.RetributionEvent_000001AC && weaponSkin == WeaponSkin.RetributionEvent_000001AC_Classic) ||
                    (hero == Hero.RetributionEvent_000001B8 && weaponSkin == WeaponSkin.RetributionEvent_000001B8_Classic) ||
                    (hero == Hero.RetributionEvent_000001BA && weaponSkin == WeaponSkin.RetributionEvent_000001BA_Classic) ||
                    (hero == Hero.RetributionEvent_000001BB && weaponSkin == WeaponSkin.RetributionEvent_000001BB_Classic) ||
                    (hero == Hero.RetributionEvent_000001CE && weaponSkin == WeaponSkin.RetributionEvent_000001CE_Classic)
                    );
            }

            // Ensure that unlocks are correctly mapped to heroes
            UnlockValidator.RunForHeroWithUnlocks(hero, skin, weaponSkin, highlightIntro, sprays, emotes, voiceLines);
        }