8bitlenser/lenser/coco3/viewer.py
2026-07-03 19:35:35 -07:00

70 lines
2.8 KiB
Python

"""CoCo 3 GIME hi-res viewer (Motorola 6809 machine code, Program Pak ROM).
Autostarts from the cartridge at $C000 in CoCo-compatible mode, then switches the
GIME into a native graphics mode. Because turning on GIME mode (writing $FF90)
can change the memory map under the running code, the setup runs in two stages:
1. At $C000 (cart ROM): mask interrupts, copy the 15360-byte image down to RAM
at $4000, program the GIME palette + mode/geometry/video-base registers
(these are safe while still in legacy mode), then plant a tiny stub in RAM.
2. Jump to the RAM stub, which writes $FF90 (COCO=0 -> GIME graphics on) and
idles. Running from RAM means the map change can't pull the code out from
under us.
The GIME shows a LINEAR 80-byte x 192-row screen from physical video base
$14000, which is exactly where CPU $4000 lives with the MMU disabled.
"""
from __future__ import annotations
from ..coco.mc6809 import Asm
CART_BASE = 0xC000
IMG_CPU = 0x4000 # image copied here (RAM, visible to GIME video)
IMG_LEN = 15360 # 80 bytes/row * 192 rows
IMG_END = IMG_CPU + IMG_LEN # $7C00
STUB_CPU = 0x3E00 # tiny "enable GIME + idle" stub (RAM)
# physical video base for CPU $4000 with the MMU disabled. CoCo 3 maps the 64K
# CPU space to blocks $38-$3F (bank+0x38); on the 512K machine bank 2 ($4000) is
# block $3A = physical $74000. base = FF9D<<11 | FF9E<<3.
FF9D = 0xE8 # $74000 >> 11
FF9E = 0x00
def build(data_src: int, cres: int, inks, border: int) -> bytes:
"""data_src: cart address of the image bytes; cres: GIME colour-resolution
bits (0=2col, 1=4col, 2=16col); inks: pen->6-bit-colour list; border: 6-bit."""
a = Asm(CART_BASE)
a.orcc(0x50) # mask IRQ + FIRQ
# copy the image from cart ROM down to RAM at $4000
a.ldx_imm(data_src)
a.ldu_imm(IMG_CPU)
a.label("copy")
a.lda_postinc("x")
a.sta_postinc("u")
a.cmpu_imm(IMG_END)
a.bne("copy")
# GIME palette: 16 pen registers $FFB0-$FFBF (unused pens -> 0)
for pen in range(16):
a.lda_imm(inks[pen] if pen < len(inks) else 0)
a.sta_ext(0xFFB0 + pen)
a.lda_imm(border & 0x3F)
a.sta_ext(0xFF9A) # border colour
a.lda_imm(0x80)
a.sta_ext(0xFF98) # VMODE: graphics, 1 line/row
a.lda_imm(0x14 | (cres & 0x03))
a.sta_ext(0xFF99) # VRES: 192 lines, 80 bytes/row, CRES
a.lda_imm(FF9D)
a.sta_ext(0xFF9D) # video base hi
a.lda_imm(FF9E)
a.sta_ext(0xFF9E) # video base lo
# plant the RAM stub: lda #0 ; sta $FF90 ; bra * (enable GIME, then idle)
for off, byte in enumerate((0x86, 0x00, 0xB7, 0xFF, 0x90, 0x20, 0xFE)):
a.lda_imm(byte)
a.sta_ext(STUB_CPU + off)
a.jmp_ext(STUB_CPU)
return a.resolve()