~90 minutes. Open your homebrew ROM in a hex editor; identify and decode the 16-byte INES header.
Goal: read your first 16 bytes of a real file format, byte by byte; understand what each header byte means; record your findings.
Estimated time: 90 minutes
Prerequisites: lab 1.1 and 1.2 complete (Mesen working; homebrew ROM available); hex editor installed (HxD / Hex Fiend / Bless / hexedit)
Steps
Step 1: Open your hex editor and load the ROM (5 minutes)
Launch your hex editor. Open the homebrew NES ROM you used in week 1. The editor displays the file in three columns:
- Left: offset (the position of the byte within the file, usually shown as a hex number)
- Middle: hex values (the actual bytes, shown in hexadecimal)
- Right: ASCII (the bytes shown as printable characters where possible; a dot or space otherwise)
Step 2: Read bytes 0-3 (the magic number) (10 minutes)
The first four bytes should be:
4E 45 53 1A
In ASCII, this reads N, E, S, and then a non-printable character (0x1A is ASCII end-of-file, originally a CP/M convention).
This is the "magic number", a fixed sequence that identifies the file as a valid INES (NES ROM) file. Every NES ROM that uses the INES format starts with these four bytes.
Write down in your journal: "Bytes 0-3 are the magic number 4E 45 53 1A ('NES' + EOF). This tells emulators and tools 'this is a NES ROM in INES format.'"
Step 3: Read byte 4 (PRG-ROM size) (10 minutes)
Byte 4 tells you the size of the PRG-ROM (the program code) in 16-kilobyte units.
If byte 4 is 01, your PRG-ROM is 16 KB (1 × 16 KB). If byte 4 is 02, PRG-ROM is 32 KB. If byte 4 is 04, PRG-ROM is 64 KB. And so on.
Write down: "Byte 4 = ___ (decimal ___). PRG-ROM size = ___ × 16 KB = ___ KB."
Step 4: Read byte 5 (CHR-ROM size) (10 minutes)
Byte 5 tells you the size of the CHR-ROM (the graphics data, sprite tiles) in 8-kilobyte units.
If byte 5 is 01, CHR-ROM is 8 KB (1 × 8 KB). If byte 5 is 02, CHR-ROM is 16 KB. If byte 5 is 00, the cart uses CHR-RAM (no CHR-ROM on the cart; graphics live in RAM and the game loads them from PRG-ROM).
Write down: "Byte 5 = ___ (decimal ___). CHR-ROM size = ___ × 8 KB = ___ KB."
Step 5: Read byte 6 (flags 6, mapper low nibble + others) (15 minutes)
Byte 6 is a bit-packed flags byte. The bits are:
- Bit 0: mirroring (0 = horizontal, 1 = vertical)
- Bit 1: battery-backed RAM at $6000-$7FFF (0 = no, 1 = yes)
- Bit 2: trainer present at $7000-$71FF (0 = no, 1 = yes; rare)
- Bit 3: four-screen VRAM (0 = no, 1 = yes; rare)
- Bits 4-7: low 4 bits of the mapper number
Convert byte 6 from hex to binary (your hex editor likely has a built-in binary view; or use any online hex-to-binary converter; or do it by hand). Write down which bits are set and what they mean.
Mapper note: NES games used "mappers" to extend the cart beyond the basic 32 KB PRG-ROM + 8 KB CHR-ROM. Each mapper number identifies a specific bank-switching scheme. NROM (mapper 0) is the simplest; MMC1 (mapper 1), UNROM (mapper 2), and CNROM (mapper 3) are also common.
Write down: "Byte 6 = ___. Mapper low nibble = ___. Mirroring = ___. Other flags: ___."
Step 6: Read byte 7 (flags 7, mapper high nibble + others) (10 minutes)
Byte 7 is another bit-packed flags byte. The high nibble (bits 4-7) is the high 4 bits of the mapper number.
Combine: full mapper number = (byte 7 high nibble << 4) | (byte 6 high nibble). Most homebrew ROMs use mapper 0 (NROM), so you will likely see byte 7 = 00 and the mapper number = 0.
Write down: "Byte 7 = ___. Full mapper number = ___."
Step 7: Read bytes 8-15 (less commonly used) (10 minutes)
Bytes 8-15 are mostly for less-common features (PRG-RAM size, TV system, region). For most homebrew ROMs they are zero or close to zero.
Write down: "Bytes 8-15 = ___ ___ ___ ___ ___ ___ ___ ___. Most are zero, typical for homebrew."
Step 8: Confirm in Mesen (10 minutes)
Open Mesen. Load your ROM. Menu: Tools → ROM Info (or similar; the exact menu name varies).
Mesen shows the parsed INES header in human-readable form: mapper number, PRG-ROM size, CHR-ROM size, mirroring, etc. Compare what Mesen reports to what you decoded by hand. They should match.
If they do not match, re-check your byte-by-byte decoding.
Step 9: Journal your decoding (10 minutes)
Open ~/spk-101/journal/lab-2-1-notes.md. Write up your full decoding of the INES header. Include:
- The raw 16 bytes
- What each byte (or field) means
- Mesen's parsed view (confirming or contradicting yours)
- A reflection: what was surprising about how compact this header is? How many bytes is this compared to a modern file header (e.g., a PDF or PNG)?
Expected output
- A full decoding of your ROM's 16-byte INES header
- Mesen's parsed view confirming your decoding
- A journal entry explaining what you found
Common pitfalls
- Counting from 1 instead of 0: file offsets start at 0, not 1. The first byte is byte 0, not byte 1. The fourth byte is byte 3
- Reading hex digits as decimal: hex
10is decimal 16, not 10. Hex20is decimal 32. HexFFis decimal 255. Double-check your conversions - Mesen does not show ROM Info: not all builds have this. Try Tools → "ROM Header" or similar; or just compare your decoded byte values to the INES wiki specification at
https://www.nesdev.org/wiki/INES
Stretch (optional)
If you finished early:
- Download a second homebrew ROM. Decode its INES header. Compare to the first
- Find a "trainer" ROM (rare, search NESdev for one) and decode its header. The trainer bit (flags 6 bit 2) should be set
- Read the full INES specification at
https://www.nesdev.org/wiki/INES. Notice that NES2.0 (a newer extension) uses bytes 8-15 more aggressively. Your homebrew is likely INES (older); some modern releases are NES2.0
Lab 2.1 v0.1. Your first decoding of a real file format, byte by byte.