Skip to main content

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 with BinaryExportSetting:
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

Build docs developers (and LLMs) love