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

View file

@ -0,0 +1,82 @@
"""Assemble the IIGS SHR boot/viewer with `xa` (origin $0800, raw bytes)."""
from __future__ import annotations
import os
import shutil
import subprocess
import tempfile
VIEWER_DIR = os.path.dirname(os.path.abspath(__file__))
class AssemblerError(RuntimeError):
pass
def have_xa() -> bool:
return shutil.which("xa") is not None
def assemble_boot() -> bytes:
"""Return the boot0 sector (<=256 bytes) that loads the SHR block + shows it."""
if not have_xa():
raise AssemblerError("The 'xa' assembler was not found on PATH.")
with tempfile.TemporaryDirectory() as td:
out = os.path.join(td, "v.bin")
proc = subprocess.run(["xa", "-w", "-o", out, "iigs.s"],
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:
boot = f.read()
if len(boot) > 256:
raise AssemblerError(f"boot sector is {len(boot)} bytes (>256)")
return boot
SS_WAITMODE = {"key": 1, "seconds": 2, "both": 3}
def _xa_wrapper(wrapper: str, what: str) -> bytes:
if not have_xa():
raise AssemblerError("The 'xa' assembler was not found on PATH.")
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", "-w", "-o", out, os.path.basename(wrap)],
capture_output=True, text=True, cwd=VIEWER_DIR)
if proc.returncode != 0:
raise AssemblerError(f"xa failed for {what}:\n{proc.stdout}{proc.stderr}")
with open(out, "rb") as f:
return f.read()
finally:
os.unlink(wrap)
def build_slideshow(n_images: int, advance: str = "both", seconds: int = 10,
loop: bool = True, video: str = "ntsc"):
"""Assemble the two-stage IIGS SHR slideshow.
Returns (boot_sector<=256B, stage2_bytes, stage2_pages). The boot reads
stage2 (stage2_pages sectors) into $0900, then the images into bank-0 $2000
and banks them; stage2 cycles them onto the SHR screen.
"""
speed = 8 if video != "pal" else 7 # ~1s delay outer count (fast IIGS)
stage2 = _xa_wrapper(
f"#define WAITMODE {SS_WAITMODE[advance]}\n"
f"#define WAITSECS {max(0, min(255, int(seconds)))}\n"
f"#define SPEED {speed}\n"
f"#define NIMAGES {n_images}\n"
f"#define LOOPFLAG {1 if loop else 0}\n"
'#include "slideshow_stage2.s"\n', "iigs stage2")
s2_pages = max(1, (len(stage2) + 255) // 256)
boot = _xa_wrapper(
f"#define NIMAGES {n_images}\n"
f"#define S2END ${0x09 + s2_pages:02X}\n"
'#include "slideshow_boot.s"\n', "iigs boot")
if len(boot) > 256:
raise AssemblerError(f"IIGS slideshow boot is {len(boot)} bytes (>256)")
return boot, stage2, s2_pages

116
lenser/iigs/viewer/iigs.s Normal file
View file

@ -0,0 +1,116 @@
; lenser -- Apple IIGS Super Hi-Res boot loader + viewer (8-bit, no GS/OS).
;
; The Disk II boot ROM (slot 6) loads track 0 sector 0 to $0800 and JMPs $0801.
; This re-entrant loader reads the 32K SHR block (pixels $2000-$9CFF, SCBs
; $9D00, palettes $9E00) into main RAM $2000-$9FFF, then copies it to AUX RAM
; with SHR shadowing enabled -- so the writes mirror into bank $E1 (the SHR
; screen) -- and turns SHR on (NEWVIDEO bit 7). All 8-bit, no 65C816 needed.
* = $0800
.byte $01 ; ROM scratch (boot sector byte 0)
entry: ; $0801, re-entered after every ROM read
lda dpage
cmp #$a0
bcs done ; loaded $2000..$9FFF -> show it
lda psec
cmp #$10
bcc readit ; sectors left on this track
jsr seeknext ; finished a track -> step to the next
lda #$00
sta psec
readit:
lda psec
sta $3d ; desired sector
lda curtrk
sta $41 ; desired track
lda #$00
sta $26 ; buffer lo
lda dpage
sta $27 ; buffer hi
inc psec
inc dpage
ldx $2b ; slot*16 (set by boot ROM)
jmp $c65c ; slot 6 ROM read; reads sector then JMP $0801
done:
; Turn on Super Hi-Res (bit 7) AND linearize (bit 6) FIRST. With these
; set, CPU writes to bank $E1 $2000-$9FFF are de-interleaved by hardware
; into the megaII layout the video reads -- so we can copy our linear SHR
; block straight in and it displays correctly.
lda $c029
ora #$c0
sta $c029
; copy 32K from bank $00 $2000 -> bank $E1 $2000 (65C816 block move)
clc
xce ; -> native mode
rep #$30 ; 16-bit A/X/Y
.al
.xl
lda #$7fff ; count-1 (32768 bytes)
ldx #$2000 ; source offset
ldy #$2000 ; dest offset
.byte $54, $e1, $00 ; MVN src bank $00 -> dest bank $E1
sep #$30 ; 8-bit
.as
.xs
sec
xce ; -> emulation mode
lda $c034 ; border colour is the low nibble of $C034
and #$f0
sta $c034 ; black border
hang:
jmp hang
; advance the head one track (two half-steps), phase-overlap step.
seeknext:
inc curtrk
lda #$00
sta $0a
jsr onestep
jsr onestep
lda halftrk
and #$03
asl
ora $2b
tax
lda $c080,x
rts
onestep:
lda halftrk
clc
adc #$01
and #$03
asl
ora $2b
tax
lda $c081,x
ldx $0a
lda ontable,x
jsr wait
lda halftrk
and #$03
asl
ora $2b
tax
lda $c080,x
ldx $0a
lda offtable,x
jsr wait
inc halftrk
inc $0a
rts
ontable: .byte $13,$0a,$08,$06
offtable: .byte $46,$1a,$10,$0c
wait:
tay
w1: ldx #$00
w2: dex
bne w2
dey
bne w1
rts
psec: .byte $01
dpage: .byte $20
halftrk: .byte $00
curtrk: .byte $00

View file

@ -0,0 +1,115 @@
; lenser -- Apple IIGS SHR slideshow, stage 1 boot sector (<=256 bytes).
;
; The Disk II boot ROM loads track 0 sector 0 to $0800 and JMPs $0801; every ROM
; sector read JMPs back to $0801, so this is the master read driver. It first
; reads stage 2 into $0900 (S2END-$09 sectors), then reads each 32K SHR image
; into bank-0 $2000-$9FFF and calls stage 2 (stash, $0900) to bank it; after the
; last image it JMPs the stage-2 cycle viewer ($0903).
;
; #defines from the wrapper -- NIMAGES, S2END (one past the last stage-2 page).
* = $0800
.byte $01 ; ROM scratch (boot sector byte 0)
entry: ; $0801, re-entered after every ROM read
lda dpage
cmp endpg
bcc rdcont ; not at the current region's end yet
lda phase
bne imgdone
; stage 2 finished loading -> start reading images into $2000
inc phase
lda #$20
sta dpage
lda #$a0
sta endpg
bne rdcont ; always (endpg = $a0)
imgdone:
jsr $0900 ; stash bank0 $2000 -> next bank
inc imgcnt
lda imgcnt
cmp #NIMAGES
bcs allloaded
lda #$20
sta dpage ; reset for the next image
bne rdcont ; always
allloaded:
jmp $0903 ; stage-2 cycle viewer (never returns)
rdcont:
lda psec
cmp #$10
bcc readit
jsr seeknext
lda #$00
sta psec
readit:
lda psec
sta $3d
lda curtrk
sta $41
lda #$00
sta $26
lda dpage
sta $27
inc psec
inc dpage
ldx $2b
jmp $c65c ; slot-6 ROM read; reads a sector then JMP $0801
; advance the head one track (two half-steps), phase-overlap step (from iigs.s)
seeknext:
inc curtrk
lda #$00
sta $0a
jsr onestep
jsr onestep
lda halftrk
and #$03
asl
ora $2b
tax
lda $c080,x
rts
onestep:
lda halftrk
clc
adc #$01
and #$03
asl
ora $2b
tax
lda $c081,x
ldx $0a
lda ontable,x
jsr wait
lda halftrk
and #$03
asl
ora $2b
tax
lda $c080,x
ldx $0a
lda offtable,x
jsr wait
inc halftrk
inc $0a
rts
ontable: .byte $13,$0a,$08,$06
offtable: .byte $46,$1a,$10,$0c
wait:
tay
w1:
ldx #$00
w2:
dex
bne w2
dey
bne w1
rts
phase: .byte 0 ; 0 = loading stage 2, 1 = loading images
endpg: .byte S2END ; stage-2 ends one past this page
imgcnt: .byte 0
psec: .byte $01 ; next physical sector (track 0 starts at 1)
dpage: .byte $09 ; stage 2 loads from $0900
halftrk: .byte $00
curtrk: .byte $00

View file

@ -0,0 +1,147 @@
; lenser -- Apple IIGS SHR slideshow, stage 2 (loaded at $0900 by the boot).
;
; The boot reads each 32K SHR image into bank-0 $2000-$9FFF and calls stash to
; block-move it up into a free RAM bank (image i -> bank 1+i, offset $2000).
; Once all images are stashed the boot JMPs cycle, which block-moves each banked
; image into the SHR screen (bank $E1 $2000) in turn, waiting (key/seconds/both)
; between slides. Self-modifying MVN bank bytes select the bank per image.
;
; #defines from the wrapper -- WAITMODE 1 key/2 seconds/3 both, WAITSECS, SPEED
; (delay outer count ~1s at the IIGS clock), NIMAGES, LOOPFLAG.
* = $0900
jmp stash ; $0900 entry (called per loaded image)
jmp cycle ; $0903 entry (called once, all images loaded)
; ---- MVN bank0 $2000 -> curbank $2000 (32K), then advance curbank ----
stash:
clc
xce
rep #$30
.al
.xl
lda #$7fff
ldx #$2000
ldy #$2000
.byte $54 ; MVN dest, src
mvndst: .byte $01 ; dest bank (patched each call)
.byte $00 ; src bank 0
.byte $4b ; phk
.byte $ab ; plb -- restore DBR = 0 (MVN left it = dest bank)
sep #$30
.as
.xs
sec
xce
inc mvndst ; next image -> next bank
rts
; ---- show each banked image on the SHR screen, forever ----
cycle:
lda $c029
ora #$c0
sta $c029 ; SHR on + linearise
lda $c034
and #$f0
sta $c034 ; black border
lda #$01
sta mvnsrc
lda #$00
sta cyci
cloop:
clc
xce
rep #$30
.al
.xl
lda #$7fff
ldx #$2000
ldy #$2000
.byte $54, $e1 ; MVN dest $E1, src ...
mvnsrc: .byte $01 ; src bank (patched per slide)
.byte $4b ; phk
.byte $ab ; plb -- restore DBR = 0
sep #$30
.as
.xs
sec
xce
jsr sswait
inc cyci
lda cyci
cmp #NIMAGES
bcc cnext
#if LOOPFLAG == 1
lda #$01
sta mvnsrc
lda #$00
sta cyci
jmp cloop
#else
jmp $e000 ; (no loop) cold start to firmware
#endif
cnext:
inc mvnsrc
jmp cloop
; ---- wait (returns); $C000 bit7 = key, delay loop for the timer ----
sswait:
bit $c010
#if WAITMODE == 1
swk:
lda $c000
bpl swk
bit $c010
rts
#endif
#if WAITMODE == 2
lda #WAITSECS
sta wsec
wso:
lda #SPEED
sta wmid
wsm:
ldx #$00
wsx:
ldy #$00
wsy:
dey
bne wsy
dex
bne wsx
dec wmid
bne wsm
dec wsec
bne wso
rts
#endif
#if WAITMODE == 3
lda #WAITSECS
sta wsec
wbo:
lda #SPEED
sta wmid
wbm:
lda $c000
bmi wbk ; a key ends the slide
ldx #$00
wbx:
ldy #$00
wby:
dey
bne wby
dex
bne wbx
dec wmid
bne wbm
dec wsec
bne wbo
rts
wbk:
bit $c010
rts
#endif
wsec: .byte 0
wmid: .byte 0
cyci: .byte 0