diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs index 3f4fbfeb0..cbe106d11 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversal.cs @@ -49,9 +49,9 @@ public static int GetSeedsIVs(Span result, uint hp, uint atk, uint def, ui /// Count of results added to public static int GetSeeds(Span result, uint first, uint second) { - ulong tmp = (ulong)(((first - (second * RMult)) >> 16) & 0xFFFF) * RLag0; - var lo = (uint)((tmp + RLower) >> 16); - var up = (uint)((tmp + RUpper) >> 16); + uint tmp = ((first - (second * RMult)) >> 16) * RLag0; + uint lo = (tmp + RLower) >> 16; + uint up = (tmp + RUpper) >> 16; if (lo != up) // true in around 10% of cases return 0; @@ -76,23 +76,23 @@ public static int GetSeeds(Span result, uint first, uint second) /// Count of results added to public static int GetSeedsIVs(Span result, uint first, uint second) { - long diff = ((long)LCRNG.Mult * first) - second; - ulong tmp = (ulong)((diff >> 16) & 0xFFFF) * Lag1; + ulong tmp = (ulong)(((LCRNG.Mult * first) - second >> 16) & 0xFFFF) * Lag1; var lo = (uint)(((tmp + Lower) >> 15) * Lag0); var mi = lo + Lag0; var up = (uint)(((tmp + Upper) >> 15) * Lag0); int ctr = 0; // around 2.70 iterations in average - AddSeeds(result, lo % Lag1, first, second, ref ctr); - AddSeeds(result, mi % Lag1, first, second, ref ctr); + AddSeeds(result, lo, first, second, ref ctr); + AddSeeds(result, mi, first, second, ref ctr); if (mi != up) // true in around 12% of cases - AddSeeds(result, up % Lag1, first, second, ref ctr); + AddSeeds(result, up, first, second, ref ctr); return ctr; } private static void AddSeeds(Span result, uint low, uint first, uint second, ref int ctr) { + low %= Lag1; // at most 3 iterations do { diff --git a/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs index e1133c856..331790e38 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/LCRNGReversalSkip.cs @@ -46,9 +46,9 @@ public static int GetSeedsIVs(Span result, uint hp, uint atk, uint def, ui /// Count of results added to public static int GetSeeds(Span result, uint first, uint third) { - ulong tmp = (ulong)(((first - (third * RMult2)) >> 16) & 0xFFFF) * RLag0; - var lo = (uint)((tmp + RLowerPID) >> 16); - var up = (uint)((tmp + RUpper) >> 16); + uint tmp = ((first - (third * RMult2)) >> 16) * RLag0; + uint lo = (tmp + RLowerPID) >> 16; + uint up = (tmp + RUpper) >> 16; if (lo != up) // true in around 35% of cases return 0; @@ -73,9 +73,9 @@ public static int GetSeeds(Span result, uint first, uint third) /// Count of results added to public static int GetSeedsIVs(Span result, uint first, uint third) { - ulong tmp = (ulong)(((first - (third * RMult2)) >> 16) & 0xFFFF) * RLag0; - var lo = (uint)((tmp + RLowerIVs) >> 15); - var up = (uint)((tmp + RUpper) >> 15); + uint tmp = ((first - (third * RMult2)) >> 16) * RLag0; + uint lo = (tmp + RLowerIVs) >> 15; + uint up = (tmp + RUpper) >> 15; int ctr = 0; AddSeeds(result, (lo * RLag1IVs) % RLag0, first, third, ref ctr); diff --git a/PKHeX.Core/Legality/RNG/Algorithms/MRNGReversal.cs b/PKHeX.Core/Legality/RNG/Algorithms/MRNGReversal.cs index 6454bdaa4..0e205ef88 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/MRNGReversal.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/MRNGReversal.cs @@ -29,9 +29,9 @@ public static int GetSeedsIVs(Span result, uint hp, uint atk, uint def, ui /// public static int GetSeedsIVs(Span result, uint first, uint third) { - ulong tmp = (ulong)(((first - (third * RMult2)) >> 16) & 0xFFFF) * RLag0; - var lo = (uint)((tmp + RLower) >> 15); - var up = (uint)((tmp + RUpper) >> 15); + uint tmp = ((first - (third * RMult2)) >> 16) * RLag0; + uint lo = (tmp + RLower) >> 15; + uint up = (tmp + RUpper) >> 15; int ctr = 0; AddSeeds(result, (lo * RLag1IVs) % RLag0, first, third, ref ctr); diff --git a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs index c157694d8..de1aa3b82 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs @@ -243,6 +243,18 @@ public static int GetSeeds(Span result, uint hp, uint atk, uint def, uint return GetSeedsIVs(result, first, second); } + // RNG IVs Constants (bounding the second variable) + // https://github.com/StarfBerry/PokeRNG/blob/1e9b9ddf2494837c7d6704c7b8a3831f644bdea9/Recovery/LCG_Recovery.py#L229 + private const uint Lag0 = 0xE8D1; // 59601 + private const uint Lag1 = 0x5F47; // -35210 mod 59601 + private const uint RLower = 0x55FF8537; // ((-0x92D27AC8F311 + 0xffff_ffff) >> 16) + (59601 << 16) + private const uint RUpper = 0x55FFBC6D; // (-0x92D14392F311 >> 16) + (59601 << 16) + + private const uint Lag0IVs = 0x44C5; // 17605 + private const uint Lag1IVs = 0xE8D1; // 59601 + private const uint RLowerIVs = 0x1E694392; // (0x1E68C392F311 + 0x7fff_ffff) >> 16 + private const uint RUpperIVs = 0x1E69FAC8; // (0x1E69FAC8F311 >> 16) + /// /// Finds all the origin seeds for two 16 bit rand() calls /// @@ -252,15 +264,33 @@ public static int GetSeeds(Span result, uint hp, uint atk, uint def, uint /// Count of results added to public static int GetSeeds(Span result, uint first, uint second) { - ulong t = second - (first * Mult) - Sub; - ulong kmax = (Base - t) >> 32; + // `tmp` must be 64bit to avoid overflow via addition with the LOWER and UPPER constants + ulong tmp = ((first - (second * rMult)) >> 16) * Lag0; + uint lo = (uint)((tmp + RLower) >> 16); + uint up = (uint)((tmp + RUpper) >> 16); int ctr = 0; - for (ulong k = 0; k <= kmax; k++, t += 0x1_0000_0000) // at most 4 iterations + + // each loop performs at most 2 iterations + uint low = (lo * Lag1) % Lag0; + do { - if (t % Mult < 0x1_0000) - result[ctr++] = Prev(first | (ushort)(t / Mult)); - } + uint seed = Prev(second | low); + if ((seed & 0xffff0000) == first) + result[ctr++] = Prev(seed); + } while ((low += Lag0) < 0x1_0000); + + if (lo == up) + return ctr; + + // true in around 22% of cases + low = (up * Lag1) % Lag0; + do + { + uint seed = Prev(second | low); + if ((seed & 0xffff0000) == first) + result[ctr++] = Prev(seed); + } while ((low += Lag0) < 0x1_0000); return ctr; } @@ -273,19 +303,50 @@ public static int GetSeeds(Span result, uint first, uint second) /// Count of results added to public static int GetSeedsIVs(Span result, uint first, uint second) { - ulong t = (second - (first * Mult) - Sub) & 0x7FFF_FFFF; - ulong kmax = (Base - t) >> 31; + ulong tmp = ((((rMult * second) - first) >> 16) & 0xFFFF) * Lag1IVs; + + var lo = (uint)((tmp + RLowerIVs) >> 15) * Lag0IVs; + var mi = lo + Lag0IVs; + var up = (uint)((tmp + RUpperIVs) >> 15) * Lag0IVs; int ctr = 0; - for (ulong k = 0; k <= kmax; k++, t += 0x8000_0000) // at most 7 iterations + // each loop performs at most 2 iterations + uint low = lo % Lag1IVs; + do { - if (t % Mult < 0x1_0000) - { - var s = Prev(first | (ushort)(t / Mult)); - result[ctr++] = s; - result[ctr++] = s ^ 0x8000_0000; // top bit flip - } - } + uint seed = Prev(second | low); + if ((seed & 0x7fff0000) != first) + continue; + seed = Prev(seed); + result[ctr++] = seed; + result[ctr++] = seed ^ 0x80000000; + } while ((low += Lag1IVs) < 0x1_0000); + + low = mi % Lag1IVs; + do + { + uint seed = Prev(second | low); + if ((seed & 0x7fff0000) != first) + continue; + seed = Prev(seed); + result[ctr++] = seed; + result[ctr++] = seed ^ 0x80000000; + } while ((low += Lag1IVs) < 0x1_0000); + + if (mi == up) + return ctr; + + // true in around 43% of cases + low = up % Lag1IVs; + do + { + uint seed = Prev(second | low); + if ((seed & 0x7fff0000) != first) + continue; + seed = Prev(seed); + result[ctr++] = seed; + result[ctr++] = seed ^ 0x80000000; + } while ((low += Lag1IVs) < 0x1_0000); return ctr; } @@ -297,12 +358,12 @@ public static int GetSeedsChannel(Span 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 long r0 = -002_528_644; - const long r1 = -024_142_902; - const long r2 = 052_961_366; - const long r3 = 007_565_619; - const long r4 = 024_945_956; - const long r5 = -099_942_057; + 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; // Constants to bound the variables in the linear combinations for calculating potential solutions const long lower0 = 0x2A_B966_D1C2; @@ -318,6 +379,8 @@ public static int GetSeedsChannel(Span 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; diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index 1f50c6be1..f70e4a9a5 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -307,6 +307,7 @@ private static bool GetChannelMatch(Span seeds, PKM pk, uint top, uint bot if (version is not (R or S)) return GetNonMatch(out pidiv); + // failed implementation of the shiny lock due to operator precedence var undo = (top >> 16) ^ 0x8000; if ((undo > 7 ? 0 : 1) != ((bot >> 16) ^ pk.SID16 ^ 40122)) top = (undo << 16); diff --git a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs index 212bdb29f..56b82db08 100644 --- a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs @@ -304,7 +304,7 @@ public void MRNGReversalRanchRecovery(uint iv32, int recovered) [InlineData(0x00000000, 1, false)] [InlineData(0x00000001, 7)] [InlineData(0x00000087, 9)] - [InlineData(0x00000888, 10, false)] + [InlineData(0x00000888, 10)] [InlineData(0x0000F525, 11)] [InlineData(0x00019994, 12)] public void ChannelLatticeRecovery(uint iv32, int recovered, bool isObtainable = true) @@ -327,7 +327,11 @@ public void ChannelLatticeRecovery(uint iv32, int recovered, bool isObtainable = var seed = ChannelJirachi.SkipToIVs(origin); var result = XDRNG.GetSequentialIV32(seed); result.Should().Be(iv32); - possible |= ChannelJirachi.IsPossible(seed); + + // check if any of the PID/IV origin seeds can unroll to a menu seed for the player to hit + var check = ChannelJirachi.GetPossible(origin); + if (check.Pattern != ChannelJirachiRandomResult.None) + possible = true; } possible.Should().Be(isObtainable); }