Skip to main content
This guide will walk you through the essential operations in PKHeX.Core: loading save files, accessing Pokémon data, making modifications, and saving changes.

Basic Usage

1
Load a Save File
2
Use the SaveUtil class to detect and load save files from any generation:
3
using PKHeX.Core;

// Load a save file from disk
string savePath = "path/to/your/save.sav";

if (SaveUtil.TryGetSaveFile(savePath, out SaveFile? sav))
{
    Console.WriteLine($"Loaded {sav.Version} save file!");
    Console.WriteLine($"Trainer: {sav.OT}");
    Console.WriteLine($"Generation: {sav.Generation}");
}
else
{
    Console.WriteLine("Failed to load save file.");
}
4
SaveUtil automatically detects the save file type and returns the appropriate SaveFile subclass (e.g., SAV7SM, SAV8SWSH, SAV9SV).
5
Load from Byte Array
6
You can also load save data from a byte array:
7
byte[] saveData = File.ReadAllBytes("save.sav");
Memory<byte> data = new Memory<byte>(saveData);

if (SaveUtil.TryGetSaveFile(data, out SaveFile? sav, savePath))
{
    Console.WriteLine($"Game Version: {sav.Version}");
    Console.WriteLine($"Playtime: {sav.PlayTimeString}");
}
8
Access Pokémon Data
9
Once you have a save file loaded, you can access Pokémon from boxes or the party:
10
Box Pokémon
// Get all Pokémon from all boxes
IList<PKM> allPokemon = sav.BoxData;
Console.WriteLine($"Total box slots: {allPokemon.Count}");

// Get Pokémon from a specific box (0-indexed)
PKM[] boxPokemon = sav.GetBoxData(box: 0);
foreach (var pk in boxPokemon)
{
    if (pk.Species != 0) // Non-empty slot
    {
        Console.WriteLine($"{pk.Nickname} (#{pk.Species}) Lv. {pk.Stat_Level}");
    }
}

// Get a specific Pokémon from box and slot
PKM pokemon = sav.GetBoxSlotAtIndex(box: 0, slot: 0);
if (pokemon.Species != 0)
{
    Console.WriteLine($"Species: {pokemon.Species}");
    Console.WriteLine($"Nickname: {pokemon.Nickname}");
    Console.WriteLine($"Level: {pokemon.Stat_Level}");
    Console.WriteLine($"OT: {pokemon.OriginalTrainerName}");
}
Party Pokémon
// Get all party Pokémon
IList<PKM> party = sav.PartyData;
Console.WriteLine($"Party count: {sav.PartyCount}");

for (int i = 0; i < sav.PartyCount; i++)
{
    PKM pk = sav.GetPartySlotAtIndex(i);
    Console.WriteLine($"Party #{i + 1}: {pk.Nickname} Lv. {pk.Stat_Level}");
    Console.WriteLine($"  HP: {pk.Stat_HPCurrent}/{pk.Stat_HPMax}");
    Console.WriteLine($"  Moves: {pk.Move1}, {pk.Move2}, {pk.Move3}, {pk.Move4}");
}
11
Modify Pokémon Properties
12
PKHeX.Core provides full access to all Pokémon properties:
13
// Get a Pokémon to modify
PKM pk = sav.GetBoxSlotAtIndex(box: 0, slot: 0);

if (pk.Species != 0)
{
    // Modify basic properties
    pk.Nickname = "Charizard";
    pk.Stat_Level = 100;
    pk.CurrentFriendship = 255;
    
    // Modify stats (IVs)
    pk.IV_HP = 31;
    pk.IV_ATK = 31;
    pk.IV_DEF = 31;
    pk.IV_SPA = 31;
    pk.IV_SPD = 31;
    pk.IV_SPE = 31;
    
    // Modify EVs
    pk.EV_ATK = 252;
    pk.EV_SPE = 252;
    pk.EV_HP = 6;
    
    // Modify moves
    pk.Move1 = 406; // Dragon Rush
    pk.Move2 = 394; // Flare Blitz
    pk.Move3 = 369; // Roost
    pk.Move4 = 332; // Earthquake
    
    // Set as shiny (example)
    pk.PID = EntityPID.GetRandomPID(
        pk.Species, 
        pk.Gender, 
        pk.Version, 
        pk.Nature, 
        pk.Form, 
        pk.ID32
    );
    
    Console.WriteLine("Modified Pokémon properties!");
}
14
Always validate modifications using PKHeX.Core’s legality checker to ensure your Pokémon are legal for online play.
15
Write Pokémon Back to Save
16
After modifying a Pokémon, write it back to the save file:
17
// Write the modified Pokémon back to its slot
sav.SetBoxSlotAtIndex(pk, box: 0, slot: 0);

// Or write to a different slot
sav.SetBoxSlotAtIndex(pk, box: 1, slot: 5);

// Mark the save as edited (usually automatic)
Console.WriteLine($"Save edited: {sav.State.Edited}");
18
Save Changes to File
19
Finally, write the modified save file back to disk:
20
// Get the final save data with checksums updated
Memory<byte> finalData = sav.Write();

// Write to file
string outputPath = "path/to/modified_save.sav";
File.WriteAllBytes(outputPath, finalData.ToArray());

Console.WriteLine($"Save file written to {outputPath}");
Console.WriteLine($"Checksums valid: {sav.ChecksumsValid}");
21
The Write() method automatically updates checksums before returning the data, ensuring the save file is valid.

Complete Example

Here’s a complete example that demonstrates the entire workflow:
Program.cs
using PKHeX.Core;

class Program
{
    static void Main(string[] args)
    {
        // 1. Load the save file
        string savePath = "pokemon_save.sav";
        if (!SaveUtil.TryGetSaveFile(savePath, out SaveFile? sav))
        {
            Console.WriteLine("Failed to load save file!");
            return;
        }

        Console.WriteLine($"Loaded {sav.Version} save!");
        Console.WriteLine($"Trainer: {sav.OT}");
        Console.WriteLine($"TID: {sav.DisplayTID}");
        Console.WriteLine($"Playtime: {sav.PlayTimeString}");
        Console.WriteLine();

        // 2. Access the first Pokémon in Box 1
        PKM firstPokemon = sav.GetBoxSlotAtIndex(box: 0, slot: 0);
        
        if (firstPokemon.Species == 0)
        {
            Console.WriteLine("First slot is empty!");
            return;
        }

        Console.WriteLine($"Found: {firstPokemon.Nickname}");
        Console.WriteLine($"Species: #{firstPokemon.Species}");
        Console.WriteLine($"Level: {firstPokemon.Stat_Level}");
        Console.WriteLine($"Nature: {firstPokemon.Nature}");
        Console.WriteLine($"Ability: {firstPokemon.Ability}");
        Console.WriteLine();

        // 3. Modify the Pokémon
        firstPokemon.Stat_Level = 100;
        firstPokemon.CurrentFriendship = 255;
        
        // Max out IVs
        firstPokemon.IV_HP = 31;
        firstPokemon.IV_ATK = 31;
        firstPokemon.IV_DEF = 31;
        firstPokemon.IV_SPA = 31;
        firstPokemon.IV_SPD = 31;
        firstPokemon.IV_SPE = 31;
        
        // Heal to full HP
        firstPokemon.Stat_HPCurrent = firstPokemon.Stat_HPMax;
        
        Console.WriteLine("Modified Pokémon to Level 100 with max IVs!");
        Console.WriteLine();

        // 4. Write back to save
        sav.SetBoxSlotAtIndex(firstPokemon, box: 0, slot: 0);

        // 5. Save the modified save file
        Memory<byte> finalData = sav.Write();
        File.WriteAllBytes("pokemon_save_modified.sav", finalData.ToArray());
        
        Console.WriteLine("Save file modified and written!");
        Console.WriteLine($"Checksums valid: {sav.ChecksumsValid}");
    }
}

Working with Different Formats

Export Pokémon to File

Export individual Pokémon to .pk* files:
PKM pokemon = sav.GetBoxSlotAtIndex(0, 0);

// Get the encrypted box data (standard format)
byte[] data = pokemon.EncryptedBoxData;

// Save to file with appropriate extension
string ext = pokemon.Extension; // e.g., "pk9" for Gen 9
File.WriteAllBytes($"{pokemon.Nickname}.{ext}", data);

Import Pokémon from File

byte[] pkData = File.ReadAllBytes("Charizard.pk9");
Memory<byte> memory = new Memory<byte>(pkData);

// Create PKM object based on save context
PKM imported = EntityFormat.GetFromBytes(memory.Span, sav.Context);

if (imported != null)
{
    // Add to the first empty slot
    int emptySlot = sav.NextOpenBoxSlot();
    if (emptySlot >= 0)
    {
        sav.SetBoxSlotAtIndex(imported, emptySlot);
        Console.WriteLine($"Imported {imported.Nickname} to slot {emptySlot}");
    }
}

Key Classes Reference

SaveUtil

Main class for loading and detecting save file types

SaveFile

Base class for all save file types with box/party access

PKM

Abstract base class representing a Pokémon with all properties

EntityFormat

Utilities for creating PKM objects from raw data

Next Steps

Save File Concepts

Deep dive into save file structure and manipulation

Pokémon Entities

Learn about Pokémon data structures and properties

API Reference

Explore the complete API documentation

Build docs developers (and LLMs) love