"""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 = ( '\n' '\n' ' \n' f' \n' ' \n' ' \n' ' \n' ' \n' ' \n' ' \n' '\n' ) with zipfile.ZipFile(path, "w", zipfile.ZIP_DEFLATED) as z: z.writestr(binname, rom) z.writestr("layout.xml", layout)