ROM header
Nintendo required SNES developers to include a header in the game's data that describes what hardware the game cartridge contains. Emulators and flashcarts rely on this header to know how to emulate the game. Therefore, homebrew games also need to provide a header. lorom-template contains an example of how to set one up.
The header is located at the CPU address range $00FFC0-$00FFDF, right before the interrupt vectors, with an optional second header at $00FFB0-$00FFBF. This means that the location of the header within the actual ROM file will change based on the cartridge's memory map mode - with LoROM games placing it at $007Fxx, HiROM games placing it at $00FFxx, and ExHiROM games placing it at $40FFxx. Therefore, if it's correctly filled out, an emulator will have a higher chance of being able to figure out where the header is.
Things that increase an emulator's confidence include the checksum and checksum compliment adding up to $FFFF, the memory map value being correct for the header location, and the ROM and RAM size being reasonable values. If the first instruction in the reset routine is SEI
then that increases confidence even further. At least one flash cart actually checks to see if the checksum is correct, so it's recommended to set a correct checksum.
See also: Memory map
Cartridge header
First address | Length | Contents |
---|---|---|
$FFC0 | 21 | Cartridge title (21 bytes uppercase ASCII. Unused bytes should be spaces.) |
$FFD5 | 1 | ROM speed and memory map mode (LoROM/HiROM/ExHiROM) |
$FFD6 | 1 | Chipset (Indicates if a cartridge contains extra RAM, a battery, and/or a coprocessor) |
$FFD7 | 1 | ROM size: 1<<N kilobytes, rounded up (so 8=256KB, 12=4096KB and so on) |
$FFD8 | 1 | RAM size: 1<<N kilobytes (so 1=2KB, 5=32KB, and so on) |
$FFD9 | 1 | Country (Implies NTSC/PAL) |
$FFDA | 1 | Developer ID |
$FFDB | 1 | ROM version (0 = first) |
$FFDC | 2 | Checksum |
$FFDE | 2 | Checksum compliment (Checksum ^ $FFFF) |
$FFE0 | 32 | Interrupt vectors |
$FFD5
Address $00FFD5 indicates the ROM speed and map mode.
001smmmm |++++- Map mode +----- Speed: 0=Slow, 1=Fast
Available modes include:
$FFD6
Address $00FFD6 indicates what extra hardware is in the cartridge, if any.
Possible values include:
- $00 - ROM only
- $01 - ROM + RAM
- $02 - ROM + RAM + battery
- $x3 - ROM + coprocessor
- $x4 - ROM + coprocessor + RAM
- $x5 - ROM + coprocessor + RAM + battery
- $x6 - ROM + coprocessor + battery
- $0x - Coprocessor is DSP (DSP-1, 2, 3 or 4)
- $1x - Coprocessor is GSU (SuperFX)
- $2x - Coprocessor is OBC1
- $3x - Coprocessor is SA-1
- $4x - Coprocessor is S-DD1
- $5x - Coprocessor is S-RTC
- $Ex - Coprocessor is Other (Super Game Boy/Satellaview)
- $Fx - Coprocessor is Custom (specified with $FFBF)
When coprocessor is Custom, $FFBF selects from:
Expanded cartridge header
The expanded header's presence is indicate by putting $33 in $00FFDA, which is the developer ID. Some early games may indicate just $00FFBF by setting $00FFD4 to zero.
First address | Length | Contents |
---|---|---|
FFB0 | 2 | ASCII maker code |
FFB2 | 4 | ASCII game code |
FFB6 | 6 | Reserved, should be zero |
FFBC | 1 | Expansion flash size: 1 << N kilobytes |
FFBD | 1 | Expansion RAM size: 1 << N kilobytes - for GSU? |
FFBE | 1 | Special version (usually zero) |
FFBF | 1 | Chipset subtype, used if chipset is $F0-$FF |
Checksum
The checksum is computed as if the ROM is a power of two in size, but some SNES games use multiple ROM chips together. Because of this, the data size is often the sum of 2 powers of two. (E.g. a 3MB game might use a 2MB ROM and a 1MB ROM together.)
Before calculating the checksum, if the data is not already a power of 2 in size we must duplicate some of the data to fill up to a power of 2 in a way that matches how it will be mirrored in the memory map.
- Find the largest power of 2 less than or equal to the data size.
- If data remains past this point:
- Find the smallest power of 2 greater than or equal to the remainder.
- Pad with 0s to meet this power of 2.*
- Now that the remainder is a power of 2, duplicate it to fill 2x the larger power of 2 from the first part of the ROM.
* This is outside the specification, and emulators are inconsistent about how to pad. It is recommended to ensure your ROM file's remainder reaches a power of 2 boundary.
Because the ROM header will be part of the computed checksum, before computing the checksum we must normally fill the header's checksum and complement values with $0000 and $FFFF. These two values will cancel each other out in the checksum process (as would any valid checksum+complement pair).
Once ready:
- Start with a 16-bit checksum = 0.
- Add every pair of bytes from the prepared data to the checksum, as little-endian 16-bit. (Overflow is discarded.)
- Store the checksum in the ROM header ($FFDC or equivalent).
- Store checksum ^ $FFFF in the ROM header ($FFDE).