Working Python version for Commodore.

This commit is contained in:
The Dust Council 2026-06-14 17:43:12 -07:00
commit 2a48f52979
51 changed files with 3095 additions and 0 deletions

View file

@ -0,0 +1,82 @@
"""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