82 lines
2.9 KiB
Python
82 lines
2.9 KiB
Python
"""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)
|