using System.Collections.Generic;
namespace PKHeX.Core;
///
/// Logic for finding the RNG criteria pattern in Colosseum/XD.
///
public static class LockFinder
{
///
/// Checks if the encounter template can be obtained with the resulting PID and IV detail.
///
/// Encounter template with lock info
/// RNG result PID and IV seed state
/// Entity to check
/// True if all valid.
public static bool IsAllShadowLockValid(EncounterStaticShadow s, PIDIV pv, PKM pk)
{
if (s.Version == GameVersion.XD && pk.IsShiny)
return false; // no xd shiny shadow mons
var teams = s.Locks;
if (teams.Length == 0)
return true;
var tsv = s.Version == GameVersion.XD ? (pk.TID ^ pk.SID) >> 3 : -1; // no xd shiny shadow mons
return IsAllShadowLockValid(pv, teams, tsv);
}
///
/// Checks all to see if they can be reversed from the .
///
/// RNG result PID and IV seed state
/// Possible team data setups the NPC trainer has that need to generate before the shadow.
/// Trainer shiny value that is disallowed in XD
public static bool IsAllShadowLockValid(PIDIV pv, IEnumerable teams, int tsv = -1)
{
foreach (var t in teams)
{
var result = new TeamLockResult(t, pv.OriginSeed, tsv);
if (result.Valid)
return true;
}
return false;
}
///
/// Checks if the XD starter Eevee can be obtained with the trainer's IDs.
///
/// Seed that generated the PID and IV
/// Trainer ID
/// Trainer Secret ID
/// True if the starter ID correlation is correct
public static bool IsXDStarterValid(uint seed, int TID, int SID)
{
// pidiv reversed 2x yields SID, 3x yields TID. shift by 7 if another PKM is generated prior
var SIDf = RNG.XDRNG.Reverse(seed, 2);
var TIDf = RNG.XDRNG.Prev(SIDf);
return SIDf >> 16 == SID && TIDf >> 16 == TID;
}
///
/// Checks if the Colosseum starter correlation can be obtained with the trainer's IDs.
///
/// Species of the starter, to indicate Espeon vs Umbreon
/// Seed the PID/IV is generated with
/// Trainer ID of the trainer
/// Secret ID of the trainer
/// PID of the entity
/// First 3 IVs combined
/// Last 3 IVs combined
public static bool IsColoStarterValid(ushort species, ref uint seed, int TID, int SID, uint pkPID, uint IV1, uint IV2)
{
// reverse the seed the bare minimum
int rev = 2;
if (species == (int)Species.Espeon)
rev += 7;
// reverse until we find the TID/SID, then run the generation forward to see if it matches our inputs.
var rng = RNG.XDRNG;
var SIDf = rng.Reverse(seed, rev);
int ctr = 0;
uint temp;
while ((temp = rng.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID)
{
SIDf = temp;
if (ctr > 32) // arbitrary
return false;
ctr++;
}
var next = rng.Next(SIDf);
// generate Umbreon
var PIDIV = GenerateValidColoStarterPID(ref next, TID, SID);
if (species == (int)Species.Espeon) // need Espeon, which is immediately next
PIDIV = GenerateValidColoStarterPID(ref next, TID, SID);
if (!PIDIV.Equals(pkPID, IV1, IV2))
return false;
seed = rng.Reverse(SIDf, 2);
return true;
}
private readonly record struct PIDIVGroup(uint PID, uint IV1, uint IV2)
{
public bool Equals(uint pid, uint iv1, uint iv2) => PID == pid && IV1 == iv1 && IV2 == iv2;
}
private static PIDIVGroup GenerateValidColoStarterPID(ref uint uSeed, int TID, int SID)
{
var rng = RNG.XDRNG;
uSeed = rng.Advance(uSeed, 2); // skip fakePID
var IV1 = (uSeed >> 16) & 0x7FFF;
uSeed = rng.Next(uSeed);
var IV2 = (uSeed >> 16) & 0x7FFF;
uSeed = rng.Next(uSeed);
uSeed = rng.Advance(uSeed, 1); // skip ability call
var PID = GenerateStarterPID(ref uSeed, TID, SID);
uSeed = rng.Advance(uSeed, 2); // PID calls consumed
return new PIDIVGroup(PID, IV1, IV2);
}
private static bool IsShiny(int TID, int SID, uint PID) => (TID ^ SID ^ (PID >> 16) ^ (PID & 0xFFFF)) < 8;
private static uint GenerateStarterPID(ref uint uSeed, int TID, int SID)
{
uint PID;
const byte ratio = 0x1F; // 12.5% F (can't be female)
while (true)
{
var next = RNG.XDRNG.Next(uSeed);
PID = (uSeed & 0xFFFF0000) | (next >> 16);
if ((PID & 0xFF) >= ratio && !IsShiny(TID, SID, PID))
break;
uSeed = RNG.XDRNG.Next(next);
}
return PID;
}
}