Basic Usage
The simplest way to check a Pokémon’s legality:using PKHeX.Core;
// Load a Pokémon from file
byte[] data = File.ReadAllBytes("pokemon.pk9");
var pk = PKMConverter.GetPKMfromBytes(data);
if (pk == null)
{
Console.WriteLine("Failed to load Pokémon data");
return;
}
// Perform legality analysis
var analysis = new LegalityAnalysis(pk);
// Check the result
if (analysis.Valid)
{
Console.WriteLine("✓ Pokémon is legal!");
}
else
{
Console.WriteLine("✗ Pokémon has legality issues");
}
Constructors
TheLegalityAnalysis class has three constructors:
Standard Constructor
// Most common usage - uses Pokémon's built-in PersonalInfo
var analysis = new LegalityAnalysis(pk);
With Storage Context
// Specify where the Pokémon came from
var analysis = new LegalityAnalysis(pk, StorageSlotType.Box);
var analysis2 = new LegalityAnalysis(pk, StorageSlotType.Party);
var analysis3 = new LegalityAnalysis(pk, StorageSlotType.BattleTeam);
With Save File Context
// Use save file's personal table for more accurate checking
var sav = SaveUtil.GetVariantSAV(File.ReadAllBytes("save.sav"));
if (sav != null)
{
var pk = sav.GetBoxSlotAtIndex(0, 0);
// This uses the save file's PersonalInfo table
// Important for games with different form data
var analysis = new LegalityAnalysis(pk, sav.Personal, StorageSlotType.Box);
}
Checking Multiple Pokémon
Box Validation
using PKHeX.Core;
public static void ValidateBox(SaveFile sav, int boxNumber)
{
int boxStart = sav.GetBoxSlotOffset(boxNumber, 0);
int boxSize = sav.BoxSlotCount;
for (int i = 0; i < boxSize; i++)
{
var pk = sav.GetBoxSlotAtIndex(boxNumber, i);
if (pk.Species == 0)
continue; // Empty slot
var analysis = new LegalityAnalysis(pk, sav.Personal);
if (!analysis.Valid)
{
Console.WriteLine($"Box {boxNumber}, Slot {i}: {pk.Nickname}");
Console.WriteLine($" Species: {pk.Species}");
Console.WriteLine($" Issues found: {analysis.Results.Count(r => !r.Valid)}");
}
}
}
Full Save Validation
public static Dictionary<string, List<PKM>> ValidateEntireSave(SaveFile sav)
{
var illegal = new Dictionary<string, List<PKM>>();
// Check all boxes
for (int box = 0; box < sav.BoxCount; box++)
{
for (int slot = 0; slot < sav.BoxSlotCount; slot++)
{
var pk = sav.GetBoxSlotAtIndex(box, slot);
if (pk.Species == 0) continue;
var analysis = new LegalityAnalysis(pk, sav.Personal);
if (!analysis.Valid)
{
var key = $"Box {box + 1}, Slot {slot + 1}";
if (!illegal.ContainsKey(key))
illegal[key] = new List<PKM>();
illegal[key].Add(pk);
}
}
}
// Check party
for (int i = 0; i < sav.PartyCount; i++)
{
var pk = sav.GetPartySlot(sav.PartyData, i);
if (pk.Species == 0) continue;
var analysis = new LegalityAnalysis(pk, sav.Personal);
if (!analysis.Valid)
{
var key = $"Party Slot {i + 1}";
if (!illegal.ContainsKey(key))
illegal[key] = new List<PKM>();
illegal[key].Add(pk);
}
}
return illegal;
}
Batch Processing
using System.IO;
using System.Linq;
using PKHeX.Core;
public static void BatchValidate(string directory)
{
var files = Directory.GetFiles(directory, "*.pk*");
var results = files.AsParallel().Select(file =>
{
try
{
var data = File.ReadAllBytes(file);
var pk = PKMConverter.GetPKMfromBytes(data);
if (pk == null)
return (file, valid: false, error: "Failed to load");
var analysis = new LegalityAnalysis(pk);
return (file, valid: analysis.Valid, error: (string)null);
}
catch (Exception ex)
{
return (file, valid: false, error: ex.Message);
}
}).ToList();
// Summary
int total = results.Count;
int legal = results.Count(r => r.valid);
int illegal = total - legal;
Console.WriteLine($"Total: {total}");
Console.WriteLine($"Legal: {legal} ({100.0 * legal / total:F1}%)");
Console.WriteLine($"Illegal: {illegal} ({100.0 * illegal / total:F1}%)");
// Show illegal files
foreach (var (file, valid, error) in results.Where(r => !r.valid))
{
Console.WriteLine($" {Path.GetFileName(file)}: {error ?? "Illegal"}");
}
}
Performance Considerations
Reusing Analysis Objects
Don’t reuse
LegalityAnalysis objects. Create a new one for each Pokémon.// ✗ WRONG - Don't do this
var analysis = new LegalityAnalysis(pk1);
analysis.Entity = pk2; // This property doesn't exist!
// ✓ CORRECT - Create new instances
var analysis1 = new LegalityAnalysis(pk1);
var analysis2 = new LegalityAnalysis(pk2);
Parallel Processing
Legality analysis is thread-safe for read operations:var pokemonList = LoadManyPokemon();
var results = pokemonList.AsParallel()
.WithDegreeOfParallelism(Environment.ProcessorCount)
.Select(pk => new
{
Pokemon = pk,
Analysis = new LegalityAnalysis(pk)
})
.ToList();
foreach (var item in results)
{
if (!item.Analysis.Valid)
{
Console.WriteLine($"{item.Pokemon.Nickname} is illegal");
}
}
Accessing Analysis Data
Key Properties
var analysis = new LegalityAnalysis(pk);
// Overall validity
bool isLegal = analysis.Valid;
bool didComplete = analysis.Parsed;
// Entity being checked
PKM pokemon = analysis.Entity;
// All check results
IReadOnlyList<CheckResult> results = analysis.Results;
// Encounter information
IEncounterable encounter = analysis.EncounterMatch;
IEncounterable original = analysis.EncounterOriginal;
// Storage context
StorageSlotType origin = analysis.SlotOrigin;
// Detailed info
LegalInfo info = analysis.Info;
LegalInfo Properties
var analysis = new LegalityAnalysis(pk);
var info = analysis.Info;
// Generation data
byte generation = info.Generation;
// Encounter details
IEncounterable match = info.EncounterMatch;
// Move validation results
MoveResult[] currentMoves = info.Moves;
MoveResult[] relearnMoves = info.Relearn;
// Evolution chains
EvolutionHistory evolutions = info.EvoChainsAllGens;
// PID/IV information
PIDIV pidiv = info.PIDIV;
bool pidParsed = info.PIDParsed;
// Frame matching
bool frameMatches = info.FrameMatches;
bool pidivMatches = info.PIDIVMatches;
Move Validation
var analysis = new LegalityAnalysis(pk);
// Check current moves
for (int i = 0; i < 4; i++)
{
var moveResult = analysis.Info.Moves[i];
if (moveResult.Valid)
{
Console.WriteLine($"Move {i + 1}: Legal");
Console.WriteLine($" Method: {moveResult.Info.Method}");
Console.WriteLine($" Game: {moveResult.Info.Environment}");
}
else
{
Console.WriteLine($"Move {i + 1}: Illegal");
if (moveResult.Expect != 0)
{
Console.WriteLine($" Expected: {moveResult.Expect}");
}
}
}
// Check relearn moves (Gen 6+)
for (int i = 0; i < 4; i++)
{
var relearnResult = analysis.Info.Relearn[i];
if (relearnResult.Valid)
{
Console.WriteLine($"Relearn {i + 1}: Legal");
}
else
{
Console.WriteLine($"Relearn {i + 1}: Illegal");
}
}
// Quick check for all moves valid
bool allMovesValid = MoveResult.AllValid(analysis.Info.Moves);
bool allRelearnValid = MoveResult.AllValid(analysis.Info.Relearn);
Encounter Information
var analysis = new LegalityAnalysis(pk);
var encounter = analysis.EncounterMatch;
Console.WriteLine($"Encounter Type: {encounter.GetType().Name}");
Console.WriteLine($"Species: {encounter.Species}");
Console.WriteLine($"Form: {encounter.Form}");
Console.WriteLine($"Level Range: {encounter.LevelMin}-{encounter.LevelMax}");
Console.WriteLine($"Generation: {encounter.Generation}");
Console.WriteLine($"Context: {encounter.Context}");
// Check encounter type
if (encounter is EncounterStatic9 static9)
{
Console.WriteLine("Static encounter (Gen 9)");
Console.WriteLine($"Location: {static9.Location}");
}
else if (encounter is EncounterSlot9 slot9)
{
Console.WriteLine("Wild encounter (Gen 9)");
Console.WriteLine($"Location: {slot9.Location}");
}
else if (encounter is MysteryGift gift)
{
Console.WriteLine("Mystery Gift");
Console.WriteLine($"Card Title: {gift.CardTitle}");
}
else if (encounter is EncounterEgg egg)
{
Console.WriteLine("Egg encounter");
}
else if (encounter is EncounterTrade trade)
{
Console.WriteLine("In-game trade");
}
else if (encounter is EncounterInvalid)
{
Console.WriteLine("No valid encounter found!");
}
Error Handling
try
{
var pk = PKMConverter.GetPKMfromBytes(data);
if (pk == null)
{
Console.WriteLine("Invalid Pokémon data");
return;
}
var analysis = new LegalityAnalysis(pk);
if (!analysis.Parsed)
{
Console.WriteLine("Analysis failed to complete");
Console.WriteLine("This usually indicates corrupted data");
// Check for error results
var errors = analysis.Results
.Where(r => r.Result == LegalityCheckResultCode.Error);
foreach (var error in errors)
{
Console.WriteLine($"Error in {error.Identifier}");
}
return;
}
// Normal processing...
}
catch (Exception ex)
{
Console.WriteLine($"Exception during analysis: {ex.Message}");
}
Quick Reference
Check if a Pokémon is legal
Check if a Pokémon is legal
var analysis = new LegalityAnalysis(pk);
bool isLegal = analysis.Valid;
Get all invalid checks
Get all invalid checks
var analysis = new LegalityAnalysis(pk);
var issues = analysis.Results.Where(r => !r.Valid);
Check specific aspect
Check specific aspect
var analysis = new LegalityAnalysis(pk);
var abilityCheck = analysis.Results
.FirstOrDefault(r => r.Identifier == CheckIdentifier.Ability);
Get encounter match
Get encounter match
var analysis = new LegalityAnalysis(pk);
var encounter = analysis.EncounterMatch;
Next Steps
Interpreting Results
Learn how to understand and display legality check results
Custom Validation
Add your own custom validation logic