# 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 # 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}" 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" python3 "$JOEY_REPO/tools/winIni.py" "$work/c/WINDOWS/SYSTEM.INI" \ set boot shell "D:\\$3" \ set boot comm.drv kpcomm.drv \ set drivers joystick kpjoy.drv } 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="" }