Nintendo header

From NESdev Wiki
Revision as of 12:31, 25 March 2022 by Fiskbit (talk | contribs) (Improvements, corrections (sizes), and additions (encoding 2).)
Jump to navigationJump to search

About 33% of licensed Nintendo NES games have a header present at their last bank, at addresses $FFE0-$FFF9, right before the interrupt vectors. The FamicomBox reads this header to determine the validity and title of an inserted game.

The info in them is frequently incomplete and/or inaccurate. When info on a particular filed is not present, a byte $00 or $FF is used for padding.

  • $FFE0-$FFEF : Title of the game, in the specified encoding. (Sometimes the name is abbreviated or replaced by Nintendo's code). ASCII codes 20h-5Ah allowed. Should be right-justified.
  • $FFF0-$FFF1 : PRG checksum (NROM/CNROM/UNROM: the whole PRG-ROM, GNROM: each 32 KiB bank, MMC: the last 16 KiB). Sum of all bytes in the relevant area, excluding the two checksum bytes. Big-endian.
  • $FFF2-$FFF3 : CHR checksum. 0x00 if RAM. Big-endian.
  • $FFF4 : Data sizes and type.
    • D7-D4: PRG size (0 = 64 KiB, 1 = 16 KiB, 2 = 32 KiB, 3 = 128 KiB, 4 = 256 KiB, 5 = 512 KiB)
    • D3: CHR type (0 = CHR ROM, 1 = CHR RAM)
    • D2-D0: CHR size (0 = 8 KiB, 1 = 16 KiB, 2 = 32 KiB, 3 = 64 or 128 KiB, 4 = 256 KiB)
  • $FFF5 : Board type.
    • D7: Nametable arrangement (0 = Horizontal arrangement, 1 = Vertical arrangement). Inverse of iNES mirroring bit. If mapper-controlled, this might be used to indicate the primary arrangement.
    • D6-D0: Mapper (0 = NROM, 1 = CNROM, 2 = UNROM, 3 = GNROM, 4 = MMC (any)). Determines which area to checksum.
  • $FFF6 : Title encoding (0 = No title entered, 1 = ASCII, 2 = JIS X 0201 (possibly Shift JIS?))
  • $FFF7 : Valid title length - 1. 0 if no title entered.
  • $FFF8 : Maker's code, the same used for the FDS, GB, GBC and SNES headers : 1 = Nintendo, 2-254 = everyone else. 255 must be reserved.
  • $FFF9 : Header validation byte. 8-bit checksum of $FFF2-$FFF9 should = 0.

FamicomBox compatibility

The FamicomBox is the only device known to use the Nintendo header. Its firmware cartridge validates game cartridges using the header validation byte and the PRG checksum. If these are both correct, the game is marked as valid and the header title is copied, and otherwise, the game is looked up in the firmware cartridge's database for a match.

As part of validation, the FamicomBox performs writes to the game cartridge's mapper. It does this by searching forward from $8000-FFFF, ANDing each byte by a write mask and comparing against the write value. If they match, the value at that ROM address is read and then written back. This is done to avoid bus conflicts. If no match is found, $0000 is read and written, instead.

The sequence of operations for validating a cartridge are as follows:

  • The slot is checked for whether it contains a cartridge by checking 17 addresses for any non-$FF value. These addresses are: $0000, $8000, $8001, $8002, $8004, $8008, $8010, $8020, $8040, $8080, $8100, $8200, $8400, $8800, $9000, $A000, $C000. If only $FF is found, the slot is skipped. Note that the check on $0000 may result in false positives.
  • The mapper is written with value $00 and mask $FF.
  • The header is validated by verifying that $FFF2-FFF9 sum to 0. If they do not, the validation process is stopped and the database is checked.
  • The PRG checksum is calculated based on the cartridge's mapper type. The bytes at $FFF0 and $FFF1 are each subtracted from this sum and the result is compared to $FFF0-FFF1 (big endian). If they do not match, the validation process is stopped and the database is checked. Note that the bank is not changed between completing the sum and using the header's PRG checksum values.
    • NROM/CNROM: The PRG size is determined by looking for the largest range containing unique data ($E000-FFFF if $C000-DFFF and $E000-FFFF match, $C000-FFFF if $8000-BFFF and $C000-EFFF match, and $8000-FFFF otherwise). This range is summed.
    • UNROM: A single checksum is created by summing 8 banks together. The 8 banks are loaded by writing $00-07 with mask $07 to the mapper. For each bank, $8000-BFFF is summed into the combined total.
    • GNROM: Each of 4 banks is individually checked, so each requires its own checksum at $FFF0-FFF1. The 4 banks are loaded by writing $00, $10, $20, and $30 with mask $30 to the mapper. For each bank, $8000-FFFF is summed.
    • MMC: $C000-FFFF is summed.
    • Other: The match automatically fails.
  • The title length at $FFF7 is checked. If it isn't in the range $01-0F, the title is considered invalid, so a 16-byte title of value $20 (space) is used and the slot is marked as valid with an invalid title. Otherwise, the right-aligned title is copied from $FFE0-FFEF and stored left-aligned, the remaining space on the right is padded with value $20, and the slot is marked as valid with a valid title. Note that 1-character titles will be seen as invalid, but this can be fixed by incrementing the length and suffixing the title with a space. Any bytes to the left of the used portion of the title are free for general-purpose use.

For database lookups, $00 with mask $FF is written to the mapper and then the NROM/CNROM-style checksum is calculated.

Caveats for particular mappers

UNROM
The checksum routine avoids bus conflicts by searching ROM for the first byte in the currently-loaded $8000-FFFF region whose low 3 bits match the target bank number, reading that byte, and writing it back. This causes problems with variants supporting more than 8 banks, such as UOROM, because the remaining bits will depend on the value that was found by the search, so a checksum generator must perform this search to find which of the 8 banks are visited when calculating the checksum.
For 64 KiB UNROM, such as 240p Test Suite, the checksum routine reads each of the four banks twice. Thus, it double counts every byte, but still subtracts the checksum bytes only once. The checksum value must account for this. A potential strategy is to generate a checksum, and then reduce the value of an unused byte by half the sum of the two checksum bytes.
AxROM and BxROM
The header does not support these mapper types. The Japanese release of Battletoads uses AOROM and has a header that claims to be UNROM, but its checksum does not adhere to UNROM rules. These are best supported using the NROM or MMC types and placing the header information in the first bank.
MMC1
The mapper is not reset before the checksum is read, so the last bank may not be fixed to $C000-FFFF. Therefore, every bank that could be mapped there at power-on needs its own header with its own checksum.
MMC3, most VRCs, FME-7
These mappers have a fixed bank at $E000-$FFFF and an unpredictable switchable bank at $C000-$DFFF. To get the checksum to match, either the $00 mapper write needs to be used to make this region predictable, or the sum of all 8 KiB banks that can be mapped there must be identical. For MMC3, placing $00 at the beginning of each bank will send this write to the bank select register at $8000, fixing $C000-DFFF to the second-last bank.

In general, the MMC mapper type is likely the best fit for any given project, with the header and checksum included in any place that can be loaded after a write of $00 to first of any address that can contain $00.