PKHeX/PKHeX.Core/PKM/HOME/PKH.cs
Kurt 5bcccc6d92
HOME 2.0.0: Handle conversion behavior & restrictions (#3506)
* Revises legality checks to account for traveling between the three game islands (PLA/BDSP/SWSH)
* Adds conversion mechanisms between the three formats, as well as flexible conversion options to backfill missing data (thanks GameFreak/ILCA for opting for lossy conversion instead of updating the games).
* Adds API abstractions for HOME data storage format (EKH/PKH format 1, aka EH1/PH1).
* Revises some APIs for better usage:
  - `PKM` now exposes a `Context` to indicate the isolation context for legality purposes.
  - Some method signatures have changed to accept `Context` or `GameVersion` instead of a vague `int` for Generation.
  - Evolution History is now tracked in the Legality parse for specific contexts, rather than only per generation.
2022-05-30 21:43:52 -07:00

319 lines
19 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.GameVersion;
using static PKHeX.Core.Locations;
namespace PKHeX.Core;
/// <summary> Generation 8 <see cref="PKM"/> format. </summary>
public class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories, IRibbonSetAffixed, IContestStats, IContestStatsMutable, IScaledSize
{
public readonly GameDataCore _coreData;
public GameDataPB7? DataPB7 { get; private set; }
public GameDataPK8? DataPK8 { get; private set; }
public GameDataPA8? DataPA8 { get; private set; }
public GameDataPB8? DataPB8 { get; private set; }
public override EntityContext Context => EntityContext.Invalid;
public PKH(byte[] data) : base(DecryptHome(data))
{
_coreData = new GameDataCore(Data, 0x10);
ReadGameData(Data, CoreDataSize, GameDataSize);
}
private void ReadGameData(byte[] data, int coreSize, int gameSize)
{
var baseOfs = 0x14 + coreSize;
var offset = 0;
while (offset < gameSize)
{
var fmt = (HomeGameDataFormat)data[baseOfs + offset];
switch (fmt)
{
case HomeGameDataFormat.PB7: DataPB7 = new GameDataPB7(data, baseOfs + offset); break;
case HomeGameDataFormat.PK8: DataPK8 = new GameDataPK8(data, baseOfs + offset); break;
case HomeGameDataFormat.PA8: DataPA8 = new GameDataPA8(data, baseOfs + offset); break;
case HomeGameDataFormat.PB8: DataPB8 = new GameDataPB8(data, baseOfs + offset); break;
default: throw new ArgumentException($"Unknown GameData {fmt}");
}
var len = ReadUInt16LittleEndian(data.AsSpan(baseOfs + offset + 1));
offset += 3 + len;
}
}
private static byte[] DecryptHome(byte[] data)
{
HomeCrypto.DecryptIfEncrypted(ref data);
//Array.Resize(ref data, HomeCrypto.SIZE_1STORED);
return data;
}
public ushort DataVersion { get => ReadUInt16LittleEndian(Data.AsSpan(0x00)); set => WriteUInt16LittleEndian(Data.AsSpan(0x00), value); }
public ulong EncryptionSeed { get => ReadUInt64LittleEndian(Data.AsSpan(0x02)); set => WriteUInt64LittleEndian(Data.AsSpan(0x02), value); }
public uint Checksum { get => ReadUInt32LittleEndian(Data.AsSpan(0x0A)); set => WriteUInt32LittleEndian(Data.AsSpan(0x0A), value); }
public ushort EncodedDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x0E)); set => WriteUInt16LittleEndian(Data.AsSpan(0x0E), value); }
public ushort CoreDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x10)); set => WriteUInt16LittleEndian(Data.AsSpan(0x10), value); }
public ushort GameDataSize { get => ReadUInt16LittleEndian(Data.AsSpan(0x12 + CoreDataSize)); set => WriteUInt16LittleEndian(Data.AsSpan(0x12 + CoreDataSize), value); }
public override Span<byte> Nickname_Trash => _coreData.Nickname_Trash;
public override Span<byte> OT_Trash => _coreData.OT_Trash;
public override Span<byte> HT_Trash => _coreData.HT_Trash;
#region Core
public ulong Tracker { get => _coreData.Tracker; set => _coreData.Tracker = value; }
public override uint EncryptionConstant { get => _coreData.EncryptionConstant; set => _coreData.EncryptionConstant = value; }
public bool IsBadEgg { get => _coreData.IsBadEgg; set => _coreData.IsBadEgg = value; }
public override int Species { get => _coreData.Species; set => _coreData.Species = value; }
public override int TID { get => _coreData.TID; set => _coreData.TID = value; }
public override int SID { get => _coreData.SID; set => _coreData.SID = value; }
public override uint EXP { get => _coreData.EXP; set => _coreData.EXP = value; }
public override int Ability { get => _coreData.Ability; set => _coreData.Ability = value; }
public override int AbilityNumber { get => _coreData.AbilityNumber; set => _coreData.AbilityNumber = value; }
public bool Favorite { get => _coreData.Favorite; set => _coreData.Favorite = value; }
public override int MarkValue { get => _coreData.MarkValue; set => _coreData.MarkValue = value; }
public override uint PID { get => _coreData.PID; set => _coreData.PID = value; }
public override int Nature { get => _coreData.Nature; set => _coreData.Nature = value; }
public override int StatNature { get => _coreData.StatNature; set => _coreData.StatNature = value; }
public override bool FatefulEncounter { get => _coreData.FatefulEncounter; set => _coreData.FatefulEncounter = value; }
public override int Gender { get => _coreData.Gender; set => _coreData.Gender = value; }
public override int Form { get => _coreData.Form; set => _coreData.Form = value; }
public override int EV_HP { get => _coreData.EV_HP; set => _coreData.EV_HP = value; }
public override int EV_ATK { get => _coreData.EV_ATK; set => _coreData.EV_ATK = value; }
public override int EV_DEF { get => _coreData.EV_DEF; set => _coreData.EV_DEF = value; }
public override int EV_SPE { get => _coreData.EV_SPE; set => _coreData.EV_SPE = value; }
public override int EV_SPA { get => _coreData.EV_SPA; set => _coreData.EV_SPA = value; }
public override int EV_SPD { get => _coreData.EV_SPD; set => _coreData.EV_SPD = value; }
public byte CNT_Cool { get => _coreData.CNT_Cool; set => _coreData.CNT_Cool = value; }
public byte CNT_Beauty { get => _coreData.CNT_Beauty; set => _coreData.CNT_Beauty = value; }
public byte CNT_Cute { get => _coreData.CNT_Cute; set => _coreData.CNT_Cute = value; }
public byte CNT_Smart { get => _coreData.CNT_Smart; set => _coreData.CNT_Smart = value; }
public byte CNT_Tough { get => _coreData.CNT_Tough; set => _coreData.CNT_Tough = value; }
public byte CNT_Sheen { get => _coreData.CNT_Sheen; set => _coreData.CNT_Sheen = value; }
public override int PKRS_Days { get => _coreData.PKRS_Days; set => _coreData.PKRS_Days = value; }
public override int PKRS_Strain { get => _coreData.PKRS_Strain; set => _coreData.PKRS_Strain = value; }
public byte HeightScalar { get => _coreData.HeightScalar; set => _coreData.HeightScalar = value; }
public byte WeightScalar { get => _coreData.WeightScalar; set => _coreData.WeightScalar = value; }
public override int Stat_HPCurrent { get => _coreData.Stat_HPCurrent; set => _coreData.Stat_HPCurrent = value; }
public override int IV_HP { get => _coreData.IV_HP; set => _coreData.IV_HP = value; }
public override int IV_ATK { get => _coreData.IV_ATK; set => _coreData.IV_ATK = value; }
public override int IV_DEF { get => _coreData.IV_DEF; set => _coreData.IV_DEF = value; }
public override int IV_SPE { get => _coreData.IV_SPE; set => _coreData.IV_SPE = value; }
public override int IV_SPA { get => _coreData.IV_SPA; set => _coreData.IV_SPA = value; }
public override int IV_SPD { get => _coreData.IV_SPD; set => _coreData.IV_SPD = value; }
public override bool IsEgg { get => _coreData.IsEgg; set => _coreData.IsEgg = value; }
public override bool IsNicknamed { get => _coreData.IsNicknamed; set => _coreData.IsNicknamed = value; }
public override int Status_Condition { get => _coreData.Status_Condition; set => _coreData.Status_Condition = value; }
public override int HT_Gender { get => _coreData.HT_Gender; set => _coreData.HT_Gender = value; }
public byte HT_Language { get => _coreData.HT_Language; set => _coreData.HT_Language = value; }
public override int CurrentHandler { get => _coreData.CurrentHandler; set => _coreData.CurrentHandler = value; }
public int HT_TrainerID { get => _coreData.HT_TrainerID; set => _coreData.HT_TrainerID = value; }
public override int HT_Friendship { get => _coreData.HT_Friendship; set => _coreData.HT_Friendship = value; }
public byte HT_Intensity { get => _coreData.HT_Intensity; set => _coreData.HT_Intensity = value; }
public byte HT_Memory { get => _coreData.HT_Memory; set => _coreData.HT_Memory = value; }
public byte HT_Feeling { get => _coreData.HT_Feeling; set => _coreData.HT_Feeling = value; }
public ushort HT_TextVar { get => _coreData.HT_TextVar; set => _coreData.HT_TextVar = value; }
public override int Version { get => _coreData.Version; set => _coreData.Version = value; }
public byte BattleVersion { get => _coreData.BattleVersion; set => _coreData.BattleVersion = value; }
public override int Language { get => _coreData.Language; set => _coreData.Language = value; }
public uint FormArgument { get => _coreData.FormArgument; set => _coreData.FormArgument = value; }
public byte FormArgumentRemain { get => _coreData.FormArgumentRemain; set => _coreData.FormArgumentRemain = value; }
public byte FormArgumentElapsed { get => _coreData.FormArgumentElapsed; set => _coreData.FormArgumentElapsed = value; }
public byte FormArgumentMaximum { get => _coreData.FormArgumentMaximum; set => _coreData.FormArgumentMaximum = value; }
public sbyte AffixedRibbon { get => _coreData.AffixedRibbon; set => _coreData.AffixedRibbon = value; }
public override int OT_Friendship { get => _coreData.OT_Friendship; set => _coreData.OT_Friendship = value; }
public byte OT_Intensity { get => _coreData.OT_Intensity; set => _coreData.OT_Intensity = value; }
public byte OT_Memory { get => _coreData.OT_Memory; set => _coreData.OT_Memory = value; }
public ushort OT_TextVar { get => _coreData.OT_TextVar; set => _coreData.OT_TextVar = value; }
public byte OT_Feeling { get => _coreData.OT_Feeling; set => _coreData.OT_Feeling = value; }
public override int Egg_Year { get => _coreData.Egg_Year; set => _coreData.Egg_Year = value; }
public override int Egg_Month { get => _coreData.Egg_Month; set => _coreData.Egg_Month = value; }
public override int Egg_Day { get => _coreData.Egg_Day; set => _coreData.Egg_Day = value; }
public override int Met_Year { get => _coreData.Met_Year; set => _coreData.Met_Year = value; }
public override int Met_Month { get => _coreData.Met_Month; set => _coreData.Met_Month = value; }
public override int Met_Day { get => _coreData.Met_Day; set => _coreData.Met_Day = value; }
public override int Met_Level { get => _coreData.Met_Level; set => _coreData.Met_Level = value; }
public override int OT_Gender { get => _coreData.OT_Gender; set => _coreData.OT_Gender = value; }
public byte HyperTrainFlags { get => _coreData.HyperTrainFlags; set => _coreData.HyperTrainFlags = value; }
public bool HT_HP { get => _coreData.HT_HP; set => _coreData.HT_HP = value; }
public bool HT_ATK { get => _coreData.HT_ATK; set => _coreData.HT_ATK = value; }
public bool HT_DEF { get => _coreData.HT_DEF; set => _coreData.HT_DEF = value; }
public bool HT_SPA { get => _coreData.HT_SPA; set => _coreData.HT_SPA = value; }
public bool HT_SPD { get => _coreData.HT_SPD; set => _coreData.HT_SPD = value; }
public bool HT_SPE { get => _coreData.HT_SPE; set => _coreData.HT_SPE = value; }
public override int HeldItem { get => _coreData.HeldItem; set => _coreData.HeldItem = value; }
public override string Nickname { get => _coreData.Nickname; set => _coreData.Nickname = value; }
public override string OT_Name { get => _coreData.OT_Name; set => _coreData.OT_Name = value; }
public override string HT_Name { get => _coreData.HT_Name; set => _coreData.HT_Name = value; }
#endregion
#region Calculated
public override int CurrentFriendship { get => CurrentHandler == 0 ? OT_Friendship : HT_Friendship; set { if (CurrentHandler == 0) OT_Friendship = value; else HT_Friendship = value; } }
public override int PSV => (int)((PID >> 16 ^ (PID & 0xFFFF)) >> 4);
public override int TSV => (TID ^ SID) >> 4;
public override int Characteristic
{
get
{
int pm6 = (int)(EncryptionConstant % 6);
int maxIV = MaximumIV;
int pm6stat = 0;
for (int i = 0; i < 6; i++)
{
pm6stat = (pm6 + i) % 6;
if (GetIV(pm6stat) == maxIV)
break;
}
return (pm6stat * 5) + (maxIV % 5);
}
}
#endregion
#region Children
public override int Move1 { get => LatestGameData.Move1 ; set => LatestGameData.Move1 = value; }
public override int Move2 { get => LatestGameData.Move2 ; set => LatestGameData.Move2 = value; }
public override int Move3 { get => LatestGameData.Move3 ; set => LatestGameData.Move3 = value; }
public override int Move4 { get => LatestGameData.Move4 ; set => LatestGameData.Move4 = value; }
public override int Move1_PP { get => LatestGameData.Move1_PP ; set => LatestGameData.Move1_PP = value; }
public override int Move2_PP { get => LatestGameData.Move2_PP ; set => LatestGameData.Move2_PP = value; }
public override int Move3_PP { get => LatestGameData.Move3_PP ; set => LatestGameData.Move3_PP = value; }
public override int Move4_PP { get => LatestGameData.Move4_PP ; set => LatestGameData.Move4_PP = value; }
public override int Move1_PPUps { get => LatestGameData.Move1_PPUps; set => LatestGameData.Move1_PPUps = value; }
public override int Move2_PPUps { get => LatestGameData.Move2_PPUps; set => LatestGameData.Move2_PPUps = value; }
public override int Move3_PPUps { get => LatestGameData.Move3_PPUps; set => LatestGameData.Move3_PPUps = value; }
public override int Move4_PPUps { get => LatestGameData.Move4_PPUps; set => LatestGameData.Move4_PPUps = value; }
public override int Ball { get => LatestGameData.Ball; set => LatestGameData.Ball = value; }
public override int Met_Location { get => LatestGameData.Met_Location; set => LatestGameData.Met_Location = value; }
public override int Egg_Location { get => LatestGameData.Egg_Location; set => LatestGameData.Egg_Location = value; }
#endregion
public override int Stat_Level { get => CurrentLevel; set => CurrentLevel = value; }
public override int Stat_HPMax { get => 0; set { } }
public override int Stat_ATK { get => 0; set { } }
public override int Stat_DEF { get => 0; set { } }
public override int Stat_SPE { get => 0; set { } }
public override int Stat_SPA { get => 0; set { } }
public override int Stat_SPD { get => 0; set { } }
#region Maximums
public override int MaxIV => 31;
public override int MaxEV => 252;
public override int OTLength => 12;
public override int NickLength => 12;
public override int MaxMoveID => Legal.MaxMoveID_8a;
public override int MaxSpeciesID => Legal.MaxSpeciesID_8a;
public override int MaxAbilityID => Legal.MaxAbilityID_8a;
public override int MaxItemID => Legal.MaxItemID_8a;
public override int MaxBallID => Legal.MaxBallID_8a;
public override int MaxGameID => Legal.MaxGameID_8a;
#endregion
public override int SIZE_PARTY => HomeCrypto.SIZE_1STORED;
public override int SIZE_STORED => HomeCrypto.SIZE_1STORED;
public override bool Valid { get => true; set { } }
public override PersonalInfo PersonalInfo => LatestGameData.GetPersonalInfo(Species, Form);
public override void RefreshChecksum() => Checksum = 0;
public override bool ChecksumValid => true;
protected override byte[] Encrypt()
{
var result = Rebuild();
return HomeCrypto.Encrypt(result);
}
private const int GameDataStart = HomeCrypto.SIZE_1HEADER + (2 + HomeCrypto.SIZE_1CORE) + 2;
public byte[] Rebuild()
{
var length = WriteLength;
// Handle PKCS7 manually
var remainder = length & 0xF;
if (remainder != 0) // pad to nearest 0x10, fill remainder bytes with value.
remainder = 0x10 - remainder;
var result = new byte[length + remainder];
result.AsSpan()[^remainder..].Fill((byte)remainder);
// Header and Core are already in the current byte array.
// Write each part, starting with header and core.
int ctr = GameDataStart;
Data.AsSpan(0, ctr).CopyTo(result);
if (DataPK8 is { } pk8) ctr += pk8.CopyTo(result.AsSpan(ctr));
if (DataPB7 is { } pb7) ctr += pb7.CopyTo(result.AsSpan(ctr));
if (DataPA8 is { } pa8) ctr += pa8.CopyTo(result.AsSpan(ctr));
if (DataPB8 is { } pb8) ctr += pb8.CopyTo(result.AsSpan(ctr));
// Update metadata to ensure we're a valid object.
DataVersion = HomeCrypto.Version1;
EncodedDataSize = (ushort)(result.Length - HomeCrypto.SIZE_1HEADER);
CoreDataSize = HomeCrypto.SIZE_1CORE;
GameDataSize = (ushort)(ctr - GameDataStart);
return result;
}
private int WriteLength
{
get
{
var length = GameDataStart;
if (DataPK8 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PK8;
if (DataPB7 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PB7;
if (DataPA8 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PA8;
if (DataPB8 is not null) length += 3 + HomeCrypto.SIZE_1GAME_PB8;
return length;
}
}
public override PKM Clone() => new PKH((byte[])Data.Clone())
{
DataPK8 = DataPK8?.Clone(),
DataPA8 = DataPA8?.Clone(),
DataPB8 = DataPB8?.Clone(),
DataPB7 = DataPB7?.Clone(),
};
public IGameDataSide LatestGameData => OriginalGameData() ?? GetFallbackGameData();
private IGameDataSide GetFallbackGameData() => Version switch
{
(int)GP or (int)GE => DataPB7 ??= new(),
(int)BD or (int)SP => DataPB8 ??= new(),
(int)PLA => DataPA8 ??= new(),
_ => DataPK8 ??= new(),
};
private IGameDataSide? OriginalGameData() => Version switch
{
(int)GP or (int)GE => DataPB7,
(int)BD or (int)SP => DataPB8,
(int)PLA => DataPA8,
(int)SW or (int)SH when DataPK8 is { Met_Location: HOME_SWLA } => DataPA8,
(int)SW or (int)SH when DataPK8 is { Met_Location: HOME_SWBD or HOME_SHSP } => DataPB8,
(int)SW or (int)SH => DataPK8,
_ => DataPK8,
};
public PKM? ConvertToPB7() => DataPB7 is { } x ? x.ConvertToPB7(this) : (DataPB7 ??= GameDataPB7.TryCreate(this))?.ConvertToPB7(this);
public PK8? ConvertToPK8() => DataPK8 is { } x ? x.ConvertToPK8(this) : (DataPK8 ??= GameDataPK8.TryCreate(this))?.ConvertToPK8(this);
public PB8? ConvertToPB8() => DataPB8 is { } x ? x.ConvertToPB8(this) : (DataPB8 ??= GameDataPB8.TryCreate(this))?.ConvertToPB8(this);
public PA8? ConvertToPA8() => DataPA8 is { } x ? x.ConvertToPA8(this) : (DataPA8 ??= GameDataPA8.TryCreate(this))?.ConvertToPA8(this);
public void CopyTo(PKM pk) => _coreData.CopyTo(pk);
}