From 3123f0df2ffe925e45575a3dc906c9569bb7564d Mon Sep 17 00:00:00 2001 From: Kurt Date: Thu, 3 Aug 2017 00:59:39 -0700 Subject: [PATCH] Drastically speed up rand() pairs -> seed search Meet in the middle attack trades some RAM (2^16 flag/byte array) to reduce future searches by a factor of 1:2^8 profiling yielded >100x speed improvements, even a 2x would have been impressive ;) knocks the biggest cpu hog (when searching db) out of the race! --- PKHeX.Core/Legality/RNG/MethodFinder.cs | 20 +++------ PKHeX.Core/Legality/RNG/RNG.cs | 54 ++++++++++++++++++++++++- 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index a6884d512..454a9f553 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -467,26 +467,16 @@ namespace PKHeX.Core { uint cmp = a << 16; uint x = b << 16; - for (uint i = 0; i <= 0xFFFF; i++) - { - var seed = x | i; - if ((method.Next(seed) & 0xFFFF0000) == cmp) - yield return method.Prev(seed); - } + return method.RecoverLower16Bits(x, cmp); } private static IEnumerable GetSeedsFromIVs(RNG method, uint a, uint b) { uint cmp = a << 16 & 0x7FFF0000; uint x = b << 16 & 0x7FFF0000; - for (uint i = 0; i <= 0xFFFF; i++) - { - var seed = x | i; - if ((method.Next(seed) & 0x7FFF0000) != cmp) - continue; - var prev = method.Prev(seed); - yield return prev; - yield return prev ^ 0x80000000; - } + return method.RecoverLower16Bits(x ^ 0x00000000, cmp ^ 0x00000000).Concat( + method.RecoverLower16Bits(x ^ 0x80000000, cmp ^ 0x00000000).Concat( + method.RecoverLower16Bits(x ^ 0x00000000, cmp ^ 0x80000000).Concat( + method.RecoverLower16Bits(x ^ 0x80000000, cmp ^ 0x80000000)))); } /// diff --git a/PKHeX.Core/Legality/RNG/RNG.cs b/PKHeX.Core/Legality/RNG/RNG.cs index 29cac98ca..00cc37a86 100644 --- a/PKHeX.Core/Legality/RNG/RNG.cs +++ b/PKHeX.Core/Legality/RNG/RNG.cs @@ -1,4 +1,6 @@ -namespace PKHeX.Core +using System.Collections.Generic; + +namespace PKHeX.Core { public class RNG { @@ -7,12 +9,41 @@ public static readonly RNG ARNG = new RNG(0x6C078965, 0x00000001, 0x9638806D, 0x69C77F93); private readonly uint Mult, Add, rMult, rAdd; + + // Bruteforce cache for searching seeds + private const int cacheSize = 1 << 16; + private readonly uint k2; + private readonly byte[] low8 = new byte[cacheSize]; + private readonly bool[] flags = new bool[cacheSize]; + 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; + + // Set up bruteforce utility + k2 = Mult << 8; + PopulateMeetMiddleArrays(); + } + + private void PopulateMeetMiddleArrays() + { + for (uint i = 0; i <= byte.MaxValue; i++) + { + uint right = Mult * i; + ushort val = (ushort)(right >> 16); + flags[val] = true; + low8[val] = (byte)i; + + // when calculating the left side, sometimes the low bits might not carry (not considered in calc) + // since LCGs are linear, there are no collisions if we mark the next adjacent with the prior val + // we do this now so that the search only has to access the array once per loop. + ++val; + flags[val] = true; + low8[val] = (byte)i; + } } public uint Next(uint seed) => seed * Mult + Add; @@ -56,5 +87,26 @@ } return ivs; } + + /// + /// Recovers sets of lower 16 bit seeds, then returns the origin seed. + /// + /// 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) + /// + /// Possible origin seeds that generate the 2 random numbers + internal IEnumerable RecoverLower16Bits(uint first, uint second) + { + uint k1 = second - (first * Mult + Add); + 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]); + } + } } }