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
After modifying save data, you need to properly export and write the save file back to disk. PKHeX.Core handles encryption, checksums, and format-specific requirements automatically.Basic Save Export
Writing to File
The simplest way to export a save file:using PKHeX.Core;
// Load and modify save
var sav = SaveUtil.GetSaveFile("original.sav");
if (sav == null) return;
sav.OT = "MODIFIED";
sav.Money = 999999;
// Export save data
var data = sav.Write();
// Write to disk
File.WriteAllBytes("modified.sav", data.ToArray());
Console.WriteLine("Save file exported successfully!");
Using BinaryExportSetting
Control how the save is exported withBinaryExportSetting:
using PKHeX.Core;
// Export with default settings (includes everything)
var dataDefault = sav.Write(BinaryExportSetting.None);
// Export without footer (if present)
var dataNoFooter = sav.Write(BinaryExportSetting.ExcludeFooter);
// Export without header (if present)
var dataNoHeader = sav.Write(BinaryExportSetting.ExcludeHeader);
// Export without finalization
var dataNoFinalize = sav.Write(BinaryExportSetting.ExcludeFinalize);
// Combine flags
var dataCustom = sav.Write(
BinaryExportSetting.ExcludeHeader | BinaryExportSetting.ExcludeFooter
);
// Write to file
File.WriteAllBytes("save.sav", dataDefault.ToArray());
Export Settings Explained
BinaryExportSetting.None
Exports complete save file with all sections:// Default export - includes everything
var data = sav.Write(BinaryExportSetting.None);
// This is equivalent to:
var data = sav.Write();
// Use for:
// - Normal save file export
// - When you want the complete file
BinaryExportSetting.ExcludeFooter
Removes footer data (Gen 3-7 emulator formats):// Export without footer
var data = sav.Write(BinaryExportSetting.ExcludeFooter);
// Use for:
// - Converting emulator saves to clean format
// - Removing RTC data from Gen 3 saves
BinaryExportSetting.ExcludeHeader
Removes header data (if present):// Export without header
var data = sav.Write(BinaryExportSetting.ExcludeHeader);
// Use for:
// - Removing special headers from modified saves
// - Converting between formats
BinaryExportSetting.ExcludeFinalize
Skips finalization steps:// Export without finalization
var data = sav.Write(BinaryExportSetting.ExcludeFinalize);
// Use for:
// - Debugging
// - When you want to manually handle finalization
Generation-Specific Export
Gen 1-2 (GB/GBC)
var sav1 = SaveUtil.GetSaveFile("red.sav") as SAV1;
if (sav1 != null)
{
// Modify data
sav1.OT = "ASH";
sav1.Money = 999999;
// Export (checksums updated automatically)
var data = sav1.Write();
File.WriteAllBytes("red_modified.sav", data.ToArray());
Console.WriteLine("Gen 1 save exported");
}
Gen 3 (GBA)
var sav3 = SaveUtil.GetSaveFile("emerald.sav") as SAV3E;
if (sav3 != null)
{
// Modify data
sav3.OT = "TRAINER";
// Export with checksums and section validation
var data = sav3.Write();
File.WriteAllBytes("emerald_modified.sav", data.ToArray());
Console.WriteLine("Gen 3 save exported");
}
Gen 4-5 (NDS)
var sav4 = SaveUtil.GetSaveFile("platinum.sav") as SAV4Pt;
if (sav4 != null)
{
// Modify data
sav4.OT = "TRAINER";
// Export with footer and checksums
var data = sav4.Write();
File.WriteAllBytes("platinum_modified.sav", data.ToArray());
Console.WriteLine("Gen 4 save exported");
}
Gen 6-7 (3DS)
var sav6 = SaveUtil.GetSaveFile("omega_ruby.sav") as SAV6AO;
if (sav6 != null)
{
// Modify data
sav6.OT = "TRAINER";
// Export with BEEF footer
var data = sav6.Write();
File.WriteAllBytes("omega_ruby_modified.sav", data.ToArray());
Console.WriteLine("Gen 6 save exported");
}
Gen 8-9 (Switch)
var sav8 = SaveUtil.GetSaveFile("main") as SAV8SWSH;
if (sav8 != null)
{
// Modify data
sav8.MyStatus.OT = "TRAINER";
// Export (automatically encrypted)
var data = sav8.Write();
File.WriteAllBytes("main", data.ToArray());
Console.WriteLine("Gen 8 save exported (encrypted)");
}
var sav9 = SaveUtil.GetSaveFile("main") as SAV9SV;
if (sav9 != null)
{
// Modify data
sav9.MyStatus.OT = "TRAINER";
// Export (automatically encrypted with hash validation)
var data = sav9.Write();
File.WriteAllBytes("main", data.ToArray());
Console.WriteLine("Gen 9 save exported (encrypted)");
}
Gen 8-9 saves are automatically encrypted during export. The encryption matches the game’s format.
Validating Before Export
Checksum Validation
Always verify checksums before exporting:public bool ExportWithValidation(SaveFile sav, string outputPath)
{
// Check if checksums are valid
if (!sav.ChecksumsValid)
{
Console.WriteLine("Warning: Invalid checksums detected!");
Console.WriteLine(sav.ChecksumInfo);
// Attempt to fix by recalculating
sav.SetChecksums();
if (!sav.ChecksumsValid)
{
Console.WriteLine("Failed to fix checksums. Aborting.");
return false;
}
}
// Export
var data = sav.Write();
File.WriteAllBytes(outputPath, data.ToArray());
Console.WriteLine($"Save exported to: {outputPath}");
return true;
}
Pre-Export Validation
public bool ValidateBeforeExport(SaveFile sav)
{
// Check version
if (!sav.IsVersionValid())
{
Console.WriteLine("Invalid save version");
return false;
}
// Check if exportable
if (!sav.State.Exportable)
{
Console.WriteLine("Save is not exportable");
return false;
}
// Verify party Pokémon
for (int i = 0; i < sav.PartyCount; i++)
{
var pk = sav.GetPartySlot(i);
if (pk.Species == 0)
continue;
if (pk.Species > sav.MaxSpeciesID)
{
Console.WriteLine($"Invalid species in party slot {i}");
return false;
}
}
return true;
}
Creating Backups
Backup Before Modification
public void SafeModifyAndExport(string savePath)
{
// Create backup
string backupPath = savePath + ".bak";
File.Copy(savePath, backupPath, overwrite: true);
Console.WriteLine($"Backup created: {backupPath}");
// Load original
var sav = SaveUtil.GetSaveFile(savePath);
if (sav == null)
{
Console.WriteLine("Failed to load save");
return;
}
// Make modifications
sav.OT = "MODIFIED";
sav.Money = 999999;
// Validate
if (!ValidateBeforeExport(sav))
{
Console.WriteLine("Validation failed. Keeping backup.");
return;
}
// Export
var data = sav.Write();
File.WriteAllBytes(savePath, data.ToArray());
Console.WriteLine("Save modified and exported successfully");
}
Versioned Backups
public string CreateVersionedBackup(string savePath)
{
string directory = Path.GetDirectoryName(savePath);
string fileName = Path.GetFileNameWithoutExtension(savePath);
string extension = Path.GetExtension(savePath);
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string backupPath = Path.Combine(directory,
$"{fileName}_backup_{timestamp}{extension}");
File.Copy(savePath, backupPath);
Console.WriteLine($"Backup: {backupPath}");
return backupPath;
}
Working with Save File Metadata
Preserving Metadata
var sav = SaveUtil.GetSaveFile("save.sav");
if (sav == null) return;
// Store original metadata
var originalFilePath = sav.Metadata.FilePath;
var originalFileName = sav.Metadata.FileName;
// Modify save
sav.OT = "MODIFIED";
// Export with preserved metadata
var data = sav.Write();
File.WriteAllBytes(originalFilePath, data.ToArray());
Console.WriteLine($"Saved to: {originalFilePath}");
Updating Metadata
public void ExportWithMetadata(SaveFile sav, string newPath)
{
// Update metadata
sav.Metadata.SetExtraInfo(newPath);
// Export
var data = sav.Write();
File.WriteAllBytes(newPath, data.ToArray());
Console.WriteLine($"Exported with updated metadata: {newPath}");
}
Batch Export Operations
Export Multiple Saves
public void BatchExport(string[] savePaths, string outputDirectory)
{
Directory.CreateDirectory(outputDirectory);
foreach (string path in savePaths)
{
var sav = SaveUtil.GetSaveFile(path);
if (sav == null)
{
Console.WriteLine($"Skipping invalid: {path}");
continue;
}
// Apply modifications
sav.Money = 999999;
// Export
string fileName = Path.GetFileName(path);
string outputPath = Path.Combine(outputDirectory, fileName);
var data = sav.Write();
File.WriteAllBytes(outputPath, data.ToArray());
Console.WriteLine($"Exported: {fileName}");
}
}
Special Format Handling
GameCube Memory Card Saves
var memCard = new SAV3GCMemoryCard(File.ReadAllBytes("Card.raw"));
if (SaveUtil.TryGetSaveFile(memCard, out SaveFile? gcSave))
{
// Modify save
gcSave.OT = "TRAINER";
// Export back to memory card
var data = gcSave.Write();
// Write to memory card data
memCard.WriteSaveGameData(data.Span);
// Export full memory card
File.WriteAllBytes("Card_modified.raw", memCard.Data);
}
Emulator-Specific Formats
// Load save with emulator footer
var sav = SaveUtil.GetSaveFile("save.dsv");
if (sav?.Metadata.Handler != null)
{
Console.WriteLine($"Handler: {sav.Metadata.Handler.GetType().Name}");
// Export with handler preserved
var data = sav.Write(); // Automatically includes handler footer
File.WriteAllBytes("save_modified.dsv", data.ToArray());
}
Error Handling During Export
public bool SafeExport(SaveFile sav, string outputPath)
{
try
{
// Validate before export
if (!sav.ChecksumsValid)
{
Console.WriteLine("Fixing checksums...");
sav.SetChecksums();
}
if (!sav.IsVersionValid())
{
Console.WriteLine("Warning: Save version may be invalid");
}
// Ensure directory exists
string directory = Path.GetDirectoryName(outputPath);
if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}
// Export
var data = sav.Write();
// Verify data size
if (data.Length == 0)
{
Console.WriteLine("Error: Export produced empty file");
return false;
}
// Write to disk
File.WriteAllBytes(outputPath, data.ToArray());
// Verify write
if (!File.Exists(outputPath))
{
Console.WriteLine("Error: File was not written");
return false;
}
var fileInfo = new FileInfo(outputPath);
Console.WriteLine($"✓ Exported: {outputPath} ({fileInfo.Length} bytes)");
return true;
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine($"Access denied: {ex.Message}");
return false;
}
catch (IOException ex)
{
Console.WriteLine($"I/O error: {ex.Message}");
return false;
}
catch (Exception ex)
{
Console.WriteLine($"Unexpected error: {ex.Message}");
return false;
}
}
Complete Export Workflow
public class SaveFileExporter
{
public bool ExportSave(string inputPath, string outputPath,
bool createBackup = true)
{
try
{
// 1. Validate input
if (!File.Exists(inputPath))
{
Console.WriteLine("Input file not found");
return false;
}
// 2. Load save
Console.WriteLine("Loading save file...");
var sav = SaveUtil.GetSaveFile(inputPath);
if (sav == null)
{
Console.WriteLine("Failed to load save file");
return false;
}
Console.WriteLine($"Loaded: {sav.Version} - {sav.OT}");
// 3. Create backup if requested
if (createBackup)
{
string backupPath = CreateVersionedBackup(inputPath);
Console.WriteLine($"Backup created: {backupPath}");
}
// 4. Validate save state
Console.WriteLine("Validating save...");
if (!ValidateBeforeExport(sav))
{
Console.WriteLine("Validation failed");
return false;
}
// 5. Update checksums
Console.WriteLine("Updating checksums...");
sav.SetChecksums();
// 6. Export
Console.WriteLine("Exporting...");
var data = sav.Write();
// 7. Write to disk
File.WriteAllBytes(outputPath, data.ToArray());
// 8. Verify output
var outputInfo = new FileInfo(outputPath);
Console.WriteLine($"✓ Export complete: {outputPath}");
Console.WriteLine($" Size: {outputInfo.Length} bytes");
Console.WriteLine($" Game: {sav.Version}");
Console.WriteLine($" Generation: {sav.Generation}");
return true;
}
catch (Exception ex)
{
Console.WriteLine($"Export failed: {ex.Message}");
return false;
}
}
private string CreateVersionedBackup(string path)
{
string timestamp = DateTime.Now.ToString("yyyyMMdd_HHmmss");
string backupPath = $"{path}.{timestamp}.bak";
File.Copy(path, backupPath);
return backupPath;
}
private bool ValidateBeforeExport(SaveFile sav)
{
return sav.IsVersionValid() && sav.State.Exportable;
}
}
Usage Example
// Simple export
var sav = SaveUtil.GetSaveFile("original.sav");
sav.Money = 999999;
File.WriteAllBytes("modified.sav", sav.Write().ToArray());
// Safe export with validation
var exporter = new SaveFileExporter();
exporter.ExportSave("original.sav", "modified.sav", createBackup: true);
Always test exported save files in-game before replacing your primary save. Use backups!
Next Steps
Loading Saves
Learn how to load save files
Modifying Data
Modify Pokémon and trainer data