From d86469266b063cc71e600d3e0f8a461f188785c3 Mon Sep 17 00:00:00 2001 From: Kurt Date: Fri, 8 Mar 2024 21:31:17 -0600 Subject: [PATCH] Enhance bv5 checksum documentation add for gen4 so there's no more mystery on that change span to memory for direct injecting to a bv obj --- PKHeX.Core/Saves/SAV5.cs | 8 +-- PKHeX.Core/Saves/SAV5B2W2.cs | 8 +-- PKHeX.Core/Saves/SAV5BW.cs | 8 +-- .../Battle Videos/BattleVideo4.cs | 39 ++++++++++--- .../Battle Videos/BattleVideo5.cs | 57 +++++++++++++++---- 5 files changed, 90 insertions(+), 30 deletions(-) diff --git a/PKHeX.Core/Saves/SAV5.cs b/PKHeX.Core/Saves/SAV5.cs index 86c2686b6..eaa81bcc4 100644 --- a/PKHeX.Core/Saves/SAV5.cs +++ b/PKHeX.Core/Saves/SAV5.cs @@ -193,10 +193,10 @@ public byte[] CGearSkinData public abstract AdventureInfo5 AdventureInfo { get; } IEventFlag37 IEventFlagProvider37.EventWork => EventWork; - public abstract Span BattleVideoNative { get; } - public abstract Span BattleVideoDownload1 { get; } - public abstract Span BattleVideoDownload2 { get; } - public abstract Span BattleVideoDownload3 { get; } + public abstract Memory BattleVideoNative { get; } + public abstract Memory BattleVideoDownload1 { get; } + public abstract Memory BattleVideoDownload2 { get; } + public abstract Memory BattleVideoDownload3 { get; } protected override byte[] GetFinalData() { diff --git a/PKHeX.Core/Saves/SAV5B2W2.cs b/PKHeX.Core/Saves/SAV5B2W2.cs index dc0c611c9..38c13dd48 100644 --- a/PKHeX.Core/Saves/SAV5B2W2.cs +++ b/PKHeX.Core/Saves/SAV5B2W2.cs @@ -73,8 +73,8 @@ public Span Rival_Trash set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(Data.AsSpan(0x23BA4)); } } - public override Span BattleVideoNative => Data.AsSpan(0x4C000, BattleVideo5.SIZE_USED); - public override Span BattleVideoDownload1 => Data.AsSpan(0x4DA00, BattleVideo5.SIZE_USED); - public override Span BattleVideoDownload2 => Data.AsSpan(0x4F400, BattleVideo5.SIZE_USED); - public override Span BattleVideoDownload3 => Data.AsSpan(0x50E00, BattleVideo5.SIZE_USED); + public override Memory BattleVideoNative => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED); + public override Memory BattleVideoDownload1 => Data.AsMemory(0x4DA00, BattleVideo5.SIZE_USED); + public override Memory BattleVideoDownload2 => Data.AsMemory(0x4F400, BattleVideo5.SIZE_USED); + public override Memory BattleVideoDownload3 => Data.AsMemory(0x50E00, BattleVideo5.SIZE_USED); } diff --git a/PKHeX.Core/Saves/SAV5BW.cs b/PKHeX.Core/Saves/SAV5BW.cs index ebfc57a11..47e100980 100644 --- a/PKHeX.Core/Saves/SAV5BW.cs +++ b/PKHeX.Core/Saves/SAV5BW.cs @@ -57,8 +57,8 @@ private void Initialize() public override WhiteBlack5BW Forest => Blocks.Forest; public override AdventureInfo5 AdventureInfo => Blocks.AdventureInfo; - public override Span BattleVideoNative => Data.AsSpan(0x4A000, BattleVideo5.SIZE_USED); - public override Span BattleVideoDownload1 => Data.AsSpan(0x4C000, BattleVideo5.SIZE_USED); - public override Span BattleVideoDownload2 => Data.AsSpan(0x4E000, BattleVideo5.SIZE_USED); - public override Span BattleVideoDownload3 => Data.AsSpan(0x50000, BattleVideo5.SIZE_USED); + public override Memory BattleVideoNative => Data.AsMemory(0x4A000, BattleVideo5.SIZE_USED); + public override Memory BattleVideoDownload1 => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED); + public override Memory BattleVideoDownload2 => Data.AsMemory(0x4E000, BattleVideo5.SIZE_USED); + public override Memory BattleVideoDownload3 => Data.AsMemory(0x50000, BattleVideo5.SIZE_USED); } diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo4.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo4.cs index 23138525e..89bd85890 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo4.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo4.cs @@ -19,9 +19,10 @@ public sealed class BattleVideo4(Memory Raw) : IBattleVideo private Span Data => Raw.Span[..SIZE_USED]; public bool IsDecrypted; - private const int CryptoStart = 0xE8; - private const int CryptoEnd = 0x1D4C; - private Span CryptoData => Data[CryptoStart..CryptoEnd]; + private Span CryptoData => Data[0xE8..0x1D4C]; + private Span Footer => Data.Slice(SIZE, SIZE_FOOTER); + + public Span DecryptedChecksumRegion => CryptoData; // ccitt16 -> 0x1D4C public byte Generation => 4; public IEnumerable Contents => GetTeam(0).Concat(GetTeam(1)); // don't bother with multi-battles @@ -50,6 +51,8 @@ public sealed class BattleVideo4(Memory Raw) : IBattleVideo public const int SizeVideoPoke = 0x70; public uint Key { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); } + + /// public ushort Seed { get => ReadUInt16LittleEndian(Data[0x1D4C..]); set => WriteUInt16LittleEndian(Data[0x1D4C..], value); } public void Decrypt() => SetDecryptedState(true); @@ -70,7 +73,7 @@ public uint GetEncryptionSeed() #region Footer public bool SizeValid => BlockSize == SIZE; - public bool ChecksumValid => Checksum == GetChecksum(); + public bool ChecksumValid => Checksum == CalculateFooterChecksum(); public uint Magic { get => ReadUInt32LittleEndian(Footer); set => WriteUInt32LittleEndian(Footer, value); } public uint Revision { get => ReadUInt32LittleEndian(Footer[0x4..]); set => WriteUInt32LittleEndian(Footer[0x4..], value); } @@ -78,10 +81,30 @@ public uint GetEncryptionSeed() public ushort BlockID { get => ReadUInt16LittleEndian(Footer[0xC..]); set => WriteUInt16LittleEndian(Footer[0xC..], value); } public ushort Checksum { get => ReadUInt16LittleEndian(Footer[0xE..]); set => WriteUInt16LittleEndian(Footer[0xE..], value); } - private ReadOnlySpan GetRegion() => Data[..(SIZE + SIZE_FOOTER)]; - private Span Footer => Data.Slice(SIZE, SIZE_FOOTER); - private ushort GetChecksum() => Checksums.CRC16_CCITT(GetRegion()[..^2]); - public void RefreshChecksum() => Checksum = GetChecksum(); + private ushort CalculateFooterChecksum() + { + if (IsDecrypted) + throw new InvalidOperationException("Cannot calculate checksum when decrypted."); + return Checksums.CRC16_CCITT(Data[..^2]); + } + + private ushort CalculateDecryptedChecksum() + { + if (!IsDecrypted) + throw new InvalidOperationException("Cannot calculate checksum when encrypted."); + return Checksums.CRC16_CCITT(DecryptedChecksumRegion); + } + + public void RefreshChecksums() + { + var state = IsDecrypted; + Decrypt(); + Seed = CalculateDecryptedChecksum(); + Encrypt(); + Checksum = CalculateFooterChecksum(); + SetDecryptedState(state); + } + #endregion #region Conversion diff --git a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs index f95a21ea8..0d1dac5eb 100644 --- a/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs +++ b/PKHeX.Core/Saves/Substructures/Battle Videos/BattleVideo5.cs @@ -19,11 +19,14 @@ public sealed class BattleVideo5(Memory Raw) : IBattleVideo private Span Data => Raw.Span[..SIZE_USED]; public bool IsDecrypted; - private const int CryptoStart = 0xC4; - private const int CryptoEnd = 0x18A0; - private Span CryptoData => Data[CryptoStart..CryptoEnd]; + private Span CryptoData => Data[0xC4..0x18A0]; + private Span Footer => Data.Slice(SIZE, SIZE_FOOTER); - public byte Generation => 4; + public Span DecryptedChecksumRegion => CryptoData; // ccitt16 -> 0x18A0 + public Span EncryptedChecksumRegion => Data[..0x18A4]; // ccitt16 -> 0x18A6 + public Span FooterChecksumRegion => Footer[..4]; // ccitt16 -> 0x12 + + public byte Generation => 5; public IEnumerable Contents => GetTeam(0).Concat(GetTeam(1)); // don't bother with multi-battles public static bool IsValid(ReadOnlySpan data) => data.Length == SIZE_USED && ReadUInt32LittleEndian(data[0x1908..]) == SIZE_USED; @@ -47,7 +50,13 @@ public sealed class BattleVideo5(Memory Raw) : IBattleVideo public const int SizeVideoPoke = 0x70; public uint Key { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); } + + /// public ushort Seed { get => ReadUInt16LittleEndian(Data[0x18A0..]); set => WriteUInt16LittleEndian(Data[0x18A0..], value); } + // 0000: ^ seed is truncated to u16. + public ushort EncryptedCount { get => ReadUInt16LittleEndian(Data[0x18A4..]); set => WriteUInt16LittleEndian(Data[0x18A4..], value); } + public ushort EncryptedChecksum { get => ReadUInt16LittleEndian(Data[0x18A6..]); set => WriteUInt16LittleEndian(Data[0x18A6..], value); } + // 0xFF padding(?) to 0x1900 public void Decrypt() => SetDecryptedState(true); public void Encrypt() => SetDecryptedState(false); @@ -71,15 +80,43 @@ public uint GetEncryptionSeed() #region Footer public bool SizeValid => BlockSize == SIZE; - public bool ChecksumValid => Checksum == GetChecksum(); + public bool ChecksumValid => FooterChecksum == CalculateFooterChecksum(); + public ushort EncryptedChecksumCopy { get => ReadUInt16LittleEndian(Footer); set => WriteUInt16LittleEndian(Footer, value); } + // 0000 + public uint FooterCount { get => ReadUInt32LittleEndian(Footer[0x4..]); set => WriteUInt32LittleEndian(Footer[0x4..], value); } public int BlockSize { get => ReadInt32LittleEndian(Footer[0x8..]); set => WriteInt32LittleEndian(Footer[0x8..], value); } - public ushort Checksum { get => ReadUInt16LittleEndian(Footer[0x12..]); set => WriteUInt16LittleEndian(Footer[0x12..], value); } + public uint BlockID { get => ReadUInt32LittleEndian(Footer[0xC..]); set => WriteUInt32LittleEndian(Footer[0xC..], value); } + // 0000 + public ushort FooterChecksum { get => ReadUInt16LittleEndian(Footer[0x12..]); set => WriteUInt16LittleEndian(Footer[0x12..], value); } + + private ushort CalculateEncryptedChecksum() + { + if (IsDecrypted) + throw new InvalidOperationException("Cannot calculate checksum when decrypted."); + return Checksums.CRC16_CCITT(EncryptedChecksumRegion); + } + + private ushort CalculateDecryptedChecksum() + { + if (!IsDecrypted) + throw new InvalidOperationException("Cannot calculate checksum when encrypted."); + return Checksums.CRC16_CCITT(DecryptedChecksumRegion); + } + + private ushort CalculateFooterChecksum() => Checksums.CRC16_CCITT(FooterChecksumRegion); + + public void RefreshChecksums() + { + var state = IsDecrypted; + Decrypt(); + Seed = CalculateDecryptedChecksum(); + Encrypt(); + EncryptedChecksum = CalculateEncryptedChecksum(); + FooterChecksum = CalculateFooterChecksum(); + SetDecryptedState(state); + } - private ReadOnlySpan GetRegion() => Data[..SIZE_USED]; - private Span Footer => Data.Slice(SIZE, SIZE_FOOTER); - private ushort GetChecksum() => Checksums.CRC16_CCITT(GetRegion()[..^2]); - public void RefreshChecksum() => Checksum = GetChecksum(); #endregion #region Conversion