From 977e977dbfe8bfd3e7cb7159f0f00f81dfcc742e Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 26 Mar 2022 13:28:29 -0700 Subject: [PATCH] Rewrite GeniusCrypto (XD/Batrev) More clear usage of span slightly less allocation+copying more comments for future readers --- PKHeX.Core/Saves/Encryption/GeniusCrypto.cs | 54 ++++----- PKHeX.Core/Saves/SAV3XD.cs | 52 ++++---- PKHeX.Core/Saves/SAV4BR.cs | 126 +++++++++++--------- 3 files changed, 128 insertions(+), 104 deletions(-) diff --git a/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs b/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs index 2d07ec5e0..bee5e68b9 100644 --- a/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs +++ b/PKHeX.Core/Saves/Encryption/GeniusCrypto.cs @@ -8,50 +8,48 @@ namespace PKHeX.Core /// public static class GeniusCrypto { - public static byte[] Decrypt(ReadOnlySpan input, int start, int end, Span keys) + public static void ReadKeys(ReadOnlySpan input, Span keys) { - var output = input.ToArray(); - Decrypt(input, start, end, keys, output); - return output; + for (int i = 0; i < keys.Length; i++) + keys[i] = ReadUInt16BigEndian(input[(i * 2)..]); } - public static void Decrypt(ReadOnlySpan input, int start, int end, Span keys, Span output) + public static void Decrypt(ReadOnlySpan input, Span output, Span keys) { - for (int ofs = start; ofs < end; ofs += 8) + if (keys.Length != 4) + throw new ArgumentOutOfRangeException(nameof(keys)); + + int i = 0; + do { - for (int i = 0; i < keys.Length; i++) + foreach (var key in keys) { - var index = ofs + (i * 2); - ushort val = ReadUInt16BigEndian(input[index..]); - val -= keys[i]; - WriteUInt16BigEndian(output[index..], val); + ushort value = ReadUInt16BigEndian(input[i..]); + value -= key; + WriteUInt16BigEndian(output[i..], value); + i += 2; } - AdvanceKeys(keys); - } + } while (i != input.Length); } - public static byte[] Encrypt(ReadOnlySpan input, int start, int end, Span keys) + public static void Encrypt(ReadOnlySpan input, Span output, Span keys) { - var output = input.ToArray(); - Encrypt(input, start, end, keys, output); - return output; - } + if (keys.Length != 4) + throw new ArgumentOutOfRangeException(nameof(keys)); - public static void Encrypt(ReadOnlySpan input, int start, int end, Span keys, Span output) - { - for (int ofs = start; ofs < end; ofs += 8) + int i = 0; + do { - for (int i = 0; i < keys.Length; i++) + foreach (var key in keys) { - var index = ofs + (i * 2); - ushort val = ReadUInt16BigEndian(input[index..]); - val += keys[i]; - WriteUInt16BigEndian(output[index..], val); + ushort value = ReadUInt16BigEndian(input[i..]); + value += key; + WriteUInt16BigEndian(output[i..], value); + i += 2; } - AdvanceKeys(keys); - } + } while (i != input.Length); } private static void AdvanceKeys(Span keys) diff --git a/PKHeX.Core/Saves/SAV3XD.cs b/PKHeX.Core/Saves/SAV3XD.cs index 4b2d9f3ab..b67c02000 100644 --- a/PKHeX.Core/Saves/SAV3XD.cs +++ b/PKHeX.Core/Saves/SAV3XD.cs @@ -64,16 +64,7 @@ private void InitializeData(out StrategyMemo memo, out ShadowInfoTableXD info) } // Decrypt most recent save slot - { - int slotOffset = SLOT_START + (SaveIndex * SLOT_SIZE); - ReadOnlySpan slot = Data.AsSpan(slotOffset, SLOT_SIZE); - Span keys = stackalloc ushort[4]; - for (int i = 0; i < keys.Length; i++) - keys[i] = ReadUInt16BigEndian(slot[(8 + (i * 2))..]); - - // Decrypt Slot - Data = GeniusCrypto.Decrypt(slot, 0x00010, 0x27FD8, keys); - } + Data = ReadSlot(Data, SaveIndex); // Get Offset Info Span subLength = stackalloc ushort[16]; @@ -97,6 +88,23 @@ private void InitializeData(out StrategyMemo memo, out ShadowInfoTableXD info) info = new ShadowInfoTableXD(Data.AsSpan(Shadow, subLength[7]), jp); } + private static byte[] ReadSlot(Span data, int index) + { + int slotOffset = SLOT_START + (index * SLOT_SIZE); + var slot = data.Slice(slotOffset, SLOT_SIZE); + var result = new byte[SLOT_SIZE]; + var destSpan = result.AsSpan(); + + // Decrypt Slot + Span keys = stackalloc ushort[4]; + GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys); + Range r = new(0x10, 0x27FD8); + GeniusCrypto.Decrypt(slot[r], destSpan[r], keys); // body + slot[..0x10].CopyTo(destSpan[..0x10]); // checksums + slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end + return result; + } + private void Initialize() { OFS_PouchHeldItem = Trainer1 + 0x4C8; @@ -135,16 +143,20 @@ private byte[] GetInnerData() ShadowInfo.Write().CopyTo(Data, Shadow); SetChecksums(); - // Get updated save slot data - Span keys = stackalloc ushort[4]; - for (int i = 0; i < keys.Length; i++) - keys[i] = ReadUInt16BigEndian(Data.AsSpan(8 + (i * 2))); - byte[] newSAV = GeniusCrypto.Encrypt(Data, 0x10, 0x27FD8, keys); - // Put save slot back in original save data - byte[] newFile = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[]) BAK.Clone(); - Array.Copy(newSAV, 0, newFile, SLOT_START + (SaveIndex * SLOT_SIZE), newSAV.Length); - return newFile; + var destOffset = SLOT_START + (SaveIndex * SLOT_SIZE); + byte[] dest = MemoryCard != null ? MemoryCard.ReadSaveGameData() : (byte[])BAK.Clone(); + var destSpan = dest.AsSpan(destOffset, Data.Length); + + // Get updated save slot data + Span slot = Data; + Span keys = stackalloc ushort[4]; + GeniusCrypto.ReadKeys(slot.Slice(8, keys.Length * 2), keys); + Range r = new(0x10, 0x27FD8); + GeniusCrypto.Encrypt(slot[r], destSpan[r], keys); + slot[..0x10].CopyTo(destSpan[..0x10]); // checksum/keys + slot[^0x18..].CopyTo(destSpan[^0x18..]); // tail end + return dest; } // Configuration @@ -219,7 +231,7 @@ private static byte[] SetChecksums(byte[] input, int subOffset0) WriteInt32BigEndian(data.AsSpan(start + subOffset0 + 0x38), newHC); // Body Checksum - data.AsSpan(0x10, 0x10).Fill(0); // Clear old Checksum Data + data.AsSpan(0x10, 0x10).Clear(); // Clear old Checksum Data Span checksum = stackalloc uint[4]; int dt = 8; for (int i = 0; i < checksum.Length; i++) diff --git a/PKHeX.Core/Saves/SAV4BR.cs b/PKHeX.Core/Saves/SAV4BR.cs index ac0a432fa..2b36e4122 100644 --- a/PKHeX.Core/Saves/SAV4BR.cs +++ b/PKHeX.Core/Saves/SAV4BR.cs @@ -15,6 +15,7 @@ public sealed class SAV4BR : SaveFile public override IReadOnlyList HeldItems => Legal.HeldItems_DP; private const int SAVE_COUNT = 4; + public const int SIZE_HALF = 0x1C0000; public SAV4BR() : base(SaveUtil.SIZE_G4BR) { @@ -37,10 +38,10 @@ private void InitializeData(ReadOnlySpan data) if (second > first) { // swap halves - byte[] tempData = new byte[0x1C0000]; - Array.Copy(Data, 0, tempData, 0, 0x1C0000); - Array.Copy(Data, 0x1C0000, Data, 0, 0x1C0000); - tempData.CopyTo(Data, 0x1C0000); + byte[] tempData = new byte[SIZE_HALF]; + Array.Copy(Data, 0, tempData, 0, SIZE_HALF); + Array.Copy(Data, SIZE_HALF, Data, 0, SIZE_HALF); + tempData.CopyTo(Data, SIZE_HALF); } var names = (string[]) SaveNames; @@ -134,10 +135,10 @@ protected set // Checksums protected override void SetChecksums() { - SetChecksum(Data, 0, 0x100, 8); - SetChecksum(Data, 0, 0x1C0000, 0x1BFF80); - SetChecksum(Data, 0x1C0000, 0x100, 0x1C0008); - SetChecksum(Data, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000); + SetChecksum(Data, 0x0000000, 0x0000100, 0x000008); + SetChecksum(Data, 0x0000000, SIZE_HALF, SIZE_HALF - 0x80); + SetChecksum(Data, SIZE_HALF, 0x0000100, SIZE_HALF + 0x000008); + SetChecksum(Data, SIZE_HALF, SIZE_HALF, SIZE_HALF + SIZE_HALF - 0x80); } public override bool ChecksumsValid => IsChecksumsValid(Data); @@ -145,10 +146,10 @@ protected override void SetChecksums() public static bool IsChecksumsValid(Span sav) { - return VerifyChecksum(sav, 0x000000, 0x1C0000, 0x1BFF80) - && VerifyChecksum(sav, 0x000000, 0x000100, 0x000008) - && VerifyChecksum(sav, 0x1C0000, 0x1C0000, 0x1BFF80 + 0x1C0000) - && VerifyChecksum(sav, 0x1C0000, 0x000100, 0x1C0008); + return VerifyChecksum(sav, 0x0000000, 0x0000100, 0x000008) + && VerifyChecksum(sav, 0x0000000, SIZE_HALF, SIZE_HALF - 0x80) + && VerifyChecksum(sav, SIZE_HALF, 0x0000100, SIZE_HALF + 0x000008) + && VerifyChecksum(sav, SIZE_HALF, SIZE_HALF, SIZE_HALF + SIZE_HALF - 0x80); } // Trainer Info @@ -253,11 +254,21 @@ public static byte[] DecryptPBRSaveData(ReadOnlySpan input) { byte[] output = new byte[input.Length]; Span keys = stackalloc ushort[4]; - for (int i = 0; i < SaveUtil.SIZE_G4BR; i += 0x1C0000) + for (int offset = 0; offset < SaveUtil.SIZE_G4BR; offset += SIZE_HALF) { - ReadKeys(input, i, keys); - input.Slice(i, 8).CopyTo(output.AsSpan(i, 8)); - GeniusCrypto.Decrypt(input, i + 8, i + 0x1C0000, keys, output); + var inSlice = input.Slice(offset, SIZE_HALF); + var outSlice = output.AsSpan(offset, SIZE_HALF); + + // First 8 bytes are the encryption keys for this chunk. + var keySlice = inSlice[..(keys.Length * 2)]; + GeniusCrypto.ReadKeys(keySlice, keys); + + // Copy over the keys to the result. + keySlice.CopyTo(outSlice); + + // Decrypt the input, result stored in output. + Range r = new(8, SIZE_HALF); + GeniusCrypto.Decrypt(inSlice[r], outSlice[r], keys); } return output; } @@ -266,76 +277,79 @@ private static byte[] EncryptPBRSaveData(ReadOnlySpan input) { byte[] output = new byte[input.Length]; Span keys = stackalloc ushort[4]; - for (int i = 0; i < SaveUtil.SIZE_G4BR; i += 0x1C0000) + for (int offset = 0; offset < SaveUtil.SIZE_G4BR; offset += SIZE_HALF) { - ReadKeys(input, i, keys); - input.Slice(i, 8).CopyTo(output.AsSpan(i, 8)); - GeniusCrypto.Encrypt(input, i + 8, i + 0x1C0000, keys, output); + var inSlice = input.Slice(offset, SIZE_HALF); + var outSlice = output.AsSpan(offset, SIZE_HALF); + + // First 8 bytes are the encryption keys for this chunk. + var keySlice = inSlice[..(keys.Length * 2)]; + GeniusCrypto.ReadKeys(keySlice, keys); + + // Copy over the keys to the result. + keySlice.CopyTo(outSlice); + + // Decrypt the input, result stored in output. + Range r = new(8, SIZE_HALF); + GeniusCrypto.Encrypt(inSlice[r], outSlice[r], keys); } return output; } - private static void ReadKeys(ReadOnlySpan input, int ofs, Span keys) - { - for (int i = 0; i < keys.Length; i++) - keys[i] = ReadUInt16BigEndian(input[(ofs + (i * 2))..]); - } - public static bool VerifyChecksum(Span input, int offset, int len, int checksum_offset) { + // Read original checksum data, and clear it for recomputing Span originalChecksums = stackalloc uint[16]; + var checkSpan = input.Slice(checksum_offset, 4 * originalChecksums.Length); for (int i = 0; i < originalChecksums.Length; i++) { - var chk = input.Slice(checksum_offset + (i * 4), 4); + var chk = checkSpan.Slice(i * 4, 4); originalChecksums[i] = ReadUInt32BigEndian(chk); - chk.Clear(); } + checkSpan.Clear(); + // Compute current checksum of the specified span Span checksums = stackalloc uint[16]; var span = input.Slice(offset, len); - for (int i = 0; i < span.Length; i += 2) - { - uint val = ReadUInt16BigEndian(span[i..]); - for (int j = 0; j < 16; j++) - checksums[j] += ((val >> j) & 1); - } + ComputeChecksums(span, checksums); // Restore original checksums - for (int i = 0; i < originalChecksums.Length; i++) - { - var chk = originalChecksums[i]; - var dest = input[(checksum_offset + (i * 4))..]; - WriteUInt32BigEndian(dest, chk); - } + WriteChecksums(checkSpan, originalChecksums); // Check if they match - for (int i = 0; i < originalChecksums.Length; i++) - { - if (originalChecksums[i] != checksums[i]) - return false; - } - return true; + return checksums.SequenceEqual(originalChecksums); } private static void SetChecksum(Span input, int offset, int len, int checksum_offset) { // Wipe Checksum region. - input.Slice(checksum_offset, 4 * 16).Clear(); + var checkSpan = input.Slice(checksum_offset, 4 * 16); + checkSpan.Clear(); + // Compute current checksum of the specified span Span checksums = stackalloc uint[16]; var span = input.Slice(offset, len); - for (int i = 0; i < len; i += 2) - { - uint val = ReadUInt16BigEndian(span[i..]); - for (int j = 0; j < 16; j++) - checksums[j] += ((val >> j) & 1); - } + ComputeChecksums(span, checksums); + WriteChecksums(checkSpan, checksums); + } + + private static void WriteChecksums(Span span, Span checksums) + { for (int i = 0; i < checksums.Length; i++) { - var chk = checksums[i]; - var dest = input[(checksum_offset + (i * 4))..]; - WriteUInt32BigEndian(dest, chk); + var dest = span[(i * 4)..]; + WriteUInt32BigEndian(dest, checksums[i]); + } + } + + private static void ComputeChecksums(Span span, Span checksums) + { + for (int i = 0; i < span.Length; i += 2) + { + uint value = ReadUInt16BigEndian(span[i..]); + for (int c = 0; c < checksums.Length; c++) + checksums[c] += ((value >> c) & 1); } }