mirror of
https://github.com/kwsch/PKHeX
synced 2024-11-10 06:34:19 +00:00
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:
parent
fa44b91511
commit
fe06309134
3 changed files with 103 additions and 26 deletions
|
@ -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>
|
||||
|
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue