Expand My Pokémon Ranch Support (#3595)

* Add RanchMii for SAV4Ranch

* Add RanchToy

* Add RanchTrainerMii

* Add RanchLevel

* Add RanchToy class; make existing RanchToy RanchToyType

* Add RanchToy and RanchLevel to SAV4Ranch

* Remove incorrect MaxPkmCount entry from RanchLevel

* Move code to remove PtHGSS data to a function in G$PKM

* Add RK4 for My Pokemon Ranch Pokemon

* Add RanchPkOwnershipType

* SAV4Ranch updates

* Fix PK4/RK4 conversion logic to stop breaking nicknames/OTs

* Fix EntityDetection.IsPresent() check tripping on the data end marker for SAV4Ranch

* Add .rk4 to README translations

* Minor tweaks

Fix RK4 TID/SID endianness/order, split Ownership enum into two enums
Condense mii classes to get/set properties
Make RanchLevel a static class for logic
Remove ClearFF for TrainerMii -- the FFFF is the string terminator char for gen4
Make Toy byte enum, with unused alignment bytes

Co-authored-by: Kurt <kaphotics@gmail.com>
This commit is contained in:
Zazsona 2022-10-02 21:14:42 +01:00 committed by GitHub
parent 72410e1619
commit b804557627
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 755 additions and 55 deletions

View file

@ -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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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),
};

View file

@ -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<BK4>();
// 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

345
PKHeX.Core/PKM/RK4.cs Normal file
View file

@ -0,0 +1,345 @@
using System;
using System.Collections.Generic;
using static System.Buffers.Binary.BinaryPrimitives;
namespace PKHeX.Core;
/// <summary> Generation 4 <see cref="PKM"/> format for My Pokémon Ranch. </summary>
/// <remarks>
/// MPR-specific values are stored in Big Endian format rather than Little Endian. Beware.
/// </remarks>
public sealed class RK4 : G4PKM
{
private static readonly ushort[] Unused =
{
0x42, 0x43, 0x5E, 0x63, 0x64, 0x65, 0x66, 0x67, 0x87,
};
public override IReadOnlyList<ushort> 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<byte> 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;
}
}

View file

@ -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<T>() where T : G4PKM, new()
{
var pk = new T

View file

@ -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),
};

View file

@ -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<byte> data) => data[0] != 0; // Species non-zero
public static bool IsPresentGC(ReadOnlySpan<byte> data) => ReadUInt16BigEndian(data) != 0; // Species non-zero
public static bool IsPresentGBA(ReadOnlySpan<byte> data) => (data[0x13] & 0xFB) == 2; // ignore egg flag, must be FlagHasSpecies.
public static bool IsPresentSAV4Ranch(ReadOnlySpan<byte> data) => ReadUInt32LittleEndian(data) != 0 && ReadUInt32BigEndian(data) != 0x28; // Species non-zero, ignore file end marker
public static bool IsPresent(ReadOnlySpan<byte> data)
{

View file

@ -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)

View file

@ -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<byte> 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,

View file

@ -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;

View file

@ -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);

View file

@ -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;
/// </summary>
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<ushort> 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<byte> 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<byte> 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<byte> 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<byte> data) => StringConverter4GC.GetStringUnicode(data);
public override int SetString(Span<byte> destBuffer, ReadOnlySpan<char> value, int maxLength, StringConverterOption option)

View file

@ -0,0 +1,61 @@
namespace PKHeX.Core;
/// <summary>
/// Logic to calculate max counts denoted by the ranch level in My Pokemon Ranch
/// </summary>
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,
};
}

View file

@ -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<byte> 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);
}
}

View file

@ -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,
}

View file

@ -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); }
}

View file

@ -0,0 +1,34 @@
namespace PKHeX.Core;
/// <summary>
/// Toys used in My Pokemon Ranch save files.
/// </summary>
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,
}

View file

@ -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<byte> 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);
}
}

View file

@ -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