#!/usr/bin/env bash # runViaFinder.sh — boot real GS/OS 6.0.2 in MAME, drive Finder via # Lua keyboard automation to launch a user OMF, sample memory at # specific frames to verify the program executed. # # Usage: runViaFinder.sh [--data /VOL/PATH/NAME=local_file]... # --check =... # The OMF file is injected as /DATA/HELLO on a separate 800K data # disk; Lua drives Finder to open the Data volume and launch HELLO. # Each --data option also injects an arbitrary file (raw bytes) onto # the disk at the requested ProDOS path — used for stdio smoke tests # that need a known file present at runtime (`tmpfile`, `posixfile` # GS/OS path, `cxxstdlib::filesystem`). # # /VOL is one of /DATA (the injected data disk, default) or /SYS (the # boot disk). Sub-directories are auto-created via cadius CREATEFOLDER. # The on-disk basename is the trailing component of the path; the file # is dropped as a ProDOS type=$06 (BIN) so GS/OS treats it as a plain # readable file via gsosOpen. Pass multiple `--data` options to inject # more than one file. # # Memory checks happen at frame 5400 (~90s emulated, well after the # launch path completes) and exit 0 / 1 depending on whether each # requested address holds the requested value. # # Requires: # - tools/gsos/sys602.po (GS/OS 6.0.2 boot disk) # - /tmp/cadius/cadius (forked-file-aware ProDOS tool) # - mame apple2gs in PATH set -euo pipefail OMF="$1" shift [ -f "$OMF" ] || { echo "missing: $OMF" >&2; exit 2; } # Collect optional --data injections before --check. DATA_INJECTS=() while [ $# -gt 0 ] && [ "$1" = "--data" ]; do [ $# -ge 2 ] || { echo "usage: $0 [--data /VOL/PATH/NAME=path]... --check =..." >&2; exit 2; } DATA_INJECTS+=("$2") shift 2 done [ "${1:-}" = "--check" ] || { echo "usage: $0 [--data /VOL/PATH/NAME=path]... --check =..." >&2; exit 2; } shift PROJECT_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" CADIUS=${CADIUS:-$PROJECT_ROOT/tools/cadius/cadius} SYSDISK=${SYSDISK:-$PROJECT_ROOT/tools/gsos/sys602.po} [ -x "$CADIUS" ] || { echo "cadius not found at $CADIUS" >&2; exit 2; } [ -f "$SYSDISK" ] || { echo "sysdisk not found at $SYSDISK" >&2; exit 2; } WORK=$(mktemp -d -t finderlaunch.XXXXXX) trap 'rm -rf "$WORK"' EXIT cp "$SYSDISK" "$WORK/disk.po" # Create a separate 800K data disk and put HELLO on it. Keeps the # boot disk untouched (and avoids the "20K free" limit on sys602.po # that fails for OMFs > ~15K). "$CADIUS" CREATEVOLUME "$WORK/data.po" DATA 800KB >/dev/null cp "$OMF" "$WORK/HELLO#B30000" "$CADIUS" ADDFILE "$WORK/data.po" /DATA "$WORK/HELLO#B30000" >/dev/null # Inject extra data files. Path syntax: /VOL/[sub/dirs/]NAME=local_file. # Each gets type=$06 (BIN, generic data) so GS/OS treats it as a # plain file readable via gsosOpen. Sub-directories are CREATEFOLDER'd # as needed (cadius is idempotent — a CREATEFOLDER on an existing path # is a no-op). for inj in "${DATA_INJECTS[@]}"; do targetPath="${inj%=*}" srcPath="${inj#*=}" [ -f "$srcPath" ] || { echo "missing data injection source: $srcPath" >&2; exit 2; } # Map the user-facing volume prefix (/SYS or /DATA) to (a) the .po # file cadius mutates and (b) the volume name as known to the disk # image itself (which differs — sys602.po is `/System.Disk`). case "$targetPath" in /SYS/*) targetDisk="$WORK/disk.po" volPrefix="/System.Disk" relPath="${targetPath#/SYS/}";; /DATA/*) targetDisk="$WORK/data.po" volPrefix="/DATA" relPath="${targetPath#/DATA/}";; *) echo "--data path must start with /SYS/ or /DATA/: $targetPath" >&2 exit 2;; esac inVolName="${relPath##*/}" # trailing component = filename subDirs="${relPath%"$inVolName"}" # leading dirs (with trailing /) subDirs="${subDirs%/}" # strip trailing / # Walk sub-dirs and CREATEFOLDER each one progressively. cadius # is idempotent on CREATEFOLDER for an already-existing path, so # callers can re-inject without manually pruning. if [ -n "$subDirs" ]; then accum="$volPrefix" IFS='/' read -r -a dirParts <<<"$subDirs" for part in "${dirParts[@]}"; do accum="$accum/$part" "$CADIUS" CREATEFOLDER "$targetDisk" "$accum" >/dev/null 2>&1 || true done parentDir="$volPrefix/$subDirs" else parentDir="$volPrefix" fi cp "$srcPath" "$WORK/${inVolName}#060000" "$CADIUS" ADDFILE "$targetDisk" "$parentDir" "$WORK/${inVolName}#060000" >/dev/null done LUA_CHECKS="" EXPECTS=() for pair in "$@"; do [ "$pair" = "--check" ] && continue addr="${pair%=*}"; val="${pair#*=}" EXPECTS+=("$pair") LUA_CHECKS="$LUA_CHECKS print(string.format('MAME-READ %s=%02x', '$addr', mem:read_u8($addr)))"$'\n' done cat > "$WORK/finder.lua" <= steps[idx][1] do steps[idx][2]() idx = idx + 1 end end) LUA OUT=$(timeout 240 mame apple2gs -rompath "$PROJECT_ROOT/tools/mame/roms" \ -window -nothrottle -sound none \ -seconds_to_run 200 -flop3 "$WORK/disk.po" -flop4 "$WORK/data.po" \ -autoboot_script "$WORK/finder.lua" &1) # Verify each expected value. fail=0 for pair in "${EXPECTS[@]}"; do addr="${pair%=*}"; want="${pair#*=}" line=$(echo "$OUT" | grep "MAME-READ $addr=" | tail -1) got=$(echo "$line" | sed -E 's/.*=([0-9a-f]+).*/\1/') # Compare numerically (handles case differences and 0x prefix variants). gotN=$(printf '%d' "0x$got" 2>/dev/null || echo -1) wantN=$(printf '%d' "$want" 2>/dev/null || echo -2) if [ "$gotN" = "$wantN" ]; then echo " $addr = 0x$got (want $want) ✓" else echo " $addr = 0x$got (want $want) ✗" fail=1 fi done exit $fail