PKHeX/PKHeX.Core/Legality/RNG/MethodFinder.cs
Kurt 68aa9aeec9 Relocate encounter data loading to separate files
shifts encounter data from Legal's split tables to individual files for
easier maintenance and initialization. Legal Core's init is so much
simpler now.

fix resource name typo
2017-07-25 00:28:43 -07:00

645 lines
24 KiB
C#

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.EncryptionConstant;
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 (pk.Species == 201 && GetLCRNGUnownMatch(top, bot, IVs, out pidiv)) // frlg only
return pidiv;
if (GetXDRNGMatch(top, bot, IVs, out pidiv))
return pidiv;
// Special cases
if (GetLCRNGRoamerMatch(top, bot, IVs, out pidiv))
return pidiv;
if (GetChannelMatch(top, bot, IVs, out pidiv, pk))
return pidiv;
if (GetMG4Match(pid, IVs, out pidiv))
return pidiv;
if (pk.IsShiny)
{
if (GetChainShinyMatch(pk, pid, IVs, out pidiv))
return pidiv;
if (GetModifiedPID(pk, pid, out pidiv))
return pidiv;
}
if (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv))
return pidiv;
if (GetBACDMatch(pk, pid, IVs, out pidiv))
return pidiv;
if (GetPokewalkerMatch(pk, pid, out pidiv))
return pidiv;
return new PIDIV {Type=PIDType.None, NoSeed=true}; // no match
}
private static bool GetLCRNGMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv)
{
var reg = GetSeedsFromPID(RNG.LCRNG, top, bot);
var iv1 = GetIVChunk(IVs, 0);
var iv2 = GetIVChunk(IVs, 3);
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 ivC = C >> 16 & 0x7FFF;
if (iv1 == ivC)
{
var D = RNG.LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv2 == ivD) // ABCD
{
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_1};
return true;
}
var E = RNG.LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // ABCE
{
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_4};
return true;
}
}
else
{
var D = RNG.LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD)
continue;
var E = RNG.LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // ABDE
{
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_2};
return true;
}
}
}
pidiv = null;
return false;
}
private static bool GetLCRNGUnownMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv)
{
// this is an exact copy of LCRNG 1,2,4 matching, except the PID has its halves switched (BACD, BADE, BACE)
var reg = GetSeedsFromPID(RNG.LCRNG, bot, top); // reversed!
var iv1 = GetIVChunk(IVs, 0);
var iv2 = GetIVChunk(IVs, 3);
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 ivC = C >> 16 & 0x7FFF;
if (iv1 == ivC)
{
var D = RNG.LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv2 == ivD) // BACD
{
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_1_Unown};
return true;
}
var E = RNG.LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // BACE
{
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_4_Unown};
return true;
}
}
else
{
var D = RNG.LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD)
continue;
var E = RNG.LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // BADE
{
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_2_Unown};
return true;
}
}
}
pidiv = null;
return false;
}
private static bool GetLCRNGRoamerMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv)
{
if (IVs.Skip(2).Any(iv => iv != 0) || IVs[1] > 7)
{
pidiv = null;
return false;
}
var iv1 = GetIVChunk(IVs, 0);
var reg = GetSeedsFromPID(RNG.LCRNG, top, bot);
foreach (var seed in reg)
{
// Only the first 8 bits are kept
var ivC = RNG.LCRNG.Advance(seed, 3) >> 16 & 0x00FF;
if (iv1 != ivC)
continue;
pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.Method_1_Roamer};
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 B = RNG.XDRNG.Prev(seed);
var A = RNG.XDRNG.Prev(B);
if (!GetIVs(A >> 16, B >> 16).SequenceEqual(IVs))
continue;
pidiv = new PIDIV {OriginSeed = RNG.XDRNG.Prev(A), RNG = RNG.XDRNG, Type = PIDType.CXD};
return true;
}
pidiv = null;
return false;
}
private static bool GetChannelMatch(uint top, uint bot, uint[] IVs, out PIDIV pidiv, PKM pk)
{
var ver = pk.Version;
if (ver != (int) GameVersion.R && ver != (int) GameVersion.S)
{
pidiv = null;
return false;
}
var undo = top ^ 0x8000;
if ((undo > 7 ? 0 : 1) != (bot ^ pk.SID ^ 40122))
top = undo;
var channel = GetSeedsFromPID(RNG.XDRNG, bot, top);
foreach (var seed in channel)
{
var C = RNG.XDRNG.Advance(seed, 3); // held item
// no checks, held item can be swapped
var D = RNG.XDRNG.Next(C); // Version
if ((D >> 31) + 1 != ver) // (0-Sapphire, 1-Ruby)
continue;
var E = RNG.XDRNG.Next(D); // OT Gender
if (E >> 31 != pk.OT_Gender)
continue;
if (!RNG.XDRNG.GetSequentialIVsUInt32(E).SequenceEqual(IVs))
continue;
if (seed >> 16 != pk.SID)
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.G4MGAntiShiny};
return true;
}
pidiv = null;
return false;
}
private static bool GetModifiedPID(PKM pk, uint pid, out PIDIV pidiv)
{
var low = pid & 0xFFFF;
// generation 5 shiny PIDs
if (low <= 0xFF)
{
var high = pid >> 16;
if (((pk.TID ^ pk.SID ^ low) - high & 0xFFFE) == 0)
{
pidiv = new PIDIV {NoSeed = true, Type = PIDType.G5MGShiny};
return true;
}
}
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;
var rate = pk.Gender == 1 ? 0 : 25*(gr/25 + 1); // buffered
var nature = pid % 25;
if (nature + rate != pid)
break;
pidiv = new PIDIV {NoSeed = true, RNG = RNG.LCRNG, Type = PIDType.CuteCharm};
return true;
case 1: // female
if (pid >= 25)
break; // nope
if (254 <= pk.PersonalInfo.Gender) // no modification for PID
break;
pidiv = new PIDIV {NoSeed = true, RNG = RNG.LCRNG, Type = PIDType.CuteCharm};
return true;
}
pidiv = null;
return false;
}
private static bool GetChainShinyMatch(PKM pk, uint pid, uint[] IVs, out PIDIV pidiv)
{
// 13 shiny bits
// PIDH & 7
// PIDL & 7
// IVs
var bot = GetIVChunk(IVs, 0);
var top = GetIVChunk(IVs, 3);
var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot);
foreach (var seed in reg)
{
// check the individual bits
var s = seed;
int i = 15;
while (true)
{
var bit = s >> 16 & 1;
if (bit != (pid >> i & 1))
break;
s = RNG.LCRNG.Prev(s);
if (--i == 2)
break;
}
if (i != 2) // bit failed
continue;
// Shiny Bits of PID validated
var upper = s;
if ((upper >> 16 & 7) != (pid >> 16 & 7))
continue;
var lower = RNG.LCRNG.Prev(upper);
if ((lower >> 16 & 7) != (pid & 7))
continue;
var upid = ((pid & 0xFFFF) ^ pk.TID ^ pk.SID) & 0xFFF8 | (upper >> 16) & 0x7;
if (upid != pid >> 16)
continue;
s = RNG.LCRNG.Reverse(lower, 2); // unroll one final time to get the origin seed
pidiv = new PIDIV {OriginSeed = s, RNG = RNG.LCRNG, Type = PIDType.ChainShiny};
return true;
}
pidiv = null;
return false;
}
private static bool GetBACDMatch(PKM pk, uint pid, uint[] IVs, out PIDIV pidiv)
{
var bot = GetIVChunk(IVs, 0);
var top = GetIVChunk(IVs, 3);
var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot);
foreach (var seed in reg)
{
var B = seed;
var A = RNG.LCRNG.Prev(B);
var PID = A & 0xFFFF0000 | B >> 16;
bool isShiny = (pk.TID ^ pk.SID ^ PID >> 16 ^ PID & 0xFFFF) < 8;
bool forceShiny = false;
bool antiShiny = false;
if (PID != pid)
{
if (!isShiny)
{
// check for force shiny pkm
if (!pk.IsShiny)
continue; // obviously not force shiny
// 0-Origin
// 1-PIDH
// 2-PIDL (ends up unused)
// 3-FORCEBITS
// PID = PIDH << 16 | (SID ^ TID ^ PIDH)
var X = RNG.LCRNG.Prev(A);
PID = X & 0xFFFF0000 | (uint)pk.SID ^ (uint)pk.TID ^ X >> 16;
PID &= 0xFFFFFFF8;
PID |= B >> 16 & 0x7; // lowest 3 bits
if (PID != pid)
continue;
forceShiny = true;
}
if (!forceShiny)
{
if ((PID + 8 & 0xFFFFFFF8) != pid)
continue;
antiShiny = true;
}
}
var s = RNG.LCRNG.Prev(A);
// Check for prior Restricted seed
var sn = s;
for (int i = 0; i < 3; i++, sn = RNG.LCRNG.Prev(sn))
{
if ((sn & 0xFFFF0000) != 0)
continue;
var type = forceShiny ? PIDType.BACD_R_S : antiShiny ? PIDType.BACD_R_A : PIDType.BACD_R;
pidiv = new PIDIV {OriginSeed = sn, RNG = RNG.LCRNG, Type = type};
return true;
}
// no restricted seed found, thus unrestricted
var t = forceShiny ? PIDType.BACD_U_S : antiShiny ? PIDType.BACD_U_A : PIDType.BACD_U;
pidiv = new PIDIV {OriginSeed = s, RNG = RNG.LCRNG, Type = t};
return true;
}
pidiv = null;
return false;
}
private static bool GetPokewalkerMatch(PKM pk, uint oldpid, out PIDIV pidiv)
{
var nature = oldpid % 25;
if (nature == 24)
{
pidiv = null;
return false;
}
uint pid = (uint)((pk.TID ^ pk.SID) >> 8 ^ 0xFF) << 24; // the most significant byte of the PID is chosen so the Pokémon can never be shiny.
pid += nature - pid % 25;
uint gv = 0;
switch (pk.Gender)
{
case 0: // Male
var gr = pk.PersonalInfo.Gender + 1;
gv = (uint) (((gr - (pid & 0xFF)) / 25 + 1) * 25); // Ensures gender is set to male without affecting nature.
break;
case 1: // Female
var gr2 = pk.PersonalInfo.Gender;
gv = (uint) ((((pid & 0xFF) - gr2) / 25 + 1) * 25); // Ensures gender is set to female without affecting nature
break;
}
pid += gv;
if ((nature & 1) != (pid & 1)) // If ability does not match the chosen ability
pid -= 25; // Switches ability without affecting nature
if (pid == oldpid)
{
pidiv = new PIDIV {NoSeed = true, RNG = RNG.LCRNG, Type = PIDType.Pokewalker};
return true;
}
pidiv = null;
return false;
}
private static PIDIV AnalyzeGB(PKM pk)
{
return null;
}
private static IEnumerable<uint> GetSeedsFromPID(RNG method, uint a, uint b)
{
uint cmp = a << 16;
uint x = b << 16;
for (uint i = 0; i <= 0xFFFF; i++)
{
var seed = x | i;
if ((method.Next(seed) & 0xFFFF0000) == cmp)
yield return method.Prev(seed);
}
}
private static IEnumerable<uint> GetSeedsFromIVs(RNG method, uint a, uint b)
{
uint cmp = a << 16 & 0x7FFF0000;
uint x = b << 16 & 0x7FFF0000;
for (uint i = 0; i <= 0xFFFF; i++)
{
var seed = x | i;
if ((method.Next(seed) & 0x7FFF0000) != cmp)
continue;
var prev = method.Prev(seed);
yield return prev;
yield return prev ^ 0x80000000;
}
}
/// <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,
};
}
internal static int[] GetIVsInt32(uint r1, uint r2)
{
return new[]
{
(int)r1 & 31,
(int)r1 >> 5 & 31,
(int)r1 >> 10 & 31,
(int)r2 & 31,
(int)r2 >> 5 & 31,
(int)r2 >> 10 & 31,
};
}
private static uint GetIVChunk(uint[] IVs, int start)
{
uint val = 0;
for (int i = 0; i < 3; i++)
val |= IVs[i+start] << (5*i);
return val;
}
public static IEnumerable<PIDIV> GetPokeSpotSeeds(PKM pkm, int slot)
{
// Activate (rand % 3)
// Munchlax / Bonsly (10%/30%)
// Encounter Slot Value (ESV) = 50%/35%/15% rarity (0-49, 50-84, 85-99)
var pid = pkm.PID;
var top = pid >> 16;
var bot = pid & 0xFFFF;
var seeds = GetSeedsFromPID(RNG.XDRNG, bot, top);
foreach (var seed in seeds)
{
// check for valid encounter slot info
var esv = (seed>>16)%100;
switch (slot)
{
case 0:
if (esv < 50) break; // valid
continue;
case 1:
if (esv >= 50 && esv < 85) break; // valid
continue;
case 2:
if (esv >= 85) break;
continue;
default:
continue;
}
// check for valid activation
var s = RNG.XDRNG.Prev(seed);
if ((s>>16)%3 != 0)
{
if ((s>>16)%100 < 10) // can't fail a munchlax/bonsly encounter check
continue;
s = RNG.XDRNG.Prev(s);
if ((s>>16)%3 != 0) // can't activate even if generous
continue;
}
yield return new PIDIV {OriginSeed = s, RNG = RNG.XDRNG, Type = PIDType.PokeSpot};
}
}
public static bool IsCompatible3(this PIDType val, IEncounterable encounter, PKM pkm)
{
switch (encounter)
{
case WC3 g:
return val == g.Method;
case EncounterStaticShadow d when d.EReader:
return val == PIDType.None; // All IVs are 0
case EncounterStatic s:
switch (pkm.Version)
{
case (int)GameVersion.CXD: return val == PIDType.CXD;
case (int)GameVersion.E: return val == PIDType.Method_1; // no roamer glitch
case (int)GameVersion.FR:
case (int)GameVersion.LG:
return s.Roaming ? val == PIDType.Method_1_Roamer : val == PIDType.Method_1; // roamer glitch
default: // RS, roamer glitch && RSBox s/w emulation => method 4 available
return s.Roaming ? val == PIDType.Method_1_Roamer : MethodH14.Any(z => z == val);
}
case EncounterSlot w:
if (pkm.Version == 15)
return val == PIDType.PokeSpot;
return (w.Species == 201 ? MethodH_Unown : MethodH).Any(z => z == val);
default:
return val == PIDType.None;
}
}
public static bool IsCompatible4(this PIDType val, IEncounterable encounter, PKM pkm)
{
switch (encounter)
{
case EncounterStatic s:
if (s == Encounters4.SpikyEaredPichu) // nonshiny forced nature, undocumented
return val == PIDType.None;
if (s.Location == 233 && s.Gift)
return val == PIDType.Pokewalker;
return s.Shiny == true ? val == PIDType.ChainShiny : val == PIDType.Method_1;
case EncounterSlot sl:
if (val == PIDType.Method_1)
return true;
if (val == PIDType.CuteCharm)
// Cute charm does not work with swarms pokemon
return sl.Type != SlotType.Swarm;
if (val != PIDType.ChainShiny)
return false;
// Chain shiny with poke radar is only possible in DPPt in tall grass, safari zone do not allow pokeradar
// TypeEncounter TallGrass discard any cave or city
var IsDPPt = GameVersion.DP.Contains((GameVersion)pkm.Version) || (GameVersion)pkm.Version == GameVersion.Pt;
return pkm.IsShiny && IsDPPt && sl.TypeEncounter == EncounterType.TallGrass && !Encounters4.SafariZoneLocation_4.Contains(sl.Location);
case PGT _: // manaphy
return IsG4ManaphyPIDValid(val, pkm);
default:
return val == PIDType.None;
}
}
private static bool IsG4ManaphyPIDValid(PIDType val, PKM pkm)
{
if (pkm.IsEgg)
{
if (pkm.IsShiny)
return false;
if (val == PIDType.Method_1)
return true;
return val == PIDType.G4MGAntiShiny && IsAntiShinyARNG();
}
if (val == PIDType.Method_1)
return pkm.WasTradedEgg || !pkm.IsShiny; // can't be shiny on received game
return val == PIDType.G4MGAntiShiny && (pkm.WasTradedEgg || IsAntiShinyARNG());
bool IsAntiShinyARNG()
{
var shinyPID = RNG.ARNG.Prev(pkm.PID);
return (pkm.TID ^ pkm.SID ^ (shinyPID & 0xFFFF) ^ (shinyPID >> 16)) < 8; // shiny proc
}
}
private static readonly PIDType[] MethodH = { PIDType.Method_1, PIDType.Method_2, PIDType.Method_4 };
private static readonly PIDType[] MethodH14 = { PIDType.Method_1, PIDType.Method_4 };
private static readonly PIDType[] MethodH_Unown = { PIDType.Method_1_Unown, PIDType.Method_2_Unown, PIDType.Method_4_Unown };
}
}