Skip to content

Instantly share code, notes, and snippets.

@jaymcgavren
Last active May 23, 2026 06:50
Show Gist options
  • Select an option

  • Save jaymcgavren/724dee25090f2b68ede8710028c62062 to your computer and use it in GitHub Desktop.

Select an option

Save jaymcgavren/724dee25090f2b68ede8710028c62062 to your computer and use it in GitHub Desktop.
A collection of scripts for building the "Comprehensive Super Mario Bros. disassembly" on MacOS (and maybe Linux)

Compiling Super Mario Bros.

To compile assembly code for the NES's 6502 processor, you'll need the cc65 toolchain. That will provide the ca65 compiler and ld65 linker used by these scripts.

On Mac you can install cc65 using Homebrew:

$ brew install cc65

Save the Super Mario Bros. ROM that you've extracted from your cartridge to original.nes.

Extract the iNES header from the ROM to a file.

$ python3 extract_hdr.py original.nes && mv original.hdr smb.hdr
Wrote 16 bytes to original.hdr

Extract the CHR-ROM data from the original ROM to a file.

$ python3 extract_chr.py original.nes && mv original.chr smb.chr
PRG-ROM: 2 bank(s) (32768 bytes)
CHR-ROM: 1 bank(s) (8192 bytes)
Wrote 8192 bytes to original.chr

The original "Comprehensive Super Mario Bros. Disassembly" uses older directives that cc65 can't process.

Retrieve the .asm file from https://gist.github.com/1wErt3r/4048722 and save it as smbdis_original.asm.

Then run this script to re-write it with directives cc65 can handle.

$ ./update_asm.sh smbdis_original.asm > smbdis.asm

If this isn't your first time compiling, remove any previous ROM you've generated.

$ rm smb.nes

Finally, run this script to compile everything into a playable ROM.

$ ./build.sh

Now you can try your new ROM in an emulator!

$ /Applications/Mesen.app/Contents/MacOS/Mesen smb.nes &
#!/bin/bash
# Compile the source into an object file.
ca65 smbdis.asm
# Link the object file into a flat binary.
ld65 -C ldconfig.txt smbdis.o
# Join the iNES header, flat binary, and CHR-ROM into a playable ROM.
cat smb.hdr smb.prg smb.chr > smb.nes
#!/usr/bin/env python3
"""Extract CHR-ROM data from an iNES (.nes) ROM file."""
import sys
from pathlib import Path
INES_MAGIC = b"NES\x1a"
HEADER_SIZE = 16
PRG_BANK_SIZE = 16 * 1024 # 16KB
CHR_BANK_SIZE = 8 * 1024 # 8KB
def extract_chr(rom_path: Path) -> None:
data = rom_path.read_bytes()
if len(data) < HEADER_SIZE:
sys.exit(f"Error: {rom_path} is too small to be a valid iNES ROM")
if data[:4] != INES_MAGIC:
sys.exit(f"Error: {rom_path} does not start with the iNES magic number")
prg_banks = data[4]
chr_banks = data[5]
if chr_banks == 0:
sys.exit(f"Error: {rom_path} has no CHR-ROM (uses CHR-RAM)")
prg_size = prg_banks * PRG_BANK_SIZE
chr_size = chr_banks * CHR_BANK_SIZE
chr_start = HEADER_SIZE + prg_size
chr_end = chr_start + chr_size
if len(data) < chr_end:
sys.exit(
f"Error: file is {len(data)} bytes but CHR-ROM requires data up to byte {chr_end}"
)
chr_data = data[chr_start:chr_end]
out_path = rom_path.with_suffix(".chr")
out_path.write_bytes(chr_data)
print(f"PRG-ROM: {prg_banks} bank(s) ({prg_size} bytes)")
print(f"CHR-ROM: {chr_banks} bank(s) ({chr_size} bytes)")
print(f"Wrote {len(chr_data)} bytes to {out_path}")
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit(f"Usage: {sys.argv[0]} <rom.nes>")
extract_chr(Path(sys.argv[1]))
#!/usr/bin/env python3
"""Extract the iNES header from a .nes ROM file."""
import sys
from pathlib import Path
INES_MAGIC = b"NES\x1a"
HEADER_SIZE = 16
def extract_hdr(rom_path: Path) -> None:
data = rom_path.read_bytes()
if len(data) < HEADER_SIZE:
sys.exit(f"Error: {rom_path} is too small to be a valid iNES ROM")
if data[:4] != INES_MAGIC:
sys.exit(f"Error: {rom_path} does not start with the iNES magic number")
out_path = rom_path.with_suffix(".hdr")
out_path.write_bytes(data[:HEADER_SIZE])
print(f"Wrote {HEADER_SIZE} bytes to {out_path}")
if __name__ == "__main__":
if len(sys.argv) != 2:
sys.exit(f"Usage: {sys.argv[0]} <rom.nes>")
extract_hdr(Path(sys.argv[1]))
MEMORY {
ROM0: start = $8000, size = $8000, file = "smb.prg" ;
}
SEGMENTS {
CODE: load = ROM0, type = ro;
}
#!/bin/bash
# Updates the "Comprehensive Super Mario Bros. disassembly" SMBDIS.ASM
# to work with the cc65 compiler toolchain.
# - Removes ".index 8" and ".mem 8". ca65 doesn't support them.
# - Replaces ".db" -> ".byte" and ".dw" -> ".word". These are the ca65-standard equivalents.
if [ $# -ne 1 ]; then
echo "Usage: $0 <file.asm>" >&2
exit 1
fi
sed "$1" \
-e '/^\s*\.index\b/d' \
-e '/^\s*\.mem\b/d' \
-e 's/\.db\b/.byte/g' \
-e 's/\.dw\b/.word/g'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment