68 lines
2.7 KiB
Python
68 lines
2.7 KiB
Python
"""Read/write C64 .crt cartridge images (CCS64/VICE format) for a 16K ROM at $8000.
|
|
|
|
VICE and MAME disagree on how a 16K Generic cart must be described:
|
|
* VICE attaches it only as ONE 16K CHIP packet at $8000 (two 8K packets are
|
|
rejected with "Failed to attach image").
|
|
* MAME maps each CHIP at its own load address and treats a single 16K CHIP as
|
|
8K -- it needs TWO 8K packets (ROML $8000 + ROMH $A000) to map $A000-$BFFF.
|
|
So `split=False` (default) targets VICE; the MAME preview path uses `split=True`.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import struct
|
|
|
|
CART_SIZE = 0x4000
|
|
|
|
|
|
def _chip(load_addr: int, payload: bytes) -> bytes:
|
|
c = bytearray(16)
|
|
c[0:4] = b"CHIP"
|
|
struct.pack_into(">I", c, 4, 16 + len(payload)) # packet length
|
|
struct.pack_into(">H", c, 8, 0x0000) # chip type: ROM
|
|
struct.pack_into(">H", c, 10, 0x0000) # bank
|
|
struct.pack_into(">H", c, 12, load_addr)
|
|
struct.pack_into(">H", c, 14, len(payload))
|
|
return bytes(c) + payload
|
|
|
|
|
|
def write_crt(rom: bytes, path: str, name: str = "8bitlenser",
|
|
split: bool = False) -> str:
|
|
"""Wrap a 16K cart `rom` ($8000-$BFFF) in a .crt (16K config: EXROM & GAME
|
|
both 0). ``split`` False = one 16K CHIP (VICE); True = two 8K CHIPs (MAME)."""
|
|
if len(rom) != CART_SIZE:
|
|
raise ValueError(f"expected a 16K cart ROM, got {len(rom)} bytes")
|
|
|
|
header = bytearray(64)
|
|
header[0:16] = b"C64 CARTRIDGE "
|
|
struct.pack_into(">I", header, 16, 64) # header length
|
|
struct.pack_into(">H", header, 20, 0x0100) # version 1.0
|
|
struct.pack_into(">H", header, 22, 0x0000) # hardware type: normal/generic
|
|
header[24] = 0 # EXROM line (asserted)
|
|
header[25] = 0 # GAME line (asserted) -> 16K
|
|
nm = name.encode("ascii", "replace")[:32]
|
|
header[32:32 + len(nm)] = nm
|
|
|
|
with open(path, "wb") as f:
|
|
f.write(header)
|
|
if split:
|
|
f.write(_chip(0x8000, rom[:0x2000])) # ROML
|
|
f.write(_chip(0xA000, rom[0x2000:])) # ROMH
|
|
else:
|
|
f.write(_chip(0x8000, rom)) # one 16K CHIP
|
|
return path
|
|
|
|
|
|
def read_rom(path: str) -> bytes:
|
|
"""Extract the raw 16K cart ROM from a .crt (handles one- or two-CHIP)."""
|
|
with open(path, "rb") as f:
|
|
data = f.read()
|
|
hdr_len = struct.unpack_from(">I", data, 16)[0]
|
|
rom = bytearray()
|
|
pos = hdr_len
|
|
while pos + 16 <= len(data) and data[pos:pos + 4] == b"CHIP":
|
|
plen = struct.unpack_from(">I", data, pos + 4)[0]
|
|
size = struct.unpack_from(">H", data, pos + 14)[0]
|
|
rom += data[pos + 16:pos + 16 + size]
|
|
pos += plen
|
|
return bytes(rom)
|