237 lines
8 KiB
Bash
Executable file
237 lines
8 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
# Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License.
|
|
# See LICENSE.md for license information.
|
|
# SPDX-License-Identifier: MIT
|
|
#
|
|
# verify-iigs-joy.sh - Joystick gate for the IIgs: the stick on the game
|
|
# port, every direction, both buttons and every release, driven through
|
|
# MAME's joystick and read back from what examples/joy draws.
|
|
#
|
|
# The port (src/iigs/input.c) reads paddles 0 and 1 (the asm in
|
|
# joeyDraw.s) and the buttons at $C061/$C062 as JOYSTICK_0 (stick A); until
|
|
# a program calls jlJoystickReset a paddle reads as a digital direction.
|
|
# The IIgs exposes one stick: B must read unplugged throughout. MAME's
|
|
# joystick (-gameio joy) is held from Lua at each step's paddle positions
|
|
# (0 and 255 full, 128 at rest) and buttons, and a snapshot taken once JOY
|
|
# has drawn it; the snapshots, lines doubled (MAME's IIgs frame has
|
|
# double-width pixels), then go through scripts/lib/joyScreen.py
|
|
# (scripts/lib/joyGate.sh) in order. Escape must then end JOY.
|
|
#
|
|
# The program boots from a GS/OS floppy whose launcher starts it, with its
|
|
# data disk beside it (scripts/make-iigs-boot-disk.sh, make-iigs-disk.sh),
|
|
# as joey-run boots it.
|
|
#
|
|
# Usage: scripts/verify-iigs-joy.sh
|
|
# Env: JOY_SHOTS also keep the screenshots in this folder
|
|
# Requires: MAME, the IIgs firmware and GS/OS, the IIgs toolchain (the disk
|
|
# scripts), ImageMagick, python3 + PIL; build/iigs/bin/JOY
|
|
# (make iigs-disk).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
source "$repo/scripts/lib/joyGate.sh"
|
|
joeyNeed MAME mame
|
|
joeyNeed IIGS_ROMPATH iigsFirmware
|
|
joeyNeed GSOS_SYSTEM_DISK iigsFirmware
|
|
joeyNeed CADIUS cadius
|
|
|
|
JOY_LABEL=verify-iigs-joy
|
|
JOY_REST_B="$JOY_ABSENT"
|
|
# The checks read snapshots MAME has already taken: no waiting, and none
|
|
# taken again.
|
|
JOY_PRESS_SECONDS=0
|
|
JOY_SHOT_TRIES=1
|
|
program="$repo/build/iigs/bin/JOY"
|
|
wall=300 # hard cap on MAME, whatever happens to this script
|
|
readySeconds=80 # emulated: GS/OS booting and JOY drawing
|
|
settleSeconds=0.5 # emulated: a change until JOY has drawn it
|
|
exitSeconds=3 # emulated: Escape until JOY's screen is gone
|
|
paddleLow=0
|
|
paddleRest=128
|
|
paddleHigh=255
|
|
|
|
[ -f "$program" ] || { echo "$JOY_LABEL: build JOY first (make iigs-disk)" >&2; exit 2; }
|
|
|
|
JOY_WORK=$(mktemp -d -t joeylib-iigs-joy.XXXXXX)
|
|
mame=""
|
|
cleanup() {
|
|
joeyStop "$mame" 2> /dev/null
|
|
rm -rf "$JOY_WORK"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
# The steps checked: stick A's, B left alone.
|
|
steps=("${JOY_STEPS_TWO_STICKS[@]:0:9}")
|
|
|
|
|
|
# state CONTROL... -- the Lua table of paddle X, paddle Y, button 0 and
|
|
# button 1 with the controls held.
|
|
state() {
|
|
local x=$paddleRest
|
|
local y=$paddleRest
|
|
local b0=0
|
|
local b1=0
|
|
local control
|
|
for control in "$@"; do
|
|
case "$control" in
|
|
A-left) x=$paddleLow ;;
|
|
A-right) x=$paddleHigh ;;
|
|
A-up) y=$paddleLow ;;
|
|
A-down) y=$paddleHigh ;;
|
|
A-button0) b0=1 ;;
|
|
A-button1) b1=1 ;;
|
|
*) joyFail "no control $control on the IIgs" ;;
|
|
esac
|
|
done
|
|
printf '{%s, %s, %s, %s}' "$x" "$y" "$b0" "$b1"
|
|
}
|
|
|
|
|
|
# The snapshots, in the order the checks take them: rest, then for each
|
|
# step held and released.
|
|
shotIndex=0
|
|
joyPress() {
|
|
:
|
|
}
|
|
|
|
|
|
joyRelease() {
|
|
:
|
|
}
|
|
|
|
|
|
joyShot() {
|
|
local shot
|
|
shot=$(ls "$JOY_WORK"/snap/*.png 2> /dev/null | sort | sed -n "$((shotIndex + 1))p")
|
|
shotIndex=$((shotIndex + 1))
|
|
[ -n "$shot" ] || return 1
|
|
convert "$shot" -sample 100%x200% "$1"
|
|
}
|
|
|
|
|
|
states="$(state)"
|
|
for step in "${steps[@]}"; do
|
|
IFS='|' read -r _ held _ _ <<< "$step"
|
|
read -r -a controls <<< "$held"
|
|
states="$states, $(state "${controls[@]}"), $(state)"
|
|
done
|
|
|
|
mkdir -p "$JOY_WORK/cfg" "$JOY_WORK/nvram" "$JOY_WORK/snap"
|
|
BINDIR="$(dirname "$program")" JOEY_DISK_EXAMPLES="$(basename "$program")" \
|
|
"$repo/scripts/make-iigs-disk.sh" "$JOY_WORK/data.2mg" > "$JOY_WORK/disk.log" 2>&1 ||
|
|
{ echo "$JOY_LABEL: FAIL - could not build the data disk" >&2; tail -5 "$JOY_WORK/disk.log" >&2; exit 1; }
|
|
"$repo/scripts/make-iigs-boot-disk.sh" "$JOY_WORK/boot.po" "$(basename "$program")" > "$JOY_WORK/boot.log" 2>&1 ||
|
|
{ echo "$JOY_LABEL: FAIL - could not build the boot disk" >&2; tail -5 "$JOY_WORK/boot.log" >&2; exit 1; }
|
|
|
|
cat > "$JOY_WORK/joy.lua" << LUA
|
|
local states = { $states }
|
|
local readyAt = $readySeconds
|
|
local settle = $settleSeconds
|
|
local exitAfter = $exitSeconds
|
|
|
|
-- A field of an ioport whose name contains pattern, or nil.
|
|
local function field(tag, pattern)
|
|
local p = manager.machine.ioport.ports[tag]
|
|
if not p then return nil end
|
|
for name, f in pairs(p.fields) do
|
|
if name:find(pattern, 1, true) then return f end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- The field of an ioport that drives mask, or nil.
|
|
local function maskField(tag, mask)
|
|
local p = manager.machine.ioport.ports[tag]
|
|
if not p then return nil end
|
|
for _, f in pairs(p.fields) do
|
|
if f.mask == mask then return f end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-- A field named exactly name on any port whose tag starts with prefix.
|
|
local function key(prefix, name)
|
|
for tag, p in pairs(manager.machine.ioport.ports) do
|
|
if tag:sub(1, #prefix) == prefix and p.fields[name] then return p.fields[name] end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local fx = field(":gameio:joy:joystick_1_x", "")
|
|
local fy = field(":gameio:joy:joystick_1_y", "")
|
|
-- The game port's switches SW0 and SW1: bits 4 and 5 of MAME's button
|
|
-- port (its player numbers do not follow the switches).
|
|
local fb0 = maskField(":gameio:joy:joystick_buttons", 0x10)
|
|
local fb1 = maskField(":gameio:joy:joystick_buttons", 0x20)
|
|
local esc = key(":macadb:", "Esc")
|
|
if not (fx and fy and fb0 and fb1 and esc) then
|
|
for tag, p in pairs(manager.machine.ioport.ports) do
|
|
for name, f in pairs(p.fields) do
|
|
if tag:find(":gameio:", 1, true) or name:lower():find("esc", 1, true) then
|
|
print(string.format("JOYGATE field %s %s mask %x", tag, name, f.mask))
|
|
end
|
|
end
|
|
end
|
|
print("JOYGATE: MAME's joystick or Escape key not found")
|
|
manager.machine:exit()
|
|
return
|
|
end
|
|
|
|
local i = 0
|
|
local nextAt = readyAt
|
|
local phase = "apply"
|
|
local escAt = nil
|
|
emu.register_frame_done(function()
|
|
local t = manager.machine.time:as_double()
|
|
if t < nextAt then return end
|
|
if phase == "apply" then
|
|
i = i + 1
|
|
if i > #states then
|
|
esc:set_value(1)
|
|
escAt = t
|
|
phase = "escape"
|
|
nextAt = t + 0.5
|
|
return
|
|
end
|
|
local s = states[i]
|
|
fx:set_value(s[1]); fy:set_value(s[2]); fb0:set_value(s[3]); fb1:set_value(s[4])
|
|
phase = "shoot"
|
|
nextAt = t + settle
|
|
elseif phase == "shoot" then
|
|
manager.machine.video:snapshot()
|
|
phase = "apply"
|
|
nextAt = t
|
|
elseif phase == "escape" then
|
|
esc:set_value(0)
|
|
phase = "after"
|
|
nextAt = escAt + exitAfter
|
|
elseif phase == "after" then
|
|
manager.machine.video:snapshot()
|
|
manager.machine:exit()
|
|
end
|
|
end)
|
|
LUA
|
|
|
|
(cd "$JOY_WORK" && exec env SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy timeout -s KILL "$wall" "$MAME" apple2gs \
|
|
-rompath "$IIGS_ROMPATH" -flop3 "$JOY_WORK/boot.po" -flop4 "$JOY_WORK/data.2mg" -gameio joy \
|
|
-skip_gameinfo -cfg_directory "$JOY_WORK/cfg" -nvram_directory "$JOY_WORK/nvram" \
|
|
-snapshot_directory "$JOY_WORK/snap" -snapname "shot%i" \
|
|
-video none -sound none -nothrottle -autoboot_script "$JOY_WORK/joy.lua") > "$JOY_WORK/mame.log" 2>&1 &
|
|
mame=$!
|
|
wait "$mame"
|
|
mame=""
|
|
if grep -q '^JOYGATE' "$JOY_WORK/mame.log"; then
|
|
echo "$JOY_LABEL: FAIL - $(grep '^JOYGATE:' "$JOY_WORK/mame.log")" >&2
|
|
grep '^JOYGATE field' "$JOY_WORK/mame.log" >&2
|
|
exit 1
|
|
fi
|
|
|
|
joyCheck "rest" "A=$JOY_REST B=$JOY_REST_B"
|
|
joyRunSteps "${steps[@]}"
|
|
# The last snapshot, after Escape: JOY gone.
|
|
if joyShot "$JOY_WORK/after.png" && python3 "$repo/scripts/lib/joyScreen.py" "$JOY_WORK/after.png" > /dev/null 2>&1; then
|
|
joyFail "Escape did not end JOY"
|
|
fi
|
|
|
|
joyResult "$JOY_LABEL" "the game port's stick, every direction, both buttons and every release, B unplugged; Escape ends JOY"
|