diff --git a/.github/README-de.md b/.github/README-de.md index 8aacab7c4..106da18d2 100644 --- a/.github/README-de.md +++ b/.github/README-de.md @@ -7,7 +7,7 @@ Save Editor für die Pokémon Hauptreihe, geschrieben in [C#](https://de.wikiped Die folgenden Dateien werden unterstützt: * Spielstände ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin) * GameCube Memory Card Daten (\*.raw, \*.bin), die GC Pokémon Spielstände enthalten. -* Einzelne Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4) +* Einzelne Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4) * Wunderkarten (\*.pgt, \*.pcd, \*.pgf, .wc\*), inklusive Konvertierung zu .pk\* * Import von GO Park Pokémon (\*.gp1) inklusive Konvertierung zu .pb7 * Import von Teams aus entschlüsselten 3DS Battle Videos. diff --git a/.github/README-es.md b/.github/README-es.md index c7e391580..fd3f228bb 100644 --- a/.github/README-es.md +++ b/.github/README-es.md @@ -7,7 +7,7 @@ Editor de guardado de las series principales de Pokémon, programado en [C#](htt Soporta los siguientes archivos: * Archivos de guardado ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin) * Archivos de Memory Card de GameCube (\*.raw, \*.bin) que contienen archivos de GC Pokémon. -* Archivos de entidades individuales de Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4) +* Archivos de entidades individuales de Pokémon (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4) * Archivos de Regalos Misteriosos (\*.pgt, \*.pcd, \*.pgf, .wc\*) incluyendo conversión a .pk\* * Importar archivos de entidades de GO Park (\*.gp1) incluyendo conversión a .pb7 * Importar equipos desde archivos Decrypted 3DS Battle Videos diff --git a/.github/README-fr.md b/.github/README-fr.md index ff6dc3bd0..92b6a85a4 100644 --- a/.github/README-fr.md +++ b/.github/README-fr.md @@ -7,7 +7,7 @@ PKHeX Prend en charge les fichiers suivants : * Enregistrer les fichiers ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin) * Fichiers de carte mémoire GameCube (\*.raw, \*.bin) contenant des sauvegardes de Pokémon GC. -* Fichiers d'entités Pokémon individuels (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4) +* Fichiers d'entités Pokémon individuels (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4) * Fichiers de cadeau mystère (\*.pgt, \*.pcd, \*.pgf, .wc\*) y compris la conversion en .pk\* * Importation d'entités GO Park (\*.gp1) incluant la conversion en .pb7 * Importation d'équipes à partir de 3DS Battle Videos diff --git a/.github/README-it.md b/.github/README-it.md index 4579fcab5..a7b74e708 100644 --- a/.github/README-it.md +++ b/.github/README-it.md @@ -7,7 +7,7 @@ Editor di Salvataggi Pokémon per la serie principale, programmato in [C#](https Supporta i seguenti file: * File di salvataggio ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin) * File di Memory Card GameCube (\*.raw, \*.bin) contenenti File di Salvataggio Pokémon. -* File di Entità Pokémon individuali (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4) +* File di Entità Pokémon individuali (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4) * File di Dono Segreto (\*.pgt, \*.pcd, \*.pgf, .wc\*) inclusa conversione in .pk\* * Importazione di Entità del Go Park (\*.gp1) inclusa conversione in .pb7 * Importazione di squadre da Video Lotta del 3DS decriptati diff --git a/PKHeX.Core/Editing/Bulk/BatchEditing.cs b/PKHeX.Core/Editing/Bulk/BatchEditing.cs index 1c66dc890..a3d4c15ae 100644 --- a/PKHeX.Core/Editing/Bulk/BatchEditing.cs +++ b/PKHeX.Core/Editing/Bulk/BatchEditing.cs @@ -19,7 +19,7 @@ public static class BatchEditing { typeof (PK8), typeof (PA8), typeof (PB8), typeof (PB7), - typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4), + typeof (PK7), typeof (PK6), typeof (PK5), typeof (PK4), typeof(BK4), typeof(RK4), typeof (PK3), typeof (XK3), typeof (CK3), typeof (PK2), typeof (SK2), typeof (PK1), }; diff --git a/PKHeX.Core/PKM/PK4.cs b/PKHeX.Core/PKM/PK4.cs index 658e3d0d7..3de71547a 100644 --- a/PKHeX.Core/PKM/PK4.cs +++ b/PKHeX.Core/PKM/PK4.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using static System.Buffers.Binary.BinaryPrimitives; namespace PKHeX.Core; @@ -288,15 +289,24 @@ public sealed class PK4 : G4PKM { BK4 bk4 = ConvertTo(); - // Enforce DP content only (no PtHGSS) - if (Form != 0 && !PersonalTable.DP[Species].HasForms && Species != 201) - bk4.Form = 0; - if (HeldItem > Legal.MaxItemID_4_DP) - bk4.HeldItem = 0; + StripPtHGSSContent(bk4); bk4.RefreshChecksum(); return bk4; } + public RK4 ConvertToRK4() + { + byte[] data = Data.AsSpan(0, PokeCrypto.SIZE_4RSTORED).ToArray(); + for (int i = PokeCrypto.SIZE_4STORED; i < PokeCrypto.SIZE_4RSTORED; i++) + data[i] = 0; + + RK4 rk4 = new RK4(data); + rk4.OwnershipType = RanchOwnershipType.Hayley; + + rk4.RefreshChecksum(); + return rk4; + } + public PK5 ConvertToPK5() { // Double Check Location Data to see if we're already a PK5 diff --git a/PKHeX.Core/PKM/RK4.cs b/PKHeX.Core/PKM/RK4.cs new file mode 100644 index 000000000..c174f8dc2 --- /dev/null +++ b/PKHeX.Core/PKM/RK4.cs @@ -0,0 +1,345 @@ +using System; +using System.Collections.Generic; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +/// Generation 4 format for My Pokémon Ranch. +/// +/// MPR-specific values are stored in Big Endian format rather than Little Endian. Beware. +/// +public sealed class RK4 : G4PKM +{ + private static readonly ushort[] Unused = +{ + 0x42, 0x43, 0x5E, 0x63, 0x64, 0x65, 0x66, 0x67, 0x87, + }; + + public override IReadOnlyList ExtraBytes => Unused; + + public override int SIZE_PARTY => PokeCrypto.SIZE_4RSTORED; + public override int SIZE_STORED => PokeCrypto.SIZE_4RSTORED; + public override EntityContext Context => EntityContext.Gen4; + public override PersonalInfo PersonalInfo => PersonalTable.Pt.GetFormEntry(Species, Form); + + public RK4() : base(PokeCrypto.SIZE_4RSTORED) { } + public RK4(byte[] data) : base(Decrypt(data)) { } + + private static byte[] Decrypt(byte[] data) + { + byte[] pkData = data.Slice(0, PokeCrypto.SIZE_4STORED); + PokeCrypto.DecryptIfEncrypted45(ref pkData); + return data; + } + + public override PKM Clone() => new RK4((byte[])Data.Clone()); + + // Structure + public override uint PID { get => ReadUInt32LittleEndian(Data.AsSpan(0x00)); set => WriteUInt32LittleEndian(Data.AsSpan(0x00), value); } + public override ushort Sanity { get => ReadUInt16LittleEndian(Data.AsSpan(0x04)); set => WriteUInt16LittleEndian(Data.AsSpan(0x04), value); } + public override ushort Checksum { get => ReadUInt16LittleEndian(Data.AsSpan(0x06)); set => WriteUInt16LittleEndian(Data.AsSpan(0x06), value); } + + #region Block A + public override ushort Species { get => ReadUInt16LittleEndian(Data.AsSpan(0x08)); set => WriteUInt16LittleEndian(Data.AsSpan(0x08), value); } + public override int HeldItem { get => ReadUInt16LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0A), (ushort)value); } + public override int TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), (ushort)value); } + public override int SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), (ushort)value); } + public override uint EXP { get => ReadUInt32LittleEndian(Data.AsSpan(0x10)); set => WriteUInt32LittleEndian(Data.AsSpan(0x10), value); } + public override int OT_Friendship { get => Data[0x14]; set => Data[0x14] = (byte)value; } + public override int Ability { get => Data[0x15]; set => Data[0x15] = (byte)value; } + public override int MarkValue { get => Data[0x16]; set => Data[0x16] = (byte)value; } + public override int Language { get => Data[0x17]; set => Data[0x17] = (byte)value; } + public override int EV_HP { get => Data[0x18]; set => Data[0x18] = (byte)value; } + public override int EV_ATK { get => Data[0x19]; set => Data[0x19] = (byte)value; } + public override int EV_DEF { get => Data[0x1A]; set => Data[0x1A] = (byte)value; } + public override int EV_SPE { get => Data[0x1B]; set => Data[0x1B] = (byte)value; } + public override int EV_SPA { get => Data[0x1C]; set => Data[0x1C] = (byte)value; } + public override int EV_SPD { get => Data[0x1D]; set => Data[0x1D] = (byte)value; } + public override byte CNT_Cool { get => Data[0x1E]; set => Data[0x1E] = value; } + public override byte CNT_Beauty { get => Data[0x1F]; set => Data[0x1F] = value; } + public override byte CNT_Cute { get => Data[0x20]; set => Data[0x20] = value; } + public override byte CNT_Smart { get => Data[0x21]; set => Data[0x21] = value; } + public override byte CNT_Tough { get => Data[0x22]; set => Data[0x22] = value; } + public override byte CNT_Sheen { get => Data[0x23]; set => Data[0x23] = value; } + + private byte RIB0 { get => Data[0x24]; set => Data[0x24] = value; } // Sinnoh 1 + private byte RIB1 { get => Data[0x25]; set => Data[0x25] = value; } // Sinnoh 2 + private byte RIB2 { get => Data[0x26]; set => Data[0x26] = value; } // Unova 1 + private byte RIB3 { get => Data[0x27]; set => Data[0x27] = value; } // Unova 2 + public override bool RibbonChampionSinnoh { get => (RIB0 & (1 << 0)) == 1 << 0; set => RIB0 = (byte)((RIB0 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonAbility { get => (RIB0 & (1 << 1)) == 1 << 1; set => RIB0 = (byte)((RIB0 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonAbilityGreat { get => (RIB0 & (1 << 2)) == 1 << 2; set => RIB0 = (byte)((RIB0 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonAbilityDouble { get => (RIB0 & (1 << 3)) == 1 << 3; set => RIB0 = (byte)((RIB0 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonAbilityMulti { get => (RIB0 & (1 << 4)) == 1 << 4; set => RIB0 = (byte)((RIB0 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonAbilityPair { get => (RIB0 & (1 << 5)) == 1 << 5; set => RIB0 = (byte)((RIB0 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonAbilityWorld { get => (RIB0 & (1 << 6)) == 1 << 6; set => RIB0 = (byte)((RIB0 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonAlert { get => (RIB0 & (1 << 7)) == 1 << 7; set => RIB0 = (byte)((RIB0 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonShock { get => (RIB1 & (1 << 0)) == 1 << 0; set => RIB1 = (byte)((RIB1 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonDowncast { get => (RIB1 & (1 << 1)) == 1 << 1; set => RIB1 = (byte)((RIB1 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonCareless { get => (RIB1 & (1 << 2)) == 1 << 2; set => RIB1 = (byte)((RIB1 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonRelax { get => (RIB1 & (1 << 3)) == 1 << 3; set => RIB1 = (byte)((RIB1 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonSnooze { get => (RIB1 & (1 << 4)) == 1 << 4; set => RIB1 = (byte)((RIB1 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonSmile { get => (RIB1 & (1 << 5)) == 1 << 5; set => RIB1 = (byte)((RIB1 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonGorgeous { get => (RIB1 & (1 << 6)) == 1 << 6; set => RIB1 = (byte)((RIB1 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonRoyal { get => (RIB1 & (1 << 7)) == 1 << 7; set => RIB1 = (byte)((RIB1 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonGorgeousRoyal { get => (RIB2 & (1 << 0)) == 1 << 0; set => RIB2 = (byte)((RIB2 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonFootprint { get => (RIB2 & (1 << 1)) == 1 << 1; set => RIB2 = (byte)((RIB2 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonRecord { get => (RIB2 & (1 << 2)) == 1 << 2; set => RIB2 = (byte)((RIB2 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonEvent { get => (RIB2 & (1 << 3)) == 1 << 3; set => RIB2 = (byte)((RIB2 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonLegend { get => (RIB2 & (1 << 4)) == 1 << 4; set => RIB2 = (byte)((RIB2 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonChampionWorld { get => (RIB2 & (1 << 5)) == 1 << 5; set => RIB2 = (byte)((RIB2 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonBirthday { get => (RIB2 & (1 << 6)) == 1 << 6; set => RIB2 = (byte)((RIB2 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonSpecial { get => (RIB2 & (1 << 7)) == 1 << 7; set => RIB2 = (byte)((RIB2 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonSouvenir { get => (RIB3 & (1 << 0)) == 1 << 0; set => RIB3 = (byte)((RIB3 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonWishing { get => (RIB3 & (1 << 1)) == 1 << 1; set => RIB3 = (byte)((RIB3 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonClassic { get => (RIB3 & (1 << 2)) == 1 << 2; set => RIB3 = (byte)((RIB3 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonPremier { get => (RIB3 & (1 << 3)) == 1 << 3; set => RIB3 = (byte)((RIB3 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RIB3_4 { get => (RIB3 & (1 << 4)) == 1 << 4; set => RIB3 = (byte)((RIB3 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIB3_5 { get => (RIB3 & (1 << 5)) == 1 << 5; set => RIB3 = (byte)((RIB3 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIB3_6 { get => (RIB3 & (1 << 6)) == 1 << 6; set => RIB3 = (byte)((RIB3 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIB3_7 { get => (RIB3 & (1 << 7)) == 1 << 7; set => RIB3 = (byte)((RIB3 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + #endregion + + #region Block B + public override ushort Move1 { get => ReadUInt16LittleEndian(Data.AsSpan(0x28)); set => WriteUInt16LittleEndian(Data.AsSpan(0x28), value); } + public override ushort Move2 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2A), value); } + public override ushort Move3 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2C), value); } + public override ushort Move4 { get => ReadUInt16LittleEndian(Data.AsSpan(0x2E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x2E), value); } + public override int Move1_PP { get => Data[0x30]; set => Data[0x30] = (byte)value; } + public override int Move2_PP { get => Data[0x31]; set => Data[0x31] = (byte)value; } + public override int Move3_PP { get => Data[0x32]; set => Data[0x32] = (byte)value; } + public override int Move4_PP { get => Data[0x33]; set => Data[0x33] = (byte)value; } + public override int Move1_PPUps { get => Data[0x34]; set => Data[0x34] = (byte)value; } + public override int Move2_PPUps { get => Data[0x35]; set => Data[0x35] = (byte)value; } + public override int Move3_PPUps { get => Data[0x36]; set => Data[0x36] = (byte)value; } + public override int Move4_PPUps { get => Data[0x37]; set => Data[0x37] = (byte)value; } + public uint IV32 { get => ReadUInt32LittleEndian(Data.AsSpan(0x38)); set => WriteUInt32LittleEndian(Data.AsSpan(0x38), value); } + public override int IV_HP { get => (int)(IV32 >> 00) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 00)) | ((value > 31 ? 31u : (uint)value) << 00); } + public override int IV_ATK { get => (int)(IV32 >> 05) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 05)) | ((value > 31 ? 31u : (uint)value) << 05); } + public override int IV_DEF { get => (int)(IV32 >> 10) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 10)) | ((value > 31 ? 31u : (uint)value) << 10); } + public override int IV_SPE { get => (int)(IV32 >> 15) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 15)) | ((value > 31 ? 31u : (uint)value) << 15); } + public override int IV_SPA { get => (int)(IV32 >> 20) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 20)) | ((value > 31 ? 31u : (uint)value) << 20); } + public override int IV_SPD { get => (int)(IV32 >> 25) & 0x1F; set => IV32 = (IV32 & ~(0x1Fu << 25)) | ((value > 31 ? 31u : (uint)value) << 25); } + public override bool IsEgg { get => ((IV32 >> 30) & 1) == 1; set => IV32 = (IV32 & ~0x40000000u) | (value ? 0x40000000u : 0u); } + public override bool IsNicknamed { get => ((IV32 >> 31) & 1) == 1; set => IV32 = (IV32 & 0x7FFFFFFFu) | (value ? 0x80000000u : 0u); } + + private byte RIB4 { get => Data[0x3C]; set => Data[0x3C] = value; } // Hoenn 1a + private byte RIB5 { get => Data[0x3D]; set => Data[0x3D] = value; } // Hoenn 1b + private byte RIB6 { get => Data[0x3E]; set => Data[0x3E] = value; } // Hoenn 2a + private byte RIB7 { get => Data[0x3F]; set => Data[0x3F] = value; } // Hoenn 2b + public override bool RibbonG3Cool { get => (RIB4 & (1 << 0)) == 1 << 0; set => RIB4 = (byte)((RIB4 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3CoolSuper { get => (RIB4 & (1 << 1)) == 1 << 1; set => RIB4 = (byte)((RIB4 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3CoolHyper { get => (RIB4 & (1 << 2)) == 1 << 2; set => RIB4 = (byte)((RIB4 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3CoolMaster { get => (RIB4 & (1 << 3)) == 1 << 3; set => RIB4 = (byte)((RIB4 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG3Beauty { get => (RIB4 & (1 << 4)) == 1 << 4; set => RIB4 = (byte)((RIB4 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG3BeautySuper { get => (RIB4 & (1 << 5)) == 1 << 5; set => RIB4 = (byte)((RIB4 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG3BeautyHyper { get => (RIB4 & (1 << 6)) == 1 << 6; set => RIB4 = (byte)((RIB4 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG3BeautyMaster { get => (RIB4 & (1 << 7)) == 1 << 7; set => RIB4 = (byte)((RIB4 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG3Cute { get => (RIB5 & (1 << 0)) == 1 << 0; set => RIB5 = (byte)((RIB5 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3CuteSuper { get => (RIB5 & (1 << 1)) == 1 << 1; set => RIB5 = (byte)((RIB5 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3CuteHyper { get => (RIB5 & (1 << 2)) == 1 << 2; set => RIB5 = (byte)((RIB5 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3CuteMaster { get => (RIB5 & (1 << 3)) == 1 << 3; set => RIB5 = (byte)((RIB5 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG3Smart { get => (RIB5 & (1 << 4)) == 1 << 4; set => RIB5 = (byte)((RIB5 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG3SmartSuper { get => (RIB5 & (1 << 5)) == 1 << 5; set => RIB5 = (byte)((RIB5 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG3SmartHyper { get => (RIB5 & (1 << 6)) == 1 << 6; set => RIB5 = (byte)((RIB5 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG3SmartMaster { get => (RIB5 & (1 << 7)) == 1 << 7; set => RIB5 = (byte)((RIB5 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG3Tough { get => (RIB6 & (1 << 0)) == 1 << 0; set => RIB6 = (byte)((RIB6 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG3ToughSuper { get => (RIB6 & (1 << 1)) == 1 << 1; set => RIB6 = (byte)((RIB6 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG3ToughHyper { get => (RIB6 & (1 << 2)) == 1 << 2; set => RIB6 = (byte)((RIB6 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG3ToughMaster { get => (RIB6 & (1 << 3)) == 1 << 3; set => RIB6 = (byte)((RIB6 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonChampionG3 { get => (RIB6 & (1 << 4)) == 1 << 4; set => RIB6 = (byte)((RIB6 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonWinning { get => (RIB6 & (1 << 5)) == 1 << 5; set => RIB6 = (byte)((RIB6 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonVictory { get => (RIB6 & (1 << 6)) == 1 << 6; set => RIB6 = (byte)((RIB6 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonArtist { get => (RIB6 & (1 << 7)) == 1 << 7; set => RIB6 = (byte)((RIB6 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonEffort { get => (RIB7 & (1 << 0)) == 1 << 0; set => RIB7 = (byte)((RIB7 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonChampionBattle { get => (RIB7 & (1 << 1)) == 1 << 1; set => RIB7 = (byte)((RIB7 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonChampionRegional { get => (RIB7 & (1 << 2)) == 1 << 2; set => RIB7 = (byte)((RIB7 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonChampionNational { get => (RIB7 & (1 << 3)) == 1 << 3; set => RIB7 = (byte)((RIB7 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonCountry { get => (RIB7 & (1 << 4)) == 1 << 4; set => RIB7 = (byte)((RIB7 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonNational { get => (RIB7 & (1 << 5)) == 1 << 5; set => RIB7 = (byte)((RIB7 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonEarth { get => (RIB7 & (1 << 6)) == 1 << 6; set => RIB7 = (byte)((RIB7 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonWorld { get => (RIB7 & (1 << 7)) == 1 << 7; set => RIB7 = (byte)((RIB7 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + + public override bool FatefulEncounter { get => (Data[0x40] & 1) == 1; set => Data[0x40] = (byte)((Data[0x40] & ~0x01) | (value ? 1 : 0)); } + public override int Gender { get => (Data[0x40] >> 1) & 0x3; set => Data[0x40] = (byte)((Data[0x40] & ~0x06) | (value << 1)); } + public override byte Form { get => (byte)(Data[0x40] >> 3); set => Data[0x40] = (byte)((Data[0x40] & 0x07) | (value << 3)); } + public override int ShinyLeaf { get => Data[0x41]; set => Data[0x41] = (byte)value; } + // 0x42-0x43 Unused + public override ushort Egg_LocationExtended + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x44)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x44), value); + } + + public override ushort Met_LocationExtended + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x46)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x46), value); + } + + #endregion + + #region Block C + public override string Nickname + { + get => StringConverter4.GetString(Nickname_Trash); + set => StringConverter4.SetString(Nickname_Trash, value.AsSpan(), 10, StringConverterOption.None); + } + + // 0x5E unused + public override int Version { get => Data[0x5F]; set => Data[0x5F] = (byte)value; } + private byte RIB8 { get => Data[0x60]; set => Data[0x60] = value; } // Sinnoh 3 + private byte RIB9 { get => Data[0x61]; set => Data[0x61] = value; } // Sinnoh 4 + private byte RIBA { get => Data[0x62]; set => Data[0x62] = value; } // Sinnoh 5 + private byte RIBB { get => Data[0x63]; set => Data[0x63] = value; } // Sinnoh 6 + public override bool RibbonG4Cool { get => (RIB8 & (1 << 0)) == 1 << 0; set => RIB8 = (byte)((RIB8 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4CoolGreat { get => (RIB8 & (1 << 1)) == 1 << 1; set => RIB8 = (byte)((RIB8 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4CoolUltra { get => (RIB8 & (1 << 2)) == 1 << 2; set => RIB8 = (byte)((RIB8 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4CoolMaster { get => (RIB8 & (1 << 3)) == 1 << 3; set => RIB8 = (byte)((RIB8 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG4Beauty { get => (RIB8 & (1 << 4)) == 1 << 4; set => RIB8 = (byte)((RIB8 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG4BeautyGreat { get => (RIB8 & (1 << 5)) == 1 << 5; set => RIB8 = (byte)((RIB8 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG4BeautyUltra { get => (RIB8 & (1 << 6)) == 1 << 6; set => RIB8 = (byte)((RIB8 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG4BeautyMaster { get => (RIB8 & (1 << 7)) == 1 << 7; set => RIB8 = (byte)((RIB8 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG4Cute { get => (RIB9 & (1 << 0)) == 1 << 0; set => RIB9 = (byte)((RIB9 & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4CuteGreat { get => (RIB9 & (1 << 1)) == 1 << 1; set => RIB9 = (byte)((RIB9 & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4CuteUltra { get => (RIB9 & (1 << 2)) == 1 << 2; set => RIB9 = (byte)((RIB9 & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4CuteMaster { get => (RIB9 & (1 << 3)) == 1 << 3; set => RIB9 = (byte)((RIB9 & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RibbonG4Smart { get => (RIB9 & (1 << 4)) == 1 << 4; set => RIB9 = (byte)((RIB9 & ~(1 << 4)) | (value ? 1 << 4 : 0)); } + public override bool RibbonG4SmartGreat { get => (RIB9 & (1 << 5)) == 1 << 5; set => RIB9 = (byte)((RIB9 & ~(1 << 5)) | (value ? 1 << 5 : 0)); } + public override bool RibbonG4SmartUltra { get => (RIB9 & (1 << 6)) == 1 << 6; set => RIB9 = (byte)((RIB9 & ~(1 << 6)) | (value ? 1 << 6 : 0)); } + public override bool RibbonG4SmartMaster { get => (RIB9 & (1 << 7)) == 1 << 7; set => RIB9 = (byte)((RIB9 & ~(1 << 7)) | (value ? 1 << 7 : 0)); } + public override bool RibbonG4Tough { get => (RIBA & (1 << 0)) == 1 << 0; set => RIBA = (byte)((RIBA & ~(1 << 0)) | (value ? 1 << 0 : 0)); } + public override bool RibbonG4ToughGreat { get => (RIBA & (1 << 1)) == 1 << 1; set => RIBA = (byte)((RIBA & ~(1 << 1)) | (value ? 1 << 1 : 0)); } + public override bool RibbonG4ToughUltra { get => (RIBA & (1 << 2)) == 1 << 2; set => RIBA = (byte)((RIBA & ~(1 << 2)) | (value ? 1 << 2 : 0)); } + public override bool RibbonG4ToughMaster { get => (RIBA & (1 << 3)) == 1 << 3; set => RIBA = (byte)((RIBA & ~(1 << 3)) | (value ? 1 << 3 : 0)); } + public override bool RIBA_4 { get => (RIBA & (1 << 4)) == 1 << 4; set => RIBA = (byte)((RIBA & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIBA_5 { get => (RIBA & (1 << 5)) == 1 << 5; set => RIBA = (byte)((RIBA & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIBA_6 { get => (RIBA & (1 << 6)) == 1 << 6; set => RIBA = (byte)((RIBA & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIBA_7 { get => (RIBA & (1 << 7)) == 1 << 7; set => RIBA = (byte)((RIBA & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + public override bool RIBB_0 { get => (RIBB & (1 << 0)) == 1 << 0; set => RIBB = (byte)((RIBB & ~(1 << 0)) | (value ? 1 << 0 : 0)); } // Unused + public override bool RIBB_1 { get => (RIBB & (1 << 1)) == 1 << 1; set => RIBB = (byte)((RIBB & ~(1 << 1)) | (value ? 1 << 1 : 0)); } // Unused + public override bool RIBB_2 { get => (RIBB & (1 << 2)) == 1 << 2; set => RIBB = (byte)((RIBB & ~(1 << 2)) | (value ? 1 << 2 : 0)); } // Unused + public override bool RIBB_3 { get => (RIBB & (1 << 3)) == 1 << 3; set => RIBB = (byte)((RIBB & ~(1 << 3)) | (value ? 1 << 3 : 0)); } // Unused + public override bool RIBB_4 { get => (RIBB & (1 << 4)) == 1 << 4; set => RIBB = (byte)((RIBB & ~(1 << 4)) | (value ? 1 << 4 : 0)); } // Unused + public override bool RIBB_5 { get => (RIBB & (1 << 5)) == 1 << 5; set => RIBB = (byte)((RIBB & ~(1 << 5)) | (value ? 1 << 5 : 0)); } // Unused + public override bool RIBB_6 { get => (RIBB & (1 << 6)) == 1 << 6; set => RIBB = (byte)((RIBB & ~(1 << 6)) | (value ? 1 << 6 : 0)); } // Unused + public override bool RIBB_7 { get => (RIBB & (1 << 7)) == 1 << 7; set => RIBB = (byte)((RIBB & ~(1 << 7)) | (value ? 1 << 7 : 0)); } // Unused + // 0x64-0x67 Unused + #endregion + + #region Block D + public override string OT_Name + { + get => StringConverter4.GetString(OT_Trash); + set => StringConverter4.SetString(OT_Trash, value.AsSpan(), 7, StringConverterOption.None); + } + + public override int Egg_Year { get => Data[0x78]; set => Data[0x78] = (byte)value; } + public override int Egg_Month { get => Data[0x79]; set => Data[0x79] = (byte)value; } + public override int Egg_Day { get => Data[0x7A]; set => Data[0x7A] = (byte)value; } + public override int Met_Year { get => Data[0x7B]; set => Data[0x7B] = (byte)value; } + public override int Met_Month { get => Data[0x7C]; set => Data[0x7C] = (byte)value; } + public override int Met_Day { get => Data[0x7D]; set => Data[0x7D] = (byte)value; } + + public override ushort Egg_LocationDP + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x7E)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x7E), value); + } + public override ushort Met_LocationDP + { + get => ReadUInt16LittleEndian(Data.AsSpan(0x80)); + set => WriteUInt16LittleEndian(Data.AsSpan(0x80), value); + } + + private byte PKRS { get => Data[0x82]; set => Data[0x82] = value; } + public override int PKRS_Days { get => PKRS & 0xF; set => PKRS = (byte)((PKRS & ~0xF) | value); } + public override int PKRS_Strain { get => PKRS >> 4; set => PKRS = (byte)((PKRS & 0xF) | (value << 4)); } + public override byte BallDPPt { get => Data[0x83]; set => Data[0x83] = value; } + public override int Met_Level { get => Data[0x84] & ~0x80; set => Data[0x84] = (byte)((Data[0x84] & 0x80) | value); } + public override int OT_Gender { get => Data[0x84] >> 7; set => Data[0x84] = (byte)((Data[0x84] & ~0x80) | (value << 7)); } + public override GroundTileType GroundTile { get => (GroundTileType)Data[0x85]; set => Data[0x85] = (byte)value; } + public override byte BallHGSS { get => Data[0x86]; set => Data[0x86] = value; } + public override byte PokeathlonStat { get => Data[0x87]; set => Data[0x87] = value; } + #endregion + + #region Battle Stats + public override int Status_Condition { get => 0; set { } } + public override int Stat_Level { get => CurrentLevel; set { } } + public override int Stat_HPCurrent { get => PersonalInfo.HP; set { } } + public override int Stat_HPMax { get => PersonalInfo.HP; set { } } + public override int Stat_ATK { get => PersonalInfo.ATK; set { } } + public override int Stat_DEF { get => PersonalInfo.DEF; set { } } + public override int Stat_SPE { get => PersonalInfo.SPE; set { } } + public override int Stat_SPA { get => PersonalInfo.SPA; set { } } + public override int Stat_SPD { get => PersonalInfo.SPD; set { } } + + #endregion + + #region My Pokémon Ranch Data + + /* ====Metadata==== + * uint8_t poke_type;// 01 trainer, 04 hayley, 05 traded + * unused alignment byte + * uint16_t tradeable;// 02 is tradeable, normal 00 + * uint16_t tid; + * uint16_t sid; + * uint32_t name1; + * uint32_t name2; + * uint32_t name3; + * uint32_t name4; + */ + + // 4 bytes extra at the end of the metadata, unused/reserved; or, it's just extra for the Trainer Name. + + public RanchOwnershipType OwnershipType + { + get => (RanchOwnershipType)Data[0x88]; + set => Data[0x88] = (byte)value; + } + + public RanchOwnershipStatus OwnershipStatus + { + get => (RanchOwnershipStatus)ReadUInt16BigEndian(Data.AsSpan(0x8A)); + set => WriteUInt16BigEndian(Data.AsSpan(0x8A), (ushort)value); + } + + public ushort HT_TID { get => ReadUInt16LittleEndian(Data.AsSpan(0x8C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8C), value); } + public ushort HT_SID { get => ReadUInt16LittleEndian(Data.AsSpan(0x8E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x8E), value); } + + public override Span HT_Trash => Data.AsSpan(0x90, 0x10); + public override string HT_Name + { + get => StringConverter4.GetString(HT_Trash); + set => StringConverter4.SetString(HT_Trash, value.AsSpan(), 7, StringConverterOption.None); + } + + #endregion + + // Methods + public void SetSaveRevision(int ranchSaveRevision) + { + if (ranchSaveRevision == 0) + StripPtHGSSContent(this); + } + + protected override byte[] Encrypt() + { + RefreshChecksum(); + + byte[] data = (byte[])Data.Clone(); + byte[] pkData = data.Slice(0, PokeCrypto.SIZE_4STORED); + pkData = PokeCrypto.EncryptArray45(pkData); + pkData.CopyTo(data, 0); + return data; + } + + public PK4 ConvertToPK4() + { + byte[] data = Data.AsSpan(0, PokeCrypto.SIZE_4STORED).ToArray(); + var pk4 = new PK4(data); + pk4.ResetPartyStats(); + pk4.RefreshChecksum(); + return pk4; + } +} diff --git a/PKHeX.Core/PKM/Shared/G4PKM.cs b/PKHeX.Core/PKM/Shared/G4PKM.cs index dfd90f6d4..433f311d6 100644 --- a/PKHeX.Core/PKM/Shared/G4PKM.cs +++ b/PKHeX.Core/PKM/Shared/G4PKM.cs @@ -294,6 +294,15 @@ public abstract class G4PKM : PKM, return false; } + // Enforce DP content only (no PtHGSS) + protected void StripPtHGSSContent(PKM pk) + { + if (Form != 0 && !PersonalTable.DP[Species].HasForms && Species != 201) + pk.Form = 0; + if (HeldItem > Legal.MaxItemID_4_DP) + pk.HeldItem = 0; + } + protected T ConvertTo() where T : G4PKM, new() { var pk = new T diff --git a/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs b/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs index c7ddf2c5c..005b9e496 100644 --- a/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs +++ b/PKHeX.Core/PKM/Util/Conversion/EntityConverter.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Diagnostics; using static PKHeX.Core.EntityConverterResult; @@ -119,6 +119,7 @@ public static class EntityConverter PK3 pk3 when destType == typeof(CK3) => pk3.ConvertToCK3(), PK3 pk3 when destType == typeof(XK3) => pk3.ConvertToXK3(), PK4 pk4 when destType == typeof(BK4) => pk4.ConvertToBK4(), + PK4 pk4 when destType == typeof(RK4) => pk4.ConvertToRK4(), PB8 pb8 when destType == typeof(PK8) => pb8.ConvertToPK8(), PK8 pk8 when destType == typeof(PB8) => pk8.ConvertToPB8(), @@ -141,6 +142,7 @@ public static class EntityConverter CK3 ck3 => ck3.ConvertToPK3(), XK3 xk3 => xk3.ConvertToPK3(), BK4 bk4 => bk4.ConvertToPK4(), + RK4 rk4 => rk4.ConvertToPK4(), _ => InvalidTransfer(out result, NoTransferRoute), }; diff --git a/PKHeX.Core/PKM/Util/EntityDetection.cs b/PKHeX.Core/PKM/Util/EntityDetection.cs index df6996a3e..ed9f09227 100644 --- a/PKHeX.Core/PKM/Util/EntityDetection.cs +++ b/PKHeX.Core/PKM/Util/EntityDetection.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using static System.Buffers.Binary.BinaryPrimitives; @@ -12,7 +12,7 @@ public static class EntityDetection PokeCrypto.SIZE_2ULIST, PokeCrypto.SIZE_2JLIST, PokeCrypto.SIZE_2STADIUM, PokeCrypto.SIZE_3STORED, PokeCrypto.SIZE_3PARTY, PokeCrypto.SIZE_3CSTORED, PokeCrypto.SIZE_3XSTORED, - PokeCrypto.SIZE_4STORED, PokeCrypto.SIZE_4PARTY, + PokeCrypto.SIZE_4STORED, PokeCrypto.SIZE_4PARTY, PokeCrypto.SIZE_4RSTORED, PokeCrypto.SIZE_5PARTY, PokeCrypto.SIZE_6STORED, PokeCrypto.SIZE_6PARTY, PokeCrypto.SIZE_8STORED, PokeCrypto.SIZE_8PARTY, @@ -29,6 +29,7 @@ public static class EntityDetection public static bool IsPresentGB(ReadOnlySpan data) => data[0] != 0; // Species non-zero public static bool IsPresentGC(ReadOnlySpan data) => ReadUInt16BigEndian(data) != 0; // Species non-zero public static bool IsPresentGBA(ReadOnlySpan data) => (data[0x13] & 0xFB) == 2; // ignore egg flag, must be FlagHasSpecies. + public static bool IsPresentSAV4Ranch(ReadOnlySpan data) => ReadUInt32LittleEndian(data) != 0 && ReadUInt32BigEndian(data) != 0x28; // Species non-zero, ignore file end marker public static bool IsPresent(ReadOnlySpan data) { diff --git a/PKHeX.Core/PKM/Util/EntityFileExtension.cs b/PKHeX.Core/PKM/Util/EntityFileExtension.cs index ec9329424..66f3586cc 100644 --- a/PKHeX.Core/PKM/Util/EntityFileExtension.cs +++ b/PKHeX.Core/PKM/Util/EntityFileExtension.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace PKHeX.Core; @@ -30,7 +30,10 @@ public static class EntityFileExtension result.Add("xk3"); // xd } if (maxGeneration >= 4) + { result.Add("bk4"); // battle revolution + result.Add("rk4"); // My Pokemon Ranch + } if (maxGeneration >= 7) result.Add(ExtensionPB7); // let's go if (maxGeneration >= 8) diff --git a/PKHeX.Core/PKM/Util/EntityFormat.cs b/PKHeX.Core/PKM/Util/EntityFormat.cs index 4647782a8..0a12a7d8e 100644 --- a/PKHeX.Core/PKM/Util/EntityFormat.cs +++ b/PKHeX.Core/PKM/Util/EntityFormat.cs @@ -29,6 +29,7 @@ public static class EntityFormat SIZE_3CSTORED => FormatCK3, SIZE_3XSTORED => FormatXK3, SIZE_4PARTY or SIZE_4STORED => GetFormat45(data), + SIZE_4RSTORED => FormatRK4, SIZE_5PARTY => FormatPK5, SIZE_6STORED => GetFormat67(data), SIZE_6PARTY => GetFormat67_PGT(data), @@ -65,6 +66,8 @@ public static class EntityFormat // assumes decrypted state private static EntityFormatDetected GetFormat45(ReadOnlySpan data) { + if (data.Length == PokeCrypto.SIZE_4RSTORED) + return FormatRK4; if (ReadUInt16LittleEndian(data[0x4..]) != 0) return FormatBK4; // BK4 non-zero sanity if (data[0x5F] < 0x10 && ReadUInt16LittleEndian(data[0x80..]) < 0x3333) @@ -114,6 +117,7 @@ public static class EntityFormat FormatXK3 => new XK3(data), FormatPK4 => new PK4(data), FormatBK4 => new BK4(data), + FormatRK4 => new RK4(data), FormatPK5 => new PK5(data), FormatPK6 => new PK6(data), FormatPK7 => new PK7(data), @@ -202,7 +206,7 @@ public enum EntityFormatDetected FormatPK1, FormatPK2, FormatSK2, FormatPK3, FormatCK3, FormatXK3, - FormatPK4, FormatBK4, FormatPK5, + FormatPK4, FormatBK4, FormatRK4, FormatPK5, FormatPK6, FormatPK7, FormatPB7, FormatPK8, FormatPA8, FormatPB8, diff --git a/PKHeX.Core/PKM/Util/PokeCrypto.cs b/PKHeX.Core/PKM/Util/PokeCrypto.cs index de0c9f675..b97c60df6 100644 --- a/PKHeX.Core/PKM/Util/PokeCrypto.cs +++ b/PKHeX.Core/PKM/Util/PokeCrypto.cs @@ -31,6 +31,7 @@ public static class PokeCrypto internal const int SIZE_4PARTY = 236; internal const int SIZE_4STORED = 136; + internal const int SIZE_4RSTORED = 164; // 4STORED + 0x1C bytes of extra data private const int SIZE_4BLOCK = 32; internal const int SIZE_5PARTY = 220; diff --git a/PKHeX.Core/Saves/SaveFile.cs b/PKHeX.Core/Saves/SaveFile.cs index f56a35634..ec1abe1a1 100644 --- a/PKHeX.Core/Saves/SaveFile.cs +++ b/PKHeX.Core/Saves/SaveFile.cs @@ -534,7 +534,7 @@ public abstract class SaveFile : ITrainerInfo, IGameValueLimit, IBoxDetailWallpa public int NextOpenBoxSlot(int lastKnownOccupied = -1) { var storage = BoxBuffer.AsSpan(); - int count = BoxSlotCount * BoxCount; + int count = SlotCount; for (int i = lastKnownOccupied + 1; i < count; i++) { int offset = GetBoxSlotOffset(i); diff --git a/PKHeX.Core/Saves/Storage/SAV4Ranch.cs b/PKHeX.Core/Saves/Storage/SAV4Ranch.cs index 26253a1f8..0df4110ef 100644 --- a/PKHeX.Core/Saves/Storage/SAV4Ranch.cs +++ b/PKHeX.Core/Saves/Storage/SAV4Ranch.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Security.Cryptography; using static System.Buffers.Binary.BinaryPrimitives; @@ -10,75 +11,145 @@ namespace PKHeX.Core; /// public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision { - protected override int SIZE_STORED => 0x88 + 0x1C; - protected override int SIZE_PARTY => SIZE_STORED; + protected override int SIZE_STORED => PokeCrypto.SIZE_4RSTORED; + protected override int SIZE_PARTY => PokeCrypto.SIZE_4RSTORED; public int SaveRevision => Version == GameVersion.DP ? 0 : 1; public string SaveRevisionString => Version == GameVersion.DP ? "-DP" : "-Pt"; - public override int BoxCount { get; } - public override int SlotCount { get; } + // ReSharper disable PrivateFieldCanBeConvertedToLocalVariable + private readonly int DataEndMarker; + private int DataEndMarkerOffset; + private readonly int MiiDataOffset; + private readonly int MiiCountOffset; + private readonly int TrainerMiiDataOffset; + private readonly int TrainerMiiCountOffset; + private readonly int PokemonCountOffset; + + public override int SlotCount => RanchLevel.GetSlotCount(CurrentRanchLevelIndex); + public override int BoxCount => (int)Math.Ceiling((decimal)SlotCount / SlotsPerBox); + public int MiiCount { get; } + public int TrainerMiiCount { get; } + public int MaxToys => RanchLevel.GetMaxToys(CurrentRanchLevelIndex); + public int MaxMiiCount => RanchLevel.GetMaxMiis(CurrentRanchLevelIndex); public override IPersonalTable Personal => PersonalTable.Pt; public override IReadOnlyList HeldItems => Legal.HeldItems_Pt; protected override SaveFile CloneInternal() => new SAV4Ranch((byte[])Data.Clone()); - public override string PlayTimeString => $"{Checksums.CRC16Invert(Data):X4}"; protected internal override string ShortSummary => $"{OT} {PlayTimeString}"; public override string Extension => ".bin"; - protected override PKM GetPKM(byte[] data) => new PK4(data); - protected override byte[] DecryptPKM(byte[] data) => PokeCrypto.DecryptArray45(data); + protected override PKM GetPKM(byte[] data) => new RK4(data); public override StorageSlotSource GetSlotFlags(int index) => index >= SlotCount ? StorageSlotSource.Locked : StorageSlotSource.None; protected override bool IsSlotSwapProtected(int box, int slot) => IsSlotOverwriteProtected(box, slot); + public override bool IsPKMPresent(ReadOnlySpan data) => EntityDetection.IsPresentSAV4Ranch(data); - public SAV4Ranch(byte[] data) : base(data, typeof(PK4), 0) + public SAV4Ranch(byte[] data) : base(data, typeof(RK4), 0) { Version = Data.Length == SaveUtil.SIZE_G4RANCH_PLAT ? GameVersion.Pt : GameVersion.DP; OT = GetString(0x770, 0x12); - // 0x18 starts the header table - // Block 00, Offset = ??? - // Block 01, Offset = Mii Data - // Block 02, Offset = Mii Link Data - // Block 03, Offset = Pokemon Data - // Block 04, Offset = ?? + // 0x18 starts the header table: [u32 BlockID, u32 Offset] + // Block 00, Offset = Metadata object + // Block 01, Offset = Mii Data Array object + // Block 02, Offset = Mii Link Data Array object + // Block 03, Offset = Pokemon Data Array object + // Block 04, Offset = reserved object // Unpack the binary a little: - // size, count, Mii data[count] - // size, count, Mii Link data[count] - // size, count, Pokemon (PK4 + metadata)[count] - // size, count, ??? + // 00: size, ??? + // 01: size, count, Mii data[count] + // 02: size, count, Mii Link data[count] + // 03: size, count, Pokemon (PK4 + metadata)[count] + // 04: size, count, ??? - /* ====Metadata==== - * uint8_t poke_type;// 01 trainer, 04 hayley, 05 traded - * uint8_t tradeable;// 02 is tradeable, normal 00 - * uint16_t tid; - * uint16_t sid; - * uint32_t name1; - * uint32_t name2; - * uint32_t name3; - * uint32_t name4; - */ + MiiCountOffset = ReadInt32BigEndian(Data.AsSpan(0x24)) + 4; + TrainerMiiCountOffset = ReadInt32BigEndian(Data.AsSpan(0x2C)) + 4; + MiiCount = ReadInt32BigEndian(Data.AsSpan(MiiCountOffset)); + TrainerMiiCount = ReadInt32BigEndian(Data.AsSpan(TrainerMiiCountOffset)); - var pkCountOffset = ReadInt32BigEndian(Data.AsSpan(0x34)) + 4; - SlotCount = ReadInt32BigEndian(Data.AsSpan(pkCountOffset)); - BoxCount = (int)Math.Ceiling((decimal)SlotCount / SlotsPerBox); + MiiDataOffset = MiiCountOffset + 4; + TrainerMiiDataOffset = TrainerMiiCountOffset + 4; - Box = pkCountOffset + 4; + PokemonCountOffset = ReadInt32BigEndian(Data.AsSpan(0x34)) + 4; + Box = PokemonCountOffset + 4; - FinalCountOffset = ReadInt32BigEndian(Data.AsSpan(0x3C)); - FinalCount = ReadInt32BigEndian(Data.AsSpan(FinalCountOffset)); + DataEndMarkerOffset = ReadInt32BigEndian(Data.AsSpan(0x3C)); + DataEndMarker = ReadInt32BigEndian(Data.AsSpan(DataEndMarkerOffset)); } - private readonly int FinalCount; - private readonly int FinalCountOffset; + private const int ToyBaseOffset = 0x227B; + + public byte CurrentRanchLevelIndex { get => Data[0x5A]; set => Data[0x5A] = value; } + public byte PlannedRanchLevelIndex { get => Data[0x5B]; set => Data[0x5B] = value; } // tomorrow's level + + public uint SecondsSince2000 { get => ReadUInt32BigEndian(Data.AsSpan(0x5C)); set => WriteUInt32BigEndian(Data.AsSpan(0x5C), value); } + public uint TotalSeconds { get => ReadUInt32BigEndian(Data.AsSpan(0x60)); set => WriteUInt32BigEndian(Data.AsSpan(0x60), value); } + public ushort NextHayleyBringNationalDex { get => ReadUInt16LittleEndian(Data.AsSpan(0x6A)); set => WriteUInt16LittleEndian(Data.AsSpan(0x6A), value); } + + public RanchToy GetRanchToy(int index) + { + if ((uint)index >= MaxToys) + throw new ArgumentOutOfRangeException(nameof(index)); + + int toyOffset = ToyBaseOffset + (RanchToy.SIZE * index); + var data = Data.Slice(toyOffset, RanchToy.SIZE); + return new RanchToy(data); + } + + public void SetRanchToy(RanchToy toy, int index) + { + if ((uint)index >= MaxToys) + throw new ArgumentOutOfRangeException(nameof(index)); + + int toyOffset = ToyBaseOffset + (RanchToy.SIZE * index); + SetData(Data, toy.Data, toyOffset); + } + + public RanchMii GetRanchMii(int index) + { + if ((uint)index >= MiiCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + int offset = MiiDataOffset + (RanchMii.SIZE * index); + var data = Data.Slice(offset, RanchMii.SIZE); + return new RanchMii(data); + } + + public void SetRanchMii(RanchMii trainer, int index) + { + if ((uint)index >= MiiCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + int offset = MiiDataOffset + (RanchMii.SIZE * index); + SetData(Data, trainer.Data, offset); + } + + public RanchTrainerMii GetRanchTrainerMii(int index) + { + if ((uint)index >= TrainerMiiCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + int offset = TrainerMiiDataOffset + (RanchTrainerMii.SIZE * index); + var data = Data.Slice(offset, RanchTrainerMii.SIZE); + return new RanchTrainerMii(data); + } + + public void SetRanchTrainerMii(RanchTrainerMii mii, int index) + { + if ((uint)index >= TrainerMiiCount) + throw new ArgumentOutOfRangeException(nameof(index)); + + int offset = TrainerMiiDataOffset + (RanchTrainerMii.SIZE * index); + SetData(Data, mii.Data, offset); + } protected override void SetChecksums() { // ensure the final data is written if the user screws stuff up - WriteInt32BigEndian(Data.AsSpan(FinalCountOffset), FinalCount); - var goodlen = (FinalCountOffset + 4); + WriteInt32BigEndian(Data.AsSpan(DataEndMarkerOffset), DataEndMarker); + var goodlen = (DataEndMarkerOffset + 4); Array.Clear(Data, goodlen, Data.Length - goodlen); // 20 byte SHA checksum at the top of the file, which covers all data that follows. @@ -87,6 +158,78 @@ public sealed class SAV4Ranch : BulkStorage, ISaveFileRevision SetData(result, 0); } + protected override byte[] DecryptPKM(byte[] data) + { + var pokeData = PokeCrypto.DecryptArray45(data.Slice(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 data, int offset, string htName = "", ushort htTID = 0, ushort htSID = 0, RanchOwnershipType type = RanchOwnershipType.Hayley) + { + RK4 rk = (RK4)this.GetCompatiblePKM(pk); + rk.OwnershipType = type; + rk.HT_TID = htTID; + rk.HT_SID = htSID; + rk.HT_Name = htName; + + WriteBoxSlot(rk, data, offset); + } + + public override void WriteBoxSlot(PKM pk, Span data, int offset) + { + bool isBlank = pk.Data.SequenceEqual(BlankPKM.Data); + if (pk is not RK4 rk4) + { + WriteBoxSlotInternal(pk, data, offset); + return; + } + + if (!isBlank && rk4.OwnershipType == RanchOwnershipType.None) + rk4.OwnershipType = RanchOwnershipType.Hayley; // Pokemon without an Ownership type get erased when the save is loaded. Hayley is considered 'default'. + + base.WriteBoxSlot(rk4, data, offset); + if ((offset + SIZE_STORED) > DataEndMarkerOffset) + { + DataEndMarkerOffset = (offset + SIZE_STORED); + WriteInt32BigEndian(Data.AsSpan(0x3C), DataEndMarkerOffset); + WriteInt32BigEndian(Data.AsSpan(DataEndMarkerOffset), DataEndMarker); + } + + int pkStart = PokemonCountOffset + 4; + int pkEnd = DataEndMarkerOffset; + int pkCount = (pkEnd - pkStart) / SIZE_STORED; + WriteInt32BigEndian(Data.AsSpan(PokemonCountOffset), pkCount); + } + + private TimeSpan PlayedSpan + { + get => TimeSpan.FromSeconds(TotalSeconds); + set => TotalSeconds = (uint)value.TotalSeconds; + } + + public override int PlayedHours + { + get => (ushort)PlayedSpan.TotalHours; + set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromHours(time.TotalHours) + TimeSpan.FromHours(value); } + } + + public override int PlayedMinutes + { + get => (byte)PlayedSpan.Minutes; + set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromMinutes(time.Minutes) + TimeSpan.FromMinutes(value); } + } + + public override int PlayedSeconds + { + get => (byte)PlayedSpan.Seconds; + set { var time = PlayedSpan; PlayedSpan = time - TimeSpan.FromSeconds(time.Seconds) + TimeSpan.FromSeconds(value); } + } + public override string GetString(ReadOnlySpan data) => StringConverter4GC.GetStringUnicode(data); public override int SetString(Span destBuffer, ReadOnlySpan value, int maxLength, StringConverterOption option) diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchLevel.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchLevel.cs new file mode 100644 index 000000000..61db27314 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchLevel.cs @@ -0,0 +1,61 @@ +namespace PKHeX.Core; + +/// +/// Logic to calculate max counts denoted by the ranch level in My Pokemon Ranch +/// +public static class RanchLevel +{ + public static int GetLevel(byte levelIndex) => levelIndex + 1; + + public static int GetMaxMiis(byte levelIndex) => levelIndex switch + { + >= 11 => 20, + >= 08 => 15, + >= 04 => 10, + _ => 5, + }; + + public static int GetMaxToys(byte levelIndex) => levelIndex switch + { + >= 25 => 6, + >= 20 => 5, + >= 15 => 4, + >= 11 => 3, + >= 08 => 2, + _ => 1, + }; + + public static int GetSlotCount(byte levelIndex) => levelIndex switch + { + 00 => 020, + 01 => 025, + 02 => 030, + 03 => 040, + 04 => 050, + 05 => 060, + 06 => 080, + + 07 => 100, + 08 => 150, + 09 => 200, + 10 => 250, + 11 => 300, + 12 => 350, + + 13 => 400, + 14 => 500, + 15 => 600, + 16 => 700, + 17 => 800, + 18 => 900, + 19 => 1000, + + 20 => 1000, + 21 => 1000, + 22 => 1000, + 23 => 1000, + 24 => 1000, + + _ => 1500, + }; +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs new file mode 100644 index 000000000..b4ae3091a --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchMii.cs @@ -0,0 +1,22 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public sealed class RanchMii +{ + public const int SIZE = 0x28; + public readonly byte[] Data; + + public RanchMii(byte[] data) => Data = data; + + public uint MiiId { get => ReadUInt32BigEndian(Data); set => WriteUInt32BigEndian(Data, value); } + public uint SystemId { get => ReadUInt32BigEndian(Data.AsSpan(0x04)); set => WriteUInt32BigEndian(Data.AsSpan(0x04), value); } + public Span Name_Trash => Data.AsSpan(0x10, 0x18); + + public string MiiName + { + get => StringConverter4GC.GetStringUnicode(Name_Trash); + set => StringConverter4GC.SetStringUnicode(value.AsSpan(), Name_Trash, value.Length, StringConverterOption.None); + } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchOwnershipType.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchOwnershipType.cs new file mode 100644 index 000000000..28d555832 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchOwnershipType.cs @@ -0,0 +1,16 @@ +namespace PKHeX.Core; + +public enum RanchOwnershipType : byte +{ + None = 0, + Trainer = 1, + Hayley = 4, + Hayley_Traded = 5, +} + +// this might actually be an index to which Mii trainer owns the pokemon +public enum RanchOwnershipStatus : ushort +{ + None = 0, + Traded = 2, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchToy.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchToy.cs new file mode 100644 index 000000000..31b87d2f2 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchToy.cs @@ -0,0 +1,16 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public sealed class RanchToy +{ + public const int SIZE = 0x08; + public readonly byte[] Data; + + public RanchToy(byte[] ranchToyData) => Data = ranchToyData; + + public RanchToyType ToyType { get => (RanchToyType)Data[0]; set => Data[0] = (byte)value; } + // 1,2,3 alignment + public uint ToyMetadata { get => ReadUInt32BigEndian(Data.AsSpan(4)); set => WriteUInt32BigEndian(Data.AsSpan(4), value); } +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchToyType.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchToyType.cs new file mode 100644 index 000000000..8ec52272f --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchToyType.cs @@ -0,0 +1,34 @@ +namespace PKHeX.Core; + +/// +/// Toys used in My Pokemon Ranch save files. +/// +public enum RanchToyType : byte +{ + None = 0, + Poke_Balloons = 1, + Slippery_Peel = 2, + Poke_Bell = 3, + BounceBack_Ball = 4, + Poke_Rocket = 5, + Poke_Cushion = 6, + Parade_Drum = 7, + Bonfire = 8, + Leader_Flag = 9, + Fountain = 10, + Ice_Block = 11, + Poke_Microphone = 12, + Burst_Ball = 13, + Poke_Palette = 14, + Poke_Pendulum = 15, + Pitfall = 16, + Training_Bag = 17, + Stinky_Ball = 18, + Snowman = 19, + Round_Rock = 20, + Spin_Ride = 21, + Sprint_Stand = 22, + Attractor = 23, + Challenger = 24, + Toy_Box = 25, +} diff --git a/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs new file mode 100644 index 000000000..0eb3f8497 --- /dev/null +++ b/PKHeX.Core/Saves/Substructures/Gen4/Ranch/RanchTrainerMii.cs @@ -0,0 +1,33 @@ +using System; +using static System.Buffers.Binary.BinaryPrimitives; + +namespace PKHeX.Core; + +public sealed class RanchTrainerMii +{ + public const int SIZE = 0x2C; + public readonly byte[] Data; + + public RanchTrainerMii(byte[] data) => Data = data; + + public uint MiiId { get => ReadUInt32BigEndian(Data.AsSpan(0x00)); set => WriteUInt32BigEndian(Data.AsSpan(0x00), value); } + public uint SystemId { get => ReadUInt32BigEndian(Data.AsSpan(0x04)); set => WriteUInt32BigEndian(Data.AsSpan(0x04), value); } + + public ushort TrainerId { get => ReadUInt16LittleEndian(Data.AsSpan(0x0C)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0C), value); } + public ushort SecretId { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value); } + + private Span Trainer_Trash => Data.AsSpan(0x10, 0x10); + + // 0x20-23: ?? + // 0x24: ?? + // 0x25: ?? + // 0x26-27: ?? + // 0x28-29: ?? + // 0x2A-2B: ?? + + public string TrainerName + { + get => StringConverter4.GetString(Trainer_Trash); + set => StringConverter4.SetString(Trainer_Trash, value.AsSpan(), 7); + } +} diff --git a/README.md b/README.md index d4a969695..7c7ce029f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Pokémon core series save editor, programmed in [C#](https://en.wikipedia.org/wi Supports the following files: * Save files ("main", \*.sav, \*.dsv, \*.dat, \*.gci, \*.bin) * GameCube Memory Card files (\*.raw, \*.bin) containing GC Pokémon savegames. -* Individual Pokémon entity files (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4) +* Individual Pokémon entity files (.pk\*, \*.ck3, \*.xk3, \*.pb7, \*.sk2, \*.bk4, \*.rk4) * Mystery Gift files (\*.pgt, \*.pcd, \*.pgf, .wc\*) including conversion to .pk\* * Importing GO Park entities (\*.gp1) including conversion to .pb7 * Importing teams from Decrypted 3DS Battle Videos