427 lines
17 KiB
Bash
Executable file
427 lines
17 KiB
Bash
Executable file
#!/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 <<EOF
|
|
missing CFFA2 firmware: a2cffa2/cffa20eec02.bin
|
|
Needed to mount AGI's data disk (4096 bytes, SHA1
|
|
080ff88f19de22328e162954ee2b51ee65f9d5cd). It ships in dreher.net's
|
|
Run6_CDROM.zip as Firmware/V2.0/CFFA20EEC02.BIN. Drop it in either
|
|
$staged_roms/a2cffa2/ or $user_roms/a2cffa2/
|
|
EOF
|
|
exit 1
|
|
fi
|
|
|
|
headless=${MAME_HEADLESS:-0}
|
|
work=$(mktemp -d -t joeylib-run-agi.XXXXXX)
|
|
mame_pid=""
|
|
cleanup() {
|
|
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
|
|
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" <<LUA
|
|
local nat = manager.machine.natkeyboard
|
|
local function get_field(port, field_name)
|
|
local p = manager.machine.ioport.ports[port]
|
|
if p == nil then return nil end
|
|
return p.fields[field_name]
|
|
end
|
|
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
|
|
local function press(f) if f then f:set_value(1) end end
|
|
local function release(f) if f then f:set_value(0) end end
|
|
local headless = ($headless == 1)
|
|
local frames = 0
|
|
local steps = {
|
|
{ 3000, function() nat:post("A") end },
|
|
{ 3120, function() press(key_cmd) end },
|
|
{ 3126, function() nat:post("o") end },
|
|
{ 3180, function() release(key_cmd) end },
|
|
{ 3540, function() nat:post("AGI") end },
|
|
{ 3660, function() press(key_cmd) end },
|
|
{ 3666, function() nat:post("o") end },
|
|
{ 3720, function() release(key_cmd) end },
|
|
}
|
|
if headless then
|
|
table.insert(steps, { 4200, function() manager.machine.video:snapshot() end })
|
|
table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
|
|
table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
|
|
table.insert(steps, { 9100, function() manager.machine:exit() end })
|
|
end
|
|
table.sort(steps, function(a, b) return a[1] < b[1] end)
|
|
local step_idx = 1
|
|
emu.register_periodic(function()
|
|
frames = frames + 1
|
|
while step_idx <= #steps and frames >= 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 <<EOF
|
|
MAME apple2gs: AGI ($game) on a CFFA2 disk. GS/OS boots from hard1
|
|
(~50 s) and mounts the AGI volume on hard2; the Lua driver opens it and
|
|
launches AGI. Quit MAME (or close its window) to end the session.
|
|
EOF
|
|
|
|
cd "$work"
|
|
# -ramsize 4M: AGI's ~240KB app plus GS/OS/Finder leaves the stock
|
|
# 2MB Memory Manager pool too tight -- its first jlAlloc (the view
|
|
# cache) returns NULL and the app exits. 4MB is the assumed target.
|
|
mame apple2gs \
|
|
-ramsize 4M \
|
|
-rompath "$rompath" \
|
|
-sl7 cffa2 -hard1 "$work/boot.po" -hard2 "$work/agi.2mg" \
|
|
$video_arg $sound_arg $throttle_arg \
|
|
-skip_gameinfo \
|
|
-snapshot_directory "$work/snap" \
|
|
-autoboot_script "$work/launch.lua" &
|
|
mame_pid=$!
|
|
wait "$mame_pid" || true
|
|
mame_pid=""
|
|
if [[ -d $work/snap ]]; then
|
|
rm -rf /tmp/run-iigs-snap
|
|
mv "$work/snap" /tmp/run-iigs-snap
|
|
echo "run-iigs: snapshots -> /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" <<LUA
|
|
local nat = manager.machine.natkeyboard
|
|
|
|
local function get_field(port, field_name)
|
|
local p = manager.machine.ioport.ports[port]
|
|
if p == nil then return nil end
|
|
return p.fields[field_name]
|
|
end
|
|
|
|
local key_cmd = get_field(":macadb:KEY3", "Command / Open Apple")
|
|
|
|
local function press(f) if f then f:set_value(1) end end
|
|
local function release(f) if f then f:set_value(0) end end
|
|
|
|
local headless = ($headless == 1)
|
|
local frames = 0
|
|
local steps = {
|
|
{ 3000, function() nat:post("J") end },
|
|
{ 3120, function() press(key_cmd) end },
|
|
{ 3126, function() nat:post("o") end },
|
|
{ 3180, function() release(key_cmd) end },
|
|
{ 3540, function() nat:post("$target") end },
|
|
{ 3660, function() press(key_cmd) end },
|
|
{ 3666, function() nat:post("o") end },
|
|
{ 3720, function() release(key_cmd) end },
|
|
}
|
|
if headless then
|
|
-- Batch verification: snapshot right after the launch lands, mid
|
|
-- asset load, and after the example settles on its first screen,
|
|
-- then exit so the caller regains control. Interactive runs never
|
|
-- exit on their own.
|
|
table.insert(steps, { 3900, function() manager.machine.video:snapshot() end })
|
|
table.insert(steps, { 6600, function() manager.machine.video:snapshot() end })
|
|
table.insert(steps, { 9000, function() manager.machine.video:snapshot() end })
|
|
table.insert(steps, { 9100, function() manager.machine:exit() end })
|
|
end
|
|
local step_idx = 1
|
|
|
|
emu.register_periodic(function()
|
|
frames = frames + 1
|
|
if headless and frames % 600 == 0 then
|
|
-- Pacing heartbeat for batch runs (rescued with the snaps).
|
|
local f = io.open("launch-progress.txt", "w")
|
|
if f ~= nil then
|
|
f:write(string.format("frames=%d\n", frames))
|
|
f:close()
|
|
end
|
|
end
|
|
while step_idx <= #steps and frames >= 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 <<EOF
|
|
MAME apple2gs: auto-launching $target off $run_disk.
|
|
GS/OS takes ~50 s to reach Finder; the Lua driver then opens JOEYLIB
|
|
and launches $target for you. Quit MAME (or close its window) to end
|
|
the session.
|
|
EOF
|
|
|
|
cd "$work"
|
|
# Throttled (real IIgs speed) -- this is the "play it" path,
|
|
# unlike the verify/bench harnesses which run -nothrottle. Runs
|
|
# as a tracked child so the cleanup trap can reap it on Ctrl-C.
|
|
# -skip_gameinfo matters: without it MAME parks on the "driver is
|
|
# not working correctly" info screen waiting for a keypress and
|
|
# the machine never boots (the -debug harnesses never hit this).
|
|
# -snapshot_directory is explicit because MAME otherwise resolves
|
|
# it against the user's mame.ini (snapshots vanished into
|
|
# ~/.mame/snap during bring-up).
|
|
mame apple2gs \
|
|
-rompath "$rompath" \
|
|
-flop3 "$work/boot.po" \
|
|
-flop4 "$work/joey-run.2mg" \
|
|
$video_arg $sound_arg $throttle_arg \
|
|
-skip_gameinfo \
|
|
-snapshot_directory "$work/snap" \
|
|
-autoboot_script "$work/launch.lua" &
|
|
mame_pid=$!
|
|
wait "$mame_pid" || true
|
|
mame_pid=""
|
|
if [[ -d $work/snap ]]; then
|
|
rm -rf /tmp/run-iigs-snap
|
|
mv "$work/snap" /tmp/run-iigs-snap
|
|
echo "run-iigs: snapshots -> /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 <<EOF
|
|
GSplus launching GS/OS 6.0.4.
|
|
Once Finder is up:
|
|
1. Open the JOEYLIB disk on the desktop.
|
|
2. Double-click whichever demo you want to run.
|
|
(Tip: '$0 <example>' 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 "-<key>
|
|
# <value>". 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
|