diff --git a/PKHeX.Core/Legality/RNG/MethodFinder.cs b/PKHeX.Core/Legality/RNG/MethodFinder.cs index 1cfec072a..2db30d631 100644 --- a/PKHeX.Core/Legality/RNG/MethodFinder.cs +++ b/PKHeX.Core/Legality/RNG/MethodFinder.cs @@ -334,22 +334,39 @@ public static class MethodFinder private static bool GetMG4Match(Span seeds, uint pid, ReadOnlySpan IVs, out PIDIV pidiv) { - uint mg4Rev = ARNG.Prev(pid); + var currentPSV = getPSV(pid); + pid = ARNG.Prev(pid); + var originalPSV = getPSV(pid); + // ARNG shiny value must be different from the original shiny + // if we have a multi-rerolled PID, each re-roll must be from the same shiny value + if (originalPSV == currentPSV) + return GetNonMatch(out pidiv); - var count = LCRNGReversal.GetSeeds(seeds, mg4Rev << 16, mg4Rev & 0xFFFF0000); - var mg4 = seeds[..count]; - foreach (var seed in mg4) + // ARNG can happen at most 3 times (checked all 2^32 seeds) + for (int i = 0; i < 3; i++) { - var B = LCRNG.Next2(seed); - var C = LCRNG.Next(B); - var D = LCRNG.Next(C); - if (!IVsMatch(C >> 16, D >> 16, IVs)) - continue; + var count = LCRNGReversal.GetSeeds(seeds, pid << 16, pid & 0xFFFF0000); + var mg4 = seeds[..count]; + foreach (var seed in mg4) + { + var C = LCRNG.Next3(seed); + var D = LCRNG.Next(C); + if (!IVsMatch(C >> 16, D >> 16, IVs)) + continue; - pidiv = new PIDIV(G4MGAntiShiny, seed); - return true; + pidiv = new PIDIV(G4MGAntiShiny, seed); + return true; + } + + // Continue checking for multi-rerolls + pid = ARNG.Prev(pid); + var prevPSV = getPSV(pid); + if (prevPSV != originalPSV) + break; } return GetNonMatch(out pidiv); + + static uint getPSV(uint u32) => ((u32 >> 16) ^ (u32 & 0xFFFF)) >> 3; } private static bool GetG5MGShinyMatch(PKM pk, uint pid, out PIDIV pidiv) diff --git a/PKHeX.Core/MysteryGifts/PGT.cs b/PKHeX.Core/MysteryGifts/PGT.cs index deacc3f9f..4784fbf5e 100644 --- a/PKHeX.Core/MysteryGifts/PGT.cs +++ b/PKHeX.Core/MysteryGifts/PGT.cs @@ -322,25 +322,17 @@ public sealed class PGT : DataMysteryGift, IRibbonSetEvent3, IRibbonSetEvent4, I private static bool IsG4ManaphyPIDValid(PIDType val, PKM pk) { + // Unhatched: Can't trigger ARNG, so it must always be Method 1 if (pk.IsEgg) - { - if (pk.IsShiny) - return false; - if (val == PIDType.Method_1) - return true; - return val == PIDType.G4MGAntiShiny && IsAntiShinyARNG(pk); - } + return val == PIDType.Method_1; + // Hatching: Code checks if the TID/SID yield a shiny, and re-roll until not shiny. + // However, the TID/SID reference is stale (original OT, not hatching OT), so it's fallible. + // Hatched: Can't be shiny for an un-traded egg. if (val == PIDType.Method_1) - return pk.WasTradedEgg || !pk.IsShiny; // can't be shiny on received game - return val == PIDType.G4MGAntiShiny && (pk.WasTradedEgg || IsAntiShinyARNG(pk)); + return pk.WasTradedEgg || !pk.IsShiny; - static bool IsAntiShinyARNG(PKM pk) - { - var shinyPID = ARNG.Prev(pk.PID); - var tmp = pk.ID32 ^ shinyPID; - var xor = (ushort)(tmp ^ (tmp >> 16)); - return xor < 8; // shiny proc - } + // Hatched when the egg was shiny: PID needs to be from the ARNG. + return val == PIDType.G4MGAntiShiny; } }