diff --git a/PKHeX/Legality/Analysis.cs b/PKHeX/Legality/Analysis.cs
index 948cf520d..515715f37 100644
--- a/PKHeX/Legality/Analysis.cs
+++ b/PKHeX/Legality/Analysis.cs
@@ -364,6 +364,13 @@ private string getVerboseLegalityReport()
lines.AddRange(br);
lines.Add(string.Format(V195, EncounterName));
+ var pidiv = MethodFinder.Analyze(pkm);
+ if (pidiv != null)
+ {
+ if (!pidiv.NoSeed)
+ lines.Add(string.Format(V248, pidiv.OriginSeed.ToString("X8")));
+ lines.Add(string.Format(V249, pidiv.Type));
+ }
return getLegalityReport() + string.Join(Environment.NewLine, lines);
}
diff --git a/PKHeX/Legality/LegalityCheckStrings.cs b/PKHeX/Legality/LegalityCheckStrings.cs
index 4426a3c66..044be09f8 100644
--- a/PKHeX/Legality/LegalityCheckStrings.cs
+++ b/PKHeX/Legality/LegalityCheckStrings.cs
@@ -24,6 +24,10 @@ public static class LegalityCheckStrings
public static string V192 {get; set;} = "{0} Relearn Move {1}: {2}";
/// Format text for exporting the type of Encounter that was matched for the the
public static string V195 {get; set;} = "Encounter Type: {0}";
+ /// Format text for exporting the that was matched for the the
+ public static string V248 {get; set;} = "Origin Seed: {0}";
+ /// Format text for exporting the that was matched for the the
+ public static string V249 {get; set;} = "PID Type: {0}";
/// Severity string for
public static string V500 { get; set; } = "Indeterminate";
diff --git a/PKHeX/Legality/RNG/MethodFinder.cs b/PKHeX/Legality/RNG/MethodFinder.cs
index 13c73433e..4ed0fdb52 100644
--- a/PKHeX/Legality/RNG/MethodFinder.cs
+++ b/PKHeX/Legality/RNG/MethodFinder.cs
@@ -17,7 +17,10 @@ public static PIDIV Analyze(PKM pk)
{
if (pk.Format < 3)
return AnalyzeGB(pk);
- var pid = pk.PID;
+ var pid = pk.Format >= 6 && pk.GenNumber >= 3 && pk.GenNumber < 6
+ ? pk.EncryptionConstant // use unmodified PID, quicker than checking if bit was flipped
+ : pk.PID; // use actual PID
+
var top = pid >> 16;
var bot = pid & 0xFFFF;
@@ -37,10 +40,13 @@ public static PIDIV Analyze(PKM pk)
return pidiv;
if (getMG4Match(pid, IVs, out pidiv))
return pidiv;
- if (getModifiedPID(pid, out pidiv))
+
+ if (getModifiedPID(pk, pid, out pidiv))
return pidiv;
if (pid <= 0xFF && getCuteCharmMatch(pk, pid, out pidiv))
return pidiv;
+ if (pk.IsShiny && getChainShinyMatch(pk, pid, IVs, out pidiv))
+ return pidiv;
return pidiv; // no match
}
@@ -125,16 +131,26 @@ private static bool getMG4Match(uint pid, uint[] IVs, out PIDIV pidiv)
if (!getIVs(C >> 16, D >> 16).SequenceEqual(IVs))
continue;
- pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.G4AntiShiny};
+ pidiv = new PIDIV {OriginSeed = seed, RNG = RNG.LCRNG, Type = PIDType.G4MGAntiShiny};
return true;
}
pidiv = null;
return false;
}
- private static bool getModifiedPID(uint pid, out PIDIV pidiv)
+ private static bool getModifiedPID(PKM pk, uint pid, out PIDIV pidiv)
{
+ var low = pid & 0xFFFF;
// generation 5 shiny PIDs
- // todo
+ if (low <= 0xFF)
+ {
+ var high = pid >> 16;
+ if ((pk.TID ^ pk.SID ^ low) == high)
+ {
+ pidiv = new PIDIV {NoSeed = true, Type = PIDType.G5MGShiny};
+ return true;
+ }
+ }
+
pidiv = null;
return false;
}
@@ -153,7 +169,7 @@ private static bool getCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv)
if (pk.PID >= gr + 25)
break;
- pidiv = new PIDIV { OriginSeed = 0, RNG = RNG.LCRNG, Type = PIDType.CuteCharm };
+ pidiv = new PIDIV {NoSeed = true, RNG = RNG.LCRNG, Type = PIDType.CuteCharm};
return true;
case 1: // female
if (pk.PID >= 25)
@@ -161,12 +177,57 @@ private static bool getCuteCharmMatch(PKM pk, uint pid, out PIDIV pidiv)
if (254 <= pk.PersonalInfo.Gender) // no modification for PID
break;
- pidiv = new PIDIV { OriginSeed = 0, RNG = RNG.LCRNG, Type = PIDType.CuteCharm };
+ pidiv = new PIDIV {NoSeed = true, RNG = RNG.LCRNG, Type = PIDType.CuteCharm};
return true;
}
pidiv = null;
return false;
}
+ private static bool getChainShinyMatch(PKM pk, uint pid, uint[] IVs, out PIDIV pidiv)
+ {
+ // 13 shiny bits
+ // PIDH & 7
+ // PIDL & 7
+ // IVs
+ var bot = getIVChunk(IVs, 0);
+ var top = getIVChunk(IVs, 3);
+ var reg = getSeedsFromIVs(RNG.LCRNG, top, bot);
+ foreach (var seed in reg)
+ {
+ // check the individual bits
+ var s = seed;
+ int i = 15;
+ while (true)
+ {
+ var bit = s >> 16 & 1;
+ if (bit != (pid >> i & 1))
+ break;
+ s = RNG.LCRNG.Prev(s);
+ if (--i == 2)
+ break;
+ }
+ if (i != 2) // bit failed
+ break;
+ // Shiny Bits of PID validated
+ var upper = s;
+ if ((upper >> 16 & 7) != (pid >> 16 & 7))
+ break;
+ var lower = RNG.LCRNG.Prev(upper);
+ if ((lower >> 16 & 7) != (pid & 7))
+ break;
+
+ var upid = ((pid & 0xFFFF) ^ pk.TID ^ pk.SID) & 0xFFF8 | (upper >> 16) & 0x7;
+ if (upid != pid >> 16)
+ break;
+
+ s = RNG.LCRNG.Reverse(lower, 2); // unroll one final time to get the origin seed
+ pidiv = new PIDIV {OriginSeed = s, RNG = RNG.LCRNG, Type = PIDType.ChainShiny};
+ return true;
+ }
+
+ pidiv = null;
+ return false;
+ }
private static PIDIV AnalyzeGB(PKM pk)
{
@@ -232,5 +293,12 @@ private static uint[] getIVs(RNG method, uint seed)
}
return ivs;
}
+ private static uint getIVChunk(uint[] IVs, int start)
+ {
+ uint val = 0;
+ for (int i = 0; i < 3; i++)
+ val |= IVs[i+start] << (5*i);
+ return val;
+ }
}
}
diff --git a/PKHeX/Legality/RNG/PIDIV.cs b/PKHeX/Legality/RNG/PIDIV.cs
index a55eac525..ee7fe2843 100644
--- a/PKHeX/Legality/RNG/PIDIV.cs
+++ b/PKHeX/Legality/RNG/PIDIV.cs
@@ -8,6 +8,8 @@ public class PIDIV
/// The RNG seed which immediately generates the PIDIV (starting with PID or IVs, whichever comes first).
public uint OriginSeed;
+ public bool NoSeed;
+
/// Type of PIDIV correlation
public PIDType Type;
}
diff --git a/PKHeX/Legality/RNG/PIDType.cs b/PKHeX/Legality/RNG/PIDType.cs
index a24d56b3c..acf12f4ed 100644
--- a/PKHeX/Legality/RNG/PIDType.cs
+++ b/PKHeX/Legality/RNG/PIDType.cs
@@ -35,10 +35,10 @@ public enum PIDType
Channel,
// ARNG Based
- G4AntiShiny,
+ G4MGAntiShiny,
// Formulaic
- G5AntiShiny,
+ G5MGShiny,
// Specified
Static,
diff --git a/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt b/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt
index 759da4af2..821975bed 100644
--- a/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt
+++ b/PKHeX/Resources/text/en/LegalityCheckStrings_en.txt
@@ -162,7 +162,6 @@ V98 = Unused Super Training Flag is flagged.
V95 = Can't receive Ribbon(s) as an egg.
V96 = GBA Champion Ribbon
V97 = Artist Ribbon
-V98 = National Ribbon (Purified)
V99 = Sinnoh Champion Ribbon
V100 = Legend Ribbon
V104 = Record Ribbon
diff --git a/Tests/PKHeX.Tests/PKM/PKMTests.cs b/Tests/PKHeX.Tests/PKM/PKMTests.cs
index 2cbe28435..2ff075bb9 100644
--- a/Tests/PKHeX.Tests/PKM/PKMTests.cs
+++ b/Tests/PKHeX.Tests/PKM/PKMTests.cs
@@ -180,7 +180,25 @@ public void PIDIVMatchingTest()
PID = 0x07578CB7, // 0x5271E97E rerolled
IVs = new[] {16, 13, 12, 02, 18, 03},
};
- Assert.AreEqual(PIDType.G4AntiShiny, MethodFinder.Analyze(pkASR)?.Type, "Unable to match PID to Antishiny4 spread");
+ Assert.AreEqual(PIDType.G4MGAntiShiny, MethodFinder.Analyze(pkASR)?.Type, "Unable to match PID to Antishiny4 spread");
+
+ var pkCS = new PK4
+ {
+ PID = 0xA9C1A9C6,
+ // TID = 0,
+ // SID = 0, // already default values, necessary for the forcing of a shiny PID
+ IVs = new[] {22, 14, 23, 24, 11, 04}
+ };
+ Assert.AreEqual(PIDType.ChainShiny, MethodFinder.Analyze(pkCS)?.Type, "Unable to match PID to Chain Shiny spread");
+
+ var pkS5 = new PK5
+ {
+ PID = 0xBEEF0037,
+ TID = 01337,
+ SID = 48097,
+ // IVs = new[] {22, 14, 23, 24, 11, 04} // unnecessary
+ };
+ Assert.AreEqual(PIDType.G5MGShiny, MethodFinder.Analyze(pkS5)?.Type, "Unable to match PID to PGF Shiny spread");
}
}
}