PKHeX/PKHeX.Core/PKM/HOME/PKH.cs
Kurt d47bb1d297
Update .NET Runtime to .NET 8.0 (#4082)
With the new version of Visual Studio bringing C# 12, we can revise our logic for better readability as well as use new methods/APIs introduced in the .NET 8.0 BCL.
2023-12-03 20:13:20 -08:00

418 lines
22 KiB
C#

using System;
using static System.Buffers.Binary.BinaryPrimitives;
using static PKHeX.Core.GameVersion;
namespace PKHeX.Core;
/// <summary> Pokémon HOME <see cref="PKM"/> format. </summary>
public sealed class PKH : PKM, IHandlerLanguage, IFormArgument, IHomeTrack, IBattleVersion, ITrainerMemories, IRibbonSetAffixed, IContestStats, IScaledSize, IRibbonSetRibbons, IRibbonSetMarks
{
public readonly GameDataCore Core;
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 GameDataPK9? DataPK9 { get; private set; }
public override EntityContext Context => EntityContext.None;
public PKH(byte[] data) : base(DecryptHome(data))
{
var mem = Data.AsMemory(HomeCrypto.SIZE_1HEADER + 2);
var core = mem[..CoreDataSize];
var side = mem.Slice(core.Length + 2, GameDataSize);
Core = new GameDataCore(core);
ReadGameData1(side);
}
public PKH() : base(HomeCrypto.SIZE_STORED)
{
CoreDataSize = HomeCrypto.SIZE_CORE;
var mem = Data.AsMemory(HomeCrypto.SIZE_1HEADER + 2);
var core = mem[..CoreDataSize];
Core = new GameDataCore(core) { AffixedRibbon = -1 };
}
private void ReadGameData1(Memory<byte> data)
{
// Can potentially have no side-game data (GO imports)
while (data.Length != 0)
{
var span = data.Span;
var format = (HomeGameDataFormat)span[0];
var length = ReadUInt16LittleEndian(span[1..]);
data = data[HomeOptional1.HeaderSize..];
var chunk = data[..length];
data = data[chunk.Length..];
_ = ReadGameData1(chunk, format);
}
}
private IGameDataSide ReadGameData1(Memory<byte> chunk, HomeGameDataFormat format) => format switch
{
HomeGameDataFormat.PB7 => DataPB7 = new GameDataPB7(chunk),
HomeGameDataFormat.PK8 => DataPK8 = new GameDataPK8(chunk),
HomeGameDataFormat.PA8 => DataPA8 = new GameDataPA8(chunk),
HomeGameDataFormat.PB8 => DataPB8 = new GameDataPB8(chunk),
HomeGameDataFormat.PK9 => DataPK9 = new GameDataPK9(chunk),
_ => throw new ArgumentException($"Unknown {nameof(HomeGameDataFormat)} {format}"),
};
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); }
private const int GameDataStart = HomeCrypto.SIZE_1HEADER + 2 + HomeCrypto.SIZE_CORE + 2;
public override Span<byte> Nickname_Trash => Core.Nickname_Trash;
public override Span<byte> OT_Trash => Core.OT_Trash;
public override Span<byte> HT_Trash => Core.HT_Trash;
public override bool IsUntraded => ReadUInt16LittleEndian(HT_Trash) == 0; // immediately terminated HT_Name data (\0)
#region Core
public ulong Tracker { get => Core.Tracker; set => Core.Tracker = value; }
public override uint EncryptionConstant { get => Core.EncryptionConstant; set => Core.EncryptionConstant = value; }
public bool IsBadEgg { get => Core.IsBadEgg; set => Core.IsBadEgg = value; }
public override ushort Species { get => Core.Species; set => Core.Species = value; }
public override uint ID32 { get => Core.ID32; set => Core.ID32 = value; }
public override ushort TID16 { get => Core.TID16; set => Core.TID16 = value; }
public override ushort SID16 { get => Core.SID16; set => Core.SID16 = value; }
public override uint EXP { get => Core.EXP; set => Core.EXP = value; }
public bool Favorite { get => Core.IsFavorite; set => Core.IsFavorite = value; }
public override int MarkValue { get => Core.MarkValue; set => Core.MarkValue = value; }
public override uint PID { get => Core.PID; set => Core.PID = value; }
public override int Nature { get => Core.Nature; set => Core.Nature = value; }
public override int StatNature { get => Core.StatNature; set => Core.StatNature = value; }
public override bool FatefulEncounter { get => Core.FatefulEncounter; set => Core.FatefulEncounter = value; }
public override int Gender { get => Core.Gender; set => Core.Gender = value; }
public override byte Form { get => Core.Form; set => Core.Form = value; }
public override int EV_HP { get => Core.EV_HP; set => Core.EV_HP = value; }
public override int EV_ATK { get => Core.EV_ATK; set => Core.EV_ATK = value; }
public override int EV_DEF { get => Core.EV_DEF; set => Core.EV_DEF = value; }
public override int EV_SPE { get => Core.EV_SPE; set => Core.EV_SPE = value; }
public override int EV_SPA { get => Core.EV_SPA; set => Core.EV_SPA = value; }
public override int EV_SPD { get => Core.EV_SPD; set => Core.EV_SPD = value; }
public byte CNT_Cool { get => Core.CNT_Cool; set => Core.CNT_Cool = value; }
public byte CNT_Beauty { get => Core.CNT_Beauty; set => Core.CNT_Beauty = value; }
public byte CNT_Cute { get => Core.CNT_Cute; set => Core.CNT_Cute = value; }
public byte CNT_Smart { get => Core.CNT_Smart; set => Core.CNT_Smart = value; }
public byte CNT_Tough { get => Core.CNT_Tough; set => Core.CNT_Tough = value; }
public byte CNT_Sheen { get => Core.CNT_Sheen; set => Core.CNT_Sheen = value; }
public byte HeightScalar { get => Core.HeightScalar; set => Core.HeightScalar = value; }
public byte WeightScalar { get => Core.WeightScalar; set => Core.WeightScalar = value; }
public override int Stat_HPCurrent { get => Core.Stat_HPCurrent; set => Core.Stat_HPCurrent = value; }
public override int IV_HP { get => Core.IV_HP; set => Core.IV_HP = value; }
public override int IV_ATK { get => Core.IV_ATK; set => Core.IV_ATK = value; }
public override int IV_DEF { get => Core.IV_DEF; set => Core.IV_DEF = value; }
public override int IV_SPE { get => Core.IV_SPE; set => Core.IV_SPE = value; }
public override int IV_SPA { get => Core.IV_SPA; set => Core.IV_SPA = value; }
public override int IV_SPD { get => Core.IV_SPD; set => Core.IV_SPD = value; }
public override bool IsEgg { get => Core.IsEgg; set => Core.IsEgg = value; }
public override bool IsNicknamed { get => Core.IsNicknamed; set => Core.IsNicknamed = value; }
public override int Status_Condition { get => Core.Status_Condition; set => Core.Status_Condition = value; }
public override int HT_Gender { get => Core.HT_Gender; set => Core.HT_Gender = value; }
public byte HT_Language { get => Core.HT_Language; set => Core.HT_Language = value; }
public override int CurrentHandler { get => Core.CurrentHandler; set => Core.CurrentHandler = value; }
public int HT_TrainerID { get => Core.HT_TrainerID; set => Core.HT_TrainerID = value; }
public override int HT_Friendship { get => Core.HT_Friendship; set => Core.HT_Friendship = value; }
public byte HT_Intensity { get => Core.HT_Intensity; set => Core.HT_Intensity = value; }
public byte HT_Memory { get => Core.HT_Memory; set => Core.HT_Memory = value; }
public byte HT_Feeling { get => Core.HT_Feeling; set => Core.HT_Feeling = value; }
public ushort HT_TextVar { get => Core.HT_TextVar; set => Core.HT_TextVar = value; }
public override int Version { get => Core.Version; set => Core.Version = value; }
public byte BattleVersion { get => Core.BattleVersion; set => Core.BattleVersion = value; }
public override int Language { get => Core.Language; set => Core.Language = value; }
public uint FormArgument { get => Core.FormArgument; set => Core.FormArgument = value; }
public byte FormArgumentRemain { get => Core.FormArgumentRemain; set => Core.FormArgumentRemain = value; }
public byte FormArgumentElapsed { get => Core.FormArgumentElapsed; set => Core.FormArgumentElapsed = value; }
public byte FormArgumentMaximum { get => Core.FormArgumentMaximum; set => Core.FormArgumentMaximum = value; }
public sbyte AffixedRibbon { get => Core.AffixedRibbon; set => Core.AffixedRibbon = value; }
public override int OT_Friendship { get => Core.OT_Friendship; set => Core.OT_Friendship = value; }
public byte OT_Intensity { get => Core.OT_Intensity; set => Core.OT_Intensity = value; }
public byte OT_Memory { get => Core.OT_Memory; set => Core.OT_Memory = value; }
public ushort OT_TextVar { get => Core.OT_TextVar; set => Core.OT_TextVar = value; }
public byte OT_Feeling { get => Core.OT_Feeling; set => Core.OT_Feeling = value; }
public override int Egg_Year { get => Core.Egg_Year; set => Core.Egg_Year = value; }
public override int Egg_Month { get => Core.Egg_Month; set => Core.Egg_Month = value; }
public override int Egg_Day { get => Core.Egg_Day; set => Core.Egg_Day = value; }
public override int Met_Year { get => Core.Met_Year; set => Core.Met_Year = value; }
public override int Met_Month { get => Core.Met_Month; set => Core.Met_Month = value; }
public override int Met_Day { get => Core.Met_Day; set => Core.Met_Day = value; }
public override int Met_Level { get => Core.Met_Level; set => Core.Met_Level = value; }
public override int OT_Gender { get => Core.OT_Gender; set => Core.OT_Gender = value; }
public byte HyperTrainFlags { get => Core.HyperTrainFlags; set => Core.HyperTrainFlags = value; }
public bool HT_HP { get => Core.HT_HP; set => Core.HT_HP = value; }
public bool HT_ATK { get => Core.HT_ATK; set => Core.HT_ATK = value; }
public bool HT_DEF { get => Core.HT_DEF; set => Core.HT_DEF = value; }
public bool HT_SPA { get => Core.HT_SPA; set => Core.HT_SPA = value; }
public bool HT_SPD { get => Core.HT_SPD; set => Core.HT_SPD = value; }
public bool HT_SPE { get => Core.HT_SPE; set => Core.HT_SPE = value; }
public override int HeldItem { get => Core.HeldItem; set => Core.HeldItem = value; }
public override string Nickname { get => Core.Nickname; set => Core.Nickname = value; }
public override string OT_Name { get => Core.OT_Name; set => Core.OT_Name = value; }
public override string HT_Name { get => Core.HT_Name; set => Core.HT_Name = value; }
public override int MarkingCount => Core.MarkingCount;
public int RibbonCount => Core.RibbonCount;
public int MarkCount => Core.MarkCount;
public int RibbonMarkCount => Core.RibbonMarkCount;
public override int GetMarking(int index) => Core.GetMarking(index);
public override void SetMarking(int index, int value) => Core.SetMarking(index, value);
#endregion
// Used to be in Core, now we just don't bother.
public override int PKRS_Days { get => 0; set { } }
public override int PKRS_Strain { get => 0; set { } }
public override int Ability { get => 0; set { } }
public override int AbilityNumber { get => 0; set { } }
#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 uint PSV => ((PID >> 16) ^ (PID & 0xFFFF)) >> 4;
public override uint TSV => (uint)(TID16 ^ SID16) >> 4;
public override int Characteristic => EntityCharacteristic.GetCharacteristic(EncryptionConstant, [IV_HP, IV_ATK, IV_DEF, IV_SPE, IV_SPA, IV_SPD]);
#endregion
#region Children
public override ushort Move1 { get => LatestGameData.Move1 ; set => LatestGameData.Move1 = value; }
public override ushort Move2 { get => LatestGameData.Move2 ; set => LatestGameData.Move2 = value; }
public override ushort Move3 { get => LatestGameData.Move3 ; set => LatestGameData.Move3 = value; }
public override ushort 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 => EffortValues.Max252;
public override int MaxStringLengthOT => 12;
public override int MaxStringLengthNickname => 12;
public override ushort MaxMoveID => Legal.MaxMoveID_8a;
public override ushort 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_HOME;
#endregion
public override int SIZE_PARTY => HomeCrypto.SIZE_3STORED;
public override int SIZE_STORED => HomeCrypto.SIZE_3STORED;
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);
}
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];
var span = result.AsSpan(0, length);
result.AsSpan(length).Fill((byte)remainder);
// Header and Core are already in the current byte array.
// Write each part, starting with header and core.
int ctr = HomeCrypto.SIZE_1HEADER + 2;
ctr += Core.WriteTo(span[ctr..]);
var gameDataLengthSpan = span[ctr..];
int gameDataStart = (ctr += 2);
if (DataPK8 is { } pk8) ctr += pk8.WriteTo(span[ctr..]);
if (DataPB7 is { } pb7) ctr += pb7.WriteTo(span[ctr..]);
if (DataPA8 is { } pa8) ctr += pa8.WriteTo(span[ctr..]);
if (DataPB8 is { } pb8) ctr += pb8.WriteTo(span[ctr..]);
if (DataPK9 is { } pk9) ctr += pk9.WriteTo(span[ctr..]);
WriteUInt16LittleEndian(gameDataLengthSpan, GameDataSize = (ushort)(ctr - gameDataStart));
// Update metadata to ensure we're a valid object.
DataVersion = HomeCrypto.VersionLatest;
EncodedDataSize = (ushort)(result.Length - HomeCrypto.SIZE_1HEADER);
CoreDataSize = (ushort)Core.SerializedSize;
Data.AsSpan(0, HomeCrypto.SIZE_1HEADER + 2).CopyTo(span); // Copy updated header & CoreData length.
return result;
}
private int WriteLength
{
get
{
var length = GameDataStart;
if (DataPK8 is {} k8) length += k8.SerializedSize;
if (DataPB7 is {} b7) length += b7.SerializedSize;
if (DataPA8 is {} a8) length += a8.SerializedSize;
if (DataPB8 is {} b8) length += b8.SerializedSize;
if (DataPK9 is {} k9) length += k9.SerializedSize;
return length;
}
}
public override PKH Clone() => new((byte[])Data.Clone())
{
DataPK9 = DataPK9?.Clone(),
DataPK8 = DataPK8?.Clone(),
DataPA8 = DataPA8?.Clone(),
DataPB8 = DataPB8?.Clone(),
DataPB7 = DataPB7?.Clone(),
};
public IGameDataSide LatestGameData => OriginalGameData() ?? GetFallbackGameData();
private IGameDataSide GetFallbackGameData() => DataPB7
?? DataPK9
?? DataPB8
?? DataPA8
?? DataPK8 ?? CreateFallback();
private IGameDataSide CreateFallback() => Version switch
{
(int)GP or (int)GE => DataPB7 ??= new(),
(int)BD or (int)SP => DataPB8 ??= new(),
(int)PLA => DataPA8 ??= new(),
(int)SL or (int)VL => DataPK9 ??= new(),
_ => DataPK8 ??= new(),
};
private IGameDataSide? OriginalGameData() => Version switch
{
(int)GameVersion.GO when DataPB7 is not null => DataPB7,
(int)GP or (int)GE => DataPB7,
(int)BD or (int)SP => DataPB8,
(int)PLA => DataPA8,
(int)SL or (int)VL => DataPK9,
// SW/SH can be confused with others if we didn't seed with the original transfer data.
(int)SW or (int)SH => DataPK8 switch
{
{ Met_Location: LocationsHOME.SWLA } => DataPA8,
{ Met_Location: LocationsHOME.SWBD or LocationsHOME.SHSP } => DataPB8,
{ Met_Location: LocationsHOME.SWSL or LocationsHOME.SHVL } => DataPK9,
_ => DataPK8,
},
_ => DataPK8, // Gen7 and below.
};
public PB7? ConvertToPB7() => DataPB7 is { } x ? x.ConvertToPKM(this) : (DataPB7 ??= GameDataPB7.TryCreate(this))?.ConvertToPKM(this);
public PK8? ConvertToPK8() => DataPK8 is { } x ? x.ConvertToPKM(this) : (DataPK8 ??= GameDataPK8.TryCreate(this))?.ConvertToPKM(this);
public PB8? ConvertToPB8() => DataPB8 is { } x ? x.ConvertToPKM(this) : (DataPB8 ??= GameDataPB8.TryCreate(this))?.ConvertToPKM(this);
public PA8? ConvertToPA8() => DataPA8 is { } x ? x.ConvertToPKM(this) : (DataPA8 ??= GameDataPA8.TryCreate(this))?.ConvertToPKM(this);
public PK9? ConvertToPK9() => DataPK9 is { } x ? x.ConvertToPKM(this) : (DataPK9 ??= GameDataPK9.TryCreate(this))?.ConvertToPKM(this);
public void CopyTo(PKM pk) => Core.CopyTo(pk);
public static HomeGameDataFormat GetType(Type type)
{
if (type == typeof(PB7)) return HomeGameDataFormat.PB7;
if (type == typeof(PK8)) return HomeGameDataFormat.PK8;
if (type == typeof(PB8)) return HomeGameDataFormat.PB8;
if (type == typeof(PA8)) return HomeGameDataFormat.PA8;
if (type == typeof(PK9)) return HomeGameDataFormat.PK9;
return HomeGameDataFormat.None;
}
public PKM? ConvertToPKM(HomeGameDataFormat type) => type switch
{
HomeGameDataFormat.PB7 => ConvertToPB7(),
HomeGameDataFormat.PK8 => ConvertToPK8(),
HomeGameDataFormat.PB8 => ConvertToPB8(),
HomeGameDataFormat.PA8 => ConvertToPA8(),
HomeGameDataFormat.PK9 => ConvertToPK9(),
_ => null,
};
public static PKH ConvertFromPKM(PKM pk)
{
var blank = new PKH();
blank.CopyFrom(pk);
blank.EnsureScaleSizeExists();
if (blank.Species is (int)PKHeX.Core.Species.Arceus or (int)PKHeX.Core.Species.Silvally)
blank.Form = 0;
return blank;
}
public void CopyFrom(PKM pk)
{
Core.CopyFrom(pk);
if (pk is PB7 pb7) (DataPB7 ??= new GameDataPB7()).CopyFrom(pb7, this);
else if (pk is PK7 pk7) (DataPK8 ??= new GameDataPK8()).CopyFrom(pk7, this);
else if (pk is PK8 pk8) (DataPK8 ??= new GameDataPK8()).CopyFrom(pk8, this);
else if (pk is PB8 pb8) (DataPB8 ??= new GameDataPB8()).CopyFrom(pb8, this);
else if (pk is PA8 pa8) (DataPA8 ??= new GameDataPA8()).CopyFrom(pa8, this);
else if (pk is PK9 pk9) (DataPK9 ??= new GameDataPK9()).CopyFrom(pk9, this);
}
private IGameDataSide? FirstScaleData => DataPK9 ?? DataPA8 as IGameDataSide;
private void EnsureScaleSizeExists()
{
if (Core.RibbonMarkAlpha)
{
// Fix for PLA static encounter Alphas with 127 scale.
Core.HeightScalar = Core.WeightScalar = 255;
if (DataPA8 is { Scale: not 255 } pa8)
pa8.Scale = 255;
if (DataPK9 is { Scale: not 255 } pk9)
pk9.Scale = 255;
return;
}
if (GO_HOME || FirstScaleData is IScaledSize3)
return; // data exists for scale, keep values.
while (HeightScalar == 0 && WeightScalar == 0)
{
var rnd = Util.Rand;
HeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
WeightScalar = PokeSizeUtil.GetRandomScalar(rnd);
}
}
}