Screenshot
What This Example Shows
How to save and load game data using the SNES cartridge SRAM (Static RAM). The SRAM is battery-backed – data persists even when the console is powered off. This is how classic SNES games like Zelda and Final Fantasy save progress.
Prerequisites
Read text/print_string first (console setup, text display).
Controls
| Button | Action |
| A | Write test data to Slot 1 |
| B | Read and display Slot 1 |
| X | Write test data to Slot 2 |
| Y | Read and display Slot 2 |
Build & Run
cd $OPENSNES_HOME
make -C examples/memory/save_game
Then open save_game.sfc in your emulator (Mesen2 recommended).
How It Works
1. Define a save structure
typedef struct {
signed short s16
16-bit signed integer (-32768 to 32767)
Definition types.h:50
unsigned short u16
16-bit unsigned integer (0 to 65535)
Definition types.h:53
Data structure representing one save slot's persistent state.
Definition main.c:49
The structure is 8 bytes. In a real game, this would hold player position, inventory, level progress, etc.
2. Save to SRAM
unsigned char u8
8-bit unsigned integer (0 to 255)
Definition types.h:47
#define SAVE_SIZE
Size in bytes of one save slot (must equal sizeof(SaveState)).
Definition main.c:68
#define SLOT1
SRAM byte offset for save slot 1 (starts at byte 0).
Definition main.c:64
SaveState vts
Write buffer: filled with test data before saving to SRAM.
Definition main.c:57
void sramSaveOffset(const u8 *data, u16 size, u16 offset)
Save data to SRAM at offset.
sramSaveOffset() copies SAVE_SIZE bytes from RAM to SRAM at the given offset. Multiple save slots use different offsets (Slot 0 at offset 0, Slot 1 at offset 8, etc.).
3. Load from SRAM
SaveState vtl
Read buffer: populated by sramLoadOffset() when loading from SRAM.
Definition main.c:59
void sramLoadOffset(u8 *data, u16 size, u16 offset)
Load data from SRAM at offset.
sramLoadOffset() copies bytes back from SRAM into a RAM structure. The loaded values are then displayed as hex on screen using textPrintHex().
4. Enable SRAM in the Makefile
USE_SRAM = 1
SRAM_SIZE = 3
USE_SRAM sets the ROM header flag telling the emulator (or real hardware) that the cartridge has battery-backed SRAM. SRAM_SIZE = 3 means 8 KB (2^3 = 8 KB).
5. Text display
The example uses the text system on BG1 in Mode 0 (2bpp):
void bgSetMapPtr(u8 bg, u16 vramAddr, u8 mapSize)
Set background tilemap address and size.
void bgSetGfxPtr(u8 bg, u16 vramAddr)
Set background tile graphics address.
#define BG_MAP_32x32
Definition background.h:30
void textLoadFont(u16 vram_addr)
Load font tiles to VRAM.
#define TEXT_DEFAULT_FONT_TILE
Default first font tile (zero — font occupies tiles 0-95).
Definition text.h:44
#define TEXT_DEFAULT_PALETTE
Default palette slot (palette 0).
Definition text.h:46
void textInit(u16 tilemap_addr, u16 font_tile, u8 palette)
Initialize the text rendering system.
#define TEXT_DEFAULT_TILEMAP_ADDR
Default tilemap WORD address — 32×32 tilemap at VRAM word $3800.
Definition text.h:42
After each save/load operation, textPrintAt() updates the on-screen feedback and textFlush() sends the updated text buffer to VRAM.
SNES Concepts
SRAM Address Space
In LoROM mode, SRAM is mapped at $70:0000-$71:FFFF (up to 64 KB, though most cartridges have 8 KB). In HiROM mode, it appears at $20:6000-$3F:7FFF. The sramSaveOffset() and sramLoadOffset() functions handle the addressing – you just provide a byte offset.
Save Slots
Save slots are simply offsets into SRAM. With 8 KB of SRAM and 8-byte saves, you could theoretically have 1024 slots. Real games use 3-4 slots with larger structures (100-500 bytes for RPG save states).
Checksum Validation
This example does not validate saves. A real game should store a checksum alongside the save data to detect corrupted SRAM (weak battery, first boot with uninitialized RAM, etc.). A simple XOR or CRC-8 of the save bytes suffices.
Battery Persistence
On real SNES hardware, a CR2032 lithium battery in the cartridge powers the SRAM when the console is off. Batteries last 15-25 years. In emulators, the SRAM is saved to a .srm file on disk.
Project Structure
| File | Purpose |
| main.c | Save/load logic, text display, input handling |
| Makefile | USE_SRAM := 1, LIB_MODULES := console dma text background sprite input |
Going Further
- Add a checksum: Before saving, compute a checksum of the data bytes and store it alongside the save. On load, verify the checksum and display "CORRUPT" if it does not match.
- Larger save structure: Add fields for health, inventory, level number, and play time. This is how real RPG save systems work.
- Explore related examples:
- memory/hirom_demo – Understand LoROM vs HiROM memory mapping
- games/breakout – See game state management in a complete game