diff --git a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs index ac1c382f8..9494e5d01 100644 --- a/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs +++ b/PKHeX.Core/Legality/Encounters/Generator/ByGeneration/EncounterGenerator3.cs @@ -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 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; } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/ARNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/ARNG.cs new file mode 100644 index 000000000..353d61fbe --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/ARNG.cs @@ -0,0 +1,66 @@ +using System.Runtime.CompilerServices; + +namespace PKHeX.Core; + +/// +/// 32 Bit Linear Congruential Random Number Generator +/// +/// Frame advancement for forward and reverse. +///
+/// https://en.wikipedia.org/wiki/Linear_congruential_generator +///
+///
+/// seed_n+1 = seed_n * + +///
+///
+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; + + /// + /// Advances the RNG seed to the next state value. + /// + /// Current seed + /// Seed advanced a single time. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Next(uint seed) => (seed * Mult) + Add; + + /// + /// Reverses the RNG seed to the previous state value. + /// + /// Current seed + /// Seed reversed a single time. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Prev(uint seed) => (seed * rMult) + rAdd; + + /// + /// Advances the RNG seed to the next state value a specified amount of times. + /// + /// Current seed + /// Amount of times to advance. + /// Seed advanced the specified amount of times. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Advance(uint seed, int frames) + { + for (int i = 0; i < frames; i++) + seed = Next(seed); + return seed; + } + + /// + /// Reverses the RNG seed to the previous state value a specified amount of times. + /// + /// Current seed + /// Amount of times to reverse. + /// Seed reversed the specified amount of times. + public static uint Reverse(uint seed, int frames) + { + for (int i = 0; i < frames; i++) + seed = Prev(seed); + return seed; + } +} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs index 739fe22f7..9432853a1 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs @@ -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 * + ///
/// -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 - /// - /// Advances the RNG seed to the next state value. - /// - /// Current seed - /// Seed advanced a single time. - [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 - /// - /// Reverses the RNG seed to the previous state value. - /// - /// Current seed - /// Seed reversed a single time. - [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; /// /// Advances the RNG seed to the next state value a specified amount of times. @@ -54,7 +91,7 @@ public class LCRNG /// Amount of times to advance. /// Seed advanced the specified amount of times. [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 /// Current seed /// Amount of times to reverse. /// Seed reversed the specified amount of times. - [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); diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs new file mode 100644 index 000000000..7f44b213b --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs @@ -0,0 +1,148 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Seed reversal logic for the algorithm. +/// +/// +/// 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 +/// +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. + } + } + + /// + /// Finds all seeds that can generate the by two successive rand() calls. + /// + /// Result storage array, to be populated starting at index 0. + /// PID to be reversed into seeds that generate it. + /// Count of results added to + public static int GetSeeds(Span result, uint pid) + { + uint first = pid << 16; + uint second = pid & 0xFFFF_0000; + return GetSeeds(result, first, second); + } + + /// + /// Finds all seeds that can generate the IVs by two successive rand() calls. + /// + /// Result storage array, to be populated starting at index 0. + /// Entity IV for HP + /// Entity IV for Attack + /// Entity IV for Defense + /// Entity IV for Special Attack + /// Entity IV for Special Defense + /// Entity IV for Speed + /// Count of results added to + public static int GetSeedsIVs(Span 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); + } + + /// + /// Finds all the origin seeds for two 16 bit rand() calls + /// + /// Result storage array, to be populated starting at index 0. + /// First rand() call, 16 bits, already shifted left 16 bits. + /// Second rand() call, 16 bits, already shifted left 16 bits. + /// Count of results added to + public static int GetSeeds(Span 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; + } + + /// + /// Finds all the origin seeds for two 15 bit rand() calls + /// + /// Result storage array, to be populated starting at index 0. + /// First rand() call, 15 bits, already shifted left 16 bits. + /// Second rand() call, 15 bits, already shifted left 16 bits. + /// Count of results added to + public static int GetSeedsIVs(Span 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; + } +} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs new file mode 100644 index 000000000..cf975cb03 --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs @@ -0,0 +1,147 @@ +using System; + +namespace PKHeX.Core; + +/// +/// Seed reversal logic for the algorithm, with a gap in between the two observed rand() results. +/// +/// +/// 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 +/// +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. + } + } + + /// + /// Finds all seeds that can generate the with a discarded rand() between the two halves. + /// + /// Result storage array, to be populated starting at index 0. + /// PID to be reversed into seeds that generate it. + /// Count of results added to + public static int GetSeeds(Span result, uint pid) + { + uint first = pid << 16; + uint second = pid & 0xFFFF_0000; + return GetSeeds(result, first, second); + } + + /// + /// Finds all seeds that can generate the IVs, with a vblank skip between the two IV rand() calls. + /// + /// Result storage array, to be populated starting at index 0. + /// Entity IV for HP + /// Entity IV for Attack + /// Entity IV for Defense + /// Entity IV for Special Attack + /// Entity IV for Special Defense + /// Entity IV for Speed + /// Count of results added to + public static int GetSeedsIVs(Span 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); + } + + /// + /// Finds all the origin seeds for two 16 bit rand() calls (ignoring a rand() in between) + /// + /// Result storage array, to be populated starting at index 0. + /// First rand() call, 16 bits, already shifted left 16 bits. + /// Third rand() call, 16 bits, already shifted left 16 bits. + /// Count of results added to + public static int GetSeeds(Span 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; + } + + /// + /// Finds all the origin seeds for two 15 bit rand() calls (ignoring a rand() in between) + /// + /// Result storage array, to be populated starting at index 0. + /// First rand() call, 15 bits, already shifted left 16 bits. + /// Third rand() call, 15 bits, already shifted left 16 bits. + /// Count of results added to + public static int GetSeedsIVs(Span 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; + } +} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs deleted file mode 100644 index 5383de315..000000000 --- a/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs +++ /dev/null @@ -1,187 +0,0 @@ -using System.Collections.Generic; -using System.Runtime.CompilerServices; - -namespace PKHeX.Core; - -/// -/// -/// -/// -/// -///
-/// Provides common RNG algorithms used by Generation 3 & 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. -///
-///
-public sealed class RNG : LCRNG -{ - /// LCRNG used for Encryption and mainline game RNG calls. - public static readonly RNG LCRNG = new(0x41C64E6D, 0x00006073, 0xEEB9EB65, 0x0A3561A1); - - /// LCRNG used by Colosseum & XD for game RNG calls. - public static readonly RNG XDRNG = new(0x000343FD, 0x00269EC3, 0xB9B33155, 0xA170F641); - - /// Alternate LCRNG used by mainline game RNG calls to disassociate the seed from the , for anti-shiny and other purposes. - 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 - - /// - /// Gets the origin seeds for two successive 16 bit rand() calls using a meet-in-the-middle approach. - /// - /// First rand() call, 16 bits, already shifted left 16 bits. - /// Second rand() call, 16 bits, already shifted left 16 bits. - /// - /// 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 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable 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]); - } - } - - /// - /// Gets the origin seeds for two 16 bit rand() calls (ignoring a rand() in between) using a meet-in-the-middle approach. - /// - /// First rand() call, 16 bits, already shifted left 16 bits. - /// Third rand() call, 16 bits, already shifted left 16 bits. - /// - /// 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 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable 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]); - } - } - - /// - /// Gets the origin seeds for two successive 16 bit rand() calls using a Euclidean division approach. - /// - /// First rand() call, 16 bits, already shifted left 16 bits. - /// Second rand() call, 16 bits, already shifted left 16 bits. - /// - /// 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 . - /// https://crypto.stackexchange.com/a/10629 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable RecoverLower16BitsEuclid16(uint first, uint second) - { - const int bitshift = 32; - const long inc = 1L << bitshift; - return GetPossibleSeedsEuclid(first, second, bitshift, inc); - } - - /// - /// Gets the origin seeds for two successive 15 bit rand() calls using a Euclidean division approach. - /// - /// First rand() call, 15 bits, already shifted left 16 bits. - /// Second rand() call, 15 bits, already shifted left 16 bits. - /// - /// 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 . - /// https://crypto.stackexchange.com/a/10629 - /// - /// Possible origin seeds that generate the 2 random numbers - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal IEnumerable 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 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); - } - } -} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs deleted file mode 100644 index 93abe597d..000000000 --- a/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Runtime.CompilerServices; - -namespace PKHeX.Core; - -public static class RNGUtil -{ - /// - /// Generates an IV for each RNG call using the top 5 bits of frame seeds. - /// - /// RNG to use - /// RNG seed - /// Expected IVs - /// True if all match. - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static bool GetSequentialIVsUInt32(this LCRNG rng, uint seed, ReadOnlySpan IVs) - { - foreach (var iv in IVs) - { - seed = rng.Next(seed); - var IV = seed >> 27; - if (IV != iv) - return false; - } - return true; - } - - /// - /// Generates an IV for each RNG call using the top 5 bits of frame seeds. - /// - /// RNG to use - /// RNG seed - /// Buffer to store generated values - /// Array of 6 IVs as . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void GetSequentialIVsInt32(this LCRNG rng, uint seed, Span ivs) - { - for (int i = 0; i < ivs.Length; i++) - { - seed = rng.Next(seed); - ivs[i] = (int)(seed >> 27); - } - } -} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs new file mode 100644 index 000000000..f87e8c191 --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs @@ -0,0 +1,235 @@ +using System; +using System.Runtime.CompilerServices; + +namespace PKHeX.Core; + +/// +/// 32 Bit Linear Congruential Random Number Generator +/// +/// Frame advancement for forward and reverse. +///
+/// https://en.wikipedia.org/wiki/Linear_congruential_generator +///
+///
+/// seed_n+1 = seed_n * + +///
+///
+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; + + /// + /// Advances the RNG seed to the next state value a specified amount of times. + /// + /// Current seed + /// Amount of times to advance. + /// Seed advanced the specified amount of times. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Advance(uint seed, int frames) + { + for (int i = 0; i < frames; i++) + seed = Next(seed); + return seed; + } + + /// + /// Reverses the RNG seed to the previous state value a specified amount of times. + /// + /// Current seed + /// Amount of times to reverse. + /// Seed reversed the specified amount of times. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static uint Reverse(uint seed, int frames) + { + for (int i = 0; i < frames; i++) + seed = Prev(seed); + return seed; + } + + /// + /// Generates an IV for each RNG call using the top 5 bits of frame seeds. + /// + /// RNG seed + /// Expected IVs + /// True if all match. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static bool GetSequentialIVsUInt32(uint seed, ReadOnlySpan IVs) + { + foreach (var iv in IVs) + { + seed = Next(seed); + var expect = seed >> 27; + if (iv != expect) + return false; + } + return true; + } + + /// + /// Generates an IV for each RNG call using the top 5 bits of frame seeds. + /// + /// RNG seed + /// Buffer to store generated values + /// Array of 6 IVs as . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void GetSequentialIVsInt32(uint seed, Span 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; + + /// + /// Finds all seeds that can generate the by two successive rand() calls. + /// + /// Result storage array, to be populated starting at index 0. + /// PID to be reversed into seeds that generate it. + /// Count of results added to + public static int GetSeeds(Span result, uint pid) + { + uint first = pid & 0xFFFF_0000; + uint second = pid << 16; + return GetSeeds(result, first, second); + } + + /// + /// Finds all seeds that can generate the IVs by two successive rand() calls. + /// + /// Result storage array, to be populated starting at index 0. + /// Entity IV for HP + /// Entity IV for Attack + /// Entity IV for Defense + /// Entity IV for Special Attack + /// Entity IV for Special Defense + /// Entity IV for Speed + /// Count of results added to + public static int GetSeeds(Span 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); + } + + /// + /// Finds all the origin seeds for two 16 bit rand() calls + /// + /// Result storage array, to be populated starting at index 0. + /// First rand() call, 16 bits, already shifted left 16 bits. + /// Second rand() call, 16 bits, already shifted left 16 bits. + /// Count of results added to + public static int GetSeeds(Span 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; + } + + /// + /// Finds all the origin seeds for two 15 bit rand() calls + /// + /// Result storage array, to be populated starting at index 0. + /// First rand() call, 15 bits, already shifted left 16 bits. + /// Second rand() call, 15 bits, already shifted left 16 bits. + /// Count of results added to + public static int GetSeedsIVs(Span 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; + } +} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/XorShift128.cs b/PKHeX.Core/Legality/RNG/Algorithms/XorShift128.cs index 6bb0d96b3..87aef60c9 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/XorShift128.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/XorShift128.cs @@ -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; /// - /// Uses the to advance the seed for each 32-bit input. + /// Uses the to advance the seed for each 32-bit input. /// /// 32 bit seed /// sub_E0F5E0 in v1.1.3 diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs b/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs index e5a1376f7..01a5b25ac 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameCache.cs @@ -1,34 +1,28 @@ -using System; using System.Collections.Generic; using System.Runtime.CompilerServices; namespace PKHeX.Core; /// -/// Frame List used to cache results. +/// Frame List used to cache results, lazily reversing backwards and keeping the previous results. /// public sealed class FrameCache { private const int DefaultSize = 32; private readonly List Seeds = new(DefaultSize); private readonly List Values = new(DefaultSize); - private readonly Func Advance; + private uint Last; /// /// Creates a new instance of a . /// /// Seed at frame 0. - /// method used to get the next seed. Can use or . - public FrameCache(uint origin, Func 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]; } } diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs index ebabb6a83..cef1bdc7f 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameFinder.cs @@ -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); } } } diff --git a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs index 6f4bab3b3..d64e3a744 100644 --- a/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs +++ b/PKHeX.Core/Legality/RNG/Frame/FrameGenerator.cs @@ -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); diff --git a/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs b/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs index 088a33578..7f9bba6b5 100644 --- a/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs +++ b/PKHeX.Core/Legality/RNG/Frame/SeedInfo.cs @@ -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]); } /// @@ -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); } diff --git a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs index 99676c7d7..ecc611289 100644 --- a/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs +++ b/PKHeX.Core/Legality/RNG/Locks/LockFinder.cs @@ -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; diff --git a/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs b/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs index 0e00efab5..841616504 100644 --- a/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs +++ b/PKHeX.Core/Legality/RNG/Locks/TeamLockResult.cs @@ -62,7 +62,7 @@ public sealed class TeamLockResult { Locks = new Stack((Specifications = teamSpec).Locks); Team = new Stack(Locks.Count); - Cache = new FrameCache(RNG.XDRNG.Reverse(originSeed, 2), RNG.XDRNG.Prev); + Cache = new FrameCache(XDRNG.Prev2(originSeed)); TSV = tsv; Valid = FindLockSeed(); diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index d06e81158..3c57bc441 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -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 temp = stackalloc uint[6]; for (int i = 0; i < 6; i++) temp[i] = (uint)pk.GetIV(i); ReadOnlySpan 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 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 IVs, out PIDIV pidiv) + private static bool GetModifiedPIDMatch(Span seeds, PKM pk, uint pid, ReadOnlySpan 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 IVs, out PIDIV pidiv) + private static bool GetLCRNGMatch(Span seeds, uint top, uint bot, ReadOnlySpan 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 IVs, out PIDIV pidiv) + private static bool GetLCRNGUnownMatch(Span seeds, uint top, uint bot, ReadOnlySpan 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 IVs, out PIDIV pidiv) + private static bool GetLCRNGRoamerMatch(Span seeds, uint top, uint bot, ReadOnlySpan 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 IVs, out PIDIV pidiv) + private static bool GetXDRNGMatch(Span seeds, PKM pk, uint top, uint bot, ReadOnlySpan 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 IVs, out PIDIV pidiv, PKM pk) + private static bool GetChannelMatch(Span seeds, uint top, uint bot, ReadOnlySpan 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 IVs, out PIDIV pidiv) + private static bool GetMG4Match(Span seeds, uint pid, ReadOnlySpan 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 IVs, out PIDIV pidiv) + private static bool GetChainShinyMatch(Span seeds, PKM pk, uint pid, ReadOnlySpan 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 GetCuteCharmSeeds(PKM pk) + private static bool GetBACDMatch(Span seeds, PKM pk, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) { - Span 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 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 IVs, out PIDIV pidiv) + private static bool GetColoStarterMatch(Span seeds, PKM pk, uint top, uint bot, ReadOnlySpan 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 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 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 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 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 GetSeedsFromPIDEuclid(RNG method, uint rand1, uint rand2) - { - return method.RecoverLower16BitsEuclid16(rand1 << 16, rand2 << 16); - } - - public static IEnumerable GetSeedsFromIVsEuclid(RNG method, uint rand1, uint rand2) - { - return method.RecoverLower16BitsEuclid15(rand1 << 16, rand2 << 16); - } - /// /// Generates IVs from 2 RNG calls using 15 bits of each to generate 6 IVs (5bits each). /// @@ -762,41 +705,7 @@ public static class MethodFinder val |= IVs[i+start] << (5*i); return val; } - - public static IEnumerable 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 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 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; + } } diff --git a/PKHeX.Core/Legality/RNG/PIDGenerator.cs b/PKHeX.Core/Legality/RNG/PIDGenerator.cs index 64c7b3f2e..0652bb842 100644 --- a/PKHeX.Core/Legality/RNG/PIDGenerator.cs +++ b/PKHeX.Core/Legality/RNG/PIDGenerator.cs @@ -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 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 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 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)) diff --git a/PKHeX.Core/Legality/RNG/PIDType.cs b/PKHeX.Core/Legality/RNG/PIDType.cs index 159d8b959..66b04a11d 100644 --- a/PKHeX.Core/Legality/RNG/PIDType.cs +++ b/PKHeX.Core/Legality/RNG/PIDType.cs @@ -12,99 +12,99 @@ public enum PIDType #region LCRNG /// Method 1 Variants (H1/J/K) - /// + /// Method_1, /// Method H2 - /// + /// Method_2, /// Method H3 - /// + /// Method_3, /// Method H4 - /// + /// Method_4, /// Method H1_Unown (FRLG) - /// + /// Method_1_Unown, /// Method H2_Unown (FRLG) - /// + /// Method_2_Unown, /// Method H3_Unown (FRLG) - /// + /// Method_3_Unown, /// Method H4_Unown (FRLG) - /// + /// Method_4_Unown, /// Method 1 Roamer (Gen3) - /// + /// Method_1_Roamer, /// /// Event Reversed Order PID restricted to 16bit Origin Seed /// - /// seed is clamped to 16bits. + /// seed is clamped to 16bits. BACD_R, /// /// Event Reversed Order PID without Origin Seed restrictions /// - /// + /// BACD_U, /// /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny. /// - /// seed is clamped to 16bits. + /// seed is clamped to 16bits. BACD_R_A, /// /// Event Reversed Order PID without Origin Seed restrictions, antishiny. /// - /// + /// BACD_U_A, /// /// Event Reversed Order PID restricted to 8bit Origin Seed, shiny /// - /// seed is clamped to 16bits. + /// seed is clamped to 16bits. BACD_R_S, /// /// Event Reversed Order PID without Origin Seed restrictions, shiny /// - /// + /// BACD_U_S, /// /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny (nyx) /// - /// seed is clamped to 16bits. + /// seed is clamped to 16bits. BACD_R_AX, /// /// Event Reversed Order PID without Origin Seed restrictions, antishiny (nyx) /// - /// + /// BACD_U_AX, /// /// Generation 4 Cute Charm PID, which is forced to an 8 bit PID value based on the gender & gender ratio value. /// - /// + /// CuteCharm, /// /// Generation 4 Chained Shiny /// - /// + /// ChainShiny, #endregion @@ -114,31 +114,31 @@ public enum PIDType /// /// Generation 3 PID+IV correlation. /// - /// + /// CXD, /// /// Generation 3 PID+IV correlation that was rerolled because it was shiny. /// - /// + /// CXDAnti, /// /// Generation 3 PID+IV which is created immediately after the TID and SID RNG calls. /// - /// . The second starter is created after the first starter, with the same TID and SID. + /// . The second starter is created after the first starter, with the same TID and SID. CXD_ColoStarter, /// /// Generation 3 Pokémon Channel Jirachi /// - /// + /// Channel, /// /// Generation 3 PokeSpot PID /// - /// + /// PokeSpot, #endregion @@ -148,7 +148,7 @@ public enum PIDType /// /// Generation 4 Mystery Gift Anti-Shiny /// - /// + /// G4MGAntiShiny, #endregion diff --git a/PKHeX.Core/Legality/Tables/MystryMew.cs b/PKHeX.Core/Legality/Tables/MystryMew.cs index 2d41cfb4b..a8920b7a4 100644 --- a/PKHeX.Core/Legality/Tables/MystryMew.cs +++ b/PKHeX.Core/Legality/Tables/MystryMew.cs @@ -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; /// @@ -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; diff --git a/PKHeX.Core/MysteryGifts/PGT.cs b/PKHeX.Core/MysteryGifts/PGT.cs index ff2e9113d..ea274757a 100644 --- a/PKHeX.Core/MysteryGifts/PGT.cs +++ b/PKHeX.Core/MysteryGifts/PGT.cs @@ -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; } diff --git a/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs b/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs index 78fe62bba..12b50cca5 100644 --- a/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs +++ b/Tests/PKHeX.Core.Tests/Legality/ShadowTests.cs @@ -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 results, TeamLock[] team) { var pk = new PK3(); + Span 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 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 = diff --git a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs index d94e732da..c3ab16040 100644 --- a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs @@ -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 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 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 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); } }