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)]