Minor perf tweaks

Relocate checksum adders to Checksums class, improve performance by eliminating slice calls
Improve HOME sharing for GO8 and IMoveset encounters: actually sanity check GO8, and skip other non-PK7/PB7 cases.
This commit is contained in:
Kurt 2023-07-15 11:22:48 -07:00
parent 2aef48172e
commit 18fd790657
25 changed files with 247 additions and 176 deletions

View file

@ -3,7 +3,7 @@ namespace PKHeX.Core;
/// <summary>
/// Ability IDs for the corresponding English ability name.
/// </summary>
public enum Ability
public enum Ability : ushort
{
None,
Stench,

View file

@ -197,14 +197,14 @@ public sealed class LearnGroup6 : ILearnGroup
private static void FlagEncounterMoves(IEncounterTemplate enc, Span<bool> result)
{
if (enc is IMoveset { Moves: { Move1: not 0 } x })
if (enc is IMoveset { Moves: { HasMoves: true } x })
{
result[x.Move4] = true;
result[x.Move3] = true;
result[x.Move2] = true;
result[x.Move1] = true;
}
if (enc is IRelearn { Relearn: {Move1: not 0} r})
if (enc is IRelearn { Relearn: { HasMoves: true } r})
{
result[r.Move4] = true;
result[r.Move3] = true;

View file

@ -27,7 +27,7 @@ public sealed class LearnGroup8a : ILearnGroup
var home = LearnGroupHOME.Instance;
if (option != LearnOption.HOME && home.HasVisited(pk, history))
return home.Check(result, current, pk, history, enc, types);
return home.Check(result, current, pk, history, enc, types, option);
return false;
}

View file

@ -32,7 +32,7 @@ public sealed class LearnGroup8b : ILearnGroup
var home = LearnGroupHOME.Instance;
if (option != LearnOption.HOME && home.HasVisited(pk, history))
return home.Check(result, current, pk, history, enc, types);
return home.Check(result, current, pk, history, enc, types, option);
return false;
}

View file

@ -54,13 +54,14 @@ public sealed class LearnGroupHOME : ILearnGroup
if (CleanPurge(result, current, pk, types, local, evos))
return true;
}
if (!history.HasVisitedSWSH && enc is EncounterSlot8GO { OriginFormat: PogoImportFormat.PK7 or PogoImportFormat.PB7 } g8)
if (TryAddOriginalMoves(result, current, pk, enc))
{
if (TryAddOriginalMovesGO(g8, result, current, pk.Met_Level))
if (CleanPurge(result, current, pk, types, local, evos))
return true;
}
// Ignore Battle Version; can be transferred back to SW/SH and wiped after the moves have been shared from HOME
if (history.HasVisitedLGPE)
{
var instance = LearnGroup7b.Instance;
@ -85,21 +86,6 @@ public sealed class LearnGroupHOME : ILearnGroup
return false;
}
private static bool TryAddOriginalMovesGO(EncounterSlot8GO enc, Span<MoveResult> result, ReadOnlySpan<ushort> current, int met)
{
Span<ushort> moves = stackalloc ushort[4];
enc.GetInitialMoves(met, moves);
foreach (var move in moves)
{
if (move == 0)
break;
var index = current.IndexOf(move);
if (index != -1)
result[index] = new MoveResult(LearnMethod.Shared, LearnEnvironment.HOME);
}
return MoveResult.AllParsed(result);
}
/// <summary>
/// Scan the results and remove any that are not valid for the game <see cref="local"/> game.
/// </summary>
@ -140,19 +126,6 @@ public sealed class LearnGroupHOME : ILearnGroup
return MoveResult.AllParsed(result);
}
/// <summary>
/// Get the current HOME source for the given context.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private static IHomeSource GetCurrent(EntityContext context) => context switch
{
EntityContext.Gen8 => LearnSource8SWSH.Instance,
EntityContext.Gen8a => LearnSource8LA.Instance,
EntityContext.Gen8b => LearnSource8BDSP.Instance,
EntityContext.Gen9 => LearnSource9SV.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null),
};
public void GetAllMoves(Span<bool> result, PKM pk, EvolutionHistory history, IEncounterTemplate enc,
MoveSourceType types = MoveSourceType.All, LearnOption option = LearnOption.HOME)
{
@ -169,8 +142,7 @@ public sealed class LearnGroupHOME : ILearnGroup
RentLoopGetAll(LearnGroup8a.Instance, result, pk, history, enc, types, option, evos, local);
if (history.HasVisitedBDSP && pk is not PB8)
RentLoopGetAll(LearnGroup8b.Instance, result, pk, history, enc, types, option, evos, local);
if (enc is EncounterSlot8GO { OriginFormat: PogoImportFormat.PK7 or PogoImportFormat.PB7 } g8)
AddOriginalMovesGO(g8, result, enc.LevelMin);
AddOriginalMoves(result, pk, enc, types, local, evos);
// Looking backwards before HOME
if (history.HasVisitedLGPE)
@ -191,6 +163,52 @@ public sealed class LearnGroupHOME : ILearnGroup
}
}
private static bool TryAddOriginalMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, PKM pk, IEncounterTemplate enc)
{
if (enc is IMoveset { Moves: { HasMoves: true } x })
{
Span<ushort> moves = stackalloc ushort[4];
x.CopyTo(moves);
return AddOriginalMoves(result, current, moves, enc.Generation);
}
if (enc is EncounterSlot8GO { OriginFormat: PogoImportFormat.PK7 or PogoImportFormat.PB7 } g8)
{
Span<ushort> moves = stackalloc ushort[4];
g8.GetInitialMoves(pk.Met_Level, moves);
return AddOriginalMoves(result, current, moves, g8.Generation);
}
return false;
}
private static void AddOriginalMoves(Span<bool> result, PKM pk, IEncounterTemplate enc, MoveSourceType types, IHomeSource local, ReadOnlySpan<EvoCriteria> evos)
{
if (enc is IMoveset { Moves: { HasMoves: true } x })
{
Span<ushort> moves = stackalloc ushort[4];
x.CopyTo(moves);
AddOriginalMoves(result, pk, evos, types, local, moves);
}
else if (enc is EncounterSlot8GO { OriginFormat: PogoImportFormat.PK7 or PogoImportFormat.PB7 } g8)
{
Span<ushort> moves = stackalloc ushort[4];
g8.GetInitialMoves(pk.Met_Level, moves);
AddOriginalMoves(result, pk, evos, types, local, moves);
}
}
/// <summary>
/// Get the current HOME source for the given context.
/// </summary>
/// <exception cref="ArgumentOutOfRangeException"></exception>
private static IHomeSource GetCurrent(EntityContext context) => context switch
{
EntityContext.Gen8 => LearnSource8SWSH.Instance,
EntityContext.Gen8a => LearnSource8LA.Instance,
EntityContext.Gen8b => LearnSource8BDSP.Instance,
EntityContext.Gen9 => LearnSource9SV.Instance,
_ => throw new ArgumentOutOfRangeException(nameof(context), context, null),
};
private static void RentLoopGetAll<T>(T instance, Span<bool> result, PKM pk, EvolutionHistory history,
IEncounterTemplate enc,
MoveSourceType types, LearnOption option, ReadOnlySpan<EvoCriteria> evos, IHomeSource local) where T : ILearnGroup
@ -234,13 +252,44 @@ public sealed class LearnGroupHOME : ILearnGroup
}
}
private static void AddOriginalMovesGO(EncounterSlot8GO enc, Span<bool> result, int met)
private static void AddOriginalMoves(Span<bool> result, PKM pk, ReadOnlySpan<EvoCriteria> evos, MoveSourceType types, IHomeSource dest, ReadOnlySpan<ushort> moves)
{
Span<ushort> moves = stackalloc ushort[4];
enc.GetInitialMoves(met, moves);
foreach (var move in moves)
result[move] = true;
{
if (move == 0)
break;
if (move >= result.Length)
continue;
if (result[move])
continue; // already possible to learn in current game
foreach (var evo in evos)
{
var chk = dest.GetCanLearnHOME(pk, evo, move, types);
if (chk.Method == LearnMethod.None)
continue;
result[move] = true;
break;
}
}
}
private static bool IsPikachuLine(ushort species) => species is (int)Species.Raichu or (int)Species.Pikachu or (int)Species.Pichu;
private static bool AddOriginalMoves(Span<MoveResult> result, ReadOnlySpan<ushort> current, Span<ushort> moves, int generation)
{
bool addedAny = false;
foreach (var move in moves)
{
if (move == 0)
break;
var index = current.IndexOf(move);
if (index == -1)
continue;
if (result[index].Valid)
continue;
result[index] = MoveResult.Initial with { Generation = (byte)generation };
addedAny = true;
}
return addedAny;
}
}

View file

@ -34,7 +34,7 @@ public static class LearnPossible
{
if (pk.IsOriginalMovesetDeleted())
return;
if (enc is EncounterSlot8GO g)
if (enc is EncounterSlot8GO { OriginFormat: PogoImportFormat.PK7 or PogoImportFormat.PB7 } g)
{
Span<ushort> initial = stackalloc ushort[4];
g.GetInitialMoves(pk.Met_Level, initial);

View file

@ -53,7 +53,7 @@ internal static class LearnVerifierHistory
{
MarkInitialMoves(result, current, moves);
}
else if (enc is EncounterSlot8GO g)
else if (enc is EncounterSlot8GO { OriginFormat: PogoImportFormat.PK7 or PogoImportFormat.PB7 } g)
{
Span<ushort> initial = stackalloc ushort[4];
g.GetInitialMoves(pk.Met_Level, initial);

View file

@ -31,7 +31,7 @@ public sealed class EffortValueVerifier : Verifier
data.AddLine(GetInvalid(LEffortAbove510));
Span<int> evs = stackalloc int[6];
pk.GetEVs(evs);
if (format >= 6 && evs.Find(static ev => ev > 252) != default)
if (format >= 6 && evs.IndexOfAny(253, 254, 255) != -1)
data.AddLine(GetInvalid(LEffortAbove252));
const int vitaMax = 100; // Vitamin Max

View file

@ -733,12 +733,26 @@ public sealed class WC8 : DataMysteryGift, ILangNick, INature, IGigantamax, IDyn
if (pk is PK8 pk8 && pk8.DynamaxLevel < DynamaxLevel)
return false;
if (IsHOMEGift && pk is IScaledSize s and not IHomeTrack { HasTracker: true } && ParseSettings.IgnoreTransferIfNoTracker)
if (IsHOMEGift)
{
if (s.HeightScalar != 0)
return false;
if (s.WeightScalar != 0)
return false;
// Prior to 3.0.0, HOME would set the Height and Weight exactly and not give a random value if it was 0.
// However, entering HOME will re-randomize the Height and Weight.
if (pk.MetDate is { } x && IsHOMEGiftOld(x))
{
// Need to defer and not mismatch date ranges.
if (!EncounterServerDate.IsValidDateWC8(this, x))
return false;
if (pk.Context is not (EntityContext.Gen8 or EntityContext.Gen8b))
{
// Only these 3 can transfer to PLA and get 0 scale prior to 3.0.0
if (Species is not ((ushort)Core.Species.Pikachu or (ushort)Core.Species.Eevee or (ushort)Core.Species.Rotom or (ushort)Core.Species.Pichu))
{
if (pk is IScaledSize { HeightScalar: 0, WeightScalar: 0 })
return false;
}
}
}
}
// Duplicate card; one with Nickname specified and another without.

View file

@ -287,14 +287,7 @@ public sealed class BK4 : G4PKM
public override int Characteristic => EntityCharacteristic.GetCharacteristicInvertFields(PID, IV32);
// Methods
protected override ushort CalculateChecksum()
{
ReadOnlySpan<byte> arr = Data;
ushort chk = 0;
for (int i = 8; i < PokeCrypto.SIZE_4STORED; i += 2)
chk += ReadUInt16BigEndian(arr[i..]);
return chk;
}
protected override ushort CalculateChecksum() => Checksums.Add16BigEndian(Data.AsSpan()[8..PokeCrypto.SIZE_4STORED]);
protected override byte[] Encrypt()
{

View file

@ -48,13 +48,7 @@ public sealed class PA8 : PKM, ISanityChecksum,
return data;
}
private ushort CalculateChecksum()
{
ushort chk = 0;
for (int i = 8; i < PokeCrypto.SIZE_8ASTORED; i += 2)
chk += ReadUInt16LittleEndian(Data.AsSpan(i));
return chk;
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_8ASTORED]);
// Simple Generated Attributes

View file

@ -202,14 +202,15 @@ public sealed class PK3 : G3PKM, ISanityChecksum
return PokeCrypto.EncryptArray3(Data);
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[0x20..PokeCrypto.SIZE_3STORED]);
public override void RefreshChecksum()
{
FlagIsBadEgg = false;
Checksum = PokeCrypto.GetCHK3(Data);
Checksum = CalculateChecksum();
}
public override bool ChecksumValid => CalculateChecksum() == Checksum;
private ushort CalculateChecksum() => PokeCrypto.GetCHK3(Data);
public PK4 ConvertToPK4()
{

View file

@ -38,7 +38,7 @@ public sealed class PK5 : PKM, ISanityChecksum,
public override void RefreshChecksum() => Checksum = CalculateChecksum();
public override bool ChecksumValid => CalculateChecksum() == Checksum;
public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } }
private ushort CalculateChecksum() => PokeCrypto.GetCHK(Data.AsSpan()[8..PokeCrypto.SIZE_4STORED]);
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_4STORED]);
// Trash Bytes
public override Span<byte> Nickname_Trash => Data.AsSpan(0x48, 22);

View file

@ -47,13 +47,7 @@ public sealed class PK9 : PKM, ISanityChecksum, ITeraType, ITechRecord, IObedien
return data;
}
private ushort CalculateChecksum()
{
ushort chk = 0;
for (int i = 8; i < PokeCrypto.SIZE_9STORED; i += 2)
chk += ReadUInt16LittleEndian(Data.AsSpan(i));
return chk;
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_9STORED]);
// Simple Generated Attributes
public override int CurrentFriendship

View file

@ -33,7 +33,7 @@ public abstract class G4PKM : PKM,
public sealed override void RefreshChecksum() => Checksum = CalculateChecksum();
public sealed override bool ChecksumValid => CalculateChecksum() == Checksum;
public override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } }
protected virtual ushort CalculateChecksum() => PokeCrypto.GetCHK(Data.AsSpan()[8..PokeCrypto.SIZE_4STORED]);
protected virtual ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_4STORED]);
// Trash Bytes
public sealed override Span<byte> Nickname_Trash => Data.AsSpan(0x48, 22);

View file

@ -1,5 +1,4 @@
using System;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@ -22,13 +21,7 @@ public abstract class G6PKM : PKM, ISanityChecksum
public sealed override bool ChecksumValid => CalculateChecksum() == Checksum;
public sealed override bool Valid { get => Sanity == 0 && ChecksumValid; set { if (!value) return; Sanity = 0; RefreshChecksum(); } }
private ushort CalculateChecksum()
{
ushort chk = 0;
for (int i = 8; i < PokeCrypto.SIZE_6STORED; i += 2) // don't use SIZE_STORED property; pb7 overrides stored size
chk += ReadUInt16LittleEndian(Data.AsSpan(i));
return chk;
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_6STORED]);
// Simple Generated Attributes
public sealed override int CurrentFriendship

View file

@ -19,13 +19,7 @@ public abstract class G8PKM : PKM, ISanityChecksum,
return data;
}
private ushort CalculateChecksum()
{
ushort chk = 0;
for (int i = 8; i < PokeCrypto.SIZE_8STORED; i += 2)
chk += ReadUInt16LittleEndian(Data.AsSpan(i));
return chk;
}
private ushort CalculateChecksum() => Checksums.Add16(Data.AsSpan()[8..PokeCrypto.SIZE_8STORED]);
// Simple Generated Attributes
public abstract IPermitRecord Permit { get; } // PersonalInfo derived metadata

View file

@ -33,7 +33,7 @@ public sealed class DefaultEntityNamer : IFileNamer<PKM>
private static string GetRegular(PKM pk)
{
var chk = pk is ISanityChecksum s ? s.Checksum : PokeCrypto.GetCHK(pk.Data.AsSpan()[8..pk.SIZE_STORED]);
var chk = pk is ISanityChecksum s ? s.Checksum : Checksums.Add16(pk.Data.AsSpan()[8..pk.SIZE_STORED]);
var form = pk.Form != 0 ? $"-{pk.Form:00}" : string.Empty;
var star = pk.IsShiny ? " ★" : string.Empty;
return $"{pk.Species:0000}{form}{star} - {pk.Nickname} - {chk:X4}{pk.EncryptionConstant:X8}";

View file

@ -54,7 +54,7 @@ public static class EntityFormat
if (data[..^0x10].IndexOfAnyExcept<byte>(0) != -1)
return true;
if (ReadUInt16LittleEndian(data[0x06..]) == GetCHK(data[8..SIZE_6STORED]))
if (ReadUInt16LittleEndian(data[0x06..]) == Checksums.Add16(data[8..SIZE_6STORED]))
return true; // decrypted
return false;
}

View file

@ -116,17 +116,25 @@ public static class PokeCrypto
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] ShuffleArray(ReadOnlySpan<byte> data, uint sv, int blockSize)
{
byte[] sdata = data.ToArray();
byte[] sdata = new byte[data.Length];
ShuffleArray(data, sdata, sv, blockSize);
return sdata;
}
private static void ShuffleArray(ReadOnlySpan<byte> data, Span<byte> result, uint sv, int blockSize)
{
int index = (int)sv * 4;
const int start = 8;
for (int block = 0; block < 4; block++)
data[..start].CopyTo(result[..start]);
var end = start + (blockSize * 4);
data[end..].CopyTo(result[end..]);
for (int block = 3; block >= 0; block--)
{
var dest = result.Slice(start + (blockSize * block), blockSize);
int ofs = BlockPosition[index + block];
var src = data.Slice(start + (blockSize * ofs), blockSize);
var dest = sdata.AsSpan(start + (blockSize * block), blockSize);
src.CopyTo(dest);
}
return sdata;
}
/// <summary>
@ -323,14 +331,13 @@ public static class PokeCrypto
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CryptArray(Span<byte> data, uint seed)
{
var reinterpret = MemoryMarshal.Cast<byte, ushort>(data);
for (int i = 0; i < reinterpret.Length; i++)
foreach (ref var u32 in MemoryMarshal.Cast<byte, ushort>(data))
{
seed = (0x41C64E6D * seed) + 0x00006073;
var xor = (ushort)(seed >> 16);
if (!BitConverter.IsLittleEndian)
xor = ReverseEndianness(xor);
reinterpret[i] ^= xor;
u32 ^= xor;
}
}
@ -346,18 +353,19 @@ public static class PokeCrypto
uint PID = ReadUInt32LittleEndian(ekm);
uint OID = ReadUInt32LittleEndian(ekm[4..]);
uint seed = PID ^ OID;
var toEncrypt = ekm[SIZE_3HEADER..SIZE_3STORED];
for (int i = 0; i < toEncrypt.Length; i += 4)
{
var span = toEncrypt.Slice(i, 4);
var chunk = ReadUInt32LittleEndian(span);
var update = chunk ^ seed;
WriteUInt32LittleEndian(span, update);
}
CryptArray3(ekm, seed);
return ShuffleArray3(ekm, PID % 24);
}
private static void CryptArray3(Span<byte> ekm, uint seed)
{
if (!BitConverter.IsLittleEndian)
seed = ReverseEndianness(seed);
var toEncrypt = ekm[SIZE_3HEADER..SIZE_3STORED];
foreach (ref var u32 in MemoryMarshal.Cast<byte, uint>(toEncrypt))
u32 ^= seed;
}
/// <summary>
/// Shuffles an 80 byte format Generation 3 Pokémon byte array.
/// </summary>
@ -366,17 +374,23 @@ public static class PokeCrypto
/// <returns>Un-shuffled data.</returns>
private static byte[] ShuffleArray3(ReadOnlySpan<byte> data, uint sv)
{
byte[] sdata = data.ToArray();
byte[] sdata = new byte[data.Length];
ShuffleArray3(data, sdata, sv);
return sdata;
}
private static void ShuffleArray3(ReadOnlySpan<byte> data, Span<byte> result, uint sv)
{
int index = (int)sv * 4;
for (int block = 0; block < 4; block++)
data[..SIZE_3HEADER].CopyTo(result[..SIZE_3HEADER]);
data[SIZE_3STORED..].CopyTo(result[SIZE_3STORED..]);
for (int block = 3; block >= 0; block--)
{
var dest = result.Slice(SIZE_3HEADER + (SIZE_3BLOCK * block), SIZE_3BLOCK);
int ofs = BlockPosition[index + block];
var src = data.Slice(SIZE_3HEADER + (SIZE_3BLOCK * ofs), SIZE_3BLOCK);
var dest = sdata.AsSpan(SIZE_3HEADER + (SIZE_3BLOCK * block), SIZE_3BLOCK);
src.CopyTo(dest);
}
return sdata;
}
/// <summary>
@ -393,44 +407,17 @@ public static class PokeCrypto
uint seed = PID ^ OID;
byte[] ekm = ShuffleArray3(pk, BlockPositionInvert[(int)(PID % 24)]);
var toEncrypt = ekm.AsSpan()[SIZE_3HEADER..SIZE_3STORED];
for (int i = 0; i < toEncrypt.Length; i += 4)
{
var span = toEncrypt.Slice(i, 4);
var chunk = ReadUInt32LittleEndian(span);
var update = chunk ^ seed;
WriteUInt32LittleEndian(span, update);
}
CryptArray3(ekm, seed);
return ekm;
}
/// <summary>
/// Gets the 16-bit checksum of a byte array.
/// </summary>
/// <param name="data">Decrypted Pokémon data.</param>
public static ushort GetCHK(ReadOnlySpan<byte> data)
{
ushort chk = 0;
for (int i = 0; i < data.Length; i += 2)
chk += ReadUInt16LittleEndian(data[i..]);
return chk;
}
/// <summary>
/// Gets the checksum of a Generation 3 byte array.
/// </summary>
/// <param name="data">Decrypted Pokémon data.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort GetCHK3(ReadOnlySpan<byte> data) => GetCHK(data[0x20..SIZE_3STORED]);
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// </summary>
/// <remarks>Generation 3 Format encryption check which verifies the checksum</remarks>
public static void DecryptIfEncrypted3(ref byte[] pk)
{
ushort chk = GetCHK3(pk);
ushort chk = Checksums.Add16(pk.AsSpan(0x20, 4 * SIZE_3BLOCK));
if (chk != ReadUInt16LittleEndian(pk.AsSpan(0x1C)))
pk = DecryptArray3(pk);
}

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@ -19,18 +20,24 @@ public static class GeniusCrypto
if (keys.Length != 4)
throw new ArgumentOutOfRangeException(nameof(keys));
var in16 = MemoryMarshal.Cast<byte, ushort>(input);
var out16 = MemoryMarshal.Cast<byte, ushort>(output);
int i = 0;
do
{
foreach (var key in keys)
{
ushort value = ReadUInt16BigEndian(input[i..]);
var value = in16[i];
if (BitConverter.IsLittleEndian)
value = ReverseEndianness(value);
value -= key;
WriteUInt16BigEndian(output[i..], value);
i += 2;
if (BitConverter.IsLittleEndian)
value = ReverseEndianness(value);
out16[i] = value;
i++;
}
AdvanceKeys(keys);
} while (i != input.Length);
} while (i != in16.Length);
}
public static void Encrypt(ReadOnlySpan<byte> input, Span<byte> output, Span<ushort> keys)
@ -38,18 +45,24 @@ public static class GeniusCrypto
if (keys.Length != 4)
throw new ArgumentOutOfRangeException(nameof(keys));
var in16 = MemoryMarshal.Cast<byte, ushort>(input);
var out16 = MemoryMarshal.Cast<byte, ushort>(output);
int i = 0;
do
{
foreach (var key in keys)
{
ushort value = ReadUInt16BigEndian(input[i..]);
var value = in16[i];
if (BitConverter.IsLittleEndian)
value = ReverseEndianness(value);
value += key;
WriteUInt16BigEndian(output[i..], value);
i += 2;
if (BitConverter.IsLittleEndian)
value = ReverseEndianness(value);
out16[i] = value;
i++;
}
AdvanceKeys(keys);
} while (i != input.Length);
} while (i != in16.Length);
}
private static void AdvanceKeys(Span<ushort> keys)

View file

@ -227,14 +227,14 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
// Hall of Fame Checksums
{
var sector2 = Data.AsSpan(0x1C000, SIZE_SECTOR);
ushort chk = Checksums.CheckSum32(sector2[..SIZE_SECTOR_USED]);
WriteUInt16LittleEndian(sector2[0xFF4..], chk);
var sector = Data.AsSpan(0x1C000, SIZE_SECTOR);
ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
WriteUInt16LittleEndian(sector[0xFF4..], chk);
}
{
var sector2 = Data.AsSpan(0x1D000, SIZE_SECTOR);
ushort chk = Checksums.CheckSum32(sector2[..SIZE_SECTOR_USED]);
WriteUInt16LittleEndian(sector2[0xFF4..], chk);
var sector = Data.AsSpan(0x1D000, SIZE_SECTOR);
ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
WriteUInt16LittleEndian(sector[0xFF4..], chk);
}
}
@ -262,8 +262,9 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
private bool IsSectorValidExtra(int ofs)
{
var sector = Data.AsSpan(ofs, SIZE_SECTOR);
ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
return chk == ReadUInt16LittleEndian(sector[0xFF4..]);
var expect = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
var actual = ReadUInt16LittleEndian(sector[0xFF4..]);
return expect == actual;
}
private bool IsSectorValid(int sectorIndex)
@ -271,8 +272,9 @@ public abstract class SAV3 : SaveFile, ILangDeviantSave, IEventFlag37
int start = ActiveSlot * SIZE_MAIN;
int ofs = start + (sectorIndex * SIZE_SECTOR);
var sector = Data.AsSpan(ofs, SIZE_SECTOR);
ushort chk = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
return chk == ReadUInt16LittleEndian(sector[0xFF6..]);
var expect = Checksums.CheckSum32(sector[..SIZE_SECTOR_USED]);
var actual = ReadUInt16LittleEndian(sector[0xFF6..]);
return expect == actual;
}
public sealed override string ChecksumInfo

View file

@ -1,4 +1,5 @@
using System;
using System.Runtime.InteropServices;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
@ -105,7 +106,7 @@ public static class Checksums
{
ushort chk = initial;
foreach (var b in data)
chk = (ushort)(crc16[(b ^ chk) & 0xFF] ^ (chk >> 8));
chk = (ushort)(crc16[(byte)(b ^ chk)] ^ (chk >> 8));
return chk;
}
@ -123,10 +124,15 @@ public static class Checksums
/// <returns>Checksum</returns>
public static ushort CheckSum32(ReadOnlySpan<byte> data, uint initial = 0)
{
uint val = initial;
for (int i = 0; i < data.Length; i += 4)
val += ReadUInt32LittleEndian(data[i..]);
return (ushort)(val + (val >> 16));
uint chk = initial;
foreach (var u32 in MemoryMarshal.Cast<byte, uint>(data))
{
if (BitConverter.IsLittleEndian)
chk += u32;
else
chk += ReverseEndianness(u32);
}
return (ushort)(chk + (chk >> 16));
}
/// <summary>Calculates the 32bit checksum over an input byte array.</summary>
@ -166,13 +172,43 @@ public static class Checksums
/// <returns>Checksum</returns>
public static uint CheckSum16BigInvert(ReadOnlySpan<byte> data)
{
if ((data.Length & 1) != 0)
data = data[..^2];
ushort chk = 0; // initial value
for (int i = 0; i < data.Length; i += 2)
chk += ReadUInt16BigEndian(data[i..]);
var chk = Add16BigEndian(data);
return (uint)((chk << 16) | (ushort)(0xF004u - chk));
}
/// <summary>
/// Gets the 16-bit checksum of a byte array reading as Little Endian.
/// </summary>
/// <param name="data">Input byte array</param>
/// <returns>Checksum</returns>
public static ushort Add16(ReadOnlySpan<byte> data)
{
ushort chk = 0;
foreach (var u16 in MemoryMarshal.Cast<byte, ushort>(data))
{
if (BitConverter.IsLittleEndian)
chk += u16;
else
chk += ReverseEndianness(u16);
}
return chk;
}
/// <summary>
/// Gets the 16-bit checksum of a byte array reading as Big Endian.
/// </summary>
/// <param name="data">Input byte array</param>
/// <returns>Checksum</returns>
public static ushort Add16BigEndian(ReadOnlySpan<byte> data)
{
ushort chk = 0;
foreach (var u16 in MemoryMarshal.Cast<byte, ushort>(data))
{
if (!BitConverter.IsLittleEndian)
chk += u16;
else
chk += ReverseEndianness(u16);
}
return chk;
}
}

View file

@ -128,7 +128,8 @@ public partial class Main : Form
{
foreach (var x in args)
{
if (string.Equals(x.Trim('-'), nameof(HaX), StringComparison.CurrentCultureIgnoreCase))
var arg = x.AsSpan().Trim('-');
if (arg.Equals(nameof(HaX), StringComparison.CurrentCultureIgnoreCase))
return true;
}

View file

@ -198,7 +198,7 @@ public class ShowdownSetTests
{
string input = $@"Eevee\nFriendship: {value}";
var set = new ShowdownSet(input);
set.Level.Should().NotBe(value);
set.Friendship.Should().NotBe(value);
}
[Theory]