Saving progress with SRAM

If you're making a game which is long and there's a lot of data to be kept track of (e.g. in a RPG) then you want to have a way to save progress. One of the most common ways (and well supported in emultors) is to add RAM with a battery. Other kinds of non-volatile RAM (like FeRAM or MRAM) also work.

ROM header changes

Before anything, we need to change the ROM header to indicate that the game uses SRAM. The 12 bytes after the RAM address range should look like this:

    dc.b 'RA',$F8,$20    ; SRAM type
    dc.l $200001         ; SRAM start address
    dc.l $20FFFF         ; SRAM end address

If you're really curious about what it means check the ROM header reference.

Writing to SRAM

First some handy addresses:

SramStart:  equ $200001  ; First SRAM address
SramEnd:    equ $20FFFF  ; Last SRAM address
SramLock:   equ $A130F1  ; Write 1 to unlock SRAM

OK. There are three things you need to do, in order:

  1. Write 1 to SramLock ($A130F1)
  2. Write your data into SRAM
  3. Write 0 to SramLock ($A130F1)

The first and last steps are pretty much what they say, so I'll just remark that they're byte accesses and from now on focus on the middle step.

SRAM covers the $200001-$20FFFF address range, and only every other byte is used (i.e. $200001, $200003, $200005, etc.). This gives you a total of 32KB to work with. If you need to save numbers larger than fit in a byte, split it into its separate bytes.

Here's a quick example that copies a chunk of data into the beginning of SRAM (note: this code is not optimized)

    move.b  #1, (SramLock)   ; Unlock SRAM
    
    lea     (DataPtr), a0    ; Beginning of data
    lea     (SramStart), a1  ; Beginning of SRAM
    move.w  #DataSize-1, d0  ; Number of bytes
Loop:
    move.b  (a0), (a1)       ; Write byte to SRAM
    addq.l  #1, a0           ; Advance data byte
    addq.l  #2, a1           ; Advance SRAM byte
    dbf     d0, Loop         ; Keep going
    
    move.b  #0, (SramLock)   ; Lock SRAM

Reading from SRAM

Reading back the data is similar to writing so I'll just summarize it:

  1. Write 1 to SramLock ($A130F1)
  2. Read whatever you need from SRAM
  3. Write 0 to SramLock ($A130F1)

Yeah, that's all there's to it.

Precautions

Players will get really furious if they lose their progress so some warnings about common hazards are warranted:

Also Sega used to warn against saving data at $200001 and $20FFFF (i.e. the first and last SRAM addresses), as they may be more likely to lose data if something goes wrong. Skip those two bytes, I guess.

Games larger than 2MB

If you paid attention you may have noticed that SRAM is at the 2MB mark. What happens if your game is larger than that?

Well, this is why we're writing to SramLock: when we write 1 to it, SRAM shows up in the upper 2MB of the cartridge, and when we write 0 the upper part of the ROM shows up instead. This isn't needed for smaller games, but we recommend doing this anyway (you never know on what kind of cartridge it can end up).

The obvious catch is that the code that accesses SRAM must be in the lower 2MB (or run from RAM). This is rarely a problem however, since normally code is at the beginning of the ROM and it's never going to be that large… right?