Gen 3 Save File Format Explained

Last updated: November 2026 · Applies to Pokémon Ruby, Sapphire, Emerald, FireRed and LeafGreen (Game Boy Advance)

Every Pokémon Game Boy Advance game keeps its save in a small flash chip on the cartridge. Dumped by an emulator or a backup tool, that chip becomes a single 128 KB file, usually named <game>.sav. It looks like 131,072 random bytes, but the layout is rigid and — unlike later generations — completely unencrypted except for one small, well-understood region. This guide walks through the whole file the way a parser sees it, and explains every step our online editor performs.

The big picture: two saves in one file

The 128 KB file is divided into five regions:

OffsetSizeContents
0x0000057,344 BGame save A
0x0E00057,344 BGame save B
0x1C0008,192 BHall of Fame
0x1E0004,096 BMystery Gift / e-Reader data
0x1F0004,096 BRecorded battle

The interesting part is the pair of game saves. The game alternates between them: each time you save, it writes the other slot, so one slot always holds a working backup. If the newest slot fails its checksum checks when the game loads, the game silently falls back to the older one. A save editor has to respect this dance — which is why our editor reports both slots and writes your edits to both of them.

Sections: fourteen blocks per save slot

Each 57,344-byte game save is a run of 14 sections of 4,096 bytes. Every section carries the same skeleton: up to 3,968 bytes of data followed by a fixed footer at offset 0xFF4 inside the section:

Section offsetSizeContents
0x0003,968 BData
0xFF42 BSection ID (u16)
0xFF62 BChecksum (u16)
0xFF84 BSignature — always 0x08012025 (little-endian bytes 25 20 01 08)
0xFFC4 BSave counter (u32)

Two details matter for parsing. First, the sections are rotated: the game shifts the section order by one on every save, so section ID 0 (trainer info) is rarely the first physical section. A parser must walk all 14 slots, read the footer of each, and index sections by their ID instead of by position. Second, the footer's save counter is the same in all 14 sections of a slot, and the last physical section's counter decides which slot is newer: if slot A's counter is greater, A is the current save; otherwise (including ties) B wins.

Section IDs

IDChecksum coversContents
03,884 BTrainer info
13,968 BTeam and items
23,968 BGame state (flags, variables)
33,968 BMisc data
43,848 BRival info / game-specific data
5–123,968 B eachPC buffer parts A–H
132,000 BPC buffer part I

The section checksum, step by step

Every section protects itself with a 16-bit additive checksum. The documented algorithm is refreshingly simple:

  1. Start a 32-bit accumulator at zero.
  2. Add the section's data to it, four bytes at a time, interpreting each group as one little-endian 32-bit word. How many bytes are covered depends on the section ID (see table above).
  3. Fold the result into 16 bits: add the upper 16 bits of the accumulator to the lower 16 bits. That value is the checksum.

In JavaScript, a section checksum looks like this:

// data: Uint8Array of the section, size: bytes covered for this section ID
const view = new DataView(data.buffer, data.byteOffset, size);
let sum = 0;
for (let i = 0; i < size; i += 4) {
  sum = (sum + view.getUint32(i, true)) >>> 0;
}
const checksum = ((sum & 0xffff) + (sum >>> 16)) & 0xffff;

This is why Gen 3 saves are so editor-friendly: the checksum is a plain addition, no lookup tables, no cryptography. After any edit the tool recomputes the affected section's checksum and writes it back to 0xFF6; a mismatch would make the game discard that save slot.

Trainer info (section 0)

Section 0 holds the player profile. The fields relevant to editing sit at fixed offsets, shared by all five games:

OffsetSizeContents
0x007 BPlayer name (game charset, 0xFF-terminated)
0x081 BGender (0 = boy, 1 = girl)
0x0A4 BTrainer ID — low 16 bits = visible ID, high 16 bits = Secret ID
0x0E5 BTime played: u16 hours, then u8 minutes, seconds, frames
0x133 BOptions (text speed, sound, battle style…)

FireRed and LeafGreen can be told apart from Ruby/Sapphire/Emerald by a game code field: FRLG stores the value 1 at section offset 0x0AF8. The game family matters because the team section uses different offsets in the two groups.

Team and items (section 1)

The party is up to six Pokémon records of 100 bytes each. Ruby/Sapphire/Emerald store a 4-byte party counter at 0x234 and the records at 0x238; FireRed/LeafGreen use a 1-byte counter at 0x034 with records at 0x038. Money and coins also live in this section — in RSE they are stored plainly, while FRLG masks them with a security key. Our editor currently covers the four most-demanded fields (nickname, OT name, IDs, experience); item editing is a natural follow-up.

The PC buffer (sections 5–13)

Nine of the fourteen sections form one logical region, the PC buffer: 8 × 3,968 bytes plus 2,000 bytes, 33,744 bytes in total. To read it, a parser concatenates the data areas of sections 5 through 13 in section-ID order (not physical order). The assembled buffer then lays out as:

Buffer offsetSizeContents
0x0004 BCurrently viewed box minus 1 (u32)
0x00433,600 B420 Pokémon records of 80 bytes (14 boxes × 30 slots)
0x8344126 B14 box names, 9 bytes each
0x83C214 BOne wallpaper byte per box

Box records are only 80 bytes long — the trailing stats of the 100-byte party structure are not stored, because the game recomputes them from the data block when a Pokémon is withdrawn (this is the famous "Box trick": stats, including level from experience, are recalculated on withdrawal).

The Pokémon record, byte by byte

Both party (100 B) and box (80 B) records share the same head:

OffsetSizeContents
0x004 BPersonality value (PID) — controls nature, gender, shininess and more
0x044 BOT ID — low 16 bits visible Trainer ID, high 16 bits Secret ID
0x0810 BNickname (game charset, 0xFF-terminated, 0x00-padded)
0x121 BLanguage (1 = Japanese, 2 = English, 3 = French, 4 = Italian, 5 = German, 7 = Spanish)
0x131 BFlags — bit 0 Bad Egg, bit 1 has species, bit 2 use egg name, bit 3 block Box R/S
0x147 BOT name
0x1B1 BMarkings
0x1C2 BChecksum over the 48-byte data block
0x2048 BFour encrypted substructures (12 bytes each)
0x5020 BStatus, level, mail, current HP and stats — party records only

Substructures and their rotation

The 48-byte data block is four 12-byte substructures: Growth (species, held item, experience, friendship), Attacks (four moves and their PP), EVs & condition, and Misc (Pokérus, origin ball and game, IVs, egg and ability flags, ribbons). Their storage order is dictated by the PID modulo 24 — the same personality value that fixes a Pokémon's nature also shuffles its own data layout. PID 4, for example, gives order GMAE, so the growth block starts at byte 12 of the data area, not byte 0. A parser that assumes a fixed order corrupts the very fields it tries to edit.

Decryption

The whole 48-byte block is XOR-encrypted with a single 32-bit key:

key = OT ID (u32) XOR personality value (u32)
for each 4-byte word of the data block: word ^= key

Decrypt, patch whatever you need (say, the u32 experience at growth offset +4), sum the 48 bytes as u32 words into a u16, store that as the record checksum, then re-encrypt with the same key. Editing the Trainer ID is even simpler — it lives outside the encrypted block — but note that the OT ID participates in the XOR key, so any tool that changes it must also re-encrypt every Pokémon owned by that trainer. Our editor recomputes keys and checksums automatically for every edit it applies.

The Gen III character set

Nicknames and trainer names are not stored as ASCII or UTF-8. The GBA games use a proprietary single-byte encoding: capital letters start at 0xBB, digits at 0xA1, the male/female symbols sit at 0xB5/0xB6, and the PK/MN ligatures at 0x53/0x54. Fixed-length fields end with a 0xFF terminator and are padded with 0x00 bytes. This is why a name exported to a text file can contain odd accented letters, and why an editor must map characters explicitly — writing raw ASCII into a nickname field produces garbage in-game. Our editor only accepts characters the Western versions can display and substitutes "?" for anything else, so the game never meets an invalid byte.

Why all this makes Gen 3 saves a great editor target

  • No global encryption — later generations encrypt or hash large parts of the save; Gen 3 protects each section with one additive checksum and each Pokémon block with one XOR key.
  • Deterministic layout — every offset above is fixed per game family; only the section rotation varies, and the rotation is self-describing via the section IDs.
  • Self-healing structure — the dual-slot design gives every save a built-in backup, and a careful editor keeps both slots consistent.

If you want to poke at a save yourself, open the editor with your .sav file — everything runs locally — or read the format documentation this article is based on:

References