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:
- Write 1 to
SramLock
($A130F1
) - Write your data into SRAM
- 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:
- Write 1 to
SramLock
($A130F1
) - Read whatever you need from SRAM
- 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:
- The first time the game runs SRAM will have garbage. Always check if you need to reset it (writing some value you can always look for is a good way to tell when it's needed — if it's not there, then reset it).
- Things can go wrong and the data not get written properly, e.g. a power outage happening right at the worst moment. Make sure to include some checksum to know if the data is valid and ideally a way to restore an older copy when it's not.
- SRAM can fail (e.g. running out of battery). Account for this as well (show a warning? and many games will let you play without saving)
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?