306 lines
10 KiB
Bash
Executable file
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
|