122 lines
4.8 KiB
Python
122 lines
4.8 KiB
Python
"""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)
|