PKM: Reduce allocation via in-place decryption logic (#4764)

This commit is contained in:
Kurt 2026-03-24 21:31:59 -05:00 committed by GitHub
parent 793525875f
commit 5bf1e2cf45
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
81 changed files with 882 additions and 759 deletions

View File

@ -100,7 +100,13 @@ public class EntitySummary : IFatefulEncounterReadOnly // do NOT seal, allow inh
public string Relearn2 => Get(Strings.movelist, Entity.RelearnMove2);
public string Relearn3 => Get(Strings.movelist, Entity.RelearnMove3);
public string Relearn4 => Get(Strings.movelist, Entity.RelearnMove4);
public ushort Checksum => Entity is ISanityChecksum s ? s.Checksum : Checksums.CRC16_CCITT(Entity.Data[Entity.SIZE_STORED..]);
public ushort Checksum => Entity switch
{
ISanityChecksum s => s.Checksum,
PK1 gb => gb.GetSingleListChecksum(),
PK2 gb => gb.GetSingleListChecksum(),
_ => Checksums.CRC16_CCITT(Entity.Data[Entity.SIZE_STORED..]),
};
public int Friendship => Entity.OriginalTrainerFriendship;
public int EggYear => Entity.EggMetDate.GetValueOrDefault().Year;
public int EggMonth => Entity.EggMetDate.GetValueOrDefault().Month;

View File

@ -40,7 +40,8 @@ public static string GetMessage(PKM pk)
return GetMessage(pk7);
var server = GetExploitURLPrefixPKM(pk.Format);
var data = pk.EncryptedBoxData;
Span<byte> data = stackalloc byte[pk.SIZE_STORED];
pk.WriteEncryptedDataStored(data);
return GetMessageBase64(data, server);
}

View File

@ -47,10 +47,10 @@ public sealed class FakeSaveFile : SaveFile
protected override void SetChecksums() { }
public override GameVersion Version { get => GameVersion.R; set { } }
public override Type PKMType => typeof(PK3);
protected override PK3 GetPKM(byte[] data) => BlankPKM;
protected override byte[] DecryptPKM(byte[] data) => data;
protected override PK3 GetPKM(Memory<byte> data) => BlankPKM;
protected override void DecryptPKM(Span<byte> data) { }
public override PK3 BlankPKM => new();
public override EntityContext Context => EntityContext.Gen3;
protected override int SIZE_STORED => 0;
protected override int SIZE_PARTY => 0;
public override int SIZE_STORED => 0;
public override int SIZE_PARTY => 0;
}

View File

@ -87,6 +87,7 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
int count = GetSlotCountForBox(boxSlotCount, box, total);
int ctr = 0;
// Export each slot in the box.
Span<byte> data = stackalloc byte[sav.SIZE_STORED];
for (int slot = 0; slot < count; slot++)
{
var pk = sav.GetBoxSlotAtIndex(box, slot);
@ -98,7 +99,8 @@ public static int Export(SaveFile sav, string destPath, IFileNamer<PKM> namer, B
var fileName = GetFileName(pk, settings.FileIndexPrefix, namer, box, slot, boxSlotCount);
var fn = Path.Combine(destPath, fileName);
File.WriteAllBytes(fn, pk.DecryptedPartyData);
pk.WriteDecryptedDataStored(data);
File.WriteAllBytes(fn, data);
ctr++;
}
return ctr;

View File

@ -254,7 +254,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
{
const int size = 0x1F0;
var ofs = (i * size) + 8;
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
var entry = shinyCache.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
if (EntityDetection.IsPresent(entry.Span))
list.Add(new(entry, i, true) { Type = StorageSlotType.Shiny, HideLegality = true }); // no OT info
else
@ -266,7 +266,7 @@ private static List<SlotInfoMisc> GetExtraSlots9a(SAV9ZA sav)
{
const int size = 0x1A8;
var ofs = (i * size) + 8;
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_9PARTY);
var entry = giveAway.Raw.Slice(ofs, PokeCrypto.SIZE_8PARTY);
if (EntityDetection.IsPresent(entry.Span))
list.Add(new(entry, i, true, Mutable: true) { Type = StorageSlotType.Scripted });
else

View File

@ -41,7 +41,7 @@ public override ReadOnlySpan<byte> Write()
{
// Ensure PGT content is encrypted
var clone = new PCD(Data.ToArray());
clone.Gift.VerifyPKEncryption();
clone.Gift.VerifyGiftEncryption();
return clone.Data;
}

View File

@ -45,17 +45,16 @@ public override byte Ball
public int ItemSubID { get => ReadInt32LittleEndian(Data[0x8..]); set => WriteInt32LittleEndian(Data[0x8..], value); }
public int PokewalkerCourseID { get => Data[0x4]; set => Data[0x4] = (byte)value; }
private Span<byte> DataGift => Data.Slice(8, PokeCrypto.SIZE_4PARTY);
public PK4 PK
{
get => field ??= new PK4(Data.Slice(8, PokeCrypto.SIZE_4PARTY).ToArray());
get => field ??= new PK4(DataGift.ToArray());
set
{
field = value;
var data = value.Data;
bool zero = !data.ContainsAnyExcept<byte>(0); // all zero
if (!zero)
data = PokeCrypto.EncryptArray45(data);
data.CopyTo(Data[8..]);
field = value.Clone(); // cache the PK4 for future use
value.Data.CopyTo(DataGift);
VerifyGiftEncryption();
}
}
@ -63,29 +62,27 @@ public override ReadOnlySpan<byte> Write()
{
// Ensure PGT content is encrypted
var clone = new PGT(Data.ToArray());
clone.VerifyPKEncryption();
clone.VerifyGiftEncryption();
return clone.Data;
}
/// <summary>
/// Double-checks the encryption of the gift data for Pokémon data.
/// Double-checks the encryption of the gift data.
/// </summary>
/// <returns>True if data was encrypted, false if the data was not modified.</returns>
public bool VerifyPKEncryption()
public bool VerifyGiftEncryption()
{
if (GiftType is not (Pokémon or PokémonEgg))
return false; // not encrypted
if (ReadUInt32LittleEndian(Data[(0x64 + 8)..]) != 0)
return false; // already encrypted (unused PK4 field, zero)
EncryptPK();
return true;
}
private void EncryptPK()
{
var span = Data.Slice(8, PokeCrypto.SIZE_4PARTY);
var ekdata = PokeCrypto.EncryptArray45(span);
ekdata.CopyTo(span);
var gift = DataGift;
var isEmpty = !gift.ContainsAnyExcept<byte>(0); // all zero
if (isEmpty) // shouldn't ever be empty, just return if so.
return false;
if (PokeCrypto.IsEncrypted45(gift)) // unused PK4 ribbon bits
return false;
PokeCrypto.Encrypt45(gift);
return true;
}
public GiftType4 GiftType { get => (GiftType4)CardType; set => CardType = (byte)value; }

View File

@ -22,19 +22,11 @@ public sealed class BK4 : G4PKM
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
public override EntityContext Context => EntityContext.Gen4;
public override PersonalInfo4 PersonalInfo => PersonalTable.HGSS[Species];
public override byte[] DecryptedBoxData => EncryptedBoxData;
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt4BE(stored);
protected override void EncryptParty(Span<byte> party) { }
public override bool Valid => ChecksumValid || (Sanity == 0 && Species <= MaxSpeciesID);
public static BK4 ReadUnshuffle(ReadOnlySpan<byte> data)
{
var unshuffled = PokeCrypto.DecryptArray4BE(data);
var result = new BK4(unshuffled);
result.RefreshChecksum();
return result;
}
public BK4(Memory<byte> data) : base(data)
{
Sanity = 0x4000;
@ -302,12 +294,6 @@ public override ushort MetLocationDP
// Methods
protected override ushort CalculateChecksum() => Checksums.Add16BigEndian(Data[8..PokeCrypto.SIZE_4STORED]);
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray4BE(Data);
}
public PK4 ConvertToPK4()
{
PK4 pk4 = ConvertTo<PK4>();

View File

@ -23,6 +23,8 @@ public sealed class CK3(Memory<byte> Raw) : G3PKM(Raw), IShadowCapture, ISeparat
public override EntityContext Context => EntityContext.Gen3;
public override PersonalInfo3 PersonalInfo => PersonalTable.RS[Species];
public override CK3 Clone() => new(Data.ToArray());
protected override void EncryptStored(Span<byte> stored) { }
protected override void EncryptParty(Span<byte> party) { }
// Trash Bytes
public override Span<byte> OriginalTrainerTrash => Data.Slice(0x18, 22);
@ -238,8 +240,6 @@ public bool IsFatefulValid(bool japanese)
public const int Purified = -100;
public bool IsShadow => ShadowID != 0 && Purification != Purified;
protected override byte[] Encrypt() => Data.ToArray();
public PK3 ConvertToPK3()
{
var pk = ConvertTo<PK3>();

View File

@ -17,6 +17,9 @@ public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBat
public GameDataPA9? DataPA9 { get; private set; }
public override EntityContext Context => EntityContext.None;
public override int WriteDecryptedDataStored(Span<byte> destination) => Rebuild(destination);
protected override void EncryptStored(Span<byte> stored) { }
protected override void EncryptParty(Span<byte> party) { }
public PKH(Memory<byte> data) : base(DecryptHome(data))
{
@ -255,24 +258,46 @@ private static Memory<byte> DecryptHome(Memory<byte> data)
public override void RefreshChecksum() => Checksum = 0;
public override bool ChecksumValid => true;
protected override byte[] Encrypt()
{
var result = Rebuild();
return HomeCrypto.Encrypt(result);
}
public byte[] Rebuild()
{
var length = WriteLength;
// Handle PKCS7 manually
var remainder = length & 0xF;
var totalSize = GetPaddedSize(length, out var remainder);
var result = new byte[totalSize];
WriteTo(result, length, remainder, totalSize);
return result;
}
public int Rebuild(Span<byte> dest)
{
var length = WriteLength;
// Handle PKCS7 manually
var totalSize = GetPaddedSize(length, out var remainder);
var result = dest[..totalSize];
WriteTo(result, length, remainder, totalSize);
return totalSize;
}
private void WriteTo(Span<byte> data, int innerLength, int remainder, int totalSize)
{
var payload = data[..innerLength];
data[innerLength..].Fill((byte)remainder);
WriteTo(payload, totalSize);
}
public static int GetPaddedSize(int innerLength, out int remainder)
{
remainder = innerLength & 0xF;
if (remainder != 0) // pad to nearest 0x10, fill remainder bytes with value.
remainder = 0x10 - remainder;
var result = new byte[length + remainder];
var span = result.AsSpan(0, length);
result.AsSpan(length).Fill((byte)remainder);
var totalSize = innerLength + remainder;
return totalSize;
}
private void WriteTo(Span<byte> span, int innerLength)
{
// Header and Core are already in the current byte array.
// Write each part, starting with header and core.
int ctr = HomeCrypto.SIZE_1HEADER + 2;
@ -289,11 +314,9 @@ public byte[] Rebuild()
// Update metadata to ensure we're a valid object.
DataVersion = HomeCrypto.VersionLatest;
EncodedDataSize = (ushort)(result.Length - HomeCrypto.SIZE_1HEADER);
EncodedDataSize = (ushort)(innerLength - HomeCrypto.SIZE_1HEADER);
CoreDataSize = (ushort)Core.SerializedSize;
Data[..(HomeCrypto.SIZE_1HEADER + 2)].CopyTo(span); // Copy updated header & CoreData length.
return result;
}
private int WriteLength

View File

@ -35,6 +35,8 @@ public sealed class PA8 : PKM, ISanityChecksum,
public override EntityContext Context => EntityContext.Gen8a;
public PA8() : base(PokeCrypto.SIZE_8APARTY) => AffixedRibbon = Core.AffixedRibbon.None;
public PA8(Memory<byte> data) : base(DecryptParty(data)) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8A(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
public override int SIZE_PARTY => PokeCrypto.SIZE_8APARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8ASTORED;
@ -43,7 +45,7 @@ public sealed class PA8 : PKM, ISanityChecksum,
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted8A(ref data);
PokeCrypto.DecryptIfEncrypted8A(data.Span);
if (data.Length >= PokeCrypto.SIZE_8APARTY)
return data;
@ -86,11 +88,6 @@ public override byte CurrentFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray8A(Data);
}
public void FixRelearn()
{

View File

@ -21,24 +21,26 @@ public sealed class PA9 : PKM, ISanityChecksum, ITechRecord, IObedienceLevel, IH
public override PersonalInfo9ZA PersonalInfo => PersonalTable.ZA.GetFormEntry(Species, Form);
public IPermitRecord Permit => PersonalInfo;
public override EntityContext Context => EntityContext.Gen9a;
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
public PA9() : base(PokeCrypto.SIZE_9PARTY) => AffixedRibbon = PKHeX.Core.AffixedRibbon.None;
public PA9() : base(PokeCrypto.SIZE_8PARTY) => AffixedRibbon = PKHeX.Core.AffixedRibbon.None;
public PA9(Memory<byte> data) : base(DecryptParty(data)) { }
public override PA9 Clone() => new(Data.ToArray());
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted9(ref data);
if (data.Length >= PokeCrypto.SIZE_9PARTY)
PokeCrypto.DecryptIfEncrypted8(data.Span);
if (data.Length >= PokeCrypto.SIZE_8PARTY)
return data;
var result = new byte[PokeCrypto.SIZE_9PARTY];
var result = new byte[PokeCrypto.SIZE_8PARTY];
data.Span.CopyTo(result);
return result;
}
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_9STORED]);
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_8STORED]);
// Simple Generated Attributes
public override byte CurrentFriendship
@ -47,8 +49,8 @@ public override byte CurrentFriendship
set { if (CurrentHandler == 0) OriginalTrainerFriendship = value; else HandlingTrainerFriendship = value; }
}
public override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_9STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
public override bool ChecksumValid => CalculateChecksum() == Checksum;
public override void RefreshChecksum() => Checksum = CalculateChecksum();
@ -76,11 +78,6 @@ public override byte CurrentFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray9(Data);
}
public void FixRelearn()
{

View File

@ -33,7 +33,7 @@ public sealed class PB7 : G6PKM, IHyperTrain, IAwakened, IScaledSizeValue, IComb
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted67(ref data);
PokeCrypto.DecryptIfEncrypted67(data.Span);
if (data.Length >= SIZE)
return data;

View File

@ -10,14 +10,14 @@ public sealed class PK1 : GBPKML, IPersonalType
public override bool Valid => Species <= 151 && (Data[0] == 0 || Species != 0);
public override int SIZE_PARTY => PokeCrypto.SIZE_1PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_1STORED;
public override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
public override int SIZE_PARTY => SIZE_STORED;
public override bool Korean => false;
public override EntityContext Context => EntityContext.Gen1;
public PK1(bool jp = false) : base(PokeCrypto.SIZE_1PARTY, jp) { }
public PK1(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
public PK1(Memory<byte> decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
public PK1(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> nick)
: this(ot.Length == StringLengthJapanese)
@ -27,11 +27,13 @@ public PK1(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> ni
nick.CopyTo(NicknameTrash);
}
private static byte[] EnsurePartySize(byte[] data)
private static Memory<byte> EnsurePartySize(Memory<byte> data)
{
if (data.Length != PokeCrypto.SIZE_1PARTY)
Array.Resize(ref data, PokeCrypto.SIZE_1PARTY);
return data;
if (data.Length == PokeCrypto.SIZE_1PARTY)
return data;
var result = new byte[PokeCrypto.SIZE_1PARTY];
data.CopyTo(result);
return result;
}
public override PK1 Clone()
@ -42,7 +44,13 @@ public override PK1 Clone()
return clone;
}
protected override byte[] Encrypt() => PokeList1.WrapSingle(this);
// We (PKHeX) internally manage as single-entry lists in temp buffers.
public override int WriteDecryptedDataStored(Span<byte> destination) => PokeList1.WrapSingle(this, destination);
public override void WriteEncryptedDataStored(Span<byte> destination) => WriteDecryptedDataStored(destination);
public override void WriteDecryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
public override void WriteEncryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
public override void WriteDecryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
public override void WriteEncryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
#region Stored Attributes
public byte SpeciesInternal { get => Data[0]; set => Data[0] = value; } // raw access

View File

@ -10,14 +10,14 @@ public sealed class PK2 : GBPKML, ICaughtData2
public override bool Valid => Species <= Legal.MaxSpeciesID_2;
public override int SIZE_PARTY => PokeCrypto.SIZE_2PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_2STORED;
public override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
public override int SIZE_PARTY => SIZE_STORED;
public override bool Korean => !Japanese && OriginalTrainerTrash[0] <= 0xB;
public override EntityContext Context => EntityContext.Gen2;
public PK2(bool jp = false) : base(PokeCrypto.SIZE_2PARTY, jp) { }
public PK2(byte[] decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
public PK2(Memory<byte> decryptedData, bool jp = false) : base(EnsurePartySize(decryptedData), jp) { }
public PK2(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> nick)
: this(ot.Length == StringLengthJapanese)
@ -27,11 +27,13 @@ public PK2(ReadOnlySpan<byte> data, ReadOnlySpan<byte> ot, ReadOnlySpan<byte> ni
nick.CopyTo(NicknameTrash);
}
private static byte[] EnsurePartySize(byte[] data)
private static Memory<byte> EnsurePartySize(Memory<byte> data)
{
if (data.Length != PokeCrypto.SIZE_2PARTY)
Array.Resize(ref data, PokeCrypto.SIZE_2PARTY);
return data;
if (data.Length == PokeCrypto.SIZE_2PARTY)
return data;
var result = new byte[PokeCrypto.SIZE_2PARTY];
data.CopyTo(result);
return result;
}
public override PK2 Clone()
@ -42,7 +44,14 @@ public override PK2 Clone()
return clone;
}
protected override byte[] Encrypt() => PokeList2.WrapSingle(this);
// We (PKHeX) internally manage as single-entry lists in temp buffers.
public override int WriteDecryptedDataStored(Span<byte> destination) => PokeList2.WrapSingle(this, destination);
public override void WriteEncryptedDataStored(Span<byte> destination) => WriteDecryptedDataStored(destination);
public override void WriteDecryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
public override void WriteEncryptedDataParty(Span<byte> destination) => WriteDecryptedDataStored(destination);
public override void WriteDecryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
public override void WriteEncryptedDataParty(Span<byte> stored, Span<byte> party) => WriteDecryptedDataStored(stored);
#region Stored Attributes
public override ushort Species { get => Data[0]; set => Data[0] = (byte)value; }

View File

@ -16,10 +16,12 @@ public sealed class PK3 : G3PKM, ISanityChecksum
public PK3() : base(PokeCrypto.SIZE_3PARTY) { }
public PK3(Memory<byte> data) : base(DecryptParty(data)) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt3(stored);
protected override void EncryptParty(Span<byte> party) { }
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted3(ref data);
PokeCrypto.DecryptIfEncrypted3(data.Span);
if (data.Length >= PokeCrypto.SIZE_3PARTY)
return data;
@ -199,12 +201,6 @@ public override bool IsEgg
public override int Stat_SPD { get => ReadUInt16LittleEndian(Data[0x62..]); set => WriteUInt16LittleEndian(Data[0x62..], (ushort)value); }
#endregion
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray3(Data);
}
private ushort CalculateChecksum() => Checksums.Add16(Data[0x20..PokeCrypto.SIZE_3STORED]);
public override void RefreshChecksum()

View File

@ -24,7 +24,7 @@ public sealed class PK4 : G4PKM
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted45(ref data);
PokeCrypto.DecryptIfEncrypted45(data.Span);
if (data.Length >= PokeCrypto.SIZE_4PARTY)
return data;
@ -292,11 +292,6 @@ public override ushort MetLocationDP
#endregion
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray45(Data);
}
public BK4 ConvertToBK4()
{

View File

@ -22,13 +22,15 @@ public sealed class PK5 : PKM, ISanityChecksum,
public override int SIZE_STORED => PokeCrypto.SIZE_5STORED;
public override EntityContext Context => EntityContext.Gen5;
public override PersonalInfo5B2W2 PersonalInfo => PersonalTable.B2W2.GetFormEntry(Species, Form);
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt45(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
public PK5() : base(PokeCrypto.SIZE_5PARTY) { }
public PK5(Memory<byte> data) : base(DecryptParty(data)) { }
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted45(ref data);
PokeCrypto.DecryptIfEncrypted45(data.Span);
if (data.Length >= PokeCrypto.SIZE_5PARTY)
return data;
@ -307,11 +309,6 @@ public override string Nickname
public override int MaxStringLengthNickname => 10;
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray45(Data);
}
// Synthetic Trading Logic
public bool BelongsTo(ITrainerInfo tr)

View File

@ -22,7 +22,7 @@ public sealed class PK6 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted67(ref data);
PokeCrypto.DecryptIfEncrypted67(data.Span);
if (data.Length >= PokeCrypto.SIZE_6PARTY)
return data;

View File

@ -23,7 +23,7 @@ public sealed class PK7 : G6PKM, IRibbonSetEvent3, IRibbonSetEvent4, IRibbonSetC
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted67(ref data);
PokeCrypto.DecryptIfEncrypted67(data.Span);
if (data.Length >= PokeCrypto.SIZE_6PARTY)
return data;

View File

@ -23,7 +23,7 @@ public sealed class PK9 : PKM, ISanityChecksum, ITeraType, ITechRecord, IObedien
public IPermitRecord Permit => PersonalInfo;
public override EntityContext Context => EntityContext.Gen9;
public PK9() : base(PokeCrypto.SIZE_9PARTY)
public PK9() : base(PokeCrypto.SIZE_8PARTY)
{
AffixedRibbon = PKHeX.Core.AffixedRibbon.None;
TeraTypeOverride = (MoveType)TeraTypeUtil.OverrideNone;
@ -34,16 +34,18 @@ public PK9() : base(PokeCrypto.SIZE_9PARTY)
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted9(ref data);
if (data.Length >= PokeCrypto.SIZE_9PARTY)
PokeCrypto.DecryptIfEncrypted8(data.Span);
if (data.Length >= PokeCrypto.SIZE_8PARTY)
return data;
var result = new byte[PokeCrypto.SIZE_9PARTY];
var result = new byte[PokeCrypto.SIZE_8PARTY];
data.Span.CopyTo(result);
return result;
}
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_9STORED]);
private ushort CalculateChecksum() => Checksums.Add16(Data[8..PokeCrypto.SIZE_8STORED]);
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
// Simple Generated Attributes
public override byte CurrentFriendship
@ -52,8 +54,8 @@ public override byte CurrentFriendship
set { if (CurrentHandler == 0) OriginalTrainerFriendship = value; else HandlingTrainerFriendship = value; }
}
public override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_9STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
public override bool ChecksumValid => CalculateChecksum() == Checksum;
public override void RefreshChecksum() => Checksum = CalculateChecksum();
@ -81,11 +83,6 @@ public override byte CurrentFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray9(Data);
}
public void FixRelearn()
{

View File

@ -29,11 +29,6 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
protected PKM(Memory<byte> data) => Raw = data;
protected PKM([ConstantExpected] int size) => Raw = new byte[size];
public virtual byte[] EncryptedPartyData => Encrypt().AsSpan()[..SIZE_PARTY].ToArray();
public virtual byte[] EncryptedBoxData => Encrypt().AsSpan()[..SIZE_STORED].ToArray();
public virtual byte[] DecryptedPartyData => Write()[..SIZE_PARTY].ToArray();
public virtual byte[] DecryptedBoxData => Write()[..SIZE_STORED].ToArray();
/// <summary>
/// Rough indication if the data is junk or not.
/// </summary>
@ -49,17 +44,61 @@ public abstract class PKM : ISpeciesForm, ITrainerID32, IGeneration, IShiny, ILa
/// </summary>
public virtual void PrepareNickname() { }
protected abstract byte[] Encrypt();
public abstract EntityContext Context { get; }
public byte Format => Context.Generation;
public TrainerIDFormat TrainerIDDisplayFormat => this.GetTrainerIDFormat();
private Span<byte> Write()
/// <summary> Writes the entity data to a sequential (stored only, no party stats) buffer destination. </summary>
public virtual int WriteDecryptedDataStored(Span<byte> destination)
{
RefreshChecksum();
return Data;
int length = SIZE_STORED;
Data[..length].CopyTo(destination);
return length;
}
/// <summary> Writes the entity data to a sequential (stored, party) buffer destination. </summary>
public virtual void WriteDecryptedDataParty(Span<byte> destination)
{
var stored = destination[..SIZE_STORED];
var party = destination[SIZE_STORED..SIZE_PARTY];
WriteDecryptedDataParty(stored, party);
}
/// <summary> Writes the entity data to a separate (stored, party) buffer destination. </summary>
public virtual void WriteDecryptedDataParty(Span<byte> stored, Span<byte> party)
{
WriteDecryptedDataStored(stored);
Data[SIZE_STORED..SIZE_PARTY].CopyTo(party);
}
/// <summary> Writes the entity data to a sequential (stored only, no party stats) buffer destination and encrypts to the at-rest state. </summary>
public virtual void WriteEncryptedDataStored(Span<byte> destination)
{
var stored = destination[..SIZE_STORED];
WriteDecryptedDataStored(stored);
EncryptStored(stored);
}
/// <summary> Writes the entity data to a sequential (stored, party) buffer destination and encrypts to the at-rest state. </summary>
public virtual void WriteEncryptedDataParty(Span<byte> destination)
{
var stored = destination[..SIZE_STORED];
var party = destination[SIZE_STORED..SIZE_PARTY];
WriteEncryptedDataParty(stored, party);
}
/// <summary> Writes the entity data to a separate (stored, party) buffer destination and encrypts to the at-rest state. </summary>
public virtual void WriteEncryptedDataParty(Span<byte> stored, Span<byte> party)
{
WriteDecryptedDataParty(stored, party);
EncryptStored(stored);
EncryptParty(party);
}
protected abstract void EncryptStored(Span<byte> stored);
protected abstract void EncryptParty(Span<byte> party);
// Surface Properties
public abstract ushort Species { get; set; }
public abstract string Nickname { get; set; }
@ -1163,4 +1202,23 @@ public void ClearInvalidMoves()
5 => IV_SPD,
_ => throw new ArgumentOutOfRangeException(nameof(index), index, "IV index must be between 0 and 5."),
};
/// <summary>
/// Checks if the current <see cref="PKM"/> has the same stored data as another <see cref="PKM"/>. This is used to check if a PKM has been modified from its original imported state.
/// </summary>
public virtual bool EqualsStored(PKM pk)
{
// Generally, the objects should be of the same derived type. Don't bother checking that explicitly.
if (pk.PID != PID)
return false;
var stored = pk.Data;
if (stored.Length >= pk.SIZE_STORED)
stored = stored[..SIZE_STORED];
var self = Data;
if (self.Length >= SIZE_STORED)
self = self[..SIZE_STORED];
return stored.SequenceEqual(self);
}
}

View File

@ -28,7 +28,7 @@ public sealed class RK4 : G4PKM
private static Memory<byte> Decrypt(Memory<byte> data)
{
data = data[..PokeCrypto.SIZE_4RSTORED];
PokeCrypto.DecryptIfEncrypted45(ref data);
PokeCrypto.DecryptIfEncrypted45(data.Span);
return data;
}
@ -327,17 +327,6 @@ public override string HandlingTrainerName
#endregion
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
byte[] data = Data.ToArray();
byte[] pkData = data[..PokeCrypto.SIZE_4STORED];
pkData = PokeCrypto.EncryptArray45(pkData);
pkData.CopyTo(data, 0);
return data;
}
public PK4 ConvertToPK4()
{
byte[] data = Data[..PokeCrypto.SIZE_4STORED].ToArray();
@ -358,4 +347,7 @@ public override int GetStringTerminatorIndex(ReadOnlySpan<byte> data)
public override int GetStringLength(ReadOnlySpan<byte> data)
=> TrashBytesUTF16.GetStringLength(data, StringConverter4.Terminator);
public override int GetBytesPerChar() => 2;
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt45(stored[..SIZE_STORED]);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party[..(SIZE_STORED-SIZE_PARTY)], PID);
}

View File

@ -30,8 +30,6 @@ public override SK2 Clone() => new(Data.ToArray(), Japanese)
IsEgg = IsEgg,
};
protected override byte[] Encrypt() => Data.ToArray();
#region Stored Attributes
public override ushort Species { get => Data[0]; set => Data[0] = (byte)value; }
public override int SpriteItem => ItemConverter.GetItemFuture2((byte)HeldItem);

View File

@ -11,6 +11,8 @@ public abstract class G4PKM : PKM, IHandlerUpdate,
{
protected G4PKM(Memory<byte> data) : base(data) { }
protected G4PKM([ConstantExpected] int size) : base(size) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt45(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
// Maximums
public sealed override ushort MaxMoveID => Legal.MaxMoveID_4;

View File

@ -10,6 +10,8 @@ public abstract class G6PKM : PKM, ISanityChecksum, IHandlerUpdate
public override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected G6PKM(Memory<byte> data) : base(data) { }
protected G6PKM([ConstantExpected] int size) : base(size) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt67(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
// Trash Bytes
public sealed override Span<byte> NicknameTrash => Data.Slice(0x40, 26);
@ -48,11 +50,6 @@ public byte OppositeFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected sealed override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray6(Data);
}
// General User-error Fixes
public void FixRelearn()

View File

@ -11,10 +11,12 @@ public abstract class G8PKM : PKM, ISanityChecksum,
{
protected G8PKM() : base(PokeCrypto.SIZE_8PARTY) { }
protected G8PKM(Memory<byte> data) : base(DecryptParty(data)) { }
protected override void EncryptStored(Span<byte> stored) => PokeCrypto.Encrypt8(stored);
protected override void EncryptParty(Span<byte> party) => PokeCrypto.CryptArray(party, PID);
private static Memory<byte> DecryptParty(Memory<byte> data)
{
PokeCrypto.DecryptIfEncrypted8(ref data);
PokeCrypto.DecryptIfEncrypted8(data.Span);
if (data.Length >= PokeCrypto.SIZE_8PARTY)
return data;
@ -62,11 +64,6 @@ public override byte CurrentFriendship
public override int Characteristic => EntityCharacteristic.GetCharacteristicInit0(EncryptionConstant, IV32);
// Methods
protected override byte[] Encrypt()
{
RefreshChecksum();
return PokeCrypto.EncryptArray8(Data);
}
public void FixRelearn()
{

View File

@ -23,10 +23,8 @@ public abstract class GBPKM : PKM
protected GBPKM([ConstantExpected] int size) : base(size) { }
protected GBPKM(Memory<byte> data) : base(data) { }
public sealed override byte[] EncryptedPartyData => Encrypt();
public sealed override byte[] EncryptedBoxData => Encrypt();
public sealed override byte[] DecryptedBoxData => Encrypt();
public sealed override byte[] DecryptedPartyData => Encrypt();
protected override void EncryptStored(Span<byte> stored) { }
protected override void EncryptParty(Span<byte> party) { }
public override bool Valid { get => true; set { } }
public sealed override void RefreshChecksum() { }

View File

@ -91,4 +91,20 @@ private void SetStringKeepTerminatorStyle(ReadOnlySpan<char> value, Span<byte> e
var option = zeroed ? StringConverterOption.ClearZero : StringConverterOption.Clear50;
SetString(exist, value, value.Length, option);
}
public override bool EqualsStored(PKM pk)
{
var storedSize = Format == 1 ? PokeCrypto.SIZE_1STORED : PokeCrypto.SIZE_2STORED;
var self = Data[..storedSize];
var other = pk.Data[..storedSize];
if (!self.SequenceEqual(other))
return false;
// Compare string buffers as well, since they are stored separately in Gen 1 & 2 formats.
if (!NicknameTrash.SequenceEqual(pk.NicknameTrash))
return false;
if (!OriginalTrainerTrash.SequenceEqual(pk.OriginalTrainerTrash))
return false;
return true;
}
}

View File

@ -59,6 +59,8 @@ public static int CountPresent(ReadOnlySpan<byte> input, int capacity, int lerp
private static bool IsJapaneseList(int length) => length == PokeCrypto.SIZE_1JLIST;
private static bool IsJapaneseString(int length) => length == GBPKML.StringLengthJapanese;
public static int GetListLength(int capacity, int sizeBody, int stringLength) => 1 + (capacity + 1) + (sizeBody * capacity) + (stringLength * capacity * 2);
public static int GetListLength(int capacity, bool jp, bool party) => 1 + (capacity + 1) + (GetBodyLength(party) * capacity) + (GetStringLength(jp) * capacity * 2);
public static int GetListLengthSingle(bool jp) => jp ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
private static int GetBodyLength(bool party) => party ? PokeCrypto.SIZE_1PARTY : PokeCrypto.SIZE_1STORED;
private static int GetStringLength(bool jp) => jp ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
@ -111,6 +113,9 @@ public static PK1 ReadFromSingle(ReadOnlySpan<byte> input)
/// <param name="index">Entity index to read</param>
public static PK1 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int capacity = 1, bool isParty = true, int index = 0)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var start = 1 + (capacity + 1);
int sizeBody = GetBodyLength(isParty);
@ -133,12 +138,15 @@ public static PK1 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int c
/// <param name="capacity">Count of slots allowed in the list</param>
/// <param name="isParty">List stores party stats for each entity</param>
/// <param name="index">Entity index to write</param>
public static void WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool isParty = true, int index = 0)
public static int WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool isParty = true, int index = 0)
{
var start = 1 + (capacity + 1);
var sizeBody = GetBodyLength(isParty);
var stringLength = pk.OriginalTrainerTrash.Length;
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var ofsBody = start + (sizeBody * index);
var ofsStr1 = start + (sizeBody * capacity) + (stringLength * index);
var ofsStr2 = ofsStr1 + (capacity * stringLength);
@ -154,6 +162,9 @@ public static void WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool
output[1 + index] = GetHeaderIdentifierMark(pk);
output[0] = (byte)CountPresent(output, capacity);
output[1 + capacity] = SlotEmpty; // cap off the list
// indicate the byte length of the list for the given parameters
return GetListLength(capacity, sizeBody, stringLength);
}
/// <summary>
@ -166,6 +177,8 @@ public static void WriteToList(Span<byte> output, PK1 pk, int capacity = 1, bool
/// <param name="isParty">List stores party stats for each entity</param>
public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
var lengthBody = GetBodyLength(isParty);
var lengthParty = GetBodyLength(true);
@ -208,6 +221,8 @@ public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int strin
/// <param name="isDestInitialized">True if the destination list is initialized</param>
public static bool MergeSingles(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty, bool isDestInitialized = true)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
// Collect the count of set slots
var jp = IsJapaneseString(stringLength);
var size = GetListLengthSingle(jp);
@ -270,7 +285,7 @@ public static byte[] WrapSingle(PK1 pk)
/// </summary>
/// <param name="pk">Entity to wrap</param>
/// <param name="output">Destination to write the single-slot list</param>
public static void WrapSingle(PK1 pk, Span<byte> output) => WriteToList(output, pk);
public static int WrapSingle(PK1 pk, Span<byte> output) => WriteToList(output, pk);
public static void UnpackNOB(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, bool isParty = false)
{

View File

@ -63,6 +63,8 @@ private static int CountPresent(ReadOnlySpan<byte> input, int capacity, int lerp
private static bool IsJapaneseList(int length) => length == PokeCrypto.SIZE_2JLIST;
private static bool IsJapaneseString(int length) => length == GBPKML.StringLengthJapanese;
public static int GetListLength(int capacity, int sizeBody, int stringLength) => 1 + (capacity + 1) + (sizeBody * capacity) + (stringLength * capacity * 2);
public static int GetListLength(int capacity, bool jp, bool party) => 1 + (capacity + 1) + (GetBodyLength(party) * capacity) + (GetStringLength(jp) * capacity * 2);
public static int GetListLengthSingle(bool jp) => jp ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
private static int GetBodyLength(bool party) => party ? PokeCrypto.SIZE_2PARTY : PokeCrypto.SIZE_2STORED;
private static int GetStringLength(bool jp) => jp ? GBPKML.StringLengthJapanese : GBPKML.StringLengthNotJapan;
@ -115,6 +117,9 @@ public static PK2 ReadFromSingle(ReadOnlySpan<byte> input)
/// <param name="index">Entity index to read</param>
public static PK2 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int capacity = 1, bool isParty = true, int index = 0)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var start = 1 + (capacity + 1);
var sizeBody = GetBodyLength(isParty);
@ -137,12 +142,15 @@ public static PK2 ReadFromList(ReadOnlySpan<byte> input, int stringLength, int c
/// <param name="capacity">Count of slots allowed in the list</param>
/// <param name="isParty">List stores party stats for each entity</param>
/// <param name="index">Entity index to write</param>
public static void WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool isParty = true, int index = 0)
public static int WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool isParty = true, int index = 0)
{
var start = 1 + (capacity + 1);
var sizeBody = GetBodyLength(isParty);
var stringLength = pk.OriginalTrainerTrash.Length;
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)index, (uint)capacity);
var ofsBody = start + (sizeBody * index);
var ofsStr1 = start + (sizeBody * capacity) + (stringLength * index);
var ofsStr2 = ofsStr1 + (capacity * stringLength);
@ -158,6 +166,9 @@ public static void WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool
output[1 + index] = GetHeaderIdentifierMark(pk);
output[0] = (byte)CountPresent(output, capacity);
output[1 + capacity] = SlotEmpty; // cap off the list
// indicate the byte length of the list for the given parameters
return GetListLength(capacity, sizeBody, stringLength);
}
/// <summary>
@ -170,6 +181,8 @@ public static void WriteToList(Span<byte> output, PK2 pk, int capacity = 1, bool
/// <param name="isParty">List stores party stats for each entity</param>
public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
var lengthBody = GetBodyLength(isParty);
var lengthParty = GetBodyLength(true);
@ -211,6 +224,8 @@ public static void Unpack(ReadOnlySpan<byte> input, Span<byte> output, int strin
/// <param name="isParty">List stores party stats for each entity</param>
public static bool MergeSingles(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, int capacity, bool isParty)
{
ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual((uint)capacity - 1, IsJapaneseString(stringLength) ? 30u : 20u);
// Collect the count of set slots
var jp = IsJapaneseString(stringLength);
var size = GetListLengthSingle(jp);
@ -273,7 +288,7 @@ public static byte[] WrapSingle(PK2 pk)
/// </summary>
/// <param name="pk">Entity to wrap</param>
/// <param name="output">Destination to write the single-slot list</param>
public static void WrapSingle(PK2 pk, Span<byte> output) => WriteToList(output, pk);
public static int WrapSingle(PK2 pk, Span<byte> output) => WriteToList(output, pk);
public static void UnpackNOB(ReadOnlySpan<byte> input, Span<byte> output, int stringLength, bool isParty = false)
{

View File

@ -25,8 +25,10 @@ public static EntityFormatDetected GetFormat(ReadOnlySpan<byte> data)
private static EntityFormatDetected GetFormatInternal(ReadOnlySpan<byte> data) => data.Length switch
{
SIZE_1JLIST or SIZE_1ULIST => FormatPK1,
SIZE_2JLIST or SIZE_2ULIST => FormatPK2,
SIZE_1JLIST or SIZE_1ULIST => FormatPK1List,
SIZE_2JLIST or SIZE_2ULIST => FormatPK2List,
SIZE_1PARTY or SIZE_1STORED => FormatPK1,
SIZE_2PARTY or SIZE_2STORED => FormatPK2,
SIZE_2STADIUM => FormatSK2,
SIZE_3PARTY or SIZE_3STORED => FormatPK3,
SIZE_3CSTORED => FormatCK3,
@ -120,8 +122,10 @@ private static bool IsFormatReally9(PK8 pk)
private static PKM? GetFromBytes(Memory<byte> data, EntityFormatDetected format, EntityContext prefer) => format switch
{
FormatPK1 => PokeList1.ReadFromSingle(data.Span),
FormatPK2 => PokeList2.ReadFromSingle(data.Span),
FormatPK1List => PokeList1.ReadFromSingle(data.Span),
FormatPK2List => PokeList2.ReadFromSingle(data.Span),
FormatPK1 => new PK1(data),
FormatPK2 => new PK2(data),
FormatSK2 => new SK2(data),
FormatPK3 => new PK3(data),
FormatCK3 => new CK3(data),
@ -222,6 +226,8 @@ public enum EntityFormatDetected
{
None,
FormatPK1List, FormatPK2List,
FormatPK1,
FormatPK2, FormatSK2,
FormatPK3, FormatCK3, FormatXK3,

View File

@ -53,10 +53,6 @@ public static class PokeCrypto
internal const int SIZE_8APARTY = SIZE_8ASTORED + 0x10; // 0x178
private const int SIZE_8ABLOCK = 88; // 0x58
internal const int SIZE_9STORED = SIZE_8STORED;
internal const int SIZE_9PARTY = SIZE_8PARTY;
private const int SIZE_9BLOCK = SIZE_8BLOCK;
private const int BlockCount = 4;
/// <summary>
@ -64,40 +60,16 @@ public static class PokeCrypto
/// </summary>
private static ReadOnlySpan<byte> BlockPosition =>
[
0, 1, 2, 3,
0, 1, 3, 2,
0, 2, 1, 3,
0, 3, 1, 2,
0, 2, 3, 1,
0, 3, 2, 1,
1, 0, 2, 3,
1, 0, 3, 2,
2, 0, 1, 3,
3, 0, 1, 2,
2, 0, 3, 1,
3, 0, 2, 1,
1, 2, 0, 3,
1, 3, 0, 2,
2, 1, 0, 3,
3, 1, 0, 2,
2, 3, 0, 1,
3, 2, 0, 1,
1, 2, 3, 0,
1, 3, 2, 0,
2, 1, 3, 0,
3, 1, 2, 0,
2, 3, 1, 0,
3, 2, 1, 0,
0, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 3, 1, 2,
0, 2, 3, 1, 0, 3, 2, 1, 1, 0, 2, 3, 1, 0, 3, 2,
2, 0, 1, 3, 3, 0, 1, 2, 2, 0, 3, 1, 3, 0, 2, 1,
1, 2, 0, 3, 1, 3, 0, 2, 2, 1, 0, 3, 3, 1, 0, 2,
2, 3, 0, 1, 3, 2, 0, 1, 1, 2, 3, 0, 1, 3, 2, 0,
2, 1, 3, 0, 3, 1, 2, 0, 2, 3, 1, 0, 3, 2, 1, 0,
// duplicates of 0-7 to eliminate modulus
0, 1, 2, 3,
0, 1, 3, 2,
0, 2, 1, 3,
0, 3, 1, 2,
0, 2, 3, 1,
0, 3, 2, 1,
1, 0, 2, 3,
1, 0, 3, 2,
// duplicates of 0-7 to eliminate modulus (32 => 24)
0, 1, 2, 3, 0, 1, 3, 2, 0, 2, 1, 3, 0, 3, 1, 2,
0, 2, 3, 1, 0, 3, 2, 1, 1, 0, 2, 3, 1, 0, 3, 2,
];
/// <summary>
@ -105,232 +77,251 @@ public static class PokeCrypto
/// </summary>
private static ReadOnlySpan<byte> BlockPositionInvert =>
[
0, 1, 2, 4, 3, 5, 6, 7, 12, 18, 13, 19, 8, 10, 14, 20, 16, 22, 9, 11, 15, 21, 17, 23,
0, 1, 2, 4, 3, 5, 6, 7, // duplicates of 0-7 to eliminate modulus
00, 01, 02, 04,
03, 05, 06, 07,
12, 18, 13, 19,
08, 10, 14, 20,
16, 22, 09, 11,
15, 21, 17, 23,
// duplicates of 0-7 to eliminate modulus (32 => 24)
00, 01, 02, 04,
03, 05, 06, 07,
];
/// <summary>
/// Shuffles a 4-block byte array containing Pokémon data.
/// Decrypts an 80 byte format Generation 3 Pokémon byte array.
/// </summary>
/// <param name="data">Data to shuffle</param>
/// <param name="sv">Block Shuffle order</param>
/// <param name="blockSize">Size of shuffling chunks</param>
/// <returns>Shuffled byte array</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte[] ShuffleArray(ReadOnlySpan<byte> data, uint sv, [ConstantExpected(Min = 0)] int blockSize)
/// <param name="data">Encrypted data.</param>
/// <returns>Decrypted data.</returns>
public static void Decrypt3(Span<byte> data)
{
byte[] sdata = new byte[data.Length];
ShuffleArray(data, sdata, sv, blockSize);
return sdata;
}
uint PID = ReadUInt32LittleEndian(data);
uint OID = ReadUInt32LittleEndian(data[4..]);
uint seed = PID ^ OID;
uint sv = PID % 24;
private static void ShuffleArray(ReadOnlySpan<byte> data, Span<byte> result, uint sv, [ConstantExpected(Min = 0)] int blockSize)
{
int index = (int)sv * BlockCount;
const int start = 8;
data[..start].CopyTo(result[..start]);
var end = start + (blockSize * BlockCount);
data[end..].CopyTo(result[end..]);
for (int block = 0; block < BlockCount; block++)
{
var dest = result.Slice(start + (blockSize * block), blockSize);
int ofs = BlockPosition[index + block];
var src = data.Slice(start + (blockSize * ofs), blockSize);
src.CopyTo(dest);
}
var shuffle = data[SIZE_3HEADER..SIZE_3STORED];
CryptArray3(shuffle, seed);
Shuffle3(shuffle, sv);
}
/// <summary>
/// Decrypts a Gen8 pk byte array.
/// Encrypts an 80 byte format Generation 3 Pokémon byte array.
/// </summary>
/// <param name="ekm">Encrypted Pokémon data.</param>
/// <param name="data">Decrypted data.</param>
/// <returns>Encrypted data.</returns>
public static void Encrypt3(Span<byte> data)
{
uint PID = ReadUInt32LittleEndian(data);
uint OID = ReadUInt32LittleEndian(data[4..]);
uint seed = PID ^ OID;
uint sv = PID % 24;
sv = BlockPositionInvert[(int)sv];
var shuffle = data[SIZE_3HEADER..SIZE_3STORED];
Shuffle3(shuffle, sv);
CryptArray3(shuffle, seed);
}
/// <summary>
/// Decrypts a 136 byte array from Battle Revolution (Gen4).
/// </summary>
/// <param name="data">Encrypted Pokémon data.</param>
/// <returns>Decrypted Pokémon data.</returns>
/// <returns>Encrypted Pokémon data.</returns>
public static byte[] DecryptArray8(Span<byte> ekm)
public static void Decrypt4BE(Span<byte> data)
{
uint pv = ReadUInt32LittleEndian(ekm);
Debug.Assert(data.Length is SIZE_4STORED);
uint pv = ReadUInt32BigEndian(data);
uint sv = (pv >> 13) & 31;
CryptPKM(ekm, pv, SIZE_8BLOCK);
return ShuffleArray(ekm, sv, SIZE_8BLOCK);
var shuffle = data[8..SIZE_4STORED];
// No encryption applied at rest.
Shuffle45(shuffle, sv);
}
/// <summary>
/// Decrypts a Gen8 pk byte array.
/// Encrypts a 136 byte array from Battle Revolution (Gen4).
/// </summary>
/// <param name="ekm">Encrypted Pokémon data.</param>
/// <param name="data">Decrypted Pokémon data.</param>
/// <returns>Encrypted Pokémon data.</returns>
public static void Encrypt4BE(Span<byte> data)
{
Debug.Assert(data.Length is SIZE_4STORED);
uint pv = ReadUInt32BigEndian(data);
uint sv = (pv >> 13) & 31;
sv = BlockPositionInvert[(int)sv];
var shuffle = data[8..SIZE_4STORED];
// No encryption applied at rest.
Shuffle45(shuffle, sv);
}
/// <summary>
/// Decrypts a 136 byte + party stat byte array.
/// </summary>
/// <param name="data">Encrypted Pokémon data.</param>
/// <returns>Decrypted Pokémon data.</returns>
public static void Decrypt45(Span<byte> data)
{
Debug.Assert(data.Length is SIZE_4STORED or SIZE_4PARTY or SIZE_5PARTY);
uint pv = ReadUInt32LittleEndian(data);
uint chk = ReadUInt16LittleEndian(data[6..]);
uint sv = (pv >> 13) & 31;
var shuffle = data[8..SIZE_4STORED];
CryptArray(shuffle, chk);
if (data.Length > SIZE_4STORED) // Party Stats
CryptArray(data[SIZE_4STORED..], pv);
Shuffle45(shuffle, sv);
}
/// <summary>
/// Encrypts a 136 byte + party stat byte array.
/// </summary>
/// <param name="data">Decrypted Pokémon data.</param>
/// <returns>Encrypted Pokémon data.</returns>
public static byte[] DecryptArray8A(Span<byte> ekm)
public static void Encrypt45(Span<byte> data)
{
uint pv = ReadUInt32LittleEndian(ekm);
Debug.Assert(data.Length is SIZE_4STORED or SIZE_4PARTY or SIZE_5PARTY);
uint pv = ReadUInt32LittleEndian(data);
uint chk = ReadUInt16LittleEndian(data[6..]);
uint sv = (pv >> 13) & 31;
sv = BlockPositionInvert[(int)sv];
CryptPKM(ekm, pv, SIZE_8ABLOCK);
return ShuffleArray(ekm, sv, SIZE_8ABLOCK);
}
/// <summary>
/// Decrypts a Gen9 pk byte array.
/// </summary>
/// <param name="ekm">Encrypted Pokémon data.</param>
/// <returns>Decrypted Pokémon data.</returns>
/// <returns>Encrypted Pokémon data.</returns>
public static byte[] DecryptArray9(Span<byte> ekm)
{
uint pv = ReadUInt32LittleEndian(ekm);
uint sv = (pv >> 13) & 31;
CryptPKM(ekm, pv, SIZE_9BLOCK);
return ShuffleArray(ekm, sv, SIZE_9BLOCK);
}
/// <summary>
/// Encrypts a Gen8 pk byte array.
/// </summary>
/// <param name="pk">Decrypted Pokémon data.</param>
public static byte[] EncryptArray8(ReadOnlySpan<byte> pk)
{
uint pv = ReadUInt32LittleEndian(pk);
uint sv = (pv >> 13) & 31;
byte[] ekm = ShuffleArray(pk, BlockPositionInvert[(int)sv], SIZE_8BLOCK);
CryptPKM(ekm, pv, SIZE_8BLOCK);
return ekm;
}
/// <summary>
/// Encrypts a Gen8 pk byte array.
/// </summary>
/// <param name="pk">Decrypted Pokémon data.</param>
public static byte[] EncryptArray8A(ReadOnlySpan<byte> pk)
{
uint pv = ReadUInt32LittleEndian(pk);
uint sv = (pv >> 13) & 31;
byte[] ekm = ShuffleArray(pk, BlockPositionInvert[(int)sv], SIZE_8ABLOCK);
CryptPKM(ekm, pv, SIZE_8ABLOCK);
return ekm;
}
/// <summary>
/// Encrypts a Gen9 pk byte array.
/// </summary>
/// <param name="pk">Decrypted Pokémon data.</param>
public static byte[] EncryptArray9(ReadOnlySpan<byte> pk)
{
uint pv = ReadUInt32LittleEndian(pk);
uint sv = (pv >> 13) & 31;
byte[] ekm = ShuffleArray(pk, BlockPositionInvert[(int)sv], SIZE_9BLOCK);
CryptPKM(ekm, pv, SIZE_9BLOCK);
return ekm;
var shuffle = data[8..SIZE_4STORED];
Shuffle45(shuffle, sv);
CryptArray(shuffle, chk);
if (data.Length > SIZE_4STORED) // Party Stats
CryptArray(data[SIZE_4STORED..], pv);
}
/// <summary>
/// Decrypts a 232 byte + party stat byte array.
/// </summary>
/// <param name="ekm">Encrypted Pokémon data.</param>
/// <param name="data">Encrypted Pokémon data.</param>
/// <returns>Decrypted Pokémon data.</returns>
/// <returns>Encrypted Pokémon data.</returns>
public static byte[] DecryptArray6(Span<byte> ekm)
public static void Decrypt67(Span<byte> data)
{
uint pv = ReadUInt32LittleEndian(ekm);
Debug.Assert(data.Length is SIZE_6STORED or SIZE_6PARTY);
uint pv = ReadUInt32LittleEndian(data);
uint sv = (pv >> 13) & 31;
CryptPKM(ekm, pv, SIZE_6BLOCK);
return ShuffleArray(ekm, sv, SIZE_6BLOCK);
var shuffle = data[8..SIZE_6STORED];
CryptArray(shuffle, pv);
if (data.Length > SIZE_6STORED) // Party Stats
CryptArray(data[SIZE_6STORED..], pv);
Shuffle67(shuffle, sv);
}
/// <summary>
/// Encrypts a 232 byte + party stat byte array.
/// </summary>
/// <param name="pk">Decrypted Pokémon data.</param>
public static byte[] EncryptArray6(ReadOnlySpan<byte> pk)
/// <param name="data">Decrypted Pokémon data.</param>
public static void Encrypt67(Span<byte> data)
{
uint pv = ReadUInt32LittleEndian(pk);
uint sv = (pv >> 13) & 31;
Debug.Assert(data.Length is SIZE_6STORED or SIZE_6PARTY);
byte[] ekm = ShuffleArray(pk, BlockPositionInvert[(int)sv], SIZE_6BLOCK);
CryptPKM(ekm, pv, SIZE_6BLOCK);
return ekm;
uint pv = ReadUInt32LittleEndian(data);
uint sv = (pv >> 13) & 31;
sv = BlockPositionInvert[(int)sv];
var shuffle = data[8..SIZE_6STORED];
Shuffle67(shuffle, sv);
CryptArray(shuffle, pv);
if (data.Length > SIZE_6STORED) // Party Stats
CryptArray(data[SIZE_6STORED..], pv);
}
/// <summary>
/// Decrypts a 136 byte + party stat byte array.
/// Decrypts a Gen8 pk byte array.
/// </summary>
/// <param name="ekm">Encrypted Pokémon data.</param>
/// <param name="data">Encrypted Pokémon data.</param>
/// <returns>Decrypted Pokémon data.</returns>
public static byte[] DecryptArray45(Span<byte> ekm)
public static void Decrypt8(Span<byte> data)
{
uint pv = ReadUInt32LittleEndian(ekm);
uint chk = ReadUInt16LittleEndian(ekm[6..]);
Debug.Assert(data.Length is SIZE_8STORED or SIZE_8PARTY);
uint pv = ReadUInt32LittleEndian(data);
uint sv = (pv >> 13) & 31;
CryptPKM45(ekm, pv, chk, SIZE_4BLOCK);
return ShuffleArray(ekm, sv, SIZE_4BLOCK);
var shuffle = data[8..SIZE_8STORED];
CryptArray(shuffle, pv);
if (data.Length > SIZE_8STORED) // Party Stats
CryptArray(data[SIZE_8STORED..], pv); // Party Stats
Shuffle8(shuffle, sv);
}
/// <summary>
/// Encrypts a 136 byte + party stat byte array.
/// Encrypts a Gen8 pk byte array.
/// </summary>
/// <param name="pk">Decrypted Pokémon data.</param>
/// <returns>Encrypted Pokémon data.</returns>
public static byte[] EncryptArray45(ReadOnlySpan<byte> pk)
/// <param name="data">Decrypted Pokémon data.</param>
public static void Encrypt8(Span<byte> data)
{
uint pv = ReadUInt32LittleEndian(pk);
uint chk = ReadUInt16LittleEndian(pk[6..]);
uint sv = (pv >> 13) & 31;
Debug.Assert(data.Length is SIZE_8STORED or SIZE_8PARTY);
byte[] ekm = ShuffleArray(pk, BlockPositionInvert[(int)sv], SIZE_4BLOCK);
CryptPKM45(ekm, pv, chk, SIZE_4BLOCK);
return ekm;
uint pv = ReadUInt32LittleEndian(data);
uint sv = (pv >> 13) & 31;
sv = BlockPositionInvert[(int)sv];
var shuffle = data[8..SIZE_8STORED];
Shuffle8(shuffle, sv);
CryptArray(shuffle, pv);
if (data.Length > SIZE_8STORED) // Party Stats
CryptArray(data[SIZE_8STORED..], pv); // Party Stats
}
/// <summary>
/// Decrypts a 136 byte + party stat byte array.
/// Decrypts a PA8 byte array.
/// </summary>
/// <param name="ekm">Encrypted Pokémon data.</param>
/// <param name="data">Encrypted Pokémon data.</param>
/// <returns>Decrypted Pokémon data.</returns>
public static byte[] DecryptArray4BE(ReadOnlySpan<byte> ekm)
public static void Decrypt8A(Span<byte> data)
{
uint pv = ReadUInt32BigEndian(ekm);
Debug.Assert(data.Length is SIZE_8ASTORED or SIZE_8APARTY);
uint pv = ReadUInt32LittleEndian(data);
uint sv = (pv >> 13) & 31;
return ShuffleArray(ekm, sv, SIZE_4BLOCK);
var shuffle = data[8..SIZE_8ASTORED];
CryptArray(shuffle, pv);
if (data.Length > SIZE_8ASTORED)
CryptArray(data[SIZE_8ASTORED..], pv); // Party Stats
Shuffle8A(shuffle, sv);
}
/// <summary>
/// Encrypts a 136 byte + party stat byte array.
/// Encrypts a PA8 byte array.
/// </summary>
/// <param name="pk">Decrypted Pokémon data.</param>
/// <returns>Encrypted Pokémon data.</returns>
public static byte[] EncryptArray4BE(ReadOnlySpan<byte> pk)
/// <param name="data">Decrypted Pokémon data.</param>
public static void Encrypt8A(Span<byte> data)
{
uint pv = ReadUInt32BigEndian(pk);
Debug.Assert(data.Length is SIZE_8ASTORED or SIZE_8APARTY);
uint pv = ReadUInt32LittleEndian(data);
uint sv = (pv >> 13) & 31;
return ShuffleArray(pk, BlockPositionInvert[(int)sv], SIZE_4BLOCK);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CryptPKM(Span<byte> data, uint pv, [ConstantExpected(Min = 0)] int blockSize)
{
const int start = 8;
int end = (BlockCount * blockSize) + start;
CryptArray(data[start..end], pv); // Blocks
if (data.Length > end)
CryptArray(data[end..], pv); // Party Stats
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CryptPKM45(Span<byte> data, uint pv, uint chk, [ConstantExpected(Min = 0)] int blockSize)
{
const int start = 8;
int end = (BlockCount * blockSize) + start;
CryptArray(data[start..end], chk); // Blocks
if (data.Length > end)
CryptArray(data[end..], pv); // Party Stats
sv = BlockPositionInvert[(int)sv];
var shuffle = data[8..SIZE_8ASTORED];
Shuffle8A(shuffle, sv);
CryptArray(shuffle, pv);
if (data.Length > SIZE_8ASTORED) // Party Stats
CryptArray(data[SIZE_8ASTORED..], pv);
}
/// <summary>
/// Encrypts or Decrypts the input <see cref="data"/> using the provided <see cref="seed"/>.
/// </summary>
/// <param name="data">The byte array to be encrypted or decrypted. Must be a multiple of 2 bytes in length.</param>
/// <param name="seed">
/// The seed used for encryption or decryption.
/// For Gen3 entities, this is the XOR of PID and OID.
/// For Gen4/5 entities, this is the checksum for the main data, and the PV for the party stats encryption as well.
/// For Gen6+ entities, this is the Personality Value (PV) used for both the main data and party stats encryption.
/// </param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void CryptArray(Span<byte> data, uint seed)
{
@ -344,141 +335,149 @@ public static void CryptArray(Span<byte> data, uint seed)
}
}
/// <summary>
/// Decrypts an 80 byte format Generation 3 Pokémon byte array.
/// </summary>
/// <param name="ekm">Encrypted data.</param>
/// <returns>Decrypted data.</returns>
public static byte[] DecryptArray3(Span<byte> ekm)
{
Debug.Assert(ekm.Length is SIZE_3PARTY or SIZE_3STORED);
uint PID = ReadUInt32LittleEndian(ekm);
uint OID = ReadUInt32LittleEndian(ekm[4..]);
uint seed = PID ^ OID;
CryptArray3(ekm, seed);
return ShuffleArray3(ekm, PID % 24);
}
private static void CryptArray3(Span<byte> ekm, uint seed)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void CryptArray3(Span<byte> data, 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))
foreach (ref var u32 in MemoryMarshal.Cast<byte, uint>(data))
u32 ^= seed;
}
/// <summary>
/// Shuffles an 80 byte format Generation 3 Pokémon byte array.
/// </summary>
/// <param name="data">Un-shuffled data.</param>
/// <param name="sv">Block order shuffle value</param>
/// <returns>Un-shuffled data.</returns>
private static byte[] ShuffleArray3(ReadOnlySpan<byte> data, uint sv)
private static void Shuffle3(Span<byte> data, uint sv) => Shuffle<uint>(data, sv, SIZE_3BLOCK);
private static void Shuffle45(Span<byte> data, uint sv) => Shuffle<ulong>(data, sv, SIZE_4BLOCK);
private static void Shuffle67(Span<byte> data, uint sv) => Shuffle<ulong>(data, sv, SIZE_6BLOCK);
private static void Shuffle8(Span<byte> data, uint sv) => Shuffle<ulong>(data, sv, SIZE_8BLOCK);
private static void Shuffle8A(Span<byte> data, uint sv) => Shuffle<ulong>(data, sv, SIZE_8ABLOCK);
private static void Shuffle<T>(Span<byte> data, uint sv, [ConstantExpected(Min = 0)] int blockSizeBytes) where T : unmanaged
{
byte[] sdata = new byte[data.Length];
ShuffleArray3(data, sdata, sv);
return sdata;
if (sv == 0)
return;
var size = Unsafe.SizeOf<T>(); // JIT constant-folded
var count = blockSizeBytes / size; // number of T-elements per block
Span<byte> perm = stackalloc byte[BlockCount];
Span<byte> slotOf = stackalloc byte[BlockCount];
// build current layout and reverse map (identity)
for (byte s = 0; s < BlockCount; s++)
perm[s] = slotOf[s] = s;
var shuffle = BlockPosition.Slice((int)sv * BlockCount, BlockCount);
// Perform shuffle, let JIT unroll.
var u = MemoryMarshal.Cast<byte, T>(data);
for (byte i = 0; i < BlockCount - 1; i++)
{
var desired = shuffle[i];
var j = slotOf[desired]; // O(1)
if (j == i)
continue;
SwapBlocks(u, i * count, j * count, count);
// reflect permutation, update reverse map
// no need to update processed indexes - they won't be used in future loops.
// we don't care to book-keep the full state of the perm, just the location of unprocessed blocks
var blockAtI = perm[i];
// perm[i] = desired;
perm[j] = blockAtI;
// slotOf[desired] = i;
slotOf[blockAtI] = j;
}
}
private static void ShuffleArray3(ReadOnlySpan<byte> data, Span<byte> result, uint sv)
private static void SwapBlocks<T>(Span<T> u, int a, int b, int count) where T : unmanaged
{
int index = (int)sv * BlockCount;
data[..SIZE_3HEADER].CopyTo(result[..SIZE_3HEADER]);
data[SIZE_3STORED..].CopyTo(result[SIZE_3STORED..]);
for (int block = 0; block < BlockCount; block++)
for (int i = 0; i < count; i++)
{
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);
src.CopyTo(dest);
ref var ra = ref u[a + i];
ref var rb = ref u[b + i];
(ra, rb) = (rb, ra);
}
}
/// <summary>
/// Encrypts an 80 byte format Generation 3 Pokémon byte array.
/// Checks if the Gen3 format entity is encrypted, when the checksum of the data does not match the expected value.
/// </summary>
/// <param name="pk">Decrypted data.</param>
/// <returns>Encrypted data.</returns>
public static byte[] EncryptArray3(ReadOnlySpan<byte> pk)
public static bool IsEncrypted3(ReadOnlySpan<byte> data)
{
Debug.Assert(pk.Length is SIZE_3PARTY or SIZE_3STORED);
uint PID = ReadUInt32LittleEndian(pk);
uint OID = ReadUInt32LittleEndian(pk[4..]);
uint seed = PID ^ OID;
byte[] ekm = ShuffleArray3(pk, BlockPositionInvert[(int)(PID % 24)]);
CryptArray3(ekm, seed);
return ekm;
var calc = Checksums.Add16(data[SIZE_3HEADER..SIZE_3STORED]);
var expect = ReadUInt16LittleEndian(data[0x1C..]);
return calc != expect;
}
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// Checks if the Gen4/5 format entity is encrypted, when the unused ribbon bits are not 0.
/// </summary>
/// <remarks>Generation 3 Format encryption check which verifies the checksum</remarks>
public static void DecryptIfEncrypted3(ref Memory<byte> pk)
{
ushort chk = Checksums.Add16(pk.Span.Slice(0x20, BlockCount * SIZE_3BLOCK));
if (chk != ReadUInt16LittleEndian(pk.Span[0x1C..]))
pk = DecryptArray3(pk.Span);
}
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// </summary>
/// <remarks>Generation 4 &amp; 5 Format encryption check which checks for the unused bytes</remarks>
public static void DecryptIfEncrypted45(ref Memory<byte> pk)
{
var span = pk.Span;
if (IsEncrypted45(span))
pk = DecryptArray45(span);
}
public static bool IsEncrypted45(ReadOnlySpan<byte> data) => ReadUInt32LittleEndian(data[0x64..]) != 0;
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// Checks if the Gen6/7 format entity is encrypted, when the final terminators of the text strings are not 0.
/// </summary>
/// <remarks> Checks Nickname and Original Trainer. </remarks>
private static bool IsEncrypted67(ReadOnlySpan<byte> data) => ReadUInt16LittleEndian(data[0xC8..]) != 0 || ReadUInt16LittleEndian(data[0x58..]) != 0;
/// <summary>
/// Checks if the Gen8 format entity is encrypted, when the final terminators of the text strings are not 0.
/// </summary>
/// <remarks> Checks Nickname and Original Trainer. </remarks>
public static bool IsEncrypted8(ReadOnlySpan<byte> data) => ReadUInt16LittleEndian(data[0x70..]) != 0 || ReadUInt16LittleEndian(data[0x110..]) != 0;
/// <summary>
/// Checks if the Gen8a format entity is encrypted, when the final terminators of the text strings are not 0.
/// </summary>
/// <remarks> Checks Nickname and Original Trainer. </remarks>
public static bool IsEncrypted8A(ReadOnlySpan<byte> data) => ReadUInt16LittleEndian(data[0x78..]) != 0 || ReadUInt16LittleEndian(data[0x128..]) != 0;
/// <summary>
/// Decrypts the input <see cref="data"/> if it is encrypted.
/// </summary>
/// <remarks>Generation 3 Format encryption check which verifies the checksum</remarks>
public static void DecryptIfEncrypted3(Span<byte> data)
{
if (IsEncrypted3(data))
Decrypt3(data);
}
/// <summary>
/// Decrypts the input <see cref="data"/> if it is encrypted.
/// </summary>
/// <remarks>Generation 4 &amp; 5 Format encryption check which checks for the unused bytes</remarks>
public static void DecryptIfEncrypted45(Span<byte> data)
{
if (IsEncrypted45(data))
Decrypt45(data);
}
/// <summary>
/// Decrypts the input <see cref="data"/> if it is encrypted.
/// </summary>
/// <remarks>Generation 6 &amp; 7 Format encryption check</remarks>
public static void DecryptIfEncrypted67(ref Memory<byte> pk)
public static void DecryptIfEncrypted67(Span<byte> data)
{
var span = pk.Span;
if (ReadUInt16LittleEndian(span[0xC8..]) != 0 || ReadUInt16LittleEndian(span[0x58..]) != 0)
pk = DecryptArray6(span);
if (IsEncrypted67(data))
Decrypt67(data);
}
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// Decrypts the input <see cref="data"/> if it is encrypted.
/// </summary>
/// <remarks>Generation 8 Format encryption check</remarks>
public static void DecryptIfEncrypted8(ref Memory<byte> pk)
public static void DecryptIfEncrypted8(Span<byte> data)
{
var span = pk.Span;
if (ReadUInt16LittleEndian(span[0x70..]) != 0 || ReadUInt16LittleEndian(span[0x110..]) != 0)
pk = DecryptArray8(span);
if (IsEncrypted8(data))
Decrypt8(data);
}
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// Decrypts the input <see cref="data"/> if it is encrypted.
/// </summary>
/// <remarks>Generation 8 Format encryption check</remarks>
public static void DecryptIfEncrypted8A(ref Memory<byte> pk)
public static void DecryptIfEncrypted8A(Span<byte> data)
{
var span = pk.Span;
if (ReadUInt16LittleEndian(span[0x78..]) != 0 || ReadUInt16LittleEndian(span[0x128..]) != 0)
pk = DecryptArray8A(span);
}
/// <summary>
/// Decrypts the input <see cref="pk"/> data into a new array if it is encrypted, and updates the reference.
/// </summary>
/// <remarks>Generation 9 Format encryption check</remarks>
public static void DecryptIfEncrypted9(ref Memory<byte> pk)
{
var span = pk.Span;
if (ReadUInt16LittleEndian(span[0x70..]) != 0 || ReadUInt16LittleEndian(span[0x110..]) != 0)
pk = DecryptArray9(span);
if (IsEncrypted8A(data))
Decrypt8A(data);
}
}

View File

@ -24,6 +24,9 @@ public sealed class XK3 : G3PKM, IShadowCapture, ISeparateIVs, IGCRegion
public override XK3 Clone() => new(Data.ToArray()) { Purification = Purification, IsShadow = IsShadow };
public override void RefreshChecksum() => Valid = true;
protected override void EncryptStored(Span<byte> stored) { }
protected override void EncryptParty(Span<byte> party) { }
// Trash Bytes
public override Span<byte> OriginalTrainerTrash => Data.Slice(0x38, 22);
public Span<byte> NicknameDisplayTrash => Data.Slice(0x4E, 22);
@ -218,8 +221,6 @@ public override int EV_SPE
public bool IsShadow { get; internal set; } // determined by savedata, not written back to sav
protected override byte[] Encrypt() => Data.ToArray();
public PK3 ConvertToPK3()
{
var pk = ConvertTo<PK3>();

View File

@ -216,8 +216,8 @@ private int GetBoxRawDataOffset(int box)
// Configuration
protected override SAV1 CloneInternal() => new(GetFinalData(), (LanguageID)Language, Version);
protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
protected override int SIZE_PARTY => SIZE_STORED;
public override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_1JLIST : PokeCrypto.SIZE_1ULIST;
public override int SIZE_PARTY => SIZE_STORED;
private int SIZE_BOX_AS_SINGLES => BoxSlotCount * SIZE_STORED;
private int SIZE_BOX_LIST => (((StringLength * 2) + PokeCrypto.SIZE_1STORED + 1) * BoxSlotCount) + 2;
private int SIZE_PARTY_LIST => (((StringLength * 2) + PokeCrypto.SIZE_1PARTY + 1) * 6) + 2;
@ -472,17 +472,14 @@ public string GetBoxName(int box)
return BoxDetailNameExtensions.GetDefaultBoxName(box);
}
protected override PK1 GetPKM(byte[] data)
protected override PK1 GetPKM(Memory<byte> data)
{
if (data.Length == SIZE_STORED)
return PokeList1.ReadFromList(data, StringLength);
return PokeList1.ReadFromList(data.Span, StringLength);
return new(data);
}
protected override byte[] DecryptPKM(byte[] data)
{
return data;
}
protected override void DecryptPKM(Span<byte> data) { }
// Pokédex
protected override void SetDex(PKM pk)
@ -525,20 +522,12 @@ private void SetDexFlag(int region, ushort species, bool value)
SetFlag(region + ofs, bit & 7, value);
}
public override void WriteSlotFormatStored(PKM pk, Span<byte> data)
protected override void WriteSlotStored(PKM pk, Span<byte> data)
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
((PK1)pk).Stat_LevelBox = pk.CurrentLevel;
base.WriteSlotFormatStored(pk, data);
}
public override void WriteBoxSlot(PKM pk, Span<byte> data)
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
((PK1)pk).Stat_LevelBox = pk.CurrentLevel;
base.WriteBoxSlot(pk, data);
base.WriteSlotStored(pk, data);
}
private const int SpawnFlagCount = 0xF0;

View File

@ -38,8 +38,8 @@ public sealed class SAV1Stadium : SAV_STADIUM, IStorageCleanup
public override PK1 BlankPKM => new(Japanese);
private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLengthJ); // 0x2D
private const int SIZE_PK1U = PokeCrypto.SIZE_1STORED + (2 * StringLengthU); // 0x37
protected override int SIZE_STORED => Japanese ? SIZE_PK1J : SIZE_PK1U;
protected override int SIZE_PARTY => Japanese ? SIZE_PK1J : SIZE_PK1U;
public override int SIZE_STORED => Japanese ? SIZE_PK1J : SIZE_PK1U;
public override int SIZE_PARTY => Japanese ? SIZE_PK1J : SIZE_PK1U;
private int ListHeaderSize => Japanese ? 0x0C : 0x10;
private const int ListFooterSize = 6; // POKE + 2byte checksum
@ -134,7 +134,7 @@ private void ConditionBoxes()
var species = slice[0];
if (species == 0) // don't bother converting from internal->national
continue; // don't bother wiping already-empty slots.
WriteBoxSlot(blank, slice);
WriteSlotBox(blank, slice);
}
}
}
@ -227,34 +227,20 @@ public bool FixStoragePreWrite()
return anyShifted;
}
protected override PK1 GetPKM(byte[] data)
protected override PK1 GetPKM(Memory<byte> data)
{
var inner = data[..PokeCrypto.SIZE_1STORED];
var extra = data[PokeCrypto.SIZE_1STORED..].Span;
var pk1 = new PK1(inner, Japanese);
int len = StringLength;
var nick = data.AsSpan(PokeCrypto.SIZE_1STORED, len);
var ot = data.AsSpan(PokeCrypto.SIZE_1STORED + len, len);
var pk1 = new PK1(data[..PokeCrypto.SIZE_1STORED], Japanese);
var nick = extra[..len];
var ot = extra.Slice(len, len);
nick.CopyTo(pk1.NicknameTrash);
ot.CopyTo(pk1.OriginalTrainerTrash);
return pk1;
}
public override byte[] GetDataForFormatStored(PKM pk)
{
byte[] result = new byte[SIZE_STORED];
var gb = (PK1)pk;
var data = pk.Data;
int len = StringLength;
data.CopyTo(result);
gb.NicknameTrash.CopyTo(result.AsSpan(PokeCrypto.SIZE_1STORED));
gb.OriginalTrainerTrash.CopyTo(result.AsSpan(PokeCrypto.SIZE_1STORED + len));
return result;
}
public override byte[] GetDataForFormatParty(PKM pk) => GetDataForFormatStored(pk);
public override byte[] GetDataForParty(PKM pk) => GetDataForFormatStored(pk);
public override byte[] GetDataForBox(PKM pk) => GetDataForFormatStored(pk);
public int GetTeamOffset(int team) => Japanese ? GetTeamOffsetJ(team) : GetTeamOffsetU(team);
private int GetTeamOffsetJ(int team)
@ -372,20 +358,21 @@ public override SlotGroup GetTeam(int team)
return new SlotGroup(name, members, StorageSlotType.Box);
}
public override void WriteSlotFormatStored(PKM pk, Span<byte> data)
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
((PK1)pk).Stat_LevelBox = pk.CurrentLevel;
base.WriteSlotFormatStored(pk, data);
}
// Only box data format, no list prefix.
protected override void WriteSlotParty(PKM pk, Span<byte> data) => WriteSlotStored(pk, data);
protected override void WriteSlotBox(PKM pk, Span<byte> data) => WriteSlotStored(pk, data);
public override void WriteBoxSlot(PKM pk, Span<byte> data)
protected override void WriteSlotStored(PKM pk, Span<byte> data)
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
((PK1)pk).Stat_LevelBox = pk.CurrentLevel;
base.WriteBoxSlot(pk, data);
var gb = (PK1)pk;
gb.Stat_LevelBox = pk.CurrentLevel;
var self = pk.Data;
self[..PokeCrypto.SIZE_1STORED].CopyTo(data);
gb.NicknameTrash.CopyTo(data[PokeCrypto.SIZE_1STORED..]);
gb.OriginalTrainerTrash.CopyTo(data[(PokeCrypto.SIZE_1STORED + StringLength)..]);
}
public static bool IsStadium(ReadOnlySpan<byte> data)

View File

@ -32,8 +32,8 @@ public sealed class SAV1StadiumJ : SAV_STADIUM
public override int MaxAbilityID => Legal.MaxAbilityID_1;
public override int MaxItemID => Legal.MaxItemID_1;
private const int SIZE_PK1J = PokeCrypto.SIZE_1STORED + (2 * StringLength); // 0x2D
protected override int SIZE_STORED => SIZE_PK1J;
protected override int SIZE_PARTY => SIZE_PK1J;
public override int SIZE_STORED => SIZE_PK1J;
public override int SIZE_PARTY => SIZE_PK1J;
public override Type PKMType => typeof(PK1);
public override PK1 BlankPKM => new(true);
@ -80,35 +80,20 @@ protected override void SetBoxMetadata(int box)
// Not implemented
}
protected override PK1 GetPKM(byte[] data)
protected override PK1 GetPKM(Memory<byte> data)
{
var inner = data[..PokeCrypto.SIZE_1STORED];
var extra = data[PokeCrypto.SIZE_1STORED..].Span;
var pk1 = new PK1(inner, true);
const int len = StringLength;
var nick = data.AsSpan(0x21, len);
var ot = data.AsSpan(0x21 + len, len);
data = data[..0x21];
var pk1 = new PK1(data, true);
var nick = extra[..len];
var ot = extra.Slice(len, len);
nick.CopyTo(pk1.NicknameTrash);
ot.CopyTo(pk1.OriginalTrainerTrash);
return pk1;
}
public override byte[] GetDataForFormatStored(PKM pk)
{
byte[] result = new byte[SIZE_STORED];
var gb = (PK1)pk;
var data = pk.Data;
const int len = StringLength;
data.CopyTo(result);
gb.NicknameTrash.CopyTo(result.AsSpan(PokeCrypto.SIZE_1STORED));
gb.OriginalTrainerTrash.CopyTo(result.AsSpan(PokeCrypto.SIZE_1STORED + len));
return result;
}
public override byte[] GetDataForFormatParty(PKM pk) => GetDataForFormatStored(pk);
public override byte[] GetDataForParty(PKM pk) => GetDataForFormatStored(pk);
public override byte[] GetDataForBox(PKM pk) => GetDataForFormatStored(pk);
public override int GetBoxOffset(int box) => Box + ListHeaderSize + (box * BoxSizeJ);
public static int GetTeamOffset(int team) => 0 + (team * 2 * TeamSizeJ); // backups are after each team
@ -140,20 +125,12 @@ public override SlotGroup GetTeam(int team)
return new SlotGroup(name, members, StorageSlotType.Box);
}
public override void WriteSlotFormatStored(PKM pk, Span<byte> data)
protected override void WriteSlotStored(PKM pk, Span<byte> data)
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
((PK1)pk).Stat_LevelBox = pk.CurrentLevel;
base.WriteSlotFormatStored(pk, data);
}
public override void WriteBoxSlot(PKM pk, Span<byte> data)
{
// pk that have never been boxed have yet to save the 'current level' for box indication
// set this value at this time
((PK1)pk).Stat_LevelBox = pk.CurrentLevel;
base.WriteBoxSlot(pk, data);
base.WriteSlotStored(pk, data);
}
public static bool IsStadium(ReadOnlySpan<byte> data)

View File

@ -242,8 +242,8 @@ private ushort GetKoreanChecksum()
// Configuration
protected override SAV2 CloneInternal() => new(GetFinalData(), (LanguageID)Language, Version);
protected override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
protected override int SIZE_PARTY => SIZE_STORED;
public override int SIZE_STORED => Japanese ? PokeCrypto.SIZE_2JLIST : PokeCrypto.SIZE_2ULIST;
public override int SIZE_PARTY => SIZE_STORED;
public override PK2 BlankPKM => new(jp: Japanese);
public override Type PKMType => typeof(PK2);
@ -611,17 +611,14 @@ public void SetBoxName(int box, ReadOnlySpan<char> value)
SetString(span, deflated[..len], maxLen, StringConverterOption.Clear50);
}
protected override PK2 GetPKM(byte[] data)
protected override PK2 GetPKM(Memory<byte> data)
{
if (data.Length == SIZE_STORED)
return PokeList2.ReadFromList(data, StringLength);
return PokeList2.ReadFromList(data.Span, StringLength);
return new(data);
}
protected override byte[] DecryptPKM(byte[] data)
{
return data;
}
protected override void DecryptPKM(Span<byte> data) { }
// Pokédex
protected override void SetDex(PKM pk)

View File

@ -33,11 +33,11 @@ public sealed class SAV2Stadium : SAV_STADIUM, IBoxDetailName
public override Type PKMType => typeof(SK2);
public override SK2 BlankPKM => new(Japanese);
protected override SK2 GetPKM(byte[] data) => new(data, Japanese);
protected override SK2 GetPKM(Memory<byte> data) => new(data, Japanese);
private const int SIZE_SK2 = PokeCrypto.SIZE_2STADIUM; // 60
protected override int SIZE_STORED => SIZE_SK2;
protected override int SIZE_PARTY => SIZE_SK2;
public override int SIZE_STORED => SIZE_SK2;
public override int SIZE_PARTY => SIZE_SK2;
private const int ListHeaderSizeTeam = 0x10;
private const int ListHeaderSizeBox = 0x20;

View File

@ -177,8 +177,8 @@ public void WriteBothSaveSlots(Span<byte> data)
SetSlotChecksums(data, 1);
}
protected sealed override int SIZE_STORED => PokeCrypto.SIZE_3STORED;
protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY;
public sealed override int SIZE_STORED => PokeCrypto.SIZE_3STORED;
public sealed override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY;
public sealed override PK3 BlankPKM => new();
public sealed override Type PKMType => typeof(PK3);
@ -219,8 +219,8 @@ public void WriteBothSaveSlots(Span<byte> data)
public sealed override int GetPartyOffset(int slot) => SIZE_PARTY * slot;
public sealed override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentGBA(data);
protected sealed override PK3 GetPKM(byte[] data) => new(data);
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray3(data);
protected sealed override PK3 GetPKM(Memory<byte> data) => new(data);
protected sealed override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt3(data);
protected sealed override Span<byte> BoxBuffer => Storage;
protected sealed override Span<byte> PartyBuffer => LargeBlock.PartyBuffer;

View File

@ -121,8 +121,8 @@ private byte[] GetInnerData()
// Configuration
protected override SAV3Colosseum CloneInternal() => new((SaveIndex, SaveCount), Container.ToArray(), false) { MemoryCard = MemoryCard };
protected override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED; // unused
public override int SIZE_STORED => PokeCrypto.SIZE_3CSTORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_3CSTORED; // unused
public override CK3 BlankPKM => new();
public override Type PKMType => typeof(CK3);
@ -182,14 +182,22 @@ public void SetBoxName(int box, ReadOnlySpan<char> value)
SetString(GetBoxNameSpan(box), value, 8, StringConverterOption.ClearZero);
}
protected override CK3 GetPKM(byte[] data)
protected override CK3 GetPKM(Memory<byte> data)
{
if (data.Length != SIZE_STORED)
Array.Resize(ref data, SIZE_STORED);
data = EnsurePartySize(data);
return new(data);
}
protected override byte[] DecryptPKM(byte[] data) => data;
private static Memory<byte> EnsurePartySize(Memory<byte> data)
{
if (data.Length == PokeCrypto.SIZE_3CSTORED)
return data;
var result = new byte[PokeCrypto.SIZE_3CSTORED];
data.CopyTo(result);
return result;
}
protected override void DecryptPKM(Span<byte> data) { }
protected override void SetPKM(PKM pk, bool isParty = false)
{

View File

@ -125,8 +125,8 @@ public override void CopyChangesFrom(SaveFile sav)
s.BoxBuffer.CopyTo(BoxBuffer);
}
protected override int SIZE_STORED => PokeCrypto.SIZE_3STORED + 4;
protected override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; // unused
public override int SIZE_STORED => PokeCrypto.SIZE_3STORED + 4; // tid-sid of depositor
public override int SIZE_PARTY => PokeCrypto.SIZE_3PARTY; // unused
public override PK3 BlankPKM => new();
public override Type PKMType => typeof(PK3);
@ -233,27 +233,14 @@ public void SetBoxName(int box, ReadOnlySpan<char> value)
SetString(span, value, 8, StringConverterOption.ClearZero);
}
protected override PK3 GetPKM(byte[] data)
{
if (data.Length != PokeCrypto.SIZE_3STORED)
Array.Resize(ref data, PokeCrypto.SIZE_3STORED);
return new(data);
}
protected override PK3 GetPKM(Memory<byte> data) => new(data);
protected override byte[] DecryptPKM(byte[] data)
{
if (data.Length != PokeCrypto.SIZE_3STORED)
Array.Resize(ref data, PokeCrypto.SIZE_3STORED);
return PokeCrypto.DecryptArray3(data);
}
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt3(data);
protected override void SetDex(PKM pk) { /* No Pokédex for this game, do nothing */ }
public override void WriteBoxSlot(PKM pk, Span<byte> data)
protected override void WriteSlotBox(PKM pk, Span<byte> data)
{
base.WriteBoxSlot(pk, data);
WriteUInt16LittleEndian(data[(PokeCrypto.SIZE_3STORED)..], pk.TID16);
WriteUInt16LittleEndian(data[(PokeCrypto.SIZE_3STORED + 2)..], pk.SID16);
base.WriteSlotBox(pk, data);
WriteUInt32LittleEndian(data[PokeCrypto.SIZE_3STORED..], pk.ID32); // assume from OT
}
public override string GetString(ReadOnlySpan<byte> data)

View File

@ -140,8 +140,8 @@ private byte[] GetInnerData()
// Configuration
protected override SAV3XD CloneInternal() => new((SaveIndex, SaveCount), Container.ToArray(), false) { MemoryCard = MemoryCard };
protected override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; // unused
public override int SIZE_STORED => PokeCrypto.SIZE_3XSTORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_3XSTORED; // unused
public override XK3 BlankPKM => new();
public override Type PKMType => typeof(XK3);
@ -264,14 +264,8 @@ public void SetBoxName(int box, ReadOnlySpan<char> value)
SetString(Data.Slice(GetBoxInfoOffset(box), 20), value, 8, StringConverterOption.ClearZero);
}
protected override XK3 GetPKM(byte[] data)
{
if (data.Length != SIZE_STORED)
Array.Resize(ref data, SIZE_STORED);
return new(data);
}
protected override byte[] DecryptPKM(byte[] data) => data;
protected override XK3 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) { }
public override XK3 GetPartySlot(ReadOnlySpan<byte> data) => GetStoredSlot(data);
public override XK3 GetStoredSlot(ReadOnlySpan<byte> data)

View File

@ -85,8 +85,8 @@ public sealed override void CopyChangesFrom(SaveFile sav)
SetData(Storage, s4.Storage);
}
protected sealed override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY;
public sealed override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
public sealed override int SIZE_PARTY => PokeCrypto.SIZE_4PARTY;
public sealed override PK4 BlankPKM => new();
public sealed override Type PKMType => typeof(PK4);
@ -370,8 +370,8 @@ public int Region
}
#endregion
protected sealed override PK4 GetPKM(byte[] data) => new(data);
protected sealed override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
protected sealed override PK4 GetPKM(Memory<byte> data) => new(data);
protected sealed override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt45(data);
protected override void SetPKM(PKM pk, bool isParty = false)
{
@ -805,7 +805,7 @@ public virtual void SetMysteryGift(int index, PGT pgt)
throw new ArgumentOutOfRangeException(nameof(index));
if (pgt.Data.Length != PGT.Size)
throw new InvalidCastException(nameof(pgt));
pgt.VerifyPKEncryption();
pgt.VerifyGiftEncryption();
SAV.SetData(GetCardSpanPGT(index), pgt.Data);
}
@ -816,7 +816,7 @@ public virtual void SetMysteryGift(int index, PCD pcd)
if (pcd.Data.Length != PCD.Size)
throw new InvalidCastException(nameof(pcd));
var gift = pcd.Gift;
gift.VerifyPKEncryption();
gift.VerifyGiftEncryption();
SAV.SetData(GetCardSpanPCD(index), pcd.Data);
}

View File

@ -153,8 +153,8 @@ protected override Memory<byte> GetFinalData()
// Configuration
protected override SAV4BR CloneInternal() => new(this);
protected override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_4STORED + 84;
public override int SIZE_STORED => PokeCrypto.SIZE_4STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_4STORED + 84;
public override BK4 BlankPKM => new();
public override Type PKMType => typeof(BK4);
@ -463,14 +463,8 @@ public void SetBoxName(int box, ReadOnlySpan<char> value)
SetString(span, value, BoxNameLength / 2, StringConverterOption.ClearZero);
}
protected override BK4 GetPKM(byte[] data)
{
if (data.Length != SIZE_STORED)
Array.Resize(ref data, SIZE_STORED);
return BK4.ReadUnshuffle(data);
}
protected override byte[] DecryptPKM(byte[] data) => data;
protected override BK4 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt4BE(data);
protected override void SetPKM(PKM pk, bool isParty = false)
{
@ -493,21 +487,19 @@ protected override void SetPartyValues(PKM pk, bool isParty)
/// <returns>Where the PKM was found, or (255, 255) otherwise</returns>
public (byte Box, byte Slot) FindSlot(PKM pk)
{
var party = PartyData;
for (byte slot = 0; slot < PartyCount; slot++)
{
PKM other = party[slot];
if (pk.PID == other.PID && pk.DecryptedBoxData.SequenceEqual(other.DecryptedBoxData))
var other = GetPartySlotAtIndex(slot);
if (pk.EqualsStored(other))
return (0, slot);
}
var boxes = BoxData;
for (byte box = 0; box < BoxCount; box++)
{
for (byte slot = 0; slot < BoxSlotCount; slot++)
{
PKM other = boxes[(box * BoxSlotCount) + slot];
if (pk.PID == other.PID && pk.DecryptedBoxData.SequenceEqual(other.DecryptedBoxData))
var other = GetBoxSlotAtIndex(box, slot);
if (pk.EqualsStored(other))
return (++box, slot);
}
}

View File

@ -10,15 +10,15 @@ namespace PKHeX.Core;
/// </summary>
public abstract class SAV5 : SaveFile, ISaveBlock5BW, IEventFlagProvider37, IBoxDetailName, IBoxDetailWallpaper, IDaycareRandomState<ulong>, IDaycareStorage, IDaycareExperience, IDaycareEggState, IMysteryGiftStorageProvider
{
protected override PK5 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data);
protected override PK5 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt45(data);
protected internal override string ShortSummary => $"{OT} ({Version}) - {PlayTimeString}";
public override string Extension => ".sav";
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_BW;
protected override int SIZE_STORED => PokeCrypto.SIZE_5STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_5STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_5PARTY;
public override PK5 BlankPKM => new();
public override Type PKMType => typeof(PK5);

View File

@ -16,8 +16,8 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
protected SAV6([ConstantExpected] int size, [ConstantExpected] int biOffset) : base(size, biOffset) { }
// Configuration
protected sealed override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected sealed override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public sealed override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
public sealed override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public sealed override PK6 BlankPKM => new();
public sealed override Type PKMType => typeof(PK6);
@ -32,8 +32,8 @@ public abstract class SAV6 : SAV_BEEF, ITrainerStatRecord, ISaveBlock6Core, IReg
public override int MaxBallID => Legal.MaxBallID_6;
public override GameVersion MaxGameID => Legal.MaxGameID_6; // OR
protected override PK6 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data);
protected override PK6 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt67(data);
protected int JPEG { get; set; } = int.MinValue;
public int PSS { get; protected set; } = int.MinValue;

View File

@ -62,8 +62,8 @@ protected void ReloadBattleTeams()
#endregion
// Configuration
protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public override PK7 BlankPKM => new();
public override Type PKMType => typeof(PK7);
@ -76,8 +76,8 @@ protected void ReloadBattleTeams()
public override int MaxBallID => Legal.MaxBallID_7; // 26
public override GameVersion MaxGameID => Legal.MaxGameID_7;
protected override PK7 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data);
protected override PK7 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt67(data);
// Feature Overrides

View File

@ -14,12 +14,15 @@ public sealed class SAV7b : SAV_BEEF, ISaveBlock7b, IGameSync, IMysteryGiftStora
public override Type PKMType => typeof(PB7);
public override PB7 BlankPKM => new();
protected override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_6STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_6PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_6PARTY;
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
public override PB7 GetBoxSlot(int offset) => GetDecryptedPKM(Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PB7 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
protected override PB7 GetBoxSlot(int offset) => GetDecryptedPKM(Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PB7 GetDecryptedPKM(Memory<byte> data)
{
DecryptPKM(data.Span);
return GetPKM(data);
}
public override PersonalTable7GG Personal => PersonalTable.GG;
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_GG;
@ -106,8 +109,8 @@ protected override void SetPKM(PKM pk, bool isParty = false)
public override bool GetCaught(ushort species) => Blocks.Zukan.GetCaught(species);
public override bool GetSeen(ushort species) => Blocks.Zukan.GetSeen(species);
protected override PB7 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray6(data);
protected override PB7 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt67(data);
public override int GetBoxOffset(int box) => Box + (box * BoxSlotCount * SIZE_BOXSLOT);
protected override IList<int>[] SlotPointers => [ Blocks.Storage.PokeListInfo ];

View File

@ -101,8 +101,8 @@ private void Initialize()
}
// Configuration
protected override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8PARTY;
public override PB8 BlankPKM => new();
public override Type PKMType => typeof(PB8);
@ -197,8 +197,8 @@ public override bool ChecksumsValid
#endregion
protected override PB8 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8(data);
protected override PB8 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt8(data);
#region Blocks
// public Box8 BoxInfo { get; }
@ -276,7 +276,6 @@ public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, i
public void SetBoxWallpaper(int box, int value) => BoxLayout.SetBoxWallpaper(box, value);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
public override int CurrentBox { get => BoxLayout.CurrentBox; set => BoxLayout.CurrentBox = (byte)value; }
public override int BoxesUnlocked { get => BoxLayout.BoxesUnlocked; set => BoxLayout.BoxesUnlocked = (byte)value; }
@ -333,8 +332,14 @@ public override int PartyCount
protected set => PartyInfo.PartyCount = value;
}
public override PB8 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public override PB8 GetBoxSlot(int offset) => GetDecryptedPKM(Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PB8 GetDecryptedPKM(Memory<byte> data)
{
DecryptPKM(data.Span);
return GetPKM(data);
}
protected override PB8 GetBoxSlot(int offset) => GetDecryptedPKM(Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
protected override void WriteSlotBox(PKM pk, Span<byte> data) => pk.WriteEncryptedDataParty(data);
public enum TopMenuItemType
{

View File

@ -57,11 +57,11 @@ public override void CopyChangesFrom(SaveFile sav)
State.Edited = true;
}
protected override int SIZE_STORED => PokeCrypto.SIZE_8ASTORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_8APARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8ASTORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8APARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8ASTORED;
protected override PA8 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8A(data);
protected override PA8 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt8A(data);
public override PA8 BlankPKM => new();
public override Type PKMType => typeof(PA8);

View File

@ -121,8 +121,8 @@ private void Initialize()
public override IReadOnlyList<string> PKMExtensions => EntityFileExtension.GetExtensionsHOME();
// Configuration
protected override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8PARTY;
public override PK8 BlankPKM => new();
public override Type PKMType => typeof(PK8);
@ -133,8 +133,8 @@ private void Initialize()
public override EntityContext Context => EntityContext.Gen8;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
protected override PK8 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray8(data);
protected override PK8 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt8(data);
public override bool IsVersionValid() => Version is GameVersion.SW or GameVersion.SH;
@ -168,7 +168,6 @@ public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, i
public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
protected override void SetPKM(PKM pk, bool isParty = false)
{
@ -228,8 +227,14 @@ public override int PartyCount
protected override Span<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
public override PK8 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public override PK8 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PK8 GetDecryptedPKM(Memory<byte> data)
{
DecryptPKM(data.Span);
return GetPKM(data);
}
protected override PK8 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
protected override void WriteSlotBox(PKM pk, Span<byte> data) => pk.WriteEncryptedDataParty(data);
public int GetRecord(int recordID) => Records.GetRecord(recordID);
public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value);

View File

@ -123,9 +123,9 @@ private void Initialize()
}
// Configuration
protected override int SIZE_STORED => PokeCrypto.SIZE_9STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_9PARTY;
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8PARTY;
public override PK9 BlankPKM => new();
public override Type PKMType => typeof(PK9);
@ -135,8 +135,8 @@ private void Initialize()
public override EntityContext Context => EntityContext.Gen9;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
protected override PK9 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray9(data);
protected override PK9 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt8(data);
public override bool IsVersionValid() => Version is GameVersion.SL or GameVersion.VL;
@ -171,7 +171,6 @@ public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, i
public override int GetBoxOffset(int box) => Box + (SIZE_PARTY * box * 30);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => pk.EncryptedPartyData;
protected override void SetPKM(PKM pk, bool isParty = false)
{
@ -233,8 +232,14 @@ public override int PartyCount
protected override Span<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
public override PK9 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public override PK9 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PK9 GetDecryptedPKM(Memory<byte> data)
{
DecryptPKM(data.Span);
return GetPKM(data);
}
protected override PK9 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
protected override void WriteSlotBox(PKM pk, Span<byte> data) => pk.WriteEncryptedDataParty(data);
//public int GetRecord(int recordID) => Records.GetRecord(recordID);
//public void SetRecord(int recordID, int value) => Records.SetRecord(recordID, value);

View File

@ -128,9 +128,9 @@ private void Initialize()
public override IReadOnlyList<string> PKMExtensions => EntityFileExtension.GetExtensionsHOME();
// Configuration
protected override int SIZE_STORED => PokeCrypto.SIZE_9STORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_9PARTY + GapBoxSlot;
public override int SIZE_STORED => PokeCrypto.SIZE_8STORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_8PARTY;
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_8PARTY + GapBoxSlot;
public override PA9 BlankPKM => new();
public override Type PKMType => typeof(PA9);
@ -140,8 +140,8 @@ private void Initialize()
public override EntityContext Context => EntityContext.Gen9a;
public override int MaxStringLengthTrainer => 12;
public override int MaxStringLengthNickname => 12;
protected override PA9 GetPKM(byte[] data) => new(data);
protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray9(data);
protected override PA9 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt8(data);
public override bool IsVersionValid() => Version is GameVersion.ZA;
@ -176,7 +176,6 @@ public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, i
public override int GetBoxOffset(int box) => Box + (SIZE_BOXSLOT * box * 30);
public string GetBoxName(int box) => BoxLayout[box];
public void SetBoxName(int box, ReadOnlySpan<char> value) => BoxLayout.SetBoxName(box, value);
public override byte[] GetDataForBox(PKM pk) => [..pk.EncryptedPartyData, ..new byte[GapBoxSlot]];
protected override void SetPKM(PKM pk, bool isParty = false)
{
@ -217,8 +216,14 @@ public override int PartyCount
protected override Span<byte> BoxBuffer => BoxInfo.Data;
protected override Span<byte> PartyBuffer => PartyInfo.Data;
public override PA9 GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public override PA9 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
public override PA9 GetDecryptedPKM(Memory<byte> data)
{
DecryptPKM(data.Span);
return GetPKM(data);
}
protected override PA9 GetBoxSlot(int offset) => GetDecryptedPKM(BoxInfo.Data.Slice(offset, SIZE_PARTY).ToArray()); // party format in boxes!
protected override void WriteSlotBox(PKM pk, Span<byte> data) => pk.WriteEncryptedDataParty(data);
public override StorageSlotSource GetBoxSlotFlags(int index)
{

View File

@ -47,7 +47,7 @@ protected SAV_STADIUM(bool japanese, [ConstantExpected] int size) : base(size)
OT = BlankSaveFile.GetSafeTrainerName(this, (LanguageID)Language);
}
protected sealed override byte[] DecryptPKM(byte[] data) => data;
protected sealed override void DecryptPKM(Span<byte> data) { }
public sealed override int GetPartyOffset(int slot) => -1;
public sealed override bool ChecksumsValid => GetBoxChecksumsValid();
public sealed override string ChecksumInfo => ChecksumsValid ? "Checksum valid." : "Checksum invalid";

View File

@ -239,7 +239,7 @@ public void SetSlotFormatParty(PKM pk, Span<byte> data, EntityImportSettings set
UpdatePKM(pk, isParty: true, settings);
SetPartyValues(pk, isParty: true);
WritePartySlot(pk, data);
WriteSlotParty(pk, data);
}
public void SetSlotFormatStored(PKM pk, Span<byte> data, EntityImportSettings settings = default)
@ -249,7 +249,7 @@ public void SetSlotFormatStored(PKM pk, Span<byte> data, EntityImportSettings se
UpdatePKM(pk, isParty: false, settings);
SetPartyValues(pk, isParty: false);
WriteSlotFormatStored(pk, data);
WriteSlotStored(pk, data);
}
public void SetPartySlot(PKM pk, Span<byte> data, EntityImportSettings settings = default)
@ -262,7 +262,7 @@ public void SetBoxSlot(PKM pk, Span<byte> data, EntityImportSettings settings =
UpdatePKM(pk, isParty: false, settings);
SetPartyValues(pk, isParty: false);
WriteBoxSlot(pk, data);
WriteSlotBox(pk, data);
}
public void DeletePartySlot(int slot)
@ -287,11 +287,11 @@ public void DeletePartySlot(int slot)
public static EntityImportSettings SetUpdateSettings => new(SetUpdatePKM, SetUpdateDex, SetUpdateRecords);
public abstract Type PKMType { get; }
protected abstract PKM GetPKM(byte[] data);
protected abstract byte[] DecryptPKM(byte[] data);
protected abstract PKM GetPKM(Memory<byte> data);
protected abstract void DecryptPKM(Span<byte> data);
public abstract PKM BlankPKM { get; }
protected abstract int SIZE_STORED { get; }
protected abstract int SIZE_PARTY { get; }
public abstract int SIZE_STORED { get; }
public abstract int SIZE_PARTY { get; }
public virtual int SIZE_BOXSLOT => SIZE_STORED;
public abstract int MaxEV { get; }
public virtual int MaxIV => 31;
@ -299,20 +299,19 @@ public void DeletePartySlot(int slot)
protected virtual Span<byte> BoxBuffer => Data;
protected virtual Span<byte> PartyBuffer => Data;
public virtual bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresent(data);
public virtual PKM GetDecryptedPKM(byte[] data) => GetPKM(DecryptPKM(data));
public virtual PKM GetDecryptedPKM(Memory<byte> data)
{
DecryptPKM(data.Span);
return GetPKM(data);
}
public virtual PKM GetPartySlot(ReadOnlySpan<byte> data) => GetDecryptedPKM(data[..SIZE_PARTY].ToArray());
public virtual PKM GetStoredSlot(ReadOnlySpan<byte> data) => GetDecryptedPKM(data[..SIZE_STORED].ToArray());
public virtual PKM GetBoxSlot(int offset) => GetStoredSlot(BoxBuffer[offset..]);
protected virtual PKM GetBoxSlot(int offset) => GetStoredSlot(BoxBuffer[offset..]);
public virtual byte[] GetDataForFormatStored(PKM pk) => pk.EncryptedBoxData;
public virtual byte[] GetDataForFormatParty(PKM pk) => pk.EncryptedPartyData;
public virtual byte[] GetDataForParty(PKM pk) => pk.EncryptedPartyData;
public virtual byte[] GetDataForBox(PKM pk) => pk.EncryptedBoxData;
public virtual void WriteSlotFormatStored(PKM pk, Span<byte> data) => SetData(data, GetDataForFormatStored(pk));
public virtual void WriteSlotFormatParty(PKM pk, Span<byte> data) => SetData(data, GetDataForFormatParty(pk));
public virtual void WritePartySlot(PKM pk, Span<byte> data) => SetData(data, GetDataForParty(pk));
public virtual void WriteBoxSlot(PKM pk, Span<byte> data) => SetData(data, GetDataForBox(pk));
protected virtual void WriteSlotStored(PKM pk, Span<byte> data) => pk.WriteEncryptedDataStored(data);
protected virtual void WriteSlotParty(PKM pk, Span<byte> data) => pk.WriteEncryptedDataParty(data);
protected virtual void WriteSlotBox(PKM pk, Span<byte> data) => WriteSlotStored(pk, data);
protected virtual void SetPartyValues(PKM pk, bool isParty)
{
@ -735,7 +734,11 @@ public int ClearBoxes(int BoxStart = 0, int BoxEnd = -1, Func<PKM, bool>? delete
if ((uint)BoxEnd >= BoxCount)
BoxEnd = BoxCount - 1;
var blank = GetDataForBox(BlankPKM);
// Get the at-rest data for a blank slot in the box. Only need to do this once rather than every slot.
var fake = BlankPKM;
Span<byte> blank = stackalloc byte[SIZE_BOXSLOT];
WriteSlotBox(fake, blank);
int deleted = 0;
for (int i = BoxStart; i <= BoxEnd; i++)
{
@ -796,8 +799,31 @@ public int ModifyBoxes(Action<PKM> action, int BoxStart = 0, int BoxEnd = -1)
#endregion
#region Box Binaries
public byte[] GetPCBinary() => BoxData.SelectMany(GetDataForBox).ToArray();
public byte[] GetBoxBinary(int box) => GetBoxData(box).SelectMany(GetDataForBox).ToArray();
public byte[] GetPCBinary()
{
var size = SIZE_BOXSLOT;
var result = new byte[size * SlotCount];
for (int i = 0; i < SlotCount; i++)
{
var data = GetBoxSlotAtIndex(i);
var dest = result.AsSpan(i * size, size);
WriteSlotBox(data, dest);
}
return result;
}
public byte[] GetBoxBinary(int box)
{
var size = SIZE_BOXSLOT;
var result = new byte[size * BoxSlotCount];
for (int i = 0; i < BoxSlotCount; i++)
{
var data = GetBoxSlotAtIndex(box, i);
var dest = result.AsSpan(i * size, size);
WriteSlotBox(data, dest);
}
return result;
}
public bool SetPCBinary(ReadOnlySpan<byte> data)
{
@ -832,6 +858,7 @@ private bool SetConcatenatedBinary(ReadOnlySpan<byte> data, int expectLength, in
continue;
var src = data.Slice(i, entryLength);
var arr = src.ToArray();
DecryptPKM(arr);
var pk = GetPKM(arr);
SetBoxSlotAtIndex(pk, ctr++);
}

View File

@ -11,6 +11,9 @@ public sealed class Bank3 : BulkStorage, IBoxDetailNameRead
public override GameVersion Version { get => GameVersion.RS; set { } }
public override PersonalTable3 Personal => PersonalTable.RS;
protected override PK3 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt3(data);
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_RS;
protected override Bank3 CloneInternal() => new(Data.ToArray());
public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4");

View File

@ -11,6 +11,8 @@ public sealed class Bank4 : BulkStorage, IBoxDetailNameRead
public override GameVersion Version { get => GameVersion.HGSS; set { } }
public override PersonalTable4 Personal => PersonalTable.HGSS;
protected override PK4 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt45(data);
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_HGSS;
protected override Bank4 CloneInternal() => new(Data.ToArray());
public override string PlayTimeString => Checksums.CRC16Invert(Data).ToString("X4");

View File

@ -14,6 +14,8 @@ public sealed class Bank7 : BulkStorage, IBoxDetailNameRead
public override GameVersion Version { get => GameVersion.USUM; set { } }
public override PersonalTable7 Personal => PersonalTable.USUM;
public override ReadOnlySpan<ushort> HeldItems => Legal.HeldItems_SM;
protected override PK7 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt67(data);
protected override Bank7 CloneInternal() => new(Data.ToArray(), PKMType, BoxStart, SlotsPerBox);
public override string PlayTimeString => $"{Year:00}{Month:00}{Day:00}_{Hours:00}ː{Minutes:00}";
protected internal override string ShortSummary => PlayTimeString;

View File

@ -28,11 +28,8 @@ protected BulkStorage(Memory<byte> data, Type t, int start, int slotsPerBox = 30
public sealed override Type PKMType => blank.GetType();
public sealed override PKM BlankPKM => blank.Clone();
protected override PKM GetPKM(byte[] data) => EntityFormat.GetFromBytes(data, prefer: Context) ?? blank;
protected override byte[] DecryptPKM(byte[] data) => GetPKM(data).Data.ToArray();
protected override int SIZE_STORED => blank.SIZE_STORED;
protected override int SIZE_PARTY => blank.SIZE_PARTY;
public override int SIZE_STORED => blank.SIZE_STORED;
public override int SIZE_PARTY => blank.SIZE_PARTY;
public sealed override int MaxEV => blank.MaxEV;
public sealed override byte Generation => blank.Format;
public sealed override EntityContext Context => blank.Context;

View File

@ -9,8 +9,8 @@ namespace PKHeX.Core;
/// </summary>
public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision
{
protected override int SIZE_STORED => PokeCrypto.SIZE_4RSTORED;
protected override int SIZE_PARTY => PokeCrypto.SIZE_4RSTORED;
public override int SIZE_STORED => PokeCrypto.SIZE_4RSTORED;
public override int SIZE_PARTY => PokeCrypto.SIZE_4RSTORED;
public int MaxToyID => (int) ((SaveRevision == 0) ? RanchToyType.Poke_Ball : RanchToyType.Water);
public int SaveRevision => Version == GameVersion.DP ? 0 : 1;
public string SaveRevisionString => Version == GameVersion.DP ? "-DP" : "-Pt";
@ -39,7 +39,9 @@ public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision
protected internal override string ShortSummary => $"{OT} {PlayTimeString}";
public override string Extension => ".bin";
protected override RK4 GetPKM(byte[] data) => new(data);
protected override RK4 GetPKM(Memory<byte> data) => new(data);
protected override void DecryptPKM(Span<byte> data) => PokeCrypto.Decrypt45(data[..PokeCrypto.SIZE_4STORED]);
public override StorageSlotSource GetBoxSlotFlags(int index) => index >= SlotCount ? StorageSlotSource.Locked : StorageSlotSource.None;
protected override bool IsSlotSwapProtected(int box, int slot) => IsBoxSlotOverwriteProtected(box, slot);
public override bool IsPKMPresent(ReadOnlySpan<byte> data) => EntityDetection.IsPresentSAV4Ranch(data);
@ -172,17 +174,6 @@ private int GetOccupiedSlotCount()
return 0;
}
protected override byte[] DecryptPKM(byte[] data)
{
var pokeData = PokeCrypto.DecryptArray45(data.AsSpan(0, PokeCrypto.SIZE_4STORED));
var ranchData = data.AsSpan(PokeCrypto.SIZE_4STORED, 0x1C);
var finalData = new byte[SIZE_STORED];
pokeData.CopyTo(finalData, 0);
ranchData.CopyTo(finalData.AsSpan(PokeCrypto.SIZE_4STORED));
return finalData;
}
public void WriteBoxSlotInternal(PKM pk, Span<byte> data, string htName = "", ushort htTID = 0, ushort htSID = 0, RanchOwnershipType type = RanchOwnershipType.Hayley)
{
RK4 rk = (RK4)this.GetCompatiblePKM(pk);
@ -191,10 +182,10 @@ public void WriteBoxSlotInternal(PKM pk, Span<byte> data, string htName = "", us
rk.HandlingTrainerSID = htSID;
rk.HandlingTrainerName = htName;
WriteBoxSlot(rk, data);
WriteSlotBox(rk, data);
}
public override void WriteBoxSlot(PKM pk, Span<byte> data)
protected override void WriteSlotBox(PKM pk, Span<byte> data)
{
if (pk is not RK4 rk4)
{
@ -206,7 +197,7 @@ public override void WriteBoxSlot(PKM pk, Span<byte> data)
if (!isBlank && rk4.OwnershipType == RanchOwnershipType.None)
rk4.OwnershipType = RanchOwnershipType.Hayley; // Pokémon without an Ownership type get erased when the save is loaded. Hayley is considered 'default'.
base.WriteBoxSlot(rk4, data);
base.WriteSlotBox(rk4, data);
}
private void UpdateMetadata(int pkEnd)

View File

@ -61,7 +61,7 @@ public void SetTeam(IReadOnlyList<PK3> team, int teamIndex)
for (int p = 0; p < 6; p++)
{
int offset = ofs + (PokeCrypto.SIZE_3PARTY * p);
team[p].EncryptedPartyData.CopyTo(Data[offset..]);
team[p].WriteEncryptedDataParty(Data[offset..]);
}
}

View File

@ -113,7 +113,7 @@ public void SetTeam(IReadOnlyList<PK6> team, int t)
{
int offset = start + (PokeCrypto.SIZE_6PARTY * ((t * 6) + p));
offset += 8 * (((t * 6) + p) / 6); // 8 bytes padding between teams
team[p].EncryptedPartyData.CopyTo(Data[offset..]);
team[p].WriteEncryptedDataParty(Data[offset..]);
}
}

View File

@ -55,7 +55,7 @@ public void SetTeam(IReadOnlyList<PK7> team, int teamIndex)
for (int p = 0; p < 6; p++)
{
int offset = ofs + (PokeCrypto.SIZE_6PARTY * p);
team[p].EncryptedPartyData.CopyTo(Data[offset..]);
team[p].WriteEncryptedDataParty(Data[offset..]);
}
}

View File

@ -148,13 +148,19 @@ public void ResetPresetIndexes()
// 0x4B8-53F Party Member 6
private static int GetPartyOffset(int index) => 0x1FC + (PokeSize * index);
private Span<byte> GetPartySpan(int index) => Data.Slice(GetPartyOffset(index), PokeSize);
public BK4 GetPartySlotAtIndex(int index) => BK4.ReadUnshuffle(GetPartySpan(index));
public BK4 GetPartySlotAtIndex(int index)
{
var data = GetPartySpan(index).ToArray();
PokeCrypto.Decrypt4BE(data);
return new BK4(data);
}
public void SetPartySlotAtIndex(PKM pk, int index)
{
while (index > 0 && !GetPartySlotPresent(index - 1))
index--;
pk.EncryptedBoxData.CopyTo(GetPartySpan(index));
pk.WriteEncryptedDataParty(GetPartySpan(index));
SetPartySlotPresent(index, pk.Species != 0);
}

View File

@ -86,7 +86,7 @@ public static void SetQRData(PK7 pk7, Span<byte> span, int box = 0, int slot = 0
WriteInt32LittleEndian(span[0x0C..], slot);
WriteInt32LittleEndian(span[0x10..], num_copies); // No need to check max num_copies, payload parser handles it on-console.
pk7.EncryptedPartyData.CopyTo(span[0x30..]); // Copy in Pokémon data
pk7.WriteEncryptedDataParty(span[0x30..]); // Copy in Pokémon data
SetDexData(span[0x140..], pk7.Species, pk7.Form, pk7.IsShiny, pk7.Gender);
var chk = Checksums.CRC16Invert(span[..0x1A0]);

View File

@ -19,7 +19,7 @@ public static int GetFusedSlotOffset(int slot)
public Memory<byte> this[int i] => Raw.Slice(GetFusedSlotOffset(i), SizeStored);
private Span<byte> GetSlotSpan(int index) => Data.Slice(GetFusedSlotOffset(index), SizeStored);
private PK8 GetStoredSlot(int index) => (PK8)SAV.GetStoredSlot(GetSlotSpan(index));
private void SetStoredSlot(PK8 pk, int index) => pk.EncryptedBoxData.CopyTo(GetSlotSpan(index));
private void SetStoredSlot(PK8 pk, int index) => pk.WriteEncryptedDataStored(GetSlotSpan(index));
public PK8 Kyurem { get => GetStoredSlot(0); set => SetStoredSlot(value, 0); }
public PK8 NecrozmaSolgaleo { get => GetStoredSlot(1); set => SetStoredSlot(value, 1); }

View File

@ -4,6 +4,6 @@ namespace PKHeX.Core;
public sealed class Box9(SaveFile sav, SCBlock block) : SaveBlock<SaveFile>(sav, block.Raw)
{
private const int afterBox = BoxLayout9.BoxCount * 30 * PokeCrypto.SIZE_9PARTY;
public Memory<byte> RideLegend => Raw.Slice(afterBox, PokeCrypto.SIZE_9PARTY);
private const int afterBox = BoxLayout9.BoxCount * 30 * PokeCrypto.SIZE_8PARTY;
public Memory<byte> RideLegend => Raw.Slice(afterBox, PokeCrypto.SIZE_8PARTY);
}

View File

@ -68,8 +68,8 @@ public bool IsEnabled
[Category(General), Description("Encrypted Entity data.")]
public PK9 Entity
{
get => new(Data.Slice(0x09, PokeCrypto.SIZE_9PARTY).ToArray());
set => value.EncryptedPartyData.CopyTo(Data.Slice(0x9, PokeCrypto.SIZE_9PARTY));
get => new(Data.Slice(0x09, PokeCrypto.SIZE_8PARTY).ToArray());
set => value.WriteEncryptedDataParty(Data.Slice(0x9, PokeCrypto.SIZE_8PARTY));
}
// 7 bytes of padding, for alignment of next field.

View File

@ -4,7 +4,7 @@ public sealed class Party9(SAV9SV sav, SCBlock block) : SaveBlock<SAV9SV>(sav, b
{
public int PartyCount
{
get => Data[6 * PokeCrypto.SIZE_9PARTY];
set => Data[6 * PokeCrypto.SIZE_9PARTY] = (byte)value;
get => Data[6 * PokeCrypto.SIZE_8PARTY];
set => Data[6 * PokeCrypto.SIZE_8PARTY] = (byte)value;
}
}

View File

@ -5,7 +5,7 @@ namespace PKHeX.Core;
public sealed class Party9a(SAV9ZA sav, SCBlock block) : SaveBlock<SAV9ZA>(sav, block.Raw)
{
private const int MaxCount = 6;
public const int SlotSize = PokeCrypto.SIZE_9PARTY + 0x40 + 0x48;
public const int SlotSize = PokeCrypto.SIZE_8PARTY + 0x40 + 0x48;
public Memory<byte> GetSlot(int slot) => block.Raw.Slice(SlotSize * slot, SlotSize);

View File

@ -46,10 +46,10 @@ public PK8 GetSlot(int slot)
public void SetSlot(int slot, PK8 pk)
{
var ofs = GetSlotOffset(slot);
var data1 = pk.EncryptedPartyData;
var dest = Data[ofs..];
pk.WriteEncryptedDataStored(dest);
// Wipe Party Stats
Array.Clear(data1, LEN_STORED, LEN_PARTYSTAT);
data1.CopyTo(Data[ofs..]);
dest.Slice(LEN_STORED, LEN_PARTYSTAT).Clear();
}
public PK8[] GetTeam()

View File

@ -77,10 +77,10 @@ public PK9 GetSlot(int slot)
public void SetSlot(int slot, PK9 pk)
{
var ofs = GetSlotOffset(slot);
var data = pk.EncryptedPartyData;
var dest = Data[ofs..];
pk.WriteEncryptedDataStored(dest);
// Wipe Party Stats
Array.Clear(data, LEN_STORED, LEN_PARTYSTAT);
data.CopyTo(Data[ofs..]);
dest.Slice(LEN_STORED, LEN_PARTYSTAT).Clear();
}
public PK9[] GetTeam()

View File

@ -25,6 +25,7 @@ public int DumpBoxes(string path, bool boxFolders = false)
if (!sav.HasBox)
return -1;
Span<byte> data = stackalloc byte[sav.SIZE_PARTY];
var boxData = sav.BoxData;
int boxSlotCount = sav.BoxSlotCount;
var ctr = 0;
@ -49,7 +50,9 @@ public int DumpBoxes(string path, bool boxFolders = false)
if (File.Exists(fn))
continue;
File.WriteAllBytes(fn, pk.DecryptedPartyData);
pk.ForcePartyData();
pk.WriteDecryptedDataParty(data);
File.WriteAllBytes(fn, data);
ctr++;
}
return ctr;
@ -66,6 +69,7 @@ public int DumpBox(string path, int currentBox)
if (!sav.HasBox)
return -1;
Span<byte> data = stackalloc byte[sav.SIZE_PARTY];
var boxData = sav.BoxData;
int boxSlotCount = sav.BoxSlotCount;
var ctr = 0;
@ -80,7 +84,9 @@ public int DumpBox(string path, int currentBox)
if (File.Exists(fileName))
continue;
File.WriteAllBytes(fileName, pk.DecryptedPartyData);
pk.ForcePartyData();
pk.WriteDecryptedDataParty(data);
File.WriteAllBytes(fileName, data);
ctr++;
}
return ctr;

View File

@ -205,7 +205,12 @@ private string CreateDragDropPKM(PictureBox pb, bool encrypt, out bool external)
string newfile = FileUtil.GetPKMTempFileName(pk, encrypt);
try
{
var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData;
pk.ForcePartyData();
Span<byte> data = stackalloc byte[pk.SIZE_PARTY];
if (!encrypt)
pk.WriteDecryptedDataParty(data);
else
pk.WriteEncryptedDataParty(data);
external = TryMakeDragDropPKM(pb, data, newfile);
}
// Tons of things can happen with drag & drop; don't try to handle things, just indicate failure.

View File

@ -1232,7 +1232,11 @@ private async void Dragout_MouseDown(object sender, MouseEventArgs e)
var pk = PreparePKM();
var preModify = pk.Clone();
var encrypt = ModifierKeys == Keys.Control;
var data = encrypt ? pk.EncryptedPartyData : pk.DecryptedPartyData;
var data = new byte[pk.SIZE_PARTY];
if (!encrypt)
pk.WriteDecryptedDataParty(data);
else
pk.WriteEncryptedDataParty(data);
// Create Temp File to Drag
var newfile = FileUtil.GetPKMTempFileName(pk, encrypt);

View File

@ -283,6 +283,11 @@ private void TryProcess(string source, string destDir, IReadOnlyList<StringInstr
}
if (editor.Process(pk, pkFilters, instructions))
File.WriteAllBytes(Path.Combine(destDir, Path.GetFileName(source)), pk.DecryptedPartyData);
{
Span<byte> result = stackalloc byte[pk.SIZE_PARTY];
pk.ForcePartyData();
pk.WriteDecryptedDataParty(result);
File.WriteAllBytes(Path.Combine(destDir, Path.GetFileName(source)), result);
}
}
}

View File

@ -224,7 +224,7 @@ private void ClickDelete(object sender, EventArgs e)
{
// Data from Box: Delete from save file
var exist = b.Read(SAV);
if (!exist.DecryptedBoxData.SequenceEqual(pk.DecryptedBoxData)) // data modified already?
if (!exist.EqualsStored(pk)) // data modified already?
{
WinFormsUtil.Error(MsgDBDeleteFailModified, MsgDBDeleteFailWarning);
return;
@ -263,7 +263,9 @@ private void ClickSet(object sender, EventArgs e)
return;
}
File.WriteAllBytes(path, pk.DecryptedBoxData);
Span<byte> data = stackalloc byte[pk.SIZE_STORED];
pk.WriteDecryptedDataStored(data);
File.WriteAllBytes(path, data);
var info = new SlotInfoFileSingle(path);
var entry = new SlotCache(info, pk);
@ -446,8 +448,14 @@ private void Menu_Export_Click(object sender, EventArgs e)
string path = fbd.SelectedPath;
Directory.CreateDirectory(path);
Span<byte> data = stackalloc byte[SAV.SIZE_PARTY];
foreach (var pk in Results.Select(z => z.Entity))
File.WriteAllBytes(Path.Combine(path, PathUtil.CleanFileName(pk.FileName)), pk.DecryptedPartyData);
{
var fileName = Path.Combine(path, PathUtil.CleanFileName(pk.FileName));
pk.ForcePartyData();
pk.WriteDecryptedDataParty(data);
File.WriteAllBytes(fileName, data);
}
}
private void Menu_Import_Click(object sender, EventArgs e)

View File

@ -349,9 +349,9 @@ private void SaveBattleAgency()
WriteUInt16LittleEndian(SAV.Data[0x6C3EE..], (ushort)(CHK_TrainerInvited.Checked ? GetSavData16(0x6C3EE) | InvitedValue : 0));
WriteUInt16LittleEndian(SAV.Data[0x6C526..], (ushort)(CHK_TrainerInvited.Checked ? GetSavData16(0x6C526) | InvitedValue : 0));
}
SAV.SetData(p[0].EncryptedBoxData, 0x6C200);
SAV.SetData(p[1].EncryptedPartyData, 0x6C2E8);
SAV.SetData(p[2].EncryptedPartyData, 0x6C420);
p[0].WriteEncryptedDataStored(SAV.Data[0x6C200..]); // BattleFesSave
p[1].WriteEncryptedDataParty(SAV.Data[0x6C2E8..]);
p[2].WriteEncryptedDataParty(SAV.Data[0x6C420..]);
var gradeDefeated = ((((int)NUD_Defeated.Value & 0xF) << 12) | (((int)NUD_Grade.Value & 0x3F) << 6) | (SAV.Data[0x6C55C] & 0x3F));
WriteUInt16LittleEndian(SAV.Data[0x6C558..], (ushort)NUD_DefeatMon.Value);

View File

@ -366,7 +366,11 @@ private static void SavePKM(PKM pk, string path, ReadOnlySpan<char> pkx)
{
SaveBackup(path);
var ext = Path.GetExtension(path);
var data = ext == $".{pkx}" ? pk.DecryptedPartyData : pk.EncryptedPartyData;
Span<byte> data = stackalloc byte[pk.SIZE_PARTY];
if (ext == $".{pkx}")
pk.WriteDecryptedDataParty(data);
else
pk.WriteEncryptedDataParty(data);
File.WriteAllBytes(path, data);
}

View File

@ -20,6 +20,10 @@ private static IEnumerable<string> GetHomeEncrypted()
public static void CheckCrypto1()
{
var paths = GetHomeEncrypted();
const int maxSize = HomeCrypto.SIZE_STORED;
var totalSize = PKH.GetPaddedSize(maxSize, out _);
Span<byte> write = stackalloc byte[totalSize];
foreach (var f in paths)
{
var data = File.ReadAllBytes(f);
@ -46,8 +50,8 @@ public static void CheckCrypto1()
ph1.Clone().Should().NotBeNull();
var write = ph1.Rebuild();
write.Length.Should().Be(decrypted.Length);
var writeLength = ph1.Rebuild(write);
writeLength.Should().Be(decrypted.Length);
for (int i = 0; i < decrypted.Length; i++)
write[i].Should().Be(decrypted[i], $"Offset {i:X2}");