mirror of
https://github.com/kwsch/PKHeX.git
synced 2026-08-17 18:26:50 -05:00
Update xoro128 solver, channel IV solver
I accidentally reverted my intermediate value sum for the channel solver after I discovered the counterexample -- now with the refinements as detailed in the issue, we can proceed with many `long` => `uint` The xoroshiro128+ solvers aren't really set up to take advantage of the eager returns; the iterator struct (not class) would have to be made heavier to store the intermediate values for a given `assume`. Probably would have to inline the methods and break out their inner Update steps. Probably not worth the hassle :) Closes #4844 Co-Authored-By: StarfBerry <65578604+StarfBerry@users.noreply.github.com>
This commit is contained in:
@@ -358,12 +358,12 @@ public static int GetSeedsChannel(Span<uint> result, uint hp, uint atk, uint def
|
||||
{
|
||||
// https://github.com/StarfBerry/PokeRNG/blob/main/Recovery/LCG_Recovery.py
|
||||
// First row of the BKZ-reduced matrix
|
||||
const int r0 = -002_528_644;
|
||||
const int r1 = -024_142_902;
|
||||
const int r2 = 052_961_366;
|
||||
const int r3 = 007_565_619;
|
||||
const int r4 = 024_945_956;
|
||||
const int r5 = -099_942_057;
|
||||
const uint r0 = 0xFFD96A7C; // -2528644
|
||||
const uint r1 = 0xFE8F9BCA; // -24142902
|
||||
const uint r2 = 0x3282056; // 52961366
|
||||
const uint r3 = 0x737133; // 7565619
|
||||
const uint r4 = 0x17CA524; // 24945956
|
||||
const uint r5 = 0xFA0B0157; // -99942057
|
||||
|
||||
// Constants to bound the variables in the linear combinations for calculating potential solutions
|
||||
const long lower0 = 0x2A_B966_D1C2;
|
||||
@@ -379,42 +379,44 @@ public static int GetSeedsChannel(Span<uint> result, uint hp, uint atk, uint def
|
||||
const long upper4 = 0x10_9800_0000;
|
||||
const long upper5 = -0x07_E800_0000;
|
||||
|
||||
// IVs 20/12/6/0/0/3 overflows x5Max `int`, so all are kept as `long` as a precaution.
|
||||
|
||||
long f0 = ((-10L * hp) + (23L * atk) - def - (15L * spe) + (52L * spa) - (53L * spd)) << 27;
|
||||
long x0Min = ((f0 + upper0) >> 32) * r0; // LOWER and UPPER are inverted relative to xmin and xmax because R0 is negative (same with R1 and R5)
|
||||
long x0Max = ((f0 + lower0) >> 32) * r0;
|
||||
uint x0Min = (uint)((f0 + upper0) >> 32) * r0; // LOWER and UPPER are inverted relative to xmin and xmax because r0 is negative (same with r1 and r5)
|
||||
uint x0Max = (uint)((f0 + lower0) >> 32) * r0 - r0;
|
||||
long f1 = ((-14L * hp) + (7L * atk) - (18L * def) - (21L * spe) - (26L * spa) - (24L * spd)) << 27;
|
||||
long x1Min = ((f1 + upper1) >> 32) * r1;
|
||||
long x1Max = ((f1 + lower1) >> 32) * r1;
|
||||
uint x1Min = (uint)((f1 + upper1) >> 32) * r1;
|
||||
uint x1Max = (uint)((f1 + lower1) >> 32) * r1 - r1;
|
||||
long f2 = ((24L * hp) - (5L * atk) + (22L * def) + (15L * spe) - (5L * spa) - (15L * spd)) << 27;
|
||||
long x2Min = ((f2 + lower2) >> 32) * r2;
|
||||
long x2Max = ((f2 + upper2) >> 32) * r2;
|
||||
uint x2Min = (uint)((f2 + lower2) >> 32) * r2;
|
||||
uint x2Max = (uint)((f2 + upper2) >> 32) * r2 + r2;
|
||||
long f3 = ((-5L * hp) - (24L * atk) + (26L * def) - (12L * spe) + (9L * spa) + (14L * spd)) << 27;
|
||||
long x3Min = ((f3 + lower3) >> 32) * r3;
|
||||
long x3Max = ((f3 + upper3) >> 32) * r3;
|
||||
uint x3Min = (uint)((f3 + lower3) >> 32) * r3;
|
||||
uint x3Max = (uint)((f3 + upper3) >> 32) * r3 + r3;
|
||||
long f4 = ((27L * atk) - (18L * spe) - (8L * spa) - spd) << 27;
|
||||
long x4Min = ((f4 + lower4) >> 32) * r4;
|
||||
long x4Max = ((f4 + upper4) >> 32) * r4;
|
||||
uint x4Min = (uint)((f4 + lower4) >> 32) * r4;
|
||||
uint x4Max = (uint)((f4 + upper4) >> 32) * r4 + r4;
|
||||
long f5 = ((-27L * hp) + (18L * def) + (8L * spe) + spa) << 27;
|
||||
long x5Min = ((f5 + upper5) >> 32) * r5;
|
||||
long x5Max = ((f5 + lower5) >> 32) * r5;
|
||||
uint x5Min = (uint)((f5 + upper5) >> 32) * r5;
|
||||
uint x5Max = (uint)((f5 + lower5) >> 32) * r5 - r5;
|
||||
|
||||
// at most 720 iterations in total (around 369 in average, 48 in the best case)
|
||||
int ctr = 0;
|
||||
for (long x5 = x5Min; x5 <= x5Max; x5 -= r5)
|
||||
for (uint x5 = x5Min; x5 != x5Max; x5 -= r5)
|
||||
{
|
||||
for (long x4 = x4Min; x4 <= x4Max; x4 += r4)
|
||||
for (uint x4 = x4Min; x4 != x4Max; x4 += r4)
|
||||
{
|
||||
for (long x2 = x2Min; x2 <= x2Max; x2 += r2)
|
||||
uint l4 = x5 + x4;
|
||||
for (uint x2 = x2Min; x2 != x2Max; x2 += r2)
|
||||
{
|
||||
for (long x3 = x3Min; x3 <= x3Max; x3 += r3)
|
||||
uint l2 = l4 + x2;
|
||||
for (uint x3 = x3Min; x3 != x3Max; x3 += r3)
|
||||
{
|
||||
for (long x1 = x1Min; x1 <= x1Max; x1 -= r1)
|
||||
uint l3 = l2 + x3;
|
||||
for (uint x1 = x1Min; x1 != x1Max; x1 -= r1)
|
||||
{
|
||||
for (long x0 = x0Min; x0 <= x0Max; x0 -= r0)
|
||||
uint l1 = l3 + x1;
|
||||
for (uint x0 = x0Min; x0 != x0Max; x0 -= r0)
|
||||
{
|
||||
uint seed = unchecked((uint)(x5 + x4 + x2 + x3 + x1 + x0));
|
||||
uint seed = l1 + x0;
|
||||
if ((seed >> 27) != hp)
|
||||
continue;
|
||||
if (Next5(ref seed) != atk)
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using static System.Numerics.BitOperations;
|
||||
|
||||
using static PKHeX.Core.Xoroshiro128Plus;
|
||||
|
||||
@@ -11,55 +10,57 @@ namespace PKHeX.Core;
|
||||
/// </summary>
|
||||
public static class Xoroshiro128PlusReversal
|
||||
{
|
||||
/*
|
||||
* https://github.com/StarfBerry/PokeRNG/blob/1e9b9ddf2494837c7d6704c7b8a3831f644bdea9/Recovery/Xoroshiro_Recovery.py
|
||||
* Instead of assuming bits 32~37 of the seeds, we assume bits 58~63.
|
||||
* These new bits make it possible to check if the system of equations is solvable.
|
||||
* On average, this avoids about half of the calculations.
|
||||
*
|
||||
* In the case of two outputs with a skip in between:
|
||||
* - the old algorithm assumes 1 carry bit and bits 32~36 and 48~53 of the seeds,
|
||||
* - the new algorithm assumes 2 carry bits and bits 43~50 of the seeds.
|
||||
*
|
||||
* The number of brute-forced bits has been reduced from 12 to 10.
|
||||
* This approach is between 7 and 36 times faster than the old algorithm when benchmarked.
|
||||
*
|
||||
* C# yield syntax allocates an object on the heap rather than a struct, so we must manually implement the iterator.
|
||||
* This manual iterator repeats the intro work before the eager check, but it is still faster than the old algorithm.
|
||||
* If a fatter struct is used, we can avoid the repeated work by storing the intermediate results in the struct.
|
||||
* Not really worth the extra effort at this time.
|
||||
*/
|
||||
|
||||
/// <summary>
|
||||
/// Reverses two consecutive <see cref="Xoroshiro128Plus.Next()"/> calls (low 32-bits) to get the original 64-bit seed.
|
||||
/// </summary>
|
||||
/// <param name="seed">Resulting seed</param>
|
||||
/// <param name="out1">First output, low 32-bits</param>
|
||||
/// <param name="out2">Second output, low 32-bits</param>
|
||||
/// <param name="assume1">Carry bit</param>
|
||||
/// <param name="assume2">Brute-forced guess of bits 32-37</param>
|
||||
/// <param name="assume">Brute-forced guess of bits 58-63</param>
|
||||
/// <param name="carry">Carry bit</param>
|
||||
/// <returns>True if a satisfactory seed was found, false otherwise.</returns>
|
||||
public static bool Explore(out ulong seed, uint out1, uint out2, ulong assume1, ulong assume2)
|
||||
public static bool Explore(out ulong seed, uint out1, uint out2, byte assume, byte carry)
|
||||
{
|
||||
seed = 0;
|
||||
ulong baseSeed = (uint)unchecked(out1 - XOROSHIRO_CONST);
|
||||
ulong x0 = baseSeed ^ XOROSHIRO_CONST;
|
||||
|
||||
// out1 = A + B
|
||||
seed |= (uint)unchecked(out1 - XOROSHIRO_CONST);
|
||||
baseSeed |= ((out2 - (x0 >> 27) ^ x0) & 0x1F) << 40;
|
||||
ulong x1 = (baseSeed << 6) ^ (x0 >> 18) ^ (x0 >> 2);
|
||||
ulong sub = (out2 >> 18) - (x1 ^ assume);
|
||||
ulong r = (sub - carry) & 0x3FFF;
|
||||
|
||||
// Assume 32-37
|
||||
seed |= ((assume2 & 0x3F) << 32);
|
||||
seed = ((ulong)assume << 58) | baseSeed;
|
||||
if ((assume & 1u) != (r >> 13))
|
||||
return false; // the assumed and recovered bits 58 don't match
|
||||
|
||||
ulong s0 = seed, s1 = XOROSHIRO_CONST;
|
||||
|
||||
s1 ^= s0;
|
||||
ulong x0 = s1;
|
||||
|
||||
//s0 = BitOperations.RotateLeft(s0, 24) ^ s1 ^ (s1 << 16);
|
||||
s1 = RotateLeft(s1, 37);
|
||||
|
||||
// Extract 40-50
|
||||
seed |= (((out2 - s1) ^ x0) & 0x7FF) << 40;
|
||||
|
||||
// Assuming carry, extract 51-58
|
||||
seed |= ((((out2 >> 24) - (seed ^ (x0 >> 8) ^ (x0 >> 24)) - assume1) ^ (XOROSHIRO_CONST >> 51)) & 0xFF) << 51;
|
||||
|
||||
// Extract 38-39
|
||||
seed |= ((((out2 - ((seed >> 40) ^ x0)) >> 11) ^ (XOROSHIRO_CONST >> 38)) & 3) << 38;
|
||||
|
||||
// Extract the rest
|
||||
seed |= (((out2 - RotateLeft(seed ^ XOROSHIRO_CONST, 37)) >> 19) ^ (x0 >> 3) ^ (x0 >> 19)) << 59;
|
||||
// the xor operation to recover the bits can be applied after the check because ((XOROSHIRO_CONST >> 45) >> 13) & 1 == 0
|
||||
//
|
||||
// 45-57
|
||||
seed |= ((r ^ 0x1515) << 45); // 0x1515 = (XOROSHIRO_CONST >> 45) & 0x3fff
|
||||
// 32-39
|
||||
seed |= (((((out2 - ((seed >> 40) ^ x0)) >> 5) ^ 0x75) & 0xFF) << 32); // 0x75 = (XOROSHIRO_CONST >> 32) & 0xff
|
||||
|
||||
var check = new Xoroshiro128Plus(seed);
|
||||
var test1 = check.Next();
|
||||
var test2 = check.Next();
|
||||
|
||||
// Double-check our result.
|
||||
if ((uint)test1 != out1)
|
||||
return false;
|
||||
if ((uint)test2 != out2)
|
||||
return false;
|
||||
return true;
|
||||
return (uint)check.Next() == out1 && (uint)check.Next() == out2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -68,58 +69,78 @@ public static bool Explore(out ulong seed, uint out1, uint out2, ulong assume1,
|
||||
/// <param name="seed">Resulting seed</param>
|
||||
/// <param name="out1">First output, low 32-bits</param>
|
||||
/// <param name="out2">Second output, low 32-bits</param>
|
||||
/// <param name="assume1">Carry bit</param>
|
||||
/// <param name="assume2">Brute-forced guess of bits 32-36</param>
|
||||
/// <param name="assume3">Brute-forced guess of bits 48-53</param>
|
||||
/// <param name="assume2">Brute-forced guess of bits 43-50</param>
|
||||
/// <param name="carry1">First carry bit</param>
|
||||
/// <param name="carry2">Second carry bit</param>
|
||||
/// <returns>True if a satisfactory seed was found, false otherwise.</returns>
|
||||
public static bool ExploreDouble(out ulong seed, uint out1, uint out2, ulong assume1, ulong assume2, ulong assume3)
|
||||
public static bool ExploreDouble(out ulong seed, uint out1, uint out2, byte assume2, byte carry1, byte carry2)
|
||||
{
|
||||
seed = 0;
|
||||
ulong baseSeed = (uint)unchecked(out1 - XOROSHIRO_CONST);
|
||||
ulong bitsCheck = baseSeed & 7;
|
||||
ulong x0 = baseSeed ^ XOROSHIRO_CONST;
|
||||
|
||||
// out1 = A + B
|
||||
seed |= (uint)unchecked(out1 - XOROSHIRO_CONST);
|
||||
seed |= ((assume2 & 0x1F) << 32); // Assume 32-36
|
||||
seed |= ((assume3 & 0x3F) << 48); // Assume 48-53
|
||||
ulong x1_ = (baseSeed >> 19) ^ (x0 >> 6) ^ 0x56;
|
||||
ulong x2_ = (x0 >> 16) ^ 0x65;
|
||||
ulong x3_ = x1_ ^ (x0 >> 27);
|
||||
ulong x4_ = x2_ ^ (x0 >> 27);
|
||||
ulong x5 = (baseSeed >> 16) ^ x0 ^ 0x2B1;
|
||||
ulong x6 = (baseSeed >> 3) ^ (x0 >> 11) ^ 0xE0A;
|
||||
ulong x7 = XOROSHIRO_CONST ^ (x0 >> 24);
|
||||
ulong t0 = out2 >> 16;
|
||||
ulong t1 = out2 >> 27;
|
||||
|
||||
ulong s0 = seed, s1 = XOROSHIRO_CONST;
|
||||
ulong assume = baseSeed | ((ulong)assume2 << 43);
|
||||
x1_ ^= assume2;
|
||||
x2_ ^= assume2;
|
||||
x3_ ^= assume2;
|
||||
x4_ ^= assume2;
|
||||
|
||||
s1 ^= s0;
|
||||
ulong x0 = s1;
|
||||
ulong sub0 = t0 - carry1;
|
||||
|
||||
s0 = RotateLeft(s0, 24) ^ s1 ^ (s1 << 16);
|
||||
s1 = RotateLeft(s1, 37);
|
||||
// 32-36
|
||||
ulong tmp = ((((sub0 - x3_) ^ x4_) & 0x1F) << 32) | assume;
|
||||
x0 = tmp ^ XOROSHIRO_CONST;
|
||||
|
||||
s1 ^= s0;
|
||||
// 37-39
|
||||
tmp |= (((sub0 - (x1_ ^ (x0 >> 27))) ^ x2_ ^ (x0 >> 27)) & 0xFF) << 32;
|
||||
x0 = tmp ^ XOROSHIRO_CONST;
|
||||
|
||||
s0 = RotateLeft(s0, 24) ^ s1 ^ (s1 << 16);
|
||||
//s1 = BitOperations.RotateLeft(s1, 37);
|
||||
ulong r = (((out2 - (x5 ^ (x0 >> 27) ^ (x0 >> 24))) ^ x6 ^ (x0 >> 27)) & 0x1FFF);
|
||||
if ((r >> 10) != bitsCheck) // recovered bits 37-39 cannot yield a solution
|
||||
{
|
||||
seed = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Extract 54-63
|
||||
seed |= ((out2 - s0) ^ ((seed >> 3) ^ (x0 >> 27) ^ (x0 >> 11) ^ (XOROSHIRO_CONST >> 54))) << 54;
|
||||
// 54-63
|
||||
tmp |= (r & 0x3FF) << 54;
|
||||
x0 = tmp ^ XOROSHIRO_CONST;
|
||||
ulong x8 = (tmp >> 30) ^ (x0 >> 17) ^ (x0 >> 54);
|
||||
ulong x9 = (tmp >> 43) ^ (tmp >> 51) ^ (x0 >> 27) ^ (x0 >> 54) ^ 3;
|
||||
ulong x10 = x8 ^ (x0 >> 38);
|
||||
ulong x11 = x9 ^ (x0 >> 38);
|
||||
ulong x12 = x7 ^ (x0 >> 35) ^ (tmp >> 48);
|
||||
ulong x13 = (tmp >> 19) ^ (x0 >> 6);
|
||||
ulong x14 = x13 ^ (x0 >> 27);
|
||||
|
||||
// Mix in the new bits
|
||||
ulong sub1 = t1 - carry2;
|
||||
seed = ((((sub1 - x10) ^ x11) & 3) << 51) | tmp;
|
||||
x0 = seed ^ XOROSHIRO_CONST;
|
||||
seed |= ((((out2 >> 21) - ((seed >> 24) ^ (x0 >> 48) ^ (x0 >> 32) ^ (x0 >> 11)) - assume1) ^ ((XOROSHIRO_CONST >> 61) ^ (XOROSHIRO_CONST >> 45) ^ (x0 >> 21) ^ (x0 >> 48) ^ (x0 >> 32))) & 0x7) << 37;
|
||||
|
||||
// Mix in the new bits
|
||||
seed |= ((((sub0 - (x14 ^ (x0 >> 43))) >> 8) ^ x12 ^ (x0 >> 51)) & 3) << 40;
|
||||
x0 = seed ^ XOROSHIRO_CONST;
|
||||
seed |= (((((out2 >> 21) - ((seed >> 24) ^ (x0 >> 48) ^ (x0 >> 32) ^ (x0 >> 11)) - assume1) >> 3) ^ (XOROSHIRO_CONST ^ (seed >> 48) ^ (x0 >> 24) ^ (x0 >> 51) ^ (x0 >> 35))) & 0x1F) << 40;
|
||||
|
||||
// Mix in the new bits
|
||||
seed |= (((sub1 - (x8 ^ (x0 >> 38))) ^ x9 ^ (x0 >> 38)) & 7) << 51;
|
||||
x0 = seed ^ XOROSHIRO_CONST;
|
||||
seed |= (((((out2 >> 21) - ((seed >> 24) ^ (x0 >> 48) ^ (x0 >> 32) ^ (x0 >> 11)) - assume1) >> 3) ^ (XOROSHIRO_CONST ^ (seed >> 48) ^ (x0 >> 24) ^ (x0 >> 51) ^ (x0 >> 35))) & 0xFF) << 40;
|
||||
|
||||
// Double-check our result.
|
||||
r = ((((sub0 - (x13 ^ (x0 >> 27) ^ (x0 >> 43))) >> 8) ^ x7 ^ (x0 >> 35) ^ (seed >> 48) ^ (x0 >> 51)) & 0x7F);
|
||||
if ((assume2 & 0xFu) != (r >> 3))
|
||||
return false;
|
||||
|
||||
seed |= r << 40;
|
||||
var check = new Xoroshiro128Plus(seed);
|
||||
var test1 = check.Next();
|
||||
_ = check.Next(); // gap
|
||||
var test2 = check.Next();
|
||||
|
||||
if ((uint)test1 != out1)
|
||||
if ((uint)check.Next() != out1)
|
||||
return false;
|
||||
if ((uint)test2 != out2)
|
||||
return false;
|
||||
return true;
|
||||
_ = check.Next();
|
||||
return (uint)check.Next() == out2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,7 +158,7 @@ public record struct XoroMachineConsecutive(uint First, uint Second) : IEnumerat
|
||||
public readonly ulong Current => seed;
|
||||
|
||||
private ulong seed;
|
||||
private byte assume1;
|
||||
private byte assume1; // assume 58-63
|
||||
private byte carry;
|
||||
|
||||
public bool MoveNext()
|
||||
@@ -146,7 +167,7 @@ public bool MoveNext()
|
||||
{
|
||||
while (carry < 2)
|
||||
{
|
||||
if (Xoroshiro128PlusReversal.Explore(out seed, First, Second, carry++, assume1))
|
||||
if (Xoroshiro128PlusReversal.Explore(out seed, First, Second, assume1, carry++))
|
||||
return true;
|
||||
}
|
||||
carry = 0;
|
||||
@@ -175,9 +196,9 @@ public record struct XoroMachineSkip(uint First, uint Third) : IEnumerator<ulong
|
||||
public readonly ulong Current => seed;
|
||||
|
||||
private ulong seed;
|
||||
private byte assume1;
|
||||
private byte assume2;
|
||||
private byte carry;
|
||||
private byte assume1; // assume 43-50
|
||||
private byte carry1;
|
||||
private byte carry2;
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
@@ -185,20 +206,19 @@ public bool MoveNext()
|
||||
{
|
||||
do
|
||||
{
|
||||
while (carry < 2)
|
||||
while (carry1 < 2)
|
||||
{
|
||||
if (Xoroshiro128PlusReversal.ExploreDouble(out seed, First, Third, carry++, assume1, assume2))
|
||||
if (Xoroshiro128PlusReversal.ExploreDouble(out seed, First, Third, assume1, carry1++, carry2))
|
||||
return true;
|
||||
}
|
||||
carry = 0;
|
||||
} while (++assume2 < 0x40);
|
||||
assume2 = 0;
|
||||
} while (++assume1 < 0x20);
|
||||
carry1 = 0;
|
||||
} while ((carry2 ^= 1) != 0);
|
||||
} while (++assume1 != 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
// IEnumerator Implementation -- used for foreach syntax sugar inlining
|
||||
public void Reset() => assume1 = assume2 = carry = 0;
|
||||
public void Reset() => assume1 = carry1 = carry2 = 0;
|
||||
readonly object IEnumerator.Current => Current;
|
||||
public readonly void Dispose() { }
|
||||
public readonly IEnumerator<ulong> GetEnumerator() => this;
|
||||
|
||||
Reference in New Issue
Block a user