mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-23 12:33:06 +00:00
Rewrite GeniusCrypto (XD/Batrev)
More clear usage of span slightly less allocation+copying more comments for future readers
This commit is contained in:
parent
8bf618008a
commit
977e977dbf
3 changed files with 128 additions and 104 deletions
|
@ -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)
|
||||
|
|
|
@ -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++)
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue