"""Assemble the VIC-20 viewer with `xa` and lay out the 8K autostart cartridge. The cartridge occupies $A000-$BFFF (8192 bytes). The KERNAL recognises the "A0CBM" signature at $A004 and jumps through the cold vector at $A000, so no 6502 reset vector is needed. MAME's `vic20 -cart` wants a full 8K image; smaller .a0 files fail with an I/O error. ROM layout (fixed so the viewer can copy from constant addresses): $A000 header + viewer code $A800 CHARSRC character set (2048 bytes -> RAM $1400) $B000 SCRSRC screen ( 506 bytes -> RAM $1E00) $B200 COLSRC colour RAM ( 506 bytes -> RAM $9600) """ from __future__ import annotations import os import shutil import subprocess import tempfile VIEWER_DIR = os.path.dirname(os.path.abspath(__file__)) CART_BASE = 0xA000 CART_SIZE = 0x2000 CHARSRC = 0xA800 SCRSRC = 0xB000 COLSRC = 0xB200 class AssemblerError(RuntimeError): pass def have_xa() -> bool: return shutil.which("xa") is not None def _assemble(bg: int, border: int, aux: int) -> bytes: if not have_xa(): raise AssemblerError("The 'xa' (xa65) assembler was not found on PATH.\n" "Install it with: sudo apt install xa65") wrapper = (f"* = ${CART_BASE:04X}\n" f"#define CHARSRC ${CHARSRC:04X}\n" f"#define SCRSRC ${SCRSRC:04X}\n" f"#define COLSRC ${COLSRC:04X}\n" f"#define BG {bg & 15}\n" f"#define BORDER {border & 7}\n" f"#define AUX {aux & 15}\n" '#include "viewer.s"\n') with tempfile.TemporaryDirectory() as td: out = os.path.join(td, "v.bin") fd, wrap = tempfile.mkstemp(suffix=".s", prefix="_wrap_", dir=VIEWER_DIR) try: with os.fdopen(fd, "w") as f: f.write(wrapper) proc = subprocess.run(["xa", "-o", out, os.path.basename(wrap)], capture_output=True, text=True, cwd=VIEWER_DIR) if proc.returncode != 0: raise AssemblerError(f"xa failed:\n{proc.stdout}{proc.stderr}") with open(out, "rb") as f: code = f.read() finally: os.unlink(wrap) return code def build_cart(data: dict) -> bytes: """data carries chardata(2048), screen(506), color(506) and bg/border/aux.""" code = _assemble(data["bg"], data["border"], data["aux"]) if len(code) > (CHARSRC - CART_BASE): raise AssemblerError(f"viewer code is {len(code)} bytes, overruns CHARSRC") chardata = bytes(bytearray(data["chardata"])) screen = bytes(bytearray(data["screen"])) color = bytes(bytearray(data["color"])) rom = bytearray(b"\x00" * CART_SIZE) rom[0:len(code)] = code rom[CHARSRC - CART_BASE:CHARSRC - CART_BASE + len(chardata)] = chardata rom[SCRSRC - CART_BASE:SCRSRC - CART_BASE + len(screen)] = screen rom[COLSRC - CART_BASE:COLSRC - CART_BASE + len(color)] = color return bytes(rom)