First public commit.

This commit is contained in:
The Dust Council 2026-07-03 19:35:35 -07:00
parent 2a48f52979
commit 4bac9d83ed
288 changed files with 18417 additions and 1076 deletions

View file

@ -0,0 +1 @@
"""C128 8502/VDC viewer (assembled by xa)."""

View file

@ -0,0 +1,122 @@
"""Assemble the C128 VDC viewer with `xa` and build the loadable PRG.
The PRG loads at the C128 BASIC start ($1C01): a tiny BASIC stub (`10 SYS7200`)
followed by the 8502 viewer (at $1C20) and, from $2000, the 640x200 bitmap.
Running it (RUN"PIC") executes the stub, which SYSes the viewer.
"""
from __future__ import annotations
import os
import shutil
import subprocess
import tempfile
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
BASIC_START = 0x1C01
ML_ORG = 0x1C20
DATA_ORG = 0x2000 # bitmap goes here (viewer copies it to the VDC)
# BASIC: 10 SYS7200 ($1C20 = 7200) -- bytes as they sit from $1C01
_STUB = bytes([0x0B, 0x1C, 0x0A, 0x00, 0x9E,
0x37, 0x32, 0x30, 0x30, 0x00, 0x00, 0x00])
class AssemblerError(RuntimeError):
pass
def have_xa() -> bool:
return shutil.which("xa") is not None
def _xa(wrapper: str) -> bytes:
"""Assemble a generated wrapper (xa runs in VIEWER_DIR so #includes resolve)."""
if not have_xa():
raise AssemblerError("The 'xa' (xa65) assembler was not found on PATH.\n"
"Install it with: sudo apt install xa65")
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:
return f.read()
finally:
os.unlink(wrap)
def _assemble(fgbg: int, source: str) -> bytes:
return _xa(f"#define SRC ${DATA_ORG:04X}\n"
f"#define FGBG ${fgbg & 0xFF:02X}\n"
f'#include "{source}"\n')
def _wrap_prg(code: bytes, data: bytes) -> bytes:
"""Lay out load-address prefix + BASIC stub + viewer code + data @ $2000."""
mem = bytearray()
mem += _STUB # $1C01..
mem += b"\x00" * (ML_ORG - BASIC_START - len(_STUB))
mem += code # $1C20..
if len(mem) > DATA_ORG - BASIC_START:
raise AssemblerError("viewer code overruns the $2000 data area")
mem += b"\x00" * (DATA_ORG - BASIC_START - len(mem))
mem += data # $2000..
return bytes([BASIC_START & 0xFF, BASIC_START >> 8]) + bytes(mem)
def build_prg_color(attributes: bytes, fgbg: int = 0x0F) -> bytes:
"""Return the loadable PRG for the 80x100 chunky-colour viewer.
`attributes` is the 8000-byte per-cell colour map (colour in the high
nibble); the viewer fills the character matrix with a solid glyph itself.
"""
return _wrap_prg(_assemble(fgbg, "color.s"), attributes)
def build_prg_hicolor(vdc_image: bytes, fgbg: int = 0x00) -> bytes:
"""Return the loadable PRG for the 640x200 custom-charset viewer (font mode).
Used by both the `hicolor` and `mono` modes. `vdc_image` is the full VDC RAM
image (character codes, attributes and the custom character set already laid
out); `fgbg` carries the global background in its low nibble. The viewer
copies the image verbatim into VDC RAM.
"""
return _wrap_prg(_assemble(fgbg, "hicolor.s"), vdc_image)
_SS_WAITMODE = {"key": 1, "seconds": 2, "both": 3}
def build_slideshow_prg(fgbg_list, advance: str = "both", seconds: int = 10,
loop: bool = True, video: str = "pal") -> bytes:
"""Return the bootable C128 slideshow viewer PRG (RUN\"PIC\").
``fgbg_list`` is one VDC R26 background byte per image (conv.meta["fgbg"]);
the per-image pictures are separate "00".."NN" files the viewer loads. The
viewer is code-only (no appended image) -- just the BASIC stub + the 8502
loop + the ss_fgbg table.
"""
if not fgbg_list:
raise AssemblerError("a slideshow needs at least one image")
jiffyps = 60 if video == "ntsc" else 50
table = ",".join(str(int(b) & 0xFF) for b in fgbg_list)
code = _xa(
f"#define WAITMODE {_SS_WAITMODE[advance]}\n"
f"#define WAITSECS {max(0, int(seconds))}\n"
f"#define JIFFYPS {jiffyps}\n"
f"#define NIMAGES {len(fgbg_list)}\n"
f"#define LOOPFLAG {1 if loop else 0}\n"
'#include "slideshow.s"\n'
"ss_fgbg:\n"
f" .byte {table}\n")
mem = bytearray()
mem += _STUB # $1C01 BASIC stub (10 SYS7200)
mem += b"\x00" * (ML_ORG - BASIC_START - len(_STUB))
mem += code # $1C20 viewer + ss_fgbg table
return bytes([BASIC_START & 0xFF, BASIC_START >> 8]) + bytes(mem)

173
lenser/c128/viewer/color.s Normal file
View file

@ -0,0 +1,173 @@
; Commodore 128 VDC (8563) 80x100 chunky-colour image viewer.
;
; MAME's 8563 renders the bitmap through the character/font path- each display
; byte is a CHARACTER CODE whose glyph is drawn with fg = attribute high nibble,
; bg = attribute low nibble. So a per-cell SOLID colour image is produced by
; filling the whole character matrix with $FF (a solid glyph) and giving every
; cell its colour in the high nibble of its attribute byte.
;
; R9=1 (2 scan lines per char row) makes 80x100 cells- 8000 char bytes + 8000
; attribute bytes = 16000, which fits the stock 16K VDC RAM.
;
; #defines from viewer/assemble.py --
; SRC main-RAM address of the 8000 attribute bytes ($2000)
; FGBG VDC register 26 default value (unused cells / background)
* = $1C20
src = $fb
adlo = $fd
adhi = $fe
cntl = $02
cnth = $03
fill = $04
start:
sei
lda #$0e
sta $ff00 ; bank in RAM $4000-$BFFF, keep I/O for the VDC
lda #<nmi
sta $0318
lda #>nmi
sta $0319 ; neutralise NMI (not masked by SEI)
jsr setregs
; fill the character matrix VDC $0000 with $FF (solid glyph), 8000 bytes
lda #$00
sta adlo
sta adhi
lda #$ff
sta fill
lda #<8000
sta cntl
lda #>8000
sta cnth
jsr fillblk
; copy 8000 attribute bytes main SRC -> VDC $2000
lda #<SRC
sta src
lda #>SRC
sta src+1
lda #$00
sta adlo
lda #$20
sta adhi
lda #<8000
sta cntl
lda #>8000
sta cnth
jsr copyblk
; enable bitmap + attributes (display the picture)
ldx #25
lda #$c0
jsr vdcw
loop:
jmp loop
nmi:
rti
; program the VDC display geometry + attribute base (everything except R25)
setregs:
ldx #9
lda #$01
jsr vdcw ; R9 = 1 -> 2 scan lines per char row (80x100)
ldx #4
lda #131
jsr vdcw ; R4 vertical total -> (131+1)*2 = 264 lines
ldx #5
lda #$00
jsr vdcw ; R5 vertical total adjust
ldx #6
lda #100
jsr vdcw ; R6 vertical displayed = 100 rows (*2 = 200)
ldx #7
lda #116
jsr vdcw ; R7 vsync position
ldx #20
lda #$20
jsr vdcw ; R20 attribute base high = $2000
ldx #21
lda #$00
jsr vdcw ; R21 attribute base low
ldx #26
lda #FGBG
jsr vdcw ; R26 default fg/bg
ldx #10
lda #$20
jsr vdcw ; R10 cursor off (bit5) - hide the blinking cursor
ldx #12
lda #$00
jsr vdcw ; R12 display start high
ldx #13
lda #$00
jsr vdcw ; R13 display start low
rts
; fill cnt bytes = (fill) into VDC RAM from adhi/adlo (explicit address per byte)
fillblk:
ldx #18
lda adhi
jsr vdcw
ldx #19
lda adlo
jsr vdcw
ldx #31
lda fill
jsr vdcw
inc adlo
bne fb1
inc adhi
fb1:
lda cntl
bne fb2
dec cnth
fb2:
dec cntl
lda cntl
ora cnth
bne fillblk
rts
; copy cnt bytes from (src) into VDC RAM from adhi/adlo (explicit address per byte)
copyblk:
ldx #18
lda adhi
jsr vdcw
ldx #19
lda adlo
jsr vdcw
ldx #31
ldy #$00
lda (src),y
jsr vdcw
inc src
bne cb1
inc src+1
cb1:
inc adlo
bne cb2
inc adhi
cb2:
lda cntl
bne cb3
dec cnth
cb3:
dec cntl
lda cntl
ora cnth
bne copyblk
rts
; write A to VDC register X
vdcw:
stx $d600
vw:
bit $d600
bpl vw
sta $d601
rts

View file

@ -0,0 +1,92 @@
; Commodore 128 VDC (8563) 640x200 high-colour viewer (custom character set).
;
; Uses the VDC's normal 80-column CHARACTER mode (R25 left at the C128 default:
; font + attribute, bit7=0) with a per-image custom font. Each 8x8 cell draws
; its glyph in a per-cell INK colour (attribute low nibble) over a single GLOBAL
; background (VDC register 26 low nibble) -> 640x200 detail with colour.
;
; Python lays out the whole VDC RAM image in main RAM from $2000 (codes @ $0000,
; attributes @ $0800, character set bank 0 @ $2000, bank 1 @ $3000); this copies
; the full 16K verbatim into the VDC's own RAM with an explicit address per byte.
;
; #defines from viewer/assemble.py --
; SRC main-RAM address of the VDC image ($2000)
; FGBG VDC register 26 value (global background in the low nibble)
* = $1C20
src = $fb
adlo = $fd
adhi = $fe
cntl = $02
cnth = $03
start:
sei
lda #$0e
sta $ff00 ; bank in RAM $4000-$BFFF, keep I/O for the VDC
lda #<nmi
sta $0318
lda #>nmi
sta $0319 ; neutralise NMI (not masked by SEI)
lda #<SRC ; copy $4000 (16384) bytes main SRC -> VDC $0000
sta src
lda #>SRC
sta src+1
lda #$00
sta adlo
sta adhi
lda #$00
sta cntl
lda #$40
sta cnth
jsr copyblk
ldx #26
lda #FGBG
jsr vdcw ; R26 global background (low nibble)
ldx #10
lda #$20
jsr vdcw ; cursor off
; R25 deliberately untouched (C128 default = font + attribute mode)
loop:
jmp loop
nmi:
rti
copyblk:
ldx #18
lda adhi
jsr vdcw
ldx #19
lda adlo
jsr vdcw
ldx #31
ldy #$00
lda (src),y
jsr vdcw
inc src
bne cb1
inc src+1
cb1:
inc adlo
bne cb2
inc adhi
cb2:
lda cntl
bne cb3
dec cnth
cb3:
dec cntl
lda cntl
ora cnth
bne copyblk
rts
vdcw:
stx $d600
vw:
bit $d600
bpl vw
sta $d601
rts

View file

@ -0,0 +1,211 @@
; Commodore 128 VDC (8563) slideshow viewer -- 640x200 high-colour mode.
;
; Steps through NIMAGES VDC images named "00".."NN" on the disk, each a 16K VDC
; RAM image (codes/attributes/font, the same body the single hicolor/mono viewer
; copies). For each slide it KERNAL-loads the file into RAM bank 0 at $4000,
; copies the 16K verbatim into the VDC's own RAM, sets the global background
; (R26) from the per-image ss_fgbg table, then waits (key / seconds / both)
; before advancing. Boots via RUN"PIC" -> the BASIC stub SYSes here.
;
; #defines from the build wrapper --
; WAITMODE 1 key / 2 seconds / 3 both WAITSECS timeout seconds
; JIFFYPS 50 PAL / 60 NTSC NIMAGES image count
; LOOPFLAG 1 wrap at end, 0 stop
; ss_fgbg (one byte per image, VDC R26 background) is appended by the wrapper.
* = $1C20
src = $fb
adlo = $fd
adhi = $fe
cntl = $02
cnth = $03
start:
lda #$0e
sta $ff00 ; KERNAL + I/O + RAM $4000-$BFFF all visible
lda #$00
sta $9d ; suppress KERNAL LOAD messages
sta ssidx
mainloop:
jsr namebuild
; ---- C128 LOAD "NN",8,1 into RAM bank 0 at $4000 ----
lda #$00
ldx #$00
jsr $ff68 ; SETBNK (load bank 0, name bank 0)
lda #2
ldx #<ssname
ldy #>ssname
jsr $ffbd ; SETNAM
lda #1
ldx #8
ldy #1
jsr $ffba ; SETLFS (secondary 1 = file's own address)
lda #0
jsr $ffd5 ; LOAD
; ---- copy 16384 bytes from $4000 -> VDC $0000 ----
lda #$00
sta src
lda #$40
sta src+1
lda #$00
sta adlo
sta adhi
sta cntl
lda #$40
sta cnth
jsr copyblk
; ---- per-image global background (R26) + cursor off (R10) ----
ldx ssidx
lda ss_fgbg,x
ldx #26
jsr vdcw
ldx #10
lda #$20
jsr vdcw
; ---- wait for the slide's dwell ----
jsr ss_wait
; ---- advance ----
inc ssidx
lda ssidx
cmp #NIMAGES
bcc mainloop
#if LOOPFLAG == 1
lda #$00
sta ssidx
jmp mainloop
#else
rts
#endif
; --------------------------------------------------------------------------- ;
; wait -- key / seconds / both, KERNAL GETIN + jiffy clock ($a0-$a2, $a2 = LSB)
; (reuses $fb-$fe as 16-bit scratch now the copy is done)
cv_t0 = $fb
cv_el = $fd
ss_wait:
#if WAITMODE == 1
ss_drain:
jsr $ffe4
bne ss_drain ; flush leftover keys
ss_k:
jsr $ffe4
beq ss_k
rts
#endif
#if WAITMODE == 2
lda $a2
sta cv_t0
lda $a1
sta cv_t0+1
ss_s:
sec
lda $a2
sbc cv_t0
sta cv_el
lda $a1
sbc cv_t0+1
sta cv_el+1
lda cv_el+1
cmp #>(WAITSECS*JIFFYPS)
bcc ss_s
bne ss_sd
lda cv_el
cmp #<(WAITSECS*JIFFYPS)
bcc ss_s
ss_sd:
rts
#endif
#if WAITMODE == 3
ss_drain:
jsr $ffe4
bne ss_drain
lda $a2
sta cv_t0
lda $a1
sta cv_t0+1
ss_b:
jsr $ffe4
bne ss_bd ; any key ends the slide
sec
lda $a2
sbc cv_t0
sta cv_el
lda $a1
sbc cv_t0+1
sta cv_el+1
lda cv_el+1
cmp #>(WAITSECS*JIFFYPS)
bcc ss_b
bne ss_bd
lda cv_el
cmp #<(WAITSECS*JIFFYPS)
bcc ss_b
ss_bd:
rts
#endif
; build the 2-char filename "NN" (PETSCII) from ssidx (0..99)
namebuild:
lda ssidx
ldx #$2f
sec
nb_ten:
inx
sbc #10
bcs nb_ten
adc #10
ora #$30
sta ssname+1
txa
sta ssname
rts
; copy cnth/cntl bytes from (src) to VDC RAM starting at adhi/adlo
copyblk:
ldx #18
lda adhi
jsr vdcw
ldx #19
lda adlo
jsr vdcw
ldx #31
ldy #$00
lda (src),y
jsr vdcw
inc src
bne cb1
inc src+1
cb1:
inc adlo
bne cb2
inc adhi
cb2:
lda cntl
bne cb3
dec cnth
cb3:
dec cntl
lda cntl
ora cnth
bne copyblk
rts
vdcw:
stx $d600
vw:
bit $d600
bpl vw
sta $d601
rts
ssidx: .byte 0
ssname: .byte $30,$30
; ss_fgbg table (one byte per image) appended by the build wrapper