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
This commit is contained in:
Kurt 2024-03-08 21:31:17 -06:00
parent 994c063537
commit d86469266b
5 changed files with 90 additions and 30 deletions

View file

@ -193,10 +193,10 @@ public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBox
public abstract AdventureInfo5 AdventureInfo { get; }
IEventFlag37 IEventFlagProvider37.EventWork => EventWork;
public abstract Span<byte> BattleVideoNative { get; }
public abstract Span<byte> BattleVideoDownload1 { get; }
public abstract Span<byte> BattleVideoDownload2 { get; }
public abstract Span<byte> BattleVideoDownload3 { get; }
public abstract Memory<byte> BattleVideoNative { get; }
public abstract Memory<byte> BattleVideoDownload1 { get; }
public abstract Memory<byte> BattleVideoDownload2 { get; }
public abstract Memory<byte> BattleVideoDownload3 { get; }
protected override byte[] GetFinalData()
{

View file

@ -73,8 +73,8 @@ public sealed class SAV5B2W2 : SAV5, ISaveBlock5B2W2
set { if (value.Length == MaxStringLengthOT * 2) value.CopyTo(Data.AsSpan(0x23BA4)); }
}
public override Span<byte> BattleVideoNative => Data.AsSpan(0x4C000, BattleVideo5.SIZE_USED);
public override Span<byte> BattleVideoDownload1 => Data.AsSpan(0x4DA00, BattleVideo5.SIZE_USED);
public override Span<byte> BattleVideoDownload2 => Data.AsSpan(0x4F400, BattleVideo5.SIZE_USED);
public override Span<byte> BattleVideoDownload3 => Data.AsSpan(0x50E00, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoNative => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoDownload1 => Data.AsMemory(0x4DA00, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoDownload2 => Data.AsMemory(0x4F400, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoDownload3 => Data.AsMemory(0x50E00, BattleVideo5.SIZE_USED);
}

View file

@ -57,8 +57,8 @@ public sealed class SAV5BW : SAV5
public override WhiteBlack5BW Forest => Blocks.Forest;
public override AdventureInfo5 AdventureInfo => Blocks.AdventureInfo;
public override Span<byte> BattleVideoNative => Data.AsSpan(0x4A000, BattleVideo5.SIZE_USED);
public override Span<byte> BattleVideoDownload1 => Data.AsSpan(0x4C000, BattleVideo5.SIZE_USED);
public override Span<byte> BattleVideoDownload2 => Data.AsSpan(0x4E000, BattleVideo5.SIZE_USED);
public override Span<byte> BattleVideoDownload3 => Data.AsSpan(0x50000, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoNative => Data.AsMemory(0x4A000, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoDownload1 => Data.AsMemory(0x4C000, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoDownload2 => Data.AsMemory(0x4E000, BattleVideo5.SIZE_USED);
public override Memory<byte> BattleVideoDownload3 => Data.AsMemory(0x50000, BattleVideo5.SIZE_USED);
}

View file

@ -19,9 +19,10 @@ public sealed class BattleVideo4(Memory<byte> Raw) : IBattleVideo
private Span<byte> Data => Raw.Span[..SIZE_USED];
public bool IsDecrypted;
private const int CryptoStart = 0xE8;
private const int CryptoEnd = 0x1D4C;
private Span<byte> CryptoData => Data[CryptoStart..CryptoEnd];
private Span<byte> CryptoData => Data[0xE8..0x1D4C];
private Span<byte> Footer => Data.Slice(SIZE, SIZE_FOOTER);
public Span<byte> DecryptedChecksumRegion => CryptoData; // ccitt16 -> 0x1D4C
public byte Generation => 4;
public IEnumerable<PKM> Contents => GetTeam(0).Concat(GetTeam(1)); // don't bother with multi-battles
@ -50,6 +51,8 @@ public sealed class BattleVideo4(Memory<byte> Raw) : IBattleVideo
public const int SizeVideoPoke = 0x70;
public uint Key { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
/// <summary> <see cref="CalculateDecryptedChecksum"/> </summary>
public ushort Seed { get => ReadUInt16LittleEndian(Data[0x1D4C..]); set => WriteUInt16LittleEndian(Data[0x1D4C..], value); }
public void Decrypt() => SetDecryptedState(true);
@ -70,7 +73,7 @@ public sealed class BattleVideo4(Memory<byte> Raw) : IBattleVideo
#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 sealed class BattleVideo4(Memory<byte> Raw) : IBattleVideo
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<byte> GetRegion() => Data[..(SIZE + SIZE_FOOTER)];
private Span<byte> 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

View file

@ -19,11 +19,14 @@ public sealed class BattleVideo5(Memory<byte> Raw) : IBattleVideo
private Span<byte> Data => Raw.Span[..SIZE_USED];
public bool IsDecrypted;
private const int CryptoStart = 0xC4;
private const int CryptoEnd = 0x18A0;
private Span<byte> CryptoData => Data[CryptoStart..CryptoEnd];
private Span<byte> CryptoData => Data[0xC4..0x18A0];
private Span<byte> Footer => Data.Slice(SIZE, SIZE_FOOTER);
public byte Generation => 4;
public Span<byte> DecryptedChecksumRegion => CryptoData; // ccitt16 -> 0x18A0
public Span<byte> EncryptedChecksumRegion => Data[..0x18A4]; // ccitt16 -> 0x18A6
public Span<byte> FooterChecksumRegion => Footer[..4]; // ccitt16 -> 0x12
public byte Generation => 5;
public IEnumerable<PKM> Contents => GetTeam(0).Concat(GetTeam(1)); // don't bother with multi-battles
public static bool IsValid(ReadOnlySpan<byte> data) => data.Length == SIZE_USED && ReadUInt32LittleEndian(data[0x1908..]) == SIZE_USED;
@ -47,7 +50,13 @@ public sealed class BattleVideo5(Memory<byte> Raw) : IBattleVideo
public const int SizeVideoPoke = 0x70;
public uint Key { get => ReadUInt32LittleEndian(Data); set => WriteUInt32LittleEndian(Data, value); }
/// <summary> <see cref="CalculateDecryptedChecksum"/> </summary>
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 sealed class BattleVideo5(Memory<byte> Raw) : IBattleVideo
#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<byte> GetRegion() => Data[..SIZE_USED];
private Span<byte> Footer => Data.Slice(SIZE, SIZE_FOOTER);
private ushort GetChecksum() => Checksums.CRC16_CCITT(GetRegion()[..^2]);
public void RefreshChecksum() => Checksum = GetChecksum();
#endregion
#region Conversion