290 lines
12 KiB
Bash
Executable file
290 lines
12 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-psp-input.sh - Input gate for the PSP: the PSP's own controls reach
|
|
# JoeyLib, pressed through PPSSPPHeadless's input script and read back from
|
|
# what the examples draw.
|
|
#
|
|
# The PSP has no keyboard and one set of controls: SDL3's PSP joystick, which
|
|
# the SDL3 backend opens as gamepad 0 (JL_JOYSTICK_0: the d-pad or the analog
|
|
# nub, cross as button 0, circle as button 1), with Start as Return and
|
|
# Select as Escape, and Square opening a keyboard drawn over the screen
|
|
# (src/hosted/psp/pspKeyboard.c) whose d-pad moves and cross presses are
|
|
# worked out from its own source by tests/gate/pspKeyboardPath.c. Three
|
|
# runs under PPSSPPHeadless (scripts/lib/pspGate.sh), each from its folder
|
|
# on a private memory stick with every memory access checked as a PSP
|
|
# checks it, each driven by an --input script on PPSSPP's emulated clock
|
|
# (installer/patches/ppsspp):
|
|
#
|
|
# JOY (examples/joy) each d-pad direction, two diagonals, each button
|
|
# and both together, the nub left and up, with a release after
|
|
# every press so a latched line shows up; then the keyboard opened,
|
|
# the d-pad and cross pressed (the stick must rest: they are the
|
|
# keyboard's), the keyboard closed and the d-pad pressed again (the
|
|
# stick's once more). After each, a snapshot of the PSP's screen,
|
|
# whose stage tests/gate/joyScreen.c reads both sticks off (stick
|
|
# B must stay unplugged). Then Select: JOY must end by itself, as
|
|
# it does on Escape.
|
|
# KEYS (examples/keys) Start: the Return key's cell lights, and goes out
|
|
# again on release, and Start has typed one Return, read off KEYS's
|
|
# row of the characters it received (tests/gate/keysRow.c). Then
|
|
# Select: KEYS must end by itself.
|
|
# KEYS again through the keyboard: Square shows it at the bottom of the
|
|
# screen and L moves it to the top; F5 held lights KEYS's F5 cell
|
|
# and let go puts it out; the RetroNet string typed on it (Shift
|
|
# lights the Shift cell for the colon); Square takes it away, and
|
|
# KEYS must have received the string, nothing more. Then Select.
|
|
#
|
|
# Usage: scripts/verify-psp-input.sh
|
|
# Requires: PPSSPPHeadless; build/psp/bin/JOY and
|
|
# build/psp/bin/KEYS (make psp).
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
source "$repo/scripts/lib/pspGate.sh"
|
|
pspNeed
|
|
label=verify-psp-input
|
|
|
|
joyDir=$repo/build/psp/bin/JOY
|
|
keysDir=$repo/build/psp/bin/KEYS
|
|
for dir in "$joyDir" "$keysDir"; do
|
|
[ -f "$dir/EBOOT.PBP" ] || { echo "$label: build $(basename "$dir") first (make psp)" >&2; exit 2; }
|
|
done
|
|
|
|
# Real seconds a run may take: the script is emulated time, which passes
|
|
# much faster than real time while the program waits.
|
|
runSeconds=60
|
|
# Emulated milliseconds: the first step (the program is up by then), and
|
|
# from each press to its snapshot and on to the next press.
|
|
startMs=1500
|
|
holdMs=400
|
|
stepMs=600
|
|
# KEYS's Return cell (examples/keys: column 2, row 4 of 28x26 cells on a
|
|
# 32x30 pitch from (2, 2)), at its centre, on the PSP's screen, where the
|
|
# backend centres the 320x200 stage at 1:1 in 480x272.
|
|
screenSize=480x272
|
|
stageX=80
|
|
stageY=36
|
|
returnX=$((stageX + 2 + 2 * 32 + 14))
|
|
returnY=$((stageY + 2 + 4 * 30 + 13))
|
|
returnLit="0 255 0"
|
|
returnUnlit="51 51 51"
|
|
# KEYS's row of every character it received (stage row 198), which must
|
|
# hold the one Return that Start typed.
|
|
typedRow=$((stageY + 198))
|
|
typedWant=$'\r'
|
|
|
|
rest=10000
|
|
unplugged=00000
|
|
# A stick at rest under the keyboard, which covers the patch that says
|
|
# whether it is there (joyScreen reads the stage where the backend puts
|
|
# it, so it needs no patch to find it).
|
|
covered=?0000
|
|
# name, input script action, stick A's state (joyScreen's characters:
|
|
# connected, X, Y, button 0, button 1), and stick B's (unplugged unless
|
|
# given).
|
|
joySteps=(
|
|
"rest|buttons none|$rest"
|
|
"left|buttons left|1-000" "release|buttons none|$rest"
|
|
"right|buttons right|1+000" "release|buttons none|$rest"
|
|
"up|buttons up|10-00" "release|buttons none|$rest"
|
|
"down|buttons down|10+00" "release|buttons none|$rest"
|
|
"up-left|buttons up left|1--00" "release|buttons none|$rest"
|
|
"down-right|buttons down right|1++00" "release|buttons none|$rest"
|
|
"cross|buttons cross|10010" "release|buttons none|$rest"
|
|
"circle|buttons circle|10001" "release|buttons none|$rest"
|
|
"cross+circle|buttons cross circle|10011" "release|buttons none|$rest"
|
|
"nub left|analog -1 0|1-000" "nub centred|analog 0 0|$rest"
|
|
"nub up|analog 0 1|10-00" "nub centred|analog 0 0|$rest"
|
|
"keyboard open|buttons square|$covered|$covered" "release|buttons none|$covered|$covered"
|
|
"keyboard left|buttons left|$covered|$covered" "release|buttons none|$covered|$covered"
|
|
"keyboard cross|buttons cross|$covered|$covered" "release|buttons none|$covered|$covered"
|
|
"keyboard closed|buttons square|$rest" "release|buttons none|$rest"
|
|
"left again|buttons left|1-000" "release|buttons none|$rest"
|
|
)
|
|
|
|
# The keyboard over KEYS: its border's colour (COLOR_BOARD, which the PSP's
|
|
# 16-bit textures keep) at the screen's bottom (where Square opens it) and
|
|
# top (where L puts it), black where it is not; how
|
|
# long a d-pad press and a cross press last (short of the keyboard's repeat)
|
|
# and how long after each the next comes; KEYS's F5 cell (column 4, row 5)
|
|
# and its left Shift cell (column 7, row 3).
|
|
kbdSource=$repo/src/hosted/psp/pspKeyboard.c
|
|
kbdTarget="192.168.1.10:6510"
|
|
kbdBorder="33 33 33"
|
|
kbdNone="0 0 0"
|
|
kbdX=240
|
|
kbdBottomY=270
|
|
kbdTopY=1
|
|
kbdPressMs=40
|
|
kbdStepMs=100
|
|
kbdHoldMs=300
|
|
f5X=$((stageX + 2 + 4 * 32 + 14))
|
|
f5Y=$((stageY + 2 + 5 * 30 + 13))
|
|
shiftX=$((stageX + 2 + 7 * 32 + 14))
|
|
shiftY=$((stageY + 2 + 3 * 30 + 13))
|
|
|
|
work=$(mktemp -d -t joeylib-psp-input.XXXXXX)
|
|
kbdShots=$work/kbd/shots
|
|
fails=""
|
|
cleanup() {
|
|
pspStop
|
|
rm -rf "$work"
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
|
|
# fail WHAT -- note a failed check; the gate goes on to report the rest.
|
|
fail() {
|
|
echo "$label: FAIL - $1"
|
|
fails="${fails:+$fails; }$1"
|
|
}
|
|
|
|
|
|
# runScript DIR NAME -- run the program folder DIR from $work/NAME with
|
|
# $work/NAME/input.txt; fails unless it ended by itself, cleanly.
|
|
runScript() {
|
|
local run="$work/$2"
|
|
local folder
|
|
folder=$(pspStage "$run" "$1")
|
|
pspRun "$run" "$folder" "$runSeconds" --input="$run/input.txt"
|
|
pspWait
|
|
pspStrictCheck "$run" "$1" || fail "$2: a bad memory access"
|
|
if grep -q '^TIMEOUT' "$run/ppsspp.log"; then
|
|
fail "$2: did not end on Select (Escape)"
|
|
fi
|
|
}
|
|
|
|
|
|
# JOY: each step's action, then its snapshot holdMs later; Select last.
|
|
mkdir -p "$work/joy/shots"
|
|
at=$startMs
|
|
i=0
|
|
for step in "${joySteps[@]}"; do
|
|
IFS='|' read -r name action want wantB <<< "$step"
|
|
printf '%d %s\n%d snapshot %s/joy/shots/%02d.png\n' "$at" "$action" "$((at + holdMs))" "$work" "$i" >> "$work/joy/input.txt"
|
|
at=$((at + stepMs))
|
|
i=$((i + 1))
|
|
done
|
|
printf '%d buttons select\n' "$at" >> "$work/joy/input.txt"
|
|
runScript "$joyDir" joy
|
|
i=0
|
|
for step in "${joySteps[@]}"; do
|
|
IFS='|' read -r name action want wantB <<< "$step"
|
|
wantB=${wantB:-$unplugged}
|
|
png=$(printf '%s/joy/shots/%02d.png' "$work" "$i")
|
|
i=$((i + 1))
|
|
if [ ! -f "$png" ]; then
|
|
fail "JOY $name: no snapshot"
|
|
continue
|
|
fi
|
|
got=$("$(joeyTool joyScreen)" "$png" --scale 1 --at "$stageX,$stageY")
|
|
echo "$label: JOY $name: $got"
|
|
[ "${got% scale=*}" = "A=$want B=$wantB" ] || fail "JOY $name: read ${got% scale=*}, want A=$want B=$wantB"
|
|
done
|
|
|
|
# KEYS: Start held, then let go, then Select.
|
|
mkdir -p "$work/keys/shots"
|
|
{
|
|
printf '%d buttons start\n' "$startMs"
|
|
printf '%d snapshot %s/keys/shots/start.png\n' "$((startMs + holdMs))" "$work"
|
|
printf '%d buttons none\n' "$((startMs + stepMs))"
|
|
printf '%d snapshot %s/keys/shots/release.png\n' "$((startMs + stepMs + holdMs))" "$work"
|
|
printf '%d buttons select\n' "$((startMs + 2 * stepMs))"
|
|
} > "$work/keys/input.txt"
|
|
runScript "$keysDir" keys
|
|
for shot in "start|$returnLit" "release|$returnUnlit"; do
|
|
IFS='|' read -r name want <<< "$shot"
|
|
png=$work/keys/shots/$name.png
|
|
if [ ! -f "$png" ]; then
|
|
fail "KEYS $name: no snapshot"
|
|
continue
|
|
fi
|
|
got=$("$(joeyTool pngProbe)" pixel "$png" "$returnX" "$returnY")
|
|
echo "$label: KEYS $name: Return cell $got"
|
|
[ "$got" = "$want" ] || fail "KEYS $name: the Return cell is $got, want $want"
|
|
typed=$("$(joeyTool keysRow)" "$png" "$typedWant" --size "$screenSize" --at "$stageX,$typedRow" --scale 1) || fail "KEYS $name: Start did not type one Return"
|
|
echo "$label: KEYS $name: ${typed//$'\n'/; }"
|
|
done
|
|
|
|
# KEYS through the keyboard: open, to the top, F5 held and let go, the
|
|
# string typed, closed.
|
|
mkdir -p "$kbdShots"
|
|
at=$startMs
|
|
kbd() {
|
|
printf '%d %s\n' "$1" "$2" >> "$work/kbd/input.txt"
|
|
}
|
|
kbd "$at" "buttons square"
|
|
kbd "$((at + kbdPressMs))" "buttons none"
|
|
kbd "$((at + kbdHoldMs))" "snapshot $kbdShots/open.png"
|
|
at=$((at + 2 * kbdHoldMs))
|
|
kbd "$at" "buttons ltrigger"
|
|
kbd "$((at + kbdPressMs))" "buttons none"
|
|
kbd "$((at + kbdHoldMs))" "snapshot $kbdShots/top.png"
|
|
at=$((at + 2 * kbdHoldMs))
|
|
while read -r step what; do
|
|
if [ "$step" = move ]; then
|
|
kbd "$at" "buttons $what"
|
|
kbd "$((at + kbdPressMs))" "buttons none"
|
|
at=$((at + kbdStepMs))
|
|
continue
|
|
fi
|
|
kbd "$at" "buttons cross"
|
|
case "$what" in
|
|
F5)
|
|
kbd "$((at + kbdHoldMs))" "snapshot $kbdShots/f5Held.png"
|
|
kbd "$((at + 2 * kbdHoldMs))" "buttons none"
|
|
kbd "$((at + 3 * kbdHoldMs))" "snapshot $kbdShots/f5Up.png"
|
|
at=$((at + 4 * kbdHoldMs))
|
|
;;
|
|
SHIFT)
|
|
kbd "$((at + kbdPressMs))" "buttons none"
|
|
kbd "$((at + kbdHoldMs))" "snapshot $kbdShots/shift.png"
|
|
at=$((at + 2 * kbdHoldMs))
|
|
;;
|
|
*)
|
|
kbd "$((at + kbdPressMs))" "buttons none"
|
|
at=$((at + kbdStepMs))
|
|
;;
|
|
esac
|
|
done < <("$(joeyTool pspKeyboardPath)" "$kbdSource" key:F5 "type:$kbdTarget")
|
|
kbd "$at" "buttons square"
|
|
kbd "$((at + kbdPressMs))" "buttons none"
|
|
kbd "$((at + kbdHoldMs))" "snapshot $kbdShots/closed.png"
|
|
kbd "$((at + 2 * kbdHoldMs))" "buttons select"
|
|
runScript "$keysDir" kbd
|
|
|
|
|
|
# pixelIs SHOT X Y WANT WHAT -- the snapshot's pixel at X,Y must be WANT.
|
|
pixelIs() {
|
|
local got
|
|
if [ ! -f "$kbdShots/$1.png" ]; then
|
|
fail "keyboard $1: no snapshot"
|
|
return
|
|
fi
|
|
got=$("$(joeyTool pngProbe)" pixel "$kbdShots/$1.png" "$2" "$3")
|
|
echo "$label: keyboard $1: $5 $got"
|
|
[ "$got" = "$4" ] || fail "keyboard $1: $5 is $got, want $4"
|
|
}
|
|
|
|
|
|
pixelIs open "$kbdX" "$kbdBottomY" "$kbdBorder" "the bottom of the screen"
|
|
pixelIs top "$kbdX" "$kbdTopY" "$kbdBorder" "the top of the screen"
|
|
pixelIs top "$kbdX" "$kbdBottomY" "$kbdNone" "the bottom of the screen"
|
|
pixelIs f5Held "$f5X" "$f5Y" "$returnLit" "KEYS's F5 cell"
|
|
pixelIs f5Up "$f5X" "$f5Y" "$returnUnlit" "KEYS's F5 cell"
|
|
pixelIs shift "$shiftX" "$shiftY" "$returnLit" "KEYS's Shift cell"
|
|
pixelIs closed "$kbdX" "$kbdTopY" "$kbdNone" "the top of the screen"
|
|
if [ -f "$kbdShots/closed.png" ]; then
|
|
typed=$("$(joeyTool keysRow)" "$kbdShots/closed.png" "$kbdTarget" --size "$screenSize" --at "$stageX,$typedRow" --scale 1) || fail "keyboard: KEYS did not receive \"$kbdTarget\""
|
|
echo "$label: keyboard typed: ${typed//$'\n'/; }"
|
|
fi
|
|
|
|
if [ -n "$fails" ]; then
|
|
echo "$label: FAIL - $fails" >&2
|
|
exit 1
|
|
fi
|
|
echo "$label: PASS (JOY: ${#joySteps[@]} d-pad, button and nub states read back from the screen, stick B unplugged, resting while the keyboard is open; Start held KEYS's Return and typed one; the keyboard shown, moved, F5 held and \"$kbdTarget\" typed on it; Select ended all three)"
|