NES ROM patcher

NES hacks still arrive as IPS. The file they expect is usually an iNES image: a 16-byte header beginning with the bytes 4E 45 53 1A (NES followed by the end-of-file control byte), then the PRG and CHR banks. Patch records count from the start of that file. If you strip the header, every offset lands 16 bytes early and the game breaks in a way that looks like a bad IPS. I leave the header on unless the readme says the patch was built on a headerless dump, which is the uncommon case.

Mappers live in that header. A patch can rewrite mapper bits, and an emulator that ignores the header will not show the change. The patcher does not emulate. It writes bytes. Afterward Nestopia, Mesen, or FCEUX has to honor the header the patch left behind. Trainer stubs, the optional 512-byte block some old images add before PRG, are another offset shift. I only worry about them when the size is 512 bytes larger than the header plus banks.

IPS will not tell you the base was wrong. A European ROM and a US ROM can both accept the same records and only one of them is the game the author played. When a project has moved to BPS, believe the checksum. When it is IPS, believe the readme's filename and region. Sixteen mebibytes is the IPS ceiling and no licensed NES game approaches it, so the cap is not the NES problem. The header is.

Related pages are the IPS format guide and the formats index. Game Boy is a different header checksum and a different extension. Do not reuse this advice on a .gb file.

FieldRecord
Extension.nes
Header16-byte iNES header, usually kept
Usual patchIPS
Offset riskRemoving the header shifts every record
Browser ROM patcher with two file slots and a patch button
Pixel-art CRT showing a blank cartridge, used as a visual marker

Questions

Should I remove the NES header before patching?

Almost never. IPS authors count the header. Remove it only when the readme says the patch targets a headerless file.

The patch applied but the mapper is wrong.

The bytes changed. The emulator has to read the iNES mapper field. Try a core that honors the header.

Sources