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); } }
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); }