"""Assemble the 6502 viewer stubs with `xa` and build self-contained viewer PRGs. Each viewer is a small ML stub originating at $0801 (behind a BASIC SYS 2061 autostart). The picture data is appended after the stub, zero-padded so the bitmap lands exactly at $2000, screen RAM at $3F40, etc. The whole thing loads in a single pass -- no second disk access -- so it works identically on real hardware and in any emulator regardless of device configuration. `xa -o` emits raw bytes starting at the origin without the 2-byte CBM load address, which we prepend here. """ from __future__ import annotations import os import shutil import subprocess import tempfile VIEWER_DIR = os.path.dirname(os.path.abspath(__file__)) LOAD_ADDR = 0x0801 DATA_ADDR = 0x2000 # where appended picture data must land # mode/viewer key -> source filename SOURCES = { "hires": "hires.s", "multicolor": "multicolor.s", "fli": "fli.s", "fli_ntsc": "fli_ntsc.s", "interlace": "interlace.s", } _cache: dict[str, bytes] = {} class AssemblerError(RuntimeError): pass def have_xa() -> bool: return shutil.which("xa") is not None def assemble_stub(viewer_key: str) -> bytes: """Assemble a viewer stub to raw bytes (origin $0801, no load-address prefix).""" if viewer_key in _cache: return _cache[viewer_key] if not have_xa(): raise AssemblerError( "The 'xa' (xa65) assembler was not found on PATH.\n" "Install it with: sudo apt install xa65 (Debian/Ubuntu)\n" "or build from https://www.floodgap.com/retrotech/xa/") src = os.path.join(VIEWER_DIR, SOURCES[viewer_key]) if not os.path.exists(src): raise AssemblerError(f"viewer source missing: {src}") with tempfile.TemporaryDirectory() as td: out = os.path.join(td, "viewer.bin") proc = subprocess.run(["xa", "-o", out, src], capture_output=True, text=True) if proc.returncode != 0: raise AssemblerError(f"xa failed for {src}:\n{proc.stdout}{proc.stderr}") with open(out, "rb") as f: raw = f.read() _cache[viewer_key] = raw return raw def build_viewer_prg(viewer_key: str, data: bytes, data_addr: int = DATA_ADDR) -> bytes: """Combine the assembled stub + padding + picture ``data`` into one PRG. ``data`` is the block that must reside from ``data_addr`` upward (bitmap, screen, colour RAM, background, ...). """ stub = assemble_stub(viewer_key) pad_len = (data_addr - LOAD_ADDR) - len(stub) if pad_len < 0: raise AssemblerError( f"viewer stub {viewer_key} is {len(stub)} bytes, exceeds the " f"{data_addr - LOAD_ADDR} bytes available before ${data_addr:04x}") payload = stub + bytes(pad_len) + bytes(data) return bytes([LOAD_ADDR & 0xFF, (LOAD_ADDR >> 8) & 0xFF]) + payload