Skip to content

Save Functions

Chris Feger edited this page Jan 21, 2021 · 13 revisions

Value Access

A number of functions have been provided for accessing data from the save. All the functions that begin sav_get_ are used to read values from the loaded save, while all functions that begin with sav_set_ are used to write values to the save.

All of the below functions require the following two arguments:

  • int off1: the block of data within the save being targeted (see the Save Blocks section below)
  • int off2: the offset within the block of data you want to read from or write to
// individual bits
int sav_get_bit(int off1, int off2, int bitNum);
void sav_set_bit(int bitVal, int off1, int off2, int bitNum);
  • The return value of sav_get_bit and the int bitVal argument work the same: 0 means the bit is unset and 1 means the bit is set
  • int bitNum: the zero-based index of the bit to read/write (see table below if you need help understanding bit indexes)

Byte value: 0x7B

Bit
7 6 5 4 3 2 1 0
Value 0
1 1 1 1 0 1 1
// byte (1 byte == 8 bits)
char sav_get_byte(int off1, int off2);
void sav_set_byte(char data, int off1, int off2);

// short (2 bytes)
short sav_get_short(int off1, int off2);
void sav_set_short(short data, int off1, int off2);

// int (4 bytes)
int sav_get_int(int off1, int off2);
void sav_set_int(int data, int off1, int off2);

Functions for accessing 1-byte (char), 2-byte (short), and 4-byte (int) values.

// arbitrary number of bytes
void sav_get_data(char* dataOut, unsigned int size, int off1, int off2);
void sav_set_data(char* data, unsigned int size, int off1, int off2);

These two functions are for reading and writing an arbitrary number of bytes from the save file

  • unsigned int size: the number of bytes to read/write
  • char* dataOut: a pointer to an existing char array to write data from the save into
  • char* data: a pointer to an existing char array holding data to write to the save
// strings
char* sav_get_string(int off1, int off2, unsigned int codepoints);
void sav_set_string(char* string, int off1, int off2, unsigned int codepoints);

These functions are for dealing with values in saves that are meant to be strings.

  • unsigned int codepoints: the number of characters the game allows for the string
  • char* string: a UTF-8 string in a char array that will be written to the save
  • Note: the char* returned by sav_get_string needs to be manually freed

Save Blocks

The below tables detail what values to use for off1 in the sav_get_* and sav_set_* API functions detailed above.

For any Generation not mentioned below, just use 0 for off1

Generation 3

For Generation 3 saves, the value to use for off1 is the number in the ID column of this table corresponding to the contents you want to edit.

ID Contents
0 Small Block (Trainer Info)
1 Large Block Part 1
2 Large Block Part 2
3 Large Block Part 3
4 Large Block Part 4
5 PC Block 0
6 PC Block 1
7 PC Block 2
8 PC Block 3
9 PC Block 4
A PC Block 5
B PC Block 6
C PC Block 7
D PC Block 8

You can also provide -1 as the value for off1 to make the read/write use an absolute offset

Generation 4

PKSM recognizes 2 types of save blocks for Generation 4 saves: General and Storage.

The General block is usually the one scripts will want to modify. This is where the save holds data like Trainer name, money, items, and even things like the currently active daily swarm.

sav_get_int(sav_gbo(), 0x80); // gets the money for a Platinum save

The Storage block is where the game stores all info related to the PC storage system, like stored Pokémon and box metadata (box names, wallpapers, last active box, etc.). If you're looking to edit stored Pokémon, you're better off using sav_get_pkx and sav_inject_pkx, which are detailed on another page of this wiki. For editing box metadata, you will use sav_sbo() as demonstrated below

sav_set_byte(0, sav_sbo(), 0xcf2c); // sets a Platinum save's current box to Box 1

Generation 8

For Generation 8 saves, the value to use for off1 is the value in the Value to Use column of this table corresponding to the Block you want to edit.

Block Value to Use
Box 0x0d66012c
WondercardData 0x112d5141
Party 0x2985fe5d
PokeDex 0x4716c404
ArmorDex 0x3F936BA9
CrownDex 0x3C9366F0
Items 0x1177c2c4
BoxLayout 0x19722c89
Misc 0x1b882b09
TrainerCard 0x874da6fa
PlayTime 0x8cbbfd90
Status 0xf25c070e

These are a small sample of block keys reported by PKHeX. If you wish to edit a block not listed here, just use the block key from PKHeX.

Named Values

Along with the above functions, PKSM provides another for accessing certain "named" values from the save

int sav_get_value(enum SAV_Field field, ...);

enum SAV_Field
{
    SAV_OT_NAME,
    SAV_TID,
    SAV_SID,
    SAV_GENDER,
    SAV_COUNTRY,
    SAV_SUBREGION,
    SAV_REGION,
    SAV_LANGUAGE,
    SAV_MONEY,
    SAV_BP,
    SAV_HOURS,
    SAV_MINUTES,
    SAV_SECONDS,
    SAV_ITEM
};

// example usage
char *otName = (char *)sav_get_value(SAV_OT_NAME);
int tid = sav_get_value(SAV_OT_TID);
int firstMedicine = sav_get_value(SAV_ITEM, Medicine, 0);

Most fields will not require a second argument. Notable caveats and arguments are:

  • SAV_OT_NAME: Returns a UTF-8 formatted string that must be manually freed. Cast to char* to use it properly
  • SAV_TID and SAV_SID: Both return the 5-digit (pre-Generation 7) format
  • SAV_ITEM: Requires an enum Pouch (see General Enums and Structs: Pouch) and a slot number. Returns the item ID of the specified slot

Wonder Cards

int sav_wcx_free_slot();

Gets the first empty Wonder Card slot, or, if all are filled, the maximum index.

void sav_inject_wcx(char* data, enum Generation type, int slot, int alternateFormat);

Injects a wonder card.

  • data should be a pointer to the Wonder Card data in the correct format for the generation. If the Wonder Card data passed in is not in the correct format, issues will occur.
  • slot refers to which Wonder Card slot it should be injected to, though GEN_LGPE does not store Wonder Cards, and, as such, this argument does not affect it.
  • alternateFormat is a boolean that refers to the format of the data passed in, based on the value of enum Generation type as follows:
    • GEN_THREE: function call will silently do nothing, because PKSM does not support the WC3 format
    • GEN_FOUR: if true, data is interpreted as a WC4 (meaning that the internal Pokémon data is decrypted). Otherwise, data will be interpreted as a PGT.
    • GEN_FIVE: ignored
    • GEN_SIX: if true, data is interpreted as a WC6FULL. Otherwise, data will be interpreted as a WC6
    • GEN_SEVEN: if true, data is interpreted as a WC7FULL. Otherwise, data will be interpreted as a WC7
    • GEN_LGPE: if true, data is interpreted as a WB7FULL. Otherwise, data will be interpreted as a WB7
    • GEN_EIGHT: ignored

Box Data Encryption

void sav_box_decrypt();
void sav_box_encrypt();

Since the games store Pokémon in an encrypted format, these functions let scripts decrypt that data so it can be properly read and/or edited. Any edits you aim to make should be done after calling sav_box_decrypt and before calling sav_box_encrypt.

IMPORTANT: These should always be used as a pair and always in this order. Mixing them or not using them in pairs will produce unpredictable results.

Save File

char* save_path(void);

For those scripts that might want to know where on the SD card the loaded save file is from, this function returns the file's path. If the loaded save is the active save of a 3DS title, this function returns NULL.

Note: The returned char* needs to be manually freed.