237 lines
8.7 KiB
Bash
Executable file
237 lines
8.7 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-x68000-input.sh - Headless typed-character acceptance gate for
|
|
# the X68000 port. Boots Human68k under the patched MAME with KEYS.X in
|
|
# AUTOEXEC.BAT, waits for the example's A5A5 sentinel to appear on the
|
|
# screen, types the RetroNet definition-of-done string on the emulated
|
|
# keyboard, and reads the verification scanline back out of text VRAM.
|
|
# KEYS encodes every character it received from jlInputGetChar as nibble
|
|
# pixel pairs, so the row must equal the typed ASCII exactly.
|
|
#
|
|
# Then the named keys the X68000 gives a second key for (src/x68000/hal.c's
|
|
# kKeyAlias): the keypad's 8/2/4/6 as the arrows, its ENTER as Return, and
|
|
# OPT.2 as Alt, each held alone and read back from KEYS's cells, and the
|
|
# real Up and the keypad's 8 held together, then either let go, which must
|
|
# leave Up down.
|
|
#
|
|
# Text-plane geometry: four 1bpp planes 0x20000 apart at 0xE00000, 128
|
|
# bytes per row, leftmost pixel in bit 7; the 320x200 stage is centered at
|
|
# origin (96, 156). Verification row = stage y 198 -> screen y 354.
|
|
#
|
|
# bash scripts/verify-x68000-input.sh
|
|
set -uo pipefail
|
|
|
|
repo=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
source "$repo/scripts/lib/joeyEnv.sh"
|
|
joeyNeed MAME mame
|
|
joeyNeed X68K_DIR x68kFirmware
|
|
joeyNeedFile "$X68K_DIR/roms/x68000/cgrom.dat" x68kCgrom
|
|
source "$repo/scripts/lib/x68kDisk.sh"
|
|
WALL="${X68K_INPUT_WALL:-900}"
|
|
|
|
target="192.168.1.10:6510"
|
|
|
|
[ -f "$X68K_SYSTEM_DISK" ] || { echo "verify-x68000-input: missing $X68K_SYSTEM_DISK" >&2; exit 2; }
|
|
[ -f "$repo/build/x68000/bin/KEYS.X" ] || { echo "verify-x68000-input: build KEYS.X first (make -f make/x68000.mk)" >&2; exit 2; }
|
|
|
|
work=$(mktemp -d -t joey-x68input.XXXXXX)
|
|
trap 'rm -rf "$work"' EXIT
|
|
|
|
x68kBootDisk "$work/boot.xdf" 'A:\KEYS.X' || exit 1
|
|
x68kXdf add "$work/boot.xdf" "$repo/build/x68000/bin/KEYS.X" KEYS.X >/dev/null || exit 1
|
|
|
|
cat > "$work/input.lua" <<LUA
|
|
local cpu = manager.machine.devices[":maincpu"]
|
|
local mem = cpu.spaces["program"]
|
|
local nat = manager.machine.natkeyboard
|
|
local frame = 0
|
|
local typedAt = 0
|
|
local keysAt = 0
|
|
local done = false
|
|
|
|
-- KEYS's row 4 cells (examples/keys: 28x26 cells 32 and 30 apart from
|
|
-- (2, 2)) for Return, Alt, Up, Down, Left, and Right: their centers' x, and
|
|
-- the row's center y. A lit cell is color 2.
|
|
local CELL_X = { 80, 144, 176, 208, 240, 272 }
|
|
local CELL_Y = 135
|
|
local LIT = 2
|
|
local STEP_FRAMES = 30
|
|
local FIELDS = { "Tenkey 8", "Tenkey 2", "Tenkey 4", "Tenkey 6", "Tenkey ENTER", "OPT.2", "Cursor Up" }
|
|
-- Each step holds these fields (every other is let go) and wants these
|
|
-- cells lit, in CELL_X's order.
|
|
local STEPS = {
|
|
{ "rest", {}, "000000" },
|
|
{ "keypad 8 as Up", { "Tenkey 8" }, "001000" },
|
|
{ "keypad 2 as Down", { "Tenkey 2" }, "000100" },
|
|
{ "keypad 4 as Left", { "Tenkey 4" }, "000010" },
|
|
{ "keypad 6 as Right", { "Tenkey 6" }, "000001" },
|
|
{ "keypad ENTER as Return", { "Tenkey ENTER" }, "100000" },
|
|
{ "OPT.2 as Alt", { "OPT.2" }, "010000" },
|
|
{ "Up", { "Cursor Up" }, "001000" },
|
|
{ "Up and keypad 8", { "Cursor Up", "Tenkey 8" }, "001000" },
|
|
{ "Up, keypad 8 let go", { "Cursor Up" }, "001000" },
|
|
{ "keypad 8, Up let go", { "Tenkey 8" }, "001000" },
|
|
{ "rest again", {}, "000000" },
|
|
}
|
|
local fields = {}
|
|
|
|
-- Stage pixel (px, y) -> its 4-bit color index. The port presents to the
|
|
-- TEXT plane (src/x68000/hal.c X68K_PRESENT_TEXT): four bit planes 128 KB
|
|
-- apart at 0xE00000, 128 bytes per row, leftmost pixel in bit 7, the stage
|
|
-- centered at (96, 156). (Before 2026-09-24 it drew the graphics plane at
|
|
-- 0xC00000, which this gate used to read.)
|
|
-- The text plane holds two pages, and the port flips between them with the
|
|
-- text Y scroll (CRTC R11, which reads back): rows are counted from the page
|
|
-- on screen.
|
|
local function shownRow()
|
|
return mem:read_u16(0xE80016) & 0x3FF
|
|
end
|
|
|
|
local function textPix(px, y)
|
|
local sx = 96 + px
|
|
local addr = 0xE00000 + (156 + shownRow() + y) * 128 + (sx >> 3)
|
|
local bit = 7 - (sx & 7)
|
|
local v = 0
|
|
for p = 0, 3 do
|
|
v = v | (((mem:read_u8(addr + p * 0x20000) >> bit) & 1) << p)
|
|
end
|
|
return v
|
|
end
|
|
|
|
local function pix(px)
|
|
return textPix(px, 198)
|
|
end
|
|
|
|
local function findField(name)
|
|
for _, port in pairs(manager.machine.ioport.ports) do
|
|
if port.fields[name] then return port.fields[name] end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
local function hold(names)
|
|
local held = {}
|
|
for _, name in ipairs(names) do held[name] = true end
|
|
for _, name in ipairs(FIELDS) do fields[name]:set_value(held[name] and 1 or 0) end
|
|
end
|
|
|
|
local function cells()
|
|
local lit = {}
|
|
for i, x in ipairs(CELL_X) do
|
|
lit[#lit + 1] = textPix(x, CELL_Y) == LIT and "1" or "0"
|
|
end
|
|
return table.concat(lit)
|
|
end
|
|
|
|
local function report()
|
|
local bytes = {}
|
|
for i = 0, 39 do
|
|
bytes[#bytes + 1] = string.format("%02X", (pix(2 * i) << 4) | pix(2 * i + 1))
|
|
end
|
|
io.write(string.format("VERIFY-X68K-INPUT frame=%d typedAt=%d row=%s\n",
|
|
frame, typedAt, table.concat(bytes)))
|
|
io.flush()
|
|
end
|
|
|
|
emu.register_frame_done(function()
|
|
frame = frame + 1
|
|
if done then return end
|
|
if typedAt == 0 and frame > 600 and frame % 100 == 0 then
|
|
-- KEYS is up once initialPaint stamped the A5A5 sentinel.
|
|
if pix(0) == 0xA and pix(1) == 0x5 and pix(2) == 0xA and pix(3) == 0x5 then
|
|
typedAt = frame + 60
|
|
end
|
|
end
|
|
if typedAt ~= 0 and frame == typedAt then
|
|
nat:post("$target")
|
|
end
|
|
if typedAt ~= 0 and keysAt == 0 and frame >= typedAt + 3000 then
|
|
report()
|
|
for _, name in ipairs(FIELDS) do
|
|
fields[name] = findField(name)
|
|
if not fields[name] then
|
|
io.write("VERIFY-X68K-KEY missing field " .. name .. "\n")
|
|
io.flush()
|
|
done = true
|
|
manager.machine:exit()
|
|
return
|
|
end
|
|
end
|
|
keysAt = frame
|
|
end
|
|
if keysAt ~= 0 then
|
|
local step = (frame - keysAt) // STEP_FRAMES + 1
|
|
local into = (frame - keysAt) % STEP_FRAMES
|
|
if step > #STEPS then
|
|
done = true
|
|
manager.machine:exit()
|
|
return
|
|
end
|
|
if into == 0 then
|
|
hold(STEPS[step][2])
|
|
elseif into == STEP_FRAMES - 1 then
|
|
io.write(string.format("VERIFY-X68K-KEY %s|%s|%s\n", STEPS[step][1], cells(), STEPS[step][3]))
|
|
io.flush()
|
|
end
|
|
end
|
|
if frame > 30000 then
|
|
done = true
|
|
io.write("VERIFY-X68K-INPUT timeout: sentinel never appeared\n")
|
|
io.flush()
|
|
manager.machine:exit()
|
|
end
|
|
end)
|
|
LUA
|
|
|
|
# Run from the work dir so MAME's cfg/ and nvram/ droppings land there
|
|
# instead of the caller's cwd.
|
|
cd "$work"
|
|
out=$(timeout -s KILL "$WALL" "$MAME" x68000 -bios ipl10 \
|
|
-rompath "$X68K_DIR/roms" -flop1 "$work/boot.xdf" \
|
|
-video none -sound none -nothrottle \
|
|
-autoboot_script "$work/input.lua" </dev/null 2>&1) || true
|
|
|
|
line=$(echo "$out" | grep -E '^VERIFY-X68K-INPUT ' | tail -1)
|
|
echo "$line"
|
|
if [ -z "$line" ] || echo "$line" | grep -q timeout; then
|
|
echo "verify-x68000-input: FAIL - KEYS never came up" >&2
|
|
echo "$out" | tail -10 >&2
|
|
exit 1
|
|
fi
|
|
|
|
row=$(echo "$line" | sed -E 's/.*row=([0-9A-Fa-f]+).*/\1/')
|
|
expect="A5A5"
|
|
expect+=$(printf '%02X' ${#target})
|
|
expect+="00"
|
|
expect+=$(printf '%s' "$target" | od -A n -t x1 | tr -d ' \n' | tr 'a-f' 'A-F')
|
|
got=${row:0:${#expect}}
|
|
if [ "$got" != "$expect" ]; then
|
|
echo "verify-x68000-input: FAIL" >&2
|
|
echo " expected row prefix: $expect" >&2
|
|
echo " got: $got" >&2
|
|
exit 1
|
|
fi
|
|
|
|
if echo "$out" | grep -q '^VERIFY-X68K-KEY missing'; then
|
|
echo "verify-x68000-input: FAIL - $(echo "$out" | grep '^VERIFY-X68K-KEY missing' | sed 's/^VERIFY-X68K-KEY //')" >&2
|
|
exit 1
|
|
fi
|
|
keyFails=""
|
|
keySteps=0
|
|
while IFS='|' read -r name lit want; do
|
|
keySteps=$((keySteps + 1))
|
|
echo "verify-x68000-input: $name: Return/Alt/Up/Down/Left/Right $lit"
|
|
[ "$lit" = "$want" ] || keyFails="${keyFails:+$keyFails; }$name: lit $lit, want $want"
|
|
done < <(echo "$out" | grep -E '^VERIFY-X68K-KEY ' | sed 's/^VERIFY-X68K-KEY //')
|
|
if [ "$keySteps" = 0 ]; then
|
|
echo "verify-x68000-input: FAIL - the named-key steps did not run" >&2
|
|
exit 1
|
|
fi
|
|
if [ -n "$keyFails" ]; then
|
|
echo "verify-x68000-input: FAIL - $keyFails" >&2
|
|
exit 1
|
|
fi
|
|
echo "verify-x68000-input: PASS (typed \"$target\", all ${#target} chars arrived via jlInputGetChar; $keySteps named-key steps: the keypad's arrows and ENTER, OPT.2 as Alt, Up held with its alias)"
|