joeylib2/scripts/lib/win16Gate.sh
2026-10-19 15:46:47 -05:00

185 lines
7.4 KiB
Bash

# 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 Win16 gates (scripts/bench-win16.sh, scripts/verify-win16-*.sh)
# and joey-run: run a Win16 program (make win16) on Windows for Workgroups
# 3.11 under DOSBox Staging (scripts/dosbox-win16.conf), headless on a
# private Xvfb display (or, WIN16_HEADLESS=0, in a window on $DISPLAY).
# Windows is a copy of the installer's (component win311, WIN16_C) made for
# the run, so a run never changes it; the program's folder is drive D:, so
# what it writes lands there, and it is Windows' shell, so Windows ends (and
# DOSBox with it) when the program does. The copy runs JoeyLib's own drivers:
# KPJOY.DRV (component kpjoy, KPJOY_DRV), the joystick driver Windows 3.1
# lacks, and KPCOMM.DRV (component kpcomm, KPCOMM_DRV) in place of its
# COMM.DRV. Source after joeyEnv.sh.
#
# win16Need exit 2 unless Windows, its drivers and DOSBox
# are installed
# win16Stage WORK FOLDER EXE [ARGS...]
# WORK/c: a copy of Windows whose shell is EXE
# in FOLDER (drive D:), given ARGS (WIN.COM
# hands its command line to the shell; a shell=
# line with arguments is not loaded at all),
# with JoeyLib's drivers, and WinG's runtime
# when WIN16_WING=1 (the default once the wing
# component is installed; 0 runs without it)
# win16Start WORK [NAME=VALUE...]
# boot it in the background, the NAME=VALUEs in
# the DOS environment Windows starts from (the
# port's JOEYLIB_SCALE and the like); sets
# WIN16_DISPLAY, WIN16_GUARD (the timeout
# wrapper, which is DOSBox itself when
# WIN16_WALL=0: no cap) and WIN16_PID (DOSBox).
# Sound is off unless WIN16_SOUND=1 (then SDL
# plays it, through SDL_AUDIODRIVER if set);
# WIN16_DOSBOX_ARGS are more DOSBox arguments
# (--set serial1=...)
# win16Log FOLDER print the program's joeylog in FOLDER, if any
# (a DOS program makes it JOEYLOG.TXT)
# win16Window print DOSBox's X window
# win16Stop stop DOSBox and the display
#
# Win16 code only ever runs on DOSBox's emulated CPU: running it natively
# (the 32-bit Wine's winevdm) crashed the host's kernel.
WIN16_DISPLAY=""
WIN16_GUARD=""
WIN16_PID=""
WIN16_XVFB_PID=""
WIN16_FOLDER=""
WIN16_ARGS=""
WIN16_HEADLESS="${WIN16_HEADLESS:-1}"
WIN16_SOUND="${WIN16_SOUND:-0}"
if [ -n "${WING_DIR:-}" ] && [ -f "$WING_DIR/WING.DLL" ]; then
WIN16_WING="${WIN16_WING:-1}"
else
WIN16_WING=0
fi
WIN16_DOSBOX_ARGS=()
# A hard cap on DOSBox in seconds, whatever happens to the script that
# started it; 0 for none.
WIN16_WALL="${WIN16_WALL:-900}"
win16Need() {
joeyNeed DOSBOX dosbox
joeyNeed WIN16_C emulator:win16
joeyNeed KPJOY_DRV kpjoy
joeyNeed KPCOMM_DRV kpcomm
}
win16Stage() {
local work="$1"
WIN16_FOLDER="$2"
WIN16_ARGS="${*:4}"
rm -rf "${work:?}/c"
mkdir -p "$work"
cp -r "$WIN16_C" "$work/c"
cp "$KPJOY_DRV" "$work/c/WINDOWS/SYSTEM/KPJOY.DRV"
cp "$KPCOMM_DRV" "$work/c/WINDOWS/SYSTEM/KPCOMM.DRV"
"$(joeyTool winIni)" "$work/c/WINDOWS/SYSTEM.INI" \
set boot shell "D:\\$3" \
set boot comm.drv kpcomm.drv \
set drivers joystick kpjoy.drv
if [ "$WIN16_WING" = "1" ]; then
win16StageWinG "$work"
fi
}
# WinG's runtime into SYSTEM and its DVA.386 into [386Enh], as WinG's own
# setup does on Windows 3.x (this one has no VFLATD). WinG profiles the
# display the first time a program uses it and keeps the result in WIN.INI
# (WIN16_WING_PROFILE), which on a copy made afresh each run would be every
# run: about 165 s at 386SX-16 speed. Its result for this Windows (the S3
# driver at 800x600 in 256 colors), taken at that speed, goes in instead,
# under WinG's key for the display: the driver, the mode, the driver's
# version, the driver file's date as DOS keeps it (year, month and day in
# one 16-bit word, in hex) and WinG's version. The copy is dated the day it
# is made, so the key is made from the copy's own date.
WIN16_WING_DRIVER="S3TRIO.DRV"
WIN16_WING_PROFILE_HEAD="s3trio.drv800x600x8(0,12)v1.70.4.0"
WIN16_WING_PROFILE_TAIL="1.0.0.37"
WIN16_WING_PROFILE="ECHGECHGECHGECHGDDFFDDFFDDFFDDFFZZZZZZZZZZZZZZZZDDFGDGFGDDFFDDFG1F"
win16StageWinG() {
local file
local dosDate
local year
local month
local day
for file in WING.DLL WINGDE.DLL WINGDIB.DRV WINGPAL.WND DVA.386; do
cp "$WING_DIR/$file" "$1/c/WINDOWS/SYSTEM/$file"
done
# The driver's date as a DOS date word: years from 1980, month, day.
read -r year month day <<< "$(date -r "$1/c/WINDOWS/SYSTEM/$WIN16_WING_DRIVER" '+%Y %m %d')"
dosDate=$(printf '%04X' $((((year - 1980) << 9) | (10#$month << 5) | 10#$day)))
"$(joeyTool winIni)" "$1/c/WINDOWS/SYSTEM.INI" \
add 386Enh device "C:\\WINDOWS\\SYSTEM\\DVA.386"
"$(joeyTool winIni)" "$1/c/WINDOWS/WIN.INI" \
set WinG "$WIN16_WING_PROFILE_HEAD-$dosDate-$WIN16_WING_PROFILE_TAIL" "$WIN16_WING_PROFILE"
}
win16Start() {
local work="$1"
local assign
local envArgs=()
local soundArgs=(--set "nosound=true")
local procEnv=(XDG_CONFIG_HOME="$work/xdg" SDL_AUDIODRIVER=dummy)
local wrapper=()
shift
for assign in "$@"; do
envArgs+=(-c "SET $assign")
done
if [ "$WIN16_SOUND" = "1" ]; then
soundArgs=()
procEnv=(XDG_CONFIG_HOME="$work/xdg")
fi
[ "$WIN16_WALL" = "0" ] || wrapper=(timeout -s KILL "$WIN16_WALL")
mkdir -p "$work/xdg"
if [ "$WIN16_HEADLESS" = "1" ]; then
# A second boot in the same WORK (the save gate's) must not read the
# last display's number before the new Xvfb has written its own.
rm -f "$work/display"
Xvfb -displayfd 3 -screen 0 1024x768x24 -nolisten tcp 3> "$work/display" > /dev/null 2>&1 &
WIN16_XVFB_PID=$!
for _ in 1 2 3 4 5 6 7 8 9 10; do
[ -s "$work/display" ] && break
sleep 0.5
done
WIN16_DISPLAY=":$(cat "$work/display")"
else
WIN16_DISPLAY="$DISPLAY"
fi
env "${procEnv[@]}" DISPLAY="$WIN16_DISPLAY" "${wrapper[@]}" "$DOSBOX" "${JOEY_DOSBOX_ISOLATE[@]}" \
--conf "$JOEY_REPO/scripts/dosbox-win16.conf" "${soundArgs[@]}" "${WIN16_DOSBOX_ARGS[@]}" \
-c "MOUNT C \"$work/c\"" -c "MOUNT D \"$WIN16_FOLDER\"" "${envArgs[@]}" \
-c "C:" -c 'CD \WINDOWS' -c "WIN${WIN16_ARGS:+ $WIN16_ARGS}" -c "EXIT" > "$work/dosbox.log" 2>&1 &
WIN16_GUARD=$!
if [ ${#wrapper[@]} -eq 0 ]; then
WIN16_PID=$WIN16_GUARD
else
WIN16_PID="$(joeyChild "$WIN16_GUARD")"
fi
}
win16Log() {
find "$1" -maxdepth 1 -iname joeylog.txt -print -quit 2> /dev/null
}
win16Window() {
joeyFindWindow "$WIN16_DISPLAY" "$WIN16_PID"
}
win16Stop() {
joeyStop "$WIN16_PID" "$WIN16_GUARD" "$WIN16_XVFB_PID" 2> /dev/null
WIN16_PID=""
WIN16_GUARD=""
WIN16_XVFB_PID=""
}