mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 14:44:24 +00:00
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:
parent
2aef48172e
commit
18fd790657
25 changed files with 247 additions and 176 deletions
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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}";
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
Loading…
Reference in a new issue