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.Linq;
@ -46,12 +47,9 @@ public static class EncounterGenerator3
{
if (z is EncounterSlot3PokeSpot w)
{
var seeds = MethodFinder.GetPokeSpotSeeds(pk, w.SlotNumber);
foreach (var s in seeds)
{
info.PIDIV = s;
break;
}
var pidiv = MethodFinder.GetPokeSpotSeedFirst(pk, w.SlotNumber);
if (pidiv.Type == PIDType.PokeSpot)
info.PIDIV = pidiv;
}
else if (z is EncounterStaticShadow s)
{
@ -229,12 +227,15 @@ public static class EncounterGenerator3
return LockFinder.IsAllShadowLockValid(s, info.PIDIV, pk);
// E-Reader have fixed IVs, and aren't recognized as CXD (no PID-IV correlation).
var possible = MethodFinder.GetColoEReaderMatches(pk.EncryptionConstant);
foreach (var poss in possible)
Span<uint> seeds = stackalloc uint[4];
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;
info.PIDIV = poss;
info.PIDIV = pidiv;
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;
@ -13,39 +13,76 @@ namespace PKHeX.Core;
/// seed_n+1 = seed_n * <see cref="Mult"/> + <see cref="Add"/>
/// </br>
/// </remarks>
public class LCRNG
public static class LCRNG
{
// Forward
protected readonly uint Mult;
private readonly uint Add;
// Forward and reverse constants
public const uint Mult = 0x41C64E6D;
public const uint Add = 0x00006073;
public const uint rMult = 0xEEB9EB65;
public const uint rAdd = 0x0A3561A1;
// Reverse
private readonly uint rMult;
private readonly uint rAdd;
private const uint Mult2 = unchecked(Mult * Mult); // 0xC2A29A69
private const uint rMult2 = unchecked(rMult * rMult); // 0xDC6C95D9
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)
{
Mult = f_mult;
Add = f_add;
rMult = r_mult;
rAdd = r_add;
}
private const uint Mult3 = unchecked(Mult2 * Mult); // 0x807DBCB5
private const uint rMult3 = unchecked(rMult2 * rMult); // 0xAC36519D
private const uint Add3 = unchecked((Add2 * Mult) + Add); // 0x52713895
private const uint rAdd3 = unchecked((rAdd2 * rMult) + rAdd);// 0x923B279F
/// <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 uint Next(uint seed) => (seed * Mult) + Add;
private const uint Mult4 = unchecked(Mult3 * Mult); // 0xEE067F11
private const uint rMult4 = unchecked(rMult3 * rMult); // 0xBECE51F1
private const uint Add4 = unchecked((Add3 * Mult) + Add); // 0x31B0DDE4
private const uint rAdd4 = unchecked((rAdd3 * rMult) + rAdd);// 0x7CD1F85C
/// <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 uint Prev(uint seed) => (seed * rMult) + rAdd;
private const uint Mult5 = unchecked(Mult4 * Mult); // 0xEBA1483D
private const uint rMult5 = unchecked(rMult4 * rMult); // 0xF1C78F15
private const uint Add5 = unchecked((Add4 * Mult) + Add); // 0x8E425287
private const uint rAdd5 = unchecked((rAdd4 * rMult) + rAdd);// 0x0A84D1ED
private const uint Mult6 = unchecked(Mult5 * Mult); // 0xD3DC57F9
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>
/// 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>
/// <returns>Seed advanced the specified amount of times.</returns>
[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++)
seed = Next(seed);
@ -67,8 +104,7 @@ public class LCRNG
/// <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 uint Reverse(uint seed, int frames)
public static uint Reverse(uint seed, int frames)
{
for (int i = 0; i < frames; i++)
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;
namespace PKHeX.Core;
@ -21,7 +21,7 @@ public ref struct XorShift128
[FieldOffset(0x8)] private readonly ulong s1;
/// <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>
/// <param name="seed">32 bit seed</param>
/// <remarks>sub_E0F5E0 in v1.1.3</remarks>

View file

@ -1,34 +1,28 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace PKHeX.Core;
/// <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>
public sealed class FrameCache
{
private const int DefaultSize = 32;
private readonly List<uint> Seeds = new(DefaultSize);
private readonly List<uint> Values = new(DefaultSize);
private readonly Func<uint, uint> Advance;
private uint Last;
/// <summary>
/// Creates a new instance of a <see cref="FrameCache"/>.
/// </summary>
/// <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, Func<uint, uint> advance)
{
Advance = advance;
Add(origin);
}
public FrameCache(uint origin) => Add(origin);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void Add(uint seed)
{
Seeds.Add(seed);
Seeds.Add(Last = seed);
Values.Add(seed >> 16);
}
@ -41,7 +35,7 @@ public sealed class FrameCache
get
{
while (index >= Seeds.Count)
Add(Advance(Seeds[^1]));
Add(XDRNG.Prev(Last));
return Values[index];
}
}
@ -54,7 +48,7 @@ public sealed class FrameCache
public uint GetSeed(int index)
{
while (index >= Seeds.Count)
Add(Advance(Seeds[^1]));
Add(XDRNG.Prev(Last));
return Seeds[index];
}
}

View file

@ -67,11 +67,11 @@ public static class FrameFinder
if (noLead)
continue;
var prev = info.RNG.Prev(f.Seed); // ESV
var prev = LCRNG.Prev(f.Seed); // ESV
var rand = prev >> 16;
f.RandESV = rand;
f.RandLevel = f.Seed >> 16;
f.OriginSeed = info.RNG.Prev(prev);
f.OriginSeed = LCRNG.Prev(prev);
if (f.Lead != LeadRequired.CuteCharm) // needs proc checking
yield return f;
@ -94,9 +94,9 @@ public static class FrameFinder
// 3 different rand places
LeadRequired lead;
var prev0 = f.Seed; // 0
var prev1 = info.RNG.Prev(f.Seed); // -1
var prev2 = info.RNG.Prev(prev1); // -2
var prev3 = info.RNG.Prev(prev2); // -3
var prev1 = LCRNG.Prev(f.Seed); // -1
var prev2 = LCRNG.Prev(prev1); // -2
var prev3 = LCRNG.Prev(prev2); // -3
// Rand call raw values
var p0 = prev0 >> 16;
@ -167,12 +167,12 @@ public static class FrameFinder
var rand = f.Seed >> 16;
f.RandESV = rand;
f.RandLevel = rand; // unused
f.OriginSeed = info.RNG.Prev(f.Seed);
f.OriginSeed = LCRNG.Prev(f.Seed);
yield return f;
// Create a copy for level; shift ESV and origin back
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);
yield return withLevel;
@ -194,9 +194,9 @@ public static class FrameFinder
{
LeadRequired lead;
var prev0 = f.Seed; // 0
var prev1 = info.RNG.Prev(f.Seed); // -1
var prev2 = info.RNG.Prev(prev1); // -2
var prev3 = info.RNG.Prev(prev2); // -3
var prev1 = LCRNG.Prev(f.Seed); // -1
var prev2 = LCRNG.Prev(prev1); // -2
var prev3 = LCRNG.Prev(prev2); // -3
// Rand call raw values
var p0 = prev0 >> 16;
@ -290,12 +290,12 @@ public static class FrameFinder
if (!sync && !reg) // doesn't generate nature frame
continue;
uint prev = RNG.LCRNG.Prev(s);
uint prev = LCRNG.Prev(s);
if (info.AllowLeads && reg) // check for failed sync
{
var failsync = (info.DPPt ? prev >> 31 : (prev >> 16) & 1) != 1;
if (failsync)
yield return info.GetFrame(RNG.LCRNG.Prev(prev), LeadRequired.SynchronizeFail);
yield return info.GetFrame(LCRNG.Prev(prev), LeadRequired.SynchronizeFail);
}
if (sync)
yield return info.GetFrame(prev, LeadRequired.Synchronize);
@ -308,7 +308,7 @@ public static class FrameFinder
else
{
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);
}
}
@ -328,10 +328,10 @@ public static class FrameFinder
for (uint i = 0; i < 25; i++)
{
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
return false;
@ -368,7 +368,7 @@ public static class FrameFinder
return false; // current nature is chosen instead, fail!
}
// 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;
}
@ -389,13 +389,13 @@ public static class FrameFinder
if (nature != info.Nature)
continue;
var prev = RNG.LCRNG.Prev(s);
var prev = LCRNG.Prev(s);
var proc = prev >> 16;
bool charmProc = (info.DPPt ? proc / 0x5556 : proc % 3) != 0; // 2/3 odds
if (!charmProc)
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;
namespace PKHeX.Core;
@ -15,7 +15,6 @@ public sealed class FrameGenerator
public readonly bool DPPt;
public readonly bool AllowLeads;
public readonly FrameType FrameType;
public readonly RNG RNG = RNG.LCRNG;
public readonly bool Safari3;
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);
var s1 = seed;
var s2 = RNG.LCRNG.Prev(s1);
var s2 = LCRNG.Prev(s1);
bool charm3 = false;
while (true)
{
@ -38,8 +38,8 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
break;
}
s1 = RNG.LCRNG.Prev(s2);
s2 = RNG.LCRNG.Prev(s1);
s1 = LCRNG.Prev(s2);
s2 = LCRNG.Prev(s1);
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 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.
var seeds = MethodFinder.GetCuteCharmSeeds(pk);
foreach (var seed in seeds)
yield return new SeedInfo(seed);
var seeds = new uint[LCRNG.MaxCountSeedsIV];
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>
@ -73,7 +75,7 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
yield return new SeedInfo(seed);
var s1 = seed;
var s2 = RNG.LCRNG.Prev(s1);
var s2 = LCRNG.Prev(s1);
while (true)
{
var a = s2 >> 16;
@ -91,8 +93,8 @@ public readonly record struct SeedInfo(uint Seed, bool Charm3 = false)
}
}
s1 = RNG.LCRNG.Prev(s2);
s2 = RNG.LCRNG.Prev(s1);
s1 = LCRNG.Prev(s2);
s2 = LCRNG.Prev(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)
{
// pidiv reversed 2x yields SID, 3x yields TID. shift by 7 if another PKM is generated prior
var SIDf = RNG.XDRNG.Reverse(seed, 2);
var TIDf = RNG.XDRNG.Prev(SIDf);
var SIDf = XDRNG.Prev2(seed);
var TIDf = XDRNG.Prev(SIDf);
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)
{
// reverse the seed the bare minimum
int rev = 2;
if (species == (int)Species.Espeon)
rev += 7;
uint SIDf = species == (int)Species.Espeon
? XDRNG.Prev9(seed)
: XDRNG.Prev2(seed);
// reverse until we find the TID/SID, then run the generation forward to see if it matches our inputs.
var rng = RNG.XDRNG;
var SIDf = rng.Reverse(seed, rev);
int ctr = 0;
uint temp;
while ((temp = rng.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID)
while ((temp = XDRNG.Prev(SIDf)) >> 16 != TID || SIDf >> 16 != SID)
{
SIDf = temp;
if (ctr > 32) // arbitrary
@ -88,7 +86,7 @@ public static class LockFinder
ctr++;
}
var next = rng.Next(SIDf);
var next = XDRNG.Next(SIDf);
// generate Umbreon
var PIDIV = GenerateValidColoStarterPID(ref next, TID, SID);
@ -97,7 +95,7 @@ public static class LockFinder
if (!PIDIV.Equals(pkPID, IV1, IV2))
return false;
seed = rng.Reverse(SIDf, 2);
seed = XDRNG.Prev2(SIDf);
return true;
}
@ -108,17 +106,15 @@ public static class LockFinder
private static PIDIVGroup GenerateValidColoStarterPID(ref uint uSeed, int TID, int SID)
{
var rng = RNG.XDRNG;
uSeed = rng.Advance(uSeed, 2); // skip fakePID
uSeed = XDRNG.Next2(uSeed); // skip fakePID
var IV1 = (uSeed >> 16) & 0x7FFF;
uSeed = rng.Next(uSeed);
uSeed = XDRNG.Next(uSeed);
var IV2 = (uSeed >> 16) & 0x7FFF;
uSeed = rng.Next(uSeed);
uSeed = rng.Advance(uSeed, 1); // skip ability call
uSeed = XDRNG.Next(uSeed);
uSeed = XDRNG.Next(uSeed); // skip ability call
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);
}
@ -131,11 +127,11 @@ public static class LockFinder
const byte ratio = 0x1F; // 12.5% F (can't be female)
while (true)
{
var next = RNG.XDRNG.Next(uSeed);
var next = XDRNG.Next(uSeed);
PID = (uSeed & 0xFFFF0000) | (next >> 16);
if ((PID & 0xFF) >= ratio && !IsShiny(TID, SID, PID))
break;
uSeed = RNG.XDRNG.Next(next);
uSeed = XDRNG.Next(next);
}
return PID;

View file

@ -62,7 +62,7 @@ public sealed class TeamLockResult
{
Locks = new Stack<NPCLock>((Specifications = teamSpec).Locks);
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;
Valid = FindLockSeed();

View file

@ -1,6 +1,4 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using static PKHeX.Core.PIDType;
@ -22,44 +20,49 @@ public static class MethodFinder
return AnalyzeGB(pk);
var pid = pk.EncryptionConstant;
var top = pid >> 16;
var bot = pid & 0xFFFF;
var top = pid & 0xFFFF0000;
var bot = pid << 16;
Span<uint> temp = stackalloc uint[6];
for (int i = 0; i < 6; i++)
temp[i] = (uint)pk.GetIV(i);
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;
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;
if (GetColoStarterMatch(pk, top, bot, IVs, out pidiv))
if (GetColoStarterMatch(seeds, pk, top, bot, IVs, out pidiv))
return pidiv;
if (GetXDRNGMatch(pk, top, bot, IVs, out pidiv))
if (GetXDRNGMatch(seeds, pk, top, bot, IVs, out pidiv))
return pidiv;
// Special cases
if (GetLCRNGRoamerMatch(top, bot, IVs, out pidiv))
if (GetLCRNGRoamerMatch(seeds, top, bot, IVs, out pidiv))
return pidiv;
if (GetChannelMatch(top, bot, IVs, out pidiv, pk))
if (GetChannelMatch(seeds, top, bot, IVs, out pidiv, pk))
return pidiv;
if (GetMG4Match(pid, IVs, out pidiv))
if (GetMG4Match(seeds, pid, IVs, out pidiv))
return pidiv;
if (GetBACDMatch(pk, pid, IVs, out pidiv))
if (GetBACDMatch(seeds, pk, pid, IVs, out pidiv))
return pidiv;
if (GetModifiedPIDMatch(pk, pid, IVs, out pidiv))
if (GetModifiedPIDMatch(seeds, pk, pid, IVs, out pidiv))
return pidiv;
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 (GetChainShinyMatch(pk, pid, IVs, out pidiv))
if (GetChainShinyMatch(seeds, pk, pid, IVs, out pidiv))
return true;
if (GetModified8BitMatch(pk, pid, out pidiv))
return true;
@ -80,22 +83,23 @@ public static class MethodFinder
: 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 iv2 = GetIVChunk(IVs, 3);
foreach (var seed in reg)
{
// 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
var C = RNG.LCRNG.Next(B);
var C = LCRNG.Next(B);
var ivC = C >> 16 & 0x7FFF;
if (iv1 == ivC)
{
var D = RNG.LCRNG.Next(C);
var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv2 == ivD) // ABCD
{
@ -103,7 +107,7 @@ public static class MethodFinder
return true;
}
var E = RNG.LCRNG.Next(D);
var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // ABCE
{
@ -113,12 +117,12 @@ public static class MethodFinder
}
else
{
var D = RNG.LCRNG.Next(C);
var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD)
continue;
var E = RNG.LCRNG.Next(D);
var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
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)
{
// A and B are already used by PID
var C = RNG.LCRNG.Advance(seed, 3);
var C = LCRNG.Next3(seed);
// Method 3
var D = RNG.LCRNG.Next(C);
var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD)
continue;
var E = RNG.LCRNG.Next(D);
var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 != ivE)
continue;
@ -148,23 +153,24 @@ public static class MethodFinder
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)
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 iv2 = GetIVChunk(IVs, 3);
foreach (var seed in reg)
{
// 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
var C = RNG.LCRNG.Next(B);
var C = LCRNG.Next(B);
var ivC = C >> 16 & 0x7FFF;
if (iv1 == ivC)
{
var D = RNG.LCRNG.Next(C);
var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv2 == ivD) // BACD
{
@ -172,7 +178,7 @@ public static class MethodFinder
return true;
}
var E = RNG.LCRNG.Next(D);
var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 == ivE) // BACE
{
@ -182,12 +188,12 @@ public static class MethodFinder
}
else
{
var D = RNG.LCRNG.Next(C);
var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD)
continue;
var E = RNG.LCRNG.Next(D);
var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
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)
{
// A and B are already used by PID
var C = RNG.LCRNG.Advance(seed, 3);
var C = LCRNG.Next3(seed);
// Method 3
var D = RNG.LCRNG.Next(C);
var D = LCRNG.Next(C);
var ivD = D >> 16 & 0x7FFF;
if (iv1 != ivD)
continue;
var E = RNG.LCRNG.Next(D);
var E = LCRNG.Next(D);
var ivE = E >> 16 & 0x7FFF;
if (iv2 != ivE)
continue;
@ -217,16 +224,18 @@ public static class MethodFinder
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)
return GetNonMatch(out pidiv);
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)
{
// 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)
continue;
@ -236,19 +245,20 @@ public static class MethodFinder
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)
{
var B = RNG.XDRNG.Prev(seed);
var A = RNG.XDRNG.Prev(B);
var B = XDRNG.Prev(seed);
var A = XDRNG.Prev(B);
var hi = A >> 16;
var lo = B >> 16;
if (IVsMatch(hi, lo, IVs))
{
pidiv = new PIDIV(CXD, RNG.XDRNG.Prev(A));
pidiv = new PIDIV(CXD, XDRNG.Prev(A));
return true;
}
@ -266,18 +276,18 @@ public static class MethodFinder
do
{
B = RNG.XDRNG.Prev(A);
A = RNG.XDRNG.Prev(B);
B = XDRNG.Prev(A);
A = XDRNG.Prev(B);
hi = A >> 16;
lo = B >> 16;
if (IVsMatch(hi, lo, IVs))
{
pidiv = new PIDIV(CXDAnti, RNG.XDRNG.Prev(A));
pidiv = new PIDIV(CXDAnti, XDRNG.Prev(A));
return true;
}
p2 = RNG.XDRNG.Prev(p1);
p1 = RNG.XDRNG.Prev(p2);
p2 = XDRNG.Prev(p1);
p1 = XDRNG.Prev(p2);
psv = (p2 ^ p1) >> 19;
}
while (psv == tsv);
@ -285,50 +295,54 @@ public static class MethodFinder
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;
if (ver is not ((int)GameVersion.R or (int)GameVersion.S))
return GetNonMatch(out pidiv);
var undo = top ^ 0x8000;
if ((undo > 7 ? 0 : 1) != (bot ^ pk.SID ^ 40122))
top = undo;
var channel = GetSeedsFromPIDEuclid(RNG.XDRNG, top, bot);
var undo = (top >> 16) ^ 0x8000;
if ((undo > 7 ? 0 : 1) != ((bot >> 16) ^ pk.SID ^ 40122))
top = (undo << 16);
var count = XDRNG.GetSeeds(seeds, top, bot);
var channel = seeds[..count];
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
var D = RNG.XDRNG.Next(C); // Version
var D = XDRNG.Next(C); // Version
if ((D >> 31) + 1 != ver) // (0-Sapphire, 1-Ruby)
continue;
var E = RNG.XDRNG.Next(D); // OT Gender
var E = XDRNG.Next(D); // OT Gender
if (E >> 31 != pk.OT_Gender)
continue;
if (!RNG.XDRNG.GetSequentialIVsUInt32(E, IVs))
if (!XDRNG.GetSequentialIVsUInt32(E, IVs))
continue;
if (seed >> 16 != pk.SID)
continue;
pidiv = new PIDIV(Channel, RNG.XDRNG.Prev(seed));
pidiv = new PIDIV(Channel, XDRNG.Prev(seed));
return true;
}
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);
var mg4 = GetSeedsFromPID(RNG.LCRNG, mg4Rev >> 16, mg4Rev & 0xFFFF);
uint mg4Rev = ARNG.Prev(pid);
var count = LCRNGReversal.GetSeeds(seeds, mg4Rev << 16, mg4Rev & 0xFFFF0000);
var mg4 = seeds[..count];
foreach (var seed in mg4)
{
var B = RNG.LCRNG.Advance(seed, 2);
var C = RNG.LCRNG.Next(B);
var D = RNG.LCRNG.Next(C);
var B = LCRNG.Next2(seed);
var C = LCRNG.Next(B);
var D = LCRNG.Next(C);
if (!IVsMatch(C >> 16, D >> 16, IVs))
continue;
@ -361,10 +375,13 @@ public static class MethodFinder
return GetNonMatch(out pidiv);
(var species, int genderValue) = GetCuteCharmGenderSpecies(pk, pid, pk.Species);
if ((uint)species > Legal.MaxSpeciesID_4)
return GetNonMatch(out pidiv);
static int getRatio(ushort species)
{
return species <= Legal.MaxSpeciesID_4
? PersonalTable.HGSS[species].Gender
: PKX.Personal[species].Gender;
}
static int getRatio(ushort species) => PersonalTable.HGSS[species].Gender;
switch (genderValue)
{
case 2: break; // can't cute charm a genderless pk
@ -391,15 +408,17 @@ public static class MethodFinder
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
// PIDH & 7
// PIDL & 7
// IVs
var bot = GetIVChunk(IVs, 0);
var top = GetIVChunk(IVs, 3);
var reg = GetSeedsFromIVs(RNG.LCRNG, top, bot);
var bot = GetIVChunk(IVs, 0) << 16;
var top = GetIVChunk(IVs, 3) << 16;
var count = LCRNGReversal.GetSeedsIVs(seeds, bot, top);
var reg = seeds[..count];
foreach (var seed in reg)
{
// check the individual bits
@ -410,7 +429,7 @@ public static class MethodFinder
var bit = s >> 16 & 1;
if (bit != (pid >> i & 1))
break;
s = RNG.LCRNG.Prev(s);
s = LCRNG.Prev(s);
}
while (--i != 2);
if (i != 2) // bit failed
@ -419,7 +438,7 @@ public static class MethodFinder
var upper = s;
if ((upper >> 16 & 7) != (pid >> 16 & 7))
continue;
var lower = RNG.LCRNG.Prev(upper);
var lower = LCRNG.Prev(upper);
if ((lower >> 16 & 7) != (pid & 7))
continue;
@ -427,34 +446,25 @@ public static class MethodFinder
if (upid != pid >> 16)
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);
return true;
}
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];
for (int i = 0; i < 6; i++)
IVs[i] = (uint)pk.GetIV(i);
var bot = GetIVChunk(IVs, 0);
var top = GetIVChunk(IVs, 3);
var bot = GetIVChunk(IVs, 0) << 16;
var top = GetIVChunk(IVs, 3) << 16;
return GetSeedsFromIVs(RNG.LCRNG, top, bot);
}
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);
var count = LCRNGReversal.GetSeedsIVs(seeds, bot, top);
var reg = seeds[..count];
PIDType type = BACD_U;
foreach (var seed in reg)
{
var B = seed;
var A = RNG.LCRNG.Prev(B);
var A = LCRNG.Prev(B);
var low = B >> 16;
var PID = (A & 0xFFFF0000) | low;
@ -482,11 +492,11 @@ public static class MethodFinder
type = BACD_U_A;
}
}
var s = RNG.LCRNG.Prev(A);
var s = LCRNG.Prev(A);
// Check for prior Restricted seed
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)
continue;
@ -540,7 +550,7 @@ public static class MethodFinder
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
{
@ -553,7 +563,9 @@ public static class MethodFinder
var iv1 = GetIVChunk(IVs, 0);
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)
{
uint origin = seed;
@ -597,7 +609,7 @@ public static class MethodFinder
// 3-FORCEBITS
// 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);
PID &= 0xFFFFFFF8;
PID |= low & 0x7; // lowest 3 bits
@ -644,75 +656,6 @@ public static class MethodFinder
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>
/// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each).
/// </summary>
@ -762,41 +705,7 @@ public static class MethodFinder
val |= IVs[i+start] << (5*i);
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)
{
s = seed;
@ -806,14 +715,14 @@ public static class MethodFinder
// todo
}
// check for valid activation
s = RNG.XDRNG.Prev(seed);
s = XDRNG.Prev(seed);
if ((s >> 16) % 3 != 0)
{
if ((s >> 16) % 100 < 10) // can't fail a munchlax/bonsly encounter check
{
// todo
}
s = RNG.XDRNG.Prev(s);
s = XDRNG.Prev(s);
if ((s >> 16) % 3 != 0) // can't activate even if generous
{
// todo
@ -913,7 +822,7 @@ public static class MethodFinder
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
}
}
@ -954,7 +863,27 @@ public static class MethodFinder
// Future evolutions
(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),
};
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;
@ -9,12 +9,11 @@ public static class PIDGenerator
{
private static void SetValuesFromSeedLCRNG(PKM pk, PIDType type, uint seed)
{
var rng = RNG.LCRNG;
var A = rng.Next(seed);
var B = rng.Next(A);
var A = LCRNG.Next(seed);
var B = LCRNG.Next(A);
var skipBetweenPID = type is PIDType.Method_3 or PIDType.Method_3_Unown;
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;
if (swappedPIDHalves) // switched order of PID halves, "BA.."
@ -22,15 +21,15 @@ public static class PIDGenerator
else
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;
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;
if (skipIV2Frame) // VBlank skip between IVs
D = rng.Next(D);
D = LCRNG.Next(D);
Span<int> IVs = stackalloc int[6];
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)
{
var rng = RNG.LCRNG;
bool shiny = type is PIDType.BACD_R_S or PIDType.BACD_U_S;
uint X = shiny ? rng.Next(seed) : seed;
var A = rng.Next(X);
var B = rng.Next(A);
var C = rng.Next(B);
var D = rng.Next(C);
uint X = shiny ? LCRNG.Next(seed) : seed;
var A = LCRNG.Next(X);
var B = LCRNG.Next(A);
var C = LCRNG.Next(B);
var D = LCRNG.Next(C);
if (shiny)
{
@ -83,25 +81,24 @@ public static class PIDGenerator
private static void SetValuesFromSeedXDRNG(PKM pk, uint seed)
{
var rng = RNG.XDRNG;
switch (pk.Species)
{
case (int)Species.Umbreon or (int)Species.Eevee: // Colo Umbreon, XD Eevee
pk.TID = (int)((seed = rng.Next(seed)) >> 16);
pk.SID = (int)((seed = rng.Next(seed)) >> 16);
seed = rng.Advance(seed, 2); // PID calls consumed
pk.TID = (int)((seed = XDRNG.Next(seed)) >> 16);
pk.SID = (int)((seed = XDRNG.Next(seed)) >> 16);
seed = XDRNG.Next2(seed); // PID calls consumed
break;
case (int)Species.Espeon: // Colo Espeon
pk.TID = (int)((seed = rng.Next(seed)) >> 16);
pk.SID = (int)((seed = rng.Next(seed)) >> 16);
seed = rng.Advance(seed, 9); // PID calls consumed, skip over Umbreon
pk.TID = (int)((seed = XDRNG.Next(seed)) >> 16);
pk.SID = (int)((seed = XDRNG.Next(seed)) >> 16);
seed = XDRNG.Next9(seed); // PID calls consumed, skip over Umbreon
break;
}
var A = rng.Next(seed); // IV1
var B = rng.Next(A); // IV2
var C = rng.Next(B); // Ability?
var D = rng.Next(C); // PID
var E = rng.Next(D); // PID
var A = XDRNG.Next(seed); // IV1
var B = XDRNG.Next(A); // IV2
var C = XDRNG.Next(B); // Ability?
var D = XDRNG.Next(C); // PID
var E = XDRNG.Next(D); // PID
pk.PID = (D & 0xFFFF0000) | (E >> 16);
Span<int> IVs = stackalloc int[6];
@ -111,23 +108,20 @@ public static class PIDGenerator
public static void SetValuesFromSeedXDRNG_EReader(PKM pk, uint seed)
{
var rng = RNG.XDRNG;
var A = rng.Reverse(seed, 4);
var D = rng.Next(A); // PID
var E = rng.Next(D); // PID
var D = XDRNG.Prev3(seed); // PID
var E = XDRNG.Next(D); // PID
pk.PID = (D & 0xFFFF0000) | (E >> 16);
}
private static void SetValuesFromSeedChannel(PKM pk, uint seed)
{
var rng = RNG.XDRNG;
var O = rng.Next(seed); // SID
var A = rng.Next(O); // PID
var B = rng.Next(A); // PID
var C = rng.Next(B); // Held Item
var D = rng.Next(C); // Version
var E = rng.Next(D); // OT Gender
var O = XDRNG.Next(seed); // SID
var A = XDRNG.Next(O); // PID
var B = XDRNG.Next(A); // PID
var C = XDRNG.Next(B); // Held Item
var D = XDRNG.Next(C); // Version
var E = XDRNG.Next(D); // OT Gender
const int TID = 40122;
var SID = (int)(O >> 16);
@ -143,7 +137,7 @@ public static class PIDGenerator
pk.Version = (int)(D >> 31) + 1; // 0-Sapphire, 1-Ruby
pk.OT_Gender = (int)(E >> 31);
Span<int> ivs = stackalloc int[6];
rng.GetSequentialIVsInt32(E, ivs);
XDRNG.GetSequentialIVsInt32(E, ivs);
pk.SetIVs(ivs);
}
@ -204,7 +198,7 @@ public static class PIDGenerator
// 1 3-bit for upper
// 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 upper = Next() & 7;
for (int i = 0; i < 13; i++)
@ -226,9 +220,8 @@ public static class PIDGenerator
if (!MethodFinder.IsPokeSpotActivation(slot, seed, out _))
continue;
var rng = RNG.XDRNG;
var D = rng.Next(seed); // PID
var E = rng.Next(D); // PID
var D = XDRNG.Next(seed); // PID
var E = XDRNG.Next(D); // PID
pk.PID = (D & 0xFFFF0000) | (E >> 16);
if (!IsValidCriteria4(pk, nature, ability, gender))

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using FluentAssertions;
using PKHeX.Core;
@ -153,13 +153,17 @@ public static class PIDTests
public static void VerifyResults(IReadOnlyList<uint[]> results, TeamLock[] team)
{
var pk = new PK3();
Span<uint> seeds = stackalloc uint[XDRNG.MaxCountSeedsPID];
for (int i = 0; i < results.Count; 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;
foreach (var seed in seeds)
foreach (var s in reg)
{
var seed = XDRNG.Prev3(s);
PIDGenerator.SetValuesFromSeed(pk, PIDType.CXD, seed);
var info = MethodFinder.Analyze(pk);
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");
}
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 =

View file

@ -1,3 +1,4 @@
using System;
using System.Linq;
using FluentAssertions;
using PKHeX.Core;
@ -145,11 +146,11 @@ public class PIDIVTest
{
// XD PokeSpots: Check all 3 Encounter Slots (examples are one for each location).
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)
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)
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]
@ -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]
public void PIDIVMethod4IVs()
{
@ -225,7 +240,11 @@ public class PIDIVTest
rand1 |= (uint)IVs[i] << (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]
@ -234,9 +253,16 @@ public class PIDIVTest
const uint seed = 0x2E15555E;
const uint rand0 = 0x20AD96A9;
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);
Assert.Contains(pidseeds, z => z == seed);
Assert.Contains(ivseeds, z => z == seed);
XDRNG.MaxCountSeedsIV.Should().BeGreaterThan(XDRNG.MaxCountSeedsPID);
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);
}
}