diff --git a/PKHeX.Core/Editing/PKM/QR/QRPK7.cs b/PKHeX.Core/Editing/PKM/QR/QRPK7.cs index d17760f35..fe41181fe 100644 --- a/PKHeX.Core/Editing/PKM/QR/QRPK7.cs +++ b/PKHeX.Core/Editing/PKM/QR/QRPK7.cs @@ -111,7 +111,7 @@ public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) Met_Level = Level, MetDate = DateTime.Now, }; - PKMConverter.SetConsoleRegionData3DS(pk); + RecentTrainerCache.SetConsoleRegionData3DS(pk, sav); pk.RefreshAbility(AbilityIndex >> 1); pk.ForcePartyData(); diff --git a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs index 96c96ca0c..7ce6c78ba 100644 --- a/PKHeX.Core/Editing/Showdown/ShowdownSet.cs +++ b/PKHeX.Core/Editing/Showdown/ShowdownSet.cs @@ -24,7 +24,7 @@ public sealed class ShowdownSet : IBattleTemplate public int Species { get; private set; } = -1; /// - public int Format { get; private set; } = PKMConverter.Format; + public int Format { get; private set; } = RecentTrainerCache.Format; /// public string Nickname { get; set; } = string.Empty; diff --git a/PKHeX.Core/Legality/BulkGenerator.cs b/PKHeX.Core/Legality/BulkGenerator.cs index 684686c71..bd69ebdcb 100644 --- a/PKHeX.Core/Legality/BulkGenerator.cs +++ b/PKHeX.Core/Legality/BulkGenerator.cs @@ -53,7 +53,7 @@ public static List GetLivingDex(this ITrainerInfo tr, IEnumerable spec if (f == null) return null; - var result = PKMConverter.ConvertToType(f, destType, out _); + var result = EntityConverter.ConvertToType(f, destType, out _); if (result == null) return null; diff --git a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs index 03497019b..409324c86 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterMisc/EncounterEgg.cs @@ -28,7 +28,7 @@ public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) { int gen = Generation; var version = Version; - var pk = PKMConverter.GetBlank(gen, version); + var pk = EntityBlank.GetBlank(gen, version); sav.ApplyTo(pk); diff --git a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs index 804b7fb39..44245869e 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterSlot/EncounterSlot.cs @@ -74,7 +74,7 @@ public virtual string LongName public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) { - var pk = PKMConverter.GetBlank(Generation, Version); + var pk = EntityBlank.GetBlank(Generation, Version); sav.ApplyTo(pk); ApplyDetails(sav, criteria, pk); return pk; diff --git a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs index 144abc0d9..b2c0b333e 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterStatic/EncounterStatic.cs @@ -51,7 +51,7 @@ public abstract record EncounterStatic(GameVersion Version) : IEncounterable, IM internal const int FormVivillon = 30; //protected const int FormRandom = 31; - protected virtual PKM GetBlank(ITrainerInfo tr) => PKMConverter.GetBlank(Generation, Version); + protected virtual PKM GetBlank(ITrainerInfo tr) => EntityBlank.GetBlank(Generation, Version); public PKM ConvertToPKM(ITrainerInfo sav) => ConvertToPKM(sav, EncounterCriteria.Unrestricted); diff --git a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs index 5647b4b88..3f6b13fac 100644 --- a/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs +++ b/PKHeX.Core/Legality/Encounters/EncounterTrade/EncounterTrade.cs @@ -68,7 +68,7 @@ public int TID7 public PKM ConvertToPKM(ITrainerInfo sav, EncounterCriteria criteria) { - var pk = PKMConverter.GetBlank(Generation, Version); + var pk = EntityBlank.GetBlank(Generation, Version); sav.ApplyTo(pk); ApplyDetails(sav, criteria, pk); diff --git a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs index 70ae572ae..44bce88e0 100644 --- a/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs +++ b/PKHeX.Core/Legality/Encounters/Information/EncounterLearn.cs @@ -61,7 +61,7 @@ public static IEnumerable GetLearn(int species, int[] moves, int if (moves.Any(z => z < 0)) return Array.Empty(); - var blank = PKMConverter.GetBlank(PKX.Generation); + var blank = EntityBlank.GetBlank(PKX.Generation); blank.Species = species; blank.Form = form; diff --git a/PKHeX.Core/PKM/PK1.cs b/PKHeX.Core/PKM/PK1.cs index e67790f1e..bb5c29f84 100644 --- a/PKHeX.Core/PKM/PK1.cs +++ b/PKHeX.Core/PKM/PK1.cs @@ -184,13 +184,13 @@ public PK7 ConvertToPK7() IsNicknamed = false, CurrentHandler = 1, - HT_Name = PKMConverter.OT_Name, - HT_Gender = PKMConverter.OT_Gender, + HT_Name = RecentTrainerCache.OT_Name, + HT_Gender = RecentTrainerCache.OT_Gender, }; - PKMConverter.SetConsoleRegionData3DS(pk7); - PKMConverter.SetFirstCountryRegion(pk7); + RecentTrainerCache.SetConsoleRegionData3DS(pk7); + RecentTrainerCache.SetFirstCountryRegion(pk7); pk7.HealPP(); - var lang = TransferLanguage(PKMConverter.Language); + var lang = TransferLanguage(RecentTrainerCache.Language); pk7.Language = lang; pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format); if (RawOT[0] == StringConverter12.G1TradeOTCode) // In-game Trade diff --git a/PKHeX.Core/PKM/PK2.cs b/PKHeX.Core/PKM/PK2.cs index 0137b84c9..a21a2e2e4 100644 --- a/PKHeX.Core/PKM/PK2.cs +++ b/PKHeX.Core/PKM/PK2.cs @@ -158,13 +158,13 @@ public PK7 ConvertToPK7() Form = Form, CurrentHandler = 1, - HT_Name = PKMConverter.OT_Name, - HT_Gender = PKMConverter.OT_Gender, + HT_Name = RecentTrainerCache.OT_Name, + HT_Gender = RecentTrainerCache.OT_Gender, }; - PKMConverter.SetConsoleRegionData3DS(pk7); - PKMConverter.SetFirstCountryRegion(pk7); + RecentTrainerCache.SetConsoleRegionData3DS(pk7); + RecentTrainerCache.SetFirstCountryRegion(pk7); pk7.HealPP(); - var lang = TransferLanguage(PKMConverter.Language); + var lang = TransferLanguage(RecentTrainerCache.Language); pk7.Language = lang; pk7.Nickname = SpeciesName.GetSpeciesNameGeneration(pk7.Species, lang, pk7.Format); diff --git a/PKHeX.Core/PKM/PK5.cs b/PKHeX.Core/PKM/PK5.cs index 7655dbb11..a6ee01893 100644 --- a/PKHeX.Core/PKM/PK5.cs +++ b/PKHeX.Core/PKM/PK5.cs @@ -444,16 +444,16 @@ public PK6 ConvertToPK6() // Write the Memories, Friendship, and Origin! CurrentHandler = 1, - HT_Name = PKMConverter.OT_Name, - HT_Gender = PKMConverter.OT_Gender, + HT_Name = RecentTrainerCache.OT_Name, + HT_Gender = RecentTrainerCache.OT_Gender, HT_Intensity = 1, HT_Memory = 4, HT_Feeling = MemoryContext6.GetRandomFeeling6(4, 10), }; // Write Transfer Location - location is dependent on 3DS system that transfers. - PKMConverter.SetConsoleRegionData3DS(pk6); - PKMConverter.SetFirstCountryRegion(pk6); + RecentTrainerCache.SetConsoleRegionData3DS(pk6); + RecentTrainerCache.SetFirstCountryRegion(pk6); // Apply trash bytes for species name of current app language -- default to PKM's language if no match int curLang = SpeciesName.GetSpeciesNameLanguage(Species, Nickname, 5); diff --git a/PKHeX.Core/PKM/PK6.cs b/PKHeX.Core/PKM/PK6.cs index 69780d50a..08dfd26fe 100644 --- a/PKHeX.Core/PKM/PK6.cs +++ b/PKHeX.Core/PKM/PK6.cs @@ -486,12 +486,13 @@ public PK7 ConvertToPK7() } pk7.SetTradeMemoryHT6(true); // oh no, memories on gen7 pkm - PKMConverter.SetFirstCountryRegion(pk7); + RecentTrainerCache.SetFirstCountryRegion(pk7); // Bank-accurate data zeroing - for (var i = 0x94; i < 0x9E; i++) pk7.Data[i] = 0; /* Geolocations. */ - for (var i = 0xAA; i < 0xB0; i++) pk7.Data[i] = 0; /* Unused/Amie Fullness & Enjoyment. */ - for (var i = 0xE4; i < 0xE8; i++) pk7.Data[i] = 0; /* Unused. */ + var span = pk7.Data.AsSpan(); + span[0x94..0x9E].Clear(); /* Geolocations. */ + span[0xAA..0xB0].Clear(); /* Unused/Amie Fullness & Enjoyment. */ + span[0xE4..0xE8].Clear(); /* Unused. */ pk7.Data[0x72] &= 0xFC; /* Clear lower two bits of Super training flags. */ pk7.Data[0xDE] = 0; /* Gen IV encounter type. */ diff --git a/PKHeX.Core/PKM/Util/EntityBlank.cs b/PKHeX.Core/PKM/Util/EntityBlank.cs new file mode 100644 index 000000000..de892f037 --- /dev/null +++ b/PKHeX.Core/PKM/Util/EntityBlank.cs @@ -0,0 +1,43 @@ +using System; +using System.Linq; +using System.Reflection; + +namespace PKHeX.Core; + +/// +/// Reflection utility to create blank without specifying a constructor. +/// +public static class EntityBlank +{ + /// + /// Gets a Blank object of the specified type. + /// + /// Type of instance desired. + /// New instance of a blank object. + public static PKM GetBlank(Type type) + { + var constructors = type.GetTypeInfo().DeclaredConstructors.Where(z => !z.IsStatic); + var argCount = constructors.Min(z => z.GetParameters().Length); + return (PKM)Activator.CreateInstance(type, new object[argCount]); + } + + public static PKM GetBlank(int gen, GameVersion ver) => gen switch + { + 1 when ver == GameVersion.BU => new PK1(true), + 7 when GameVersion.Gen7b.Contains(ver) => new PB7(), + 8 when GameVersion.BDSP.Contains(ver) => new PB8(), + 8 when GameVersion.PLA == ver => new PA8(), + _ => GetBlank(gen), + }; + + public static PKM GetBlank(int gen, int ver) => GetBlank(gen, (GameVersion)ver); + + public static PKM GetBlank(int gen) + { + var type = Type.GetType($"PKHeX.Core.PK{gen}"); + if (type is null) + throw new InvalidCastException($"Unable to get the type for PK{gen}."); + + return GetBlank(type); + } +} diff --git a/PKHeX.Core/PKM/Util/EntityConverter.cs b/PKHeX.Core/PKM/Util/EntityConverter.cs new file mode 100644 index 000000000..272db9974 --- /dev/null +++ b/PKHeX.Core/PKM/Util/EntityConverter.cs @@ -0,0 +1,270 @@ +using System; +using System.Diagnostics; +using static PKHeX.Core.MessageStrings; + +namespace PKHeX.Core; + +/// +/// Logic for converting a from one generation specific format to another. +/// +public static class EntityConverter +{ + /// + /// If a conversion method does not officially (legally) exist, then the program can try to convert via other means (illegal). + /// + public static bool AllowIncompatibleConversion { get; set; } + + /// + /// Checks if the input file is capable of being converted to the desired format. + /// + /// + /// + /// True if can be converted to the requested format value. + public static bool IsConvertibleToFormat(PKM pk, int format) + { + if (pk.Format >= 3 && pk.Format > format) + return false; // pk3->upward can't go backwards + if (pk.Format <= 2 && format is > 2 and < 7) + return false; // pk1/2->upward has to be 7 or greater + return true; + } + + /// + /// Converts a PKM from one Generation format to another. If it matches the destination format, the conversion will automatically return. + /// + /// PKM to convert + /// Format/Type to convert to + /// Comments regarding the transfer's success/failure + /// Converted PKM + public static PKM? ConvertToType(PKM pk, Type destType, out string comment) + { + Type fromType = pk.GetType(); + if (fromType == destType) + { + comment = "No need to convert, current format matches requested format."; + return pk; + } + + var pkm = ConvertPKM(pk, destType, fromType, out comment); + if (!AllowIncompatibleConversion || pkm != null) + return pkm; + + if (pk is PK8 && destType == typeof(PB8)) + return new PB8(pk.Data); + if (pk is PB8 && destType == typeof(PK8)) + return new PK8(pk.Data); + + // Try Incompatible Conversion + pkm = EntityBlank.GetBlank(destType); + pk.TransferPropertiesWithReflection(pkm); + if (!IsCompatibleWithModifications(pkm)) + return null; + comment = "Converted via reflection."; + return pkm; + } + + private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out string comment) + { + if (IsNotTransferable(pk, out comment)) + return null; + + string destName = destType.Name; + string srcName = srcType.Name; + Debug.WriteLine($"Trying to convert {srcName} to {destName}."); + + // All types that inherit PKM have the generation specifier as the last char in their class name. + int destGeneration = destName[^1] - '0'; + var pkm = ConvertPKM(pk, destType, destGeneration, ref comment); + var msg = pkm == null ? MsgPKMConvertFailFormat : MsgPKMConvertSuccess; + var formatted = string.Format(msg, srcName, destName); + comment = comment.Length != 0 ? formatted : string.Concat(formatted, Environment.NewLine, comment); + return pkm; + } + + private static PKM? ConvertPKM(PKM pk, Type destType, int destGeneration, ref string comment) + { + PKM? pkm = pk.Clone(); + if (pkm.IsEgg) + pkm.ForceHatchPKM(); + while (true) + { + pkm = IntermediaryConvert(pkm, destType, destGeneration, ref comment); + if (pkm == null) // fail convert + return null; + if (pkm.GetType() == destType) // finish convert + return pkm; + } + } + + private static PKM? IntermediaryConvert(PKM pk, Type destType, int destGeneration, ref string comment) + { + switch (pk) + { + // Non-sequential + case PK1 pk1 when destGeneration > 2: return pk1.ConvertToPK7(); + case PK2 pk2 when destGeneration > 2: return pk2.ConvertToPK7(); + case PK2 pk2 when destType == typeof(SK2): return pk2.ConvertToSK2(); + case PK3 pk3 when destType == typeof(CK3): return pk3.ConvertToCK3(); + case PK3 pk3 when destType == typeof(XK3): return pk3.ConvertToXK3(); + case PK4 pk4 when destType == typeof(BK4): return pk4.ConvertToBK4(); + + // Invalid + case PK2 pk2 when pk.Species > Legal.MaxSpeciesID_1: + var lang = pk2.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English; + var name = SpeciesName.GetSpeciesName(pk2.Species, lang); + comment = string.Format(MsgPKMConvertFailFormat, name, destType.Name); + return null; + + // Sequential + case PK1 pk1: return pk1.ConvertToPK2(); + case PK2 pk2: return pk2.ConvertToPK1(); + case PK3 pk3: return pk3.ConvertToPK4(); + case PK4 pk4: return pk4.ConvertToPK5(); + case PK5 pk5: return pk5.ConvertToPK6(); + case PK6 pk6: return pk6.ConvertToPK7(); + case PK7 pk7: return pk7.ConvertToPK8(); + case PB7 pb7: return pb7.ConvertToPK8(); + + // Side-Formats back to Mainline + case SK2 sk2: return sk2.ConvertToPK2(); + case CK3 ck3: return ck3.ConvertToPK3(); + case XK3 xk3: return xk3.ConvertToPK3(); + case BK4 bk4: return bk4.ConvertToPK4(); + + // None + default: + comment = MsgPKMConvertFailNoMethod; + return null; + } + } + + /// + /// Checks to see if a PKM is transferable relative to in-game restrictions and . + /// + /// PKM to convert + /// Comment indicating why the is not transferable. + /// Indication if Not Transferable + private static bool IsNotTransferable(PKM pk, out string comment) + { + switch (pk) + { + case PK4 { Species: (int)Species.Pichu } pk4 when pk4.Form != 0: + case PK6 { Species: (int)Species.Pikachu } pk6 when pk6.Form != 0: + case PB7 { Species: (int)Species.Pikachu } pika when pika.Form != 0: + case PB7 { Species: (int)Species.Eevee } eevee when eevee.Form != 0: + comment = MsgPKMConvertFailForm; + return true; + default: + comment = string.Empty; + return false; + } + } + + /// + /// Checks if the is compatible with the input , and makes any necessary modifications to force compatibility. + /// + /// Should only be used when forcing a backwards conversion to sanitize the PKM fields to the target format. + /// If the PKM is compatible, some properties may be forced to sanitized values. + /// PKM input that is to be sanity checked. + /// Value clamps for the destination format + /// Indication whether or not the PKM is compatible. + public static bool IsCompatibleWithModifications(PKM pk, IGameValueLimit limit) + { + if (pk.Species > limit.MaxSpeciesID) + return false; + + MakeCompatible(pk, limit); + return true; + } + + private static void MakeCompatible(PKM pk, IGameValueLimit limit) + { + if (pk.HeldItem > limit.MaxItemID) + pk.HeldItem = 0; + + if (pk.Nickname.Length > limit.NickLength) + pk.Nickname = pk.Nickname[..pk.NickLength]; + + if (pk.OT_Name.Length > limit.OTLength) + pk.OT_Name = pk.OT_Name[..pk.OTLength]; + + if (pk.Move1 > limit.MaxMoveID || pk.Move2 > limit.MaxMoveID || pk.Move3 > limit.MaxMoveID || pk.Move4 > limit.MaxMoveID) + pk.ClearInvalidMoves(); + + int maxEV = pk.MaxEV; + for (int i = 0; i < 6; i++) + { + if (pk.GetEV(i) > maxEV) + pk.SetEV(i, maxEV); + } + + int maxIV = pk.MaxIV; + for (int i = 0; i < 6; i++) + { + if (pk.GetIV(i) > maxIV) + pk.SetIV(i, maxIV); + } + } + + /// + public static bool IsCompatibleWithModifications(PKM pk) => IsCompatibleWithModifications(pk, pk); + + /// + /// Checks if the input is compatible with the target . + /// + /// Input to check -> update/sanitize + /// Target type PKM with misc properties accessible for checking. + /// Comment output + /// Output compatible PKM + /// Indication if the input is (now) compatible with the target. + public static bool TryMakePKMCompatible(PKM pk, PKM target, out string c, out PKM pkm) + { + if (!IsConvertibleToFormat(pk, target.Format)) + { + pkm = target; + c = string.Format(MsgPKMConvertFailBackwards, pk.GetType().Name, target.Format); + if (!AllowIncompatibleConversion) + return false; + } + if (IsIncompatibleGB(target, target.Japanese, pk.Japanese)) + { + pkm = target; + c = GetIncompatibleGBMessage(pk, target.Japanese); + return false; + } + var convert = ConvertToType(pk, target.GetType(), out c); + if (convert == null) + { + pkm = target; + return false; + } + + pkm = convert; + Debug.WriteLine(c); + return true; + } + + /// + /// Returns an error string to indicate that a is incompatible. + /// + public static string GetIncompatibleGBMessage(PKM pk, bool destJapanese) + { + var src = destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese; + var dest = !destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese; + return string.Format(MsgPKMConvertIncompatible, src, pk.GetType().Name, dest); + } + + /// + /// Checks if a is incompatible with the Generation 1/2 destination environment. + /// + public static bool IsIncompatibleGB(PKM pk, bool destJapanese, bool srcJapanese) + { + if (pk.Format > 2) + return false; + if (destJapanese == srcJapanese) + return false; + if (pk is SK2 sk2 && sk2.IsPossible(srcJapanese)) + return false; + return true; + } +} diff --git a/PKHeX.Core/PKM/Util/EntityFormat.cs b/PKHeX.Core/PKM/Util/EntityFormat.cs new file mode 100644 index 000000000..770a94194 --- /dev/null +++ b/PKHeX.Core/PKM/Util/EntityFormat.cs @@ -0,0 +1,197 @@ +using System; +using System.Linq; +using static PKHeX.Core.PokeCrypto; +using static System.Buffers.Binary.BinaryPrimitives; +using static PKHeX.Core.EntityFormatDetected; + +namespace PKHeX.Core; + +public static class EntityFormat +{ + /// + /// Gets the generation of the Pokemon data. + /// + /// Raw data representing a Pokemon. + /// An integer indicating the generation of the PKM file, or -1 if the data is invalid. + public static EntityFormatDetected GetFormat(ReadOnlySpan data) + { + if (!PKX.IsPKM(data.Length)) + return None; + + return GetFormatInternal(data); + } + + private static EntityFormatDetected GetFormatInternal(ReadOnlySpan data) => data.Length switch + { + SIZE_1JLIST or SIZE_1ULIST => FormatPK1, + SIZE_2JLIST or SIZE_2ULIST => FormatPK2, + SIZE_2STADIUM => FormatSK2, + SIZE_3PARTY or SIZE_3STORED => FormatPK3, + SIZE_3CSTORED => FormatCK3, + SIZE_3XSTORED => FormatXK3, + SIZE_4PARTY or SIZE_4STORED => GetFormat45(data), + SIZE_5PARTY => FormatPK5, + SIZE_6STORED => GetFormat67(data), + SIZE_6PARTY => GetFormat67_PGT(data), + SIZE_8PARTY or SIZE_8STORED => GetFormat8(data), + SIZE_8APARTY or SIZE_8ASTORED => FormatPA8, + _ => None, + }; + + private static EntityFormatDetected GetFormat67_PGT(ReadOnlySpan data) + { + if (!IsFormat67(data)) + return None; // PGT collision, same size. + return GetFormat67(data); + } + + private static bool IsFormat67(ReadOnlySpan data) + { + if (ReadUInt16LittleEndian(data[0x04..]) != 0) // Bad Sanity? + return false; // non-zero ItemID + if (ReadUInt16LittleEndian(data[0x06..]) == GetCHK(data, SIZE_6STORED)) + return true; + if (ReadUInt16LittleEndian(data[0x58..]) != 0) // Not encrypted terminator + return false; + + // PGT files have the last 0x10 bytes 00; PK6/etc will have data here. + var tail = data[..^0x10]; + foreach (var b in tail) + { + if (b != 0) + return true; + } + return false; + } + + // assumes decrypted state + private static EntityFormatDetected GetFormat45(ReadOnlySpan data) + { + if (ReadUInt16LittleEndian(data[0x4..]) != 0) + return FormatBK4; // BK4 non-zero sanity + if (data[0x5F] < 0x10 && ReadUInt16LittleEndian(data[0x80..]) < 0x3333) + return FormatPK4; // gen3/4 version origin, not Transporter + if (ReadUInt16LittleEndian(data[0x46..]) != 0) + return FormatPK4; // PK4.Met_LocationExtended (unused in PK5) + return FormatPK5; + } + + /// + /// Checks if the input PK6 file is really a PK7, if so, updates the object. + /// + /// Updated PKM if actually PK7 + private static EntityFormatDetected GetFormat67(ReadOnlySpan data) + { + var pk = new PK6(data.ToArray()); + return IsFormatReally7(pk); + } + + // assumes decrypted state + private static EntityFormatDetected GetFormat8(ReadOnlySpan data) + { + if (data[0xDE] is (byte)GameVersion.BD or (byte)GameVersion.SP) + return FormatPB8; + return FormatPK8; + } + + /// + /// Creates an instance of from the given data. + /// + /// Raw data of the Pokemon file. + /// Optional identifier for the preferred generation. Usually the generation of the destination save file. + /// An instance of created from the given , or null if is invalid. + public static PKM? GetFromBytes(byte[] data, int prefer = 7) + { + var format = GetFormat(data); + return GetFromBytes(data, format, prefer); + } + + private static PKM? GetFromBytes(byte[] data, EntityFormatDetected format, int prefer) => format switch + { + FormatPK1 => new PokeList1(data)[0], + FormatPK2 => new PokeList2(data)[0], + FormatSK2 => new SK2(data), + FormatPK3 => new PK3(data), + FormatCK3 => new CK3(data), + FormatXK3 => new XK3(data), + FormatPK4 => new PK4(data), + FormatBK4 => new BK4(data), + FormatPK5 => new PK5(data), + FormatPK6 => new PK6(data), + FormatPK7 => new PK7(data), + FormatPB7 => new PB7(data), + FormatPK8 => new PK8(data), + FormatPA8 => new PA8(data), + FormatPB8 => new PB8(data), + Format6or7 => prefer == 6 ? new PK6(data) : new PK7(data), + _ => null, + }; + + /// + /// Checks if the input PK6 file is really a PK7. + /// + /// PK6 to check + /// Boolean is a PK7 + private static EntityFormatDetected IsFormatReally7(PK6 pk) + { + if (pk.Version > Legal.MaxGameID_6) + { + if (pk.Version is ((int)GameVersion.GP or (int)GameVersion.GE)) + return FormatPB7; + return FormatPK7; + } + + // Check Ranges + if (pk.Species > Legal.MaxSpeciesID_6) + return FormatPK7; + if (pk.Moves.Any(move => move > Legal.MaxMoveID_6_AO)) + return FormatPK7; + if (pk.RelearnMoves.Any(move => move > Legal.MaxMoveID_6_AO)) + return FormatPK7; + if (pk.Ability > Legal.MaxAbilityID_6_AO) + return FormatPK7; + if (pk.HeldItem > Legal.MaxItemID_6_AO) + return FormatPK7; + + // Ground Tile property is replaced with Hyper Training PK6->PK7 + var et = pk.GroundTile; + if (et != 0) + { + if (pk.CurrentLevel < 100) // can't be hyper trained + return FormatPK6; + + if (!pk.Gen4) // can't have GroundTile + return FormatPK7; + if (et > GroundTileType.Max_Pt) // invalid gen4 GroundTile + return FormatPK7; + } + + int mb = ReadUInt16LittleEndian(pk.Data.AsSpan(0x16)); + if (mb > 0xAAA) + return FormatPK6; + for (int i = 0; i < 6; i++) + { + if ((mb >> (i << 1) & 3) == 3) // markings are 10 or 01 (or 00), never 11 + return FormatPK6; + } + + if (pk.Data[0x2A] > 20) // ResortEventStatus is always < 20 + return FormatPK6; + + return Format6or7; + } +} + +public enum EntityFormatDetected +{ + None = -1, + + FormatPK1, + FormatPK2, FormatSK2, + FormatPK3, FormatCK3, FormatXK3, + FormatPK4, FormatBK4, FormatPK5, + FormatPK6, FormatPK7, FormatPB7, + FormatPK8, FormatPA8, FormatPB8, + + Format6or7, +} diff --git a/PKHeX.Core/PKM/Util/FormConverter.cs b/PKHeX.Core/PKM/Util/FormConverter.cs index bb9390321..da84c3542 100644 --- a/PKHeX.Core/PKM/Util/FormConverter.cs +++ b/PKHeX.Core/PKM/Util/FormConverter.cs @@ -41,7 +41,7 @@ public static string[] GetFormList(int species, IReadOnlyList types, IRe } // this is a hack; depends on currently loaded SaveFile's Game ID - private static bool IsGG() => PKMConverter.Game is (int)GameVersion.GP or (int)GameVersion.GE; + private static bool IsGG() => RecentTrainerCache.Game is (int)GameVersion.GP or (int)GameVersion.GE; private static readonly string[] EMPTY = { string.Empty }; private const string Starter = nameof(Starter); diff --git a/PKHeX.Core/PKM/Util/PKMConverter.cs b/PKHeX.Core/PKM/Util/PKMConverter.cs deleted file mode 100644 index e0f0e35f5..000000000 --- a/PKHeX.Core/PKM/Util/PKMConverter.cs +++ /dev/null @@ -1,472 +0,0 @@ -using System; -using System.Diagnostics; -using System.Linq; -using System.Reflection; - -using static System.Buffers.Binary.BinaryPrimitives; -using static PKHeX.Core.MessageStrings; - -namespace PKHeX.Core -{ - /// - /// Logic for converting a from one generation specific format to another. - /// - public static class PKMConverter - { - public static void SetPrimaryTrainer(ITrainerInfo t) - { - Trainer = t; - if (t is IRegionOrigin o) - Trainer67 = o; - } - - private static ITrainerInfo Trainer { get; set; } = new SimpleTrainerInfo(); - private static IRegionOrigin Trainer67 { get; set; } = new SimpleTrainerInfo(GameVersion.SN); - public static string OT_Name => Trainer.OT; - public static int OT_Gender => Trainer.Gender; - public static int Language => Trainer.Language; - public static int Format => Trainer.Generation; - public static int Game => Trainer.Game; - public static bool AllowIncompatibleConversion { private get; set; } - - public static void SetConsoleRegionData3DS(IRegionOrigin pkm) - { - var trainer = Trainer is IRegionOrigin r ? r : Trainer67; - pkm.ConsoleRegion = trainer.ConsoleRegion; - pkm.Country = trainer.Country; - pkm.Region = trainer.Region; - } - - public static void SetFirstCountryRegion(IGeoTrack pkm) - { - var trainer = Trainer is IRegionOrigin r ? r : Trainer67; - pkm.Geo1_Country = trainer.Country; - pkm.Geo1_Region = trainer.Region; - } - - /// - /// Gets the generation of the Pokemon data. - /// - /// Raw data representing a Pokemon. - /// An integer indicating the generation of the PKM file, or -1 if the data is invalid. - public static int GetPKMDataFormat(ReadOnlySpan data) - { - if (!PKX.IsPKM(data.Length)) - return -1; - - switch (data.Length) - { - case PokeCrypto.SIZE_1JLIST or PokeCrypto.SIZE_1ULIST: - return 1; - case PokeCrypto.SIZE_2JLIST or PokeCrypto.SIZE_2ULIST: - case PokeCrypto.SIZE_2STADIUM: - return 2; - case PokeCrypto.SIZE_3PARTY or PokeCrypto.SIZE_3STORED: - case PokeCrypto.SIZE_3CSTORED: - case PokeCrypto.SIZE_3XSTORED: - return 3; - case PokeCrypto.SIZE_4PARTY or PokeCrypto.SIZE_4STORED: - case PokeCrypto.SIZE_5PARTY: - if ((ReadUInt16LittleEndian(data[0x4..]) == 0) && (ReadUInt16LittleEndian(data[0x80..]) >= 0x3333 || data[0x5F] >= 0x10) && ReadUInt16LittleEndian(data[0x46..]) == 0) // PK5 - return 5; - return 4; - case PokeCrypto.SIZE_6STORED: - return 6; - case PokeCrypto.SIZE_6PARTY: // collision with PGT, same size. - if (ReadUInt16LittleEndian(data[0x4..]) != 0) // Bad Sanity? - return -1; - if (ReadUInt32LittleEndian(data[0x06..]) == PokeCrypto.GetCHK(data, PokeCrypto.SIZE_6STORED)) - return 6; - if (ReadUInt16LittleEndian(data[0x58..]) != 0) // Encrypted? - { - for (int i = data.Length - 0x10; i < data.Length; i++) // 0x10 of 00's at the end != PK6 - { - if (data[i] != 0) - return 6; - } - - return -1; - } - return 6; - case PokeCrypto.SIZE_8PARTY or PokeCrypto.SIZE_8STORED: - return 8; - case PokeCrypto.SIZE_8APARTY or PokeCrypto.SIZE_8ASTORED: - return 8; - - default: - return -1; - } - } - - /// - /// Creates an instance of from the given data. - /// - /// Raw data of the Pokemon file. - /// Optional identifier for the preferred generation. Usually the generation of the destination save file. - /// An instance of created from the given , or null if is invalid. - public static PKM? GetPKMfromBytes(byte[] data, int prefer = 7) - { - int format = GetPKMDataFormat(data); - return format switch - { - 1 => new PokeList1(data)[0], - 2 => data.Length != PokeCrypto.SIZE_2STADIUM ? new PokeList2(data)[0] : new SK2(data), - 3 => data.Length switch - { - PokeCrypto.SIZE_3CSTORED => new CK3(data), - PokeCrypto.SIZE_3XSTORED => new XK3(data), - _ => new PK3(data), - }, - 4 => ReadUInt16LittleEndian(data.AsSpan(0x04)) == 0 ? new PK4(data) : new BK4(data), - 5 => new PK5(data), - 6 => CheckPKMFormat7(new PK6(data), prefer), - 8 => CheckPKMFormat8(data), - _ => null, - }; - } - - private static PKM CheckPKMFormat8(byte[] data) - { - if (data.Length is PokeCrypto.SIZE_8ASTORED or PokeCrypto.SIZE_8APARTY) - return new PA8(data); - - var pk8 = new PB8(data); - var ver = pk8.Version; - if (GameVersion.BDSP.Contains(ver)) - return pk8; - - return new PK8(data); - } - - /// - /// Checks if the input PK6 file is really a PK7, if so, updates the object. - /// - /// PKM to check - /// Prefer a certain generation over another - /// Updated PKM if actually PK7 - private static G6PKM CheckPKMFormat7(PK6 pk, int prefer) - { - if (GameVersion.Gen7b.Contains(pk.Version)) - return new PB7(pk.Data); - if (IsPK6FormatReallyPK7(pk, prefer)) - return new PK7(pk.Data); - return pk; - } - - /// - /// Checks if the input PK6 file is really a PK7. - /// - /// PK6 to check - /// Prefer a certain generation over another - /// Boolean is a PK7 - private static bool IsPK6FormatReallyPK7(PK6 pk, int preferredFormat) - { - if (pk.Version > Legal.MaxGameID_6) - return true; - - // Check Ranges - if (pk.Species > Legal.MaxSpeciesID_6) - return true; - if (pk.Moves.Any(move => move > Legal.MaxMoveID_6_AO)) - return true; - if (pk.RelearnMoves.Any(move => move > Legal.MaxMoveID_6_AO)) - return true; - if (pk.Ability > Legal.MaxAbilityID_6_AO) - return true; - if (pk.HeldItem > Legal.MaxItemID_6_AO) - return true; - - // Ground Tile property is replaced with Hyper Training PK6->PK7 - var et = pk.GroundTile; - if (et != 0) - { - if (pk.CurrentLevel < 100) // can't be hyper trained - return false; - - if (!pk.Gen4) // can't have GroundTile - return true; - if (et > GroundTileType.Max_Pt) // invalid gen4 GroundTile - return true; - } - - int mb = ReadUInt16LittleEndian(pk.Data.AsSpan(0x16)); - if (mb > 0xAAA) - return false; - for (int i = 0; i < 6; i++) - { - if ((mb >> (i << 1) & 3) == 3) // markings are 10 or 01 (or 00), never 11 - return false; - } - - if (pk.Data[0x2A] > 20) // ResortEventStatus is always < 20 - return false; - - return preferredFormat > 6; - } - - /// - /// Checks if the input file is capable of being converted to the desired format. - /// - /// - /// - /// - public static bool IsConvertibleToFormat(PKM pk, int format) - { - if (pk.Format >= 3 && pk.Format > format) - return false; // pk3->upward can't go backwards - if (pk.Format <= 2 && format is > 2 and < 7) - return false; // pk1/2->upward has to be 7 or greater - return true; - } - - /// - /// Converts a PKM from one Generation format to another. If it matches the destination format, the conversion will automatically return. - /// - /// PKM to convert - /// Format/Type to convert to - /// Comments regarding the transfer's success/failure - /// Converted PKM - public static PKM? ConvertToType(PKM pk, Type destType, out string comment) - { - Type fromType = pk.GetType(); - if (fromType == destType) - { - comment = "No need to convert, current format matches requested format."; - return pk; - } - - var pkm = ConvertPKM(pk, destType, fromType, out comment); - if (!AllowIncompatibleConversion || pkm != null) - return pkm; - - if (pk is PK8 && destType == typeof(PB8)) - return new PB8(pk.Data); - if (pk is PB8 && destType == typeof(PK8)) - return new PK8(pk.Data); - - // Try Incompatible Conversion - pkm = GetBlank(destType); - pk.TransferPropertiesWithReflection(pkm); - if (!IsPKMCompatibleWithModifications(pkm)) - return null; - comment = "Converted via reflection."; - return pkm; - } - - private static PKM? ConvertPKM(PKM pk, Type destType, Type srcType, out string comment) - { - if (IsNotTransferable(pk, out comment)) - return null; - - string destName = destType.Name; - string srcName = srcType.Name; - Debug.WriteLine($"Trying to convert {srcName} to {destName}."); - - // All types that inherit PKM have the generation specifier as the last char in their class name. - int destGeneration = destName[^1] - '0'; - var pkm = ConvertPKM(pk, destType, destGeneration, ref comment); - var msg = pkm == null ? MsgPKMConvertFailFormat : MsgPKMConvertSuccess; - var formatted = string.Format(msg, srcName, destName); - comment = comment.Length != 0 ? formatted : string.Concat(formatted, Environment.NewLine, comment); - return pkm; - } - - private static PKM? ConvertPKM(PKM pk, Type destType, int destGeneration, ref string comment) - { - PKM? pkm = pk.Clone(); - if (pkm.IsEgg) - pkm.ForceHatchPKM(); - while (true) - { - pkm = IntermediaryConvert(pkm, destType, destGeneration, ref comment); - if (pkm == null) // fail convert - return null; - if (pkm.GetType() == destType) // finish convert - return pkm; - } - } - - private static PKM? IntermediaryConvert(PKM pk, Type destType, int destGeneration, ref string comment) - { - switch (pk) - { - // Non-sequential - case PK1 pk1 when destGeneration > 2: return pk1.ConvertToPK7(); - case PK2 pk2 when destGeneration > 2: return pk2.ConvertToPK7(); - case PK2 pk2 when destType == typeof(SK2): return pk2.ConvertToSK2(); - case PK3 pk3 when destType == typeof(CK3): return pk3.ConvertToCK3(); - case PK3 pk3 when destType == typeof(XK3): return pk3.ConvertToXK3(); - case PK4 pk4 when destType == typeof(BK4): return pk4.ConvertToBK4(); - - // Invalid - case PK2 pk2 when pk.Species > Legal.MaxSpeciesID_1: - var lang = pk2.Japanese ? (int)LanguageID.Japanese : (int)LanguageID.English; - var name = SpeciesName.GetSpeciesName(pk2.Species, lang); - comment = string.Format(MsgPKMConvertFailFormat, name, destType.Name); - return null; - - // Sequential - case PK1 pk1: return pk1.ConvertToPK2(); - case PK2 pk2: return pk2.ConvertToPK1(); - case PK3 pk3: return pk3.ConvertToPK4(); - case PK4 pk4: return pk4.ConvertToPK5(); - case PK5 pk5: return pk5.ConvertToPK6(); - case PK6 pk6: return pk6.ConvertToPK7(); - case PK7 pk7: return pk7.ConvertToPK8(); - case PB7 pb7: return pb7.ConvertToPK8(); - - // Side-Formats back to Mainline - case SK2 sk2: return sk2.ConvertToPK2(); - case CK3 ck3: return ck3.ConvertToPK3(); - case XK3 xk3: return xk3.ConvertToPK3(); - case BK4 bk4: return bk4.ConvertToPK4(); - - // None - default: - comment = MsgPKMConvertFailNoMethod; - return null; - } - } - - /// - /// Checks to see if a PKM is transferable relative to in-game restrictions and . - /// - /// PKM to convert - /// Comment indicating why the is not transferable. - /// Indication if Not Transferable - private static bool IsNotTransferable(PKM pk, out string comment) - { - switch (pk) - { - case PK4 { Species: (int)Species.Pichu } pk4 when pk4.Form != 0: - case PK6 { Species: (int)Species.Pikachu } pk6 when pk6.Form != 0: - case PB7 { Species: (int)Species.Pikachu } pika when pika.Form != 0: - case PB7 { Species: (int)Species.Eevee } eevee when eevee.Form != 0: - comment = MsgPKMConvertFailForm; - return true; - default: - comment = string.Empty; - return false; - } - } - - /// - /// Checks if the is compatible with the input , and makes any necessary modifications to force compatibility. - /// - /// Should only be used when forcing a backwards conversion to sanitize the PKM fields to the target format. - /// If the PKM is compatible, some properties may be forced to sanitized values. - /// PKM input that is to be sanity checked. - /// Indication whether or not the PKM is compatible. - public static bool IsPKMCompatibleWithModifications(PKM pk) => IsPKMCompatibleWithModifications(pk, pk); - - public static bool IsPKMCompatibleWithModifications(PKM pk, IGameValueLimit limit) - { - if (pk.Species > limit.MaxSpeciesID) - return false; - - if (pk.HeldItem > limit.MaxItemID) - pk.HeldItem = 0; - - if (pk.Nickname.Length > limit.NickLength) - pk.Nickname = pk.Nickname[..pk.NickLength]; - - if (pk.OT_Name.Length > limit.OTLength) - pk.OT_Name = pk.OT_Name[..pk.OTLength]; - - if (pk.Moves.Any(move => move > limit.MaxMoveID)) - pk.ClearInvalidMoves(); - - int maxEV = pk.MaxEV; - for (int i = 0; i < 6; i++) - { - if (pk.GetEV(i) > maxEV) - pk.SetEV(i, maxEV); - } - - int maxIV = pk.MaxIV; - for (int i = 0; i < 6; i++) - { - if (pk.GetIV(i) > maxIV) - pk.SetIV(i, maxIV); - } - - return true; - } - - /// - /// Checks if the input is compatible with the target . - /// - /// Input to check -> update/sanitize - /// Target type PKM with misc properties accessible for checking. - /// Comment output - /// Output compatible PKM - /// Indication if the input is (now) compatible with the target. - public static bool TryMakePKMCompatible(PKM pk, PKM target, out string c, out PKM pkm) - { - if (!IsConvertibleToFormat(pk, target.Format)) - { - pkm = target; - c = string.Format(MsgPKMConvertFailBackwards, pk.GetType().Name, target.Format); - if (!AllowIncompatibleConversion) - return false; - } - if (IsIncompatibleGB(target, target.Japanese, pk.Japanese)) - { - pkm = target; - c = GetIncompatibleGBMessage(pk, target.Japanese); - return false; - } - var convert = ConvertToType(pk, target.GetType(), out c); - if (convert == null) - { - pkm = target; - return false; - } - - pkm = convert; - Debug.WriteLine(c); - return true; - } - - public static string GetIncompatibleGBMessage(PKM pk, bool destJapanese) - { - var src = destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese; - var dest = !destJapanese ? MsgPKMConvertInternational : MsgPKMConvertJapanese; - return string.Format(MsgPKMConvertIncompatible, src, pk.GetType().Name, dest); - } - - public static bool IsIncompatibleGB(PKM pk, bool destJapanese, bool srcJapanese) => pk.Format <= 2 && destJapanese != srcJapanese && !(pk is SK2 sk2 && sk2.IsPossible(srcJapanese)); - - /// - /// Gets a Blank object of the specified type. - /// - /// Type of instance desired. - /// New instance of a blank object. - public static PKM GetBlank(Type type) - { - var constructors = type.GetTypeInfo().DeclaredConstructors.Where(z => !z.IsStatic); - var argCount = constructors.Min(z => z.GetParameters().Length); - return (PKM)Activator.CreateInstance(type, new object[argCount]); - } - - public static PKM GetBlank(int gen, GameVersion ver) => gen switch - { - 1 when ver == GameVersion.BU => new PK1(true), - 7 when GameVersion.Gen7b.Contains(ver) => new PB7(), - 8 when GameVersion.BDSP.Contains(ver) => new PB8(), - 8 when GameVersion.PLA == ver => new PA8(), - _ => GetBlank(gen), - }; - - public static PKM GetBlank(int gen, int ver) => GetBlank(gen, (GameVersion) ver); - - public static PKM GetBlank(int gen) - { - var type = Type.GetType($"PKHeX.Core.PK{gen}"); - if (type is null) - throw new InvalidCastException($"Unable to get the type for PK{gen}."); - - return GetBlank(type); - } - } -} diff --git a/PKHeX.Core/PKM/Util/QRMessageUtil.cs b/PKHeX.Core/PKM/Util/QRMessageUtil.cs index 2fdeb190b..6d6fb11f7 100644 --- a/PKHeX.Core/PKM/Util/QRMessageUtil.cs +++ b/PKHeX.Core/PKM/Util/QRMessageUtil.cs @@ -25,7 +25,7 @@ public static class QRMessageUtil var data = DecodeMessagePKM(message); if (data == null) return null; - return PKMConverter.GetPKMfromBytes(data, format); + return EntityFormat.GetFromBytes(data, format); } /// diff --git a/PKHeX.Core/PKM/Util/RecentTrainerCache.cs b/PKHeX.Core/PKM/Util/RecentTrainerCache.cs new file mode 100644 index 000000000..6fe1d2db4 --- /dev/null +++ b/PKHeX.Core/PKM/Util/RecentTrainerCache.cs @@ -0,0 +1,70 @@ +namespace PKHeX.Core; + +/// +/// Caches a reference to the most recently loaded trainer data. +/// +/// Useful for sourcing trainer info for the ConvertTo methods. +public static class RecentTrainerCache +{ + private static ITrainerInfo Trainer = new SimpleTrainerInfo(); + private static IRegionOrigin Trainer67 = new SimpleTrainerInfo(GameVersion.SN); + + private static IRegionOrigin GetTrainer3DS(ITrainerInfo tr) => tr is IRegionOrigin r ? r : Trainer67; + + /// Most recently loaded . + public static string OT_Name => Trainer.OT; + + /// Most recently loaded . + public static int OT_Gender => Trainer.Gender; + + /// Most recently loaded . + public static int Language => Trainer.Language; + + /// Most recently loaded . + public static int Format => Trainer.Generation; + + /// Most recently loaded . + public static int Game => Trainer.Game; + + /// + /// Updates the cache with the most recently loaded trainer reference. + /// + /// + public static void SetRecentTrainer(ITrainerInfo trainer) + { + Trainer = trainer; + if (trainer is IRegionOrigin g67) + Trainer67 = g67; + } + + /// + public static void SetConsoleRegionData3DS(IRegionOrigin pk) => SetConsoleRegionData3DS(pk, Trainer); + + /// + public static void SetFirstCountryRegion(IGeoTrack pk) => SetFirstCountryRegion(pk, Trainer); + + /// + /// Fetches an trainer to apply details to the input . + /// + /// Entity to apply details to. + /// Trainer that is receiving the entity. + public static void SetConsoleRegionData3DS(IRegionOrigin pk, ITrainerInfo trainer) + { + var tr = GetTrainer3DS(trainer); + pk.ConsoleRegion = tr.ConsoleRegion; + pk.Country = tr.Country; + pk.Region = tr.Region; + } + + /// + /// Fetches an trainer to apply details to the input . + /// + /// Entity to apply details to. + /// Trainer that is receiving the entity. + public static void SetFirstCountryRegion(IGeoTrack pk, ITrainerInfo trainer) + { + var tr = GetTrainer3DS(trainer); + pk.Geo1_Country = tr.Country; + pk.Geo1_Region = tr.Region; + } +} diff --git a/PKHeX.Core/Saves/Storage/BulkStorage.cs b/PKHeX.Core/Saves/Storage/BulkStorage.cs index c6b19f45a..8d6c402e5 100644 --- a/PKHeX.Core/Saves/Storage/BulkStorage.cs +++ b/PKHeX.Core/Saves/Storage/BulkStorage.cs @@ -12,7 +12,7 @@ protected BulkStorage(byte[] data, Type t, int start, int slotsPerBox = 30) : ba Box = start; SlotsPerBox = slotsPerBox; - blank = PKMConverter.GetBlank(t); + blank = EntityBlank.GetBlank(t); var slots = (Data.Length - Box) / blank.SIZE_STORED; BoxCount = slots / SlotsPerBox; } @@ -28,7 +28,7 @@ protected BulkStorage(byte[] data, Type t, int start, int slotsPerBox = 30) : ba public sealed override Type PKMType => blank.GetType(); public sealed override PKM BlankPKM => blank.Clone(); - protected override PKM GetPKM(byte[] data) => PKMConverter.GetPKMfromBytes(data, prefer: Generation) ?? blank; + protected override PKM GetPKM(byte[] data) => EntityFormat.GetFromBytes(data, prefer: Generation) ?? blank; protected override byte[] DecryptPKM(byte[] data) => GetPKM(data).Data; protected override int SIZE_STORED => blank.SIZE_STORED; diff --git a/PKHeX.Core/Saves/Util/BoxUtil.cs b/PKHeX.Core/Saves/Util/BoxUtil.cs index 749637bd4..41761abe2 100644 --- a/PKHeX.Core/Saves/Util/BoxUtil.cs +++ b/PKHeX.Core/Saves/Util/BoxUtil.cs @@ -176,7 +176,7 @@ public static IEnumerable GetPKMsFromPaths(IEnumerable files, int g var result = files .Where(file => PKX.IsPKM(new FileInfo(file).Length)) .Select(File.ReadAllBytes) - .Select(data => PKMConverter.GetPKMfromBytes(data, prefer: generation)); + .Select(data => EntityFormat.GetFromBytes(data, prefer: generation)); foreach (var pkm in result) { diff --git a/PKHeX.Core/Saves/Util/SaveExtensions.cs b/PKHeX.Core/Saves/Util/SaveExtensions.cs index fecba93cc..e55f5e4a3 100644 --- a/PKHeX.Core/Saves/Util/SaveExtensions.cs +++ b/PKHeX.Core/Saves/Util/SaveExtensions.cs @@ -33,7 +33,7 @@ public static bool IsCompatiblePKM(this SaveFile sav, PKM pk) if (sav.PKMType != pk.GetType()) return false; - if (sav is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(pk, il.Japanese, pk.Japanese)) + if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(pk, il.Japanese, pk.Japanese)) return false; return true; @@ -129,16 +129,16 @@ public static IEnumerable GetCompatible(this SaveFile sav, IEnumerable foreach (var temp in pks) { - var pk = PKMConverter.ConvertToType(temp, savtype, out string c); + var pk = EntityConverter.ConvertToType(temp, savtype, out string c); if (pk == null) { Debug.WriteLine(c); continue; } - if (sav is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) + if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) { - c = PKMConverter.GetIncompatibleGBMessage(pk, il.Japanese); + c = EntityConverter.GetIncompatibleGBMessage(pk, il.Japanese); Debug.WriteLine(c); continue; } @@ -160,13 +160,13 @@ public static IEnumerable GetCompatible(this SaveFile sav, IEnumerable public static PKM GetCompatiblePKM(this SaveFile sav, PKM pk) { if (pk.Format >= 3 || sav.Generation >= 7) - return PKMConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; + return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; // gen1-2 compatibility check if (pk.Japanese != ((ILangDeviantSave)sav).Japanese) return sav.BlankPKM; if (sav is SAV2 s2 && s2.Korean != pk.Korean) return sav.BlankPKM; - return PKMConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; + return EntityConverter.ConvertToType(pk, sav.PKMType, out _) ?? sav.BlankPKM; } /// @@ -198,11 +198,11 @@ public static PKM LoadTemplate(this SaveFile sav, string? templatePath = null) if (!File.Exists(path) || !PKX.IsPKM(new FileInfo(path).Length)) return LoadTemplateInternal(sav); - var pk = PKMConverter.GetPKMfromBytes(File.ReadAllBytes(path), prefer: sav.Generation); + var pk = EntityFormat.GetFromBytes(File.ReadAllBytes(path), prefer: sav.Generation); if (pk?.Species is not > 0) return LoadTemplateInternal(sav); - return PKMConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out _) ?? LoadTemplateInternal(sav); + return EntityConverter.ConvertToType(pk, sav.BlankPKM.GetType(), out _) ?? LoadTemplateInternal(sav); } } } diff --git a/PKHeX.Core/Util/FileUtil.cs b/PKHeX.Core/Util/FileUtil.cs index cf9c49806..aa47c7e11 100644 --- a/PKHeX.Core/Util/FileUtil.cs +++ b/PKHeX.Core/Util/FileUtil.cs @@ -173,7 +173,7 @@ public static bool TryGetPKM(byte[] data, [NotNullWhen(true)] out PKM? pk, strin return false; } var format = PKX.GetPKMFormatFromExtension(ext, sav?.Generation ?? 6); - pk = PKMConverter.GetPKMfromBytes(data, prefer: format); + pk = EntityFormat.GetFromBytes(data, prefer: format); return pk != null; } diff --git a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs index 9e056587d..a5a7b72aa 100644 --- a/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs +++ b/PKHeX.WinForms/Controls/PKM Editor/PKMEditor.cs @@ -280,7 +280,7 @@ private void LoadFieldsFromPKM(PKM pk, bool focus = true, bool skipConversionChe if (focus) Tab_Main.Focus(); - if (!skipConversionCheck && !PKMConverter.TryMakePKMCompatible(pk, Entity, out string c, out pk)) + if (!skipConversionCheck && !EntityConverter.TryMakePKMCompatible(pk, Entity, out string c, out pk)) { WinFormsUtil.Alert(c); return; } FieldsLoaded = false; diff --git a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs index f2e80314b..50c120745 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SAVEditor.cs @@ -872,7 +872,7 @@ private static int ImportGroup(IEnumerable data, SaveFile sav, int box, PKM continue; } - var convert = PKMConverter.ConvertToType(x, type, out _); + var convert = EntityConverter.ConvertToType(x, type, out _); if (convert?.GetType() != type) { slotSkipped++; diff --git a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs index 71b73c5b9..9d4d712a8 100644 --- a/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs +++ b/PKHeX.WinForms/Controls/SAV Editor/SlotChangeManager.cs @@ -290,7 +290,7 @@ private bool TryLoadFiles(IReadOnlyList files, DragEventArgs e, bool bad return true; // treat as handled } - var pk = PKMConverter.ConvertToType(temp, sav.PKMType, out string c); + var pk = EntityConverter.ConvertToType(temp, sav.PKMType, out string c); if (pk == null) { WinFormsUtil.Error(c); @@ -301,9 +301,9 @@ private bool TryLoadFiles(IReadOnlyList files, DragEventArgs e, bool bad if (badDest && (pk.Species == 0 || pk.IsEgg)) return false; - if (sav is ILangDeviantSave il && PKMConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) + if (sav is ILangDeviantSave il && EntityConverter.IsIncompatibleGB(temp, il.Japanese, pk.Japanese)) { - c = PKMConverter.GetIncompatibleGBMessage(pk, il.Japanese); + c = EntityConverter.GetIncompatibleGBMessage(pk, il.Japanese); WinFormsUtil.Error(c); Debug.WriteLine(c); return false; diff --git a/PKHeX.WinForms/MainWindow/Main.cs b/PKHeX.WinForms/MainWindow/Main.cs index 6224baa6e..cc6c3c118 100644 --- a/PKHeX.WinForms/MainWindow/Main.cs +++ b/PKHeX.WinForms/MainWindow/Main.cs @@ -50,7 +50,7 @@ public Main() if (HaX) { - PKMConverter.AllowIncompatibleConversion = true; + EntityConverter.AllowIncompatibleConversion = true; WinFormsUtil.Alert(MsgProgramIllegalModeActive, MsgProgramIllegalModeBehave); } else if (showChangelog) @@ -401,7 +401,7 @@ private void ReloadProgramSettings(PKHeXSettings settings) C_SAV.M.Hover.GlowHover = settings.Hover.HoverSlotGlowEdges; ParseSettings.InitFromSettings(settings.Legality); PKME_Tabs.HideSecretValues = C_SAV.HideSecretDetails = settings.Privacy.HideSecretDetails; - PKMConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion; + EntityConverter.AllowIncompatibleConversion = settings.Advanced.AllowIncompatibleConversion; WinFormsUtil.DetectSaveFileOnFileOpen = settings.Startup.TryDetectRecentSave; SpriteBuilder.LoadSettings(settings.Sprite); @@ -588,7 +588,7 @@ private bool LoadFile(object? input, string path) private bool OpenPKM(PKM pk) { - var tmp = PKMConverter.ConvertToType(pk, C_SAV.SAV.PKMType, out string c); + var tmp = EntityConverter.ConvertToType(pk, C_SAV.SAV.PKMType, out string c); Debug.WriteLine(c); if (tmp == null) return false; @@ -615,7 +615,7 @@ private bool OpenMysteryGift(MysteryGift tg, string path) } var temp = tg.ConvertToPKM(C_SAV.SAV); - var pk = PKMConverter.ConvertToType(temp, C_SAV.SAV.PKMType, out string c); + var pk = EntityConverter.ConvertToType(temp, C_SAV.SAV.PKMType, out string c); if (pk == null) { @@ -724,7 +724,7 @@ private bool OpenSAV(SaveFile sav, string path) PKME_Tabs.Focus(); // flush any pending changes StoreLegalSaveGameData(sav); - PKMConverter.SetPrimaryTrainer(sav); + RecentTrainerCache.SetRecentTrainer(sav); SpriteUtil.Initialize(sav); // refresh sprite generator dragout.Size = new Size(SpriteUtil.Spriter.Width, SpriteUtil.Spriter.Height); diff --git a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs index 38e0b8c49..5ea742dfc 100644 --- a/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs +++ b/PKHeX.WinForms/Subforms/SAV_MysteryGiftDB.cs @@ -105,7 +105,7 @@ private void ClickView(object sender, EventArgs e) if (index < 0) return; var pk = Results[index].ConvertToPKM(SAV); - pk = PKMConverter.ConvertToType(pk, SAV.PKMType, out var c); + pk = EntityConverter.ConvertToType(pk, SAV.PKMType, out var c); if (pk == null) { WinFormsUtil.Error(c); diff --git a/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs b/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs index 51e1cabd2..38a5a2ab8 100644 --- a/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs +++ b/Tests/PKHeX.Core.Tests/Legality/LegalityTests.cs @@ -74,7 +74,7 @@ private static void VerifyAll(string folder, string name, bool isValid, bool che var dn = fi.DirectoryName ?? string.Empty; ParseSettings.AllowGBCartEra = dn.Contains("GBCartEra"); ParseSettings.AllowGen1Tradeback = dn.Contains("1 Tradeback"); - var pkm = PKMConverter.GetPKMfromBytes(data, prefer: format); + var pkm = EntityFormat.GetFromBytes(data, prefer: format); pkm.Should().NotBeNull($"the PKM '{new FileInfo(file).Name}' should have been loaded"); if (pkm == null) continue; diff --git a/Tests/PKHeX.Core.Tests/PKM/HiddenPowerTests.cs b/Tests/PKHeX.Core.Tests/PKM/HiddenPowerTests.cs index cda664153..ec0fceb9d 100644 --- a/Tests/PKHeX.Core.Tests/PKM/HiddenPowerTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/HiddenPowerTests.cs @@ -13,7 +13,7 @@ public class HiddenPowerTests [InlineData(26, 31, 31, 30, 31, 31, MoveType.Grass, 70, typeof(PK3))] public void HiddenPowerTest(int h, int a, int b, int c, int d, int s, MoveType type, int power, Type pkmType) { - var pkm = PKMConverter.GetBlank(pkmType); + var pkm = EntityBlank.GetBlank(pkmType); pkm.IV_HP = h; pkm.IV_ATK = a; pkm.IV_DEF = b;