From 82375ca464c2fd10fd69f4102ec00b879fbb58dc Mon Sep 17 00:00:00 2001 From: Kurt Date: Sat, 29 Apr 2017 16:22:32 -0700 Subject: [PATCH] Add PIDIV matching Includes 1 test for each pkm pidiv type, haven't added absolutely every method possible but it's enough for now --- PKHeX/Legality/LegalityCheckStrings.cs | 4 +- PKHeX/Legality/RNG/MethodFinder.cs | 236 ++++++++++++++++++++ PKHeX/Legality/{Structures => RNG}/PIDIV.cs | 3 + PKHeX/Legality/RNG/PIDType.cs | 46 ++++ PKHeX/Legality/RNG/RNG.cs | 8 +- PKHeX/PKHeX.Core.csproj | 2 +- Tests/PKHeX.Tests/PKM/PKMTests.cs | 59 ++++- 7 files changed, 350 insertions(+), 8 deletions(-) create mode 100644 PKHeX/Legality/RNG/MethodFinder.cs rename PKHeX/Legality/{Structures => RNG}/PIDIV.cs (79%) create mode 100644 PKHeX/Legality/RNG/PIDType.cs 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"); + } } }