diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs new file mode 100644 index 000000000..582278c0b --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNG.cs @@ -0,0 +1,78 @@ +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 class LCRNG + { + // Forward + protected readonly uint Mult; + private readonly uint Add; + + // Reverse + private readonly uint rMult; + private readonly uint rAdd; + + 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; + } + + /// + /// 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; + + /// + /// 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; + + /// + /// 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 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 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/RNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs similarity index 64% rename from PKHeX.Core/Legality/RNG/RNG.cs rename to PKHeX.Core/Legality/RNG/Algorithms/RNG.cs index f07734b8e..269747889 100644 --- a/PKHeX.Core/Legality/RNG/RNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/RNG.cs @@ -1,26 +1,31 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Runtime.CompilerServices; namespace PKHeX.Core { /// - /// 32 Bit Linear Congruential Random Number Generator + /// /// /// + /// + ///
/// Provides common RNG algorithms used by Generation 3 & 4. - /// https://en.wikipedia.org/wiki/Linear_congruential_generator + /// 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 + 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 RNG ARNG = new(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93); - private readonly uint Mult, Add, rMult, rAdd; + /// 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; @@ -33,36 +38,35 @@ public sealed class RNG 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) - private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add) - { - Mult = f_mult; - Add = f_add; - rMult = r_mult; - rAdd = r_add; + #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 = Mult << 8; - k0g = Mult * Mult; + k2 = f_mult << 8; + k0g = f_mult * f_mult; k2s = k0g << 8; - PopulateMeetMiddleArrays(); - t0 = Add - 0xFFFF; - t1 = 0xFFFF * ((long) Mult + 1); - } - private void PopulateMeetMiddleArrays() - { - uint k4g = Add * (Mult + 1); // 1,3's multiplier + // 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, Mult, Add, flags, low8); // 1,2 - SetFlagData(i, k0g, k4g, g_flags, g_low8); // 1,3 + 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. @@ -79,83 +83,7 @@ private static void SetFlagData(uint i, uint mult, uint add, bool[] f, byte[] v) // now the search only has to access the flags array once per loop. } - /// - /// 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; - - /// - /// 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; - - /// - /// 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 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 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 - /// Array of 6 IVs as . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal uint[] GetSequentialIVsUInt32(uint seed) - { - uint[] ivs = new uint[6]; - for (int i = 0; i < 6; i++) - { - seed = Next(seed); - ivs[i] = seed >> 27; - } - return ivs; - } - - /// - /// Generates an IV for each RNG call using the top 5 bits of frame seeds. - /// - /// RNG seed - /// Array of 6 IVs as . - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal int[] GetSequentialIVsInt32(uint seed) - { - int[] ivs = new int[6]; - for (int i = 0; i < 6; i++) - { - seed = Next(seed); - ivs[i] = (int)(seed >> 27); - } - return ivs; - } + #endregion /// /// Gets the origin seeds for two successive 16 bit rand() calls using a meet-in-the-middle approach. @@ -257,30 +185,4 @@ private IEnumerable GetPossibleSeedsEuclid(uint first, uint second, int bi } } } - - public enum RNGType - { - /// No RNG type specified - None, - - /// - LCRNG, - - /// - XDRNG, - - /// - ARNG, - } - - public static class RNGTypeUtil - { - public static RNG GetRNG(this RNGType type) => type switch - { - RNGType.LCRNG => RNG.LCRNG, - RNGType.XDRNG => RNG.XDRNG, - RNGType.ARNG => RNG.ARNG, - _ => throw new ArgumentException(nameof(type)) - }; - } } diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs new file mode 100644 index 000000000..cd18e610d --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/RNGType.cs @@ -0,0 +1,30 @@ +using System; + +namespace PKHeX.Core +{ + public enum RNGType + { + /// No RNG type specified + None, + + /// + LCRNG, + + /// + XDRNG, + + /// + ARNG, + } + + public static class RNGTypeUtil + { + public static LCRNG GetRNG(this RNGType type) => type switch + { + RNGType.LCRNG => RNG.LCRNG, + RNGType.XDRNG => RNG.XDRNG, + RNGType.ARNG => RNG.ARNG, + _ => throw new ArgumentException(nameof(type)) + }; + } +} diff --git a/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs b/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs new file mode 100644 index 000000000..115a015c4 --- /dev/null +++ b/PKHeX.Core/Legality/RNG/Algorithms/RNGUtil.cs @@ -0,0 +1,43 @@ +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 + /// Array of 6 IVs as . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static uint[] GetSequentialIVsUInt32(this LCRNG rng, uint seed) + { + uint[] ivs = new uint[6]; + for (int i = 0; i < 6; i++) + { + seed = rng.Next(seed); + ivs[i] = seed >> 27; + } + return ivs; + } + + /// + /// Generates an IV for each RNG call using the top 5 bits of frame seeds. + /// + /// RNG to use + /// RNG seed + /// Array of 6 IVs as . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static int[] GetSequentialIVsInt32(this LCRNG rng, uint seed) + { + int[] ivs = new int[6]; + for (int i = 0; i < 6; i++) + { + seed = rng.Next(seed); + ivs[i] = (int)(seed >> 27); + } + return ivs; + } + } +} diff --git a/PKHeX.Core/Legality/RNG/Xoroshiro128Plus.cs b/PKHeX.Core/Legality/RNG/Algorithms/Xoroshiro128Plus.cs similarity index 92% rename from PKHeX.Core/Legality/RNG/Xoroshiro128Plus.cs rename to PKHeX.Core/Legality/RNG/Algorithms/Xoroshiro128Plus.cs index 22e52651b..93f84b57d 100644 --- a/PKHeX.Core/Legality/RNG/Xoroshiro128Plus.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/Xoroshiro128Plus.cs @@ -2,6 +2,10 @@ namespace PKHeX.Core { + /// + /// Self-modifying RNG structure that implements xoroshiro128+ + /// + /// https://en.wikipedia.org/wiki/Xoroshiro128%2B [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1815:Override equals and operator equals on value types", Justification = "Unused")] public ref struct Xoroshiro128Plus { diff --git a/PKHeX.Core/Legality/RNG/PIDGenerator.cs b/PKHeX.Core/Legality/RNG/PIDGenerator.cs index 30faf91f2..9ea960cea 100644 --- a/PKHeX.Core/Legality/RNG/PIDGenerator.cs +++ b/PKHeX.Core/Legality/RNG/PIDGenerator.cs @@ -2,6 +2,9 @@ namespace PKHeX.Core { + /// + /// Contains a collection of methods that mutate the input Pokémon object, usually to obtain a correlation. + /// public static class PIDGenerator { private static void SetValuesFromSeedLCRNG(PKM pk, PIDType type, uint seed) diff --git a/PKHeX.Core/Legality/RNG/PIDType.cs b/PKHeX.Core/Legality/RNG/PIDType.cs index f066bc9ab..97c62f44e 100644 --- a/PKHeX.Core/Legality/RNG/PIDType.cs +++ b/PKHeX.Core/Legality/RNG/PIDType.cs @@ -1,5 +1,9 @@ namespace PKHeX.Core { + /// + /// PID + IV correlation. + /// + /// This is just a catch-all enumeration to describe the different correlations. public enum PIDType { /// No relationship between the PID and IVs @@ -46,7 +50,7 @@ public enum PIDType /// /// Event Reversed Order PID restricted to 16bit Origin Seed /// - /// + /// seed is clamped to 16bits. BACD_R, /// @@ -58,7 +62,7 @@ public enum PIDType /// /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny. /// - /// + /// seed is clamped to 16bits. BACD_R_A, /// @@ -70,7 +74,7 @@ public enum PIDType /// /// Event Reversed Order PID restricted to 8bit Origin Seed, shiny /// - /// + /// seed is clamped to 16bits. BACD_R_S, /// @@ -82,7 +86,7 @@ public enum PIDType /// /// Event Reversed Order PID restricted to 16bit Origin Seed, antishiny (nyx) /// - /// + /// seed is clamped to 16bits. BACD_R_AX, /// @@ -92,7 +96,7 @@ public enum PIDType BACD_U_AX, /// - /// Generation 4 Cute Charm forced to an 8 bit buffered PID + /// Generation 4 Cute Charm PID, which is forced to an 8 bit PID value based on the gender & gender ratio value. /// /// CuteCharm, @@ -108,31 +112,31 @@ public enum PIDType #region XDRNG /// - /// Standard PIDIV + /// Generation 3 PID+IV correlation. /// /// CXD, /// - /// Antishiny Rerolled PIDIV + /// Generation 3 PID+IV correlation that was rerolled because it was shiny. /// /// CXDAnti, /// - /// Standard PIDIV which is immediately after the RNG calls that create the TID and SID. + /// 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. CXD_ColoStarter, /// - /// Pokémon Channel Jirachi + /// Generation 3 Pokémon Channel Jirachi /// /// Channel, /// - /// XD PokeSpot PID + /// Generation 3 PokeSpot PID /// /// PokeSpot, @@ -142,7 +146,7 @@ public enum PIDType #region ARNG /// - /// 4th Generation Mystery Gift Anti-Shiny + /// Generation 4 Mystery Gift Anti-Shiny /// /// G4MGAntiShiny, @@ -152,27 +156,27 @@ public enum PIDType #region Formulaic /// - /// 5th Generation Mystery Gift Shiny + /// Generation 5 Mystery Gift Shiny /// /// Formulaic based on TID, SID, and Gender bytes. /// Unrelated to IVs G5MGShiny, /// - /// 4th Generation Pokewalker PID, never Shiny. + /// Generation 4 Pokewalker PID, never Shiny. /// /// Formulaic based on TID, SID, and Gender bytes. /// Unrelated to IVs Pokewalker, /// - /// 8th Generation Raid PID + /// Generation 8 Raid PID /// /// Formulaic based on PID & EC values from a 64bit-seed. Raid8, /// - /// 8th Generation Overworld Spawn PID + /// Generation 8 Overworld Spawn PID /// /// Formulaic based on PID & EC values from a 32bit-seed. Overworld8,