# 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 DVX gates (scripts/verify-dvx-*.sh, scripts/bench-dvx.sh) and # joey-run: a DVX installation staged in a scratch folder -- a copy of the # runtime the installer unpacked from DVX's dist/dvx.zip (toolchains/opt/dvx, # without its SDK) plus JoeyLib programs from build/dvx/bin -- whose DVX.INI makes # one of them the desktop, so DVX boots straight into it; and the DOSBox # Staging run of that folder as drive C:. Nothing here writes to the DVX # project or to toolchains/opt/dvx. Source after joeyEnv.sh. A project built # on make/project.mk sets DVX_BIN and DVX_VENDOR to its own before sourcing. DVX_GATE="$(basename "$0" .sh)" DVX_BIN="${DVX_BIN:-$JOEY_REPO/build/dvx/bin}" # UBER's probe build (make dvx-probe), packaged apart from bin/. DVX_PROBE_DIR="${DVX_PROBE_DIR:-$JOEY_REPO/build/dvx/probe}" # The folder a program installs in under apps/ (make/dvx.mk's DVX_VENDOR; for # JoeyLib's examples make/examples.mk's, asked of make), and the name of its # log. DVX_VENDOR="${DVX_VENDOR:-$(make -s -f "$JOEY_REPO/make/dvx.mk" --eval 'dvxVendor: ; @echo $(DVX_VENDOR)' dvxVendor 2> /dev/null)}" DVX_LOG_NAME=joeylog.txt # The port's own settings file in a program's user folder, and its # full-screen and square-pixels switches (src/dvx/hal.c). DVX_SETTINGS_FILE=JOEYLIB.INI DVX_SETTINGS_SECTION=display DVX_SETTINGS_FULL_SCREEN=fullScreen DVX_SETTINGS_SQUARE=squarePixels # DVX.LOG's line when a program's full-screen mode is set (the port asks for # the stage's size at 8 bits a pixel). DVX_FULL_SCREEN_LOG="Video: full screen 320x200 at 8 bpp" # DVX.LOG's line when a program cannot be loaded (a missing file, a name # that is not 8.3, an unresolved import). DVX_LOAD_FAILED_LOG="DXE load failed" # The runtime parts of toolchains/opt/dvx (all but the SDK and SETUP.EXE). DVX_RUNTIME="dvx.exe system libs widgets apps shared" # The desktop the gates run: 640x480 in 16-bit color, DVX's own DOS test # mode, where a program's window shows the stage at 1x. A caller may set # others before dvxStage (joey-run shows 1024x768, the stage at 3x). DVX_VIDEO_W=${DVX_VIDEO_W:-640} DVX_VIDEO_H=${DVX_VIDEO_H:-480} DVX_VIDEO_BPP=${DVX_VIDEO_BPP:-16} # DOSBox Staging as DVX's harness runs it: an S3 card (VBE 2.0 with a linear # frame buffer, which DVX needs) and the host's full speed. DVX_DOSBOX_MACHINE=svga_s3 DVX_DOSBOX_MEM_MB=32 # dvxAppDir PROGRAM -- print the built folder of a program (its make/dvx.mk # name: uber, keys, ...): the .app, its help and its DATA. dvxAppDir() { printf '%s\n' "$DVX_BIN/apps/$DVX_VENDOR/$1" } # dvxNeedBuild PROGRAM... -- exit 2 unless each program is built. dvxNeedBuild() { local prog for prog in "$@"; do if [ ! -f "$(dvxAppDir "$prog")/$prog.app" ]; then echo "$DVX_GATE: $(dvxAppDir "$prog")/$prog.app is missing; run: make dvx" >&2 exit 2 fi done } # dvxStage DIR APPDIR... -- stage DVX in DIR/DVX with the programs in the # APPDIRs (each a folder holding NAME.app, from dvxAppDir or anywhere else), # installed as apps/VENDOR/NAME. The first one is the desktop. DIR may hold # an earlier staging: the runtime and the programs are copied over it, and # what DVX wrote there (its user folder, USER\: its settings and log, and # each program's) stays. dvxStage() { local dir="$1" local part local app local name shift mkdir -p "$dir/DVX/apps/$DVX_VENDOR" for part in $DVX_RUNTIME; do cp -a "$DVX_ROOT/$part" "$dir/DVX/" done for app in "$@"; do name="$(basename "$app")" rm -rf "${dir:?}/DVX/apps/$DVX_VENDOR/$name" cp -a "$app" "$dir/DVX/apps/$DVX_VENDOR/" done name="$(basename "$1")" dvxIniSet "$(dvxIni "$dir")" "apps/$DVX_VENDOR/$name/$name.app" } # dvxSettings DIR PROGRAM SWITCH... -- have PROGRAM start with the port's # own display switches on (DVX_SETTINGS_FULL_SCREEN, DVX_SETTINGS_SQUARE) in # the DVX staged in DIR: its JOEYLIB.INI, [display], in the program's user # folder, as the Game menu leaves it. DOS names are case-blind, so an # existing folder of any case is used. dvxSettings() { local conf local key conf="$(find "$1/DVX" -ipath "*/user/apps/$DVX_VENDOR/$2" -type d -print -quit 2>/dev/null)" if [ -z "$conf" ]; then conf="$(dvxUserDir "$1")/apps/$DVX_VENDOR/$2" mkdir -p "$conf" fi printf '[%s]\r\n' "$DVX_SETTINGS_SECTION" > "$conf/$DVX_SETTINGS_FILE" for key in "${@:3}"; do printf '%s = true\r\n' "$key" >> "$conf/$DVX_SETTINGS_FILE" done } # dvxFullScreen DIR PROGRAM -- have PROGRAM start on the whole screen # (dvxSettings). dvxFullScreen() { dvxSettings "$1" "$2" "$DVX_SETTINGS_FULL_SCREEN" } # dvxUserDir DIR -- print the user folder (USER\) of the DVX staged in DIR, # where DVX writes everything (its DVX.INI and DVX.LOG, and each program's # settings and saves), making it when DVX has not yet. DOS names are # case-blind, so a folder of any case is DVX's. dvxUserDir() { local user user="$(find "$1/DVX" -mindepth 1 -maxdepth 1 -type d -iname user -print -quit 2>/dev/null)" if [ -z "$user" ]; then user="$1/DVX/user" mkdir -p "$user" fi printf '%s\n' "$user" } # dvxIni DIR -- print the DVX.INI the DVX staged in DIR starts with: the user # folder's, made from the one DVX ships (SYSTEM\DVX.INI) when it is not there # yet, as DVX's first start makes it. dvxIni() { local user local ini user="$(dvxUserDir "$1")" ini="$(find "$user" -maxdepth 1 -type f -iname dvx.ini -print -quit)" if [ -z "$ini" ]; then ini="$user/DVX.INI" cp "$(find "$1/DVX/system" -maxdepth 1 -type f -iname dvx.ini -print -quit)" "$ini" fi printf '%s\n' "$ini" } # dvxIniSet INI DESKTOP -- DVX.INI for a run: DESKTOP as the desktop program, # no first-run Welcome window, the gate video mode, and no desktop sounds (a # recording gate must hear only the program). DOS line ends are kept. dvxIniSet() { awk -v desktop="$2" -v width="$DVX_VIDEO_W" -v height="$DVX_VIDEO_H" -v bpp="$DVX_VIDEO_BPP" ' BEGIN { RS = "\r\n" sets["shell/desktop"] = desktop sets["video/width"] = width sets["video/height"] = height sets["video/bpp"] = bpp } { line = $0 stripped = $0 gsub(/^[ \t\r\n\v\f]+|[ \t\r\n\v\f]+$/, "", stripped) if (substr(stripped, 1, 1) == "[") { section = stripped gsub(/^[][]+|[][]+$/, "", section) section = tolower(section) } else if (index(stripped, "=") > 0 && substr(stripped, 1, 1) != ";") { key = substr(stripped, 1, index(stripped, "=") - 1) gsub(/^[ \t\r\n\v\f]+|[ \t\r\n\v\f]+$/, "", key) if (section == "shell" && tolower(key) == "welcome") { next } if (section == "sounds") { line = key " =" } else if ((section "/" tolower(key)) in sets) { line = key " = " sets[section "/" tolower(key)] } } out = out (NR > 1 ? "\r\n" : "") line } END { printf "%s%s", out, (RT == "" ? "" : "\r\n") > FILENAME }' "$1" } # dvxDosboxConf FILE DIR [SBTYPE] -- a DOSBox Staging configuration that boots # DIR/DVX as C:\DVX. SBTYPE (e.g. sb16) adds a Sound Blaster; without it the # machine is silent (DVX then runs without sound, as on a PC without a card). dvxDosboxConf() { local sbType="${3:-none}" local nosound=true [ "$sbType" = none ] || nosound=false cat > "$1" </dev/null } # dvxAwaitLog DIR PROGRAM REGEX SECONDS PID -- wait up to SECONDS (or until PID # exits, or DVX.LOG says a program failed to load) for PROGRAM's log to hold # a line matching REGEX; print its path if it does. dvxAwaitLog() { local log local waited for ((waited = 0; waited < $4; waited++)); do log="$(dvxProgramLog "$1" "$2")" if [ -n "$log" ] && tr -d '\r' < "$log" | grep -qE "$3"; then printf '%s\n' "$log" return 0 fi joeyAlive "$5" || return 1 dvxDvxLog "$1" 400 | grep -q "$DVX_LOAD_FAILED_LOG" && return 1 sleep 1 done return 1 } # dvxKeysTyped LOG TARGET -- KEYS's typed line in LOG (its joeylog.txt: the # hex of every character it received) must be TARGET's bytes. dvxKeysTyped() { local typed local want typed="$(tr -d '\r' < "$1" | sed -n 's/^typed=//p' | tail -1)" want="$(printf '%s' "$2" | od -An -tx1 | tr -d ' \n' | tr 'a-f' 'A-F')" echo "$DVX_GATE: log typed=$typed" [ "$typed" = "$want" ] } # dvxDvxLog DIR -- print the tail of the staged DVX's own log (DVX.LOG), the # place a program that failed to load is explained. dvxDvxLog() { local log log="$(find "$1/DVX" -maxdepth 2 -ipath "*/user/dvx.log" -print -quit)" [ -n "$log" ] && tr -d '\r' < "$log" | tail -"${2:-15}" return 0 } # dvxDvxLogCopy DIR FILE -- the staged DVX's whole DVX.LOG as FILE (Unix line # ends); FILE is left out when there is none. dvxDvxLogCopy() { local log log="$(find "$1/DVX" -maxdepth 2 -ipath "*/user/dvx.log" -print -quit)" [ -n "$log" ] && tr -d '\r' < "$log" > "$2" return 0 }