"""Assemble the C128 VDC viewer with `xa` and build the loadable PRG. The PRG loads at the C128 BASIC start ($1C01): a tiny BASIC stub (`10 SYS7200`) followed by the 8502 viewer (at $1C20) and, from $2000, the 640x200 bitmap. Running it (RUN"PIC") executes the stub, which 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 = 0x1C01 ML_ORG = 0x1C20 DATA_ORG = 0x2000 # bitmap goes here (viewer copies it to the VDC) # BASIC: 10 SYS7200 ($1C20 = 7200) -- bytes as they sit from $1C01 _STUB = bytes([0x0B, 0x1C, 0x0A, 0x00, 0x9E, 0x37, 0x32, 0x30, 0x30, 0x00, 0x00, 0x00]) class AssemblerError(RuntimeError): pass def have_xa() -> bool: return shutil.which("xa") is not None def _xa(wrapper: str) -> bytes: """Assemble a generated wrapper (xa runs in VIEWER_DIR so #includes resolve).""" if not have_xa(): raise AssemblerError("The 'xa' (xa65) assembler was not found on PATH.\n" "Install it with: sudo apt install xa65") 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: return f.read() finally: os.unlink(wrap) def _assemble(fgbg: int, source: str) -> bytes: return _xa(f"#define SRC ${DATA_ORG:04X}\n" f"#define FGBG ${fgbg & 0xFF:02X}\n" f'#include "{source}"\n') def _wrap_prg(code: bytes, data: bytes) -> bytes: """Lay out load-address prefix + BASIC stub + viewer code + data @ $2000.""" mem = bytearray() mem += _STUB # $1C01.. mem += b"\x00" * (ML_ORG - BASIC_START - len(_STUB)) mem += code # $1C20.. if len(mem) > DATA_ORG - BASIC_START: raise AssemblerError("viewer code overruns the $2000 data area") mem += b"\x00" * (DATA_ORG - BASIC_START - len(mem)) mem += data # $2000.. return bytes([BASIC_START & 0xFF, BASIC_START >> 8]) + bytes(mem) def build_prg_color(attributes: bytes, fgbg: int = 0x0F) -> bytes: """Return the loadable PRG for the 80x100 chunky-colour viewer. `attributes` is the 8000-byte per-cell colour map (colour in the high nibble); the viewer fills the character matrix with a solid glyph itself. """ return _wrap_prg(_assemble(fgbg, "color.s"), attributes) def build_prg_hicolor(vdc_image: bytes, fgbg: int = 0x00) -> bytes: """Return the loadable PRG for the 640x200 custom-charset viewer (font mode). Used by both the `hicolor` and `mono` modes. `vdc_image` is the full VDC RAM image (character codes, attributes and the custom character set already laid out); `fgbg` carries the global background in its low nibble. The viewer copies the image verbatim into VDC RAM. """ return _wrap_prg(_assemble(fgbg, "hicolor.s"), vdc_image) _SS_WAITMODE = {"key": 1, "seconds": 2, "both": 3} def build_slideshow_prg(fgbg_list, advance: str = "both", seconds: int = 10, loop: bool = True, video: str = "pal") -> bytes: """Return the bootable C128 slideshow viewer PRG (RUN\"PIC\"). ``fgbg_list`` is one VDC R26 background byte per image (conv.meta["fgbg"]); the per-image pictures are separate "00".."NN" files the viewer loads. The viewer is code-only (no appended image) -- just the BASIC stub + the 8502 loop + the ss_fgbg table. """ if not fgbg_list: raise AssemblerError("a slideshow needs at least one image") jiffyps = 60 if video == "ntsc" else 50 table = ",".join(str(int(b) & 0xFF) for b in fgbg_list) code = _xa( f"#define WAITMODE {_SS_WAITMODE[advance]}\n" f"#define WAITSECS {max(0, int(seconds))}\n" f"#define JIFFYPS {jiffyps}\n" f"#define NIMAGES {len(fgbg_list)}\n" f"#define LOOPFLAG {1 if loop else 0}\n" '#include "slideshow.s"\n' "ss_fgbg:\n" f" .byte {table}\n") mem = bytearray() mem += _STUB # $1C01 BASIC stub (10 SYS7200) mem += b"\x00" * (ML_ORG - BASIC_START - len(_STUB)) mem += code # $1C20 viewer + ss_fgbg table return bytes([BASIC_START & 0xFF, BASIC_START >> 8]) + bytes(mem)