Open the ROM file you ran last week in a hex editor instead of an emulator. Read the INES header. Find the program code section. Find the graphics. By the end of the week you can point to a specific byte and say what it does.
Theme
Last week you saw what the ROM does (it boots a game). This week you see what the ROM is (a sequence of bytes with structure). A NES ROM file is not magic; it is a specific file format with a specific header and specific sections, and most of those bytes follow rules you can learn this week.
You install a hex editor (HxD on Windows, Hex Fiend on macOS, Bless or hexedit on Linux, all free) and open the homebrew ROM from week 1. You walk through what each section means. By the end of the week you understand that a ROM file is a 32 KB or 256 KB or 512 KB blob of bytes, with the first 16 bytes being the INES header, the next chunk being the program code, and the rest being the graphics data.
This is the foundation for week 3 (when you read the program code as assembly) and week 4 (when you modify specific bytes to make a ROM hack).
Reading list (~50 minutes)
- NESdev wiki: "INES file format" at
wiki.nesdev.org/w/index.php/INES, the canonical reference for the file format. Read carefully; you will use this all week. - NESdev wiki: "PPU pattern tables" at
wiki.nesdev.org/w/index.php/PPU_pattern_tables, the graphics data section of a ROM is the "pattern table." This page explains the byte layout of an 8x8 sprite. - Easy 6502 by Nick Morgan, intro section at
skilldrick.github.io/easy6502/, read just the introduction and the first few paragraphs. You are getting a flavor of where week 3 goes; the deep read happens then.
Lecture outline (~1.5 hours)
Section 1: What is a byte?
- A byte is 8 bits; each bit is a 0 or a 1
- 8 bits gives 256 possible values (0 through 255 in decimal, 00 through FF in hexadecimal)
- A hex editor displays each byte as 2 hexadecimal digits (00 to FF)
- We use hex because it is more compact than binary and the boundaries align: each hex digit is 4 bits, so each byte is 2 hex digits
- The same byte can mean different things depending on context: byte 0x41 means the letter 'A' in ASCII text, or the number 65 in decimal, or the assembly instruction EOR (indirect, X) on the 6502 CPU. The same bits; different meanings based on what code is reading them.
Section 2: The INES file format
- A NES ROM dump is wrapped in the INES file format (named after iNES, the historical first NES emulator)
- The first 16 bytes are the INES header
- Header byte 0-3: the ASCII magic number "NES" followed by a 0x1A byte. Every legit NES ROM file starts with these 4 bytes
- Header byte 4: how many 16 KB chunks of PRG-ROM (program code) the file contains
- Header byte 5: how many 8 KB chunks of CHR-ROM (character / graphics) the file contains
- Header bytes 6-7: flags (mapper number, mirroring mode, trainer presence, NES 2.0 indicator)
- Header bytes 8-15: more flags, mostly NES 2.0 extensions; often 0
- After the 16-byte header: PRG-ROM data (the program code) followed by CHR-ROM data (the graphics)
Section 3: What is PRG-ROM?
- PRG-ROM (Program ROM) is the 6502 machine code the game runs
- 6502 instructions are 1, 2, or 3 bytes each
- The CPU reads PRG-ROM bytes as instructions and executes them
- A simple NES game has 16 KB of PRG-ROM (one chunk); larger games have 32, 64, 128, 256, 512, or more KB across multiple chunks
- You will read the PRG-ROM as assembly code in week 3
Section 4: What is CHR-ROM?
- CHR-ROM (Character ROM) is the graphics data
- Each 8x8 sprite tile is 16 bytes (2 bits per pixel: 2 bits times 64 pixels = 128 bits = 16 bytes)
- The PPU reads CHR-ROM bytes to draw the screen
- 8 KB of CHR-ROM holds 512 distinct 8x8 tiles (8192 bytes divided by 16 bytes per tile)
- In week 4 you will modify CHR-ROM bytes to change what sprites look like
The two-bits-per-pixel encoding is the PPU's bitplane scheme: byte 0 holds the low bit of every pixel in row 0, byte 8 holds the high bit of every pixel in row 0, and the eight pairs (0,8), (1,9), (2,10), ..., (7,15) make the eight rows of the tile. Read each byte once for the low plane and once for the high plane to see how the two contribute to the final four-color pixel. The interactive below lets you toggle individual bits and watch the rendered tile change.
Section 5: Hex editor basics
- A hex editor shows the bytes of a file in two columns: the hex representation (left) and the ASCII representation (right)
- You can navigate by byte offset (typically with Ctrl+G or Cmd+G to "go to address")
- You can edit a byte by clicking on it and typing the new hex digits
- Modern hex editors highlight your changes in red until you save
- Always work on a copy of a ROM, not the original. Always keep the original ROM filed away with its SHA-256 hash recorded so you can verify you have not corrupted the source.
Labs (~4 hours)
Two labs this week.
Lab 2.1: Open the ROM in a hex editor and find the INES header (labs/lab-2-1-hex-view-header.md)
- Goal: You open last week's homebrew ROM in a hex editor; identify the INES header; decode each header byte; document what each byte means
- Time: ~90 minutes
- Artifact: a marked-up screenshot of the first 32 bytes of the ROM with each header byte annotated
Lab 2.2: Find the graphics data and view a sprite (labs/lab-2-2-view-sprite-tile.md)
- Goal: You navigate to the start of CHR-ROM in the hex editor; locate the first 16-byte sprite tile; either compute its appearance on paper or use a viewer tool like YY-CHR or NES Sprite Editor to render it
- Time: ~90 minutes
- Artifact: a sketch (paper or digital) of the first sprite tile based on its 16 raw bytes
Independent practice (~2.5 hours)
Pick any one or two:
- Compare two ROMs. Open the homebrew ROM you used in week 1 and another homebrew ROM in your hex editor side by side. Are their INES headers the same? Different? Where do they differ? Why?
- Find the title screen text. Many NES games store their title text as ASCII bytes somewhere in PRG-ROM (so it is easy to display). Use your hex editor's "search for ASCII text" feature to find the title text in your homebrew ROM. Note the byte offset.
- Read the NESdev wiki page on iNES mappers. Mappers are how NES games larger than 32 KB worked (the cartridge contains extra hardware to switch which 16 KB chunk of PRG-ROM is currently visible to the CPU). Read the wiki page on mapper 0 (the simplest case, no mapper hardware needed). This is the mapper your homebrew ROM probably uses.
- Practice your hex editor's navigation. Type Ctrl+G (or your editor's "go to offset" shortcut) and jump to byte 0x4000. Byte 0x8000. Byte 0x0010. Get fast at this; you will use it a lot in weeks 4 and 6.
Reflection prompts (~30 minutes)
- Before this week, what did you think was inside a ROM file? After this week, has that changed?
- The INES header is 16 bytes. The same byte can mean different things in different contexts. What is one example you saw this week where the meaning of a byte depended on where it appeared in the file?
- The course teaches you to always make a copy of a ROM before editing. Why do you think this matters?
- The graphics data section (CHR-ROM) stores 8x8 sprite tiles as 16 bytes each. What would happen if you changed one byte of one tile? What part of the game would look different?
- Looking ahead: week 3 reads the PRG-ROM section as 6502 assembly code. Based on what you saw this week, where do you think the CPU starts reading the program when the game first boots?
Visual Helpers
Three of this week's concepts are the ones most likely to slow down younger students working independently. If any of these feel stuck, the cognitive tools supplement at curriculum-supplements/spk-101-younger-learner-tools.md has worked examples and step-by-step breakdowns for each.
Offset calculations (hex + decimal math): When you calculate CHR-ROM start = 16 + (PRG-ROM size), you are mixing hex and decimal. If you are getting the wrong tile when you navigate to an offset, re-derive the formula from scratch on a piece of paper before editing the ROM. The supplement has two fully-worked examples.
Bit-packed flags (header byte 6): Header byte 6 packs eight yes/no signals into one byte. If reading "byte 6 = 0x01" feels abstract, draw eight boxes on paper and fill in 0s and 1s for the binary representation (0000 0001). Label each box with its flag name from the INES spec. The supplement's Bitplane Decomposer section explains the bit-stacking idea in plain English before you need it for sprites.
Sprite tile decoding (Lab 2.2): Two bytes combine bit-by-bit to set one pixel's color. Before using any software, decode the first row of tile 0 by hand on graph paper. The supplement walks you through this column by column. Do not skip the graph-paper step.
What comes next
Week 3 introduces the 6502 CPU. You take the PRG-ROM section you found this week and read it as code: load this value into the accumulator, store it at this address, jump here, branch if not equal. The 6502 is the friendliest 8-bit CPU ever built; you will learn the small set of instructions it uses and read real game code with them. By the end of week 3 you can read a few hundred bytes of PRG-ROM and explain what the game is doing in plain English.