diff --git a/PKHeX/Legality/LegalityCheckStrings.cs b/PKHeX/Legality/LegalityCheckStrings.cs
index db49da2fb..4426a3c66 100644
--- a/PKHeX/Legality/LegalityCheckStrings.cs
+++ b/PKHeX/Legality/LegalityCheckStrings.cs
@@ -18,9 +18,9 @@ public static class LegalityCheckStrings
public static string V189 {get; set;} = "Analysis not available for this Pokémon.";
/// Format text for exporting a legality check result.
public static string V196 {get; set;} = "{0}: {1}";
- /// Format text for exporting a legality check result for an invalid Move.
+ /// Format text for exporting a legality check result for a Move.
public static string V191 {get; set;} = "{0} Move {1}: {2}";
- /// Format text for exporting a legality check result for an invalid Relearn Move.
+ /// Format text for exporting a legality check result for a Relearn Move.
public static string V192 {get; set;} = "{0} Relearn Move {1}: {2}";
/// Format text for exporting the type of Encounter that was matched for the the
public static string V195 {get; set;} = "Encounter Type: {0}";
diff --git a/PKHeX/Legality/RNG/MethodFinder.cs b/PKHeX/Legality/RNG/MethodFinder.cs
new file mode 100644
index 000000000..13c73433e
--- /dev/null
+++ b/PKHeX/Legality/RNG/MethodFinder.cs
@@ -0,0 +1,236 @@
+using System.Collections.Generic;
+using System.Linq;
+
+namespace PKHeX.Core
+{
+ ///
+ /// Class containing logic to obtain a PKM's PIDIV method.
+ ///
+ public static class MethodFinder
+ {
+ ///
+ /// Analyzes a to find a matching PIDIV method.
+ ///
+ /// Input .
+ /// object containing seed and method info.
+ public static PIDIV Analyze(PKM pk)
+ {
+ if (pk.Format < 3)
+ return AnalyzeGB(pk);
+ var pid = pk.PID;
+ var top = pid >> 16;
+ var bot = pid & 0xFFFF;
+
+ var iIVs = pk.IVs;
+ var IVs = new uint[6];
+ for (int i = 0; i < 6; i++)
+ IVs[i] = (uint)iIVs[i];
+
+ PIDIV pidiv;
+ if (getLCRNGMatch(top, bot, IVs, out pidiv))
+ return pidiv;
+ if (getXDRNGMatch(top, bot, IVs, out pidiv))
+ return pidiv;
+
+ // Special cases
+ if (getChannelMatch(top, bot, IVs, out pidiv))
+ return pidiv;
+ if (getMG4Match(pid, IVs, out pidiv))
+ return pidiv;
+ if (getModifiedPID(pid, out pidiv))
+ return pidiv;
+ if (pid <= 0xFF && getCuteCharmMatch(pk, pid, out pidiv))
+ return pidiv;
+
+ return pidiv; // no match
+ }
+
+ private static bool getLCRNGMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv)
+ {
+ var reg = getSeedsFromPID(RNG.LCRNG, top, bot);
+ foreach (var seed in reg)
+ {
+ // A and B are already used by PID
+ var B = RNG.LCRNG.Advance(seed, 2);
+
+ // Method 1/2/4 can use 3 different RNG frames
+ var C = RNG.LCRNG.Next(B);
+ var D = RNG.LCRNG.Next(C);
+
+ if (getIVs(C >> 16, D >> 16).SequenceEqual(IVs)) // ABCD
+ {
+ pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_1};
+ return true;
+ }
+
+ var E = RNG.LCRNG.Next(D);
+ if (getIVs(D >> 16, E >> 16).SequenceEqual(IVs)) // ABDE
+ {
+ pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_2};
+ return true;
+ }
+
+ if (getIVs(C >> 16, E >> 16).SequenceEqual(IVs)) // ABCE
+ {
+ pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_4};
+ return true;
+ }
+ }
+ pidiv = null;
+ return false;
+ }
+ private static bool getXDRNGMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv)
+ {
+ var xdc = getSeedsFromPID(RNG.XDRNG, bot, top);
+ foreach (var seed in xdc)
+ {
+ var C = RNG.XDRNG.Reverse(seed, 3);
+
+ var D = RNG.XDRNG.Next(C);
+ var E = RNG.XDRNG.Next(D);
+
+ if (!getIVs(D >> 16, E >> 16).SequenceEqual(IVs))
+ continue;
+
+ pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.XDRNG, Type = PIDType.XDC};
+ return true;
+ }
+ pidiv = null;
+ return false;
+ }
+ private static bool getChannelMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv)
+ {
+ var channel = getSeedsFromPID(RNG.XDRNG, bot, top ^ 0x8000);
+ foreach (var seed in channel)
+ {
+ var E = RNG.XDRNG.Advance(seed, 5);
+ if (!getIVs(RNG.XDRNG, E).SequenceEqual(IVs))
+ continue;
+
+ pidiv = new PIDIV {OriginSeed = RNG.XDRNG.Prev(seed), RNG = RNG.XDRNG, Type = PIDType.Channel};
+ return true;
+ }
+ pidiv = null;
+ return false;
+ }
+ private static bool getMG4Match(uint pid, uint[] IVs, out PIDIV pidiv)
+ {
+ uint mg4Rev = RNG.ARNG.Prev(pid);
+ var mg4 = getSeedsFromPID(RNG.LCRNG, mg4Rev >> 16, mg4Rev & 0xFFFF);
+ foreach (var seed in mg4)
+ {
+ var B = RNG.LCRNG.Advance(seed, 2);
+ var C = RNG.LCRNG.Next(B);
+ var D = RNG.LCRNG.Next(C);
+ if (!getIVs(C >> 16, D >> 16).SequenceEqual(IVs))
+ continue;
+
+ pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.G4AntiShiny};
+ return true;
+ }
+ pidiv = null;
+ return false;
+ }
+ private static bool getModifiedPID(uint pid, out PIDIV pidiv)
+ {
+ // generation 5 shiny PIDs
+ // todo
+ pidiv = null;
+ return false;
+ }
+ private static bool getCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv)
+ {
+ int genderValue = pk.Gender;
+ switch (genderValue)
+ {
+ case 2: break; // can't cute charm a genderless pkm
+ case 0: // male
+ var gr = pk.PersonalInfo.Gender;
+ if (254 <= gr) // no modification for PID
+ break;
+ if (pk.PID < gr)
+ break;
+ if (pk.PID >= gr + 25)
+ break;
+
+ pidiv = new PIDIV { OriginSeed = 0, RNG = RNG.LCRNG, Type = PIDType.CuteCharm };
+ return true;
+ case 1: // female
+ if (pk.PID >= 25)
+ break; // nope
+ if (254 <= pk.PersonalInfo.Gender) // no modification for PID
+ break;
+
+ pidiv = new PIDIV { OriginSeed = 0, RNG = RNG.LCRNG, Type = PIDType.CuteCharm };
+ return true;
+ }
+ pidiv = null;
+ return false;
+ }
+
+ private static PIDIV AnalyzeGB(PKM pk)
+ {
+ return null;
+ }
+
+ private static IEnumerable getSeedsFromPID(RNG method, uint top, uint bot)
+ {
+ uint cmp = top << 16;
+ uint start = bot << 16;
+ uint end = start | 0xFFFF;
+ for (uint i = start; i <= end; i++)
+ if ((method.Next(i) & 0xFFFF0000) == cmp)
+ yield return method.Prev(i);
+ }
+ private static IEnumerable getSeedsFromIVs(RNG method, uint top, uint bot)
+ {
+ uint cmp = top << 16 & 0x7FFF0000;
+ uint start = bot << 16 & 0x7FFF0000;
+ uint end = start | 0xFFFF;
+ for (uint i = start; i <= end; i++)
+ if ((method.Next(i) & 0x7FFF0000) == cmp)
+ yield return method.Prev(i);
+
+ start |= 0x80000000;
+ end |= 0x80000000;
+ for (uint i = start; i <= end; i++)
+ if ((method.Next(i) & 0x7FFF0000) == cmp)
+ yield return method.Prev(i);
+ }
+
+ ///
+ /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each).
+ ///
+ /// First rand frame
+ /// Second rand frame
+ /// Array of 6 IVs
+ private static uint[] getIVs(uint r1, uint r2)
+ {
+ return new[]
+ {
+ r1 & 31,
+ r1 >> 5 & 31,
+ r1 >> 10 & 31,
+ r2 & 31,
+ r2 >> 5 & 31,
+ r2 >> 10 & 31,
+ };
+ }
+ ///
+ /// Generates an IV for each RNG call using the top 5 bits of frame seeds.
+ ///
+ /// RNG advancement method
+ /// RNG seed
+ /// Array of 6 IVs
+ private static uint[] getIVs(RNG method, uint seed)
+ {
+ uint[] ivs = new uint[6];
+ for (int i = 0; i < 6; i++)
+ {
+ seed = method.Next(seed);
+ ivs[i] = seed >> 27;
+ }
+ return ivs;
+ }
+ }
+}
diff --git a/PKHeX/Legality/Structures/PIDIV.cs b/PKHeX/Legality/RNG/PIDIV.cs
similarity index 79%
rename from PKHeX/Legality/Structures/PIDIV.cs
rename to PKHeX/Legality/RNG/PIDIV.cs
index c0e4017e8..a55eac525 100644
--- a/PKHeX/Legality/Structures/PIDIV.cs
+++ b/PKHeX/Legality/RNG/PIDIV.cs
@@ -7,5 +7,8 @@ public class PIDIV
/// The RNG seed which immediately generates the PIDIV (starting with PID or IVs, whichever comes first).
public uint OriginSeed;
+
+ /// Type of PIDIV correlation
+ public PIDType Type;
}
}
diff --git a/PKHeX/Legality/RNG/PIDType.cs b/PKHeX/Legality/RNG/PIDType.cs
new file mode 100644
index 000000000..a24d56b3c
--- /dev/null
+++ b/PKHeX/Legality/RNG/PIDType.cs
@@ -0,0 +1,46 @@
+namespace PKHeX.Core
+{
+ public enum PIDType
+ {
+ /// No match
+ None,
+
+ /// Method 1 Variants (H1/J/K)
+ Method_1,
+ /// Method H2
+ Method_2,
+ /// Method H4
+ Method_4,
+
+ ///
+ /// Event Reversed Order PID restricted to 16bit Origin Seed
+ ///
+ BACD_R,
+ ///
+ /// Event Reversed Order PID without Origin Seed restrictions
+ ///
+ BACD_U,
+
+ ///
+ /// Generation 4 Cute Charm forced 8 bit
+ ///
+ CuteCharm,
+ ///
+ /// Generation 4 Chained Shiny
+ ///
+ ChainShiny,
+
+ // XDRNG Based
+ XDC,
+ Channel,
+
+ // ARNG Based
+ G4AntiShiny,
+
+ // Formulaic
+ G5AntiShiny,
+
+ // Specified
+ Static,
+ }
+}
diff --git a/PKHeX/Legality/RNG/RNG.cs b/PKHeX/Legality/RNG/RNG.cs
index 83c118858..f504dbc93 100644
--- a/PKHeX/Legality/RNG/RNG.cs
+++ b/PKHeX/Legality/RNG/RNG.cs
@@ -7,7 +7,7 @@ public class RNG
public static readonly RNG ARNG = new RNG(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93);
private readonly uint Mult, Add, rMult, rAdd;
- protected RNG(uint f_mult, uint f_add, uint r_mult, uint r_add)
+ private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add)
{
Mult = f_mult;
Add = f_add;
@@ -16,15 +16,15 @@ protected RNG(uint f_mult, uint f_add, uint r_mult, uint r_add)
}
public uint Next(uint seed) => seed * Mult + Add;
- private uint Prev(uint seed) => seed * rMult + rAdd;
+ public uint Prev(uint seed) => seed * rMult + rAdd;
- private uint Advance(uint seed, int frames)
+ public uint Advance(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
seed = Next(seed);
return seed;
}
- private uint Reverse(uint seed, int frames)
+ public uint Reverse(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
seed = Prev(seed);
diff --git a/PKHeX/PKHeX.Core.csproj b/PKHeX/PKHeX.Core.csproj
index 60b211d6f..cf4a94f99 100644
--- a/PKHeX/PKHeX.Core.csproj
+++ b/PKHeX/PKHeX.Core.csproj
@@ -173,7 +173,7 @@
-
+
diff --git a/Tests/PKHeX.Tests/PKM/PKMTests.cs b/Tests/PKHeX.Tests/PKM/PKMTests.cs
index a537edb98..2cbe28435 100644
--- a/Tests/PKHeX.Tests/PKM/PKMTests.cs
+++ b/Tests/PKHeX.Tests/PKM/PKMTests.cs
@@ -1,12 +1,14 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
+using PKHeX.Core;
namespace PKHeX.Tests.PKM
{
[TestClass]
public class PKMTests
{
- const string DateTestCategory = "PKM Date Tests";
+ private const string DateTestCategory = "PKM Date Tests";
+ private const string PIDIVTestCategory = "PKM PIDIV Matching Tests";
[TestMethod]
[TestCategory(DateTestCategory)]
@@ -125,5 +127,60 @@ public void EggMetDateSetterTest()
Assert.AreEqual(now.Month, pk.EggMetMonth, "Egg_Month was not correctly set");
Assert.AreEqual(now.Year - 2000, pk.EggMetYear, "Egg_Year was not correctly set");
}
+
+ [TestMethod]
+ [TestCategory(PIDIVTestCategory)]
+ public void PIDIVMatchingTest()
+ {
+ // IVs are stored HP/ATK/DEF/SPE/SPA/SPD
+ var pk1 = new PK3
+ {
+ PID = 0xE97E0000,
+ IVs = new[] {17, 19, 20, 16, 13, 12}
+ };
+ Assert.AreEqual(PIDType.Method_1, MethodFinder.Analyze(pk1)?.Type, "Unable to match PID to Method 1 spread");
+ var pk2 = new PK3
+ {
+ PID = 0x5271E97E,
+ IVs = new[] {02, 18, 03, 12, 22, 24}
+ };
+ Assert.AreEqual(PIDType.Method_2, MethodFinder.Analyze(pk2)?.Type, "Unable to match PID to Method 2 spread");
+ var pk4 = new PK3
+ {
+ PID = 0x31B05271,
+ IVs = new[] {02, 18, 03, 05, 30, 11}
+ };
+ Assert.AreEqual(PIDType.Method_4, MethodFinder.Analyze(pk4)?.Type, "Unable to match PID to Method 4 spread");
+
+ var pk3 = new PK3
+ {
+ PID = 0x0985A297,
+ IVs = new[] {06, 01, 00, 07, 17, 07}
+ };
+ Assert.AreEqual(PIDType.XDC, MethodFinder.Analyze(pk3)?.Type, "Unable to match PID to XDC spread");
+
+ var pkC = new PK3
+ {
+ PID = 0x9E27D2F6,
+ IVs = new[] {04, 15, 21, 14, 18, 29}
+ };
+ Assert.AreEqual(PIDType.Channel, MethodFinder.Analyze(pkC)?.Type, "Unable to match PID to Channel spread");
+
+ var pkCC = new PK4
+ {
+ PID = 0x00000037,
+ IVs = new[] {16, 13, 12, 02, 18, 03},
+ Species = 1,
+ Gender = 0,
+ };
+ Assert.AreEqual(PIDType.CuteCharm, MethodFinder.Analyze(pkCC)?.Type, "Unable to match PID to Cute Charm spread");
+
+ var pkASR = new PK4
+ {
+ PID = 0x07578CB7, // 0x5271E97E rerolled
+ IVs = new[] {16, 13, 12, 02, 18, 03},
+ };
+ Assert.AreEqual(PIDType.G4AntiShiny, MethodFinder.Analyze(pkASR)?.Type, "Unable to match PID to Antishiny4 spread");
+ }
}
}