# 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 colours), 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="" }