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.
Generation 9 Save Files
Save file implementation for Pokemon Scarlet and Violet on Nintendo Switch.
SAV9SV
Save file for Pokemon Scarlet and Violet.
Class Definition
public sealed class SAV9SV : SaveFile, ISaveBlock9Main, ISCBlockArray,
ISaveFileRevision, IBoxDetailName, IBoxDetailWallpaper
Source: PKHeX.Core/Saves/SAV9SV.cs
Save Revisions
Scarlet and Violet has 3 save revisions based on DLC:
public int SaveRevision { get; } // 0, 1, or 2
public string SaveRevisionString { get; }
// Revision 0: "-Base" - Base game (Vanilla)
// Revision 1: "-TM" - Teal Mask (Kitakami)
// Revision 2: "-ID" - Indigo Disk (Blueberry Academy)
Storage Specifications
| Property | Base | Teal Mask | Indigo Disk |
|---|
| Box Count | 32 | 32 | 32 |
| Max Species | 512 | 565 | 590 |
| Max Move | 919 | 920 | 921 |
| Max Item | 2268 | 2400 | 2557 |
| Max Ability | 307 | 308 | 309 |
SCBlock System
Gen 9 uses the SCBlock architecture introduced in Gen 8:
public IReadOnlyList<SCBlock> AllBlocks { get; }
public SCBlockAccessor Accessor { get; }
public T GetValue<T>(uint key) where T : struct
public void SetValue<T>(uint key, T value) where T : struct
Block Accessors
public SaveBlockAccessor9SV Blocks { get; }
public Box9 BoxInfo { get; }
public Party9 PartyInfo { get; }
public MyItem9 Items { get; }
public MyStatus9 MyStatus { get; }
public Zukan9 Zukan { get; }
public BoxLayout9 BoxLayout { get; }
public PlayTime9 Played { get; }
public ConfigSave9 Config { get; }
public TeamIndexes8 TeamIndexes { get; }
public Epoch1900DateTimeValue LastSaved { get; }
public Epoch1970Value LastDateCycle { get; }
public PlayerFashion9 PlayerFashion { get; }
public PlayerAppearance9 PlayerAppearance { get; }
public RaidSpawnList9 RaidPaldea { get; }
public RaidSpawnList9 RaidKitakami { get; }
public RaidSpawnList9 RaidBlueberry { get; }
public RaidSevenStar9 RaidSevenStar { get; }
public Epoch1900DateValue EnrollmentDate { get; } // Blueberry Academy
public BlueberryQuestRecord9 BlueberryQuestRecord { get; }
public BlueberryClubRoom9 BlueberryClubRoom { get; }
Key Properties
public override uint ID32 { get; set; }
public override ushort TID16 { get; set; }
public override ushort SID16 { get; set; }
public override GameVersion Version { get; set; }
public override byte Gender { get; set; }
public override int Language { get; set; }
public override string OT { get; set; }
Currency
public override uint Money { get; set; }
public uint LeaguePoints { get; set; }
public uint BlueberryPoints { get; set; } // Indigo Disk only
Position & Rotation
public Span<byte> Coordinates { get; }
public float X { get; set; }
public float Y { get; set; }
public float Z { get; set; }
public Span<byte> PlayerRotation { get; }
public float RX { get; set; } // Quaternion rotation
public float RY { get; set; }
public float RZ { get; set; }
public float RW { get; set; }
public void SetCoordinates(float x, float y, float z)
public void SetPlayerRotation(float rx, float ry, float rz, float rw)
Box Management
public override int BoxCount { get; } // 32
public override int CurrentBox { get; set; }
public override int BoxesUnlocked { get; set; }
public string GetBoxName(int box)
public void SetBoxName(int box, ReadOnlySpan<char> value)
public int GetBoxWallpaper(int box)
public void SetBoxWallpaper(int box, int value)
// Legend wallpaper unlock flag
public byte BoxLegendWallpaperFlag { get; set; }
Battle Teams
public TeamIndexes8 TeamIndexes { get; }
public override StorageSlotSource GetBoxSlotFlags(int index)
{
int team = TeamIndexes.TeamSlots.IndexOf(index);
if (team < 0)
return StorageSlotSource.None;
team /= 6;
var result = (StorageSlotSource)((int)StorageSlotSource.BattleTeam1 << team);
if (TeamIndexes.GetIsTeamLocked(team))
result |= StorageSlotSource.Locked;
return result;
}
Tera Raid Battles
public RaidSpawnList9 RaidPaldea { get; } // Base game raids
public RaidSpawnList9 RaidKitakami { get; } // Teal Mask raids
public RaidSpawnList9 RaidBlueberry { get; } // Indigo Disk raids
public RaidSevenStar9 RaidSevenStar { get; } // 7-star event raids
Throw Style (Indigo Disk)
public ThrowStyle9 ThrowStyle { get; set; }
public enum ThrowStyle9
{
OriginalStyle,
SmugElegant, // Baseball Club 1
TwirlingNinja, // Baseball Club 2
Champion, // Baseball Club 3
}
public void UnlockAllThrowStyles()
{
// Unlocks all throwing styles and updates support board
}
protected override void SetPKM(PKM pk, bool isParty = false)
{
PK9 pk9 = (PK9)pk;
pk9.UpdateHandler(this);
if (FormArgumentUtil.IsFormArgumentTypeDatePair(pk9.Species, pk9.Form))
{
pk9.FormArgumentElapsed = pk9.FormArgumentMaximum = 0;
pk9.FormArgumentRemain = (byte)GetFormArgument(pk9);
}
pk9.RefreshChecksum();
}
private static uint GetFormArgument(PKM pk)
{
if (pk.Form == 0) return 0;
return pk.Species switch
{
(int)Species.Furfrou => 5u,
// Hoopa no longer sets Form Argument in Gen 9
_ => 0u,
};
}
Legendary Stakes
Collect stakes to unlock the Treasures of Ruin:
public void CollectAllStakes()
{
// Sets all stake collection flags for:
// - Ting-Lu (Rock stakes)
// - Chien-Pao (Ice stakes)
// - Wo-Chien (Grass stakes)
// - Chi-Yu (Fire stakes)
// Removes 8 stakes from each shrine (32 total)
// Updates shrine seal states
}
TM Recipes
public void UnlockAllTMRecipes()
{
// Unlocks all 229 TM recipes
for (int i = 1; i <= 229; i++)
{
var flag = $"FSYS_UI_WAZA_MACHINE_RELEASE_{i:000}";
var hash = (uint)FnvHash.HashFnv1a_64(flag);
if (Accessor.TryGetBlock(hash, out var block))
block.ChangeBooleanType(SCTypeCode.Bool2);
}
}
Snacksworth Legendaries (Indigo Disk)
public void ActivateSnacksworthLegendaries()
{
// Marks legendary Pokemon as available from Snacksworth
// After completing Blueberry Quests (BBQs)
for (int i = 13; i <= 37; i++)
{
var flag = $"WEVT_S2_SUB_{i:000}_STATE";
var hash = (uint)FnvHash.HashFnv1a_64(flag);
if (Accessor.TryGetBlock(hash, out var block))
block.SetValue(1); // 1 = appeared (not captured)
}
}
Blueberry Academy (Indigo Disk)
Coaches
public void UnlockAllCoaches()
{
// Unlocks all special coaches for BB League rematches:
// - Champions (Cynthia, etc.)
// - Teachers
// - Friends & Rivals
}
Enrollment Date
public Epoch1900DateValue EnrollmentDate { get; }
// Tracks when player first enrolled at Blueberry Academy
Blueberry Quests
public BlueberryQuestRecord9 BlueberryQuestRecord { get; }
// Tracks completed Blueberry Quests (BBQs)
Club Room
public BlueberryClubRoom9 BlueberryClubRoom { get; }
// Manages club room customization and support board
Player Customization
public PlayerFashion9 PlayerFashion { get; }
// Unlocked clothing and accessories
public PlayerAppearance9 PlayerAppearance { get; }
// Current player appearance settings
No Traditional Checksums
public override bool ChecksumsValid => true;
public override string ChecksumInfo => string.Empty;
protected override void SetChecksums() { }
Technical Notes
SwishCrypto
Gen 9 uses SwishCrypto encryption like Gen 8 Switch games:
public SAV9SV(Memory<byte> data) : this(SwishCrypto.Decrypt(data.Span)) { }
protected override Memory<byte> GetFinalData() => SwishCrypto.Encrypt(AllBlocks);
SCBlock Keys
Blocks are accessed using FNV-1a hashed string keys:
// Example: Money block
public const uint KMoney = 0x19E41E69; // FnvHash.HashFnv1a_64("MONEY")
public override uint Money
{
get => (uint)Blocks.GetBlockValue(SaveBlockAccessor9SV.KMoney);
set => Blocks.SetBlockValue(SaveBlockAccessor9SV.KMoney, value);
}
Save Revision Detection
Revision is detected by checking for DLC-specific blocks:
public SAV9SV(IReadOnlyList<SCBlock> blocks) : base(Memory<byte>.Empty)
{
AllBlocks = blocks;
Blocks = new SaveBlockAccessor9SV(this);
SaveRevision = Blocks.HasBlock(SaveBlockAccessor9SV.KBlueberryPoints) ? 2
: RaidKitakami.Data.Length != 0 ? 1
: 0;
Initialize();
}
Gen 9 uses PK9 format:
- Stored Size: 0x158 bytes
- Party Size: 0x168 bytes
- Both formats stored in boxes (party format)
protected override int SIZE_STORED => PokeCrypto.SIZE_9STORED; // 0x158
protected override int SIZE_PARTY => PokeCrypto.SIZE_9PARTY; // 0x168
public override int SIZE_BOXSLOT => PokeCrypto.SIZE_9PARTY; // 0x168
String Encoding
Gen 9 uses StringConverter8 (same as Gen 8):
public override string GetString(ReadOnlySpan<byte> data)
=> StringConverter8.GetString(data);
Version Validation
public override bool IsVersionValid()
=> Version is GameVersion.SL or GameVersion.VL;
Differences from Gen 8
Multi-Region Raids
Gen 9 tracks raids across three separate regions:
- Paldea: Base game region
- Kitakami: Teal Mask DLC region
- Blueberry: Indigo Disk DLC region (underwater)
Open World Coordinates
Full 3D positioning with quaternion rotation:
// Position (Vector3)
float X, Y, Z
// Rotation (Quaternion)
float RX, RY, RZ, RW
Multiple Currencies
uint Money // Standard Poke Dollars
uint LeaguePoints // LP from raids
uint BlueberryPoints // BP from Blueberry Academy (DLC 2)
Tera Types
Pokemon can have Tera Types different from their regular types, tracked in the PKM data but influenced by save file raid encounters.
Records System
Gen 9 currently does not expose a traditional records system like Gen 6-8 (commented out in the code).