"""Assemble the C16 TED viewer with `xa` and build the loadable PRG. The PRG loads at the C16 BASIC start ($1001): a BASIC stub (`10 SYS4128`) followed by the 7501 viewer (at $1020), the two colour matrices ($1800 / $1C00) and the bitmap ($2000). MAME's quickload runs it, the stub SYSes the viewer. """ from __future__ import annotations import os import shutil import subprocess import tempfile VIEWER_DIR = os.path.dirname(os.path.abspath(__file__)) BASIC_START = 0x1001 ML_ORG = 0x1020 ATTR_ORG = 0x1800 # luminance matrix (video matrix base) CH_ORG = 0x1C00 # hue matrix (video base | $400) BITMAP_ORG = 0x2000 # BASIC: 10 SYS4128 ($1020 = 4128) -- bytes as they sit from $1001 _STUB = bytes([0x0B, 0x10, 0x0A, 0x00, 0x9E, 0x34, 0x31, 0x32, 0x38, 0x00, 0x00, 0x00]) class AssemblerError(RuntimeError): pass def have_xa() -> bool: return shutil.which("xa") is not None def _assemble() -> bytes: if not have_xa(): raise AssemblerError("The 'xa' (xa65) assembler was not found on PATH.\n" "Install it with: sudo apt install xa65") proc = subprocess.run(["xa", "-o", "/dev/stdout", "viewer.s"], capture_output=True, cwd=VIEWER_DIR) if proc.returncode != 0: raise AssemblerError(f"xa failed:\n{proc.stderr.decode()}") return proc.stdout def build_prg(payload: bytes) -> bytes: """payload = bitmap(8000) + attr(1000) + ch(1000); returns the loadable PRG.""" bitmap, attr, ch = payload[:8000], payload[8000:9000], payload[9000:10000] code = _assemble() mem = bytearray() mem += _STUB # $1001.. mem += b"\x00" * (ML_ORG - BASIC_START - len(mem)) mem += code # $1020.. if len(mem) > ATTR_ORG - BASIC_START: raise AssemblerError("viewer code overruns the $1800 matrix area") mem += b"\x00" * (ATTR_ORG - BASIC_START - len(mem)) mem += attr # $1800.. mem += b"\x00" * (CH_ORG - BASIC_START - len(mem)) mem += ch # $1C00.. mem += b"\x00" * (BITMAP_ORG - BASIC_START - len(mem)) mem += bitmap # $2000.. return bytes([BASIC_START & 0xFF, BASIC_START >> 8]) + bytes(mem)