joeylib2/scripts/run-iigs-mame.sh

306 lines
10 KiB
Bash
Executable file

#!/usr/bin/env bash
# Launch the IIgs sprite demo (or other example) under MAME's apple2gs
# driver instead of GSplus. We get:
# - The MAME debugger window (always visible with -debug)
# - debug.log file written to the working directory
# - A Lua hook that, when the CPU halts (e.g., BRK), dumps registers
# and the surrounding code bytes to /tmp/mame-iigs/crash.txt
#
# The boot path is the same as run-iigs.sh: gsos-system.po as flop3, our
# joey.2mg as flop4. Once Finder is up, navigate to JOEYLIB and double-
# click the example.
#
# Outputs of a run land in /tmp/mame-iigs/:
# - debug.log MAME's debugger console output (-debuglog)
# - crash.txt Lua-hook crash dump (if anything halts the CPU)
# - joeylog.txt Extracted from the post-run disk image
set -euo pipefail
if [[ $# -gt 1 ]]; then
echo "usage: $0 [example-name]" >&2
exit 2
fi
prog=${1:-pattern}
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
bin_dir=$repo/build/iigs/bin
target=${prog^^}
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
sys_disk=$repo/toolchains/emulators/support/gsos-system.po
data_disk=$bin_dir/joey.2mg
for f in "$sys_disk" "$data_disk"; do
if [[ ! -f $f ]]; then
echo "missing: $f" >&2
exit 1
fi
done
work=$(mktemp -d -t joeylib-mame.XXXXXX)
out=/tmp/mame-iigs
mkdir -p "$out"
cp "$sys_disk" "$work/boot.po"
cp "$data_disk" "$work/joey.2mg"
# Lua script: drives Finder via natural keyboard + macadb key fields
# to launch the requested example, dumps register state on any halt
# (BRK, breakpoint, watchpoint, manual stop). Field names for keys
# come from MAME's apple2gs macadb input definitions:
# :macadb:KEY0 -> "d D"
# :macadb:KEY1 -> "o O"
# :macadb:KEY2 -> "j J", "p P"
# :macadb:KEY3 -> "Command / Open Apple"
# Letters not on KEY0..2 fall back to natkeyboard:post() (which
# handles modifier-less character entry only).
# Type the FULL program name to disambiguate (DRAW vs DATA which both
# start with D and live in the same JOEYLIB volume).
prog_select_str=${target}
cat > "$work/crash-hook.lua" <<LUA
-- Crash diagnostics + Finder driver for IIgs demos. Auto-resumes
-- the initial debug pause; at calibrated frame counts taps "J" to
-- select JOEYLIB, Cmd-O to open it, the program first letter to
-- select the binary, Cmd-O to launch it. On any halt (BRK trap,
-- watchpoint, breakpoint) dumps registers + bytes around PC to
-- crash.txt.
local cpu = manager.machine.devices[":maincpu"]
local prog = cpu.spaces["program"]
local outpath = "/tmp/mame-iigs/crash.txt"
local function dump(label)
local f = io.open(outpath, "a")
if f == nil then return end
f:write(string.format("=== %s @ %s ===\n", label, os.date("%H:%M:%S")))
for k, v in pairs(cpu.state) do
f:write(string.format(" %s = %X\n", k, v.value))
end
local pc = cpu.state["CURPC"].value
local lo = (pc - 16) & 0xFFFFFF
f:write(string.format(" bytes %06X..%06X:", lo, lo + 32))
for i = 0, 32 do
local b = prog:read_u8(lo + i)
f:write(string.format(" %02X", b))
end
f:write("\n")
-- Probe addresses from joeyDraw.asm
f:write(string.format(" probe 012050=%02X 011F80=%02X 023000=%02X\n",
prog:read_u8(0x012050),
prog:read_u8(0x011F80),
prog:read_u8(0x023000)))
f:close()
end
local done_marker = "/tmp/mame-iigs/.done"
local started = false
local crashed = false
local boot_frames = 0
local function signal_done(reason)
local f = io.open(done_marker, "w")
if f ~= nil then
f:write(reason)
f:close()
end
end
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 function log_step(label)
local f = io.open("/tmp/mame-iigs/steps.txt", "a")
if f ~= nil then
f:write(string.format("frame=%d %s\n", boot_frames, label))
f:close()
end
end
local function snap(label)
log_step("snap " .. label)
manager.machine.video:snapshot()
end
-- Finder navigation: natkeyboard handles per-letter scancode/timing
-- correctly; the Cmd modifier is forced via the macadb field for the
-- duration of the post() call. Mirrors the proven approach in the
-- pre-existing /tmp/auto_run.lua.
local steps = {
{ 2700, function() snap("preboot") end },
{ 3000, function() snap("at_3000_finder") log_step("post J") nat:post("J") end },
{ 3120, function() snap("after_J") log_step("Cmd hold") press(key_cmd) end },
{ 3126, function() log_step("post o under Cmd") nat:post("o") end },
{ 3180, function() log_step("Cmd release") release(key_cmd) end },
{ 3300, function() snap("after_Cmd_O") end },
{ 3540, function() log_step("post name") nat:post("${prog_select_str}") end },
{ 3600, function() snap("after_first") end },
{ 3660, function() log_step("Cmd hold #2") press(key_cmd) end },
{ 3666, function() log_step("post o #2") nat:post("o") end },
{ 3720, function() log_step("Cmd release #2") release(key_cmd) end },
{ 3900, function() snap("after_launch") end },
{ 6000, function() snap("running") end },
{ 9000, function() snap("running_2") end },
{ 12000, function() snap("running_3") end },
{ 13500, function() snap("running_4") end },
{ 14500, function() snap("running_5") end },
{ 15500, function() snap("running_6") end },
{ 16500, function() snap("running_7") end },
}
local step_idx = 1
-- Probe-address poll. Watch 3 addresses and log every change, so we
-- can verify which (if any) is reachable by our long-mode STAs.
local last1, last2, last3, last4, last5, last6, last7 = -1, -1, -1, -1, -1, -1, -1
local function check_ckpt()
local v1 = prog:read_u8(0x012050)
local v2 = prog:read_u8(0x011F80)
local v3 = prog:read_u8(0x023000)
local v4 = prog:read_u8(0xE12050) -- SHR pixel byte (mirror of $012050 once blitted)
local v5 = prog:read_u8(0xE19D00) -- SCB row 0 (0 = 320 mode)
local v6 = prog:read_u8(0xE15000) -- SHR pixel byte mid-screen (row ~38, col ~64)
local v7 = prog:read_u8(0xE19E00) -- palette[0][0] low byte (color 0 lo)
if v1 ~= last1 or v2 ~= last2 or v3 ~= last3 or v4 ~= last4 or v5 ~= last5 or v6 ~= last6 or v7 ~= last7 then
local f = io.open("/tmp/mame-iigs/ckpt-trace.txt", "a")
if f ~= nil then
f:write(string.format("frame=%d 012050=%02X 011F80=%02X 023000=%02X E12050=%02X E19D00=%02X E15000=%02X E19E00=%02X\n",
boot_frames, v1, v2, v3, v4, v5, v6, v7))
f:close()
end
last1, last2, last3, last4, last5, last6, last7 = v1, v2, v3, v4, v5, v6, v7
end
end
emu.register_periodic(function()
local dbg = manager.machine.debugger
if dbg == nil then return end
if dbg.execution_state == "stop" then
if not started then
started = true
dbg.execution_state = "run"
return
end
if not crashed then
crashed = true
dump("halt")
signal_done("halt")
end
else
boot_frames = boot_frames + 1
check_ckpt()
while step_idx <= #steps and boot_frames >= steps[step_idx][1] do
steps[step_idx][2]()
step_idx = step_idx + 1
end
if boot_frames > 18000 and not crashed then
crashed = true
dump("watchdog")
signal_done("watchdog")
end
end
end)
LUA
# Wipe prior crash.txt so we don't confuse runs.
: > "$out/crash.txt"
cat <<EOF
MAME apple2gs (auto-launch ${prog^^}):
Boot disk: $work/boot.po (flop3)
Boots GS/OS, waits ~50s for Finder, then drives the keyboard via Lua
to select the JOEYLIB volume and double-launch ${prog^^}.
On crash the MAME debugger halts and Lua dumps state to:
$out/crash.txt
After exit:
$out/joeylog.txt extracted from disk image
$out/debug.log MAME debugger console output
EOF
cleanup() {
# Always rescue debug.log first -- it's written by MAME relative to
# cwd ($work) and lost when the dir is removed.
if [[ -f $work/debug.log ]]; then
mv -f "$work/debug.log" "$out/debug.log"
fi
# Snapshots that the Lua hook captured for visual debugging.
if [[ -d $work/snap ]]; then
rm -rf "$out/snap"
mv "$work/snap" "$out/snap"
fi
local profuse=$repo/toolchains/iigs/gg-tools/bin/profuse
local mnt=$work/_mnt
if [[ -x $profuse && -f $work/joey.2mg ]]; then
export GOLDEN_GATE="$repo/toolchains/iigs/goldengate"
export ORCA_ROOT="$GOLDEN_GATE"
mkdir -p "$mnt"
if "$profuse" -oro "$work/joey.2mg" "$mnt" 2>/dev/null; then
for name in JOEYLOG.TXT joeylog.txt; do
if [[ -f $mnt/$name ]]; then
cp "$mnt/$name" "$out/joeylog.txt"
echo "extracted joeylog.txt -> $out/joeylog.txt" >&2
break
fi
done
fusermount -u "$mnt" 2>/dev/null || true
fi
fi
rm -rf "$work"
}
trap cleanup EXIT
# Visible by default. Set MAME_HEADLESS=1 to suppress the video window
# (CI / batch runs that only care about crash.txt).
video_arg="-window"
if [[ "${MAME_HEADLESS:-0}" = "1" ]]; then
video_arg="-video none"
fi
# Clear the done-marker the Lua hook uses to signal shutdown.
rm -f "$out/.done"
cd "$work"
mame apple2gs \
-flop3 "$work/boot.po" \
-flop4 "$work/joey.2mg" \
$video_arg -sound none \
-nothrottle \
-debug -debuglog \
-autoboot_script "$work/crash-hook.lua" &
mame_pid=$!
# Poll for the done-marker. Kill MAME once Lua signals it. Cap total
# wall-clock at 6 min so we don't outlive the Lua-side watchdog (5 min).
deadline=$((SECONDS + 360))
while kill -0 "$mame_pid" 2>/dev/null; do
if [[ -f $out/.done ]]; then
kill "$mame_pid" 2>/dev/null
break
fi
if (( SECONDS > deadline )); then
kill "$mame_pid" 2>/dev/null
break
fi
sleep 0.5
done
wait "$mame_pid" 2>/dev/null || true