Documentation Index
Fetch the complete documentation index at: https://mintlify.com/kwsch/PKHeX/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The SaveFile class is the abstract base class for all Pokémon save file formats in PKHeX. It provides a unified interface for reading, writing, and manipulating save data across all generations of Pokémon games.
Key Concepts
Memory Buffer Architecture
SaveFile uses a memory-based architecture for efficient data manipulation:
public readonly Memory<byte> Buffer;
public Span<byte> Data => Buffer.Span;
This design allows direct byte-level access to save data while maintaining memory safety.
Core Properties
Every SaveFile implementation must provide:
- Version: The game version (see GameVersion enum)
- Generation: The generation number (1-9)
- Context: The entity context for this save file
- PKMType: The type of PKM entities this save uses
- BoxCount: Number of storage boxes
- BoxSlotCount: Slots per box (typically 30)
Working with Save Files
Loading a Save File
using PKHeX.Core;
// Load from file bytes
byte[] saveData = File.ReadAllBytes("pokemon.sav");
var saveFile = SaveUtil.GetVariantSAV(saveData);
if (saveFile == null)
{
Console.WriteLine("Invalid save file!");
return;
}
Console.WriteLine($"Game: {saveFile.Version}");
Console.WriteLine($"Generation: {saveFile.Generation}");
Console.WriteLine($"Trainer: {saveFile.OT}");
Console.WriteLine($"TID: {saveFile.DisplayTID}");
SaveFile implements ITrainerInfo and provides trainer properties:
// Basic trainer info
string trainerName = saveFile.OT;
byte gender = saveFile.Gender;
int language = saveFile.Language;
// Trainer IDs
uint tid = saveFile.DisplayTID; // Visible TID
uint sid = saveFile.DisplaySID; // Visible SID
uint id32 = saveFile.ID32; // Combined 32-bit ID
// Play time
int hours = saveFile.PlayedHours;
int minutes = saveFile.PlayedMinutes;
int seconds = saveFile.PlayedSeconds;
string playTime = saveFile.PlayTimeString; // "123ː45ː67"
// Money and resources
uint money = saveFile.Money;
Box Data Management
Reading Box Data
// Get all Pokémon from all boxes
IList<PKM> allPokemon = saveFile.BoxData;
// Get Pokémon from a specific box (0-indexed)
PKM[] box1Pokemon = saveFile.GetBoxData(0);
// Get a specific Pokémon
PKM pokemon = saveFile.GetBoxSlotAtIndex(box: 0, slot: 5);
// Check if slot has Pokémon
int offset = saveFile.GetBoxSlotOffset(0, 0);
bool hasData = saveFile.IsPKMPresent(saveFile.BoxBuffer[offset..]);
Writing Box Data
// Set a Pokémon in a box slot
PKM myPokemon = CreatePokemon();
saveFile.SetBoxSlotAtIndex(myPokemon, box: 1, slot: 10);
// Set all box data at once
IList<PKM> newBoxData = LoadPokemonFromFile();
saveFile.BoxData = newBoxData;
// Find next open slot
int nextSlot = saveFile.NextOpenBoxSlot();
if (nextSlot >= 0)
{
saveFile.SetBoxSlotAtIndex(myPokemon, nextSlot);
}
else
{
Console.WriteLine("Storage is full!");
}
Party Management
// Check if save has party data
if (saveFile.HasParty)
{
// Get party Pokémon
IList<PKM> party = saveFile.PartyData;
int partyCount = saveFile.PartyCount;
// Get specific party member
PKM starter = saveFile.GetPartySlotAtIndex(0);
// Set party data
PKM[] newParty = new PKM[6];
// ... populate newParty ...
saveFile.PartyData = newParty;
// Delete a party slot (shifts remaining down)
saveFile.DeletePartySlot(2);
}
Pokédex Operations
if (saveFile.HasPokeDex)
{
// Check seen/caught status
bool seenPikachu = saveFile.GetSeen(25);
bool caughtPikachu = saveFile.GetCaught(25);
// Set seen/caught
saveFile.SetSeen(25, true);
saveFile.SetCaught(25, true);
// Get completion stats
int seenCount = saveFile.SeenCount;
int caughtCount = saveFile.CaughtCount;
decimal percentSeen = saveFile.PercentSeen;
decimal percentCaught = saveFile.PercentCaught;
Console.WriteLine($"Seen: {seenCount}/{saveFile.MaxSpeciesID} ({percentSeen:P1})");
Console.WriteLine($"Caught: {caughtCount}/{saveFile.MaxSpeciesID} ({percentCaught:P1})");
}
Storage Operations
Sorting Boxes
// Sort all boxes by species
int repositioned = saveFile.SortBoxes();
// Sort specific box range
int count = saveFile.SortBoxes(
BoxStart: 0,
BoxEnd: 5,
reverse: false
);
// Custom sort method
int sorted = saveFile.SortBoxes(
BoxStart: 0,
BoxEnd: -1,
sortMethod: (pokemon, startIndex) => pokemon.OrderBy(p => p.CurrentLevel)
);
Clearing Boxes
// Clear all Pokémon from boxes 10-15
int deleted = saveFile.ClearBoxes(BoxStart: 10, BoxEnd: 15);
// Clear with criteria
int deletedShinies = saveFile.ClearBoxes(
BoxStart: 0,
BoxEnd: -1,
deleteCriteria: pk => pk.IsShiny
);
Modifying Pokémon in Bulk
// Apply modification to all Pokémon in boxes
int modified = saveFile.ModifyBoxes(
action: pk => pk.Heal(), // Heal all Pokémon
BoxStart: 0,
BoxEnd: -1
);
// Make all Pokémon shiny
int shinified = saveFile.ModifyBoxes(
action: pk => pk.SetShiny(),
BoxStart: 0,
BoxEnd: 5
);
Flags and Event Data
// Read and write flag bits
bool flag = saveFile.GetFlag(offset: 0x1000, bitIndex: 3);
saveFile.SetFlag(offset: 0x1000, bitIndex: 3, value: true);
// Marks save as edited
// saveFile.State.Edited is automatically set to true
Saving Changes
Writing Save File
// Export save data
Memory<byte> finalData = saveFile.Write();
File.WriteAllBytes("pokemon_modified.sav", finalData.ToArray());
// With export settings
var settings = BinaryExportSetting.IncludeFooter;
Memory<byte> dataWithFooter = saveFile.Write(settings);
Checksums
SaveFile automatically manages checksums:
// Check if checksums are valid
if (!saveFile.ChecksumsValid)
{
Console.WriteLine("Warning: Invalid checksums!");
Console.WriteLine(saveFile.ChecksumInfo);
}
// Checksums are automatically updated on Write()
var data = saveFile.Write(); // SetChecksums() called internally
Game-Specific Implementations
Generation 8 Example (Sword/Shield)
if (saveFile is SAV8SWSH swsh)
{
// Access game-specific blocks
Box8 boxInfo = swsh.BoxInfo;
Party8 partyInfo = swsh.PartyInfo;
MyItem8 items = swsh.Items;
Zukan8 pokedex = swsh.Zukan;
Record8 records = swsh.Records;
// Check save revision (base game, IoA, CT)
int revision = swsh.SaveRevision;
string revisionStr = swsh.SaveRevisionString; // "-Base", "-IoA", "-CT"
// Access raid data
RaidSpawnList8 galarRaids = swsh.RaidGalar;
RaidSpawnList8 armorRaids = swsh.RaidArmor;
RaidSpawnList8 crownRaids = swsh.RaidCrown;
}
Advanced Topics
Entity Import Settings
// Configure how Pokémon are imported
var settings = new EntityImportSettings
{
UpdateToSaveFile = EntityImportOption.Enable, // Adapt to save format
UpdatePokeDex = EntityImportOption.Enable, // Update Pokédex
UpdateRecord = EntityImportOption.Enable // Update records
};
saveFile.SetBoxSlotAtIndex(pokemon, 0, 0, settings);
Slot Protection
// Check if slot is locked or protected
bool isLocked = saveFile.IsBoxSlotLocked(box: 0, slot: 0);
bool isProtected = saveFile.IsBoxSlotOverwriteProtected(box: 0, slot: 0);
// Check storage status
bool isFull = saveFile.IsStorageFull;
Box Management
// Current box
int currentBox = saveFile.CurrentBox;
saveFile.CurrentBox = 5;
// Swap two boxes
bool success = saveFile.SwapBox(box1: 0, box2: 5);
// Move box to new position
bool moved = saveFile.MoveBox(box: 3, insertBeforeBox: 0);
Best Practices
Always validate save data before making modifications:
- Check
ChecksumsValid before editing
- Verify
Version and Generation match expectations
- Use
IsOriginValid to validate Pokémon species
Use the State.Edited property to track changes:if (saveFile.State.Edited)
{
Console.WriteLine("Save file has been modified");
}