#!/usr/bin/env bash # Launch the built Apple IIgs examples. # # scripts/run-iigs.sh GSplus + Finder: boots GS/OS with # joey.2mg mounted; pick the demo by # double-clicking it in Finder. # scripts/run-iigs.sh staxi MAME auto-launch: builds a # single-example run disk # (joey-run.2mg -- joey.2mg is left # alone so benches stay honest), # boots GS/OS at real speed, and # drives Finder via Lua to launch # the example. No clicking needed. # GS/OS itself takes ~50 s to boot; # that part is authentic. # # The MAME path needs the apple2gs ROM set (default ~/.mame/roms, # override with MAME_ROMPATH). MAME_HEADLESS=1 runs it windowless with # periodic PNG snapshots (batch verification). set -euo pipefail repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) bin_dir=$repo/build/iigs/bin sys_disk=$repo/toolchains/emulators/support/gsos-system.po if [[ $# -gt 1 ]]; then echo "usage: $0 [example-name]" >&2 exit 2 fi # ---------------------------------------------------------------- MAME # Example given: auto-launching MAME path. if [[ $# -eq 1 ]]; then target=${1^^} if [[ ! -f "$bin_dir/$target" ]]; then echo "$bin_dir/$target not built. Run 'make iigs' first." >&2 if compgen -G "$bin_dir/*" > /dev/null; then echo "available examples in $bin_dir:" >&2 find "$bin_dir" -maxdepth 1 -type f -printf ' %f\n' \ | grep -vE '\.2mg$|\.txt$' >&2 || true fi exit 1 fi if [[ ! -f $sys_disk ]]; then echo "missing: $sys_disk (run ./toolchains/install.sh)" >&2 exit 1 fi # ------------------------------------------------------------- AGI # AGI needs ~880KB (240KB interpreter + 644KB KQ3 resources), which # overflows the 800KB 3.5" floppy the generic path uses, and MAME's # IIgs 3.5 drive is 800K-GCR-only. So AGI runs off a CFFA2 CompactFlash # card instead: partition 1 (-hard1) is a bootable GS/OS, partition 2 # (-hard2) is a 2MB ProDOS volume with AGI + the game data. The boot # scan hits slot 7 first, boots GS/OS from hard1, and GS/OS then mounts # the AGI volume; the Lua driver opens it and launches AGI. No BSS/BRAM # or startup-slot changes are needed with this two-partition topology. if [[ $target == AGI ]]; then game=${JOEY_AGI_GAME:-kq3} # make-agi-iigs-disk.sh needs the toolchain env (cadius path). if [[ -z "${LLVM816_ROOT:-}" ]]; then # shellcheck disable=SC1091 source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true fi # MAME loads the CFFA2 firmware as an a2cffa2 device ROM. Prefer the # copy staged in the repo (toolchains/install.sh territory) so the # run does not depend on the user's ~/.mame; fall back to the user # rompath. Legal: the firmware is freely redistributed by dreher.net # and the card is owned. staged_roms=$repo/toolchains/emulators/support/mame-roms user_roms="${MAME_ROMPATH:-$HOME/.mame/roms}" rompath="$user_roms" if [[ -f "$staged_roms/a2cffa2/cffa20eec02.bin" ]]; then rompath="$user_roms;$staged_roms" elif [[ ! -f "$user_roms/a2cffa2/cffa20eec02.bin" ]]; then cat >&2 </dev/null; then kill "$mame_pid" 2>/dev/null || true sleep 1 kill -9 "$mame_pid" 2>/dev/null || true fi rm -rf "$work" } trap cleanup EXIT INT TERM # hard1: writable bootable GS/OS copy. hard2: freshly built AGI vol. cp "$sys_disk" "$work/boot.po" echo "run-iigs: building AGI CFFA data disk ($game)..." "$repo/scripts/make-agi-iigs-disk.sh" "$work/agi.2mg" "$game" # Finder driver: at calibrated frame counts type A (select the AGI # volume -- boot volume is System.Disk, so 'A' is unambiguous), Cmd-O # to open it, the full name AGI to select the app, Cmd-O to launch. cat > "$work/launch.lua" <= steps[step_idx][1] do steps[step_idx][2]() step_idx = step_idx + 1 end end) LUA video_arg="-window" sound_arg="" throttle_arg="" if [[ "$headless" = "1" ]]; then video_arg="-video soft" sound_arg="-sound none" throttle_arg="-nothrottle" export QT_QPA_PLATFORM=offscreen export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy else export SDL_MOUSE_RELATIVE_MODE_WARP=0 fi cat < /tmp/run-iigs-snap" fi exit 0 fi # Dedicated run disk so joey.2mg (the bench/golden disk) is never # replaced -- a stale-swapped joey.2mg silently corrupts the next # UBER bench run. Rebuild when missing, holding a different # example, or older than the example binary. run_disk=$bin_dir/joey-run.2mg stamp=$run_disk.contents need_disk=0 if [[ ! -f $run_disk ]]; then need_disk=1 elif [[ "$(cat "$stamp" 2>/dev/null)" != "$target" ]]; then need_disk=1 elif [[ "$bin_dir/$target" -nt $run_disk ]]; then need_disk=1 fi if [[ $need_disk -eq 1 ]]; then if [[ -z "${LLVM816_ROOT:-}" ]]; then # make-iigs-disk.sh needs the toolchain env (cadius path). # shellcheck disable=SC1091 source "$repo/toolchains/env.sh" >/dev/null 2>&1 || true fi echo "run-iigs: building single-example run disk ($target)..." JOEY_DISK_EXAMPLES="$target" "$repo/scripts/make-iigs-disk.sh" "$run_disk" printf '%s' "$target" > "$stamp" else echo "run-iigs: run disk is current ($target)" fi work=$(mktemp -d -t joeylib-run-iigs.XXXXXX) mame_pid="" cleanup() { # Kill the emulator if we are exiting first (Ctrl-C, timeout): # bash does not forward signals to a foreground child, and an # orphaned MAME keeps burning CPU with its work dir deleted # from under it. if [[ -n $mame_pid ]] && kill -0 "$mame_pid" 2>/dev/null; then kill "$mame_pid" 2>/dev/null || true sleep 1 kill -9 "$mame_pid" 2>/dev/null || true fi # Preserve the post-run disk (high scores etc.) for inspection. if [[ -f $work/joey-run.2mg ]]; then cp "$work/joey-run.2mg" "$bin_dir/joey-run-after.2mg" fi rm -rf "$work" } trap cleanup EXIT INT TERM cp "$sys_disk" "$work/boot.po" cp "$run_disk" "$work/joey-run.2mg" # Slim Finder driver -- the keyboard-injection steps proven in # run-iigs-mame.sh's harness, without the debugger/crash plumbing: # at calibrated frame counts, type J (select the JOEYLIB volume), # Cmd-O (open it), the example's full name (select; full-name # typing disambiguates e.g. DRAW vs DATA), Cmd-O (launch). Frame # counts are emulated frames, so throttling doesn't shift them. headless=${MAME_HEADLESS:-0} cat > "$work/launch.lua" <= steps[step_idx][1] do steps[step_idx][2]() step_idx = step_idx + 1 end end) LUA video_arg="-window" sound_arg="" throttle_arg="" if [[ "$headless" = "1" ]]; then # Verification mode: no window, no sound, and unthrottled so # the 12k-frame scripted run finishes in about a minute. video_arg="-video soft" sound_arg="-sound none" throttle_arg="-nothrottle" export QT_QPA_PLATFORM=offscreen export SDL_VIDEODRIVER=dummy export SDL_AUDIODRIVER=dummy fi rompath="${MAME_ROMPATH:-$HOME/.mame/roms}" cat < /tmp/run-iigs-snap" fi if [[ -f $work/launch-progress.txt ]]; then cp "$work/launch-progress.txt" /tmp/run-iigs-progress.txt fi exit 0 fi # -------------------------------------------------------------- GSplus # No argument: original GSplus + interactive Finder path. gsplus=$repo/toolchains/emulators/gsplus/bin/gsplus rom=$repo/toolchains/emulators/support/apple-iigs.rom data_disk=$bin_dir/joey.2mg null_c600=$repo/toolchains/emulators/support/iigs-null-c600.rom for f in "$gsplus" "$rom" "$sys_disk" "$data_disk" "$null_c600"; do if [[ ! -f $f ]]; then echo "missing: $f" >&2 if [[ $f == "$data_disk" ]]; then echo "run 'make iigs-disk' to build it." >&2 else echo "run ./toolchains/install.sh (support files should have been staged)." >&2 fi exit 1 fi done # GSplus writes back to disk images during the session; stage writable # copies so repeated runs do not mutate the originals. work=$(mktemp -d -t joeylib-iigs.XXXXXX) cleanup() { # Stash the post-run disk image for manual inspection. if [[ -f $work/joey.2mg ]]; then cp "$work/joey.2mg" "$repo/build/iigs/bin/joey-after-run.2mg" echo "run-iigs: saved post-run disk -> $repo/build/iigs/bin/joey-after-run.2mg" >&2 fi rm -rf "$work" } trap cleanup EXIT cp "$sys_disk" "$work/boot.po" cp "$data_disk" "$work/joey.2mg" # Stage the null slot-6 PROM as c600.rom in the work dir. GSplus loads # any file named c600.rom in its search path (cwd first) as the slot 6 # ROM card, overriding the built-in Disk II firmware. The staged image # is 256 bytes with a leading RTS and no Pascal 1.1 firmware signature, # so the IIgs boot ROM's slot scan skips slot 6 and the two empty 5.25 # drives never get probed. Source: toolchains/install.sh's # install_support_iigs_null_c600. cp "$null_c600" "$work/c600.rom" cat <' auto-launches via MAME instead.) EOF # GSplus auto-creates config.kegs in its cwd on first run. cd into # the scratch dir so that file lands there and gets cleaned up with # the rest of the temp state, rather than polluting the directory # the user invoked the script from. cd "$work" # GSplus accepts any config.kegs key as a CLI override via "- # ". g_limit_speed=2 caps emulation at the IIgs's native # 2.8 MHz; without this override GSplus boots at 8 MHz and our # performance characteristics don't match real hardware. The other # settings (0=unlimited, 1=1.024 MHz, 3=8 MHz) are reachable through # the in-emulator speed-cycle key if needed. # # No exec: let the bash EXIT trap fire so the scratch dir (and the # config.kegs GSplus auto-creates) gets cleaned up. "$gsplus" -rom "$rom" \ -s5d1 "$work/boot.po" \ -s5d2 "$work/joey.2mg" \ -g_limit_speed 2