Rewrite GeniusCrypto (XD/Batrev)

More clear usage of span
slightly less allocation+copying
more comments for future readers
This commit is contained in:
Kurt 2022-03-26 13:28:29 -07:00
parent 8bf618008a
commit 977e977dbf
3 changed files with 128 additions and 104 deletions

View file

@ -8,50 +8,48 @@ namespace PKHeX.Core
/// </summary>
public static class GeniusCrypto
{
public static byte[] Decrypt(ReadOnlySpan<byte> input, int start, int end, Span<ushort> keys)
public static void ReadKeys(ReadOnlySpan<byte> input, Span<ushort> 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<byte> input, int start, int end, Span<ushort> keys, Span<byte> output)
public static void Decrypt(ReadOnlySpan<byte> input, Span<byte> output, Span<ushort> 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<byte> input, int start, int end, Span<ushort> keys)
public static void Encrypt(ReadOnlySpan<byte> input, Span<byte> output, Span<ushort> 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<byte> input, int start, int end, Span<ushort> keys, Span<byte> 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<ushort> keys)

View file

@ -64,16 +64,7 @@ namespace PKHeX.Core
}
// Decrypt most recent save slot
{
int slotOffset = SLOT_START + (SaveIndex * SLOT_SIZE);
ReadOnlySpan<byte> slot = Data.AsSpan(slotOffset, SLOT_SIZE);
Span<ushort> 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<ushort> subLength = stackalloc ushort[16];
@ -97,6 +88,23 @@ namespace PKHeX.Core
info = new ShadowInfoTableXD(Data.AsSpan(Shadow, subLength[7]), jp);
}
private static byte[] ReadSlot(Span<byte> 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<ushort> 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 @@ namespace PKHeX.Core
ShadowInfo.Write().CopyTo(Data, Shadow);
SetChecksums();
// Get updated save slot data
Span<ushort> 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<byte> slot = Data;
Span<ushort> 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 @@ namespace PKHeX.Core
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<uint> checksum = stackalloc uint[4];
int dt = 8;
for (int i = 0; i < checksum.Length; i++)

View file

@ -15,6 +15,7 @@ namespace PKHeX.Core
public override IReadOnlyList<ushort> 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 @@ namespace PKHeX.Core
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 @@ namespace PKHeX.Core
// 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 @@ namespace PKHeX.Core
public static bool IsChecksumsValid(Span<byte> 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 @@ namespace PKHeX.Core
{
byte[] output = new byte[input.Length];
Span<ushort> 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 @@ namespace PKHeX.Core
{
byte[] output = new byte[input.Length];
Span<ushort> 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<byte> input, int ofs, Span<ushort> keys)
{
for (int i = 0; i < keys.Length; i++)
keys[i] = ReadUInt16BigEndian(input[(ofs + (i * 2))..]);
}
public static bool VerifyChecksum(Span<byte> input, int offset, int len, int checksum_offset)
{
// Read original checksum data, and clear it for recomputing
Span<uint> 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<uint> 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<byte> 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<uint> 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<byte> span, Span<uint> 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<byte> span, Span<uint> 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);
}
}