ROM header: Difference between revisions

From SNESdev Wiki
Jump to navigationJump to search
m (→‎$FFD6: suggest putting all DSP family at DSP-1)
(→‎Computing the Checksum: complement is at the lower address)
 
(18 intermediate revisions by 3 users not shown)
Line 1: Line 1:
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. [https://github.com/pinobatch/lorom-template/blob/master/src/snesheader.s lorom-template] contains an example of how to set one up.
Nintendo required SNES developers to include a header in the game's data that describes what hardware the game cartridge contains. This is not needed by the SNES hardware, though the game software can access it as ROM data.


The header is located at the <em>CPU</em> address range $00FFC0-$00FFDF, right before the [[CPU vectors|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.
However, emulators and flashcarts rely on this header to know how to emulate the game cartridge. Homebrew games should also provide a valid header. Example code: [https://github.com/pinobatch/lorom-template/blob/master/src/snesheader.s lorom-template]


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 <code>SEI</code> 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.
The header is located at the <em>CPU</em> address range $00FFC0-$00FFDF, right before the [[CPU vectors|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. See: [[#Header Verification|Header Verification]] below.
 
This internal ROM header is to be confused with the additional 512-byte headers used by copier devices. See: [[ROM file formats]]


See also: [[Memory map]]
See also: [[Memory map]]
Line 30: Line 32:
| $FFDB || 1 || ROM version (0 = first)
| $FFDB || 1 || ROM version (0 = first)
|-
|-
| $FFDC || 2 || Checksum
| $FFDC || 2 || Checksum complement (Checksum ^ $FFFF)
|-
|-
| $FFDE || 2 || Checksum compliment (Checksum ^ $FFFF)
| $FFDE || 2 || Checksum
|-
|-
| $FFE0 || 32 || [[CPU vectors|Interrupt vectors]]
| $FFE0 || 32 || [[CPU vectors|Interrupt vectors]]
Line 68: Line 70:
* $4x - Coprocessor is [[S-DD1]]
* $4x - Coprocessor is [[S-DD1]]
* $5x - Coprocessor is [[S-RTC]]
* $5x - Coprocessor is [[S-RTC]]
* $Ex - Coprocessor is Other (Super Game Boy/Satellaview)
* $Ex - Coprocessor is Other ([[Super Game Boy]]/[[Satellaview]])
* $Fx - Coprocessor is Custom (specified with $FFBF)
* $Fx - Coprocessor is Custom (specified with $FFBF)


Line 103: Line 105:
== Checksum ==
== 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.)
The checksum is a 16-bit sum of all of the bytes in the ROM, potentially with some portions repeated. It is always computed as if the ROM is a power of 2 in size, as given by the ROM header.
 
However, some SNES games have a ROM data size that is not a power of 2, e.g. a 3MB game might use a 2MB ROM and a 1MB ROM together. These will use [[mirroring]] to fill remaining space to reach the next largest power of 2.
 
=== Non Power-of-2 ROM Size ===
 
A physical cartridge will mirror its ROM chips to fill the [[memory map]], but a [[ROM file formats|ROM file]] dump will usually try to omit duplication. To accommodate this, when the file's data doesn't already add to a power of 2, it will be treated as a combination of two regions, each a power of 2 in size. The larger of the two always comes first in the file, and the smaller one will be duplicated until the combined size reaches the next power of 2.<ref>[[SNES Development Manual]] Book 1, page 1-2-21: Check Sum - describes non-power-of-2 ROM organization.</ref> The total size will match what is specified in the ROM header, and when preparing a dump we must ensure the result of this process matches the physical cart's mirrored memory map.


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]].
If a ROM file's data size is neither a power of 2 nor the sum of two powers of 2, an emulator will have to first pad the smaller portion to reach the next power of 2. Emulators are inconsistent about what to use for padding (some may fill with 0s), so it is recommended to ensure your ROM file's remainder reaches a power of 2 boundary to avoid this ambiguity.  


The general process for preparing the combined ROM data:
# Find the largest power of 2 less than or equal to the data size.
# Find the largest power of 2 less than or equal to the data size.
# If data remains past this point:
# If data remains past this point:
## Find the smallest power of 2 greater than or equal to the remainder.
<ol type="A">
## Pad with 0s to meet this power of 2.<tt>*</tt>
:<li>Find the smallest power of 2 greater than or equal to this remainder.</li>
## 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.
:<li>Pad the remainder with 0s to meet this power of 2.</li>
:<li>Now that the remainder is a power of 2, repeat this data until the remainder matches the size of the first part of the ROM (the power of 2 in step 1).</li>
</ol>


<tt>*</tt> 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.
When a ROM file's data size is not already a power of 2, most frequently it will be two ROMs in a 2:1 size ratio, which will result in doubling the last third of the data. Cases that need padding are usually homebrew ROMs looking to conserve space by omitting unused memory regions.


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 <tt>$0000</tt> and <tt>$FFFF</tt>. These two values will cancel each other out in the checksum process (as would any valid checksum+complement pair).
=== Computing the Checksum ===
 
Once we have a ROM prepared with a power of 2 size equal to what the ROM header specified, we may compute its checksum.
 
Because the ROM header will be part of the computed checksum, before computing the checksum we should first fill the header's checksum and complement values with <tt>$0000</tt> and <tt>$FFFF</tt>. Any value plus its complement will produce the same result, so this ensures the resulting checksum matches the ROM even after the computed checksum is replaced in the header.


Once ready:
Once ready:


# Start with a 16-bit <tt>checksum = 0</tt>.
# Start with a 16-bit <tt>checksum = 0</tt>.
# Add every pair of bytes from the prepared data to the checksum, as little-endian 16-bit. (Overflow is discarded.)
# Add every byte from the prepared data to the checksum. (Overflow is discarded.)
# Store the checksum in the ROM header ($FFDC or equivalent).
# Store the checksum in the ROM header ($FFDE or equivalent).
# Store <tt>checksum ^ $FFFF</tt> in the ROM header ($FFDE).
# Store <tt>checksum ^ $FFFF</tt> in the ROM header ($FFDC).
 
== Header Verification ==
 
The primary way to verify a candidate header is to evaluate the [[#Checksum|checksum]] it contains. Some flash-carts appear to use only the checksum to distinguish LoROM from HiROM.
 
If no valid checksum can be found (e.g. ROM-hacks or homebrews often omit it), additional heuristics may be used to estimate validity:<ref>[https://github.com/bsnes-emu/bsnes/blob/f57657f27ddec337b1960c7ddaa1b23894bc00c3/bsnes/heuristics/super-famicom.cpp#L515 bsnes SuperFamicom::scoreHeader] - source code for estimating header likelihood</ref><ref>[https://github.com/snes9xgit/snes9x/blob/a2e0580992873ec3913fd1ef09f22f368fe44b3b/memmap.cpp#L1421 snes9x CMemory::LoadRomInt] - source code for estimating header likelihood</ref>
* ROM checksum matches.
* Checksum and complement sum to $FFFF.
* Map mode matches header location.
* Specified ROM size is not smaller than file size.
* A reset vector < $8000 is invalid because it points outside of ROM.
* The first instruction at a valid reset vector is likely to be: <tt>sei, clc, sec, stz, jmp, jml</tt>
* The first instruction at a valid reset vector is unlikely to be: <tt>brk, cop, stp, wdm, $FF (sbc long)</tt>
* ROM and RAM sizes are reasonable.
* Game name field is ASCII characters only.
 
== Links ==
* [https://github.com/bbbradsmith/SNES_stuff/blob/main/smalltext/checksum.py checksum.py] - Python code to compute and apply the checksum for a ROM file.
 
== References ==
* [[SNES Development Manual]] Book 1, page 1-2-10: ROM Registration Data - describes the ROM header and checksum process.
<References/>

Latest revision as of 07:45, 28 June 2024

Nintendo required SNES developers to include a header in the game's data that describes what hardware the game cartridge contains. This is not needed by the SNES hardware, though the game software can access it as ROM data.

However, emulators and flashcarts rely on this header to know how to emulate the game cartridge. Homebrew games should also provide a valid header. Example code: lorom-template

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. See: Header Verification below.

This internal ROM header is to be confused with the additional 512-byte headers used by copier devices. See: ROM file formats

See also: Memory map

Cartridge header

Header contents
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 complement (Checksum ^ $FFFF)
$FFDE 2 Checksum
$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.

Expanded header contents
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 a 16-bit sum of all of the bytes in the ROM, potentially with some portions repeated. It is always computed as if the ROM is a power of 2 in size, as given by the ROM header.

However, some SNES games have a ROM data size that is not a power of 2, e.g. a 3MB game might use a 2MB ROM and a 1MB ROM together. These will use mirroring to fill remaining space to reach the next largest power of 2.

Non Power-of-2 ROM Size

A physical cartridge will mirror its ROM chips to fill the memory map, but a ROM file dump will usually try to omit duplication. To accommodate this, when the file's data doesn't already add to a power of 2, it will be treated as a combination of two regions, each a power of 2 in size. The larger of the two always comes first in the file, and the smaller one will be duplicated until the combined size reaches the next power of 2.[1] The total size will match what is specified in the ROM header, and when preparing a dump we must ensure the result of this process matches the physical cart's mirrored memory map.

If a ROM file's data size is neither a power of 2 nor the sum of two powers of 2, an emulator will have to first pad the smaller portion to reach the next power of 2. Emulators are inconsistent about what to use for padding (some may fill with 0s), so it is recommended to ensure your ROM file's remainder reaches a power of 2 boundary to avoid this ambiguity.

The general process for preparing the combined ROM data:

  1. Find the largest power of 2 less than or equal to the data size.
  2. If data remains past this point:
  1. Find the smallest power of 2 greater than or equal to this remainder.
  2. Pad the remainder with 0s to meet this power of 2.
  3. Now that the remainder is a power of 2, repeat this data until the remainder matches the size of the first part of the ROM (the power of 2 in step 1).

When a ROM file's data size is not already a power of 2, most frequently it will be two ROMs in a 2:1 size ratio, which will result in doubling the last third of the data. Cases that need padding are usually homebrew ROMs looking to conserve space by omitting unused memory regions.

Computing the Checksum

Once we have a ROM prepared with a power of 2 size equal to what the ROM header specified, we may compute its checksum.

Because the ROM header will be part of the computed checksum, before computing the checksum we should first fill the header's checksum and complement values with $0000 and $FFFF. Any value plus its complement will produce the same result, so this ensures the resulting checksum matches the ROM even after the computed checksum is replaced in the header.

Once ready:

  1. Start with a 16-bit checksum = 0.
  2. Add every byte from the prepared data to the checksum. (Overflow is discarded.)
  3. Store the checksum in the ROM header ($FFDE or equivalent).
  4. Store checksum ^ $FFFF in the ROM header ($FFDC).

Header Verification

The primary way to verify a candidate header is to evaluate the checksum it contains. Some flash-carts appear to use only the checksum to distinguish LoROM from HiROM.

If no valid checksum can be found (e.g. ROM-hacks or homebrews often omit it), additional heuristics may be used to estimate validity:[2][3]

  • ROM checksum matches.
  • Checksum and complement sum to $FFFF.
  • Map mode matches header location.
  • Specified ROM size is not smaller than file size.
  • A reset vector < $8000 is invalid because it points outside of ROM.
  • The first instruction at a valid reset vector is likely to be: sei, clc, sec, stz, jmp, jml
  • The first instruction at a valid reset vector is unlikely to be: brk, cop, stp, wdm, $FF (sbc long)
  • ROM and RAM sizes are reasonable.
  • Game name field is ASCII characters only.

Links

  • checksum.py - Python code to compute and apply the checksum for a ROM file.

References

  • SNES Development Manual Book 1, page 1-2-10: ROM Registration Data - describes the ROM header and checksum process.
  1. SNES Development Manual Book 1, page 1-2-21: Check Sum - describes non-power-of-2 ROM organization.
  2. bsnes SuperFamicom::scoreHeader - source code for estimating header likelihood
  3. snes9x CMemory::LoadRomInt - source code for estimating header likelihood