using System; using static System.Buffers.Binary.BinaryPrimitives; using static PKHeX.Core.GameVersion; using static PKHeX.Core.Locations; namespace PKHeX.Core; /// Generation 8 format. 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.None; 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 Nickname_Trash => _coreData.Nickname_Trash; public override Span OT_Trash => _coreData.OT_Trash; public override Span 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; } public override int MarkingCount => _coreData.MarkingCount; public override int GetMarking(int index) => _coreData.GetMarking(index); public override void SetMarking(int index, int value) => _coreData.SetMarking(index, 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); }