First public commit.
This commit is contained in:
parent
2a48f52979
commit
4bac9d83ed
288 changed files with 18417 additions and 1076 deletions
92
lenser/ti99/cartridge.py
Normal file
92
lenser/ti99/cartridge.py
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
"""Builds a TI-99/4A 8KB cartridge ROM (>6000-7FFF) holding the viewer + image
|
||||
data, and packages it as an RPK (the cartridge container MAME loads).
|
||||
|
||||
ROM layout
|
||||
>6000 standard cartridge header (>AA magic, pointer to the program list)
|
||||
>6010 program-list entry -> appears on the TI menu, points at the viewer
|
||||
.... viewer machine code (TMS9900)
|
||||
.... image data (6144-byte pattern + 768-byte cell colours)
|
||||
pad to 8192 bytes
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import zipfile
|
||||
|
||||
from . import viewer
|
||||
|
||||
CART_BASE = 0x6000
|
||||
ROM_SIZE = 0x2000 # 8 KB
|
||||
PROG_LIST = 0x6010
|
||||
|
||||
|
||||
def _ascii(name: str, limit: int) -> bytes:
|
||||
"""TI menu names are uppercase ASCII; strip anything else."""
|
||||
out = "".join(c for c in name.upper() if 32 <= ord(c) < 127)
|
||||
return out[:limit].encode("ascii") or b"PHOTO"
|
||||
|
||||
|
||||
def _even(x: int) -> int:
|
||||
return x + (x & 1)
|
||||
|
||||
|
||||
def build_rom(data: bytes, title: str = "PHOTO", display: str = "forever",
|
||||
seconds: int = 0, video: str = "ntsc") -> bytes:
|
||||
if len(data) != viewer.PATTERN_BYTES + viewer.NCELLS:
|
||||
raise ValueError(f"unexpected data length {len(data)}")
|
||||
name = _ascii(title, 16)
|
||||
|
||||
vk = dict(display=display, seconds=seconds, video=video)
|
||||
entry = _even(PROG_LIST + 2 + 2 + 1 + len(name)) # code starts after the name
|
||||
code = viewer.build(entry, 0, **vk) # pass 1: measure length
|
||||
data_addr = _even(entry + len(code))
|
||||
code = viewer.build(entry, data_addr, **vk) # pass 2: real data address
|
||||
|
||||
if data_addr - CART_BASE + len(data) > ROM_SIZE:
|
||||
raise ValueError("image + viewer exceed 8KB cartridge")
|
||||
|
||||
rom = bytearray(b"\x00" * ROM_SIZE)
|
||||
|
||||
def put(addr, payload):
|
||||
off = addr - CART_BASE
|
||||
rom[off:off + len(payload)] = payload
|
||||
|
||||
def putw(addr, word):
|
||||
put(addr, bytes([(word >> 8) & 0xFF, word & 0xFF]))
|
||||
|
||||
# cartridge header
|
||||
rom[0] = 0xAA # valid
|
||||
rom[1] = 0x01 # version
|
||||
putw(0x6006, PROG_LIST)
|
||||
|
||||
# program-list entry (single item)
|
||||
putw(PROG_LIST + 0, 0x0000) # no next entry
|
||||
putw(PROG_LIST + 2, entry) # viewer entry point
|
||||
rom[PROG_LIST + 4 - CART_BASE] = len(name)
|
||||
put(PROG_LIST + 5, name)
|
||||
|
||||
put(entry, code)
|
||||
put(data_addr, data)
|
||||
return bytes(rom)
|
||||
|
||||
|
||||
def write_rpk(rom: bytes, path: str, title: str = "photo"):
|
||||
"""Write an MAME RPK (zip with a standard single-ROM layout)."""
|
||||
binname = "viewer.bin"
|
||||
layout = (
|
||||
'<?xml version="1.0" encoding="utf-8"?>\n'
|
||||
'<romset version="1.0">\n'
|
||||
' <resources>\n'
|
||||
f' <rom id="romimage" file="{binname}"/>\n'
|
||||
' </resources>\n'
|
||||
' <configuration>\n'
|
||||
' <pcb type="standard">\n'
|
||||
' <socket id="rom_socket" uses="romimage"/>\n'
|
||||
' </pcb>\n'
|
||||
' </configuration>\n'
|
||||
'</romset>\n'
|
||||
)
|
||||
with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as z:
|
||||
z.writestr(binname, rom)
|
||||
z.writestr("layout.xml", layout)
|
||||
Loading…
Add table
Add a link
Reference in a new issue