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;