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.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 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 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; } } /// /// 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, }; } 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 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 }; } }