diff --git a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs index 84c1e1265..5c9aaec63 100644 --- a/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs +++ b/PKHeX.Core/Legality/RNG/Algorithms/XDRNG.cs @@ -94,6 +94,9 @@ public static class XDRNG [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev11(uint seed) => (seed * rMult11) + rAdd11; [MethodImpl(MethodImplOptions.AggressiveInlining)] public static uint Prev12(uint seed) => (seed * rMult12) + rAdd12; + public static uint Next1000(uint seed) => 0xDD867B21 * seed + 0xD252C5A8; + public static uint Prev1000(uint seed) => 0x251CC8E1 * seed + 0x94750758; + /// /// Gets the upper 16 bits of the next RNG seed. /// diff --git a/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs b/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs index 5bb344017..d3ef44450 100644 --- a/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs +++ b/PKHeX.Core/Legality/RNG/CXD/MethodCXD.cs @@ -100,20 +100,20 @@ public static bool SetFromIVsCXD(TEntity pk, EncounterCriteria criteria /// /// Generation 3 Trainer ID /// Generation 3 Secret ID - /// Possible starting seed + /// Possible starting seed /// True if a seed was found, false if no seed was found - public static bool TryGetSeedTrainerID(ushort tid, ushort sid, out uint seed) + public static bool TryGetSeedTrainerID(ushort tid, ushort sid, out uint origin) { Span seeds = stackalloc uint[XDRNG.MaxCountSeedsPID]; var count = XDRNG.GetSeeds(seeds, (uint)tid << 16, (uint)sid << 16); - - if (count == 0) + foreach (var s in seeds[..count]) { - seed = 0; - return false; + var prev1k = XDRNG.Prev1000(s); + if (IsReachableFromNameScreen(prev1k, out origin)) + return true; } - seed = seeds[0]; - return true; + origin = 0; + return false; } public static bool TryGetShinySID(ushort tid, out ushort sid, uint xor, uint bits) @@ -626,4 +626,36 @@ public static bool TryGetOriginSeedStarterXD(PKM pk, out uint result) result = 0; return false; } + + /// + /// Checks if the provided can be landed at after confirming the player name. + /// + /// Seed that goes on to generate the player TID/SID (after advancing 1000 frames) + /// Current seed that confirms the player name + /// True if can be reached via + /// https://github.com/yatsuna827/PokemonCoRNGLibrary/blob/e5255b13134ab3a2119788c40b5e71ee48d849b0/PokemonCoRNGLibrary/Util/LCGExtensions.cs#L15 + public static bool IsReachableFromNameScreen(uint seed, out uint origin) + { + // rand() / 10 == 0 is a skip + const ushort SEED_THRESHOLD = 0x1999; // Only need the upper 16 bits + + var p1 = XDRNG.Prev16(ref seed) > SEED_THRESHOLD; + var p2 = XDRNG.Prev16(ref seed) > SEED_THRESHOLD; + var p3 = XDRNG.Prev16(ref seed) > SEED_THRESHOLD; + var p4 = XDRNG.Prev16(ref seed) > SEED_THRESHOLD; + + if (p1 && p2 && p3 && p4) + { + origin = seed; + return true; + } + + if (XDRNG.Prev16(ref seed) <= SEED_THRESHOLD && IsReachableFromNameScreen(XDRNG.Prev(seed), out origin)) return true; + if (XDRNG.Prev16(ref seed) <= SEED_THRESHOLD && p1 && IsReachableFromNameScreen(XDRNG.Prev(seed), out origin)) return true; + if (XDRNG.Prev16(ref seed) <= SEED_THRESHOLD && p1 && p2 && IsReachableFromNameScreen(XDRNG.Prev(seed), out origin)) return true; + if (XDRNG.Prev16(ref seed) <= SEED_THRESHOLD && p1 && p2 && p3 && IsReachableFromNameScreen(XDRNG.Prev(seed), out origin)) return true; + + origin = 0; + return false; + } } diff --git a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs index 4360563a6..bc2f05652 100644 --- a/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs +++ b/Tests/PKHeX.Core.Tests/PKM/PIDIVTests.cs @@ -142,14 +142,20 @@ public void PIDIVMatchingTest5() MethodFinder.Analyze(pkS5).Type.Should().Be(PIDType.G5MGShiny); } - [Fact] - public void PIDIVPokeSpotTest() - { - // XD PokeSpots: Check all 3 Encounter Slots (examples are one for each location). - MethodPokeSpot.TryGetOriginSeedPID(0x7B2D9DA7, 0, out _).Should().BeTrue(); // Zubat (Cave) (Common) - MethodPokeSpot.TryGetOriginSeedPID(0x3EE9AF66, 1, out _).Should().BeTrue(); // Gligar (Rock) (Uncommon) - MethodPokeSpot.TryGetOriginSeedPID(0x9B667F3C, 2, out _).Should().BeTrue(); // Surskit (Oasis) (Rare) - } + [Theory] + [InlineData(0x7B2D9DA7, 0)] // Zubat (Cave) (Common) + [InlineData(0x3EE9AF66, 1)] // Gligar (Rock) (Uncommon) + [InlineData(0x9B667F3C, 2)] // Surskit (Oasis) (Rare) + public void PIDIVPokeSpotTest(uint pid, byte slot) => MethodPokeSpot.TryGetOriginSeedPID(pid, slot, out _).Should().BeTrue(); + + [Theory] + [InlineData(62714, 62938)] + [InlineData(05724, 31840)] + [InlineData(31827, 52374)] + [InlineData(34952, 34952)] + [InlineData(31827, 00123, false)] + [InlineData(34952, 01337, false)] + public void CXDTrainerTest(ushort tid, ushort sid, bool expect = true) => MethodCXD.TryGetSeedTrainerID(tid, sid, out _).Should().Be(expect); [Theory] [InlineData(30, 31, 31, 14, 31, 31, 0x28070031, 024, PokewalkerSeedType.NoStroll)]