Refactor RNG advance/reverse methods (#3579)

The new LCRNG/GCRNG/ARNG classes are static, rather than singletons. Allows them to be inlined much better.
This commit is contained in:
Kurt 2022-09-04 12:03:37 -07:00 committed by GitHub
parent d9ad0052a1
commit 92a50264cc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 974 additions and 639 deletions

View file

@ -1,3 +1,4 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -46,12 +47,9 @@ public static class EncounterGenerator3
{ {
if (z is EncounterSlot3PokeSpot w) if (z is EncounterSlot3PokeSpot w)
{ {
var seeds = MethodFinder.GetPokeSpotSeeds(pk, w.SlotNumber); var pidiv = MethodFinder.GetPokeSpotSeedFirst(pk, w.SlotNumber);
foreach (var s in seeds) if (pidiv.Type == PIDType.PokeSpot)
{ info.PIDIV = pidiv;
info.PIDIV = s;
break;
}
} }
else if (z is EncounterStaticShadow s) else if (z is EncounterStaticShadow s)
{ {
@ -229,12 +227,15 @@ public static class EncounterGenerator3
return LockFinder.IsAllShadowLockValid(s, info.PIDIV, pk); return LockFinder.IsAllShadowLockValid(s, info.PIDIV, pk);
// E-Reader have fixed IVs, and aren't recognized as CXD (no PID-IV correlation). // E-Reader have fixed IVs, and aren't recognized as CXD (no PID-IV correlation).
var possible = MethodFinder.GetColoEReaderMatches(pk.EncryptionConstant); Span<uint> seeds = stackalloc uint[4];
foreach (var poss in possible) var count = XDRNG.GetSeeds(seeds, pk.EncryptionConstant);
var xdc = seeds[..count];
foreach (var seed in xdc)
{ {
if (!LockFinder.IsAllShadowLockValid(s, poss, pk)) var pidiv = new PIDIV(PIDType.CXD, XDRNG.Next4(seed));
if (!LockFinder.IsAllShadowLockValid(s, pidiv, pk))
continue; continue;
info.PIDIV = poss; info.PIDIV = pidiv;
return true; return true;
} }

View file

@ -0,0 +1,66 @@
using System.Runtime.CompilerServices;
namespace PKHeX.Core;
/// <summary>
/// 32 Bit Linear Congruential Random Number Generator
/// </summary>
/// <remarks>Frame advancement for forward and reverse.
/// <br>
/// https://en.wikipedia.org/wiki/Linear_congruential_generator
/// </br>
/// <br>
/// seed_n+1 = seed_n * <see cref="Mult"/> + <see cref="Add"/>
/// </br>
/// </remarks>
public static class ARNG
{
// Forward and reverse constants
public const uint Mult = 0x6C078965;
public const uint Add = 0x00000001;
public const uint rMult = 0x9638806D;
public const uint rAdd = 0x69C77F93;
/// <summary>
/// Advances the RNG seed to the next state value.
/// </summary>
/// <param name="seed">Current seed</param>
/// <returns>Seed advanced a single time.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Next(uint seed) => (seed * Mult) + Add;
/// <summary>
/// Reverses the RNG seed to the previous state value.
/// </summary>
/// <param name="seed">Current seed</param>
/// <returns>Seed reversed a single time.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Prev(uint seed) => (seed * rMult) + rAdd;
/// <summary>
/// Advances the RNG seed to the next state value a specified amount of times.
/// </summary>
/// <param name="seed">Current seed</param>
/// <param name="frames">Amount of times to advance.</param>
/// <returns>Seed advanced the specified amount of times.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Advance(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
seed = Next(seed);
return seed;
}
/// <summary>
/// Reverses the RNG seed to the previous state value a specified amount of times.
/// </summary>
/// <param name="seed">Current seed</param>
/// <param name="frames">Amount of times to reverse.</param>
/// <returns>Seed reversed the specified amount of times.</returns>
public static uint Reverse(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
seed = Prev(seed);
return seed;
}
}

View file

@ -1,4 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -13,39 +13,76 @@ namespace PKHeX.Core;
/// seed_n+1 = seed_n * <see cref="Mult"/> + <see cref="Add"/> /// seed_n+1 = seed_n * <see cref="Mult"/> + <see cref="Add"/>
/// </br> /// </br>
/// </remarks> /// </remarks>
public class LCRNG public static class LCRNG
{ {
// Forward // Forward and reverse constants
protected readonly uint Mult; public const uint Mult = 0x41C64E6D;
private readonly uint Add; public const uint Add = 0x00006073;
public const uint rMult = 0xEEB9EB65;
public const uint rAdd = 0x0A3561A1;
// Reverse private const uint Mult2 = unchecked(Mult * Mult); // 0xC2A29A69
private readonly uint rMult; private const uint rMult2 = unchecked(rMult * rMult); // 0xDC6C95D9
private readonly uint rAdd; private const uint Add2 = unchecked(Add * (Mult + 1)); // 0xE97E7B6A
private const uint rAdd2 = unchecked(rAdd * (rMult + 1)); // 0x4D3CB126
public LCRNG(uint f_mult, uint f_add, uint r_mult, uint r_add) private const uint Mult3 = unchecked(Mult2 * Mult); // 0x807DBCB5
{ private const uint rMult3 = unchecked(rMult2 * rMult); // 0xAC36519D
Mult = f_mult; private const uint Add3 = unchecked((Add2 * Mult) + Add); // 0x52713895
Add = f_add; private const uint rAdd3 = unchecked((rAdd2 * rMult) + rAdd);// 0x923B279F
rMult = r_mult;
rAdd = r_add;
}
/// <summary> private const uint Mult4 = unchecked(Mult3 * Mult); // 0xEE067F11
/// Advances the RNG seed to the next state value. private const uint rMult4 = unchecked(rMult3 * rMult); // 0xBECE51F1
/// </summary> private const uint Add4 = unchecked((Add3 * Mult) + Add); // 0x31B0DDE4
/// <param name="seed">Current seed</param> private const uint rAdd4 = unchecked((rAdd3 * rMult) + rAdd);// 0x7CD1F85C
/// <returns>Seed advanced a single time.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Next(uint seed) => (seed * Mult) + Add;
/// <summary> private const uint Mult5 = unchecked(Mult4 * Mult); // 0xEBA1483D
/// Reverses the RNG seed to the previous state value. private const uint rMult5 = unchecked(rMult4 * rMult); // 0xF1C78F15
/// </summary> private const uint Add5 = unchecked((Add4 * Mult) + Add); // 0x8E425287
/// <param name="seed">Current seed</param> private const uint rAdd5 = unchecked((rAdd4 * rMult) + rAdd);// 0x0A84D1ED
/// <returns>Seed reversed a single time.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] private const uint Mult6 = unchecked(Mult5 * Mult); // 0xD3DC57F9
public uint Prev(uint seed) => (seed * rMult) + rAdd; private const uint rMult6 = unchecked(rMult5 * rMult); // 0x8040BA49
private const uint Add6 = unchecked((Add5 * Mult) + Add); // 0xE2CCA5EE
private const uint rAdd6 = unchecked((rAdd5 * rMult) + rAdd);// 0x2795C322
private const uint Mult7 = unchecked(Mult6 * Mult); // 0x9B355305
private const uint rMult7 = unchecked(rMult6 * rMult); // 0x814B81CD
private const uint Add7 = unchecked((Add6 * Mult) + Add); // 0xAFC58AC9
private const uint rAdd7 = unchecked((rAdd6 * rMult) + rAdd);// 0xC1FD940B
private const uint Mult8 = unchecked(Mult7 * Mult); // 0xCFDDDF21
private const uint rMult8 = unchecked(rMult7 * rMult); // 0xB61664E1
private const uint Add8 = unchecked((Add7 * Mult) + Add); // 0x67DBB608
private const uint rAdd8 = unchecked((rAdd7 * rMult) + rAdd);// 0x9019E2F8
private const uint Mult9 = unchecked(Mult8 * Mult); // 0xFFA0F0DU
private const uint rMult9 = unchecked(rMult8 * rMult); // 0x7A0957C5
private const uint Add9 = unchecked((Add8 * Mult) + Add); // 0xFC3351DB
private const uint rAdd9 = unchecked((rAdd8 * rMult) + rAdd);// 0x3CFD9579
public const int MaxCountSeedsPID = 3;
public const int MaxCountSeedsIV = 6;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next (uint seed) => (seed * Mult ) + Add ;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next2(uint seed) => (seed * Mult2) + Add2;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next3(uint seed) => (seed * Mult3) + Add3;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next4(uint seed) => (seed * Mult4) + Add5;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next5(uint seed) => (seed * Mult5) + Add5;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next6(uint seed) => (seed * Mult6) + Add6;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next7(uint seed) => (seed * Mult7) + Add7;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next8(uint seed) => (seed * Mult8) + Add8;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next9(uint seed) => (seed * Mult9) + Add9;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev (uint seed) => (seed * rMult ) + rAdd ;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev2(uint seed) => (seed * rMult2) + rAdd2;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev3(uint seed) => (seed * rMult3) + rAdd3;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev4(uint seed) => (seed * rMult4) + rAdd4;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev5(uint seed) => (seed * rMult5) + rAdd5;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev6(uint seed) => (seed * rMult6) + rAdd6;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev7(uint seed) => (seed * rMult7) + rAdd7;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev8(uint seed) => (seed * rMult8) + rAdd8;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev9(uint seed) => (seed * rMult9) + rAdd9;
/// <summary> /// <summary>
/// Advances the RNG seed to the next state value a specified amount of times. /// Advances the RNG seed to the next state value a specified amount of times.
@ -54,7 +91,7 @@ public class LCRNG
/// <param name="frames">Amount of times to advance.</param> /// <param name="frames">Amount of times to advance.</param>
/// <returns>Seed advanced the specified amount of times.</returns> /// <returns>Seed advanced the specified amount of times.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
public uint Advance(uint seed, int frames) public static uint Advance(uint seed, int frames)
{ {
for (int i = 0; i < frames; i++) for (int i = 0; i < frames; i++)
seed = Next(seed); seed = Next(seed);
@ -67,8 +104,7 @@ public class LCRNG
/// <param name="seed">Current seed</param> /// <param name="seed">Current seed</param>
/// <param name="frames">Amount of times to reverse.</param> /// <param name="frames">Amount of times to reverse.</param>
/// <returns>Seed reversed the specified amount of times.</returns> /// <returns>Seed reversed the specified amount of times.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Reverse(uint seed, int frames)
public uint Reverse(uint seed, int frames)
{ {
for (int i = 0; i < frames; i++) for (int i = 0; i < frames; i++)
seed = Prev(seed); seed = Prev(seed);

View file

@ -0,0 +1,148 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Seed reversal logic for the <see cref="LCRNG"/> algorithm.
/// </summary>
/// <remarks>
/// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16
/// flag/2^8 tables are precomputed and constant (unrelated to rand pairs)
/// https://crypto.stackexchange.com/a/10609
/// </remarks>
public static class LCRNGReversal
{
// Bruteforce cache for searching seeds
private const int cacheSize = 1 << 16;
private static readonly byte[] low8 = new byte[cacheSize];
private static readonly bool[] flags = new bool[cacheSize];
private const uint Mult = LCRNG.Mult;
private const uint Add = LCRNG.Add;
private const uint k2 = Mult << 8;
static LCRNGReversal()
{
// Populate Meet Middle Arrays
var f = flags;
var b = low8;
for (uint i = 0; i <= byte.MaxValue; i++)
{
// the second rand() also has 16 bits that aren't known. It is a 16 bit value added to either side.
// to consider these bits and their impact, they can at most increment/decrement the result by 1.
// with the current calc setup, the search loop's calculated value may be -1 (loop does subtraction)
// since LCGs are linear (hence the name), there's no values in adjacent cells. (no collisions)
// if we mark the prior adjacent cell, we eliminate the need to check flags twice on each loop.
uint right = (Mult * i) + Add;
ushort val = (ushort)(right >> 16);
f[val] = true; b[val] = (byte)i;
--val;
f[val] = true; b[val] = (byte)i;
// now the search only has to access the flags array once per loop.
}
}
/// <summary>
/// Finds all seeds that can generate the <see cref="pid"/> by two successive rand() calls.
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="pid">PID to be reversed into seeds that generate it.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint pid)
{
uint first = pid << 16;
uint second = pid & 0xFFFF_0000;
return GetSeeds(result, first, second);
}
/// <summary>
/// Finds all seeds that can generate the IVs by two successive rand() calls.
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="hp" >Entity IV for HP</param>
/// <param name="atk">Entity IV for Attack</param>
/// <param name="def">Entity IV for Defense</param>
/// <param name="spa">Entity IV for Special Attack</param>
/// <param name="spd">Entity IV for Special Defense</param>
/// <param name="spe">Entity IV for Speed</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeedsIVs(Span<uint> result, uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
{
uint first = (hp | (atk << 5) | (def << 10)) << 16;
uint second = (spe | (spa << 5) | (spd << 10)) << 16;
return GetSeedsIVs(result, first, second);
}
/// <summary>
/// Finds all the origin seeds for two 16 bit rand() calls
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 16 bits, already shifted left 16 bits.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint first, uint second)
{
int ctr = 0;
uint k1 = second - (first * Mult);
for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2)
{
ushort val = (ushort)(k3 >> 16);
if (!flags[val])
continue;
// Verify PID calls line up
var seed = first | (i << 8) | low8[val];
var next = LCRNG.Next(seed);
if ((next & 0xFFFF0000) == second)
result[ctr++] = LCRNG.Prev(seed);
}
return ctr;
}
/// <summary>
/// Finds all the origin seeds for two 15 bit rand() calls
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="first">First rand() call, 15 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 15 bits, already shifted left 16 bits.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeedsIVs(Span<uint> result, uint first, uint second)
{
int ctr = 0;
// Check with the top bit of the first call both
// flipped and unflipped to account for only knowing 15 bits
uint search1 = second - (first * Mult);
uint search2 = second - ((first ^ 0x80000000) * Mult);
for (uint i = 0; i <= 255; i++, search1 -= k2, search2 -= k2)
{
uint val = (ushort)(search1 >> 16);
if (flags[val])
{
// Verify PID calls line up
var seed = first | (i << 8) | low8[val];
var next = LCRNG.Next(seed);
if ((next & 0x7FFF0000) == second)
{
var origin = LCRNG.Prev(seed);
result[ctr++] = origin;
result[ctr++] = origin ^ 0x80000000;
}
}
val = (ushort)(search2 >> 16);
if (flags[val])
{
// Verify PID calls line up
var seed = first | (i << 8) | low8[val];
var next = LCRNG.Next(seed);
if ((next & 0x7FFF0000) == second)
{
var origin = LCRNG.Prev(seed);
result[ctr++] = origin;
result[ctr++] = origin ^ 0x80000000;
}
}
}
return ctr;
}
}

View file

@ -0,0 +1,147 @@
using System;
namespace PKHeX.Core;
/// <summary>
/// Seed reversal logic for the <see cref="LCRNG"/> algorithm, with a gap in between the two observed rand() results.
/// </summary>
/// <remarks>
/// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16
/// flag/2^8 tables are precomputed and constant (unrelated to rand pairs)
/// https://crypto.stackexchange.com/a/10609
/// </remarks>
public static class LCRNGReversalSkip
{
// Bruteforce cache for searching seeds
private const int cacheSize = 1 << 16;
private static readonly byte[] low8 = new byte[cacheSize];
private static readonly bool[] flags = new bool[cacheSize];
private const uint Mult = unchecked(LCRNG.Mult * LCRNG.Mult); // 0xC2A29A69
private const uint Add = unchecked(LCRNG.Add * (LCRNG.Mult + 1)); // 0xE97E7B6A
private const uint k2 = Mult << 8;
static LCRNGReversalSkip()
{
// Populate Meet Middle Arrays
var f = flags;
var b = low8;
for (uint i = 0; i <= byte.MaxValue; i++)
{
// the second rand() also has 16 bits that aren't known. It is a 16 bit value added to either side.
// to consider these bits and their impact, they can at most increment/decrement the result by 1.
// with the current calc setup, the search loop's calculated value may be -1 (loop does subtraction)
// since LCGs are linear (hence the name), there's no values in adjacent cells. (no collisions)
// if we mark the prior adjacent cell, we eliminate the need to check flags twice on each loop.
uint right = (Mult * i) + Add;
ushort val = (ushort)(right >> 16);
f[val] = true; b[val] = (byte)i;
--val;
f[val] = true; b[val] = (byte)i;
// now the search only has to access the flags array once per loop.
}
}
/// <summary>
/// Finds all seeds that can generate the <see cref="pid"/> with a discarded rand() between the two halves.
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="pid">PID to be reversed into seeds that generate it.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint pid)
{
uint first = pid << 16;
uint second = pid & 0xFFFF_0000;
return GetSeeds(result, first, second);
}
/// <summary>
/// Finds all seeds that can generate the IVs, with a vblank skip between the two IV rand() calls.
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="hp" >Entity IV for HP</param>
/// <param name="atk">Entity IV for Attack</param>
/// <param name="def">Entity IV for Defense</param>
/// <param name="spa">Entity IV for Special Attack</param>
/// <param name="spd">Entity IV for Special Defense</param>
/// <param name="spe">Entity IV for Speed</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeedsIVs(Span<uint> result, uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
{
uint first = (hp | (atk << 5) | (def << 10)) << 16;
uint second = (spe | (spa << 5) | (spd << 10)) << 16;
return GetSeedsIVs(result, first, second);
}
/// <summary>
/// Finds all the origin seeds for two 16 bit rand() calls (ignoring a rand() in between)
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="third">Third rand() call, 16 bits, already shifted left 16 bits.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint first, uint third)
{
int ctr = 0;
uint search = third - (first * Mult);
for (uint i = 0; i <= 255; ++i, search -= k2)
{
ushort val = (ushort)(search >> 16);
if (flags[val])
{
// Verify PID calls line up
var seed = first | (i << 8) | low8[val];
var next = LCRNG.Next2(seed);
if ((next & 0xFFFF0000) == third)
result[ctr++] = LCRNG.Prev(seed);
}
}
return ctr;
}
/// <summary>
/// Finds all the origin seeds for two 15 bit rand() calls (ignoring a rand() in between)
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="first">First rand() call, 15 bits, already shifted left 16 bits.</param>
/// <param name="third">Third rand() call, 15 bits, already shifted left 16 bits.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeedsIVs(Span<uint> result, uint first, uint third)
{
int ctr = 0;
uint search1 = third - (first * Mult);
uint search3 = third - ((first ^ 0x80000000) * Mult);
for (uint i = 0; i <= 255; i++, search1 -= k2, search3 -= k2)
{
uint val = (ushort)(search1 >> 16);
if (flags[val])
{
// Verify PID calls line up
var seed = first | (i << 8) | low8[val];
var next = LCRNG.Next2(seed);
if ((next & 0x7FFF0000) == third)
{
var origin = LCRNG.Prev(seed);
result[ctr++] = origin;
result[ctr++] = origin ^ 0x80000000;
}
}
val = (ushort)(search3 >> 16);
if (flags[val])
{
// Verify PID calls line up
var seed = first | (i << 8) | low8[val];
var next = LCRNG.Next2(seed);
if ((next & 0x7FFF0000) == third)
{
var origin = LCRNG.Prev(seed);
result[ctr++] = origin;
result[ctr++] = origin ^ 0x80000000;
}
}
}
return ctr;
}
}

View file

@ -1,187 +0,0 @@
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace PKHeX.Core;
/// <summary>
/// <inheritdoc cref="Core.LCRNG"/>
/// </summary>
/// <remarks>
/// <inheritdoc cref="Core.LCRNG"/>
/// <br>
/// Provides common RNG algorithms used by Generation 3 &amp; 4.
/// This class has extra logic (tuned for performance) that can be used to find the original state(s) based on a limited amount of observed results.
/// Refer to the documentation for those methods.
/// </br>
/// </remarks>
public sealed class RNG : LCRNG
{
/// <summary> LCRNG used for Encryption and mainline game RNG calls. </summary>
public static readonly RNG LCRNG = new(0x41C64E6D, 0x00006073, 0xEEB9EB65, 0x0A3561A1);
/// <summary> LCRNG used by Colosseum & XD for game RNG calls. </summary>
public static readonly RNG XDRNG = new(0x000343FD, 0x00269EC3, 0xB9B33155, 0xA170F641);
/// <summary> Alternate LCRNG used by mainline game RNG calls to disassociate the seed from the <see cref="LCRNG"/>, for anti-shiny and other purposes. </summary>
public static readonly LCRNG ARNG = new(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93);
#region Seed Reversal Logic
// Bruteforce cache for searching seeds
private const int cacheSize = 1 << 16;
// 1,2 (no gap)
private readonly uint k2; // Mult<<8
private readonly byte[] low8 = new byte[cacheSize];
private readonly bool[] flags = new bool[cacheSize];
// 1,3 (single gap)
private readonly uint k0g; // Mult*Mult
private readonly uint k2s; // Mult*Mult<<8
private readonly byte[] g_low8 = new byte[cacheSize];
private readonly bool[] g_flags = new bool[cacheSize];
// Euclidean division approach
private readonly long t0; // Add - 0xFFFF
private readonly long t1; // 0xFFFF * ((long)Mult + 1)
#endregion
private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add) : base(f_mult, f_add, r_mult, r_add)
{
// Set up bruteforce utility
k2 = f_mult << 8;
k0g = f_mult * f_mult;
k2s = k0g << 8;
// Populate Meet Middle Arrays
uint k4g = f_add * (f_mult + 1); // 1,3's multiplier
for (uint i = 0; i <= byte.MaxValue; i++)
{
SetFlagData(i, f_mult, f_add, flags, low8); // 1,2
SetFlagData(i, k0g, k4g, g_flags, g_low8); // 1,3
}
t0 = f_add - 0xFFFFU;
t1 = 0xFFFFL * ((long) f_mult + 1);
}
#region Initialization
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void SetFlagData(uint i, uint mult, uint add, bool[] f, byte[] v)
{
// the second rand() also has 16 bits that aren't known. It is a 16 bit value added to either side.
// to consider these bits and their impact, they can at most increment/decrement the result by 1.
// with the current calc setup, the search loop's calculated value may be -1 (loop does subtraction)
// since LCGs are linear (hence the name), there's no values in adjacent cells. (no collisions)
// if we mark the prior adjacent cell, we eliminate the need to check flags twice on each loop.
uint right = (mult * i) + add;
ushort val = (ushort) (right >> 16);
f[val] = true; v[val] = (byte)i;
--val;
f[val] = true; v[val] = (byte)i;
// now the search only has to access the flags array once per loop.
}
#endregion
/// <summary>
/// Gets the origin seeds for two successive 16 bit rand() calls using a meet-in-the-middle approach.
/// </summary>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 16 bits, already shifted left 16 bits.</param>
/// <remarks>
/// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16
/// flag/2^8 tables are precomputed and constant (unrelated to rand pairs)
/// https://crypto.stackexchange.com/a/10609
/// </remarks>
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IEnumerable<uint> RecoverLower16Bits(uint first, uint second)
{
uint k1 = second - (first * Mult);
for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2)
{
ushort val = (ushort)(k3 >> 16);
if (flags[val])
yield return Prev(first | (i << 8) | low8[val]);
}
}
/// <summary>
/// Gets the origin seeds for two 16 bit rand() calls (ignoring a rand() in between) using a meet-in-the-middle approach.
/// </summary>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="third">Third rand() call, 16 bits, already shifted left 16 bits.</param>
/// <remarks>
/// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16
/// flag/2^8 tables are precomputed and constant (unrelated to rand pairs)
/// https://crypto.stackexchange.com/a/10609
/// </remarks>
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IEnumerable<uint> RecoverLower16BitsGap(uint first, uint third)
{
uint k1 = third - (first * k0g);
for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2s)
{
ushort val = (ushort)(k3 >> 16);
if (g_flags[val])
yield return Prev(first | (i << 8) | g_low8[val]);
}
}
/// <summary>
/// Gets the origin seeds for two successive 16 bit rand() calls using a Euclidean division approach.
/// </summary>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 16 bits, already shifted left 16 bits.</param>
/// <remarks>
/// For favorable multiplier values, this k_max gives a search space less than 2^8 (meet-in-the-middle)
/// For the programmed methods in this program, it is only advantageous to use this with <see cref="XDRNG"/>.
/// https://crypto.stackexchange.com/a/10629
/// </remarks>
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IEnumerable<uint> RecoverLower16BitsEuclid16(uint first, uint second)
{
const int bitshift = 32;
const long inc = 1L << bitshift;
return GetPossibleSeedsEuclid(first, second, bitshift, inc);
}
/// <summary>
/// Gets the origin seeds for two successive 15 bit rand() calls using a Euclidean division approach.
/// </summary>
/// <param name="first">First rand() call, 15 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 15 bits, already shifted left 16 bits.</param>
/// <remarks>
/// Calculate the quotient of the Euclidean division (k_max) attack to reduce the search space.
/// For favorable multiplier values, this k_max gives a search space less than 2^8 (meet-in-the-middle)
/// For the programmed methods in this program, it is only advantageous to use this with <see cref="XDRNG"/>.
/// https://crypto.stackexchange.com/a/10629
/// </remarks>
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal IEnumerable<uint> RecoverLower16BitsEuclid15(uint first, uint second)
{
const int bitshift = 31;
const long inc = 1L << bitshift;
return GetPossibleSeedsEuclid(first, second, bitshift, inc);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private IEnumerable<uint> GetPossibleSeedsEuclid(uint first, uint second, int bitshift, long inc)
{
long t = second - (Mult * first) - t0;
long kmax = (((t1 - t) >> bitshift) << bitshift) + t;
for (long k = t; k <= kmax; k += inc)
{
// compute modulo in steps for reuse in yielded value (x % Mult)
long fix = k / Mult;
long remainder = k - (Mult * fix);
if (remainder >> 16 == 0)
yield return Prev(first | (uint) fix);
}
}
}

View file

@ -1,44 +0,0 @@
using System;
using System.Runtime.CompilerServices;
namespace PKHeX.Core;
public static class RNGUtil
{
/// <summary>
/// Generates an IV for each RNG call using the top 5 bits of frame seeds.
/// </summary>
/// <param name="rng">RNG to use</param>
/// <param name="seed">RNG seed</param>
/// <param name="IVs">Expected IVs</param>
/// <returns>True if all match.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetSequentialIVsUInt32(this LCRNG rng, uint seed, ReadOnlySpan<uint> IVs)
{
foreach (var iv in IVs)
{
seed = rng.Next(seed);
var IV = seed >> 27;
if (IV != iv)
return false;
}
return true;
}
/// <summary>
/// Generates an IV for each RNG call using the top 5 bits of frame seeds.
/// </summary>
/// <param name="rng">RNG to use</param>
/// <param name="seed">RNG seed</param>
/// <param name="ivs">Buffer to store generated values</param>
/// <returns>Array of 6 IVs as <see cref="int"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GetSequentialIVsInt32(this LCRNG rng, uint seed, Span<int> ivs)
{
for (int i = 0; i < ivs.Length; i++)
{
seed = rng.Next(seed);
ivs[i] = (int)(seed >> 27);
}
}
}

View file

@ -0,0 +1,235 @@
using System;
using System.Runtime.CompilerServices;
namespace PKHeX.Core;
/// <summary>
/// 32 Bit Linear Congruential Random Number Generator
/// </summary>
/// <remarks>Frame advancement for forward and reverse.
/// <br>
/// https://en.wikipedia.org/wiki/Linear_congruential_generator
/// </br>
/// <br>
/// seed_n+1 = seed_n * <see cref="Mult"/> + <see cref="Add"/>
/// </br>
/// </remarks>
public static class XDRNG
{
// Forward and reverse constants
public const uint Mult = 0x000343FD;
public const uint Add = 0x00269EC3;
public const uint rMult = 0xB9B33155;
public const uint rAdd = 0xA170F641;
private const uint Mult2 = unchecked(Mult * Mult); // 0xA9FC6809
private const uint rMult2 = unchecked(rMult * rMult); // 0xE05FA639
private const uint Add2 = unchecked(Add * (Mult + 1)); // 0x1E278E7A
private const uint rAdd2 = unchecked(rAdd * (rMult + 1)); // 0x03882AD6
private const uint Mult3 = unchecked(Mult2 * Mult); // 0x45C82BE5
private const uint rMult3 = unchecked(rMult2 * rMult); // 0x396E19ED
private const uint Add3 = unchecked((Add2 * Mult) + Add); // 0xD2F65B55
private const uint rAdd3 = unchecked((rAdd2 * rMult) + rAdd);// 0x777C254F
private const uint Mult4 = unchecked(Mult3 * Mult); // 0xDDFF5051
private const uint rMult4 = unchecked(rMult3 * rMult); // 0x8A3BF8B1
private const uint Add4 = unchecked((Add3 * Mult) + Add); // 0x098520C4
private const uint rAdd4 = unchecked((rAdd3 * rMult) + rAdd);// 0x3E0A787C
private const uint Mult5 = unchecked(Mult4 * Mult); // 0x284A930D
private const uint rMult5 = unchecked(rMult4 * rMult); // 0x2D4673C5
private const uint Add5 = unchecked((Add4 * Mult) + Add); // 0xA2974C77
private const uint rAdd5 = unchecked((rAdd4 * rMult) + rAdd);// 0x16AEB36D
private const uint Mult6 = unchecked(Mult5 * Mult); // 0x0F56BAD9
private const uint rMult6 = unchecked(rMult5 * rMult); // 0xD44C2569
private const uint Add6 = unchecked((Add5 * Mult) + Add); // 0x2E15555E
private const uint rAdd6 = unchecked((rAdd5 * rMult) + rAdd);// 0xD4016672
private const uint Mult7 = unchecked(Mult6 * Mult); // 0x0C287375
private const uint rMult7 = unchecked(rMult6 * rMult); // 0x19DC84DD
private const uint Add7 = unchecked((Add6 * Mult) + Add); // 0x20AD96A9
private const uint rAdd7 = unchecked((rAdd6 * rMult) + rAdd);// 0x4E39CC1B
private const uint Mult8 = unchecked(Mult7 * Mult); // 0xF490B9A1
private const uint rMult8 = unchecked(rMult7 * rMult); // 0x672D6A61
private const uint Add8 = unchecked((Add7 * Mult) + Add); // 0x7E1DBEC8
private const uint rAdd8 = unchecked((rAdd7 * rMult) + rAdd);// 0xE493E638
private const uint Mult9 = unchecked(Mult8 * Mult); // 0xC07F971D
private const uint rMult9 = unchecked(rMult8 * rMult); // 0x6E43E335
private const uint Add9 = unchecked((Add8 * Mult) + Add); // 0xA8D2826B
private const uint rAdd9 = unchecked((rAdd8 * rMult) + rAdd);// 0x46C51ED9
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next (uint seed) => (seed * Mult ) + Add ;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next2(uint seed) => (seed * Mult2) + Add2;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next3(uint seed) => (seed * Mult3) + Add3;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next4(uint seed) => (seed * Mult4) + Add4;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next5(uint seed) => (seed * Mult5) + Add5;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next6(uint seed) => (seed * Mult6) + Add6;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next7(uint seed) => (seed * Mult7) + Add7;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next8(uint seed) => (seed * Mult8) + Add8;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Next9(uint seed) => (seed * Mult9) + Add9;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev (uint seed) => (seed * rMult ) + rAdd ;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev2(uint seed) => (seed * rMult2) + rAdd2;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev3(uint seed) => (seed * rMult3) + rAdd3;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev4(uint seed) => (seed * rMult4) + rAdd4;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev5(uint seed) => (seed * rMult5) + rAdd5;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev6(uint seed) => (seed * rMult6) + rAdd6;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev7(uint seed) => (seed * rMult7) + rAdd7;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev8(uint seed) => (seed * rMult8) + rAdd8;
[MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev9(uint seed) => (seed * rMult9) + rAdd9;
/// <summary>
/// Advances the RNG seed to the next state value a specified amount of times.
/// </summary>
/// <param name="seed">Current seed</param>
/// <param name="frames">Amount of times to advance.</param>
/// <returns>Seed advanced the specified amount of times.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Advance(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
seed = Next(seed);
return seed;
}
/// <summary>
/// Reverses the RNG seed to the previous state value a specified amount of times.
/// </summary>
/// <param name="seed">Current seed</param>
/// <param name="frames">Amount of times to reverse.</param>
/// <returns>Seed reversed the specified amount of times.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint Reverse(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
seed = Prev(seed);
return seed;
}
/// <summary>
/// Generates an IV for each RNG call using the top 5 bits of frame seeds.
/// </summary>
/// <param name="seed">RNG seed</param>
/// <param name="IVs">Expected IVs</param>
/// <returns>True if all match.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static bool GetSequentialIVsUInt32(uint seed, ReadOnlySpan<uint> IVs)
{
foreach (var iv in IVs)
{
seed = Next(seed);
var expect = seed >> 27;
if (iv != expect)
return false;
}
return true;
}
/// <summary>
/// Generates an IV for each RNG call using the top 5 bits of frame seeds.
/// </summary>
/// <param name="seed">RNG seed</param>
/// <param name="ivs">Buffer to store generated values</param>
/// <returns>Array of 6 IVs as <see cref="int"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void GetSequentialIVsInt32(uint seed, Span<int> ivs)
{
for (int i = 0; i < ivs.Length; i++)
{
seed = Next(seed);
ivs[i] = (int)(seed >> 27);
}
}
// By abusing the innate properties of a LCG, we can calculate the seed from a known result.
// https://crypto.stackexchange.com/questions/10608/how-to-attack-a-fixed-lcg-with-partial-output/10629#10629
// Unlike our LCRNG implementation, `k` is small enough (max = 7).
// Instead of using yield and iterators, we calculate all results in a tight loop and return the count found.
public const int MaxCountSeedsPID = 2;
public const int MaxCountSeedsIV = 6;
// Euclidean division constants
private const uint Sub = Add - 0xFFFF;
private const ulong Base = (Mult + 1ul) * 0xFFFF;
/// <summary>
/// Finds all seeds that can generate the <see cref="pid"/> by two successive rand() calls.
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="pid">PID to be reversed into seeds that generate it.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint pid)
{
uint first = pid & 0xFFFF_0000;
uint second = pid << 16;
return GetSeeds(result, first, second);
}
/// <summary>
/// Finds all seeds that can generate the IVs by two successive rand() calls.
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="hp" >Entity IV for HP</param>
/// <param name="atk">Entity IV for Attack</param>
/// <param name="def">Entity IV for Defense</param>
/// <param name="spa">Entity IV for Special Attack</param>
/// <param name="spd">Entity IV for Special Defense</param>
/// <param name="spe">Entity IV for Speed</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint hp, uint atk, uint def, uint spa, uint spd, uint spe)
{
var first = (hp | (atk << 5) | (def << 10)) << 16;
var second = (spe | (spa << 5) | (spd << 10)) << 16;
return GetSeedsIVs(result, first, second);
}
/// <summary>
/// Finds all the origin seeds for two 16 bit rand() calls
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 16 bits, already shifted left 16 bits.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeeds(Span<uint> result, uint first, uint second)
{
ulong t = second - (first * Mult) - Sub;
ulong kmax = (Base - t) >> 32;
int ctr = 0;
for (ulong k = 0; k <= kmax; k++, t += 0x1_0000_0000) // at most 4 iterations
{
if (t % Mult < 0x1_0000)
result[ctr++] = Prev(first | (ushort)(t / Mult));
}
return ctr;
}
/// <summary>
/// Finds all the origin seeds for two 15 bit rand() calls
/// </summary>
/// <param name="result">Result storage array, to be populated starting at index 0.</param>
/// <param name="first">First rand() call, 15 bits, already shifted left 16 bits.</param>
/// <param name="second">Second rand() call, 15 bits, already shifted left 16 bits.</param>
/// <returns>Count of results added to <see cref="result"/></returns>
public static int GetSeedsIVs(Span<uint> result, uint first, uint second)
{
ulong t = (second - (first * Mult) - Sub) & 0x7FFF_FFFF;
ulong kmax = (Base - t) >> 31;
int ctr = 0;
for (ulong k = 0; k <= kmax; k++, t += 0x8000_0000) // at most 7 iterations
{
if (t % Mult < 0x1_0000)
{
var s = Prev(first | (ushort)(t / Mult));
result[ctr++] = s;
result[ctr++] = s ^ 0x8000_0000; // top bit flip
}
}
return ctr;
}
}

View file

@ -1,4 +1,4 @@
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -21,7 +21,7 @@ public ref struct XorShift128
[FieldOffset(0x8)] private readonly ulong s1; [FieldOffset(0x8)] private readonly ulong s1;
/// <summary> /// <summary>
/// Uses the <see cref="RNG.ARNG"/> to advance the seed for each 32-bit input. /// Uses the <see cref="ARNG"/> to advance the seed for each 32-bit input.
/// </summary> /// </summary>
/// <param name="seed">32 bit seed</param> /// <param name="seed">32 bit seed</param>
/// <remarks>sub_E0F5E0 in v1.1.3</remarks> /// <remarks>sub_E0F5E0 in v1.1.3</remarks>

View file

@ -1,34 +1,28 @@
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
namespace PKHeX.Core; namespace PKHeX.Core;
/// <summary> /// <summary>
/// Frame List used to cache <see cref="RNG"/> results. /// Frame List used to cache <see cref="XDRNG"/> results, lazily reversing backwards and keeping the previous results.
/// </summary> /// </summary>
public sealed class FrameCache public sealed class FrameCache
{ {
private const int DefaultSize = 32; private const int DefaultSize = 32;
private readonly List<uint> Seeds = new(DefaultSize); private readonly List<uint> Seeds = new(DefaultSize);
private readonly List<uint> Values = new(DefaultSize); private readonly List<uint> Values = new(DefaultSize);
private readonly Func<uint, uint> Advance; private uint Last;
/// <summary> /// <summary>
/// Creates a new instance of a <see cref="FrameCache"/>. /// Creates a new instance of a <see cref="FrameCache"/>.
/// </summary> /// </summary>
/// <param name="origin">Seed at frame 0.</param> /// <param name="origin">Seed at frame 0.</param>
/// <param name="advance"><see cref="RNG"/> method used to get the next seed. Can use <see cref="RNG.Next"/> or <see cref="RNG.Prev"/>.</param> public FrameCache(uint origin) => Add(origin);
public FrameCache(uint origin, Func<uint, uint> advance)
{
Advance = advance;
Add(origin);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Add(uint seed) private void Add(uint seed)
{ {
Seeds.Add(seed); Seeds.Add(Last = seed);
Values.Add(seed >> 16); Values.Add(seed >> 16);
} }
@ -41,7 +35,7 @@ public sealed class FrameCache
get get
{ {
while (index >= Seeds.Count) while (index >= Seeds.Count)
Add(Advance(Seeds[^1])); Add(XDRNG.Prev(Last));
return Values[index]; return Values[index];
} }
} }
@ -54,7 +48,7 @@ public sealed class FrameCache
public uint GetSeed(int index) public uint GetSeed(int index)
{ {
while (index >= Seeds.Count) while (index >= Seeds.Count)
Add(Advance(Seeds[^1])); Add(XDRNG.Prev(Last));
return Seeds[index]; return Seeds[index];
} }
} }

View file

@ -67,11 +67,11 @@ public static class FrameFinder
if (noLead) if (noLead)
continue; continue;
var prev = info.RNG.Prev(f.Seed); // ESV var prev = LCRNG.Prev(f.Seed); // ESV
var rand = prev >> 16; var rand = prev >> 16;
f.RandESV = rand; f.RandESV = rand;
f.RandLevel = f.Seed >> 16; f.RandLevel = f.Seed >> 16;
f.OriginSeed = info.RNG.Prev(prev); f.OriginSeed = LCRNG.Prev(prev);
if (f.Lead != LeadRequired.CuteCharm) // needs proc checking if (f.Lead != LeadRequired.CuteCharm) // needs proc checking
yield return f; yield return f;
@ -94,9 +94,9 @@ public static class FrameFinder
// 3 different rand places // 3 different rand places
LeadRequired lead; LeadRequired lead;
var prev0 = f.Seed; // 0 var prev0 = f.Seed; // 0
var prev1 = info.RNG.Prev(f.Seed); // -1 var prev1 = LCRNG.Prev(f.Seed); // -1
var prev2 = info.RNG.Prev(prev1); // -2 var prev2 = LCRNG.Prev(prev1); // -2
var prev3 = info.RNG.Prev(prev2); // -3 var prev3 = LCRNG.Prev(prev2); // -3
// Rand call raw values // Rand call raw values
var p0 = prev0 >> 16; var p0 = prev0 >> 16;
@ -167,12 +167,12 @@ public static class FrameFinder
var rand = f.Seed >> 16; var rand = f.Seed >> 16;
f.RandESV = rand; f.RandESV = rand;
f.RandLevel = rand; // unused f.RandLevel = rand; // unused
f.OriginSeed = info.RNG.Prev(f.Seed); f.OriginSeed = LCRNG.Prev(f.Seed);
yield return f; yield return f;
// Create a copy for level; shift ESV and origin back // Create a copy for level; shift ESV and origin back
var esv = f.OriginSeed >> 16; var esv = f.OriginSeed >> 16;
var origin = info.RNG.Prev(f.OriginSeed); var origin = LCRNG.Prev(f.OriginSeed);
var withLevel = info.GetFrame(f.Seed, f.Lead | LeadRequired.UsesLevelCall, esv, f.RandLevel, origin); var withLevel = info.GetFrame(f.Seed, f.Lead | LeadRequired.UsesLevelCall, esv, f.RandLevel, origin);
yield return withLevel; yield return withLevel;
@ -194,9 +194,9 @@ public static class FrameFinder
{ {
LeadRequired lead; LeadRequired lead;
var prev0 = f.Seed; // 0 var prev0 = f.Seed; // 0
var prev1 = info.RNG.Prev(f.Seed); // -1 var prev1 = LCRNG.Prev(f.Seed); // -1
var prev2 = info.RNG.Prev(prev1); // -2 var prev2 = LCRNG.Prev(prev1); // -2
var prev3 = info.RNG.Prev(prev2); // -3 var prev3 = LCRNG.Prev(prev2); // -3
// Rand call raw values // Rand call raw values
var p0 = prev0 >> 16; var p0 = prev0 >> 16;
@ -290,12 +290,12 @@ public static class FrameFinder
if (!sync && !reg) // doesn't generate nature frame if (!sync && !reg) // doesn't generate nature frame
continue; continue;
uint prev = RNG.LCRNG.Prev(s); uint prev = LCRNG.Prev(s);
if (info.AllowLeads && reg) // check for failed sync if (info.AllowLeads && reg) // check for failed sync
{ {
var failsync = (info.DPPt ? prev >> 31 : (prev >> 16) & 1) != 1; var failsync = (info.DPPt ? prev >> 31 : (prev >> 16) & 1) != 1;
if (failsync) if (failsync)
yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.SynchronizeFail); yield return info.GetFrame(LCRNG.Prev(prev), LeadRequired.SynchronizeFail);
} }
if (sync) if (sync)
yield return info.GetFrame(prev, LeadRequired.Synchronize); yield return info.GetFrame(prev, LeadRequired.Synchronize);
@ -308,7 +308,7 @@ public static class FrameFinder
else else
{ {
if (info.Safari3) if (info.Safari3)
prev = RNG.LCRNG.Prev(prev); // wasted RNG call prev = LCRNG.Prev(prev); // wasted RNG call
yield return info.GetFrame(prev, LeadRequired.None); yield return info.GetFrame(prev, LeadRequired.None);
} }
} }
@ -328,10 +328,10 @@ public static class FrameFinder
for (uint i = 0; i < 25; i++) for (uint i = 0; i < 25; i++)
{ {
for (uint j = 1 + i; j < 25; j++) for (uint j = 1 + i; j < 25; j++)
stack.Push(seed = RNG.LCRNG.Prev(seed)); stack.Push(seed = LCRNG.Prev(seed));
} }
natureOrigin = RNG.LCRNG.Prev(stack.Peek()); natureOrigin = LCRNG.Prev(stack.Peek());
if (natureOrigin >> (16 % 100) >= 80) // failed proc if (natureOrigin >> (16 % 100) >= 80) // failed proc
return false; return false;
@ -368,7 +368,7 @@ public static class FrameFinder
return false; // current nature is chosen instead, fail! return false; // current nature is chosen instead, fail!
} }
// unroll once more to hit the level calc (origin with respect for beginning the nature calcs) // unroll once more to hit the level calc (origin with respect for beginning the nature calcs)
natureOrigin = RNG.LCRNG.Prev(natureOrigin); natureOrigin = LCRNG.Prev(natureOrigin);
return true; return true;
} }
@ -389,13 +389,13 @@ public static class FrameFinder
if (nature != info.Nature) if (nature != info.Nature)
continue; continue;
var prev = RNG.LCRNG.Prev(s); var prev = LCRNG.Prev(s);
var proc = prev >> 16; var proc = prev >> 16;
bool charmProc = (info.DPPt ? proc / 0x5556 : proc % 3) != 0; // 2/3 odds bool charmProc = (info.DPPt ? proc / 0x5556 : proc % 3) != 0; // 2/3 odds
if (!charmProc) if (!charmProc)
continue; continue;
yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.CuteCharm); yield return info.GetFrame(LCRNG.Prev(prev), LeadRequired.CuteCharm);
} }
} }
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using static PKHeX.Core.GameVersion; using static PKHeX.Core.GameVersion;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -15,7 +15,6 @@ public sealed class FrameGenerator
public readonly bool DPPt; public readonly bool DPPt;
public readonly bool AllowLeads; public readonly bool AllowLeads;
public readonly FrameType FrameType; public readonly FrameType FrameType;
public readonly RNG RNG = RNG.LCRNG;
public readonly bool Safari3; public readonly bool Safari3;
public Frame GetFrame(uint seed, LeadRequired lead) => new(seed, FrameType, lead); public Frame GetFrame(uint seed, LeadRequired lead) => new(seed, FrameType, lead);

View file

@ -19,7 +19,7 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
yield return new SeedInfo(seed); yield return new SeedInfo(seed);
var s1 = seed; var s1 = seed;
var s2 = RNG.LCRNG.Prev(s1); var s2 = LCRNG.Prev(s1);
bool charm3 = false; bool charm3 = false;
while (true) while (true)
{ {
@ -38,8 +38,8 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
break; break;
} }
s1 = RNG.LCRNG.Prev(s2); s1 = LCRNG.Prev(s2);
s2 = RNG.LCRNG.Prev(s1); s2 = LCRNG.Prev(s1);
yield return new SeedInfo(s1, charm3); yield return new SeedInfo(s1, charm3);
} }
@ -55,9 +55,11 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
// We cannot rely on a PID-IV origin seed. Since IVs are 2^30, they are not strong enough to assume a single seed was the source. // We cannot rely on a PID-IV origin seed. Since IVs are 2^30, they are not strong enough to assume a single seed was the source.
// We must reverse the IVs to find all seeds that could generate this. // We must reverse the IVs to find all seeds that could generate this.
// ESV,Proc,Nature,IV1,IV2; these do not do the nature loop for Method J/K so each seed originates a single seed frame. // ESV,Proc,Nature,IV1,IV2; these do not do the nature loop for Method J/K so each seed originates a single seed frame.
var seeds = MethodFinder.GetCuteCharmSeeds(pk);
foreach (var seed in seeds) var seeds = new uint[LCRNG.MaxCountSeedsIV];
yield return new SeedInfo(seed); int ctr = LCRNGReversal.GetSeedsIVs(seeds, (uint)pk.IV_HP, (uint)pk.IV_ATK, (uint)pk.IV_DEF, (uint)pk.IV_SPA, (uint)pk.IV_SPD, (uint)pk.IV_SPE);
for (int i = 0; i < ctr; i++)
yield return new SeedInfo(seeds[i]);
} }
/// <summary> /// <summary>
@ -73,7 +75,7 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
yield return new SeedInfo(seed); yield return new SeedInfo(seed);
var s1 = seed; var s1 = seed;
var s2 = RNG.LCRNG.Prev(s1); var s2 = LCRNG.Prev(s1);
while (true) while (true)
{ {
var a = s2 >> 16; var a = s2 >> 16;
@ -91,8 +93,8 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
} }
} }
s1 = RNG.LCRNG.Prev(s2); s1 = LCRNG.Prev(s2);
s2 = RNG.LCRNG.Prev(s1); s2 = LCRNG.Prev(s1);
yield return new SeedInfo(s1); yield return new SeedInfo(s1);
} }

View file

@ -53,8 +53,8 @@ public static class LockFinder
public static bool IsXDStarterValid(uint seed, int TID, int SID) 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 // 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 SIDf = XDRNG.Prev2(seed);
var TIDf = RNG.XDRNG.Prev(SIDf); var TIDf = XDRNG.Prev(SIDf);
return SIDf >> 16 == SID && TIDf >> 16 == TID; return SIDf >> 16 == SID && TIDf >> 16 == TID;
} }
@ -71,16 +71,14 @@ public static class LockFinder
public static bool IsColoStarterValid(ushort species, ref uint seed, int TID, int SID, uint pkPID, uint IV1, uint IV2) 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 // reverse the seed the bare minimum
int rev = 2; uint SIDf = species == (int)Species.Espeon
if (species == (int)Species.Espeon) ? XDRNG.Prev9(seed)
rev += 7; : XDRNG.Prev2(seed);
// reverse until we find the TID/SID, then run the generation forward to see if it matches our inputs. // 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; int ctr = 0;
uint temp; uint temp;
while ((temp = rng.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID) while ((temp = XDRNG.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID)
{ {
SIDf = temp; SIDf = temp;
if (ctr > 32) // arbitrary if (ctr > 32) // arbitrary
@ -88,7 +86,7 @@ public static class LockFinder
ctr++; ctr++;
} }
var next = rng.Next(SIDf); var next = XDRNG.Next(SIDf);
// generate Umbreon // generate Umbreon
var PIDIV = GenerateValidColoStarterPID(ref next, TID, SID); var PIDIV = GenerateValidColoStarterPID(ref next, TID, SID);
@ -97,7 +95,7 @@ public static class LockFinder
if (!PIDIV.Equals(pkPID, IV1, IV2)) if (!PIDIV.Equals(pkPID, IV1, IV2))
return false; return false;
seed = rng.Reverse(SIDf, 2); seed = XDRNG.Prev2(SIDf);
return true; return true;
} }
@ -108,17 +106,15 @@ public static class LockFinder
private static PIDIVGroup GenerateValidColoStarterPID(ref uint uSeed, int TID, int SID) private static PIDIVGroup GenerateValidColoStarterPID(ref uint uSeed, int TID, int SID)
{ {
var rng = RNG.XDRNG; uSeed = XDRNG.Next2(uSeed); // skip fakePID
uSeed = rng.Advance(uSeed, 2); // skip fakePID
var IV1 = (uSeed >> 16) & 0x7FFF; var IV1 = (uSeed >> 16) & 0x7FFF;
uSeed = rng.Next(uSeed); uSeed = XDRNG.Next(uSeed);
var IV2 = (uSeed >> 16) & 0x7FFF; var IV2 = (uSeed >> 16) & 0x7FFF;
uSeed = rng.Next(uSeed); uSeed = XDRNG.Next(uSeed);
uSeed = rng.Advance(uSeed, 1); // skip ability call uSeed = XDRNG.Next(uSeed); // skip ability call
var PID = GenerateStarterPID(ref uSeed, TID, SID); var PID = GenerateStarterPID(ref uSeed, TID, SID);
uSeed = rng.Advance(uSeed, 2); // PID calls consumed uSeed = XDRNG.Next2(uSeed); // PID calls consumed
return new PIDIVGroup(PID, IV1, IV2); return new PIDIVGroup(PID, IV1, IV2);
} }
@ -131,11 +127,11 @@ public static class LockFinder
const byte ratio = 0x1F; // 12.5% F (can't be female) const byte ratio = 0x1F; // 12.5% F (can't be female)
while (true) while (true)
{ {
var next = RNG.XDRNG.Next(uSeed); var next = XDRNG.Next(uSeed);
PID = (uSeed & 0xFFFF0000) | (next >> 16); PID = (uSeed & 0xFFFF0000) | (next >> 16);
if ((PID & 0xFF) >= ratio && !IsShiny(TID, SID, PID)) if ((PID & 0xFF) >= ratio && !IsShiny(TID, SID, PID))
break; break;
uSeed = RNG.XDRNG.Next(next); uSeed = XDRNG.Next(next);
} }
return PID; return PID;

View file

@ -62,7 +62,7 @@ public sealed class TeamLockResult
{ {
Locks = new Stack<NPCLock>((Specifications = teamSpec).Locks); Locks = new Stack<NPCLock>((Specifications = teamSpec).Locks);
Team = new Stack<SeedFrame>(Locks.Count); Team = new Stack<SeedFrame>(Locks.Count);
Cache = new FrameCache(RNG.XDRNG.Reverse(originSeed, 2), RNG.XDRNG.Prev); Cache = new FrameCache(XDRNG.Prev2(originSeed));
TSV = tsv; TSV = tsv;
Valid = FindLockSeed(); Valid = FindLockSeed();

View file

@ -1,6 +1,4 @@
using System; using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using static PKHeX.Core.PIDType; using static PKHeX.Core.PIDType;
@ -22,44 +20,49 @@ public static class MethodFinder
return AnalyzeGB(pk); return AnalyzeGB(pk);
var pid = pk.EncryptionConstant; var pid = pk.EncryptionConstant;
var top = pid >> 16; var top = pid & 0xFFFF0000;
var bot = pid & 0xFFFF; var bot = pid << 16;
Span<uint> temp = stackalloc uint[6]; Span<uint> temp = stackalloc uint[6];
for (int i = 0; i < 6; i++) for (int i = 0; i < 6; i++)
temp[i] = (uint)pk.GetIV(i); temp[i] = (uint)pk.GetIV(i);
ReadOnlySpan<uint> IVs = temp; ReadOnlySpan<uint> IVs = temp;
if (GetLCRNGMatch(top, bot, IVs, out PIDIV pidiv)) // Between XDRNG and LCRNG, the LCRNG will have the most results.
// Reuse our temp buffer across all methods.
const int maxResults = LCRNG.MaxCountSeedsIV;
Span<uint> seeds = stackalloc uint[maxResults];
if (GetLCRNGMatch(seeds, top, bot, IVs, out PIDIV pidiv))
return pidiv; return pidiv;
if (pk.Species == (int)Species.Unown && GetLCRNGUnownMatch(top, bot, IVs, out pidiv)) // frlg only if (pk.Species == (int)Species.Unown && GetLCRNGUnownMatch(seeds, top, bot, IVs, out pidiv)) // frlg only
return pidiv; return pidiv;
if (GetColoStarterMatch(pk, top, bot, IVs, out pidiv)) if (GetColoStarterMatch(seeds, pk, top, bot, IVs, out pidiv))
return pidiv; return pidiv;
if (GetXDRNGMatch(pk, top, bot, IVs, out pidiv)) if (GetXDRNGMatch(seeds, pk, top, bot, IVs, out pidiv))
return pidiv; return pidiv;
// Special cases // Special cases
if (GetLCRNGRoamerMatch(top, bot, IVs, out pidiv)) if (GetLCRNGRoamerMatch(seeds, top, bot, IVs, out pidiv))
return pidiv; return pidiv;
if (GetChannelMatch(top, bot, IVs, out pidiv, pk)) if (GetChannelMatch(seeds, top, bot, IVs, out pidiv, pk))
return pidiv; return pidiv;
if (GetMG4Match(pid, IVs, out pidiv)) if (GetMG4Match(seeds, pid, IVs, out pidiv))
return pidiv; return pidiv;
if (GetBACDMatch(pk, pid, IVs, out pidiv)) if (GetBACDMatch(seeds, pk, pid, IVs, out pidiv))
return pidiv; return pidiv;
if (GetModifiedPIDMatch(pk, pid, IVs, out pidiv)) if (GetModifiedPIDMatch(seeds, pk, pid, IVs, out pidiv))
return pidiv; return pidiv;
return PIDIV.None; // no match return PIDIV.None; // no match
} }
private static bool GetModifiedPIDMatch(PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetModifiedPIDMatch(Span<uint> seeds, PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
if (pk.IsShiny) if (pk.IsShiny)
{ {
if (GetChainShinyMatch(pk, pid, IVs, out pidiv)) if (GetChainShinyMatch(seeds, pk, pid, IVs, out pidiv))
return true; return true;
if (GetModified8BitMatch(pk, pid, out pidiv)) if (GetModified8BitMatch(pk, pid, out pidiv))
return true; return true;
@ -80,22 +83,23 @@ public static class MethodFinder
: GetG5MGShinyMatch(pk, pid, out pidiv) || (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv)); : GetG5MGShinyMatch(pk, pid, out pidiv) || (pid <= 0xFF && GetCuteCharmMatch(pk, pid, out pidiv));
} }
private static bool GetLCRNGMatch(uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetLCRNGMatch(Span<uint> seeds, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
var reg = GetSeedsFromPID(RNG.LCRNG, top, bot); var count = LCRNGReversal.GetSeeds(seeds, bot, top);
var reg = seeds[..count];
var iv1 = GetIVChunk(IVs, 0); var iv1 = GetIVChunk(IVs, 0);
var iv2 = GetIVChunk(IVs, 3); var iv2 = GetIVChunk(IVs, 3);
foreach (var seed in reg) foreach (var seed in reg)
{ {
// A and B are already used by PID // A and B are already used by PID
var B = RNG.LCRNG.Advance(seed, 2); var B = LCRNG.Next2(seed);
// Method 1/2/4 can use 3 different RNG frames // Method 1/2/4 can use 3 different RNG frames
var C = RNG.LCRNG.Next(B); var C = LCRNG.Next(B);
var ivC = C >> 16 & 0x7FFF; var ivC = C >> 16 & 0x7FFF;
if (iv1 == ivC) if (iv1 == ivC)
{ {
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF; var ivD = D >> 16 & 0x7FFF;
if (iv2 == ivD) // ABCD if (iv2 == ivD) // ABCD
{ {
@ -103,7 +107,7 @@ public static class MethodFinder
return true; return true;
} }
var E = RNG.LCRNG.Next(D); var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF; var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // ABCE if (iv2 == ivE) // ABCE
{ {
@ -113,12 +117,12 @@ public static class MethodFinder
} }
else else
{ {
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF; var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD) if (iv1 != ivD)
continue; continue;
var E = RNG.LCRNG.Next(D); var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF; var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // ABDE if (iv2 == ivE) // ABDE
{ {
@ -127,18 +131,19 @@ public static class MethodFinder
} }
} }
} }
reg = GetSeedsFromPIDSkip(RNG.LCRNG, top, bot); count = LCRNGReversalSkip.GetSeeds(seeds, bot, top);
reg = seeds[..count];
foreach (var seed in reg) foreach (var seed in reg)
{ {
// A and B are already used by PID // A and B are already used by PID
var C = RNG.LCRNG.Advance(seed, 3); var C = LCRNG.Next3(seed);
// Method 3 // Method 3
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF; var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD) if (iv1 != ivD)
continue; continue;
var E = RNG.LCRNG.Next(D); var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF; var ivE = E >> 16 & 0x7FFF;
if (iv2 != ivE) if (iv2 != ivE)
continue; continue;
@ -148,23 +153,24 @@ public static class MethodFinder
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
private static bool GetLCRNGUnownMatch(uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetLCRNGUnownMatch(Span<uint> seeds, uint top, uint bot, ReadOnlySpan<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) // 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 count = LCRNGReversal.GetSeeds(seeds, top, bot); // reversed!
var reg = seeds[..count];
var iv1 = GetIVChunk(IVs, 0); var iv1 = GetIVChunk(IVs, 0);
var iv2 = GetIVChunk(IVs, 3); var iv2 = GetIVChunk(IVs, 3);
foreach (var seed in reg) foreach (var seed in reg)
{ {
// A and B are already used by PID // A and B are already used by PID
var B = RNG.LCRNG.Advance(seed, 2); var B = LCRNG.Next2(seed);
// Method 1/2/4 can use 3 different RNG frames // Method 1/2/4 can use 3 different RNG frames
var C = RNG.LCRNG.Next(B); var C = LCRNG.Next(B);
var ivC = C >> 16 & 0x7FFF; var ivC = C >> 16 & 0x7FFF;
if (iv1 == ivC) if (iv1 == ivC)
{ {
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF; var ivD = D >> 16 & 0x7FFF;
if (iv2 == ivD) // BACD if (iv2 == ivD) // BACD
{ {
@ -172,7 +178,7 @@ public static class MethodFinder
return true; return true;
} }
var E = RNG.LCRNG.Next(D); var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF; var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // BACE if (iv2 == ivE) // BACE
{ {
@ -182,12 +188,12 @@ public static class MethodFinder
} }
else else
{ {
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF; var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD) if (iv1 != ivD)
continue; continue;
var E = RNG.LCRNG.Next(D); var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF; var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // BADE if (iv2 == ivE) // BADE
{ {
@ -196,18 +202,19 @@ public static class MethodFinder
} }
} }
} }
reg = GetSeedsFromPIDSkip(RNG.LCRNG, bot, top); // reversed! count = LCRNGReversalSkip.GetSeeds(seeds, top, bot); // reversed!
reg = seeds[..count];
foreach (var seed in reg) foreach (var seed in reg)
{ {
// A and B are already used by PID // A and B are already used by PID
var C = RNG.LCRNG.Advance(seed, 3); var C = LCRNG.Next3(seed);
// Method 3 // Method 3
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF; var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD) if (iv1 != ivD)
continue; continue;
var E = RNG.LCRNG.Next(D); var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF; var ivE = E >> 16 & 0x7FFF;
if (iv2 != ivE) if (iv2 != ivE)
continue; continue;
@ -217,16 +224,18 @@ public static class MethodFinder
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
private static bool GetLCRNGRoamerMatch(uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetLCRNGRoamerMatch(Span<uint> seeds, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
if (IVs[2] != 0 || IVs[3] != 0 || IVs[4] != 0 || IVs[5] != 0 || IVs[1] > 7) if (IVs[2] != 0 || IVs[3] != 0 || IVs[4] != 0 || IVs[5] != 0 || IVs[1] > 7)
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
var iv1 = GetIVChunk(IVs, 0); var iv1 = GetIVChunk(IVs, 0);
var reg = GetSeedsFromPID(RNG.LCRNG, top, bot); var count = LCRNGReversal.GetSeeds(seeds, bot, top);
var reg = seeds[..count];
foreach (var seed in reg) foreach (var seed in reg)
{ {
// Only the first 8 bits are kept // Only the first 8 bits are kept
var ivC = RNG.LCRNG.Advance(seed, 3) >> 16 & 0x00FF; var ivC = LCRNG.Next3(seed) >> 16 & 0x00FF;
if (iv1 != ivC) if (iv1 != ivC)
continue; continue;
@ -236,19 +245,20 @@ public static class MethodFinder
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
private static bool GetXDRNGMatch(PKM pk, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetXDRNGMatch(Span<uint> seeds, PKM pk, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot); var count = XDRNG.GetSeeds(seeds, top, bot);
var xdc = seeds[..count];
foreach (var seed in xdc) foreach (var seed in xdc)
{ {
var B = RNG.XDRNG.Prev(seed); var B = XDRNG.Prev(seed);
var A = RNG.XDRNG.Prev(B); var A = XDRNG.Prev(B);
var hi = A >> 16; var hi = A >> 16;
var lo = B >> 16; var lo = B >> 16;
if (IVsMatch(hi, lo, IVs)) if (IVsMatch(hi, lo, IVs))
{ {
pidiv = new PIDIV(CXD, RNG.XDRNG.Prev(A)); pidiv = new PIDIV(CXD, XDRNG.Prev(A));
return true; return true;
} }
@ -266,18 +276,18 @@ public static class MethodFinder
do do
{ {
B = RNG.XDRNG.Prev(A); B = XDRNG.Prev(A);
A = RNG.XDRNG.Prev(B); A = XDRNG.Prev(B);
hi = A >> 16; hi = A >> 16;
lo = B >> 16; lo = B >> 16;
if (IVsMatch(hi, lo, IVs)) if (IVsMatch(hi, lo, IVs))
{ {
pidiv = new PIDIV(CXDAnti, RNG.XDRNG.Prev(A)); pidiv = new PIDIV(CXDAnti, XDRNG.Prev(A));
return true; return true;
} }
p2 = RNG.XDRNG.Prev(p1); p2 = XDRNG.Prev(p1);
p1 = RNG.XDRNG.Prev(p2); p1 = XDRNG.Prev(p2);
psv = (p2 ^ p1) >> 19; psv = (p2 ^ p1) >> 19;
} }
while (psv == tsv); while (psv == tsv);
@ -285,50 +295,54 @@ public static class MethodFinder
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
private static bool GetChannelMatch(uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv, PKM pk) private static bool GetChannelMatch(Span<uint> seeds, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv, PKM pk)
{ {
var ver = pk.Version; var ver = pk.Version;
if (ver is not ((int)GameVersion.R or (int)GameVersion.S)) if (ver is not ((int)GameVersion.R or (int)GameVersion.S))
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
var undo = top ^ 0x8000; var undo = (top >> 16) ^ 0x8000;
if ((undo > 7 ? 0 : 1) != (bot ^ pk.SID ^ 40122)) if ((undo > 7 ? 0 : 1) != ((bot >> 16) ^ pk.SID ^ 40122))
top = undo; top = (undo << 16);
var channel = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot);
var count = XDRNG.GetSeeds(seeds, top, bot);
var channel = seeds[..count];
foreach (var seed in channel) foreach (var seed in channel)
{ {
var C = RNG.XDRNG.Advance(seed, 3); // held item var C = XDRNG.Next3(seed); // held item
// no checks, held item can be swapped // no checks, held item can be swapped
var D = RNG.XDRNG.Next(C); // Version var D = XDRNG.Next(C); // Version
if ((D >> 31) + 1 != ver) // (0-Sapphire, 1-Ruby) if ((D >> 31) + 1 != ver) // (0-Sapphire, 1-Ruby)
continue; continue;
var E = RNG.XDRNG.Next(D); // OT Gender var E = XDRNG.Next(D); // OT Gender
if (E >> 31 != pk.OT_Gender) if (E >> 31 != pk.OT_Gender)
continue; continue;
if (!RNG.XDRNG.GetSequentialIVsUInt32(E, IVs)) if (!XDRNG.GetSequentialIVsUInt32(E, IVs))
continue; continue;
if (seed >> 16 != pk.SID) if (seed >> 16 != pk.SID)
continue; continue;
pidiv = new PIDIV(Channel, RNG.XDRNG.Prev(seed)); pidiv = new PIDIV(Channel, XDRNG.Prev(seed));
return true; return true;
} }
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
private static bool GetMG4Match(uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetMG4Match(Span<uint> seeds, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
uint mg4Rev = RNG.ARNG.Prev(pid); uint mg4Rev = ARNG.Prev(pid);
var mg4 = GetSeedsFromPID(RNG.LCRNG, mg4Rev >> 16, mg4Rev & 0xFFFF);
var count = LCRNGReversal.GetSeeds(seeds, mg4Rev << 16, mg4Rev & 0xFFFF0000);
var mg4 = seeds[..count];
foreach (var seed in mg4) foreach (var seed in mg4)
{ {
var B = RNG.LCRNG.Advance(seed, 2); var B = LCRNG.Next2(seed);
var C = RNG.LCRNG.Next(B); var C = LCRNG.Next(B);
var D = RNG.LCRNG.Next(C); var D = LCRNG.Next(C);
if (!IVsMatch(C >> 16, D >> 16, IVs)) if (!IVsMatch(C >> 16, D >> 16, IVs))
continue; continue;
@ -361,10 +375,13 @@ public static class MethodFinder
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
(var species, int genderValue) = GetCuteCharmGenderSpecies(pk, pid, pk.Species); (var species, int genderValue) = GetCuteCharmGenderSpecies(pk, pid, pk.Species);
if ((uint)species > Legal.MaxSpeciesID_4) static int getRatio(ushort species)
return GetNonMatch(out pidiv); {
return species <= Legal.MaxSpeciesID_4
? PersonalTable.HGSS[species].Gender
: PKX.Personal[species].Gender;
}
static int getRatio(ushort species) => PersonalTable.HGSS[species].Gender;
switch (genderValue) switch (genderValue)
{ {
case 2: break; // can't cute charm a genderless pk case 2: break; // can't cute charm a genderless pk
@ -391,15 +408,17 @@ public static class MethodFinder
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
private static bool GetChainShinyMatch(PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetChainShinyMatch(Span<uint> seeds, PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
// 13 shiny bits // 13 shiny bits
// PIDH & 7 // PIDH & 7
// PIDL & 7 // PIDL & 7
// IVs // IVs
var bot = GetIVChunk(IVs, 0); var bot = GetIVChunk(IVs, 0) << 16;
var top = GetIVChunk(IVs, 3); var top = GetIVChunk(IVs, 3) << 16;
var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot);
var count = LCRNGReversal.GetSeedsIVs(seeds, bot, top);
var reg = seeds[..count];
foreach (var seed in reg) foreach (var seed in reg)
{ {
// check the individual bits // check the individual bits
@ -410,7 +429,7 @@ public static class MethodFinder
var bit = s >> 16 & 1; var bit = s >> 16 & 1;
if (bit != (pid >> i & 1)) if (bit != (pid >> i & 1))
break; break;
s = RNG.LCRNG.Prev(s); s = LCRNG.Prev(s);
} }
while (--i != 2); while (--i != 2);
if (i != 2) // bit failed if (i != 2) // bit failed
@ -419,7 +438,7 @@ public static class MethodFinder
var upper = s; var upper = s;
if ((upper >> 16 & 7) != (pid >> 16 & 7)) if ((upper >> 16 & 7) != (pid >> 16 & 7))
continue; continue;
var lower = RNG.LCRNG.Prev(upper); var lower = LCRNG.Prev(upper);
if ((lower >> 16 & 7) != (pid & 7)) if ((lower >> 16 & 7) != (pid & 7))
continue; continue;
@ -427,34 +446,25 @@ public static class MethodFinder
if (upid != pid >> 16) if (upid != pid >> 16)
continue; continue;
s = RNG.LCRNG.Reverse(lower, 2); // unroll one final time to get the origin seed s = LCRNG.Prev2(lower); // unroll one final time to get the origin seed
pidiv = new PIDIV(ChainShiny, s); pidiv = new PIDIV(ChainShiny, s);
return true; return true;
} }
return GetNonMatch(out pidiv); return GetNonMatch(out pidiv);
} }
public static IEnumerable<uint> GetCuteCharmSeeds(PKM pk) private static bool GetBACDMatch(Span<uint> seeds, PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
Span<uint> IVs = stackalloc uint[6]; var bot = GetIVChunk(IVs, 0) << 16;
for (int i = 0; i < 6; i++) var top = GetIVChunk(IVs, 3) << 16;
IVs[i] = (uint)pk.GetIV(i);
var bot = GetIVChunk(IVs, 0);
var top = GetIVChunk(IVs, 3);
return GetSeedsFromIVs(RNG.LCRNG, top, bot); var count = LCRNGReversal.GetSeedsIVs(seeds, bot, top);
} var reg = seeds[..count];
private static bool GetBACDMatch(PKM pk, uint pid, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{
var bot = GetIVChunk(IVs, 0);
var top = GetIVChunk(IVs, 3);
var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot);
PIDType type = BACD_U; PIDType type = BACD_U;
foreach (var seed in reg) foreach (var seed in reg)
{ {
var B = seed; var B = seed;
var A = RNG.LCRNG.Prev(B); var A = LCRNG.Prev(B);
var low = B >> 16; var low = B >> 16;
var PID = (A & 0xFFFF0000) | low; var PID = (A & 0xFFFF0000) | low;
@ -482,11 +492,11 @@ public static class MethodFinder
type = BACD_U_A; type = BACD_U_A;
} }
} }
var s = RNG.LCRNG.Prev(A); var s = LCRNG.Prev(A);
// Check for prior Restricted seed // Check for prior Restricted seed
var sn = s; var sn = s;
for (int i = 0; i < 3; i++, sn = RNG.LCRNG.Prev(sn)) for (int i = 0; i < 3; i++, sn = LCRNG.Prev(sn))
{ {
if ((sn & 0xFFFF0000) != 0) if ((sn & 0xFFFF0000) != 0)
continue; continue;
@ -540,7 +550,7 @@ public static class MethodFinder
return pid == oldpid; return pid == oldpid;
} }
private static bool GetColoStarterMatch(PKM pk, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv) private static bool GetColoStarterMatch(Span<uint> seeds, PKM pk, uint top, uint bot, ReadOnlySpan<uint> IVs, out PIDIV pidiv)
{ {
bool starter = pk.Version == (int)GameVersion.CXD && pk.Species switch bool starter = pk.Version == (int)GameVersion.CXD && pk.Species switch
{ {
@ -553,7 +563,9 @@ public static class MethodFinder
var iv1 = GetIVChunk(IVs, 0); var iv1 = GetIVChunk(IVs, 0);
var iv2 = GetIVChunk(IVs, 3); var iv2 = GetIVChunk(IVs, 3);
var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot);
var count = XDRNG.GetSeeds(seeds, top, bot);
var xdc = seeds[..count];
foreach (var seed in xdc) foreach (var seed in xdc)
{ {
uint origin = seed; uint origin = seed;
@ -597,7 +609,7 @@ public static class MethodFinder
// 3-FORCEBITS // 3-FORCEBITS
// PID = PIDH << 16 | (SID ^ TID ^ PIDH) // PID = PIDH << 16 | (SID ^ TID ^ PIDH)
var X = RNG.LCRNG.Prev(A); // unroll once as there's 3 calls instead of 2 var X = LCRNG.Prev(A); // unroll once as there's 3 calls instead of 2
uint PID = (X & 0xFFFF0000) | (idxor ^ X >> 16); uint PID = (X & 0xFFFF0000) | (idxor ^ X >> 16);
PID &= 0xFFFFFFF8; PID &= 0xFFFFFFF8;
PID |= low & 0x7; // lowest 3 bits PID |= low & 0x7; // lowest 3 bits
@ -644,75 +656,6 @@ public static class MethodFinder
return PIDIV.None; return PIDIV.None;
} }
private static IEnumerable<uint> GetSeedsFromPID(RNG method, uint a, uint b)
{
Debug.Assert(a >> 16 == 0);
Debug.Assert(b >> 16 == 0);
uint second = a << 16;
uint first = b << 16;
return method.RecoverLower16Bits(first, second);
}
private static IEnumerable<uint> GetSeedsFromPIDSkip(RNG method, uint a, uint b)
{
Debug.Assert(a >> 16 == 0);
Debug.Assert(b >> 16 == 0);
uint third = a << 16;
uint first = b << 16;
return method.RecoverLower16BitsGap(first, third);
}
private static IEnumerable<uint> GetSeedsFromIVs(RNG method, uint a, uint b)
{
Debug.Assert(a >> 15 == 0);
Debug.Assert(b >> 15 == 0);
uint second = a << 16;
uint first = b << 16;
var attempt1 = method.RecoverLower16Bits(first, second);
foreach (var z in attempt1)
{
yield return z;
yield return z ^ 0x80000000; // sister bitflip
}
var attempt2 = method.RecoverLower16Bits(first, second ^ 0x80000000);
foreach (var z in attempt2)
{
yield return z;
yield return z ^ 0x80000000; // sister bitflip
}
}
public static IEnumerable<uint> GetSeedsFromIVsSkip(RNG method, uint rand1, uint rand3)
{
Debug.Assert(rand1 >> 15 == 0);
Debug.Assert(rand3 >> 15 == 0);
rand1 <<= 16;
rand3 <<= 16;
var attempt1 = method.RecoverLower16BitsGap(rand1, rand3);
foreach (var z in attempt1)
{
yield return z;
yield return z ^ 0x80000000; // sister bitflip
}
var attempt2 = method.RecoverLower16BitsGap(rand1, rand3 ^ 0x80000000);
foreach (var z in attempt2)
{
yield return z;
yield return z ^ 0x80000000; // sister bitflip
}
}
public static IEnumerable<uint> GetSeedsFromPIDEuclid(RNG method, uint rand1, uint rand2)
{
return method.RecoverLower16BitsEuclid16(rand1 << 16, rand2 << 16);
}
public static IEnumerable<uint> GetSeedsFromIVsEuclid(RNG method, uint rand1, uint rand2)
{
return method.RecoverLower16BitsEuclid15(rand1 << 16, rand2 << 16);
}
/// <summary> /// <summary>
/// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each). /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each).
/// </summary> /// </summary>
@ -762,41 +705,7 @@ public static class MethodFinder
val |= IVs[i+start] << (5*i); val |= IVs[i+start] << (5*i);
return val; return val;
} }
public static IEnumerable<PIDIV> GetColoEReaderMatches(uint PID)
{
var top = PID >> 16;
var bot = (ushort)PID;
var xdc = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot);
foreach (var seed in xdc)
{
var B = RNG.XDRNG.Prev(seed);
var A = RNG.XDRNG.Prev(B);
var C = RNG.XDRNG.Advance(A, 7);
yield return new PIDIV(CXD, RNG.XDRNG.Prev(C));
}
}
public static IEnumerable<PIDIV> GetPokeSpotSeeds(PKM pk, 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 = pk.PID;
var top = pid >> 16;
var bot = pid & 0xFFFF;
var seeds = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot);
foreach (var seed in seeds)
{
// check for valid encounter slot info
if (!IsPokeSpotActivation(slot, seed, out uint s))
continue;
yield return new PIDIV(PokeSpot, s);
}
}
public static bool IsPokeSpotActivation(int slot, uint seed, out uint s) public static bool IsPokeSpotActivation(int slot, uint seed, out uint s)
{ {
s = seed; s = seed;
@ -806,14 +715,14 @@ public static class MethodFinder
// todo // todo
} }
// check for valid activation // check for valid activation
s = RNG.XDRNG.Prev(seed); s = XDRNG.Prev(seed);
if ((s >> 16) % 3 != 0) if ((s >> 16) % 3 != 0)
{ {
if ((s >> 16) % 100 < 10) // can't fail a munchlax/bonsly encounter check if ((s >> 16) % 100 < 10) // can't fail a munchlax/bonsly encounter check
{ {
// todo // todo
} }
s = RNG.XDRNG.Prev(s); s = XDRNG.Prev(s);
if ((s >> 16) % 3 != 0) // can't activate even if generous if ((s >> 16) % 3 != 0) // can't activate even if generous
{ {
// todo // todo
@ -913,7 +822,7 @@ public static class MethodFinder
bool IsAntiShinyARNG() bool IsAntiShinyARNG()
{ {
var shinyPID = RNG.ARNG.Prev(pk.PID); var shinyPID = ARNG.Prev(pk.PID);
return (pk.TID ^ pk.SID ^ (shinyPID & 0xFFFF) ^ (shinyPID >> 16)) < 8; // shiny proc return (pk.TID ^ pk.SID ^ (shinyPID & 0xFFFF) ^ (shinyPID >> 16)) < 8; // shiny proc
} }
} }
@ -954,7 +863,27 @@ public static class MethodFinder
// Future evolutions // Future evolutions
(int)Species.Sylveon => ((int)Species.Eevee, pk.Gender), (int)Species.Sylveon => ((int)Species.Eevee, pk.Gender),
(int)Species.MrRime => ((int)Species.MimeJr, pk.Gender),
(int)Species.Kleavor => ((int)Species.Scyther, pk.Gender),
_ => (currentSpecies, pk.Gender), _ => (currentSpecies, pk.Gender),
}; };
public static PIDIV GetPokeSpotSeedFirst(PKM pk, byte slot)
{
// Activate (rand % 3)
// Munchlax / Bonsly (10%/30%)
// Encounter Slot Value (ESV) = 50%/35%/15% rarity (0-49, 50-84, 85-99)
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsPID];
int count = XDRNG.GetSeeds(seeds, pk.EncryptionConstant);
var reg = seeds[..count];
foreach (var seed in reg)
{
// check for valid encounter slot info
if (IsPokeSpotActivation(slot, seed, out uint s))
return new PIDIV(PokeSpot, s);
}
return default;
}
} }

View file

@ -1,4 +1,4 @@
using System; using System;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -9,12 +9,11 @@ public static class PIDGenerator
{ {
private static void SetValuesFromSeedLCRNG(PKM pk, PIDType type, uint seed) private static void SetValuesFromSeedLCRNG(PKM pk, PIDType type, uint seed)
{ {
var rng = RNG.LCRNG; var A = LCRNG.Next(seed);
var A = rng.Next(seed); var B = LCRNG.Next(A);
var B = rng.Next(A);
var skipBetweenPID = type is PIDType.Method_3 or PIDType.Method_3_Unown; var skipBetweenPID = type is PIDType.Method_3 or PIDType.Method_3_Unown;
if (skipBetweenPID) // VBlank skip between PID rand() [RARE] if (skipBetweenPID) // VBlank skip between PID rand() [RARE]
B = rng.Next(B); B = LCRNG.Next(B);
var swappedPIDHalves = type is >= PIDType.Method_1_Unown and <= PIDType.Method_4_Unown; var swappedPIDHalves = type is >= PIDType.Method_1_Unown and <= PIDType.Method_4_Unown;
if (swappedPIDHalves) // switched order of PID halves, "BA.." if (swappedPIDHalves) // switched order of PID halves, "BA.."
@ -22,15 +21,15 @@ public static class PIDGenerator
else else
pk.PID = (B & 0xFFFF0000) | (A >> 16); pk.PID = (B & 0xFFFF0000) | (A >> 16);
var C = rng.Next(B); var C = LCRNG.Next(B);
var skipIV1Frame = type is PIDType.Method_2 or PIDType.Method_2_Unown; var skipIV1Frame = type is PIDType.Method_2 or PIDType.Method_2_Unown;
if (skipIV1Frame) // VBlank skip after PID if (skipIV1Frame) // VBlank skip after PID
C = rng.Next(C); C = LCRNG.Next(C);
var D = rng.Next(C); var D = LCRNG.Next(C);
var skipIV2Frame = type is PIDType.Method_4 or PIDType.Method_4_Unown; var skipIV2Frame = type is PIDType.Method_4 or PIDType.Method_4_Unown;
if (skipIV2Frame) // VBlank skip between IVs if (skipIV2Frame) // VBlank skip between IVs
D = rng.Next(D); D = LCRNG.Next(D);
Span<int> IVs = stackalloc int[6]; Span<int> IVs = stackalloc int[6];
MethodFinder.GetIVsInt32(IVs, C >> 16, D >> 16); MethodFinder.GetIVsInt32(IVs, C >> 16, D >> 16);
@ -46,13 +45,12 @@ public static class PIDGenerator
private static void SetValuesFromSeedBACD(PKM pk, PIDType type, uint seed) private static void SetValuesFromSeedBACD(PKM pk, PIDType type, uint seed)
{ {
var rng = RNG.LCRNG;
bool shiny = type is PIDType.BACD_R_S or PIDType.BACD_U_S; bool shiny = type is PIDType.BACD_R_S or PIDType.BACD_U_S;
uint X = shiny ? rng.Next(seed) : seed; uint X = shiny ? LCRNG.Next(seed) : seed;
var A = rng.Next(X); var A = LCRNG.Next(X);
var B = rng.Next(A); var B = LCRNG.Next(A);
var C = rng.Next(B); var C = LCRNG.Next(B);
var D = rng.Next(C); var D = LCRNG.Next(C);
if (shiny) if (shiny)
{ {
@ -83,25 +81,24 @@ public static class PIDGenerator
private static void SetValuesFromSeedXDRNG(PKM pk, uint seed) private static void SetValuesFromSeedXDRNG(PKM pk, uint seed)
{ {
var rng = RNG.XDRNG;
switch (pk.Species) switch (pk.Species)
{ {
case (int)Species.Umbreon or (int)Species.Eevee: // Colo Umbreon, XD Eevee case (int)Species.Umbreon or (int)Species.Eevee: // Colo Umbreon, XD Eevee
pk.TID = (int)((seed = rng.Next(seed)) >> 16); pk.TID = (int)((seed = XDRNG.Next(seed)) >> 16);
pk.SID = (int)((seed = rng.Next(seed)) >> 16); pk.SID = (int)((seed = XDRNG.Next(seed)) >> 16);
seed = rng.Advance(seed, 2); // PID calls consumed seed = XDRNG.Next2(seed); // PID calls consumed
break; break;
case (int)Species.Espeon: // Colo Espeon case (int)Species.Espeon: // Colo Espeon
pk.TID = (int)((seed = rng.Next(seed)) >> 16); pk.TID = (int)((seed = XDRNG.Next(seed)) >> 16);
pk.SID = (int)((seed = rng.Next(seed)) >> 16); pk.SID = (int)((seed = XDRNG.Next(seed)) >> 16);
seed = rng.Advance(seed, 9); // PID calls consumed, skip over Umbreon seed = XDRNG.Next9(seed); // PID calls consumed, skip over Umbreon
break; break;
} }
var A = rng.Next(seed); // IV1 var A = XDRNG.Next(seed); // IV1
var B = rng.Next(A); // IV2 var B = XDRNG.Next(A); // IV2
var C = rng.Next(B); // Ability? var C = XDRNG.Next(B); // Ability?
var D = rng.Next(C); // PID var D = XDRNG.Next(C); // PID
var E = rng.Next(D); // PID var E = XDRNG.Next(D); // PID
pk.PID = (D & 0xFFFF0000) | (E >> 16); pk.PID = (D & 0xFFFF0000) | (E >> 16);
Span<int> IVs = stackalloc int[6]; Span<int> IVs = stackalloc int[6];
@ -111,23 +108,20 @@ public static class PIDGenerator
public static void SetValuesFromSeedXDRNG_EReader(PKM pk, uint seed) public static void SetValuesFromSeedXDRNG_EReader(PKM pk, uint seed)
{ {
var rng = RNG.XDRNG; var D = XDRNG.Prev3(seed); // PID
var A = rng.Reverse(seed, 4); var E = XDRNG.Next(D); // PID
var D = rng.Next(A); // PID
var E = rng.Next(D); // PID
pk.PID = (D & 0xFFFF0000) | (E >> 16); pk.PID = (D & 0xFFFF0000) | (E >> 16);
} }
private static void SetValuesFromSeedChannel(PKM pk, uint seed) private static void SetValuesFromSeedChannel(PKM pk, uint seed)
{ {
var rng = RNG.XDRNG; var O = XDRNG.Next(seed); // SID
var O = rng.Next(seed); // SID var A = XDRNG.Next(O); // PID
var A = rng.Next(O); // PID var B = XDRNG.Next(A); // PID
var B = rng.Next(A); // PID var C = XDRNG.Next(B); // Held Item
var C = rng.Next(B); // Held Item var D = XDRNG.Next(C); // Version
var D = rng.Next(C); // Version var E = XDRNG.Next(D); // OT Gender
var E = rng.Next(D); // OT Gender
const int TID = 40122; const int TID = 40122;
var SID = (int)(O >> 16); var SID = (int)(O >> 16);
@ -143,7 +137,7 @@ public static class PIDGenerator
pk.Version = (int)(D >> 31) + 1; // 0-Sapphire, 1-Ruby pk.Version = (int)(D >> 31) + 1; // 0-Sapphire, 1-Ruby
pk.OT_Gender = (int)(E >> 31); pk.OT_Gender = (int)(E >> 31);
Span<int> ivs = stackalloc int[6]; Span<int> ivs = stackalloc int[6];
rng.GetSequentialIVsInt32(E, ivs); XDRNG.GetSequentialIVsInt32(E, ivs);
pk.SetIVs(ivs); pk.SetIVs(ivs);
} }
@ -204,7 +198,7 @@ public static class PIDGenerator
// 1 3-bit for upper // 1 3-bit for upper
// 1 3-bit for lower // 1 3-bit for lower
uint Next() => (seed = RNG.LCRNG.Next(seed)) >> 16; uint Next() => (seed = LCRNG.Next(seed)) >> 16;
uint lower = Next() & 7; uint lower = Next() & 7;
uint upper = Next() & 7; uint upper = Next() & 7;
for (int i = 0; i < 13; i++) for (int i = 0; i < 13; i++)
@ -226,9 +220,8 @@ public static class PIDGenerator
if (!MethodFinder.IsPokeSpotActivation(slot, seed, out _)) if (!MethodFinder.IsPokeSpotActivation(slot, seed, out _))
continue; continue;
var rng = RNG.XDRNG; var D = XDRNG.Next(seed); // PID
var D = rng.Next(seed); // PID var E = XDRNG.Next(D); // PID
var E = rng.Next(D); // PID
pk.PID = (D & 0xFFFF0000) | (E >> 16); pk.PID = (D & 0xFFFF0000) | (E >> 16);
if (!IsValidCriteria4(pk, nature, ability, gender)) if (!IsValidCriteria4(pk, nature, ability, gender))

View file

@ -12,99 +12,99 @@ public enum PIDType
#region LCRNG #region LCRNG
/// <summary> Method 1 Variants (H1/J/K) </summary> /// <summary> Method 1 Variants (H1/J/K) </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_1, Method_1,
/// <summary> Method H2 </summary> /// <summary> Method H2 </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_2, Method_2,
/// <summary> Method H3 </summary> /// <summary> Method H3 </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_3, Method_3,
/// <summary> Method H4 </summary> /// <summary> Method H4 </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_4, Method_4,
/// <summary> Method H1_Unown (FRLG) </summary> /// <summary> Method H1_Unown (FRLG) </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_1_Unown, Method_1_Unown,
/// <summary> Method H2_Unown (FRLG) </summary> /// <summary> Method H2_Unown (FRLG) </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_2_Unown, Method_2_Unown,
/// <summary> Method H3_Unown (FRLG) </summary> /// <summary> Method H3_Unown (FRLG) </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_3_Unown, Method_3_Unown,
/// <summary> Method H4_Unown (FRLG) </summary> /// <summary> Method H4_Unown (FRLG) </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_4_Unown, Method_4_Unown,
/// <summary> Method 1 Roamer (Gen3) </summary> /// <summary> Method 1 Roamer (Gen3) </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
Method_1_Roamer, Method_1_Roamer,
/// <summary> /// <summary>
/// Event Reversed Order PID restricted to 16bit Origin Seed /// Event Reversed Order PID restricted to 16bit Origin Seed
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/> seed is clamped to 16bits.</remarks> /// <remarks><see cref="LCRNG"/> seed is clamped to 16bits.</remarks>
BACD_R, BACD_R,
/// <summary> /// <summary>
/// Event Reversed Order PID without Origin Seed restrictions /// Event Reversed Order PID without Origin Seed restrictions
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
BACD_U, BACD_U,
/// <summary> /// <summary>
/// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny. /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny.
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/> seed is clamped to 16bits.</remarks> /// <remarks><see cref="LCRNG"/> seed is clamped to 16bits.</remarks>
BACD_R_A, BACD_R_A,
/// <summary> /// <summary>
/// Event Reversed Order PID without Origin Seed restrictions, antishiny. /// Event Reversed Order PID without Origin Seed restrictions, antishiny.
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
BACD_U_A, BACD_U_A,
/// <summary> /// <summary>
/// Event Reversed Order PID restricted to 8bit Origin Seed, shiny /// Event Reversed Order PID restricted to 8bit Origin Seed, shiny
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/> seed is clamped to 16bits.</remarks> /// <remarks><see cref="LCRNG"/> seed is clamped to 16bits.</remarks>
BACD_R_S, BACD_R_S,
/// <summary> /// <summary>
/// Event Reversed Order PID without Origin Seed restrictions, shiny /// Event Reversed Order PID without Origin Seed restrictions, shiny
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
BACD_U_S, BACD_U_S,
/// <summary> /// <summary>
/// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny (nyx) /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny (nyx)
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/> seed is clamped to 16bits.</remarks> /// <remarks><see cref="LCRNG"/> seed is clamped to 16bits.</remarks>
BACD_R_AX, BACD_R_AX,
/// <summary> /// <summary>
/// Event Reversed Order PID without Origin Seed restrictions, antishiny (nyx) /// Event Reversed Order PID without Origin Seed restrictions, antishiny (nyx)
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
BACD_U_AX, BACD_U_AX,
/// <summary> /// <summary>
/// Generation 4 Cute Charm PID, which is forced to an 8 bit PID value based on the gender &amp; gender ratio value. /// Generation 4 Cute Charm PID, which is forced to an 8 bit PID value based on the gender &amp; gender ratio value.
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
CuteCharm, CuteCharm,
/// <summary> /// <summary>
/// Generation 4 Chained Shiny /// Generation 4 Chained Shiny
/// </summary> /// </summary>
/// <remarks><see cref="RNG.LCRNG"/></remarks> /// <remarks><see cref="LCRNG"/></remarks>
ChainShiny, ChainShiny,
#endregion #endregion
@ -114,31 +114,31 @@ public enum PIDType
/// <summary> /// <summary>
/// Generation 3 <see cref="GameVersion.CXD"/> PID+IV correlation. /// Generation 3 <see cref="GameVersion.CXD"/> PID+IV correlation.
/// </summary> /// </summary>
/// <remarks><see cref="RNG.XDRNG"/></remarks> /// <remarks><see cref="XDRNG"/></remarks>
CXD, CXD,
/// <summary> /// <summary>
/// Generation 3 <see cref="GameVersion.CXD"/> PID+IV correlation that was rerolled because it was shiny. /// Generation 3 <see cref="GameVersion.CXD"/> PID+IV correlation that was rerolled because it was shiny.
/// </summary> /// </summary>
/// <remarks><see cref="RNG.XDRNG"/></remarks> /// <remarks><see cref="XDRNG"/></remarks>
CXDAnti, CXDAnti,
/// <summary> /// <summary>
/// Generation 3 <see cref="GameVersion.CXD"/> PID+IV which is created immediately after the TID and SID RNG calls. /// Generation 3 <see cref="GameVersion.CXD"/> PID+IV which is created immediately after the TID and SID RNG calls.
/// </summary> /// </summary>
/// <remarks><see cref="RNG.XDRNG"/>. The second starter is created after the first starter, with the same TID and SID.</remarks> /// <remarks><see cref="XDRNG"/>. The second starter is created after the first starter, with the same TID and SID.</remarks>
CXD_ColoStarter, CXD_ColoStarter,
/// <summary> /// <summary>
/// Generation 3 Pokémon Channel Jirachi /// Generation 3 Pokémon Channel Jirachi
/// </summary> /// </summary>
/// <remarks><see cref="RNG.XDRNG"/></remarks> /// <remarks><see cref="XDRNG"/></remarks>
Channel, Channel,
/// <summary> /// <summary>
/// Generation 3 <see cref="GameVersion.CXD"/> PokeSpot PID /// Generation 3 <see cref="GameVersion.CXD"/> PokeSpot PID
/// </summary> /// </summary>
/// <remarks><see cref="RNG.XDRNG"/></remarks> /// <remarks><see cref="XDRNG"/></remarks>
PokeSpot, PokeSpot,
#endregion #endregion
@ -148,7 +148,7 @@ public enum PIDType
/// <summary> /// <summary>
/// Generation 4 Mystery Gift Anti-Shiny /// Generation 4 Mystery Gift Anti-Shiny
/// </summary> /// </summary>
/// <remarks><see cref="RNG.ARNG"/></remarks> /// <remarks><see cref="ARNG"/></remarks>
G4MGAntiShiny, G4MGAntiShiny,
#endregion #endregion

View file

@ -1,4 +1,4 @@
using System; using System;
namespace PKHeX.Core; namespace PKHeX.Core;
@ -33,7 +33,7 @@ public static class MystryMew
0xFE9D, 0xFE9D,
}; };
private const int FramesPerMew = 5; //private const int FramesPerMew = 5;
private const int MewPerRestrictedSeed = 5; private const int MewPerRestrictedSeed = 5;
/// <summary> /// <summary>
@ -48,7 +48,7 @@ public static class MystryMew
uint position = (random % (MewPerRestrictedSeed - 1)) + 1; uint position = (random % (MewPerRestrictedSeed - 1)) + 1;
for (int i = 0; i < position; i++) for (int i = 0; i < position; i++)
seed = RNG.LCRNG.Advance(seed, FramesPerMew); seed = LCRNG.Next5(seed);
return seed; return seed;
} }
@ -63,7 +63,7 @@ public static class MystryMew
{ {
if (seed <= ushort.MaxValue) if (seed <= ushort.MaxValue)
return Array.BinarySearch(Seeds, (ushort)seed); return Array.BinarySearch(Seeds, (ushort)seed);
seed = RNG.LCRNG.Reverse(seed, FramesPerMew); seed = LCRNG.Prev5(seed);
} }
return -1; return -1;

View file

@ -208,8 +208,8 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4
// Generate IVs // Generate IVs
if ((pk4.IV32 & 0x3FFF_FFFFu) == 0) // Ignore Nickname/Egg flag bits if ((pk4.IV32 & 0x3FFF_FFFFu) == 0) // Ignore Nickname/Egg flag bits
{ {
uint iv1 = ((seed = RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF; uint iv1 = ((seed = LCRNG.Next(seed)) >> 16) & 0x7FFF;
uint iv2 = ((RNG.LCRNG.Next(seed)) >> 16) & 0x7FFF; uint iv2 = ((LCRNG.Next(seed)) >> 16) & 0x7FFF;
pk4.IV32 |= iv1 | (iv2 << 15); pk4.IV32 |= iv1 | (iv2 << 15);
} }
} }
@ -252,14 +252,14 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4
{ {
do do
{ {
uint pid1 = (seed = RNG.LCRNG.Next(seed)) >> 16; // low uint pid1 = (seed = LCRNG.Next(seed)) >> 16; // low
uint pid2 = (seed = RNG.LCRNG.Next(seed)) & 0xFFFF0000; // hi uint pid2 = (seed = LCRNG.Next(seed)) & 0xFFFF0000; // hi
pk4.PID = pid2 | pid1; pk4.PID = pid2 | pid1;
// sanity check gender for non-genderless PID cases // sanity check gender for non-genderless PID cases
} while (!pk4.IsGenderValid()); } while (!pk4.IsGenderValid());
while (pk4.IsShiny) // Call the ARNG to change the PID while (pk4.IsShiny) // Call the ARNG to change the PID
pk4.PID = RNG.ARNG.Next(pk4.PID); pk4.PID = ARNG.Next(pk4.PID);
return seed; return seed;
} }

View file

@ -1,4 +1,4 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using FluentAssertions; using FluentAssertions;
using PKHeX.Core; using PKHeX.Core;
@ -153,13 +153,17 @@ public static class PIDTests
public static void VerifyResults(IReadOnlyList<uint[]> results, TeamLock[] team) public static void VerifyResults(IReadOnlyList<uint[]> results, TeamLock[] team)
{ {
var pk = new PK3(); var pk = new PK3();
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsPID];
for (int i = 0; i < results.Count; i++) for (int i = 0; i < results.Count; i++)
{ {
var result = results[i]; var result = results[i];
var seeds = getSeeds(result[^1]); var pid = result[^1];
int count = XDRNG.GetSeeds(seeds, pid);
var reg = seeds[..count];
bool match = false; bool match = false;
foreach (var seed in seeds) foreach (var s in reg)
{ {
var seed = XDRNG.Prev3(s);
PIDGenerator.SetValuesFromSeed(pk, PIDType.CXD, seed); PIDGenerator.SetValuesFromSeed(pk, PIDType.CXD, seed);
var info = MethodFinder.Analyze(pk); var info = MethodFinder.Analyze(pk);
info.OriginSeed.Should().Be(seed); info.OriginSeed.Should().Be(seed);
@ -171,16 +175,6 @@ public static class PIDTests
} }
match.Should().BeTrue($"because the lock conditions for result {i} and species {team[0].Species} should have been verified"); match.Should().BeTrue($"because the lock conditions for result {i} and species {team[0].Species} should have been verified");
} }
static IEnumerable<uint> getSeeds(uint PID)
{
var top = PID >> 16;
var bot = PID & 0xFFFF;
var seeds = MethodFinder.GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot);
foreach (var s in seeds)
yield return RNG.XDRNG.Reverse(s, 3);
}
} }
public static readonly uint[] Mawile = public static readonly uint[] Mawile =

View file

@ -1,3 +1,4 @@
using System;
using System.Linq; using System.Linq;
using FluentAssertions; using FluentAssertions;
using PKHeX.Core; using PKHeX.Core;
@ -145,11 +146,11 @@ public class PIDIVTest
{ {
// XD PokeSpots: Check all 3 Encounter Slots (examples are one for each location). // XD PokeSpots: Check all 3 Encounter Slots (examples are one for each location).
var pkPS0 = new PK3 { PID = 0x7B2D9DA7 }; // Zubat (Cave) var pkPS0 = new PK3 { PID = 0x7B2D9DA7 }; // Zubat (Cave)
Assert.True(MethodFinder.GetPokeSpotSeeds(pkPS0, 0).Any(), "PokeSpot encounter info mismatch (Common)"); Assert.True(MethodFinder.GetPokeSpotSeedFirst(pkPS0, 0).Type == PIDType.PokeSpot, "PokeSpot encounter info mismatch (Common)");
var pkPS1 = new PK3 { PID = 0x3EE9AF66 }; // Gligar (Rock) var pkPS1 = new PK3 { PID = 0x3EE9AF66 }; // Gligar (Rock)
Assert.True(MethodFinder.GetPokeSpotSeeds(pkPS1, 1).Any(), "PokeSpot encounter info mismatch (Uncommon)"); Assert.True(MethodFinder.GetPokeSpotSeedFirst(pkPS1, 1).Type == PIDType.PokeSpot, "PokeSpot encounter info mismatch (Uncommon)");
var pkPS2 = new PK3 { PID = 0x9B667F3C }; // Surskit (Oasis) var pkPS2 = new PK3 { PID = 0x9B667F3C }; // Surskit (Oasis)
Assert.True(MethodFinder.GetPokeSpotSeeds(pkPS2, 2).Any(), "PokeSpot encounter info mismatch (Rare)"); Assert.True(MethodFinder.GetPokeSpotSeedFirst(pkPS2, 2).Type == PIDType.PokeSpot, "PokeSpot encounter info mismatch (Rare)");
} }
[Fact] [Fact]
@ -208,6 +209,20 @@ public class PIDIVTest
} }
} }
[Theory]
[InlineData(0x00001234, 0x4DCB, 0xE161)]
[InlineData(0x00005678, 0x734D, 0xC596)]
public void Method1(uint seed, ushort rand0, ushort rand1)
{
uint first = (uint)(rand0 << 16);
uint second = (uint)(rand1 << 16);
Span<uint> seeds = stackalloc uint[LCRNG.MaxCountSeedsPID];
int count = LCRNGReversal.GetSeeds(seeds, first, second);
count.Should().NotBe(0);
seeds[..count].IndexOf(seed).Should().NotBe(-1);
}
[Fact] [Fact]
public void PIDIVMethod4IVs() public void PIDIVMethod4IVs()
{ {
@ -225,7 +240,11 @@ public class PIDIVTest
rand1 |= (uint)IVs[i] << (5 * i); rand1 |= (uint)IVs[i] << (5 * i);
rand3 |= (uint)IVs[i+3] << (5 * i); rand3 |= (uint)IVs[i+3] << (5 * i);
} }
Assert.Contains(MethodFinder.GetSeedsFromIVsSkip(RNG.LCRNG, rand1, rand3), z => z == 0xFEE7047C);
Span<uint> seeds = stackalloc uint[LCRNG.MaxCountSeedsIV];
int count = LCRNGReversalSkip.GetSeedsIVs(seeds, rand1 << 16, rand3 << 16);
var reg = seeds[..count];
reg.IndexOf(0xFEE7047C).Should().NotBe(-1);
} }
[Fact] [Fact]
@ -234,9 +253,16 @@ public class PIDIVTest
const uint seed = 0x2E15555E; const uint seed = 0x2E15555E;
const uint rand0 = 0x20AD96A9; const uint rand0 = 0x20AD96A9;
const uint rand1 = 0x7E1DBEC8; const uint rand1 = 0x7E1DBEC8;
var pidseeds = MethodFinder.GetSeedsFromPIDEuclid(RNG.XDRNG, rand0 >> 16, rand1 >> 16);
var ivseeds = MethodFinder.GetSeedsFromIVsEuclid(RNG.XDRNG, (rand0 >> 16) & 0x7FFF, (rand1 >> 16) & 0x7FFF); XDRNG.MaxCountSeedsIV.Should().BeGreaterThan(XDRNG.MaxCountSeedsPID);
Assert.Contains(pidseeds, z => z == seed);
Assert.Contains(ivseeds, z => z == seed); Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsIV];
var cp = XDRNG.GetSeeds(seeds, rand0 & 0xFFFF0000, rand1 & 0xFFFF0000);
var p = seeds[..cp];
p.IndexOf(seed).Should().NotBe(-1);
var ci = XDRNG.GetSeedsIVs(seeds, rand0 & 0x7FFF0000, rand1 & 0x7FFF0000);
var i = seeds[..ci];
i.IndexOf(seed).Should().NotBe(-1);
} }
} }