using System; using System.Runtime.CompilerServices; namespace PKHeX.Core; /// /// Logic for Generating and Verifying Gen8 Raid Templates against PKM data. /// public static class RaidRNG { /// /// Verify a Raid Seed against a PKM. /// /// Entity to verify against /// Seed that generated the entity /// Buffer of IVs (potentially with already specified values) /// Parameters to generate with /// Force the entity to be non-shiny via special handling /// True if the seed matches the entity public static bool Verify(PKM pk, ulong seed, Span ivs, in GenerateParam8 param, bool forceNoShiny = false) { var rng = new Xoroshiro128Plus(seed); var ec = (uint)rng.NextInt(); if (ec != pk.EncryptionConstant) return false; uint pid; bool isShiny; { var trID = (uint)rng.NextInt(); pid = (uint)rng.NextInt(); var xor = GetShinyXor(pid, trID); isShiny = xor < 16; if (isShiny && forceNoShiny) { ForceShinyState(false, ref pid, trID); isShiny = false; } } ForceShinyState(isShiny, ref pid, pk.ID32); if (pk.PID != pid) return false; const int UNSET = -1; const int MAX = 31; if (param.IVs.IsSpecified) param.IVs.CopyToSpeedLast(ivs); else ivs.Fill(UNSET); for (int i = ivs.Count(MAX); i < param.FlawlessIVs; i++) { int index = (int)rng.NextInt(6); while (ivs[index] != UNSET) index = (int)rng.NextInt(6); ivs[index] = MAX; } for (int i = 0; i < 6; i++) { if (ivs[i] == UNSET) ivs[i] = (int)rng.NextInt(32); } if (pk.IV_HP != ivs[0]) return false; if (pk.IV_ATK != ivs[1]) return false; if (pk.IV_DEF != ivs[2]) return false; if (pk.IV_SPA != ivs[3]) return false; if (pk.IV_SPD != ivs[4]) return false; if (pk.IV_SPE != ivs[5]) return false; int abil = param.Ability switch { AbilityPermission.Any12H => (int)rng.NextInt(3), AbilityPermission.Any12 => (int)rng.NextInt(2), _ => param.Ability.GetSingleValue(), }; abil <<= 1; // 1/2/4 var current = pk.AbilityNumber; if (abil == 4) { if (current != 4 && pk is PK8) return false; } // else, for things that were made Hidden Ability, defer to Ability Checks (Ability Patch) switch (param.GenderRatio) { case PersonalInfo.RatioMagicGenderless: if (pk.Gender != 2) return false; break; case PersonalInfo.RatioMagicFemale: if (pk.Gender != 1) return false; break; case PersonalInfo.RatioMagicMale: if (pk.Gender != 0) return false; break; default: var gender = (int)rng.NextInt(253) + 1 < param.GenderRatio ? 1 : 0; if (pk.Gender != gender && pk.Gender != 2) // allow Nincada(0/1)->Shedinja(2) return false; break; } int nature = param.Nature != Nature.Random ? (int)param.Nature : param.Species == (int)Species.Toxtricity ? ToxtricityUtil.GetRandomNature(ref rng, pk.Form) : (byte)rng.NextInt(25); if (pk.Nature != nature) return false; if (pk is IScaledSize s) { var height = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); var weight = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); if (height == 0 && weight == 0 && pk is IHomeTrack { HasTracker: true}) { // HOME rerolls height/weight if both are 0 // This behavior started in 3.0.0, so only flag if the context is 9 or above. if (pk.Context is not (EntityContext.Gen8 or EntityContext.Gen8a or EntityContext.Gen8b)) return false; } else { if (s.HeightScalar != height) return false; if (s.WeightScalar != weight) return false; } } return true; } [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void ForceShinyState(bool isShiny, ref uint pid, uint tid) { if (isShiny) { if (!GetIsShiny(tid, pid)) pid = GetShinyPID((ushort)(tid & 0xFFFFu), (ushort)(tid >> 16), pid, 0); } else { if (GetIsShiny(tid, pid)) pid ^= 0x1000_0000; } } /// /// Apply the details to the PKM /// /// Entity to verify against /// Seed that generated the entity /// Buffer of IVs (potentially with already specified values) /// Parameters to generate with /// Criteria to generate with /// True if the seed matches the entity public static bool TryApply(PK8 pk, ulong seed, Span ivs, in GenerateParam8 param, EncounterCriteria criteria) { var rng = new Xoroshiro128Plus(seed); pk.EncryptionConstant = (uint)rng.NextInt(); uint pid; bool isShiny; { var trID = (uint)rng.NextInt(); pid = (uint)rng.NextInt(); var xor = GetShinyXor(pid, trID); isShiny = xor < 16; if (isShiny && param.Shiny == Shiny.Never) { ForceShinyState(false, ref pid, trID); isShiny = false; } } if (isShiny) { if (!GetIsShiny(pk.ID32, pid)) pid = GetShinyPID(pk.TID16, pk.SID16, pid, 0); } else { if (GetIsShiny(pk.ID32, pid)) pid ^= 0x1000_0000; } pk.PID = pid; const int UNSET = -1; const int MAX = 31; if (param.IVs.IsSpecified) { param.IVs.CopyToSpeedLast(ivs); } else { ivs.Fill(UNSET); for (int i = ivs.Count(MAX); i < param.FlawlessIVs; i++) { int index; do { index = (int)rng.NextInt(6); } while (ivs[index] != UNSET); ivs[index] = MAX; } } for (int i = 0; i < 6; i++) { if (ivs[i] == UNSET) ivs[i] = (int)rng.NextInt(MAX + 1); } if (!param.IVs.IsSpecified && !criteria.IsIVsCompatibleSpeedLast(ivs, 8)) return false; pk.IV_HP = ivs[0]; pk.IV_ATK = ivs[1]; pk.IV_DEF = ivs[2]; pk.IV_SPA = ivs[3]; pk.IV_SPD = ivs[4]; pk.IV_SPE = ivs[5]; int abil = param.Ability switch { AbilityPermission.Any12H => (int)rng.NextInt(3), AbilityPermission.Any12 => (int)rng.NextInt(2), _ => param.Ability.GetSingleValue(), }; pk.RefreshAbility(abil); var gender = param.GenderRatio switch { PersonalInfo.RatioMagicGenderless => 2, PersonalInfo.RatioMagicFemale => 1, PersonalInfo.RatioMagicMale => 0, _ => (int) rng.NextInt(253) + 1 < param.GenderRatio ? 1 : 0, }; if (criteria.Gender != FixedGenderUtil.GenderRandom && gender != criteria.Gender) return false; pk.Gender = gender; int nature = param.Nature != Nature.Random ? (byte)param.Nature : param.Species == (int)Species.Toxtricity ? ToxtricityUtil.GetRandomNature(ref rng, pk.Form) : (byte)rng.NextInt(25); pk.Nature = pk.StatNature = nature; var height = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); var weight = (int)rng.NextInt(0x81) + (int)rng.NextInt(0x80); pk.HeightScalar = (byte)height; pk.WeightScalar = (byte)weight; return true; } private static uint GetShinyPID(ushort tid, ushort sid, uint pid, uint type) { return (type ^ tid ^ sid ^ (pid & 0xFFFF)) << 16 | (pid & 0xFFFF); } private static bool GetIsShiny(uint id32, uint pid) => GetShinyXor(pid, id32) < 16; private static uint GetShinyXor(uint pid, uint id32) { var xor = pid ^ id32; return (xor ^ (xor >> 16)) & 0xFFFF; } }