# shellcheck shell=bash # Part of JoeyLib, Copyright (c) 2026 Scott Duensing, under the MIT License. # See LICENSE.md for license information. # SPDX-License-Identifier: MIT # # Shared by the PSP gates (scripts/bench-psp.sh, scripts/verify-psp-*.sh) and # joey-run: run a PSP program (make psp: bin/NAME/EBOOT.PBP and its DATA) # under PPSSPPHeadless, the installer's patched headless PPSSPP (ppsspp # component, installer/patches/ppsspp). Source after joeyEnv.sh. # # PPSSPPHeadless keeps its memory stick in $HOME/.ppsspp, so it always runs # with HOME pointing at a work folder: the program folder goes on that # memory stick where a PSP keeps it, PSP/GAME/NAME, and runs from there # (ms0:/PSP/GAME/NAME is its current directory, as on a PSP). Every run # checks every memory access as a PSP does (--strict-mem, on the IR JIT, # the core that checks alignment): an unaligned or wild access stops the # program, and pspStrictCheck reports it. # # pspNeed exit 2 unless PPSSPPHeadless is installed # pspStage WORK DIR copy the program folder DIR onto WORK's # memory stick; prints the folder's path there # pspRun WORK FOLDER SECS ARGS start the program in FOLDER (pspStage's) in # the background for at most SECS seconds of # real time, with PPSSPPHeadless's ARGS too; # its output goes to WORK/ppsspp.log; sets # PSP_PID # pspWait wait for the run to end by itself # pspStrictCheck WORK DIR 0 unless the run of the program folder DIR # stopped at a bad memory access; then prints # where, and the line psp-addr2line finds for # it in the program's ELF (make/psp.mk keeps # bin/NAME's in elf/NAME.elf) # pspStop stop the run PSP_PID="" # Real seconds a run is given beyond SECS before it is killed outright. PSP_KILL_GRACE=30 pspNeed() { joeyNeed PPSSPP_HEADLESS emulator:psp } pspStage() { local folder folder="$1/.ppsspp/PSP/GAME/$(basename "$2")" mkdir -p "$folder" cp -r "$2/." "$folder/" printf '%s\n' "$folder" } pspRun() { local work="$1" local folder="$2" local seconds="$3" shift 3 HOME="$work" timeout -s KILL "$((seconds + PSP_KILL_GRACE))" "$PPSSPP_HEADLESS" "$folder/EBOOT.PBP" --strict-mem --jit-ir --timeout="$seconds" "$@" > "$work/ppsspp.log" 2>&1 & PSP_PID=$! } pspWait() { wait "$PSP_PID" 2> /dev/null || true PSP_PID="" } pspStrictCheck() { local line local pc local elf line="$(grep -m1 '^STRICT-MEM: ' "$1/ppsspp.log" 2> /dev/null)" || return 0 echo "$(basename "$0"): the program stopped at a bad memory access, as a PSP would:" >&2 sed -n '/^STRICT-MEM: /,/^$/p' "$1/ppsspp.log" >&2 pc="$(sed -n 's/.*, pc \([0-9a-f]*\)$/\1/p' <<< "$line")" elf="$(dirname "$(dirname "$2")")/elf/$(basename "$2").elf" if [ -n "$pc" ] && [ -f "$elf" ] && [ -n "${PSPDEV:-}" ]; then echo " pc $pc: $("$PSPDEV/bin/psp-addr2line" -f -e "$elf" "0x$pc" | tr '\n' ' ')" >&2 fi return 1 } pspStop() { local child if [ -n "$PSP_PID" ]; then if child="$(joeyChild "$PSP_PID")"; then joeyStop "$child" fi joeyStop "$PSP_PID" fi PSP_PID="" }