First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
65
lenser/nes/cartridge.py
Normal file
65
lenser/nes/cartridge.py
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
"""Build an iNES (.nes) NROM cartridge: 6502 PPU-init viewer + image data + CHR.
|
||||
|
||||
Layout: 16-byte iNES header, 16K PRG-ROM (viewer at $C000, 32-byte palette at
|
||||
$F000, 1024-byte nametable+attributes at $F020, vectors at $FFFA), then 8K
|
||||
CHR-ROM (256 background tiles in pattern table 0).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
PRG_BASE = 0xC000
|
||||
PRG_SIZE = 0x4000 # 16K
|
||||
PAL_ADDR = 0xF000 # 32-byte palette
|
||||
NT_ADDR = 0xF020 # 1024-byte nametable + attribute
|
||||
|
||||
|
||||
def have_xa() -> bool:
|
||||
return shutil.which("xa") is not None
|
||||
|
||||
|
||||
def _assemble() -> bytes:
|
||||
if not have_xa():
|
||||
raise RuntimeError("The 'xa' (xa65) assembler was not found (apt install xa65).")
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
out = os.path.join(td, "v.bin")
|
||||
proc = subprocess.run(["xa", "-o", out, "viewer.s"],
|
||||
capture_output=True, text=True, cwd=VIEWER_DIR)
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"xa failed:\n{proc.stdout}{proc.stderr}")
|
||||
with open(out, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
def build_rom(palette: bytes, nametable: bytes, chr_rom: bytes) -> bytes:
|
||||
"""palette: 32 bytes; nametable: 1024 (960 names + 64 attr); chr_rom: 8192."""
|
||||
if len(palette) != 32 or len(nametable) != 1024 or len(chr_rom) != 8192:
|
||||
raise ValueError("bad NES data block sizes")
|
||||
code = _assemble() # 6502 viewer, org $C000
|
||||
if len(code) > PAL_ADDR - PRG_BASE:
|
||||
raise RuntimeError("viewer code overruns the $F000 data area")
|
||||
prg = bytearray(PRG_SIZE)
|
||||
prg[0:len(code)] = code # start == $C000
|
||||
prg[PAL_ADDR - PRG_BASE:PAL_ADDR - PRG_BASE + 32] = palette
|
||||
prg[NT_ADDR - PRG_BASE:NT_ADDR - PRG_BASE + 1024] = nametable
|
||||
# CPU vectors $FFFA/$FFFC/$FFFE all point at start ($C000)
|
||||
for off in (0x3FFA, 0x3FFC, 0x3FFE):
|
||||
prg[off] = PRG_BASE & 0xFF
|
||||
prg[off + 1] = PRG_BASE >> 8
|
||||
|
||||
header = bytes([0x4E, 0x45, 0x53, 0x1A, # "NES\x1A"
|
||||
1, # 16K PRG units
|
||||
1, # 8K CHR units
|
||||
0x00, 0x00] + [0] * 8) # NROM (mapper 0)
|
||||
return header + bytes(prg) + bytes(chr_rom)
|
||||
|
||||
|
||||
def write_nes(rom: bytes, path: str) -> str:
|
||||
with open(path, "wb") as f:
|
||||
f.write(rom)
|
||||
return path
|
||||
Loading…
Add table
Add a link
Reference in a new issue