Working Python version for Commodore.
This commit is contained in:
commit
2a48f52979
51 changed files with 3095 additions and 0 deletions
82
c64view/viewer/assemble.py
Normal file
82
c64view/viewer/assemble.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue