Add PIDIV matching

Includes 1 test for each pkm pidiv type, haven't added absolutely every
method possible but it's enough for now
This commit is contained in:
Kurt 2017-04-29 16:22:32 -07:00
parent 2f0eedbe7c
commit 82375ca464
7 changed files with 350 additions and 8 deletions

View file

@ -18,9 +18,9 @@ namespace PKHeX.Core
public static string V189 {get; set;} = "Analysis not available for this Pokémon.";
/// <summary>Format text for exporting a legality check result.</summary>
public static string V196 {get; set;} = "{0}: {1}";
/// <summary>Format text for exporting a legality check result for an invalid Move.</summary>
/// <summary>Format text for exporting a legality check result for a Move.</summary>
public static string V191 {get; set;} = "{0} Move {1}: {2}";
/// <summary>Format text for exporting a legality check result for an invalid Relearn Move.</summary>
/// <summary>Format text for exporting a legality check result for a Relearn Move.</summary>
public static string V192 {get; set;} = "{0} Relearn Move {1}: {2}";
/// <summary>Format text for exporting the type of Encounter that was matched for the the <see cref="PKM"/></summary>
public static string V195 {get; set;} = "Encounter Type: {0}";

View file

@ -0,0 +1,236 @@
using System.Collections.Generic;
using System.Linq;
namespace PKHeX.Core
{
/// <summary>
/// Class containing logic to obtain a PKM's PIDIV method.
/// </summary>
public static class MethodFinder
{
/// <summary>
/// Analyzes a <see cref="PKM"/> to find a matching PIDIV method.
/// </summary>
/// <param name="pk">Input <see cref="PKM"/>.</param>
/// <returns><see cref="PIDIV"/> object containing seed and method info.</returns>
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<uint> 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<uint> 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);
}
/// <summary>
/// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each).
/// </summary>
/// <param name="r1">First rand frame</param>
/// <param name="r2">Second rand frame</param>
/// <returns>Array of 6 IVs</returns>
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,
};
}
/// <summary>
/// Generates an IV for each RNG call using the top 5 bits of frame seeds.
/// </summary>
/// <param name="method">RNG advancement method</param>
/// <param name="seed">RNG seed</param>
/// <returns>Array of 6 IVs</returns>
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;
}
}
}

View file

@ -7,5 +7,8 @@
/// <summary> The RNG seed which immediately generates the PIDIV (starting with PID or IVs, whichever comes first). </summary>
public uint OriginSeed;
/// <summary> Type of PIDIV correlation </summary>
public PIDType Type;
}
}

View file

@ -0,0 +1,46 @@
namespace PKHeX.Core
{
public enum PIDType
{
/// <summary> No match </summary>
None,
/// <summary> Method 1 Variants (H1/J/K) </summary>
Method_1,
/// <summary> Method H2 </summary>
Method_2,
/// <summary> Method H4 </summary>
Method_4,
/// <summary>
/// Event Reversed Order PID restricted to 16bit Origin Seed
/// </summary>
BACD_R,
/// <summary>
/// Event Reversed Order PID without Origin Seed restrictions
/// </summary>
BACD_U,
/// <summary>
/// Generation 4 Cute Charm forced 8 bit
/// </summary>
CuteCharm,
/// <summary>
/// Generation 4 Chained Shiny
/// </summary>
ChainShiny,
// XDRNG Based
XDC,
Channel,
// ARNG Based
G4AntiShiny,
// Formulaic
G5AntiShiny,
// Specified
Static,
}
}

View file

@ -7,7 +7,7 @@
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 @@
}
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);

View file

@ -173,7 +173,7 @@
<Compile Include="Legality\Structures\IRibbonSet.cs" />
<Compile Include="Legality\Structures\Learnset.cs" />
<Compile Include="Legality\Structures\Nature.cs" />
<Compile Include="Legality\Structures\PIDIV.cs" />
<Compile Include="Legality\RNG\PIDIV.cs" />
<Compile Include="Legality\Structures\SlotType.cs" />
<Compile Include="Legality\Structures\TradebackType.cs" />
<Compile Include="Legality\Structures\ValidEncounterMoves.cs" />

View file

@ -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 @@ namespace PKHeX.Tests.PKM
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");
}
}
}