Add IV framegap search

Only usable for searching Method 4 IV spreads -> seeds;
1,3's search uses the same approach as the 1,2 search
I took the 1,2 search derivation to iterate for the next step, which
allows us to not know anything about the middle rand bits.

optimize a little bit more, move the pre-loop add to the initialization
stage; moving to the precomputed section pays off after 256 calls to the
method
This commit is contained in:
Kurt 2017-08-03 21:35:41 -07:00
parent fa44b91511
commit fe06309134
3 changed files with 103 additions and 26 deletions

View file

@ -1,4 +1,5 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
namespace PKHeX.Core
@ -465,18 +466,39 @@ namespace PKHeX.Core
private static IEnumerable<uint> GetSeedsFromPID(RNG method, uint a, uint b)
{
uint cmp = a << 16;
uint x = b << 16;
return method.RecoverLower16Bits(x, cmp);
Debug.Assert(a >> 16 == 0);
Debug.Assert(b >> 16 == 0);
uint second = a << 16;
uint first = b << 16;
return method.RecoverLower16Bits(first, second);
}
private static IEnumerable<uint> GetSeedsFromIVs(RNG method, uint a, uint b)
{
uint cmp = a << 16 & 0x7FFF0000;
uint x = b << 16 & 0x7FFF0000;
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))));
Debug.Assert(a >> 15 == 0);
Debug.Assert(b >> 15 == 0);
uint second = a << 16;
uint first = b << 16;
var pairs = method.RecoverLower16Bits(first, second)
.Concat(method.RecoverLower16Bits(first, second ^ 0x80000000));
foreach (var z in pairs)
{
yield return z;
yield return z ^ 0x80000000; // sister bitflip
}
}
public static IEnumerable<uint> GetSeedsFromIVsSkip(RNG method, uint rand1, uint rand3)
{
Debug.Assert(rand1 >> 15 == 0);
Debug.Assert(rand3 >> 15 == 0);
rand1 <<= 16;
rand3 <<= 16;
var seeds = method.RecoverLower16BitsGap(rand1, rand3)
.Concat(method.RecoverLower16BitsGap(rand1, rand3 ^ 0x80000000));
foreach (var z in seeds)
{
yield return z;
yield return z ^ 0x80000000; // sister bitflip
}
}
/// <summary>

View file

@ -12,9 +12,15 @@ namespace PKHeX.Core
// Bruteforce cache for searching seeds
private const int cacheSize = 1 << 16;
private readonly uint k2;
// 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];
private RNG(uint f_mult, uint f_add, uint r_mult, uint r_add)
{
@ -25,30 +31,37 @@ namespace PKHeX.Core
// Set up bruteforce utility
k2 = Mult << 8;
k0g = Mult * Mult;
k2s = k0g << 8;
PopulateMeetMiddleArrays();
}
private void PopulateMeetMiddleArrays()
{
uint k4g = Add * (Mult + 1); // 1,3's multiplier
for (uint i = 0; i <= byte.MaxValue; i++)
{
uint right = Mult * i;
ushort val = (ushort)(right >> 16);
flags[val] = true;
low8[val] = (byte)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.
--val;
flags[val] = true;
low8[val] = (byte)i;
// now the search only has to access the flags array once per loop.
SetFlagData(i, Mult, Add, flags, low8); // 1,2
SetFlagData(i, k0g, k4g, g_flags, g_low8); // 1,3
}
}
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.
}
public uint Next(uint seed) => seed * Mult + Add;
public uint Prev(uint seed) => seed * rMult + rAdd;
@ -103,7 +116,7 @@ namespace PKHeX.Core
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
internal IEnumerable<uint> RecoverLower16Bits(uint first, uint second)
{
uint k1 = second - (first * Mult + Add);
uint k1 = second - first * Mult;
for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2)
{
ushort val = (ushort)(k3 >> 16);
@ -111,5 +124,25 @@ namespace PKHeX.Core
yield return Prev(first | i << 8 | low8[val]);
}
}
/// <summary>
/// Recovers sets of lower 16 bit seeds, then returns the origin seed.
/// </summary>
/// <param name="first">First rand() call, 16 bits, already shifted left 16 bits.</param>
/// <param name="third">Third rand() call, 16 bits, already shifted left 16 bits.</param>
/// <remarks>
/// Use a meet-in-the-middle attack to reduce the search space to 2^8 instead of 2^16
/// flag/2^8 tables are precomputed and constant (unrelated to rand pairs)
/// </remarks>
/// <returns>Possible origin seeds that generate the 2 random numbers</returns>
internal IEnumerable<uint> RecoverLower16BitsGap(uint first, uint third)
{
uint k1 = third - first * k0g;
for (uint i = 0, k3 = k1; i <= 255; ++i, k3 -= k2s)
{
ushort val = (ushort)(k3 >> 16);
if (g_flags[val])
yield return Prev(first | i << 8 | g_low8[val]);
}
}
}
}

View file

@ -1,4 +1,5 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Linq;
using PKHeX.Core;
@ -198,5 +199,26 @@ namespace PKHeX.Tests.PKM
// var results = FrameFinder.GetFrames(pidiv, pk);
}
}
[TestMethod]
[TestCategory(PIDIVTestCategory)]
public void PIDIVMethod4IVs()
{
// 17 19 20 18 3 2
var pk4 = new PK3 { PID = 0xFEE73213, IVs = new[] { 03, 29, 23, 30, 28, 24 } };
Assert.AreEqual(PIDType.Method_4, MethodFinder.Analyze(pk4)?.Type, "Unable to match PID to Method 4 spread");
// See if any origin seed for the IVs matches what we expect
// Load the IVs
uint rand1 = 0; // HP/ATK/DEF
uint rand3 = 0; // SPE/SPA/SPD
var IVs = pk4.IVs;
for (int i = 0; i < 3; i++)
{
rand1 |= (uint)IVs[i] << (5 * i);
rand3 |= (uint)IVs[i+3] << (5 * i);
}
Assert.IsTrue(MethodFinder.GetSeedsFromIVsSkip(RNG.LCRNG, rand1, rand3).Any(z => z == 0xFEE7047C));
}
}
}