From c60c3b75c7b7570f26125f4a5ae681968f97fdcd Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 11:47:16 +0200 Subject: [PATCH 01/29] Update install.yml --- .github/workflows/install.yml | 128 +++++++++++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 1 deletion(-) diff --git a/.github/workflows/install.yml b/.github/workflows/install.yml index b26f8f8ee..cfe2d136b 100644 --- a/.github/workflows/install.yml +++ b/.github/workflows/install.yml @@ -231,7 +231,7 @@ jobs: set -Eeuo pipefail case "$CALLBACK" in - powershell | legacy) + powershell | legacy | basic) ;; *) echo "Unsupported guest script type: $CALLBACK" @@ -249,6 +249,7 @@ jobs: printf '%s\n' "$token" > "$RUNNER_TEMP/data/validation.token" printf '%s\n' "$token" > "$RUNNER_TEMP/oem/validation.token" + if [[ "$CALLBACK" != "basic" ]]; then cat > "$RUNNER_TEMP/oem/sync-log.bat" <<'BATCH' @echo off setlocal @@ -327,6 +328,7 @@ jobs: shell.Run "cmd.exe /C echo VALIDATION_CALLBACK_FAILED^>COM1", 0, True VBSCRIPT + fi case "$CALLBACK" in powershell) @@ -764,6 +766,34 @@ jobs: exit /B %result% EOF ;; + + basic) + cat > "$RUNNER_TEMP/oem/install.bat" <<'BATCH' + @echo off + + :retry + if not exist \\host.lan\Data\validation.token goto wait + + echo TOKEN> C:\OEM\validation.result + type C:\OEM\validation.token >> C:\OEM\validation.result + echo SHARE>> C:\OEM\validation.result + type \\host.lan\Data\validation.token >> C:\OEM\validation.result + echo VERSION>> C:\OEM\validation.result + ver >> C:\OEM\validation.result + + if exist \\host.lan\Data\validation.result del \\host.lan\Data\validation.result >nul + copy C:\OEM\validation.result \\host.lan\Data\validation.result >nul + if errorlevel 1 goto wait + + goto end + + :wait + ping 127.0.0.1 -n 6 >nul + goto retry + + :end + BATCH + ;; esac echo "token=$token" >> "$GITHUB_OUTPUT" @@ -890,6 +920,7 @@ jobs: - name: Install and validate Windows shell: bash env: + CALLBACK: ${{ inputs.callback }} KILL_ON_FAILURE: ${{ inputs.kill_on_failure }} INSTALL_TIMEOUT: ${{ inputs.install_timeout }} REBOOT_TIMEOUT: ${{ inputs.reboot_timeout }} @@ -1130,6 +1161,101 @@ jobs: exit 1 fi + basic_result="$RUNNER_TEMP/data/validation.result" + + if [[ "$CALLBACK" == "basic" && -s "$basic_result" ]]; then + basic_response="$(tr -d '\r' < "$basic_result")" + + oem_file="$( + awk '$0 == "TOKEN" { getline; print; exit }' <<< "$basic_response" + )" + + share="$( + awk '$0 == "SHARE" { getline; print; exit }' <<< "$basic_response" + )" + + version="$( + awk ' + $0 == "VERSION" { found = 1; next } + found && NF { print; exit } + ' <<< "$basic_response" + )" + + if [[ -z "$oem_file" || -z "$share" || -z "$version" ]] || + ! grep -Eq '[0-9]+\.[0-9]+\.[0-9]+' <<< "$version"; then + sleep 1 + continue + fi + + stop_logs + trap - EXIT + + echo + echo "------------------------------------------------------------" + echo "Received response:" + printf '%s\n' "$basic_response" + + if [[ "$oem_file" != "$EXPECTED_TOKEN" ]]; then + echo "Failed to read C:\\OEM\\validation.token." + echo "Expected contents:" + echo " $EXPECTED_TOKEN" + echo "Received:" + echo " ${oem_file:-empty}" + exit 1 + fi + + if [[ "$share" != "$EXPECTED_TOKEN" ]]; then + echo "Failed to read \\\\host.lan\\Data\\validation.token." + echo "Expected contents:" + echo " $EXPECTED_TOKEN" + echo "Received:" + echo " ${share:-empty}" + exit 1 + fi + + normalized_caption="${version//\(R\)/}" + normalized_expected_caption="${EXPECTED_CAPTION//\(R\)/}" + + if [[ "$normalized_caption" != *"$normalized_expected_caption"* ]]; then + echo "Expected caption containing:" + echo " $EXPECTED_CAPTION" + echo "Received:" + echo " $version" + exit 1 + fi + + version_number="$( + grep -Eo '[0-9]+\.[0-9]+\.[0-9]+' <<< "$version" | + head -n 1 || true + )" + + if [[ ! "$version_number" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then + echo "Unexpected Windows version: $version" + exit 1 + fi + + build="${version_number##*.}" + + if [[ ! "$build" =~ ^[0-9]+$ ]]; then + echo "Invalid Windows build number: $build" + exit 1 + fi + + if (( build < MINIMUM_BUILD )); then + echo "Expected build $MINIMUM_BUILD or newer." + echo "Received build: $build" + exit 1 + fi + + echo + echo "$DISPLAY_NAME installed successfully." + echo "Version: $version_number" + echo "Build: $build" + echo "OEM files: copied successfully" + echo "Shared folder: readable and writable" + exit 0 + fi + response="$( grep -F 'VALIDATION_RESULT=' <<< "$container_log" | tail -n 1 | From 20eed3f7e29cbb717c740da9e11d7d0d62143ac9 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 11:48:05 +0200 Subject: [PATCH 02/29] Update validate.yml --- .github/workflows/validate.yml | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml index e35cb66eb..0dcccec17 100644 --- a/.github/workflows/validate.yml +++ b/.github/workflows/validate.yml @@ -206,6 +206,39 @@ jobs: minimum_build: ${{ matrix.minimum_build }} platform: ${{ matrix.platform }} + win9x: + strategy: + fail-fast: false + matrix: + include: + - name: Windows 95 + version: "95" + expected_caption: Windows 95 + minimum_build: 950 + platform: x86 + + - name: Windows 98 + version: "98" + expected_caption: Windows 98 + minimum_build: 2222 + platform: x86 + + - name: Windows ME + version: "me" + expected_caption: Windows Millennium + minimum_build: 3000 + platform: x86 + + name: ${{ matrix.name }} + uses: ./.github/workflows/settings.yml + with: + name: ${{ matrix.name }} + version: ${{ matrix.version }} + callback: basic + expected_caption: ${{ matrix.expected_caption }} + minimum_build: ${{ matrix.minimum_build }} + platform: ${{ matrix.platform }} + tiny: strategy: fail-fast: false From ef9b2d9c9bbbfcba128dee416421ae2755a9989b Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 11:57:27 +0200 Subject: [PATCH 03/29] Update define.sh --- src/define.sh | 111 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 102 insertions(+), 9 deletions(-) diff --git a/src/define.sh b/src/define.sh index 00e535d46..737067204 100644 --- a/src/define.sh +++ b/src/define.sh @@ -95,6 +95,14 @@ parseVersion() { VERSION="winxpx64" ;; "2k" | "2000" | "win2k" | "win2000" | "windows2k" | "windows2000" ) VERSION="win2kx86" ;; + "me" | "winme" | "win9x" | "windowsme" | "windows me" ) + VERSION="win9x" ;; + "98" | "98se" | "win98" | "win98se" | "windows98" | "windows98se" | "windows 98" | "windows 98 se" ) + VERSION="win98" ;; + "95" | "95c" | "win95" | "win95c" | "windows95" | "windows 95" ) + VERSION="win95" ;; + "nt4" | "nt40" | "winnt4" | "windowsnt4" | "windows nt4" | "windows nt 4" ) + VERSION="winnt4" ;; "25" | "2025" | "win25" | "win2025" | "windows2025" | "windows 2025" ) VERSION="win2025-eval" ;; "22" | "2022" | "win22" | "win2022" | "windows2022" | "windows 2022" ) @@ -510,6 +518,8 @@ printEdition() { ;; "winxp"* ) edition="Professional" ;; + "winnt4"* ) + edition="Workstation" ;; "win2019-hv"* ) edition="2019" ;; "win20"* ) @@ -570,6 +580,14 @@ fromFile() { id="win11${arch}" ;; *"winxp"* | *"win_xp"* | *"windowsxp"* | *"windows_xp"* ) id="winxpx86" ;; + *"winme"* | *"win_me"* | *"win9x"* | *"windowsme"* | *"windows_me"* ) + id="win9x" ;; + *"win98"* | *"win_98"* | *"windows98"* | *"windows_98"* ) + id="win98" ;; + *"win95"* | *"win_95"* | *"windows95"* | *"windows_95"* ) + id="win95" ;; + *"winnt4"* | *"win_nt4"* | *"windowsnt4"* | *"windows_nt4"* | *"windows_nt_4"* ) + id="winnt4" ;; *"winvista"* | *"win_vista"* | *"windowsvista"* | *"windows_vista"* ) id="winvista${arch}" ;; "tiny11core"* | "tiny11_core"* | "tiny_11_core"* ) @@ -1038,7 +1056,7 @@ isLegacy() { local id="$1" case "${id,,}" in - "win9"* | "winnt4"* | "win2k"* | "winxp"* | "win2003"* | \ + "win9"* | "winnt4" | "win2k"* | "winxp"* | "win2003"* | \ "winvista"* | "win7"* | "win2008"* | "reactos" ) return 0 ;; esac @@ -1051,36 +1069,71 @@ supportsUnattended() { local id="$1" case "${id,,}" in - "win9"* | "winnt4"* | "reactos" ) + "winnt4" | "reactos" ) return 1 ;; esac return 0 } -supportsBootKey() { +supportsXML() { local id="$1" + supportsSIF "$id" && return 1 + supportsUnattended "$id" || return 1 + case "${id,,}" in - "win9"* | "winnt4" | "reactos" ) + "win9"* ) return 1 ;; - "win"* | "tiny"* | "core"* ) - return 0 ;; esac return 0 } -supportsXML() { +supportsSIF() { local id="$1" - supportsUnattended "$id" || return 1 - case "${id,,}" in "win2k"* | "winxp"* | "win2003"* ) + return 0 ;; + esac + + return 1 +} + +supportsACPI() { + + local id="$1" + + case "${id,,}" in + + "win9"* | "winnt4" ) + + # Windows 9x does not respond to ACPI during setup, + # so disable it during the first run of the container. + hasBootMarker || return 1 ;; + + "reactos" ) + + # If the ISO is a Live-CD it will ignore ACPI signals. + hasSystemImage && return 1 ;; + + esac + + return 0 +} + +supportsBootKey() { + + local id="$1" + + case "${id,,}" in + "win9"* | "winnt4" | "reactos" ) return 1 ;; + "win"* | "tiny"* | "core"* ) + return 0 ;; esac return 0 @@ -1337,6 +1390,26 @@ getLink1() { sum="a93251b31f92316411bb48458a695d9051b13cdeba714c46f105012fdda45bf3" url="2000/5.00.2195.6717_x86fre_client-professional_retail_en-us.7z" ;; + "win9x" ) + size=448957271 + sum="36e75592cf89e072e165da60100f91ca59b7667be90494f1aa4a78091011cfea" + url="9x/me/Windows%20Me_en.zip" + ;; + "win98" ) + size=558263127 + sum="980bfc9abc93c3104d1a00690c5db702efda7a377de7e06cda61889a972e2e08" + url="9x/98/se/Microsoft%20Windows%2098%20Second%20Edition%20%284.10.2222%29.rar" + ;; + "win95" ) + size=422127699 + sum="5456632a73a3c70f8e5ee566374876266a81f61968e623ef5500028dec8e2979" + url="9x/95/Microsoft%20Windows%2095C%20%284.03.1216.osr2.5%29.zip" + ;; + "winnt4" ) + size=323070637 + sum="30b93288e3cd17bf91b915b951e6ff25e6cdf26b062352341e066b985229a731" + url="nt/Microsoft%20Windows%20NT%204.0%20Workstation%20%284.00.1381.1.sp1%29.rar" + ;; esac case "${ret,,}" in @@ -1616,6 +1689,26 @@ getLink4() { sum="e3816f6e80b66ff686ead03eeafffe9daf020a5e4717b8bd4736b7c51733ba22" url="MicrosoftWindows2000BuildCollection/5.00.2195.6717_x86fre_client-professional_retail_en-us-ZRMPFPP_EN.iso" ;; + "win9x" ) + size=523665408 + sum="bff3e9e69e625d2fdffd01b326988c45be8b9285465a4dabbae96ac550de611e" + url="windows-me_202209/Windows%20ME.ISO" + ;; + "win98" ) + size=655591424 + sum="2adfb46df8a9c7bbd2f67bff07461cc2f9d9ec8e01f0e112cb044c9e3e62f607" + url="windows-98-se-isofile/Windows%2098%20Second%20Edition.iso" + ;; + "win95" ) + size=618229760 + sum="493212b2a3cd391269e55a8fde5ee0a35e29ddece4b5910ec49df69ab62f5e26" + url="win-95-osr-25_202103/Win95_OSR25.iso" + ;; + "winnt4" ) + size=605820928 + sum="4b397a3d60cab870d0b27c4fe2fe1f2942343cda5494733dfd268ae0d3695f42" + url="winnt40wks_sp1_en/winnt40wks_sp1_en.iso" + ;; "core11" ) size=3304132608 sum="c0e0252b24144b8defb6c7ded2bc09f9297daf1fb8369b16c5b85382331eb47f" From 99605b96513604e4f12a7be4969408a72ac32600 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 12:03:38 +0200 Subject: [PATCH 04/29] Update legacy.sh --- src/legacy.sh | 1248 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 1198 insertions(+), 50 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 810151b17..09366b03d 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -7,75 +7,75 @@ setMachine() { local iso="$2" local dir="$3" local desc="$4" - local version="" - case "${id,,}" in - "win2k"* ) version="2k" ;; - "winxp"* ) version="xp" ;; - "win2003"* ) version="2k3" ;; - esac - - if [ -n "$version" ]; then + if isLegacy "$id"; then - if ! legacyInstall "$iso" "$dir" "$desc" "$version"; then + if ! prepareLegacyInstall "$id" "$iso" "$dir" "$desc"; then error "Failed to prepare $desc ISO!" return 1 fi - fi + writeState "mode" "windows_legacy" || return 1 - if isLegacy "$id"; then + case "${id,,}" in - writeState "mode" "windows_legacy" || return 1 + "win9"* | "winnt4" | "win2k"* | "reactos" ) + + writeState "old" "pc" || return 1 + writeState "type" "auto" || return 1 + + esac case "${id,,}" in - "win9"* | "win2k"* | "reactos" ) + + "winnt4" ) + writeState "vga" "cirrus" || return 1 ;; + * ) + writeState "vga" "std" || return 1 ;; + esac - fi + case "${id,,}" in - restoreBootMode || return 1 + "win95" | "winnt4" ) - case "${id,,}" in + writeState "usb" "N" || return 1 + writeState "port" "on" || return 1 + writeState "net" "pcnet" || return 1 ;; - "win9"* | "winnt4" ) + "win98" | "win9x" ) - writeState "old" "pc" || return 1 - writeState "net" "pcnet" || return 1 - writeState "type" "auto" || return 1 - writeState "usb" "pci-ohci" || return 1 ;; + writeState "port" "on" || return 1 + writeState "net" "pcnet" || return 1 + writeState "usb" "pci-ohci" || return 1 ;; - "win2k"* ) + "win2k"* ) - writeState "old" "pc" || return 1 - writeState "type" "auto" || return 1 - writeState "net" "rtl8139" || return 1 - writeState "usb" "pci-ohci" || return 1 ;; + writeState "net" "rtl8139" || return 1 + writeState "usb" "pci-ohci" || return 1 ;; - "winxpx"* | "win2003"* ) + "winxpx"* | "win2003"* ) - writeState "type" "blk" || return 1 - writeState "net" "rtl8139" || return 1 - writeState "sound" "usb-audio" || return 1 ;; + writeState "type" "blk" || return 1 + writeState "net" "rtl8139" || return 1 + writeState "sound" "usb-audio" || return 1 ;; - "reactos" ) + "reactos" ) - writeState "old" "pc" || return 1 - writeState "type" "auto" || return 1 - writeState "net" "rtl8139" || return 1 - writeState "usb" "pci-ohci" || return 1 ;; + writeState "net" "rtl8139" || return 1 + writeState "usb" "pci-ohci" || return 1 - esac + if isReactOSLiveCD "$iso"; then + SYSTEM="$iso" + fi ;; - if [[ "${id,,}" == "reactos" ]] && ! isCustomImage; then - # The ISO is a Live-CD so we need to disable the data disk - # as it will be always wiped during the next runs currently. - DISK_DISABLE="Y" + esac fi + restoreBootMode || return 1 restoreMachine || return 1 case "${id,,}" in @@ -112,10 +112,11 @@ setMachine() { restoreMachine() { + isPlatform "x64" || return 0 + # Restore the saved machine only when q35 is still the default; # an explicit user-selected machine must remain untouched. [[ "${MACHINE,,}" != "q35" ]] && return 0 - isPlatform "x64" || return 0 MACHINE="" restoreState "MACHINE" "old" || return 1 @@ -143,6 +144,7 @@ restoreMachineState() { restoreState "USB" "usb" || return 1 restoreState "SOUND" "sound" || return 1 restoreState "ADAPTER" "net" || return 1 + restoreState "VMPORT" "port" || return 1 restoreState "CPU_MODEL" "cpu" || return 1 restoreState "DISK_TYPE" "type" || return 1 @@ -238,6 +240,68 @@ addNetworkDriver() { return 0 } +addDisplayDriver() { + + local dir="$1" + local driver="$2" + local arch="$3" + local drivers="$4" + + local source="$drivers/qxl/$driver/$arch" + local destination="$dir/\$OEM\$/\$1/Drivers/QXL" + + # The legacy QXL package is named for XP but its x86 INF targets XP and + # later NT x86 releases, so it is also suitable to keep around for 2003 x86. + if [ ! -d "$source" ] && [[ "${arch,,}" == "x86" ]]; then + source="$drivers/qxl/xp/x86" + fi + + [ -d "$source" ] || return 0 + + local file + + for file in qxl.cat qxl.inf qxl.sys qxldd.dll; do + + if [ ! -f "$source/$file" ]; then + error "Failed to locate required QXL display driver file: $file" + return 1 + fi + + done + + mkdir -p "$destination" || return 1 + cp -Lr "$source/." "$destination" || return 1 + + return 0 +} + +addBalloonDriver() { + + local dir="$1" + local driver="$2" + local arch="$3" + local drivers="$4" + + local source="$drivers/Balloon/$driver/$arch" + local destination="$dir/\$OEM\$/\$1/Drivers/Balloon" + + local file + + for file in balloon.cat balloon.inf balloon.sys blnsvr.exe; do + + if [ ! -f "$source/$file" ]; then + error "Failed to locate required Balloon driver file: $file" + return 1 + fi + + done + + mkdir -p "$destination" || return 1 + cp -Lr "$source/." "$destination" || return 1 + + return 0 +} + addSIFEntry() { local file="$1" @@ -344,6 +408,8 @@ addLegacyDrivers() { extractDrivers "$drivers" || return 1 copyStorageDriver "$dir" "$target" "$driver" "$arch" "$drivers" || return 1 addNetworkDriver "$dir" "$driver" "$arch" "$drivers" || return 1 + addDisplayDriver "$dir" "$driver" "$arch" "$drivers" || return 1 + addBalloonDriver "$dir" "$driver" "$arch" "$drivers" || return 1 local file file=$(find "$target" -maxdepth 1 -type f -iname TXTSETUP.SIF -print -quit) || return 1 @@ -514,7 +580,7 @@ writeSIF() { ' WaitForReboot="No"' \ ' DriverSigningPolicy="Ignore"' \ ' NonDriverSigningPolicy="Ignore"' \ - ' OemPnPDriversPath="Drivers\viostor;Drivers\NetKVM;Drivers\sata"' \ + ' OemPnPDriversPath="Drivers\viostor;Drivers\NetKVM;Drivers\sata;Drivers\QXL;Drivers\Balloon"' \ ' NoWaitAfterTextMode=1' \ ' NoWaitAfterGUIMode=1' \ ' FileSystem=ConvertNTFS' \ @@ -522,13 +588,18 @@ writeSIF() { ' Hibernation="No"' \ '' \ '[GuiUnattended]' \ - ' OEMSkipRegional=1' ' OemSkipWelcome=1' " AdminPassword=\"$sifPassword\"" ' TimeZone=0' + ' OEMSkipRegional=1' \ + ' OemSkipWelcome=1' \ + " AdminPassword=\"$sifPassword\"" \ + ' TimeZone=4' if disabled "$AUTOLOGIN"; then - printf '%s\n' ' AutoLogon=No' + printf '%s\n' \ + ' AutoLogon=No' else printf '%s\n' \ - ' AutoLogon=Yes' ' AutoLogonCount=65432' + ' AutoLogon=Yes' \ + ' AutoLogonCount=65432' fi printf '%s\n' \ @@ -555,14 +626,23 @@ writeSIF() { '' \ '[URL]' \ ' Home_Page = http://www.google.com' \ - ' Search_Page = http://www.google.com' '' '[TerminalServices]' ' AllowConnections=1' '' + ' Search_Page = http://www.google.com' \ + '' \ + '[TerminalServices]' \ + ' AllowConnections=1' \ + '' } | unix2dos > "$target/WINNT.SIF" || return 1 if [[ "$driver" == "2k3" ]]; then { printf '%s\n' \ '[Components]' \ - ' TerminalServer=On' '' '[LicenseFilePrintData]' ' AutoMode=PerServer' ' AutoUsers=5' '' + ' TerminalServer=On' \ + '' \ + '[LicenseFilePrintData]' \ + ' AutoMode=PerServer' \ + ' AutoUsers=5' \ + '' } | unix2dos >> "$target/WINNT.SIF" || return 1 fi @@ -658,7 +738,10 @@ appendRegistry() { '@=dword:00000000' \ '' \ '[HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\ServerOOBE\SecurityOOBE]' \ - '"DontLaunchSecurityOOBE"=dword:00000000' '' + '"DontLaunchSecurityOOBE"=dword:00000000' \ + '' \ + '[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\R2Setup]' \ + '"cd2chain"=dword:00000000' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi @@ -670,6 +753,8 @@ writeVBS() { local dir="$1" local username="$2" local shortcut="$3" + local balloon="$dir/\$OEM\$/balloon.cmd" + local balloonExe="$dir/\$OEM\$/\$1/Drivers/Balloon/blnsvr.exe" # Locate the built-in Administrator by its RID 500 SID rather than its # localized display name, then rename that account to the requested username. @@ -721,9 +806,29 @@ writeVBS() { fi } | unix2dos > "$dir/\$OEM\$/install.vbs" || return 1 + if [ -f "$balloonExe" ]; then + { + printf '%s\n' \ + '@echo off' \ + 'sc.exe query BalloonService >nul 2>&1' \ + 'if errorlevel 1 "C:\Drivers\Balloon\blnsvr.exe" -i' \ + '' + } | unix2dos > "$balloon" || return 1 + else + rm -f -- "$balloon" || return 1 + fi + { printf '%s\n' \ - '[COMMANDS]' '"REGEDIT /s install.reg"' '"Wscript install.vbs"' '' + '[COMMANDS]' \ + '"REGEDIT /s install.reg"' \ + '"Wscript install.vbs"' + + if [ -f "$balloon" ]; then + printf '%s\n' '"cmd /C balloon.cmd"' + fi + + printf '%s\n' '' } | unix2dos > "$dir/\$OEM\$/cmdlines.txt" || return 1 return 0 @@ -761,7 +866,28 @@ disableAutoReboot() { return 0 } -legacyInstall() { +prepareLegacyInstall() { + + local id="$1" + local iso="$2" + local dir="$3" + local desc="$4" + + case "${id,,}" in + "win9"* ) + prepareWin9xInstall "$id" "$iso" "$dir" "$desc" || return 1 ;; + "win2k"* ) + prepareSIFInstall "$iso" "$dir" "$desc" "2k" || return 1 ;; + "winxp"* ) + prepareSIFInstall "$iso" "$dir" "$desc" "xp" || return 1 ;; + "win2003"* ) + prepareSIFInstall "$iso" "$dir" "$desc" "2k3" || return 1 ;; + esac + + return 0 +} + +prepareSIFInstall() { local dir="$2" local desc="$3" @@ -997,6 +1123,1028 @@ validateLegacyEncoding() { return 0 } +createWin9xSystemImage() { + + local dir="$1" + local image="$2" + local desc="$3" + local setup="$4" + local options="$5" + local mouse="$6" + local logon="$7" + + local temp="$TMP/win9x-image" + local config="$temp/mtools.conf" + local autoexec="$temp/AUTOEXEC.BAT" + local msdos="$temp/MSDOS.SYS" + local tmp="${image}.tmp" + local size=$((261 * 255 * 63 * 512)) + local start=63 + local sectors=$((size / 512 - start)) + local offset=$((start * 512)) + local entry base find_pid + local fs attributes + local entries=() + + local msg="Creating system image..." + info "$msg" && html "$msg" + + rm -rf -- "$temp" || return 1 + mkdir -p "$temp" || return 1 + rm -f -- "$tmp" || return 1 + + # mtools can create a perfectly valid FAT32 filesystem, including its BPB, + # FSInfo sector and backup boot area, but its generic boot code cannot start + # Windows 9x. Microsoft FORMAT.COM carries the matching DOS FAT32 bootstrap + # as a three-sector template, so use the copy supplied by the installation + # media instead of embedding Microsoft's boot code in this script. + # + # Do not assume where FORMAT.COM lives. Windows 95 OSR2, Windows 98 and + # Windows Me media use different setup layouts, and customized media may move + # the file again. Search all FORMAT.COM copies and select the first one that + # contains a structurally valid FAT32 boot template. + local format="" + local template="" + local template_base=0 + local format_size sig boot_sig fsinfo_lead fsinfo_struct + local -a formats=() signatures=() + + mapfile -d '' formats < <( + find "$dir" -type f -iname 'FORMAT.COM' -print0 + ) + + find_pid=$! + + if ! wait "$find_pid"; then + error "Failed to enumerate FORMAT.COM files in $desc ISO image!" + return 1 + fi + + if (( ${#formats[@]} == 0 )); then + error "Failed to locate FORMAT.COM in $desc ISO image!" + return 1 + fi + + for format in "${formats[@]}"; do + + format_size=$(stat -c %s -- "$format") || return 1 + signatures=() + + # FAT32 places the filesystem type string at offset 0x52 in the extended + # BPB. Searching for only this standard marker intentionally avoids tying + # the code to the exact Windows 98 machine-code bytes that follow it; a + # Windows 95 OSR2 or Windows Me FORMAT.COM may contain a different loader. + mapfile -t signatures < <( + LC_ALL=C grep -aobF 'FAT32 ' "$format" | cut -d: -f1 + ) + + for sig in "${signatures[@]}"; do + + [[ "$sig" =~ ^[0-9]+$ ]] || continue + (( sig >= 0x52 )) || continue + + local candidate=$((sig - 0x52)) + + # The Microsoft Win9x FAT32 bootstrap occupies three 512-byte sectors. + # Reject truncated matches before probing the fixed signatures below. + (( candidate + 0x600 <= format_size )) || continue + + # Validate the candidate by FAT32 structure rather than by one particular + # release's loader bytes: + # + # +0x1fe boot-sector signature (55 aa) + # +0x200 FSInfo lead signature (RRaA) + # +0x3e4 FSInfo structure signature (rrAa) + # +0x3fe FSInfo-sector signature (55 aa) + # +0x5fe third boot-sector signature (55 aa) + # + # Requiring all five makes an accidental "FAT32 " string elsewhere in + # FORMAT.COM extremely unlikely to be mistaken for the boot template. + boot_sig=$(dd if="$format" bs=1 skip="$((candidate + 0x1fe))" count=2 status=none | xxd -p) || return 1 + [[ "$boot_sig" == "55aa" ]] || continue + + fsinfo_lead=$(dd if="$format" bs=1 skip="$((candidate + 0x200))" count=4 status=none | xxd -p) || return 1 + [[ "$fsinfo_lead" == "52526141" ]] || continue + + fsinfo_struct=$(dd if="$format" bs=1 skip="$((candidate + 0x3e4))" count=4 status=none | xxd -p) || return 1 + [[ "$fsinfo_struct" == "72724161" ]] || continue + + boot_sig=$(dd if="$format" bs=1 skip="$((candidate + 0x3fe))" count=2 status=none | xxd -p) || return 1 + [[ "$boot_sig" == "55aa" ]] || continue + + boot_sig=$(dd if="$format" bs=1 skip="$((candidate + 0x5fe))" count=2 status=none | xxd -p) || return 1 + [[ "$boot_sig" == "55aa" ]] || continue + + template="$format" + template_base="$candidate" + break 2 + + done + + done + + if [ -z "$template" ]; then + error "Failed to locate a Windows 9x FAT32 boot template in FORMAT.COM!" + return 1 + fi + + checkFreeSpace "$(dirname "$image")" "$size" || return 1 + + fs=$(stat -f -c %T "$(dirname "$tmp")") || return 1 + + if [[ "${fs,,}" == "btrfs" ]]; then + touch "$tmp" || return 1 + { chattr +C "$tmp"; } || : + attributes=$(lsattr "$tmp") || return 1 + if [[ "$attributes" != *"C"* ]]; then + error "Failed to disable COW for $desc system image $tmp on ${fs^^} filesystem!" + fi + fi + + truncate -s "$size" "$tmp" || return 1 + + printf 'drive w: file="%s" partition=1 fat_bits=32 cylinders=261 heads=255 sectors=63 mformat_only\n' \ + "$tmp" > "$config" || return 1 + + # Let mtools create all volume-specific FAT32 data. In particular, the BPB + # contains the geometry, FAT size, root cluster, FSInfo sector and backup boot + # sector locations for this image. Those values must survive the Microsoft + # boot-code transplant below, so FORMAT.COM is deliberately not passed to + # mformat as a whole-sector template. + if ! MTOOLSRC="$config" mpartition -I -c -a -T 0x0c -b "$start" -l "$sectors" w: || + ! MTOOLSRC="$config" mformat -F -m 0xf8 -H "$start" w:; then + + rm -f -- "$tmp" + error "Failed to create the $desc FAT32 system image!" + return 1 + fi + + # BIOS loads the MBR at physical address 0000:7c00, which is also the address + # where a partition boot sector (VBR) expects to run. We therefore cannot read + # the VBR directly on top of the MBR and then continue executing the MBR: + # INT 13h completes the disk transfer before returning, so the instructions + # following that interrupt would already have been replaced by VBR bytes. + # + # Relocate the complete 512-byte MBR from 0000:7c00 to 0000:0600 first. The + # far jump below continues at offset 0x1e in that relocated copy (0000:061e). + # Only then do we read CHS 0/1/1 -- LBA 63 with this 63-sector geometry -- back + # into 0000:7c00 and transfer control to the Microsoft FAT32 boot sector. + # + # DL is deliberately left untouched: BIOS supplies the boot drive number in + # DL, and the INT 13h read must use that same hard disk. + printf '%b' \ + '\xfa\x31\xc0\x8e\xd0\xbc\x00\x7c\x8e\xd8\x8e\xc0\xfb\xfc' \ + '\xbe\x00\x7c\xbf\x00\x06\xb9\x00\x01\xf3\xa5' \ + '\xea\x1e\x06\x00\x00' \ + '\x31\xc0\x8e\xc0\xbb\x00\x7c\xb8\x01\x02\xb9\x01\x00\xb6\x01' \ + '\xcd\x13\x72\x05\xea\x00\x7c\x00\x00\xcd\x18\xeb\xfe' | + dd of="$tmp" bs=1 seek=0 conv=notrunc status=none || return 1 + + # FAT32 stores the location of its backup boot sector in BPB_BkBootSec at + # offset 0x32. Read both the reserved-sector count and that pointer from the + # BPB generated by mformat rather than assuming Microsoft's usual sector 6. + # If a backup exists, patch it together with the primary boot area so recovery + # never falls back to mtools' non-Windows bootstrap. + local reserved_bytes backup_bytes reserved backup_sector backup_offset + local reserved_lo reserved_hi backup_lo backup_hi + local -a boot_targets=("$offset") + + reserved_bytes=$(dd if="$tmp" bs=1 skip="$((offset + 0x0e))" count=2 status=none | od -An -tu1) || return 1 + read -r reserved_lo reserved_hi <<< "$reserved_bytes" + [[ "$reserved_lo" =~ ^[0-9]+$ && "$reserved_hi" =~ ^[0-9]+$ ]] || return 1 + reserved=$((reserved_lo | reserved_hi << 8)) + + backup_bytes=$(dd if="$tmp" bs=1 skip="$((offset + 0x32))" count=2 status=none | od -An -tu1) || return 1 + read -r backup_lo backup_hi <<< "$backup_bytes" + [[ "$backup_lo" =~ ^[0-9]+$ && "$backup_hi" =~ ^[0-9]+$ ]] || return 1 + backup_sector=$((backup_lo | backup_hi << 8)) + + if (( backup_sector != 0 && backup_sector != 0xffff )); then + + # We copy three sectors of boot/FSInfo data starting at the backup pointer, + # therefore all three must fit inside the FAT32 reserved area. + if (( backup_sector + 2 >= reserved )); then + rm -f -- "$tmp" + error "Invalid FAT32 backup boot sector generated by mtools!" + return 1 + fi + + backup_offset=$((offset + backup_sector * 512)) + boot_targets+=("$backup_offset") + + fi + + # ms-sys' Windows 95/98/Me FAT32 writer does not overwrite the complete + # 1536-byte template. It deliberately preserves the volume-specific fields + # created by the formatter and replaces only these three ranges: + # + # 0x000..0x00a jump instruction + OEM identifier (11 bytes) + # 0x052..0x3e7 boot code + static FSInfo data (918 bytes) + # 0x3f0..0x5ff FSInfo trailer + second-stage boot code (528 bytes) + # + # Two regions are intentionally left untouched: + # + # 0x00b..0x051 FAT32 BPB/extended BPB generated by mformat + # 0x3e8..0x3ef current free-cluster and next-free hints from mformat + # + # The source offsets are relative to the FAT32 template we located inside + # FORMAT.COM, so neither the ISO layout nor FORMAT.COM's internal layout is + # hardcoded. Apply the same transplant to the backup boot area when present. + local target_base + + for target_base in "${boot_targets[@]}"; do + + dd if="$template" of="$tmp" bs=1 \ + skip="$template_base" seek="$target_base" count=11 \ + conv=notrunc status=none || return 1 + + dd if="$template" of="$tmp" bs=1 \ + skip="$((template_base + 0x52))" seek="$((target_base + 0x52))" count=918 \ + conv=notrunc status=none || return 1 + + dd if="$template" of="$tmp" bs=1 \ + skip="$((template_base + 0x3f0))" seek="$((target_base + 0x3f0))" count=528 \ + conv=notrunc status=none || return 1 + + # The Microsoft template intentionally leaves the FAT32 extended BPB alone. + # Set its BIOS drive number explicitly because QEMU exposes the system image as + # hard disk 0x80, regardless of what default mformat happened to write there. + printf '%b' '\x80' | + dd of="$tmp" bs=1 seek="$((target_base + 0x40))" conv=notrunc status=none || return 1 + + done + + # Verify both the partition table and the FAT32 structures after patching. + # The checks cover all three Microsoft boot sectors, not only sector zero, so + # a wrong FORMAT.COM match or an off-by-one copy cannot silently produce an + # image that merely looks bootable from its first 55 aa signature. + local active mbrsig vbrsig stage2sig fstype target_fsinfo_lead target_fsinfo_struct drive + active=$(dd if="$tmp" bs=1 skip=446 count=1 status=none | od -An -tu1 | tr -d ' ') || return 1 + mbrsig=$(dd if="$tmp" bs=1 skip=510 count=2 status=none | xxd -p) || return 1 + vbrsig=$(dd if="$tmp" bs=1 skip="$((offset + 0x1fe))" count=2 status=none | xxd -p) || return 1 + stage2sig=$(dd if="$tmp" bs=1 skip="$((offset + 0x5fe))" count=2 status=none | xxd -p) || return 1 + fstype=$(dd if="$tmp" bs=1 skip="$((offset + 0x52))" count=8 status=none) || return 1 + target_fsinfo_lead=$(dd if="$tmp" bs=1 skip="$((offset + 0x200))" count=4 status=none | xxd -p) || return 1 + target_fsinfo_struct=$(dd if="$tmp" bs=1 skip="$((offset + 0x3e4))" count=4 status=none | xxd -p) || return 1 + drive=$(dd if="$tmp" bs=1 skip="$((offset + 0x40))" count=1 status=none | od -An -tu1 | tr -d ' ') || return 1 + + if [[ "$active" != "128" || "$mbrsig" != "55aa" || "$vbrsig" != "55aa" || + "$stage2sig" != "55aa" || "$fstype" != "FAT32 " || + "$target_fsinfo_lead" != "52526141" || "$target_fsinfo_struct" != "72724161" || + "$drive" != "128" ]]; then + rm -f -- "$tmp" + error "Failed to make the $desc FAT32 system image bootable!" + return 1 + fi + + if (( backup_sector != 0 && backup_sector != 0xffff )); then + + vbrsig=$(dd if="$tmp" bs=1 skip="$((backup_offset + 0x1fe))" count=2 status=none | xxd -p) || return 1 + stage2sig=$(dd if="$tmp" bs=1 skip="$((backup_offset + 0x5fe))" count=2 status=none | xxd -p) || return 1 + fstype=$(dd if="$tmp" bs=1 skip="$((backup_offset + 0x52))" count=8 status=none) || return 1 + drive=$(dd if="$tmp" bs=1 skip="$((backup_offset + 0x40))" count=1 status=none | od -An -tu1 | tr -d ' ') || return 1 + + if [[ "$vbrsig" != "55aa" || "$stage2sig" != "55aa" || + "$fstype" != "FAT32 " || "$drive" != "128" ]]; then + rm -f -- "$tmp" + error "Failed to update the $desc FAT32 backup boot record!" + return 1 + fi + + fi + + # The CD boot image is not part of the Windows 9x installation contract. + # Retail, OEM and repacked discs may use a differently named El Torito image + # or omit an extracted floppy image entirely. The setup cabinets are a much + # better source for the DOS kernel files because Setup itself ships them. + # + # The exact cabinet containing COMMAND.COM and WINBOOT.SYS varies between + # Windows 9x releases, so do not hardcode a cabinet number. Search every + # PRECOPY cabinet first and only fall back to the remaining CABs for unusual + # OEM/repacked media. + # + # WINBOOT.SYS is the setup-media form of IO.SYS. Copy it as IO.SYS on the + # generated boot volume; COMMAND.COM keeps its original name. cabextract can + # follow multi-part cabinet sets automatically, which also covers a file that + # happens to span into the next cabinet. + local cab source_name target_name extracted + local cab_temp="$temp/cab" + local -a precopy_cabs=() other_cabs=() + + mapfile -d '' precopy_cabs < <( + find "$dir" -type f -iname 'PRECOPY*.CAB' -print0 + ) + + find_pid=$! + + if ! wait "$find_pid"; then + rm -f -- "$tmp" + error "Failed to enumerate Windows 9x setup cabinets!" + return 1 + fi + + mapfile -d '' other_cabs < <( + find "$dir" -type f -iname '*.CAB' ! -iname 'PRECOPY*.CAB' -print0 + ) + + find_pid=$! + + if ! wait "$find_pid"; then + rm -f -- "$tmp" + error "Failed to enumerate Windows 9x setup cabinets!" + return 1 + fi + + if (( ${#precopy_cabs[@]} == 0 && ${#other_cabs[@]} == 0 )); then + rm -f -- "$tmp" + error "Failed to locate Windows 9x setup cabinets in $desc ISO image!" + return 1 + fi + + for source_name in COMMAND.COM WINBOOT.SYS; do + + target_name="$source_name" + [[ "$source_name" == "WINBOOT.SYS" ]] && target_name="IO.SYS" + extracted="" + + for cab in "${precopy_cabs[@]}" "${other_cabs[@]}"; do + + rm -rf -- "$cab_temp" || return 1 + mkdir -p "$cab_temp" || return 1 + + # -F avoids unpacking a complete cabinet just to obtain one small system + # file. -L normalizes the extracted name so case differences in localized + # or repacked media do not matter; find below remains case-insensitive as + # an additional safeguard. A cabinet that simply lacks the requested file + # is not an error here: continue with the next candidate. + if ! cabextract -q -L -F "$source_name" -d "$cab_temp" "$cab" >/dev/null 2>&1; then + continue + fi + + extracted=$(find "$cab_temp" -type f -iname "$source_name" -print -quit) || return 1 + + if [ -n "$extracted" ] && [ -s "$extracted" ]; then + cp -f -- "$extracted" "$temp/$target_name" || return 1 + break + fi + + done + + if [ ! -s "$temp/$target_name" ]; then + rm -f -- "$tmp" + error "Failed to extract $source_name from the Windows 9x setup cabinets!" + return 1 + fi + + done + + rm -rf -- "$cab_temp" || : + + { + printf '%s\n' \ + '[Options]' \ + 'BootGUI=0' \ + 'BootDelay=0' \ + 'Logo=0' \ + '' + } | unix2dos > "$msdos" || return 1 + + # Copy the DOS system files before anything else. Older DOS boot sectors + # expect IO.SYS and MSDOS.SYS at the start of a freshly formatted volume. + MTOOLSRC="$config" mcopy "$temp/IO.SYS" w:/IO.SYS || return 1 + MTOOLSRC="$config" mcopy "$msdos" w:/MSDOS.SYS || return 1 + MTOOLSRC="$config" mcopy "$temp/COMMAND.COM" w:/COMMAND.COM || return 1 + + MTOOLSRC="$config" mattrib +h +s +r w:/IO.SYS w:/MSDOS.SYS || return 1 + + # Windows is started explicitly from AUTOEXEC.BAT so the DOS environment stays + # available for the mouse TSR. + + mapfile -d '' entries < <( + find "$dir" -mindepth 1 -maxdepth 1 -print0 + ) + + find_pid=$! + + if ! wait "$find_pid"; then + rm -f -- "$tmp" + error "Failed to enumerate $desc files!" + return 1 + fi + + for entry in "${entries[@]}"; do + + base=$(basename "$entry") + + case "${base,,}" in + "io.sys" | "msdos.sys" | "command.com" | "autoexec.bat" | "config.sys" ) + continue ;; + esac + + if ! MTOOLSRC="$config" mcopy -Q -s "$entry" w:/; then + rm -f -- "$tmp" + error "Failed to copy $desc file: $entry" + return 1 + fi + + done + + if ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.EXE" w:/VBMOUSE.EXE || + ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.DRV" w:/VBMOUSE.DRV; then + rm -f -- "$tmp" + error "Failed to add mouse integration to the $desc system image!" + return 1 + fi + + if ! disabled "$AUTOLOGIN"; then + + if ! MTOOLSRC="$config" mcopy -o "$logon" w:/WIN9XLOG.EXE; then + rm -f -- "$tmp" + error "Failed to add automatic logon support to the $desc system image!" + return 1 + fi + + fi + + { + printf '%s\n' \ + '@ECHO OFF' \ + 'IF EXIST C:\WINDOWS\WIN.COM GOTO WINDOWS' \ + 'ECHO.' \ + 'ECHO Starting Windows Setup, please wait...' \ + 'ECHO.' \ + 'C:\VBMOUSE.EXE >NUL' \ + "IF NOT EXIST C:\\${setup}\\XMSMMGR.EXE GOTO SETUP" \ + "IF NOT EXIST C:\\${setup}\\SMARTDRV.EXE GOTO SETUP" \ + "C:\\${setup}\\XMSMMGR.EXE >NUL" \ + "C:\\${setup}\\SMARTDRV.EXE C+ /Q 16384 16384 >NUL" \ + ':SETUP' \ + "C:\\${setup}\\SETUP.EXE $options" \ + 'GOTO END' \ + ':WINDOWS' \ + 'IF EXIST C:\WINDOWS\SYSTEM\VBMOUSE.DRV GOTO START' \ + 'COPY C:\VBMOUSE.DRV C:\WINDOWS\SYSTEM\VBMOUSE.DRV >NUL' \ + ':START' \ + 'C:\VBMOUSE.EXE >NUL' \ + 'C:\WINDOWS\WIN.COM' \ + ':END' \ + '' + } | unix2dos > "$autoexec" || return 1 + + MTOOLSRC="$config" mcopy -o "$autoexec" w:/AUTOEXEC.BAT || return 1 + + if ! MTOOLSRC="$config" mdir "w:/$setup/SETUP.EXE" >/dev/null; then + rm -f -- "$tmp" + error "Failed to verify the $desc system image!" + return 1 + fi + + if [[ "${fs,,}" == "btrfs" ]]; then + attributes=$(lsattr "$tmp") || return 1 + if [[ "$attributes" != *"C"* ]]; then + warn "COW (copy on write) is not disabled for $desc system image $tmp on ${fs^^} filesystem!" + fi + fi + + if ! mv -f -- "$tmp" "$image"; then + rm -f -- "$tmp" + error "Failed to save $desc system image: $image" + return 1 + fi + + if ! setOwner "$image"; then + warn "Failed to set the owner for \"$image\" !" + fi + + rm -rf -- "$temp" || : + + return 0 +} + +stageWin9xDisplayDriver() { + + local target="$1" + local source="$2" + local desc="$3" + + local dest="$target/BOXV9X" + local file + + for file in boxv9x.inf boxvmini.drv boxvmini.vxd; do + + if [ ! -f "$source/$file" ]; then + error "Failed to locate required Windows 9x display driver file: $file" + return 1 + fi + + done + + rm -rf -- "$dest" || return 1 + mkdir -p "$dest" || return 1 + + if ! cp -f -- \ + "$source/boxv9x.inf" \ + "$source/boxvmini.drv" \ + "$source/boxvmini.vxd" \ + "$dest/"; then + + error "Failed to add the display driver to $desc setup files!" + return 1 + fi + + # BOXV9X is the source-media tag named by the upstream INF. Keep the INF + # unchanged and provide that tag beside the driver files so Setup never asks + # for a separate driver disk while installing the exact QEMU PCI match. + : > "$dest/BOXV9X" || return 1 + + return 0 +} + +integrateWin9xSetupMouse() { + + local dir="$1" + local desc="$2" + local driver="$3" + + local cab + cab=$(find "$dir" -maxdepth 1 -type f -iname 'MINI.CAB' -print -quit) || return 1 + + if [ -z "$cab" ]; then + error "Failed to locate MINI.CAB in $desc ISO image!" + return 1 + fi + + if [ ! -f "$driver" ]; then + error "Failed to locate the mouse driver for $desc MINI.CAB!" + return 1 + fi + + local temp="$TMP/win9x-mini" + local files="$temp/files" + local rebuilt="$temp/MINI.CAB" + local system + local -a entries=() + + rm -rf -- "$temp" || return 1 + mkdir -p "$files" || return 1 + + if ! cabextract -q -d "$files" "$cab"; then + rm -rf -- "$temp" || : + error "Failed to extract MINI.CAB from $desc ISO image!" + return 1 + fi + + system=$(find "$files" -type f -iname 'SYSTEM.INI' -print -quit) || return 1 + + if [ -z "$system" ]; then + rm -rf -- "$temp" || : + error "Failed to locate SYSTEM.INI in $desc MINI.CAB!" + return 1 + fi + + if ! cp -f -- "$driver" "$files/VBMOUSE.DRV"; then + rm -rf -- "$temp" || : + error "Failed to add the mouse driver to $desc MINI.CAB!" + return 1 + fi + + if ! grep -Eqi '^[[:space:]]*mouse\.drv[[:space:]]*=' "$system"; then + rm -rf -- "$temp" || : + error "Failed to locate the mouse driver setting in $desc MINI.CAB!" + return 1 + fi + + if ! dos2unix "$system" >/dev/null 2>&1 || + ! sed -i -E 's/^([[:space:]]*mouse\.drv[[:space:]]*=[[:space:]]*).*$/\1vbmouse.drv/I' "$system" || + ! unix2dos "$system" >/dev/null 2>&1; then + rm -rf -- "$temp" || : + error "Failed to configure the mouse driver in $desc mini-Windows setup!" + return 1 + fi + + if ! grep -Eqi '^[[:space:]]*mouse\.drv[[:space:]]*=[[:space:]]*vbmouse\.drv[[:space:]]*$' "$system"; then + rm -rf -- "$temp" || : + error "Failed to verify the mouse driver setting in $desc MINI.CAB!" + return 1 + fi + + mapfile -d '' entries < <(find "$files" -type f -print0) + + if (( ${#entries[@]} == 0 )); then + rm -rf -- "$temp" || : + error "Failed to enumerate files from $desc MINI.CAB!" + return 1 + fi + + if ! gcab -c -z -n "$rebuilt" "${entries[@]}" >/dev/null; then + rm -rf -- "$temp" || : + error "Failed to rebuild $desc MINI.CAB!" + return 1 + fi + + if ! cabextract -q -l "$rebuilt" >/dev/null 2>&1; then + rm -rf -- "$temp" || : + error "Failed to verify the rebuilt $desc MINI.CAB!" + return 1 + fi + + if ! mv -f -- "$rebuilt" "$cab"; then + rm -rf -- "$temp" || : + error "Failed to replace $desc MINI.CAB!" + return 1 + fi + + rm -rf -- "$temp" || : + + return 0 +} + +prepareWin9xInstall() { + + local id="$1" + local dir="$3" + local desc="$4" + + local folder options="/IS /IQ /IT" target + local shortcut="Y" + + if disabled "$SHORTCUT" || disabled "${SAMBA:-Y}"; then + shortcut="N" + fi + + if [ -n "$DOMAIN" ]; then + error "The DOMAIN variable is not supported for $desc!" + return 1 + fi + + case "${id,,}" in + "win95"* ) + folder="WIN95" ;; + "win98"* ) + folder="WIN98" + options="/P I /IE /NF $options" ;; + "win9x"* ) + folder="WIN9X" + options="/IE /NF $options" ;; + * ) + return 0 ;; + esac + + target=$(find "$dir" -maxdepth 1 -type d -iname "$folder" -print -quit) || return 1 + + if [ -z "$target" ]; then + error "Failed to locate the $folder Setup folder in $desc ISO image!" + return 1 + fi + + [ -z "$WIDTH" ] && WIDTH="1280" + [ -z "$HEIGHT" ] && HEIGHT="720" + + validateResolution "WIDTH" "$WIDTH" 320 || return 1 + validateResolution "HEIGHT" "$HEIGHT" 200 || return 1 + + # Express setup still needs concrete identity values. If NameAndOrg is absent, + # Windows 9x stops during GUI setup for the user's name/company; if Network is + # missing the identity fields, it later stops for the computer name/workgroup. + # Win9x has no useful "*" computer-name generator, so provide a valid default + # when HOST was not configured. + local host="${HOST:-Docker}" + local username="${USERNAME:-Docker}" + local workgroup="${WORKGROUP:-WORKGROUP}" + + validateComputerName "$host" || return 1 + + # Reuse the normal OEM-folder preparation so /OEM and COMMAND behave like the + # other Windows paths, but place the user payload directly at C:\OEM in the + # generated Win9x system image. + local win9x_oem="$dir/OEM" + local install="" + + if ! addFolder "$dir" "win9x"; then + error "Failed to add OEM folder to image!" + return 1 + fi + + if [ -d "$win9x_oem" ]; then + install=$(find "$win9x_oem" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1 + fi + + # MSBATCH.INF and WINNT.SIF both use quoted INF-style values. Reuse the SIF + # escaping helper so quotes and percent signs cannot alter the answer file. + local batchHost batchUsername batchOrganization batchWorkgroup batchKey="" + batchHost=$(escapeSIFValue "$host") || return 1 + batchUsername=$(escapeSIFValue "$username") || return 1 + batchOrganization=$(escapeSIFValue "$APP for $ENGINE") || return 1 + batchWorkgroup=$(escapeSIFValue "$workgroup") || return 1 + + if [ -n "$KEY" ]; then + batchKey=$(escapeSIFValue "$KEY") || return 1 + fi + + local desktop="%10%\Desktop" + + local addReg="OPKInstall" + + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then + addReg+=",OEMDrivers" + fi + + local firstLogonAddReg="Win9x.User" + local firstLogonDelReg="" + local firstLogonDelFiles="" + + if ! disabled "$AUTOLOGIN"; then + firstLogonDelReg="Win9x.Logon" + fi + + if [[ "${id,,}" == "win98"* ]]; then + firstLogonAddReg+=",Win98.User" + [ -n "$firstLogonDelReg" ] && firstLogonDelReg+="," + firstLogonDelReg+="Win98.Welcome,Win98.MSN" + firstLogonDelFiles="Win98.Connect" + fi + + # Cap the memory Win9x itself can address at 4 GiB without changing QEMU's + # RAM_SIZE. Setup may use SYSTEM.CB for its minimal protected-mode/fallback + # startup, so keep the limit in both SYSTEM.INI and SYSTEM.CB. + { + printf '%s\n' \ + '[BatchSetup]' \ + 'Version=3.0 (32-bit)' \ + '' \ + '[Version]' \ + 'Signature="$CHICAGO$"' \ + 'AdvancedINF=2.5' \ + 'LayoutFile=layout.inf' \ + '' \ + '[Install]' \ + 'UpdateInis=Win9x.SystemIni,Win9x.SystemCb' \ + "AddReg=$addReg" \ + '' \ + '[OPKInstall]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion","ProductId",,"12345-OEM-1234567-12345"' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion","ProductKey",,"CDKey"' \ + "HKLM,\"SOFTWARE\Microsoft\Windows\CurrentVersion\",\"RegisteredOwner\",,\"$batchUsername\"" \ + "HKLM,\"SOFTWARE\Microsoft\Windows\CurrentVersion\",\"RegisteredOrganization\",,\"$batchOrganization\"" \ + "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\"Win9xSetup\",,\"%25%\\rundll.exe setupx.dll,InstallHinfSection Win9x.FirstLogon 4 %10%\\msbatch.inf\"" + + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","Win98Online",,"C:\WINDOWS\RUNDLL32.EXE ADVPACK.DLL,DelNodeRunDLL32 C:\WINDOWS\Desktop\Online~1"' + fi + + if enabled "$shortcut"; then + # Win9x remaps the share at every logon instead of depending on the XP + # NET USE /persistent switch. + printf '%s\n' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","SharedDrive",,"%25%\NET.EXE USE Z: \\host.lan\Data"' + fi + + if [ -n "$install" ]; then + + if enabled "${LOG:-}"; then + printf '%s\n' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","Install",,"C:\COMMAND.COM /C C:\OEM\install.bat > C:\OEM\install.log"' + else + printf '%s\n' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","Install",,"C:\COMMAND.COM /C C:\OEM\install.bat"' + fi + + fi + + if ! disabled "$AUTOLOGIN"; then + # A first Windows Logon with a blank password creates the Win9x credential + # state that makes later Windows Logon startups silent. Run the small helper + # before interactive logon so that one-time dialog can be completed unattended. + printf '%s\n' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Win9xLogon",,"C:\WIN9XLOG.EXE"' + fi + + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then + printf '%s\n' \ + '' \ + '[OEMDrivers]' \ + "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\",\"OtherDevicePath\",,\"C:\\WINDOWS\\INF\\OTHER;C:\\$folder\"" + fi + + printf '%s\n' \ + '' \ + '[Win9x.FirstLogon]' \ + "AddReg=$firstLogonAddReg" + + if [ -n "$firstLogonDelReg" ]; then + printf '%s\n' "DelReg=$firstLogonDelReg" + fi + + if [ -n "$firstLogonDelFiles" ]; then + printf '%s\n' "DelFiles=$firstLogonDelFiles" + fi + + if enabled "$shortcut"; then + printf '%s\n' 'UpdateInis=Win9x.Shortcut' + fi + + printf '%s\n' \ + '' \ + '[Win9x.User]' \ + 'HKCU,"Control Panel\Desktop","SCRNSAVE.EXE",,""' \ + 'HKCU,"Control Panel\Desktop","ScreenSaveActive",,"0"' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' + + if ! disabled "$AUTOLOGIN"; then + printf '%s\n' \ + '' \ + '[Win9x.Logon]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Win9xLogon",,,' + fi + + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + '' \ + '[Win98.User]' \ + 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Start Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Bar",,"http://www.google.com"' \ + '' \ + '[Win98.Welcome]' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Run","Welcome",,,' \ + '' \ + '[Win98.MSN]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{4B876A40-4EE8-11D1-811E-00C04FB98EEC}",,,' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{88667D10-10F0-11D0-8150-00AA00BF8457}",,,' \ + '' \ + '[Win98.Connect]' \ + 'connec~1.lnk' + fi + + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + '' \ + '[DestinationDirs]' \ + 'Win98.Connect=10,Desktop' + fi + + if enabled "$shortcut"; then + printf '%s\n' \ + '' \ + '[Win9x.Shortcut]' + printf 'setup.ini, progman.groups,, "group1=""%s"""\n' "$desktop" + printf '%s\n' \ + 'setup.ini, group1,,"""Shared"",""\\host.lan\Data"",""%10%\EXPLORER.EXE"",0,,,""Shared folder"""' + fi + + printf '%s\n' \ + '' \ + '[Win9x.SystemIni]' \ + '%10%\system.ini,boot,"mouse.drv=mouse.drv","mouse.drv=vbmouse.drv"' \ + '%10%\system.ini,386Enh,,"MaxPhysPage=100000"' \ + '%10%\system.ini,vcache,,"MaxFileCache=65536"' \ + '' \ + '[Win9x.SystemCb]' \ + '%10%\system.cb,386Enh,,"MaxPhysPage=100000"' \ + '' \ + '[Setup]' \ + 'Express=1' \ + 'InstallDir="C:\WINDOWS"' \ + 'InstallType=3' + + # When KEY is supplied, pass it through so retail media does not stop later + # for the product-key dialog. Leave it absent otherwise because some OEM + # media handles its product identification differently. + if [ -n "$batchKey" ]; then + printf 'ProductKey="%s"\n' "$batchKey" + fi + + printf '%s\n' \ + 'EBD=0' \ + 'ShowEula=0' \ + 'Network=1' \ + 'DevicePath=1' \ + 'NoPrompt2Boot=1' \ + 'TimeZone=Pacific' \ + '' \ + '[NameAndOrg]' \ + "Name=\"$batchUsername\"" \ + "Org=\"$batchOrganization\"" \ + 'Display=0' \ + '' \ + '[Display]' \ + 'BitsPerPel=16' \ + "XResolution=$WIDTH" \ + "YResolution=$HEIGHT" \ + '' \ + '[Network]' \ + "ComputerName=\"$batchHost\"" \ + "Workgroup=\"$batchWorkgroup\"" \ + 'PrimaryLogon=Windows' \ + 'Display=0' \ + '' + } | unix2dos > "$target/MSBATCH.INF" || return 1 + + # Reuse the same driver archive extraction used by the XP/2003 path. All + # Windows 9x support files live together under win9x/ in that archive. + local drivers="/tmp/drivers" + local win9x="$drivers/win9x" + local patcher="$win9x/patcher9x/patcher9x" + local mouse="$win9x/mouse" + local display="$win9x/boxv9x" + local logon="$win9x/win9xlog/WIN9XLOG.EXE" + + extractDrivers "$drivers" || return 1 + + if [ ! -f "$patcher" ]; then + rm -rf "$drivers" || : + error "Failed to locate Patcher9x!" + return 1 + fi + + if [ ! -f "$mouse/VBMOUSE.EXE" ] || [ ! -f "$mouse/VBMOUSE.DRV" ]; then + rm -rf "$drivers" || : + error "Failed to locate required Windows 9x mouse drivers!" + return 1 + fi + + if ! disabled "$AUTOLOGIN" && [ ! -f "$logon" ]; then + rm -rf "$drivers" || : + error "Failed to locate Windows 9x automatic logon helper!" + return 1 + fi + + chmod 755 "$patcher" || { + rm -rf "$drivers" || : + error "Failed to make Patcher9x executable!" + return 1 + } + + local msg="Patching Windows setup..." + info "$msg" && html "$msg" + + local patch_output + + if ! patch_output=$("$patcher" -auto -unselect creg "$target" 2>&1); then + [ -z "$patch_output" ] || printf '%s\n' "$patch_output" >&2 + rm -rf "$drivers" || : + error "Failed to patch $desc setup files!" + return 1 + fi + + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then + stageWin9xDisplayDriver "$target" "$display" "$desc" || { + rm -rf "$drivers" || : + return 1 + } + + if ! mv -f -- \ + "$target/BOXV9X/boxv9x.inf" \ + "$target/BOXV9X/boxvmini.drv" \ + "$target/BOXV9X/boxvmini.vxd" \ + "$target/"; then + rm -rf "$drivers" || : + error "Failed to stage the Windows 9x display driver in the setup source!" + return 1 + fi + + rm -rf -- "$target/BOXV9X" || return 1 + : > "$target/BOXV9X" || return 1 + fi + + if [[ "${id,,}" == "win98"* ]]; then + integrateWin9xSetupMouse "$target" "$desc" "$mouse/VBMOUSE.DRV" || { + rm -rf "$drivers" || : + return 1 + } + fi + + # Do not copy optical-media AutoRun metadata onto the system disk; Explorer + # otherwise treats C: like the installation CD and runs its default action. + find "$dir" -maxdepth 1 -type f -iname 'AUTORUN.INF' -delete || return 1 + + # Build the hard-disk system image from the setup files themselves. Do not rely + # on a particular El Torito floppy-image filename: some perfectly valid Win9x + # discs expose a differently named boot image, while the required DOS system + # files are already present in the installation cabinets. + if ! createWin9xSystemImage "$dir" "$TMP/windows.img" "$desc" "$folder" "$options" "$mouse" "$logon"; then + rm -rf "$drivers" || : + return 1 + fi + + rm -rf "$drivers" || : + SYSTEM="$TMP/windows.img" + + return 0 +} + +isReactOSLiveCD() { + + local iso="$1" + local files + + files=$(bsdtar -tf "$iso" 2>/dev/null) || return 1 + + ! grep -Ei '(^|/)reactos/txtsetup\.sif(;1)?$' <<< "$files" >/dev/null +} + detectReactOS() { local dir="$1" From c094c2a2e94341ddd586350f1482d4c01447e9f1 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 12:46:31 +0200 Subject: [PATCH 05/29] Update define.sh --- src/define.sh | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/define.sh b/src/define.sh index 737067204..d0b976c36 100644 --- a/src/define.sh +++ b/src/define.sh @@ -101,8 +101,6 @@ parseVersion() { VERSION="win98" ;; "95" | "95c" | "win95" | "win95c" | "windows95" | "windows 95" ) VERSION="win95" ;; - "nt4" | "nt40" | "winnt4" | "windowsnt4" | "windows nt4" | "windows nt 4" ) - VERSION="winnt4" ;; "25" | "2025" | "win25" | "win2025" | "windows2025" | "windows 2025" ) VERSION="win2025-eval" ;; "22" | "2022" | "win22" | "win2022" | "windows2022" | "windows 2022" ) @@ -1405,11 +1403,6 @@ getLink1() { sum="5456632a73a3c70f8e5ee566374876266a81f61968e623ef5500028dec8e2979" url="9x/95/Microsoft%20Windows%2095C%20%284.03.1216.osr2.5%29.zip" ;; - "winnt4" ) - size=323070637 - sum="30b93288e3cd17bf91b915b951e6ff25e6cdf26b062352341e066b985229a731" - url="nt/Microsoft%20Windows%20NT%204.0%20Workstation%20%284.00.1381.1.sp1%29.rar" - ;; esac case "${ret,,}" in @@ -1704,11 +1697,6 @@ getLink4() { sum="493212b2a3cd391269e55a8fde5ee0a35e29ddece4b5910ec49df69ab62f5e26" url="win-95-osr-25_202103/Win95_OSR25.iso" ;; - "winnt4" ) - size=605820928 - sum="4b397a3d60cab870d0b27c4fe2fe1f2942343cda5494733dfd268ae0d3695f42" - url="winnt40wks_sp1_en/winnt40wks_sp1_en.iso" - ;; "core11" ) size=3304132608 sum="c0e0252b24144b8defb6c7ded2bc09f9297daf1fb8369b16c5b85382331eb47f" From 14866834aac8a5bd43a72069baf9743fcf627f42 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 12:47:41 +0200 Subject: [PATCH 06/29] Update define.sh --- src/define.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/define.sh b/src/define.sh index d0b976c36..382edff20 100644 --- a/src/define.sh +++ b/src/define.sh @@ -516,8 +516,6 @@ printEdition() { ;; "winxp"* ) edition="Professional" ;; - "winnt4"* ) - edition="Workstation" ;; "win2019-hv"* ) edition="2019" ;; "win20"* ) From 55c1354806766f502f2099acd585c2eaa6e89d82 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 12:53:10 +0200 Subject: [PATCH 07/29] Update legacy.sh --- src/legacy.sh | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 09366b03d..2bbb5dc4e 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1851,7 +1851,6 @@ prepareWin9xInstall() { local firstLogonAddReg="Win9x.User" local firstLogonDelReg="" - local firstLogonDelFiles="" if ! disabled "$AUTOLOGIN"; then firstLogonDelReg="Win9x.Logon" @@ -1860,8 +1859,7 @@ prepareWin9xInstall() { if [[ "${id,,}" == "win98"* ]]; then firstLogonAddReg+=",Win98.User" [ -n "$firstLogonDelReg" ] && firstLogonDelReg+="," - firstLogonDelReg+="Win98.Welcome,Win98.MSN" - firstLogonDelFiles="Win98.Connect" + firstLogonDelReg+="Win98.Welcome" fi # Cap the memory Win9x itself can address at 4 GiB without changing QEMU's @@ -1889,15 +1887,17 @@ prepareWin9xInstall() { "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\"Win9xSetup\",,\"%25%\\rundll.exe setupx.dll,InstallHinfSection Win9x.FirstLogon 4 %10%\\msbatch.inf\"" if [[ "${id,,}" == "win98"* ]]; then + # Windows 98 can recreate its online-service desktop items late in setup. + # Schedule one cleanup from Run so it executes after the setup RunOnce work. printf '%s\n' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","Win98Online",,"C:\WINDOWS\RUNDLL32.EXE ADVPACK.DLL,DelNodeRunDLL32 C:\WINDOWS\Desktop\Online~1"' + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","Win98Cleanup",,"%25%\RunDll32.exe advpack.dll,LaunchINFSection %10%\msbatch.inf,Win98.Cleanup,5"' fi if enabled "$shortcut"; then # Win9x remaps the share at every logon instead of depending on the XP # NET USE /persistent switch. printf '%s\n' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","SharedDrive",,"%25%\NET.EXE USE Z: \\host.lan\Data"' + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","SharedDrive",,"C:\COMMAND.COM /C %25%\NET.EXE USE Z: \\host.lan\Data >NUL"' fi if [ -n "$install" ]; then @@ -1936,10 +1936,6 @@ prepareWin9xInstall() { printf '%s\n' "DelReg=$firstLogonDelReg" fi - if [ -n "$firstLogonDelFiles" ]; then - printf '%s\n' "DelFiles=$firstLogonDelFiles" - fi - if enabled "$shortcut"; then printf '%s\n' 'UpdateInis=Win9x.Shortcut' fi @@ -1949,6 +1945,7 @@ prepareWin9xInstall() { '[Win9x.User]' \ 'HKCU,"Control Panel\Desktop","SCRNSAVE.EXE",,""' \ 'HKCU,"Control Panel\Desktop","ScreenSaveActive",,"0"' \ + 'HKCU,"Control Panel\Desktop","DragFullWindows",,"1"' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' if ! disabled "$AUTOLOGIN"; then @@ -1964,25 +1961,41 @@ prepareWin9xInstall() { '[Win98.User]' \ 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","Start Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","First Home Page",,"http://www.google.com"' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Page",,"http://www.google.com"' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Bar",,"http://www.google.com"' \ '' \ '[Win98.Welcome]' \ 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Run","Welcome",,,' \ '' \ + '[Win98.Cleanup]' \ + 'DelReg=Win98.MSN,Win98.CleanupRun' \ + 'DelFiles=Win98.Connect,Win98.ConnectAll' \ + 'RunPostSetupCommands=Win98.CleanupDirs' \ + '' \ + '[Win98.CleanupRun]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","Win98Cleanup"' \ + '' \ '[Win98.MSN]' \ 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{4B876A40-4EE8-11D1-811E-00C04FB98EEC}",,,' \ 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{88667D10-10F0-11D0-8150-00AA00BF8457}",,,' \ '' \ '[Win98.Connect]' \ - 'connec~1.lnk' + 'connec~1.lnk' \ + '' \ + '[Win98.ConnectAll]' \ + 'connec~1.lnk' \ + '' \ + '[Win98.CleanupDirs]' \ + '%25%\rundll32.exe advpack.dll,DelNodeRunDLL32 %10%\Desktop\Online~1' fi if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' \ '' \ '[DestinationDirs]' \ - 'Win98.Connect=10,Desktop' + 'Win98.Connect=10,Desktop' \ + 'Win98.ConnectAll=10,alluse~1\desktop' fi if enabled "$shortcut"; then @@ -1991,7 +2004,7 @@ prepareWin9xInstall() { '[Win9x.Shortcut]' printf 'setup.ini, progman.groups,, "group1=""%s"""\n' "$desktop" printf '%s\n' \ - 'setup.ini, group1,,"""Shared"",""\\host.lan\Data"",""%10%\EXPLORER.EXE"",0,,,""Shared folder"""' + 'setup.ini, group1,,"""Shared"",""\\host.lan\Data"",""%11%\SHELL32.DLL"",3,,,""Shared folder"""' fi printf '%s\n' \ From 759dfe8a418311ce34103e8de7bb919d6d87335f Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 13:07:37 +0200 Subject: [PATCH 08/29] Update readme.md --- readme.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/readme.md b/readme.md index 5417640f8..70b6116f0 100644 --- a/readme.md +++ b/readme.md @@ -125,6 +125,11 @@ For a complete graphical desktop experience, see [WinBoat](https://winboat.app), | `vu` | Windows Vista Ultimate | 3.0 GB | | `xp` | Windows XP Professional | 0.6 GB | | `2k` | Windows 2000 Professional | 0.4 GB | + | `xp` | Windows XP Professional | 0.6 GB | + | `2k` | Windows 2000 Professional | 0.4 GB | + | `me` | Windows ME | 0.5 GB | + | `98` | Windows 98 | 0.7 GB | + | `95` | Windows 95 | 0.6 GB | |||| | `2025` | Windows Server 2025 | 7.6 GB | | `2022` | Windows Server 2022 | 6.0 GB | From c11f826f692c313ecc173c80a81834a7ed349005 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 16:21:43 +0200 Subject: [PATCH 09/29] Update legacy.sh --- src/legacy.sh | 82 +++++++++++++++++++++++++++++++-------------------- 1 file changed, 50 insertions(+), 32 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 2bbb5dc4e..cbec0f8b6 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1142,7 +1142,7 @@ createWin9xSystemImage() { local start=63 local sectors=$((size / 512 - start)) local offset=$((start * 512)) - local entry base find_pid + local entry find_pid setup_dir local fs attributes local entries=() @@ -1520,26 +1520,21 @@ createWin9xSystemImage() { # Windows is started explicitly from AUTOEXEC.BAT so the DOS environment stays # available for the mouse TSR. - mapfile -d '' entries < <( - find "$dir" -mindepth 1 -maxdepth 1 -print0 - ) - - find_pid=$! + # Setup only needs its release-specific source directory. Keeping the rest + # of the optical-media root off C: avoids exposing unrelated CD contents in + # the installed system while retaining the common Win95/98/Me image path. + setup_dir=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -iname "$setup" -print -quit) || return 1 - if ! wait "$find_pid"; then + if [ -z "$setup_dir" ]; then rm -f -- "$tmp" - error "Failed to enumerate $desc files!" + error "Failed to locate the $setup Setup folder in $desc ISO image!" return 1 fi - for entry in "${entries[@]}"; do - - base=$(basename "$entry") + entries=("$setup_dir") + [ -d "$dir/OEM" ] && entries+=("$dir/OEM") - case "${base,,}" in - "io.sys" | "msdos.sys" | "command.com" | "autoexec.bat" | "config.sys" ) - continue ;; - esac + for entry in "${entries[@]}"; do if ! MTOOLSRC="$config" mcopy -Q -s "$entry" w:/; then rm -f -- "$tmp" @@ -1765,7 +1760,7 @@ prepareWin9xInstall() { local dir="$3" local desc="$4" - local folder options="/IS /IQ /IT" target + local folder monitor="Plug and Play Monitor" options="/IS /IQ /IT" target local shortcut="Y" if disabled "$SHORTCUT" || disabled "${SAMBA:-Y}"; then @@ -1779,7 +1774,8 @@ prepareWin9xInstall() { case "${id,,}" in "win95"* ) - folder="WIN95" ;; + folder="WIN95" + monitor="Plug and Play Monitor (VESA DDC)" ;; "win98"* ) folder="WIN98" options="/P I /IE /NF $options" ;; @@ -1849,6 +1845,13 @@ prepareWin9xInstall() { addReg+=",OEMDrivers" fi + if [[ "${id,,}" == "win98"* ]]; then + # Batch.Update must remain last: Windows 98 creates some Internet desktop + # items late in Setup, so register their delayed cleanup after other AddReg + # work. Active Setup reapplies browser defaults after IE initializes users. + addReg+=",Win98.Power,Win98.ActiveSetup,Win98.BatchUpdate" + fi + local firstLogonAddReg="Win9x.User" local firstLogonDelReg="" @@ -1857,7 +1860,7 @@ prepareWin9xInstall() { fi if [[ "${id,,}" == "win98"* ]]; then - firstLogonAddReg+=",Win98.User" + firstLogonAddReg+=",Win98.User,Win98.PowerUser" [ -n "$firstLogonDelReg" ] && firstLogonDelReg+="," firstLogonDelReg+="Win98.Welcome" fi @@ -1886,13 +1889,6 @@ prepareWin9xInstall() { "HKLM,\"SOFTWARE\Microsoft\Windows\CurrentVersion\",\"RegisteredOrganization\",,\"$batchOrganization\"" \ "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\"Win9xSetup\",,\"%25%\\rundll.exe setupx.dll,InstallHinfSection Win9x.FirstLogon 4 %10%\\msbatch.inf\"" - if [[ "${id,,}" == "win98"* ]]; then - # Windows 98 can recreate its online-service desktop items late in setup. - # Schedule one cleanup from Run so it executes after the setup RunOnce work. - printf '%s\n' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","Win98Cleanup",,"%25%\RunDll32.exe advpack.dll,LaunchINFSection %10%\msbatch.inf,Win98.Cleanup,5"' - fi - if enabled "$shortcut"; then # Win9x remaps the share at every logon instead of depending on the XP # NET USE /persistent switch. @@ -1913,10 +1909,11 @@ prepareWin9xInstall() { fi if ! disabled "$AUTOLOGIN"; then - # A first Windows Logon with a blank password creates the Win9x credential - # state that makes later Windows Logon startups silent. Run the small helper - # before interactive logon so that one-time dialog can be completed unattended. + # Windows Logon only becomes silent with a blank password when Win9x user + # profiles are disabled. Keep that common Win95/98/Me prerequisite beside + # the helper that completes the one-time initial logon. printf '%s\n' \ + 'HKLM,"Network\Logon","UserProfiles",1,00' \ 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Win9xLogon",,"C:\WIN9XLOG.EXE"' fi @@ -1957,11 +1954,33 @@ prepareWin9xInstall() { if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' \ + '' \ + '[Win98.Power]' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Controls Folder\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,02,00,00,00,04,00,00,c0,00,00,00,00,02,00,00,00,04,00,00,c0,00,00,00,00' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ + '' \ + '[Win98.ActiveSetup]' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx",,,">Batch 98 - General Settings"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","IsInstalled",0x00000001,01,00,00,00' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","Version",,"3,0,0,0"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","StubPath",,"%25%\rundll.exe setupx.dll,InstallHinfSection Win98.Browser 4 %10%\msbatch.inf"' \ + '' \ + '[Win98.BatchUpdate]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","Win98Cleanup",,"%25%\RunDll32.exe advpack.dll,LaunchINFSection %10%\msbatch.inf,Win98.Cleanup,5"' \ + '' \ + '[Win98.Browser]' \ + 'AddReg=Win98.User' \ + '' \ + '[Win98.PowerUser]' \ + 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ + 'HKCU,"Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ '' \ '[Win98.User]' \ 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","Start Page",,"http://www.google.com"' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","First Home Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Default_Page_URL",,"http://www.google.com"' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Page",,"http://www.google.com"' \ 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Bar",,"http://www.google.com"' \ '' \ @@ -2042,10 +2061,9 @@ prepareWin9xInstall() { "Org=\"$batchOrganization\"" \ 'Display=0' \ '' \ - '[Display]' \ - 'BitsPerPel=16' \ - "XResolution=$WIDTH" \ - "YResolution=$HEIGHT" \ + '[System]' \ + "DisplChar=16,$WIDTH,$HEIGHT" \ + "Monitor=\"$monitor\"" \ '' \ '[Network]' \ "ComputerName=\"$batchHost\"" \ From 97d0b575f2d4cf9a16d8eb15d25a2b01df6ea772 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 18:03:47 +0200 Subject: [PATCH 10/29] Update legacy.sh --- src/legacy.sh | 437 ++++++++++++++++++++++++++++---------------------- 1 file changed, 245 insertions(+), 192 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index cbec0f8b6..52a6592d7 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1754,107 +1754,119 @@ integrateWin9xSetupMouse() { return 0 } -prepareWin9xInstall() { - - local id="$1" - local dir="$3" - local desc="$4" - - local folder monitor="Plug and Play Monitor" options="/IS /IQ /IT" target - local shortcut="Y" - - if disabled "$SHORTCUT" || disabled "${SAMBA:-Y}"; then - shortcut="N" - fi - - if [ -n "$DOMAIN" ]; then - error "The DOMAIN variable is not supported for $desc!" - return 1 - fi - - case "${id,,}" in - "win95"* ) - folder="WIN95" - monitor="Plug and Play Monitor (VESA DDC)" ;; - "win98"* ) - folder="WIN98" - options="/P I /IE /NF $options" ;; - "win9x"* ) - folder="WIN9X" - options="/IE /NF $options" ;; - * ) - return 0 ;; - esac - - target=$(find "$dir" -maxdepth 1 -type d -iname "$folder" -print -quit) || return 1 - - if [ -z "$target" ]; then - error "Failed to locate the $folder Setup folder in $desc ISO image!" - return 1 - fi - - [ -z "$WIDTH" ] && WIDTH="1280" - [ -z "$HEIGHT" ] && HEIGHT="720" - - validateResolution "WIDTH" "$WIDTH" 320 || return 1 - validateResolution "HEIGHT" "$HEIGHT" 200 || return 1 - - # Express setup still needs concrete identity values. If NameAndOrg is absent, - # Windows 9x stops during GUI setup for the user's name/company; if Network is - # missing the identity fields, it later stops for the computer name/workgroup. - # Win9x has no useful "*" computer-name generator, so provide a valid default - # when HOST was not configured. - local host="${HOST:-Docker}" - local username="${USERNAME:-Docker}" - local workgroup="${WORKGROUP:-WORKGROUP}" - - validateComputerName "$host" || return 1 +writeWin9xUserRegistry() { + + printf '%s\n' \ + '[Win9x.User]' \ + 'HKCU,"Control Panel\Desktop","SCRNSAVE.EXE",,""' \ + 'HKCU,"Control Panel\Desktop","ScreenSaveActive",,"0"' \ + 'HKCU,"Control Panel\Desktop","DragFullWindows",,"1"' \ + 'HKCU,"Control Panel\Desktop","MenuShowDelay",,"100"' \ + 'HKCU,"Control Panel\Desktop","FontSmoothing",,"2"' \ + 'HKCU,"Control Panel\Desktop","SmoothScroll",0x00010001,0' \ + 'HKCU,"Control Panel\Desktop\WindowMetrics","MinAnimate",,"0"' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","NoActiveDesktop",1,01,00,00,00' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer","link",1,00,00,00,00' +} - # Reuse the normal OEM-folder preparation so /OEM and COMMAND behave like the - # other Windows paths, but place the user payload directly at C:\OEM in the - # generated Win9x system image. - local win9x_oem="$dir/OEM" - local install="" +writeWin9xMachineRegistry() { - if ! addFolder "$dir" "win9x"; then - error "Failed to add OEM folder to image!" - return 1 - fi + printf '%s\n' \ + '[Win9x.Machine]' \ + 'HKLM,"System\CurrentControlSet\Control\FileSystem\CDFS","CacheSize",1,ac,09,00,00' \ + 'HKLM,"System\CurrentControlSet\Control\FileSystem\CDFS","Prefetch",1,e4,00,00,00' +} - if [ -d "$win9x_oem" ]; then - install=$(find "$win9x_oem" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1 - fi +writeWin98Registry() { + + printf '%s\n' \ + '[Win98.Power]' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Controls Folder\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,02,00,00,00,04,00,00,c0,00,00,00,00,02,00,00,00,04,00,00,c0,00,00,00,00' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ + '' \ + '[Win98.ActiveSetup]' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx",,,">Batch 98 - General Settings"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","IsInstalled",0x00000001,01,00,00,00' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","Version",,"3,0,0,0"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","StubPath",,"%25%\rundll.exe setupx.dll,InstallHinfSection Win98.Browser 4 %10%\msbatch.inf"' \ + '' \ + '[Win98.Browser]' \ + 'AddReg=Win98.User' \ + 'DelReg=Win98.MSN,Win98.ICWDesktop' \ + 'DelFiles=Win98.Connect,Win98.ConnectAll' \ + 'RunPostSetupCommands=Win98.CleanupDirs' \ + '' \ + '[Win98.PowerUser]' \ + 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ + 'HKCU,"Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ + '' \ + '[Win98.User]' \ + 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Start Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","First Home Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Default_Page_URL",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Page",,"http://www.google.com"' \ + 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Bar",,"http://www.google.com"' \ + '' \ + '[Win98.Welcome]' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Run","Welcome",,,' \ + '' \ + '[Win98.MSN]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{4B876A40-4EE8-11D1-811E-00C04FB98EEC}",,,' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{88667D10-10F0-11D0-8150-00AA00BF8457}",,,' \ + '' \ + '[Win98.ICWDesktop]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","^SetupICWDesktop",,,' \ + 'HKCU,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","^SetupICWDesktop",,,' \ + 'HKU,".DEFAULT\Software\Microsoft\Windows\CurrentVersion\RunOnce","^SetupICWDesktop",,,' \ + '' \ + '[Win98.Connect]' \ + 'connec~1.lnk' \ + '"Connect to the Internet.lnk"' \ + '' \ + '[Win98.ConnectAll]' \ + 'connec~1.lnk' \ + '"Connect to the Internet.lnk"' \ + '' \ + '[Win98.CleanupDirs]' \ + '%25%\rundll32.exe advpack.dll,DelNodeRunDLL32 %10%\Desktop\Online~1' \ + '' \ + '[DestinationDirs]' \ + 'Win98.Connect=10,Desktop' \ + 'Win98.ConnectAll=10,alluse~1\desktop' +} - # MSBATCH.INF and WINNT.SIF both use quoted INF-style values. Reuse the SIF - # escaping helper so quotes and percent signs cannot alter the answer file. - local batchHost batchUsername batchOrganization batchWorkgroup batchKey="" - batchHost=$(escapeSIFValue "$host") || return 1 - batchUsername=$(escapeSIFValue "$username") || return 1 - batchOrganization=$(escapeSIFValue "$APP for $ENGINE") || return 1 - batchWorkgroup=$(escapeSIFValue "$workgroup") || return 1 +writeWin9xAnswerFile() { - if [ -n "$KEY" ]; then - batchKey=$(escapeSIFValue "$KEY") || return 1 - fi + local target="$1" + local id="$2" + local folder="$3" + local monitor="$4" + local batchHost="$5" + local batchUsername="$6" + local batchOrganization="$7" + local batchWorkgroup="$8" + local batchKey="$9" + local shortcut="${10}" + local install="${11}" local desktop="%10%\Desktop" - - local addReg="OPKInstall" + local addReg="OPKInstall,Win9x.Machine" + local firstLogonAddReg="Win9x.User" + local firstLogonDelReg="" if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then addReg+=",OEMDrivers" fi if [[ "${id,,}" == "win98"* ]]; then - # Batch.Update must remain last: Windows 98 creates some Internet desktop - # items late in Setup, so register their delayed cleanup after other AddReg - # work. Active Setup reapplies browser defaults after IE initializes users. - addReg+=",Win98.Power,Win98.ActiveSetup,Win98.BatchUpdate" + # Active Setup reapplies browser defaults after IE initializes users and + # removes the Internet desktop items before Explorer lays out the desktop. + addReg+=",Win98.Power,Win98.ActiveSetup" fi - local firstLogonAddReg="Win9x.User" - local firstLogonDelReg="" - if ! disabled "$AUTOLOGIN"; then firstLogonDelReg="Win9x.Logon" fi @@ -1917,6 +1929,9 @@ prepareWin9xInstall() { 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Win9xLogon",,"C:\WIN9XLOG.EXE"' fi + printf '%s\n' '' + writeWin9xMachineRegistry + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then printf '%s\n' \ '' \ @@ -1937,13 +1952,8 @@ prepareWin9xInstall() { printf '%s\n' 'UpdateInis=Win9x.Shortcut' fi - printf '%s\n' \ - '' \ - '[Win9x.User]' \ - 'HKCU,"Control Panel\Desktop","SCRNSAVE.EXE",,""' \ - 'HKCU,"Control Panel\Desktop","ScreenSaveActive",,"0"' \ - 'HKCU,"Control Panel\Desktop","DragFullWindows",,"1"' \ - 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' + printf '%s\n' '' + writeWin9xUserRegistry if ! disabled "$AUTOLOGIN"; then printf '%s\n' \ @@ -1953,68 +1963,8 @@ prepareWin9xInstall() { fi if [[ "${id,,}" == "win98"* ]]; then - printf '%s\n' \ - '' \ - '[Win98.Power]' \ - 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Controls Folder\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,02,00,00,00,04,00,00,c0,00,00,00,00,02,00,00,00,04,00,00,c0,00,00,00,00' \ - 'HKU,".DEFAULT\Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ - 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ - '' \ - '[Win98.ActiveSetup]' \ - 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx",,,">Batch 98 - General Settings"' \ - 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","IsInstalled",0x00000001,01,00,00,00' \ - 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","Version",,"3,0,0,0"' \ - 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","StubPath",,"%25%\rundll.exe setupx.dll,InstallHinfSection Win98.Browser 4 %10%\msbatch.inf"' \ - '' \ - '[Win98.BatchUpdate]' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","Win98Cleanup",,"%25%\RunDll32.exe advpack.dll,LaunchINFSection %10%\msbatch.inf,Win98.Cleanup,5"' \ - '' \ - '[Win98.Browser]' \ - 'AddReg=Win98.User' \ - '' \ - '[Win98.PowerUser]' \ - 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ - 'HKCU,"Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ - '' \ - '[Win98.User]' \ - 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ - 'HKCU,"Software\Microsoft\Internet Explorer\Main","Start Page",,"http://www.google.com"' \ - 'HKCU,"Software\Microsoft\Internet Explorer\Main","First Home Page",,"http://www.google.com"' \ - 'HKCU,"Software\Microsoft\Internet Explorer\Main","Default_Page_URL",,"http://www.google.com"' \ - 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Page",,"http://www.google.com"' \ - 'HKCU,"Software\Microsoft\Internet Explorer\Main","Search Bar",,"http://www.google.com"' \ - '' \ - '[Win98.Welcome]' \ - 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Run","Welcome",,,' \ - '' \ - '[Win98.Cleanup]' \ - 'DelReg=Win98.MSN,Win98.CleanupRun' \ - 'DelFiles=Win98.Connect,Win98.ConnectAll' \ - 'RunPostSetupCommands=Win98.CleanupDirs' \ - '' \ - '[Win98.CleanupRun]' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","Win98Cleanup"' \ - '' \ - '[Win98.MSN]' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{4B876A40-4EE8-11D1-811E-00C04FB98EEC}",,,' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\{88667D10-10F0-11D0-8150-00AA00BF8457}",,,' \ - '' \ - '[Win98.Connect]' \ - 'connec~1.lnk' \ - '' \ - '[Win98.ConnectAll]' \ - 'connec~1.lnk' \ - '' \ - '[Win98.CleanupDirs]' \ - '%25%\rundll32.exe advpack.dll,DelNodeRunDLL32 %10%\Desktop\Online~1' - fi - - if [[ "${id,,}" == "win98"* ]]; then - printf '%s\n' \ - '' \ - '[DestinationDirs]' \ - 'Win98.Connect=10,Desktop' \ - 'Win98.ConnectAll=10,alluse~1\desktop' + printf '%s\n' '' + writeWin98Registry fi if enabled "$shortcut"; then @@ -2056,6 +2006,9 @@ prepareWin9xInstall() { 'NoPrompt2Boot=1' \ 'TimeZone=Pacific' \ '' \ + '[OptionalComponents]' \ + '"The Microsoft Network"=0' \ + '' \ '[NameAndOrg]' \ "Name=\"$batchUsername\"" \ "Org=\"$batchOrganization\"" \ @@ -2073,37 +2026,19 @@ prepareWin9xInstall() { '' } | unix2dos > "$target/MSBATCH.INF" || return 1 - # Reuse the same driver archive extraction used by the XP/2003 path. All - # Windows 9x support files live together under win9x/ in that archive. - local drivers="/tmp/drivers" - local win9x="$drivers/win9x" - local patcher="$win9x/patcher9x/patcher9x" - local mouse="$win9x/mouse" - local display="$win9x/boxv9x" - local logon="$win9x/win9xlog/WIN9XLOG.EXE" - - extractDrivers "$drivers" || return 1 + return 0 +} - if [ ! -f "$patcher" ]; then - rm -rf "$drivers" || : - error "Failed to locate Patcher9x!" - return 1 - fi +patchWin9xSetupFiles() { - if [ ! -f "$mouse/VBMOUSE.EXE" ] || [ ! -f "$mouse/VBMOUSE.DRV" ]; then - rm -rf "$drivers" || : - error "Failed to locate required Windows 9x mouse drivers!" - return 1 - fi - - if ! disabled "$AUTOLOGIN" && [ ! -f "$logon" ]; then - rm -rf "$drivers" || : - error "Failed to locate Windows 9x automatic logon helper!" - return 1 - fi + local id="$1" + local target="$2" + local desc="$3" + local patcher="$4" + local mouse="$5" + local display="$6" chmod 755 "$patcher" || { - rm -rf "$drivers" || : error "Failed to make Patcher9x executable!" return 1 } @@ -2115,23 +2050,18 @@ prepareWin9xInstall() { if ! patch_output=$("$patcher" -auto -unselect creg "$target" 2>&1); then [ -z "$patch_output" ] || printf '%s\n' "$patch_output" >&2 - rm -rf "$drivers" || : error "Failed to patch $desc setup files!" return 1 fi if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then - stageWin9xDisplayDriver "$target" "$display" "$desc" || { - rm -rf "$drivers" || : - return 1 - } + stageWin9xDisplayDriver "$target" "$display" "$desc" || return 1 if ! mv -f -- \ "$target/BOXV9X/boxv9x.inf" \ "$target/BOXV9X/boxvmini.drv" \ "$target/BOXV9X/boxvmini.vxd" \ "$target/"; then - rm -rf "$drivers" || : error "Failed to stage the Windows 9x display driver in the setup source!" return 1 fi @@ -2141,10 +2071,133 @@ prepareWin9xInstall() { fi if [[ "${id,,}" == "win98"* ]]; then - integrateWin9xSetupMouse "$target" "$desc" "$mouse/VBMOUSE.DRV" || { - rm -rf "$drivers" || : - return 1 - } + integrateWin9xSetupMouse "$target" "$desc" "$mouse/VBMOUSE.DRV" || return 1 + fi + + return 0 +} + +prepareWin9xInstall() { + + local id="$1" + local dir="$3" + local desc="$4" + + local folder monitor="Plug and Play Monitor" options="/IS /IQ /IT" target + local shortcut="Y" + + if disabled "$SHORTCUT" || disabled "${SAMBA:-Y}"; then + shortcut="N" + fi + + if [ -n "$DOMAIN" ]; then + error "The DOMAIN variable is not supported for $desc!" + return 1 + fi + + case "${id,,}" in + "win95"* ) + folder="WIN95" + monitor="Plug and Play Monitor (VESA DDC)" ;; + "win98"* ) + folder="WIN98" + monitor="Plug and Play Monitor (VESA DDC)" + options="/P I /IE /NF $options" ;; + "win9x"* ) + folder="WIN9X" + options="/IE /NF $options" ;; + * ) + return 0 ;; + esac + + target=$(find "$dir" -maxdepth 1 -type d -iname "$folder" -print -quit) || return 1 + + if [ -z "$target" ]; then + error "Failed to locate the $folder Setup folder in $desc ISO image!" + return 1 + fi + + [ -z "$WIDTH" ] && WIDTH="1280" + [ -z "$HEIGHT" ] && HEIGHT="720" + + validateResolution "WIDTH" "$WIDTH" 320 || return 1 + validateResolution "HEIGHT" "$HEIGHT" 200 || return 1 + + # Express setup still needs concrete identity values. If NameAndOrg is absent, + # Windows 9x stops during GUI setup for the user's name/company; if Network is + # missing the identity fields, it later stops for the computer name/workgroup. + # Win9x has no useful "*" computer-name generator, so provide a valid default + # when HOST was not configured. + local host="${HOST:-Docker}" + local username="${USERNAME:-Docker}" + local workgroup="${WORKGROUP:-WORKGROUP}" + + validateComputerName "$host" || return 1 + + # Reuse the normal OEM-folder preparation so /OEM and COMMAND behave like the + # other Windows paths, but place the user payload directly at C:\OEM in the + # generated Win9x system image. + local win9x_oem="$dir/OEM" + local install="" + + if ! addFolder "$dir" "win9x"; then + error "Failed to add OEM folder to image!" + return 1 + fi + + if [ -d "$win9x_oem" ]; then + install=$(find "$win9x_oem" -maxdepth 1 -type f -iname install.bat -print -quit) || return 1 + fi + + # MSBATCH.INF and WINNT.SIF both use quoted INF-style values. Reuse the SIF + # escaping helper so quotes and percent signs cannot alter the answer file. + local batchHost batchUsername batchOrganization batchWorkgroup batchKey="" + batchHost=$(escapeSIFValue "$host") || return 1 + batchUsername=$(escapeSIFValue "$username") || return 1 + batchOrganization=$(escapeSIFValue "$APP for $ENGINE") || return 1 + batchWorkgroup=$(escapeSIFValue "$workgroup") || return 1 + + if [ -n "$KEY" ]; then + batchKey=$(escapeSIFValue "$KEY") || return 1 + fi + + writeWin9xAnswerFile \ + "$target" "$id" "$folder" "$monitor" \ + "$batchHost" "$batchUsername" "$batchOrganization" "$batchWorkgroup" \ + "$batchKey" "$shortcut" "$install" || return 1 + + # Reuse the same driver archive extraction used by the XP/2003 path. All + # Windows 9x support files live together under win9x/ in that archive. + local drivers="/tmp/drivers" + local win9x="$drivers/win9x" + local patcher="$win9x/patcher9x/patcher9x" + local mouse="$win9x/mouse" + local display="$win9x/boxv9x" + local logon="$win9x/win9xlog/WIN9XLOG.EXE" + + extractDrivers "$drivers" || return 1 + + if [ ! -f "$patcher" ]; then + rm -rf "$drivers" || : + error "Failed to locate Patcher9x!" + return 1 + fi + + if [ ! -f "$mouse/VBMOUSE.EXE" ] || [ ! -f "$mouse/VBMOUSE.DRV" ]; then + rm -rf "$drivers" || : + error "Failed to locate required Windows 9x mouse drivers!" + return 1 + fi + + if ! disabled "$AUTOLOGIN" && [ ! -f "$logon" ]; then + rm -rf "$drivers" || : + error "Failed to locate Windows 9x automatic logon helper!" + return 1 + fi + + if ! patchWin9xSetupFiles "$id" "$target" "$desc" "$patcher" "$mouse" "$display"; then + rm -rf "$drivers" || : + return 1 fi # Do not copy optical-media AutoRun metadata onto the system disk; Explorer From d7d857a8b0afe4176e458a0aa0e0dc9714a9888c Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 19:16:34 +0200 Subject: [PATCH 11/29] Update legacy.sh --- src/legacy.sh | 72 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 52a6592d7..db4166349 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1551,14 +1551,10 @@ createWin9xSystemImage() { return 1 fi - if ! disabled "$AUTOLOGIN"; then - - if ! MTOOLSRC="$config" mcopy -o "$logon" w:/WIN9XLOG.EXE; then - rm -f -- "$tmp" - error "Failed to add automatic logon support to the $desc system image!" - return 1 - fi - + if ! MTOOLSRC="$config" mcopy -o "$logon" "w:/$setup/LOGIN9X.CPL"; then + rm -f -- "$tmp" + error "Failed to add the autologin helper to the $desc system image!" + return 1 fi { @@ -1577,8 +1573,15 @@ createWin9xSystemImage() { "C:\\${setup}\\SETUP.EXE $options" \ 'GOTO END' \ ':WINDOWS' \ - 'IF EXIST C:\WINDOWS\SYSTEM\VBMOUSE.DRV GOTO START' \ - 'COPY C:\VBMOUSE.DRV C:\WINDOWS\SYSTEM\VBMOUSE.DRV >NUL' \ + 'IF EXIST C:\WINDOWS\SYSTEM\VBMOUSE.DRV GOTO LOGON' \ + 'COPY C:\VBMOUSE.DRV C:\WINDOWS\SYSTEM\VBMOUSE.DRV >NUL' + + printf '%s\n' \ + ':LOGON' \ + 'IF EXIST C:\WINDOWS\SYSTEM\LOGIN9X.CPL GOTO START' \ + "COPY C:\\${setup}\\LOGIN9X.CPL C:\\WINDOWS\\SYSTEM\\LOGIN9X.CPL >NUL" + + printf '%s\n' \ ':START' \ 'C:\VBMOUSE.EXE >NUL' \ 'C:\WINDOWS\WIN.COM' \ @@ -1647,9 +1650,24 @@ stageWin9xDisplayDriver() { return 1 fi - # BOXV9X is the source-media tag named by the upstream INF. Keep the INF - # unchanged and provide that tag beside the driver files so Setup never asks - # for a separate driver disk while installing the exact QEMU PCI match. + # The virtual display driver's DDC flag makes Win9x enumerate a Plug and + # Play monitor after the display driver starts. The unattended setup already + # selects the monitor and display mode, and BOXV9X carries a fixed mode list, + # so disable DDC in our staged copy to avoid a second monitor PnP pass. + if ! grep -Eq '^[[:space:]]*HKR,DEFAULT,DDC,,1[[:space:]]*$' "$dest/boxv9x.inf"; then + error "Failed to locate the DDC setting in the Windows 9x display driver!" + return 1 + fi + + if ! sed -i 's/HKR,DEFAULT,DDC,,1/HKR,DEFAULT,DDC,,0/' "$dest/boxv9x.inf" || + ! grep -Eq '^[[:space:]]*HKR,DEFAULT,DDC,,0[[:space:]]*$' "$dest/boxv9x.inf"; then + error "Failed to disable DDC in the Windows 9x display driver!" + return 1 + fi + + # BOXV9X is the source-media tag named by the upstream INF. Provide that tag + # beside the driver files so Setup never asks for a separate driver disk while + # installing the exact QEMU PCI match. : > "$dest/BOXV9X" || return 1 return 0 @@ -1766,7 +1784,7 @@ writeWin9xUserRegistry() { 'HKCU,"Control Panel\Desktop","SmoothScroll",0x00010001,0' \ 'HKCU,"Control Panel\Desktop\WindowMetrics","MinAnimate",,"0"' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' \ - 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","NoActiveDesktop",1,01,00,00,00' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","NoActiveDesktop",0x00010001,1' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer","link",1,00,00,00,00' } @@ -1796,7 +1814,6 @@ writeWin98Registry() { 'AddReg=Win98.User' \ 'DelReg=Win98.MSN,Win98.ICWDesktop' \ 'DelFiles=Win98.Connect,Win98.ConnectAll' \ - 'RunPostSetupCommands=Win98.CleanupDirs' \ '' \ '[Win98.PowerUser]' \ 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ @@ -1830,9 +1847,6 @@ writeWin98Registry() { 'connec~1.lnk' \ '"Connect to the Internet.lnk"' \ '' \ - '[Win98.CleanupDirs]' \ - '%25%\rundll32.exe advpack.dll,DelNodeRunDLL32 %10%\Desktop\Online~1' \ - '' \ '[DestinationDirs]' \ 'Win98.Connect=10,Desktop' \ 'Win98.ConnectAll=10,alluse~1\desktop' @@ -1921,12 +1935,13 @@ writeWin9xAnswerFile() { fi if ! disabled "$AUTOLOGIN"; then - # Windows Logon only becomes silent with a blank password when Win9x user - # profiles are disabled. Keep that common Win95/98/Me prerequisite beside - # the helper that completes the one-time initial logon. printf '%s\n' \ - 'HKLM,"Network\Logon","UserProfiles",1,00' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Win9xLogon",,"C:\WIN9XLOG.EXE"' + 'HKLM,"Network\Logon","UserProfiles",0x00010001,0' \ + "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Winlogon\",\"DefaultUserName\",,\"$batchUsername\"" \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DefaultPassword",,""' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","AutoAdminLogon",,"1"' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DontDisplayLastUserName",,"0"' \ + "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices\",\"Tweak UI\",,\"RUNDLL32.EXE %11%\\LOGIN9X.CPL,TweakLogon\"" fi printf '%s\n' '' @@ -1959,7 +1974,9 @@ writeWin9xAnswerFile() { printf '%s\n' \ '' \ '[Win9x.Logon]' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Win9xLogon",,,' + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Login9x",,,' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","AutoAdminLogon",,,' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DefaultPassword",,,' fi if [[ "${id,,}" == "win98"* ]]; then @@ -2008,6 +2025,7 @@ writeWin9xAnswerFile() { '' \ '[OptionalComponents]' \ '"The Microsoft Network"=0' \ + '"Online Services"=0' \ '' \ '[NameAndOrg]' \ "Name=\"$batchUsername\"" \ @@ -2173,7 +2191,7 @@ prepareWin9xInstall() { local patcher="$win9x/patcher9x/patcher9x" local mouse="$win9x/mouse" local display="$win9x/boxv9x" - local logon="$win9x/win9xlog/WIN9XLOG.EXE" + local logon="$win9x/login9x/login9x.cpl" extractDrivers "$drivers" || return 1 @@ -2189,9 +2207,9 @@ prepareWin9xInstall() { return 1 fi - if ! disabled "$AUTOLOGIN" && [ ! -f "$logon" ]; then + if [ ! -f "$logon" ]; then rm -rf "$drivers" || : - error "Failed to locate Windows 9x automatic logon helper!" + error "Failed to locate the autologin helper!" return 1 fi From f3cf98205dc3c70b8749a264160b92b1db8089c5 Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 20:32:50 +0200 Subject: [PATCH 12/29] Update legacy.sh --- src/legacy.sh | 226 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 202 insertions(+), 24 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index db4166349..937682f38 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1131,7 +1131,6 @@ createWin9xSystemImage() { local setup="$4" local options="$5" local mouse="$6" - local logon="$7" local temp="$TMP/win9x-image" local config="$temp/mtools.conf" @@ -1551,12 +1550,6 @@ createWin9xSystemImage() { return 1 fi - if ! MTOOLSRC="$config" mcopy -o "$logon" "w:/$setup/LOGIN9X.CPL"; then - rm -f -- "$tmp" - error "Failed to add the autologin helper to the $desc system image!" - return 1 - fi - { printf '%s\n' \ '@ECHO OFF' \ @@ -1573,15 +1566,8 @@ createWin9xSystemImage() { "C:\\${setup}\\SETUP.EXE $options" \ 'GOTO END' \ ':WINDOWS' \ - 'IF EXIST C:\WINDOWS\SYSTEM\VBMOUSE.DRV GOTO LOGON' \ - 'COPY C:\VBMOUSE.DRV C:\WINDOWS\SYSTEM\VBMOUSE.DRV >NUL' - - printf '%s\n' \ - ':LOGON' \ - 'IF EXIST C:\WINDOWS\SYSTEM\LOGIN9X.CPL GOTO START' \ - "COPY C:\\${setup}\\LOGIN9X.CPL C:\\WINDOWS\\SYSTEM\\LOGIN9X.CPL >NUL" - - printf '%s\n' \ + 'IF EXIST C:\WINDOWS\SYSTEM\VBMOUSE.DRV GOTO START' \ + 'COPY C:\VBMOUSE.DRV C:\WINDOWS\SYSTEM\VBMOUSE.DRV >NUL' \ ':START' \ 'C:\VBMOUSE.EXE >NUL' \ 'C:\WINDOWS\WIN.COM' \ @@ -1772,6 +1758,182 @@ integrateWin9xSetupMouse() { return 0 } +integrateWin9xLoginApplet() { + + local dir="$1" + local desc="$2" + local source="$3" + + local layout source_entry disk subdir size mapping + local temp plain patched converted + + layout=$(find "$dir" -maxdepth 1 -type f -iname 'LAYOUT.INF' -print -quit) || return 1 + + if [ -z "$layout" ]; then + error "Failed to locate LAYOUT.INF in $desc setup files!" + return 1 + fi + + if [ ! -f "$source" ]; then + error "Failed to locate LOGIN9X.CPL!" + return 1 + fi + + # LOGIN9X.CPL lives beside SETUP.EXE in the flat Win9x setup source. Reuse + # SETUP.EXE's existing source-disk location instead of inventing media data. + source_entry=$(awk ' + BEGIN { + files = 0 + } + { + sub(/\r$/, "") + line = $0 + section = tolower(line) + gsub(/[[:space:]]/, "", section) + + if (section ~ /^\[[^]]+\]$/) { + files = (section == "[sourcedisksfiles]") + next + } + + if (!files) { + next + } + + pos = index(line, "=") + if (!pos) { + next + } + + key = substr(line, 1, pos - 1) + gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) + + if (tolower(key) == "setup.exe") { + value = substr(line, pos + 1) + sub(/[[:space:]]*;.*/, "", value) + print value + exit + } + } + ' "$layout") || return 1 + + if [ -z "$source_entry" ]; then + error "Failed to locate SETUP.EXE in the $desc LAYOUT.INF source map!" + return 1 + fi + + disk=$(cut -d',' -f1 <<< "$source_entry" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + subdir=$(cut -s -d',' -f2 <<< "$source_entry" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + + if ! [[ "$disk" =~ ^[0-9]+$ ]]; then + error "Invalid SETUP.EXE source-disk mapping in $desc LAYOUT.INF!" + return 1 + fi + + size=$(stat -c %s -- "$source") || return 1 + mapping="LOGIN9X.CPL=$disk,$subdir,$size" + + temp="$TMP/win9x-login9x" + plain="$temp/LAYOUT.INF" + patched="$temp/LAYOUT.NEW" + converted="$temp/LAYOUT.DOS" + + rm -rf -- "$temp" || return 1 + mkdir -p "$temp" || return 1 + + if ! dos2unix -n "$layout" "$plain" >/dev/null 2>&1; then + rm -rf -- "$temp" || : + error "Failed to read $desc LAYOUT.INF!" + return 1 + fi + + if ! awk -v mapping="$mapping" ' + BEGIN { + files = 0 + found = 0 + } + { + line = $0 + section = tolower(line) + gsub(/[[:space:]]/, "", section) + + if (section ~ /^\[[^]]+\]$/) { + files = (section == "[sourcedisksfiles]") + print line + + if (files && !found) { + print mapping + found = 1 + } + + next + } + + if (files && tolower(line) ~ /^[[:space:]]*login9x\.cpl[[:space:]]*=/) { + next + } + + print line + } + END { + if (!found) { + exit 1 + } + } + ' "$plain" > "$patched"; then + rm -rf -- "$temp" || : + error "Failed to add LOGIN9X.CPL to $desc LAYOUT.INF!" + return 1 + fi + + if ! unix2dos -n "$patched" "$converted" >/dev/null 2>&1 || + ! chmod --reference="$layout" "$converted" || + ! mv -f -- "$converted" "$layout" || + ! cp -f -- "$source" "$dir/LOGIN9X.CPL" || + ! cmp -s -- "$source" "$dir/LOGIN9X.CPL"; then + rm -rf -- "$temp" || : + error "Failed to integrate LOGIN9X.CPL into $desc setup files!" + return 1 + fi + + if ! awk -v expected="$mapping" ' + BEGIN { + files = 0 + count = 0 + exact = 0 + } + { + sub(/\r$/, "") + line = $0 + section = tolower(line) + gsub(/[[:space:]]/, "", section) + + if (section ~ /^\[[^]]+\]$/) { + files = (section == "[sourcedisksfiles]") + next + } + + if (files && tolower(line) ~ /^[[:space:]]*login9x\.cpl[[:space:]]*=/) { + count++ + if (line == expected) { + exact = 1 + } + } + } + END { + exit (count == 1 && exact) ? 0 : 1 + } + ' "$layout"; then + rm -rf -- "$temp" || : + error "Failed to verify LOGIN9X.CPL in $desc LAYOUT.INF!" + return 1 + fi + + rm -rf -- "$temp" || : + + return 0 +} + writeWin9xUserRegistry() { printf '%s\n' \ @@ -1845,11 +2007,7 @@ writeWin98Registry() { '' \ '[Win98.ConnectAll]' \ 'connec~1.lnk' \ - '"Connect to the Internet.lnk"' \ - '' \ - '[DestinationDirs]' \ - 'Win98.Connect=10,Desktop' \ - 'Win98.ConnectAll=10,alluse~1\desktop' + '"Connect to the Internet.lnk"' } writeWin9xAnswerFile() { @@ -1905,6 +2063,7 @@ writeWin9xAnswerFile() { 'LayoutFile=layout.inf' \ '' \ '[Install]' \ + 'CopyFiles=Win9x.Login' \ 'UpdateInis=Win9x.SystemIni,Win9x.SystemCb' \ "AddReg=$addReg" \ '' \ @@ -1941,7 +2100,7 @@ writeWin9xAnswerFile() { 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DefaultPassword",,""' \ 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","AutoAdminLogon",,"1"' \ 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DontDisplayLastUserName",,"0"' \ - "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices\",\"Tweak UI\",,\"RUNDLL32.EXE %11%\\LOGIN9X.CPL,TweakLogon\"" + "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices\",\"Login9x\",,\"RUNDLL32.EXE %11%\\LOGIN9X.CPL,TweakLogon\"" fi printf '%s\n' '' @@ -1984,6 +2143,20 @@ writeWin9xAnswerFile() { writeWin98Registry fi + printf '%s\n' \ + '' \ + '[Win9x.Login]' \ + 'LOGIN9X.CPL' \ + '' \ + '[DestinationDirs]' \ + 'Win9x.Login=11' + + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + 'Win98.Connect=10,Desktop' \ + 'Win98.ConnectAll=10,alluse~1\desktop' + fi + if enabled "$shortcut"; then printf '%s\n' \ '' \ @@ -2209,7 +2382,7 @@ prepareWin9xInstall() { if [ ! -f "$logon" ]; then rm -rf "$drivers" || : - error "Failed to locate the autologin helper!" + error "Failed to locate LOGIN9X control panel applet!" return 1 fi @@ -2218,6 +2391,11 @@ prepareWin9xInstall() { return 1 fi + if ! integrateWin9xLoginApplet "$target" "$desc" "$logon"; then + rm -rf "$drivers" || : + return 1 + fi + # Do not copy optical-media AutoRun metadata onto the system disk; Explorer # otherwise treats C: like the installation CD and runs its default action. find "$dir" -maxdepth 1 -type f -iname 'AUTORUN.INF' -delete || return 1 @@ -2226,7 +2404,7 @@ prepareWin9xInstall() { # on a particular El Torito floppy-image filename: some perfectly valid Win9x # discs expose a differently named boot image, while the required DOS system # files are already present in the installation cabinets. - if ! createWin9xSystemImage "$dir" "$TMP/windows.img" "$desc" "$folder" "$options" "$mouse" "$logon"; then + if ! createWin9xSystemImage "$dir" "$TMP/windows.img" "$desc" "$folder" "$options" "$mouse"; then rm -rf "$drivers" || : return 1 fi From 1dc7127f35d710ace0f5d2af07a2c6d240533e6a Mon Sep 17 00:00:00 2001 From: Kroese Date: Fri, 14 Aug 2026 21:51:52 +0200 Subject: [PATCH 13/29] Update legacy.sh --- src/legacy.sh | 219 +++++++++++++------------------------------------- 1 file changed, 57 insertions(+), 162 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 937682f38..d5e112cf2 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1128,7 +1128,7 @@ createWin9xSystemImage() { local dir="$1" local image="$2" local desc="$3" - local setup="$4" + local source="$4" local options="$5" local mouse="$6" @@ -1142,6 +1142,7 @@ createWin9xSystemImage() { local sectors=$((size / 512 - start)) local offset=$((start * 512)) local entry find_pid setup_dir + local setup="SETUP" local fs attributes local entries=() @@ -1521,12 +1522,13 @@ createWin9xSystemImage() { # Setup only needs its release-specific source directory. Keeping the rest # of the optical-media root off C: avoids exposing unrelated CD contents in - # the installed system while retaining the common Win95/98/Me image path. - setup_dir=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -iname "$setup" -print -quit) || return 1 + # the installed system. Rename the retained source to C:\SETUP so it cannot + # be confused with the installed C:\WINDOWS directory. + setup_dir=$(find "$dir" -mindepth 1 -maxdepth 1 -type d -iname "$source" -print -quit) || return 1 if [ -z "$setup_dir" ]; then rm -f -- "$tmp" - error "Failed to locate the $setup Setup folder in $desc ISO image!" + error "Failed to locate the $source Setup folder in $desc ISO image!" return 1 fi @@ -1543,6 +1545,13 @@ createWin9xSystemImage() { done + if ! MTOOLSRC="$config" mren "w:/$source" "w:/$setup" || + ! MTOOLSRC="$config" mattrib +h +s "w:/$setup"; then + rm -f -- "$tmp" + error "Failed to rename or hide the $desc setup folder in the system image!" + return 1 + fi + if ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.EXE" w:/VBMOUSE.EXE || ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.DRV" w:/VBMOUSE.DRV; then rm -f -- "$tmp" @@ -1758,179 +1767,43 @@ integrateWin9xSetupMouse() { return 0 } -integrateWin9xLoginApplet() { +stageWin9xLoginApplet() { local dir="$1" local desc="$2" local source="$3" - local layout source_entry disk subdir size mapping - local temp plain patched converted - - layout=$(find "$dir" -maxdepth 1 -type f -iname 'LAYOUT.INF' -print -quit) || return 1 - - if [ -z "$layout" ]; then - error "Failed to locate LAYOUT.INF in $desc setup files!" - return 1 - fi - if [ ! -f "$source" ]; then error "Failed to locate LOGIN9X.CPL!" return 1 fi - # LOGIN9X.CPL lives beside SETUP.EXE in the flat Win9x setup source. Reuse - # SETUP.EXE's existing source-disk location instead of inventing media data. - source_entry=$(awk ' - BEGIN { - files = 0 - } - { - sub(/\r$/, "") - line = $0 - section = tolower(line) - gsub(/[[:space:]]/, "", section) - - if (section ~ /^\[[^]]+\]$/) { - files = (section == "[sourcedisksfiles]") - next - } - - if (!files) { - next - } - - pos = index(line, "=") - if (!pos) { - next - } - - key = substr(line, 1, pos - 1) - gsub(/^[[:space:]]+|[[:space:]]+$/, "", key) - - if (tolower(key) == "setup.exe") { - value = substr(line, pos + 1) - sub(/[[:space:]]*;.*/, "", value) - print value - exit - } - } - ' "$layout") || return 1 - - if [ -z "$source_entry" ]; then - error "Failed to locate SETUP.EXE in the $desc LAYOUT.INF source map!" - return 1 - fi - - disk=$(cut -d',' -f1 <<< "$source_entry" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - subdir=$(cut -s -d',' -f2 <<< "$source_entry" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - - if ! [[ "$disk" =~ ^[0-9]+$ ]]; then - error "Invalid SETUP.EXE source-disk mapping in $desc LAYOUT.INF!" + if ! cp -f -- "$source" "$dir/LOGIN9X.CPL" || + ! cmp -s -- "$source" "$dir/LOGIN9X.CPL"; then + error "Failed to stage LOGIN9X.CPL in $desc setup files!" return 1 fi - size=$(stat -c %s -- "$source") || return 1 - mapping="LOGIN9X.CPL=$disk,$subdir,$size" - - temp="$TMP/win9x-login9x" - plain="$temp/LAYOUT.INF" - patched="$temp/LAYOUT.NEW" - converted="$temp/LAYOUT.DOS" - - rm -rf -- "$temp" || return 1 - mkdir -p "$temp" || return 1 + return 0 +} - if ! dos2unix -n "$layout" "$plain" >/dev/null 2>&1; then - rm -rf -- "$temp" || : - error "Failed to read $desc LAYOUT.INF!" - return 1 - fi +stageWin9xMouseExecutable() { - if ! awk -v mapping="$mapping" ' - BEGIN { - files = 0 - found = 0 - } - { - line = $0 - section = tolower(line) - gsub(/[[:space:]]/, "", section) - - if (section ~ /^\[[^]]+\]$/) { - files = (section == "[sourcedisksfiles]") - print line - - if (files && !found) { - print mapping - found = 1 - } - - next - } - - if (files && tolower(line) ~ /^[[:space:]]*login9x\.cpl[[:space:]]*=/) { - next - } - - print line - } - END { - if (!found) { - exit 1 - } - } - ' "$plain" > "$patched"; then - rm -rf -- "$temp" || : - error "Failed to add LOGIN9X.CPL to $desc LAYOUT.INF!" - return 1 - fi + local dir="$1" + local desc="$2" + local source="$3" - if ! unix2dos -n "$patched" "$converted" >/dev/null 2>&1 || - ! chmod --reference="$layout" "$converted" || - ! mv -f -- "$converted" "$layout" || - ! cp -f -- "$source" "$dir/LOGIN9X.CPL" || - ! cmp -s -- "$source" "$dir/LOGIN9X.CPL"; then - rm -rf -- "$temp" || : - error "Failed to integrate LOGIN9X.CPL into $desc setup files!" + if [ ! -f "$source" ]; then + error "Failed to locate VBMOUSE.EXE!" return 1 fi - if ! awk -v expected="$mapping" ' - BEGIN { - files = 0 - count = 0 - exact = 0 - } - { - sub(/\r$/, "") - line = $0 - section = tolower(line) - gsub(/[[:space:]]/, "", section) - - if (section ~ /^\[[^]]+\]$/) { - files = (section == "[sourcedisksfiles]") - next - } - - if (files && tolower(line) ~ /^[[:space:]]*login9x\.cpl[[:space:]]*=/) { - count++ - if (line == expected) { - exact = 1 - } - } - } - END { - exit (count == 1 && exact) ? 0 : 1 - } - ' "$layout"; then - rm -rf -- "$temp" || : - error "Failed to verify LOGIN9X.CPL in $desc LAYOUT.INF!" + if ! cp -f -- "$source" "$dir/VBMOUSE.EXE" || + ! cmp -s -- "$source" "$dir/VBMOUSE.EXE"; then + error "Failed to stage VBMOUSE.EXE in $desc setup files!" return 1 fi - rm -rf -- "$temp" || : - return 0 } @@ -1942,7 +1815,7 @@ writeWin9xUserRegistry() { 'HKCU,"Control Panel\Desktop","ScreenSaveActive",,"0"' \ 'HKCU,"Control Panel\Desktop","DragFullWindows",,"1"' \ 'HKCU,"Control Panel\Desktop","MenuShowDelay",,"100"' \ - 'HKCU,"Control Panel\Desktop","FontSmoothing",,"2"' \ + 'HKCU,"Control Panel\Desktop","FontSmoothing",,"1"' \ 'HKCU,"Control Panel\Desktop","SmoothScroll",0x00010001,0' \ 'HKCU,"Control Panel\Desktop\WindowMetrics","MinAnimate",,"0"' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' \ @@ -1954,6 +1827,11 @@ writeWin9xMachineRegistry() { printf '%s\n' \ '[Win9x.Machine]' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\FS Templates\Server","NameCache",1,a9,0a,00,00' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\FS Templates\Server","PathCache",1,40,00,00,00' \ + 'HKLM,"System\CurrentControlSet\Control\FileSystem","NameCache",1,a9,0a,00,00' \ + 'HKLM,"System\CurrentControlSet\Control\FileSystem","PathCache",1,40,00,00,00' \ + 'HKLM,"System\CurrentControlSet\Control\FileSystem","ReadAheadThreshold",1,00,00,01,00' \ 'HKLM,"System\CurrentControlSet\Control\FileSystem\CDFS","CacheSize",1,ac,09,00,00' \ 'HKLM,"System\CurrentControlSet\Control\FileSystem\CDFS","Prefetch",1,e4,00,00,00' } @@ -2014,7 +1892,7 @@ writeWin9xAnswerFile() { local target="$1" local id="$2" - local folder="$3" + local setup="$3" local monitor="$4" local batchHost="$5" local batchUsername="$6" @@ -2062,8 +1940,15 @@ writeWin9xAnswerFile() { 'AdvancedINF=2.5' \ 'LayoutFile=layout.inf' \ '' \ + '[SourceDisksNames]' \ + '22="Windows Setup",,0' \ + '' \ + '[SourceDisksFiles]' \ + 'LOGIN9X.CPL=22' \ + 'VBMOUSE.EXE=22' \ + '' \ '[Install]' \ - 'CopyFiles=Win9x.Login' \ + 'CopyFiles=Win9x.Login,Win9x.Mouse' \ 'UpdateInis=Win9x.SystemIni,Win9x.SystemCb' \ "AddReg=$addReg" \ '' \ @@ -2110,7 +1995,7 @@ writeWin9xAnswerFile() { printf '%s\n' \ '' \ '[OEMDrivers]' \ - "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\",\"OtherDevicePath\",,\"C:\\WINDOWS\\INF\\OTHER;C:\\$folder\"" + "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\",\"OtherDevicePath\",,\"C:\\WINDOWS\\INF\\OTHER;C:\\$setup\"" fi printf '%s\n' \ @@ -2148,8 +2033,12 @@ writeWin9xAnswerFile() { '[Win9x.Login]' \ 'LOGIN9X.CPL' \ '' \ + '[Win9x.Mouse]' \ + 'VBMOUSE.EXE' \ + '' \ '[DestinationDirs]' \ - 'Win9x.Login=11' + 'Win9x.Login=11' \ + 'Win9x.Mouse=10' if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' \ @@ -2275,6 +2164,7 @@ prepareWin9xInstall() { local desc="$4" local folder monitor="Plug and Play Monitor" options="/IS /IQ /IT" target + local setup="SETUP" local shortcut="Y" if disabled "$SHORTCUT" || disabled "${SAMBA:-Y}"; then @@ -2353,7 +2243,7 @@ prepareWin9xInstall() { fi writeWin9xAnswerFile \ - "$target" "$id" "$folder" "$monitor" \ + "$target" "$id" "$setup" "$monitor" \ "$batchHost" "$batchUsername" "$batchOrganization" "$batchWorkgroup" \ "$batchKey" "$shortcut" "$install" || return 1 @@ -2391,7 +2281,12 @@ prepareWin9xInstall() { return 1 fi - if ! integrateWin9xLoginApplet "$target" "$desc" "$logon"; then + if ! stageWin9xLoginApplet "$target" "$desc" "$logon"; then + rm -rf "$drivers" || : + return 1 + fi + + if ! stageWin9xMouseExecutable "$target" "$desc" "$mouse/VBMOUSE.EXE"; then rm -rf "$drivers" || : return 1 fi From 45d77abab43853d6a58eac79bdd24569a42331e2 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 00:26:46 +0200 Subject: [PATCH 14/29] Update legacy.sh --- src/legacy.sh | 129 ++++++++++++++++++++++++++++++-------------------- 1 file changed, 78 insertions(+), 51 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index d5e112cf2..abc2f63d3 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1767,20 +1767,43 @@ integrateWin9xSetupMouse() { return 0 } -stageWin9xLoginApplet() { +stageWin9xPasswordList() { local dir="$1" local desc="$2" - local source="$3" - - if [ ! -f "$source" ]; then - error "Failed to locate LOGIN9X.CPL!" - return 1 - fi - - if ! cp -f -- "$source" "$dir/LOGIN9X.CPL" || - ! cmp -s -- "$source" "$dir/LOGIN9X.CPL"; then - error "Failed to stage LOGIN9X.CPL in $desc setup files!" + local target="$dir/DOCKER.PWL" + + if ! xxd -r -p > "$target" <<'EOF' +e382859601000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000ffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffff520200000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000001000000000075ad273deae8e3a155a623d2cd02 +c4c9833636fb9ed100c68322dd0c92fb04d4c6119aaaada60ebe2972e0286df6 +3bcab4ebf885df9a413ea245fa9f5b5e81f6e70d2fd5d0d06ca06e18c328d88f +9a26b992331d22e76ab135b9851b3d9b +EOF + then + error "Failed to create DOCKER.PWL in $desc setup files!" + return 1 + fi + + if [ "$(wc -c < "$target")" -ne 688 ]; then + error "Failed to verify DOCKER.PWL in $desc setup files!" return 1 fi @@ -1904,9 +1927,14 @@ writeWin9xAnswerFile() { local desktop="%10%\Desktop" local addReg="OPKInstall,Win9x.Machine" + local copyFiles="Win9x.Mouse" local firstLogonAddReg="Win9x.User" local firstLogonDelReg="" + if ! disabled "$AUTOLOGIN"; then + copyFiles+=",Win9x.Password" + fi + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then addReg+=",OEMDrivers" fi @@ -1917,10 +1945,6 @@ writeWin9xAnswerFile() { addReg+=",Win98.Power,Win98.ActiveSetup" fi - if ! disabled "$AUTOLOGIN"; then - firstLogonDelReg="Win9x.Logon" - fi - if [[ "${id,,}" == "win98"* ]]; then firstLogonAddReg+=",Win98.User,Win98.PowerUser" [ -n "$firstLogonDelReg" ] && firstLogonDelReg+="," @@ -1944,11 +1968,16 @@ writeWin9xAnswerFile() { '22="Windows Setup",,0' \ '' \ '[SourceDisksFiles]' \ - 'LOGIN9X.CPL=22' \ - 'VBMOUSE.EXE=22' \ + 'VBMOUSE.EXE=22' + + if ! disabled "$AUTOLOGIN"; then + printf '%s\n' 'DOCKER.PWL=22' + fi + + printf '%s\n' \ '' \ '[Install]' \ - 'CopyFiles=Win9x.Login,Win9x.Mouse' \ + "CopyFiles=$copyFiles" \ 'UpdateInis=Win9x.SystemIni,Win9x.SystemCb' \ "AddReg=$addReg" \ '' \ @@ -1981,11 +2010,7 @@ writeWin9xAnswerFile() { if ! disabled "$AUTOLOGIN"; then printf '%s\n' \ 'HKLM,"Network\Logon","UserProfiles",0x00010001,0' \ - "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Winlogon\",\"DefaultUserName\",,\"$batchUsername\"" \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DefaultPassword",,""' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","AutoAdminLogon",,"1"' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DontDisplayLastUserName",,"0"' \ - "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunServices\",\"Login9x\",,\"RUNDLL32.EXE %11%\\LOGIN9X.CPL,TweakLogon\"" + 'HKLM,"Network\Logon","username",,"Docker"' fi printf '%s\n' '' @@ -2014,32 +2039,32 @@ writeWin9xAnswerFile() { printf '%s\n' '' writeWin9xUserRegistry - if ! disabled "$AUTOLOGIN"; then - printf '%s\n' \ - '' \ - '[Win9x.Logon]' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices","Login9x",,,' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","AutoAdminLogon",,,' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Winlogon","DefaultPassword",,,' - fi - if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' '' writeWin98Registry fi printf '%s\n' \ - '' \ - '[Win9x.Login]' \ - 'LOGIN9X.CPL' \ '' \ '[Win9x.Mouse]' \ - 'VBMOUSE.EXE' \ + 'VBMOUSE.EXE' + + if ! disabled "$AUTOLOGIN"; then + printf '%s\n' \ + '' \ + '[Win9x.Password]' \ + 'DOCKER.PWL' + fi + + printf '%s\n' \ '' \ '[DestinationDirs]' \ - 'Win9x.Login=11' \ 'Win9x.Mouse=10' + if ! disabled "$AUTOLOGIN"; then + printf '%s\n' 'Win9x.Password=10' + fi + if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' \ 'Win98.Connect=10,Desktop' \ @@ -2060,7 +2085,13 @@ writeWin9xAnswerFile() { '[Win9x.SystemIni]' \ '%10%\system.ini,boot,"mouse.drv=mouse.drv","mouse.drv=vbmouse.drv"' \ '%10%\system.ini,386Enh,,"MaxPhysPage=100000"' \ - '%10%\system.ini,vcache,,"MaxFileCache=65536"' \ + '%10%\system.ini,vcache,,"MaxFileCache=65536"' + + if ! disabled "$AUTOLOGIN"; then + printf '%s\n' '%10%\system.ini,"Password Lists",,"DOCKER=C:\WINDOWS\DOCKER.PWL"' + fi + + printf '%s\n' \ '' \ '[Win9x.SystemCb]' \ '%10%\system.cb,386Enh,,"MaxPhysPage=100000"' \ @@ -2183,7 +2214,7 @@ prepareWin9xInstall() { "win98"* ) folder="WIN98" monitor="Plug and Play Monitor (VESA DDC)" - options="/P I /IE /NF $options" ;; + options="/P J /IE /NF $options" ;; "win9x"* ) folder="WIN9X" options="/IE /NF $options" ;; @@ -2213,6 +2244,10 @@ prepareWin9xInstall() { local username="${USERNAME:-Docker}" local workgroup="${WORKGROUP:-WORKGROUP}" + if ! disabled "$AUTOLOGIN"; then + username="Docker" + fi + validateComputerName "$host" || return 1 # Reuse the normal OEM-folder preparation so /OEM and COMMAND behave like the @@ -2242,6 +2277,10 @@ prepareWin9xInstall() { batchKey=$(escapeSIFValue "$KEY") || return 1 fi + if ! disabled "$AUTOLOGIN"; then + stageWin9xPasswordList "$target" "$desc" || return 1 + fi + writeWin9xAnswerFile \ "$target" "$id" "$setup" "$monitor" \ "$batchHost" "$batchUsername" "$batchOrganization" "$batchWorkgroup" \ @@ -2254,7 +2293,6 @@ prepareWin9xInstall() { local patcher="$win9x/patcher9x/patcher9x" local mouse="$win9x/mouse" local display="$win9x/boxv9x" - local logon="$win9x/login9x/login9x.cpl" extractDrivers "$drivers" || return 1 @@ -2270,22 +2308,11 @@ prepareWin9xInstall() { return 1 fi - if [ ! -f "$logon" ]; then - rm -rf "$drivers" || : - error "Failed to locate LOGIN9X control panel applet!" - return 1 - fi - if ! patchWin9xSetupFiles "$id" "$target" "$desc" "$patcher" "$mouse" "$display"; then rm -rf "$drivers" || : return 1 fi - if ! stageWin9xLoginApplet "$target" "$desc" "$logon"; then - rm -rf "$drivers" || : - return 1 - fi - if ! stageWin9xMouseExecutable "$target" "$desc" "$mouse/VBMOUSE.EXE"; then rm -rf "$drivers" || : return 1 From f7f9ffd5dee22583742f8d1326183e6a4f360732 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 01:19:28 +0200 Subject: [PATCH 15/29] Update define.sh --- src/define.sh | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/define.sh b/src/define.sh index 382edff20..d21f29e78 100644 --- a/src/define.sh +++ b/src/define.sh @@ -1105,19 +1105,13 @@ supportsACPI() { case "${id,,}" in - "win9"* | "winnt4" ) - - # Windows 9x does not respond to ACPI during setup, - # so disable it during the first run of the container. - hasBootMarker || return 1 ;; - "reactos" ) # If the ISO is a Live-CD it will ignore ACPI signals. hasSystemImage && return 1 ;; esac - + return 0 } From 698c8143121272e6ae14d35cfdf500e9cef2971c Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 01:28:41 +0200 Subject: [PATCH 16/29] Update legacy.sh --- src/legacy.sh | 176 ++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 141 insertions(+), 35 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index abc2f63d3..079de1cd7 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1552,8 +1552,7 @@ createWin9xSystemImage() { return 1 fi - if ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.EXE" w:/VBMOUSE.EXE || - ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.DRV" w:/VBMOUSE.DRV; then + if ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.EXE" w:/VBMOUSE.EXE; then rm -f -- "$tmp" error "Failed to add mouse integration to the $desc system image!" return 1 @@ -1575,8 +1574,8 @@ createWin9xSystemImage() { "C:\\${setup}\\SETUP.EXE $options" \ 'GOTO END' \ ':WINDOWS' \ - 'IF EXIST C:\WINDOWS\SYSTEM\VBMOUSE.DRV GOTO START' \ - 'COPY C:\VBMOUSE.DRV C:\WINDOWS\SYSTEM\VBMOUSE.DRV >NUL' \ + 'IF NOT EXIST C:\WINDOWS\POST9X.FLG GOTO START' \ + 'REN C:\WINDOWS\POST9X.FLG POST9X.RDY' \ ':START' \ 'C:\VBMOUSE.EXE >NUL' \ 'C:\WINDOWS\WIN.COM' \ @@ -1810,22 +1809,76 @@ EOF return 0 } -stageWin9xMouseExecutable() { +stageWin9xPostSetup() { local dir="$1" local desc="$2" - local source="$3" + local id="$3" + local shortcut="$4" + local install="$5" + local target="$dir/POST9X.BAT" - if [ ! -f "$source" ]; then - error "Failed to locate VBMOUSE.EXE!" - return 1 - fi + { + printf '%s\n' \ + '@ECHO OFF' \ + 'IF NOT EXIST C:\WINDOWS\POST9X.RDY GOTO END' \ + 'C:\WINDOWS\RUNDLL.EXE SETUPX.DLL,InstallHinfSection Win9x.PostDesktop 4 C:\WINDOWS\MSBATCH.INF' \ + 'DEL C:\WINDOWS\POST9X.RDY >NUL' + + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + 'C:\WINDOWS\RUNDLL32.EXE ADVPACK.DLL,DelNodeRunDLL32 "C:\WINDOWS\Desktop\Online Services"' \ + 'C:\WINDOWS\RUNDLL32.EXE ADVPACK.DLL,DelNodeRunDLL32 "C:\WINDOWS\All Users\Desktop\Online Services"' + fi + + if enabled "$shortcut"; then + printf '%s\n' \ + 'IF EXIST Z:\NUL GOTO SHARE_READY' \ + 'C:\WINDOWS\NET.EXE USE Z: \\host.lan\Data >NUL' \ + ':SHARE_READY' + fi + + if [ -n "$install" ]; then + + if enabled "${LOG:-}"; then + printf '%s\n' 'CALL C:\OEM\install.bat > C:\OEM\install.log' + else + printf '%s\n' 'CALL C:\OEM\install.bat' + fi + + fi + + printf '%s\n' ':END' - if ! cp -f -- "$source" "$dir/VBMOUSE.EXE" || - ! cmp -s -- "$source" "$dir/VBMOUSE.EXE"; then - error "Failed to stage VBMOUSE.EXE in $desc setup files!" + } | unix2dos > "$target" || { + error "Failed to create post-desktop setup script for $desc!" return 1 - fi + } + + return 0 +} + +stageWin9xMouseFiles() { + + local dir="$1" + local desc="$2" + local source="$3" + local file + + for file in VBMOUSE.EXE VBMOUSE.DRV; do + + if [ ! -f "$source/$file" ]; then + error "Failed to locate $file!" + return 1 + fi + + if ! cp -f -- "$source/$file" "$dir/$file" || + ! cmp -s -- "$source/$file" "$dir/$file"; then + error "Failed to stage $file in $desc setup files!" + return 1 + fi + + done return 0 } @@ -1850,6 +1903,8 @@ writeWin9xMachineRegistry() { printf '%s\n' \ '[Win9x.Machine]' \ + 'HKU,".DEFAULT\Control Panel\Desktop","FontSmoothing",,"1"' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\FS Templates",,,"Server"' \ 'HKLM,"Software\Microsoft\Windows\CurrentVersion\FS Templates\Server","NameCache",1,a9,0a,00,00' \ 'HKLM,"Software\Microsoft\Windows\CurrentVersion\FS Templates\Server","PathCache",1,40,00,00,00' \ 'HKLM,"System\CurrentControlSet\Control\FileSystem","NameCache",1,a9,0a,00,00' \ @@ -1930,11 +1985,18 @@ writeWin9xAnswerFile() { local copyFiles="Win9x.Mouse" local firstLogonAddReg="Win9x.User" local firstLogonDelReg="" + local firstLogonUpdateInis="" + local post="" if ! disabled "$AUTOLOGIN"; then copyFiles+=",Win9x.Password" fi + if [[ "${id,,}" == "win98"* ]] || enabled "$shortcut" || [ -n "$install" ]; then + post="Y" + copyFiles+=",Win9x.Post" + fi + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then addReg+=",OEMDrivers" fi @@ -1968,12 +2030,17 @@ writeWin9xAnswerFile() { '22="Windows Setup",,0' \ '' \ '[SourceDisksFiles]' \ - 'VBMOUSE.EXE=22' + 'VBMOUSE.EXE=22' \ + 'VBMOUSE.DRV=22' if ! disabled "$AUTOLOGIN"; then printf '%s\n' 'DOCKER.PWL=22' fi + if enabled "$post"; then + printf '%s\n' 'POST9X.BAT=22' + fi + printf '%s\n' \ '' \ '[Install]' \ @@ -1988,23 +2055,9 @@ writeWin9xAnswerFile() { "HKLM,\"SOFTWARE\Microsoft\Windows\CurrentVersion\",\"RegisteredOrganization\",,\"$batchOrganization\"" \ "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\"Win9xSetup\",,\"%25%\\rundll.exe setupx.dll,InstallHinfSection Win9x.FirstLogon 4 %10%\\msbatch.inf\"" - if enabled "$shortcut"; then - # Win9x remaps the share at every logon instead of depending on the XP - # NET USE /persistent switch. + if enabled "$post"; then printf '%s\n' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","SharedDrive",,"C:\COMMAND.COM /C %25%\NET.EXE USE Z: \\host.lan\Data >NUL"' - fi - - if [ -n "$install" ]; then - - if enabled "${LOG:-}"; then - printf '%s\n' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","Install",,"C:\COMMAND.COM /C C:\OEM\install.bat > C:\OEM\install.log"' - else - printf '%s\n' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce","Install",,"C:\COMMAND.COM /C C:\OEM\install.bat"' - fi - + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","PostSetup",,"C:\WINDOWS\COMMAND.COM /C C:\WINDOWS\POST9X.BAT"' fi if ! disabled "$AUTOLOGIN"; then @@ -2032,8 +2085,17 @@ writeWin9xAnswerFile() { printf '%s\n' "DelReg=$firstLogonDelReg" fi + if enabled "$post"; then + firstLogonUpdateInis="Win9x.PostMarker" + fi + if enabled "$shortcut"; then - printf '%s\n' 'UpdateInis=Win9x.Shortcut' + [ -n "$firstLogonUpdateInis" ] && firstLogonUpdateInis+="," + firstLogonUpdateInis+="Win9x.Shortcut" + fi + + if [ -n "$firstLogonUpdateInis" ]; then + printf '%s\n' "UpdateInis=$firstLogonUpdateInis" fi printf '%s\n' '' @@ -2044,10 +2106,36 @@ writeWin9xAnswerFile() { writeWin98Registry fi + if enabled "$post"; then + printf '%s\n' \ + '' \ + '[Win9x.PostDesktop]' \ + 'DelReg=Win9x.PostDesktopRun' + + if enabled "$shortcut"; then + printf '%s\n' 'AddReg=Win9x.Share' + fi + + printf '%s\n' \ + '' \ + '[Win9x.PostDesktopRun]' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","PostSetup",,,' + fi + + if enabled "$shortcut"; then + printf '%s\n' \ + '' \ + '[Win9x.Share]' \ + 'HKCU,"Network\Persistent\Z","RemotePath",,"\\host.lan\Data"' \ + "HKCU,\"Network\\Persistent\\Z\",\"UserName\",,\"$batchUsername\"" \ + 'HKCU,"Network\Persistent\Z","ProviderName",,"Microsoft Network"' + fi + printf '%s\n' \ '' \ '[Win9x.Mouse]' \ - 'VBMOUSE.EXE' + 'VBMOUSE.EXE' \ + 'VBMOUSE.DRV' if ! disabled "$AUTOLOGIN"; then printf '%s\n' \ @@ -2056,15 +2144,29 @@ writeWin9xAnswerFile() { 'DOCKER.PWL' fi + if enabled "$post"; then + printf '%s\n' \ + '' \ + '[Win9x.Post]' \ + 'POST9X.BAT' \ + '' \ + '[Win9x.PostMarker]' \ + '%10%\POST9X.FLG,PostSetup,,"Ready=1"' + fi + printf '%s\n' \ '' \ '[DestinationDirs]' \ - 'Win9x.Mouse=10' + 'Win9x.Mouse=11' if ! disabled "$AUTOLOGIN"; then printf '%s\n' 'Win9x.Password=10' fi + if enabled "$post"; then + printf '%s\n' 'Win9x.Post=10' + fi + if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' \ 'Win98.Connect=10,Desktop' \ @@ -2281,6 +2383,10 @@ prepareWin9xInstall() { stageWin9xPasswordList "$target" "$desc" || return 1 fi + if [[ "${id,,}" == "win98"* ]] || enabled "$shortcut" || [ -n "$install" ]; then + stageWin9xPostSetup "$target" "$desc" "$id" "$shortcut" "$install" || return 1 + fi + writeWin9xAnswerFile \ "$target" "$id" "$setup" "$monitor" \ "$batchHost" "$batchUsername" "$batchOrganization" "$batchWorkgroup" \ @@ -2313,7 +2419,7 @@ prepareWin9xInstall() { return 1 fi - if ! stageWin9xMouseExecutable "$target" "$desc" "$mouse/VBMOUSE.EXE"; then + if ! stageWin9xMouseFiles "$target" "$desc" "$mouse"; then rm -rf "$drivers" || : return 1 fi From b6cd42181cf97d5005f562947405f3b78294f0a7 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 01:46:40 +0200 Subject: [PATCH 17/29] Update legacy.sh --- src/legacy.sh | 47 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 079de1cd7..7d409bc00 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1813,7 +1813,6 @@ stageWin9xPostSetup() { local dir="$1" local desc="$2" - local id="$3" local shortcut="$4" local install="$5" local target="$dir/POST9X.BAT" @@ -1825,12 +1824,6 @@ stageWin9xPostSetup() { 'C:\WINDOWS\RUNDLL.EXE SETUPX.DLL,InstallHinfSection Win9x.PostDesktop 4 C:\WINDOWS\MSBATCH.INF' \ 'DEL C:\WINDOWS\POST9X.RDY >NUL' - if [[ "${id,,}" == "win98"* ]]; then - printf '%s\n' \ - 'C:\WINDOWS\RUNDLL32.EXE ADVPACK.DLL,DelNodeRunDLL32 "C:\WINDOWS\Desktop\Online Services"' \ - 'C:\WINDOWS\RUNDLL32.EXE ADVPACK.DLL,DelNodeRunDLL32 "C:\WINDOWS\All Users\Desktop\Online Services"' - fi - if enabled "$shortcut"; then printf '%s\n' \ 'IF EXIST Z:\NUL GOTO SHARE_READY' \ @@ -1963,7 +1956,20 @@ writeWin98Registry() { '' \ '[Win98.ConnectAll]' \ 'connec~1.lnk' \ - '"Connect to the Internet.lnk"' + '"Connect to the Internet.lnk"' \ + '' \ + '[Win98.OnlineServices]' \ + 'americ~1.lnk' \ + 'at&two~1.lnk' \ + 'compus~1.lnk' \ + 'prodig~1.lnk' \ + 'themic~1.lnk' \ + 'aboutt~1.lnk' \ + 'abouto~1.txt' \ + 'services.txt' \ + '' \ + '[Win98.OnlineServicesFolder]' \ + '%10%\wininit.ini,DIRNUL,,"C:\WINDOWS\Desktop\Online~1=1"' } writeWin9xAnswerFile() { @@ -1985,6 +1991,7 @@ writeWin9xAnswerFile() { local copyFiles="Win9x.Mouse" local firstLogonAddReg="Win9x.User" local firstLogonDelReg="" + local firstLogonDelFiles="" local firstLogonUpdateInis="" local post="" @@ -2011,6 +2018,8 @@ writeWin9xAnswerFile() { firstLogonAddReg+=",Win98.User,Win98.PowerUser" [ -n "$firstLogonDelReg" ] && firstLogonDelReg+="," firstLogonDelReg+="Win98.Welcome" + firstLogonDelFiles="Win98.OnlineServices" + firstLogonUpdateInis="Win98.OnlineServicesFolder" fi # Cap the memory Win9x itself can address at 4 GiB without changing QEMU's @@ -2085,8 +2094,13 @@ writeWin9xAnswerFile() { printf '%s\n' "DelReg=$firstLogonDelReg" fi + if [ -n "$firstLogonDelFiles" ]; then + printf '%s\n' "DelFiles=$firstLogonDelFiles" + fi + if enabled "$post"; then - firstLogonUpdateInis="Win9x.PostMarker" + [ -n "$firstLogonUpdateInis" ] && firstLogonUpdateInis+="," + firstLogonUpdateInis+="Win9x.PostMarker" fi if enabled "$shortcut"; then @@ -2170,7 +2184,8 @@ writeWin9xAnswerFile() { if [[ "${id,,}" == "win98"* ]]; then printf '%s\n' \ 'Win98.Connect=10,Desktop' \ - 'Win98.ConnectAll=10,alluse~1\desktop' + 'Win98.ConnectAll=10,alluse~1\desktop' \ + 'Win98.OnlineServices=10,Desktop\Online~1' fi if enabled "$shortcut"; then @@ -2218,7 +2233,17 @@ writeWin9xAnswerFile() { 'NoPrompt2Boot=1' \ 'TimeZone=Pacific' \ '' \ - '[OptionalComponents]' \ + '[OptionalComponents]' + + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + '"America Online"=0' \ + '"AT&T WorldNet Service"=0' \ + '"CompuServe"=0' \ + '"Prodigy Internet"=0' + fi + + printf '%s\n' \ '"The Microsoft Network"=0' \ '"Online Services"=0' \ '' \ From 7b27c0167d8c80a9d107617269bdeeb884d9aa9b Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 02:49:10 +0200 Subject: [PATCH 18/29] Update legacy.sh --- src/legacy.sh | 148 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 101 insertions(+), 47 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 7d409bc00..bad7568d2 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1130,7 +1130,6 @@ createWin9xSystemImage() { local desc="$3" local source="$4" local options="$5" - local mouse="$6" local temp="$TMP/win9x-image" local config="$temp/mtools.conf" @@ -1552,12 +1551,6 @@ createWin9xSystemImage() { return 1 fi - if ! MTOOLSRC="$config" mcopy -o "$mouse/VBMOUSE.EXE" w:/VBMOUSE.EXE; then - rm -f -- "$tmp" - error "Failed to add mouse integration to the $desc system image!" - return 1 - fi - { printf '%s\n' \ '@ECHO OFF' \ @@ -1565,7 +1558,7 @@ createWin9xSystemImage() { 'ECHO.' \ 'ECHO Starting Windows Setup, please wait...' \ 'ECHO.' \ - 'C:\VBMOUSE.EXE >NUL' \ + "C:\\${setup}\\VBMOUSE.EXE >NUL" \ "IF NOT EXIST C:\\${setup}\\XMSMMGR.EXE GOTO SETUP" \ "IF NOT EXIST C:\\${setup}\\SMARTDRV.EXE GOTO SETUP" \ "C:\\${setup}\\XMSMMGR.EXE >NUL" \ @@ -1577,7 +1570,7 @@ createWin9xSystemImage() { 'IF NOT EXIST C:\WINDOWS\POST9X.FLG GOTO START' \ 'REN C:\WINDOWS\POST9X.FLG POST9X.RDY' \ ':START' \ - 'C:\VBMOUSE.EXE >NUL' \ + 'C:\WINDOWS\SYSTEM\VBMOUSE.EXE >NUL' \ 'C:\WINDOWS\WIN.COM' \ ':END' \ '' @@ -1809,12 +1802,64 @@ EOF return 0 } +stageWin9xQuiet() { + + local dir="$1" + local desc="$2" + local target="$dir/QUIET.EXE" + + if ! xxd -r -p > "$target" <<'EOF' +4d5a000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000080000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +504500004c010100000000000000000000000000e00002010b01010000020000 +0000000000000000001000000010000000100000000040000010000000020000 +0400000000000000040000000000000000200000000200000000000002000000 +0000100000100000000010000010000000000000100000000000000000000000 +8010000028000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000b810000010000000 +0000000000000000000000000000000000000000000000002e74657874000000 +0001000000100000000200000002000000000000000000000000000060000060 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +ff15b810400089c6803e22751146803e007440803e22740346ebf346eb12803e +007430803e207408803e09740346ebee803e207405803e09750346ebf3803e00 +74116a0056ff15bc1040006a00ff15c01040006a00ff15c01040000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +a81000000000000000000000c8100000b8100000000000000000000000000000 +0000000000000000d6100000e8100000f210000000000000d6100000e8100000 +f2100000000000004b45524e454c33322e444c4c00000000476574436f6d6d61 +6e644c696e654100000057696e457865630000004578697450726f6365737300 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +EOF + then + error "Failed to create QUIET.EXE in $desc setup files!" + return 1 + fi + + if [ "$(wc -c < "$target")" -ne 1024 ]; then + error "Failed to verify QUIET.EXE in $desc setup files!" + return 1 + fi + + return 0 +} + stageWin9xPostSetup() { local dir="$1" local desc="$2" - local shortcut="$4" - local install="$5" + local install="$3" local target="$dir/POST9X.BAT" { @@ -1824,21 +1869,10 @@ stageWin9xPostSetup() { 'C:\WINDOWS\RUNDLL.EXE SETUPX.DLL,InstallHinfSection Win9x.PostDesktop 4 C:\WINDOWS\MSBATCH.INF' \ 'DEL C:\WINDOWS\POST9X.RDY >NUL' - if enabled "$shortcut"; then - printf '%s\n' \ - 'IF EXIST Z:\NUL GOTO SHARE_READY' \ - 'C:\WINDOWS\NET.EXE USE Z: \\host.lan\Data >NUL' \ - ':SHARE_READY' - fi - - if [ -n "$install" ]; then - - if enabled "${LOG:-}"; then - printf '%s\n' 'CALL C:\OEM\install.bat > C:\OEM\install.log' - else - printf '%s\n' 'CALL C:\OEM\install.bat' - fi - + if enabled "${LOG:-}"; then + printf '%s\n' 'CALL C:\OEM\install.bat > C:\OEM\install.log' + else + printf '%s\n' 'CALL C:\OEM\install.bat' fi printf '%s\n' ':END' @@ -1890,6 +1924,11 @@ writeWin9xUserRegistry() { 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","NoActiveDesktop",0x00010001,1' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer","link",1,00,00,00,00' + + if ! disabled "$AUTOLOGIN"; then + printf '%s\n' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","NoLogOff",0x00010001,1' + fi } writeWin9xMachineRegistry() { @@ -1994,16 +2033,22 @@ writeWin9xAnswerFile() { local firstLogonDelFiles="" local firstLogonUpdateInis="" local post="" + local quiet="" if ! disabled "$AUTOLOGIN"; then copyFiles+=",Win9x.Password" fi - if [[ "${id,,}" == "win98"* ]] || enabled "$shortcut" || [ -n "$install" ]; then + if [ -n "$install" ]; then post="Y" copyFiles+=",Win9x.Post" fi + if enabled "$shortcut"; then + quiet="Y" + copyFiles+=",Win9x.Quiet" + fi + if [[ "${id,,}" == "win95"* || "${id,,}" == "win98"* || "${id,,}" == "win9x"* ]]; then addReg+=",OEMDrivers" fi @@ -2046,6 +2091,10 @@ writeWin9xAnswerFile() { printf '%s\n' 'DOCKER.PWL=22' fi + if enabled "$quiet"; then + printf '%s\n' 'QUIET.EXE=22' + fi + if enabled "$post"; then printf '%s\n' 'POST9X.BAT=22' fi @@ -2064,6 +2113,11 @@ writeWin9xAnswerFile() { "HKLM,\"SOFTWARE\Microsoft\Windows\CurrentVersion\",\"RegisteredOrganization\",,\"$batchOrganization\"" \ "HKLM,\"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\RunOnce\",\"Win9xSetup\",,\"%25%\\rundll.exe setupx.dll,InstallHinfSection Win9x.FirstLogon 4 %10%\\msbatch.inf\"" + if enabled "$shortcut"; then + printf '%s\n' \ + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","SharedDrive",,"C:\WINDOWS\QUIET.EXE C:\WINDOWS\NET.EXE USE Z: \\host.lan\Data"' + fi + if enabled "$post"; then printf '%s\n' \ 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","PostSetup",,"C:\WINDOWS\COMMAND.COM /C C:\WINDOWS\POST9X.BAT"' @@ -2124,25 +2178,10 @@ writeWin9xAnswerFile() { printf '%s\n' \ '' \ '[Win9x.PostDesktop]' \ - 'DelReg=Win9x.PostDesktopRun' - - if enabled "$shortcut"; then - printf '%s\n' 'AddReg=Win9x.Share' - fi - - printf '%s\n' \ + 'DelReg=Win9x.PostDesktopRun' \ '' \ '[Win9x.PostDesktopRun]' \ - 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","PostSetup",,,' - fi - - if enabled "$shortcut"; then - printf '%s\n' \ - '' \ - '[Win9x.Share]' \ - 'HKCU,"Network\Persistent\Z","RemotePath",,"\\host.lan\Data"' \ - "HKCU,\"Network\\Persistent\\Z\",\"UserName\",,\"$batchUsername\"" \ - 'HKCU,"Network\Persistent\Z","ProviderName",,"Microsoft Network"' + 'HKLM,"SOFTWARE\Microsoft\Windows\CurrentVersion\Run","PostSetup"' fi printf '%s\n' \ @@ -2158,6 +2197,13 @@ writeWin9xAnswerFile() { 'DOCKER.PWL' fi + if enabled "$quiet"; then + printf '%s\n' \ + '' \ + '[Win9x.Quiet]' \ + 'QUIET.EXE' + fi + if enabled "$post"; then printf '%s\n' \ '' \ @@ -2173,6 +2219,10 @@ writeWin9xAnswerFile() { '[DestinationDirs]' \ 'Win9x.Mouse=11' + if enabled "$quiet"; then + printf '%s\n' 'Win9x.Quiet=10' + fi + if ! disabled "$AUTOLOGIN"; then printf '%s\n' 'Win9x.Password=10' fi @@ -2408,8 +2458,12 @@ prepareWin9xInstall() { stageWin9xPasswordList "$target" "$desc" || return 1 fi - if [[ "${id,,}" == "win98"* ]] || enabled "$shortcut" || [ -n "$install" ]; then - stageWin9xPostSetup "$target" "$desc" "$id" "$shortcut" "$install" || return 1 + if enabled "$shortcut"; then + stageWin9xQuiet "$target" "$desc" || return 1 + fi + + if [ -n "$install" ]; then + stageWin9xPostSetup "$target" "$desc" "$install" || return 1 fi writeWin9xAnswerFile \ @@ -2457,7 +2511,7 @@ prepareWin9xInstall() { # on a particular El Torito floppy-image filename: some perfectly valid Win9x # discs expose a differently named boot image, while the required DOS system # files are already present in the installation cabinets. - if ! createWin9xSystemImage "$dir" "$TMP/windows.img" "$desc" "$folder" "$options" "$mouse"; then + if ! createWin9xSystemImage "$dir" "$TMP/windows.img" "$desc" "$folder" "$options"; then rm -rf "$drivers" || : return 1 fi From 85985f172b79c3340037fd5ecdc21eeca4838857 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 03:08:50 +0200 Subject: [PATCH 19/29] Update legacy.sh --- src/legacy.sh | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index bad7568d2..7248c6a5e 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1861,6 +1861,7 @@ stageWin9xPostSetup() { local desc="$2" local install="$3" local target="$dir/POST9X.BAT" + local marker="$dir/POST9X.NEW" { printf '%s\n' \ @@ -1882,6 +1883,15 @@ stageWin9xPostSetup() { return 1 } + # Stage a real file for Setup to copy into the Windows directory. The + # intermediate Win9x.FirstLogon phase renames it to POST9X.FLG, and the next + # AUTOEXEC pass promotes that to POST9X.RDY. This avoids relying on UpdateInis + # to create an arbitrary marker file. + if ! printf 'Ready\r\n' > "$marker"; then + error "Failed to create post-desktop marker for $desc!" + return 1 + fi + return 0 } @@ -2096,7 +2106,9 @@ writeWin9xAnswerFile() { fi if enabled "$post"; then - printf '%s\n' 'POST9X.BAT=22' + printf '%s\n' \ + 'POST9X.BAT=22' \ + 'POST9X.NEW=22' fi printf '%s\n' \ @@ -2153,8 +2165,7 @@ writeWin9xAnswerFile() { fi if enabled "$post"; then - [ -n "$firstLogonUpdateInis" ] && firstLogonUpdateInis+="," - firstLogonUpdateInis+="Win9x.PostMarker" + printf '%s\n' 'RenFiles=Win9x.PostArm' fi if enabled "$shortcut"; then @@ -2209,9 +2220,10 @@ writeWin9xAnswerFile() { '' \ '[Win9x.Post]' \ 'POST9X.BAT' \ + 'POST9X.NEW' \ '' \ - '[Win9x.PostMarker]' \ - '%10%\POST9X.FLG,PostSetup,,"Ready=1"' + '[Win9x.PostArm]' \ + 'POST9X.FLG,POST9X.NEW' fi printf '%s\n' \ @@ -2228,7 +2240,9 @@ writeWin9xAnswerFile() { fi if enabled "$post"; then - printf '%s\n' 'Win9x.Post=10' + printf '%s\n' \ + 'Win9x.Post=10' \ + 'Win9x.PostArm=10' fi if [[ "${id,,}" == "win98"* ]]; then From 70072ea5e5c27eaf847c10f60fec8a859f99c87f Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 13:13:08 +0200 Subject: [PATCH 20/29] Update legacy.sh --- src/legacy.sh | 33 +++++++++++++++------------------ 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 7248c6a5e..6b3bb84f5 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1567,9 +1567,6 @@ createWin9xSystemImage() { "C:\\${setup}\\SETUP.EXE $options" \ 'GOTO END' \ ':WINDOWS' \ - 'IF NOT EXIST C:\WINDOWS\POST9X.FLG GOTO START' \ - 'REN C:\WINDOWS\POST9X.FLG POST9X.RDY' \ - ':START' \ 'C:\WINDOWS\SYSTEM\VBMOUSE.EXE >NUL' \ 'C:\WINDOWS\WIN.COM' \ ':END' \ @@ -1883,10 +1880,9 @@ stageWin9xPostSetup() { return 1 } - # Stage a real file for Setup to copy into the Windows directory. The - # intermediate Win9x.FirstLogon phase renames it to POST9X.FLG, and the next - # AUTOEXEC pass promotes that to POST9X.RDY. This avoids relying on UpdateInis - # to create an arbitrary marker file. + # Stage a real marker file for Setup to copy into the Windows directory. + # Win9x.FirstLogon adds a WININIT.INI rename so the following reboot promotes + # POST9X.NEW to POST9X.RDY before the final desktop starts. if ! printf 'Ready\r\n' > "$marker"; then error "Failed to create post-desktop marker for $desc!" return 1 @@ -1932,7 +1928,9 @@ writeWin9xUserRegistry() { 'HKCU,"Control Panel\Desktop","SmoothScroll",0x00010001,0' \ 'HKCU,"Control Panel\Desktop\WindowMetrics","MinAnimate",,"0"' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\Advanced","HideFileExt",0x00010001,0' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer\CabinetState","Settings",1,0c,00,02,00,0a,01,00,00,60,00,00,00' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","NoActiveDesktop",0x00010001,1' \ + 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Policies\Explorer","ClassicShell",0x00010001,1' \ 'HKCU,"Software\Microsoft\Windows\CurrentVersion\Explorer","link",1,00,00,00,00' if ! disabled "$AUTOLOGIN"; then @@ -1960,9 +1958,9 @@ writeWin98Registry() { printf '%s\n' \ '[Win98.Power]' \ - 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Controls Folder\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,02,00,00,00,04,00,00,c0,00,00,00,00,02,00,00,00,04,00,00,c0,00,00,00,00' \ - 'HKU,".DEFAULT\Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ - 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ + 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Controls Folder\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,02,00,00,00,04,00,00,c0,00,00,00,00,02,00,00,00,04,00,00,c0,00,00,00,00' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg","CurrentPowerPolicy",,"3"' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,64,64,64,64,8c,82' \ '' \ '[Win98.ActiveSetup]' \ 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx",,,">Batch 98 - General Settings"' \ @@ -1976,8 +1974,8 @@ writeWin98Registry() { 'DelFiles=Win98.Connect,Win98.ConnectAll' \ '' \ '[Win98.PowerUser]' \ - 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"0"' \ - 'HKCU,"Control Panel\PowerCfg\PowerPolicies\0","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,3c,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,78,00,00,00,00,00,00,00,58,02,00,00,00,00,64,64,64,64,8c,82' \ + 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"3"' \ + 'HKCU,"Control Panel\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,64,64,64,64,8c,82' \ '' \ '[Win98.User]' \ 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ @@ -2165,7 +2163,8 @@ writeWin9xAnswerFile() { fi if enabled "$post"; then - printf '%s\n' 'RenFiles=Win9x.PostArm' + [ -n "$firstLogonUpdateInis" ] && firstLogonUpdateInis+="," + firstLogonUpdateInis+="Win9x.PostMarker" fi if enabled "$shortcut"; then @@ -2222,8 +2221,8 @@ writeWin9xAnswerFile() { 'POST9X.BAT' \ 'POST9X.NEW' \ '' \ - '[Win9x.PostArm]' \ - 'POST9X.FLG,POST9X.NEW' + '[Win9x.PostMarker]' \ + '%10%\wininit.ini,Rename,,"C:\WINDOWS\POST9X.RDY=C:\WINDOWS\POST9X.NEW"' fi printf '%s\n' \ @@ -2240,9 +2239,7 @@ writeWin9xAnswerFile() { fi if enabled "$post"; then - printf '%s\n' \ - 'Win9x.Post=10' \ - 'Win9x.PostArm=10' + printf '%s\n' 'Win9x.Post=10' fi if [[ "${id,,}" == "win98"* ]]; then From 78052d004912e3dc33b46852b129bb17caab2819 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 13:30:58 +0200 Subject: [PATCH 21/29] Update legacy.sh --- src/legacy.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 6b3bb84f5..95ea85bf2 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -15,6 +15,7 @@ setMachine() { return 1 fi + writeState "vga" "std" || return 1 writeState "mode" "windows_legacy" || return 1 case "${id,,}" in @@ -26,18 +27,6 @@ setMachine() { esac - case "${id,,}" in - - "winnt4" ) - - writeState "vga" "cirrus" || return 1 ;; - - * ) - - writeState "vga" "std" || return 1 ;; - - esac - case "${id,,}" in "win95" | "winnt4" ) From 34554910e660f2e404374d2ad53ac9ae53cb7c02 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 14:00:46 +0200 Subject: [PATCH 22/29] Update legacy.sh --- src/legacy.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 95ea85bf2..adb34be5f 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -33,18 +33,21 @@ setMachine() { writeState "usb" "N" || return 1 writeState "port" "on" || return 1 - writeState "net" "pcnet" || return 1 ;; + writeState "net" "pcnet" || return 1 + writeState "sound" "sb16" || return 1 ;; "win98" | "win9x" ) writeState "port" "on" || return 1 writeState "net" "pcnet" || return 1 + writeState "sound" "sb16" || return 1 writeState "usb" "pci-ohci" || return 1 ;; "win2k"* ) writeState "net" "rtl8139" || return 1 - writeState "usb" "pci-ohci" || return 1 ;; + writeState "usb" "pci-ohci" || return 1 + writeState "sound" "usb-audio" || return 1 ;; "winxpx"* | "win2003"* ) @@ -54,6 +57,7 @@ setMachine() { "reactos" ) + writeState "sound" "AC97" || return 1 writeState "net" "rtl8139" || return 1 writeState "usb" "pci-ohci" || return 1 From d1f40ba820a749d71ffe95ae280ec15fb13a77a5 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 14:49:45 +0200 Subject: [PATCH 23/29] Update legacy.sh --- src/legacy.sh | 135 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 130 insertions(+), 5 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index adb34be5f..0de75c6a1 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1845,6 +1845,109 @@ EOF return 0 } +stageWin98DMA() { + + local dir="$1" + local desc="$2" + local target="$dir/WIN98DMA.EXE" + + # Keep the Win98-only DMA helper embedded alongside QUIET.EXE so the driver + # archive stays unchanged. It only updates enumerated ESDI DiskDrive devices. + if ! xxd -r -p > "$target" <<'EOF' +4d5a780001000000040000000000000000000000000000004000000000000000 +0000000000000000000000000000000000000000000000000000000078000000 +0e1fba0e00b409cd21b8014ccd21546869732070726f6772616d2063616e6e6f +742062652072756e20696e20444f53206d6f64652e240000504500004c010400 +b25f806a0000000000000000e00002010b010e00000200000004000000000000 +0010000000100000000000000000400000100000000200000400000000000000 +0400000000000000005000000004000000000000020000000000100000100000 +0000100000100000000000001000000000000000000000002c2000003c000000 +000000000000000000000000000000000000000000000000004000003c000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000088200000200000000000000000000000 +000000000000000000000000000000002e74657874000000d701000000100000 +0002000000040000000000000000000000000000200000602e72646174610000 +2201000000200000000200000006000000000000000000000000000040000040 +2e64617461000000a00100000030000000000000000000000000000000000000 +00000000400000c02e72656c6f6300003c000000004000000002000000080000 +0000000000000000000000004000004200000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +5553575683ec148d442404506a086a0068222040006802000080ff1598204000 +85c07527688000000068003040006a00ff742410ff15942040003d0301000075 +1aff742404ff15902040006a00ff158820400083c4145e5f5b5dc331f689e5bf +0100000031dbeb35ff3424ff159020400089e5666666662e0f1f840000000000 +436880000000680030400053ff742410ff15942040003d0301000074a485c075 +df556a086a006800304000ff742414ff159820400085c075c768000100006880 +3040006a00ff74240cff15942040003d03010000749231edeb37ff742408ff15 +902040006666662e0f1f840000000000456800010000688030400055ff74240c +ff15942040003d030100000f8457ffffff85c075db8d442408506a036a006880 +304000ff742410ff159820400085c075bfc744241000000000c744240c200000 +008d44240c5068803140008d442418506a006801204000ff74241cff159c2040 +0085c00f8571ffffff837c2410010f8566ffffff837c240c000f845bffffff31 +c06666666666662e0f1f8400000000000fb690803140008ab00720400084d20f +44cf38f20f45ce84d274054038f274e085c90f8422ffffff6a0168002040006a +036a006811204000ff74241cff15a0204000e903ffffffcccccccccccccccccc +cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc +01436c617373004469736b447269766500444d4143757272656e746c79557365 +6400456e756d5c45534449006820000000000000000000000821000088200000 +7020000000000000000000001521000090200000000000000000000000000000 +0000000000000000a820000000000000b6200000c4200000d2200000e2200000 +f620000000000000a820000000000000b6200000c4200000d2200000e2200000 +f62000000000000000004578697450726f63657373000000526567436c6f7365 +4b6579000000526567456e756d4b6579410000005265674f70656e4b65794578 +41000000526567517565727956616c7565457841000000005265675365745661 +6c756545784100004b45524e454c33322e646c6c0041445641504933322e646c +6c00000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +001000003c00000011301c302a30363047304f306d3087309230a730b130bf30 +cb30e030f73002311f312931473153315d3193319931bb31c431ce3100000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000 +EOF + then + error "Failed to create WIN98DMA.EXE in $desc setup files!" + return 1 + fi + + if [ "$(wc -c < "$target")" -ne 2560 ]; then + error "Failed to verify WIN98DMA.EXE in $desc setup files!" + return 1 + fi + + return 0 +} + stageWin9xPostSetup() { local dir="$1" @@ -1953,22 +2056,26 @@ writeWin98Registry() { '[Win98.Power]' \ 'HKLM,"Software\Microsoft\Windows\CurrentVersion\Controls Folder\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,02,00,00,00,04,00,00,c0,00,00,00,00,02,00,00,00,04,00,00,c0,00,00,00,00' \ 'HKU,".DEFAULT\Control Panel\PowerCfg","CurrentPowerPolicy",,"3"' \ - 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,64,64,64,64,8c,82' \ + 'HKU,".DEFAULT\Control Panel\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,02,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,04,00,00,00,05,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,01,64,64,64,64,00,00' \ '' \ '[Win98.ActiveSetup]' \ 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx",,,">Batch 98 - General Settings"' \ 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","IsInstalled",0x00000001,01,00,00,00' \ 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","Version",,"3,0,0,0"' \ 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchSetupx","StubPath",,"%25%\rundll.exe setupx.dll,InstallHinfSection Win98.Browser 4 %10%\msbatch.inf"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchStoragex",,,">Batch 98 - Storage Settings"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchStoragex","IsInstalled",0x00000001,01,00,00,00' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchStoragex","Version",,"3,0,0,0"' \ + 'HKLM,"SOFTWARE\Microsoft\Active Setup\Installed Components\>BatchStoragex","StubPath",,"%10%\WIN98DMA.EXE"' \ '' \ '[Win98.Browser]' \ - 'AddReg=Win98.User' \ + 'AddReg=Win98.User,Win98.PowerUser' \ 'DelReg=Win98.MSN,Win98.ICWDesktop' \ 'DelFiles=Win98.Connect,Win98.ConnectAll' \ '' \ '[Win98.PowerUser]' \ 'HKCU,"Control Panel\PowerCfg","CurrentPowerPolicy",,"3"' \ - 'HKCU,"Control Panel\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,01,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,8c,82,02,00,00,00,02,00,00,00,00,00,00,00,f0,3b,91,81,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,64,64,64,64,8c,82' \ + 'HKCU,"Control Panel\PowerCfg\PowerPolicies\3","Policies",0x00000001,01,00,00,00,02,00,00,00,01,00,00,00,00,00,00,00,02,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,32,32,00,00,04,00,00,00,05,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,00,01,64,64,64,64,00,00' \ '' \ '[Win98.User]' \ 'HKCU,"Software\Microsoft\Internet Connection Wizard","Completed",0x00010001,1' \ @@ -2055,9 +2162,11 @@ writeWin9xAnswerFile() { fi if [[ "${id,,}" == "win98"* ]]; then - # Active Setup reapplies browser defaults after IE initializes users and - # removes the Internet desktop items before Explorer lays out the desktop. + # Seed the machine/default Always On policy before user initialization. + # Active Setup reapplies the user policy after IE/Setup finish resetting + # per-user defaults, while the separate DMA helper runs after enumeration. addReg+=",Win98.Power,Win98.ActiveSetup" + copyFiles+=",Win98.DMA" fi if [[ "${id,,}" == "win98"* ]]; then @@ -2088,6 +2197,10 @@ writeWin9xAnswerFile() { 'VBMOUSE.EXE=22' \ 'VBMOUSE.DRV=22' + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' 'WIN98DMA.EXE=22' + fi + if ! disabled "$AUTOLOGIN"; then printf '%s\n' 'DOCKER.PWL=22' fi @@ -2218,6 +2331,13 @@ writeWin9xAnswerFile() { '%10%\wininit.ini,Rename,,"C:\WINDOWS\POST9X.RDY=C:\WINDOWS\POST9X.NEW"' fi + if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' \ + '' \ + '[Win98.DMA]' \ + 'WIN98DMA.EXE' + fi + printf '%s\n' \ '' \ '[DestinationDirs]' \ @@ -2236,6 +2356,7 @@ writeWin9xAnswerFile() { fi if [[ "${id,,}" == "win98"* ]]; then + printf '%s\n' 'Win98.DMA=10' printf '%s\n' \ 'Win98.Connect=10,Desktop' \ 'Win98.ConnectAll=10,alluse~1\desktop' \ @@ -2470,6 +2591,10 @@ prepareWin9xInstall() { stageWin9xPostSetup "$target" "$desc" "$install" || return 1 fi + if [[ "${id,,}" == "win98"* ]]; then + stageWin98DMA "$target" "$desc" || return 1 + fi + writeWin9xAnswerFile \ "$target" "$id" "$setup" "$monitor" \ "$batchHost" "$batchUsername" "$batchOrganization" "$batchWorkgroup" \ From 841a72077641702700ae89dba51a394d2e5e1937 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 15:07:52 +0200 Subject: [PATCH 24/29] Update legacy.sh --- src/legacy.sh | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 0de75c6a1..fb5e55047 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -120,12 +120,6 @@ restoreMachine() { writeState "old" "$MACHINE" || return 1 fi - # Migrate existing WinXP installs to QEMU 10 - if [[ "${MACHINE,,}" == "pc-q35-2.10" ]]; then - MACHINE="" - removeState "old" || return 1 - fi - [ -z "$MACHINE" ] && MACHINE="q35" return 0 From fd539ed186423490e5592ce4dbf2737e2f311b0d Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 15:11:04 +0200 Subject: [PATCH 25/29] Update readme.md --- readme.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/readme.md b/readme.md index 70b6116f0..3d85f0ed5 100644 --- a/readme.md +++ b/readme.md @@ -125,8 +125,6 @@ For a complete graphical desktop experience, see [WinBoat](https://winboat.app), | `vu` | Windows Vista Ultimate | 3.0 GB | | `xp` | Windows XP Professional | 0.6 GB | | `2k` | Windows 2000 Professional | 0.4 GB | - | `xp` | Windows XP Professional | 0.6 GB | - | `2k` | Windows 2000 Professional | 0.4 GB | | `me` | Windows ME | 0.5 GB | | `98` | Windows 98 | 0.7 GB | | `95` | Windows 95 | 0.6 GB | From bd86beccd18e482130a6bb7f9e9b4b51373be605 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 15:21:02 +0200 Subject: [PATCH 26/29] Update legacy.sh --- src/legacy.sh | 162 ++++++++------------------------------------------ 1 file changed, 25 insertions(+), 137 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index fb5e55047..7452cac63 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -1749,29 +1749,10 @@ stageWin9xPasswordList() { local desc="$2" local target="$dir/DOCKER.PWL" - if ! xxd -r -p > "$target" <<'EOF' -e382859601000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -000000000000000000ffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff -ffffffffffffffff520200000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -00000000000000000000000001000000000075ad273deae8e3a155a623d2cd02 -c4c9833636fb9ed100c68322dd0c92fb04d4c6119aaaada60ebe2972e0286df6 -3bcab4ebf885df9a413ea245fa9f5b5e81f6e70d2fd5d0d06ca06e18c328d88f -9a26b992331d22e76ab135b9851b3d9b + if ! base64 -d <<'EOF' | gzip -dc > "$target" +H4sIAAAAAAACA3vc1DqNkWEUMPwf2SCIifIghKSj0rXqtq9ePF4Yukz50lmmIyebzcx+z7vIcKxZ +6S7PpN8sV44Jzlq1dhnfPs2iBxq536xPbXn9o/X+LEe7Ra6/5kfHNX57zqt/9cKFnAV5Eoc1bvTP +Uts5yVhW6XnWRtOdrdK2swELaJAzsAIAAA== EOF then error "Failed to create DOCKER.PWL in $desc setup files!" @@ -1792,39 +1773,12 @@ stageWin9xQuiet() { local desc="$2" local target="$dir/QUIET.EXE" - if ! xxd -r -p > "$target" <<'EOF' -4d5a000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000080000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -504500004c010100000000000000000000000000e00002010b01010000020000 -0000000000000000001000000010000000100000000040000010000000020000 -0400000000000000040000000000000000200000000200000000000002000000 -0000100000100000000010000010000000000000100000000000000000000000 -8010000028000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -000000000000000000000000000000000000000000000000b810000010000000 -0000000000000000000000000000000000000000000000002e74657874000000 -0001000000100000000200000002000000000000000000000000000060000060 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -ff15b810400089c6803e22751146803e007440803e22740346ebf346eb12803e -007430803e207408803e09740346ebee803e207405803e09750346ebf3803e00 -74116a0056ff15bc1040006a00ff15c01040006a00ff15c01040000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -a81000000000000000000000c8100000b8100000000000000000000000000000 -0000000000000000d6100000e8100000f210000000000000d6100000e8100000 -f2100000000000004b45524e454c33322e444c4c00000000476574436f6d6d61 -6e644c696e654100000057696e457865630000004578697450726f6365737300 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 + if ! base64 -d <<'EOF' | gzip -dc > "$target" +H4sIAAAAAAACA/ONYiAbNDBQDgJcGRh8GBlRxB4wMDFyg8SYkAQFkDCDA4QGyrNApWE0gwIDXB8T +TKMAMg2nIH4AcjQYqA92CKDagw70SlIrSkAMRga4X1D8CwQJYERb8F90h4ADQ+exBjulUkG3BjuG +Egcgs4TZ7fVnt9dCIL5Bg51CCUeDHSdI8B2IwwrklIJUgKQFsxjC/ovuARqSBTTsABJNFFiBFEgn +BCDhhg1cA4q/AOJPAtj53q5Bfq4+xkZ6Lj4+IL57aolzfm5uYl6KT2ZeqiNQJDwzz7UiNRnIcq3I +LAkoyk9OLS5mGOkAAN77EFsABAAA EOF then error "Failed to create QUIET.EXE in $desc setup files!" @@ -1847,87 +1801,21 @@ stageWin98DMA() { # Keep the Win98-only DMA helper embedded alongside QUIET.EXE so the driver # archive stays unchanged. It only updates enumerated ESDI DiskDrive devices. - if ! xxd -r -p > "$target" <<'EOF' -4d5a780001000000040000000000000000000000000000004000000000000000 -0000000000000000000000000000000000000000000000000000000078000000 -0e1fba0e00b409cd21b8014ccd21546869732070726f6772616d2063616e6e6f -742062652072756e20696e20444f53206d6f64652e240000504500004c010400 -b25f806a0000000000000000e00002010b010e00000200000004000000000000 -0010000000100000000000000000400000100000000200000400000000000000 -0400000000000000005000000004000000000000020000000000100000100000 -0000100000100000000000001000000000000000000000002c2000003c000000 -000000000000000000000000000000000000000000000000004000003c000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000088200000200000000000000000000000 -000000000000000000000000000000002e74657874000000d701000000100000 -0002000000040000000000000000000000000000200000602e72646174610000 -2201000000200000000200000006000000000000000000000000000040000040 -2e64617461000000a00100000030000000000000000000000000000000000000 -00000000400000c02e72656c6f6300003c000000004000000002000000080000 -0000000000000000000000004000004200000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -5553575683ec148d442404506a086a0068222040006802000080ff1598204000 -85c07527688000000068003040006a00ff742410ff15942040003d0301000075 -1aff742404ff15902040006a00ff158820400083c4145e5f5b5dc331f689e5bf -0100000031dbeb35ff3424ff159020400089e5666666662e0f1f840000000000 -436880000000680030400053ff742410ff15942040003d0301000074a485c075 -df556a086a006800304000ff742414ff159820400085c075c768000100006880 -3040006a00ff74240cff15942040003d03010000749231edeb37ff742408ff15 -902040006666662e0f1f840000000000456800010000688030400055ff74240c -ff15942040003d030100000f8457ffffff85c075db8d442408506a036a006880 -304000ff742410ff159820400085c075bfc744241000000000c744240c200000 -008d44240c5068803140008d442418506a006801204000ff74241cff159c2040 -0085c00f8571ffffff837c2410010f8566ffffff837c240c000f845bffffff31 -c06666666666662e0f1f8400000000000fb690803140008ab00720400084d20f -44cf38f20f45ce84d274054038f274e085c90f8422ffffff6a0168002040006a -036a006811204000ff74241cff15a0204000e903ffffffcccccccccccccccccc -cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc -01436c617373004469736b447269766500444d4143757272656e746c79557365 -6400456e756d5c45534449006820000000000000000000000821000088200000 -7020000000000000000000001521000090200000000000000000000000000000 -0000000000000000a820000000000000b6200000c4200000d2200000e2200000 -f620000000000000a820000000000000b6200000c4200000d2200000e2200000 -f62000000000000000004578697450726f63657373000000526567436c6f7365 -4b6579000000526567456e756d4b6579410000005265674f70656e4b65794578 -41000000526567517565727956616c7565457841000000005265675365745661 -6c756545784100004b45524e454c33322e646c6c0041445641504933322e646c -6c00000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -001000003c00000011301c302a30363047304f306d3087309230a730b130bf30 -cb30e030f73002311f312931473153315d3193319931bb31c431ce3100000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 -0000000000000000000000000000000000000000000000000000000000000000 + if ! base64 -d <<'EOF' | gzip -dc > "$target" +H4sIAAAAAAACA+1VTUgUcRR/s2610eKs4EKE4CgLUcTy//fpoWC2nUFEzcltFTKzzZ11dtsPm52N +FTosuEsaBGIdCjp4COpYERJ1UEiQKA28dPCQBB7Cuniw4/RmZ79U/LhWPni87/d+///+52371TQw +AGCF9cTDzpRGrq5/Vw1vDs41vGXa5hquKOEkN6gmBtRAjOsPxOMJjbspc2oqzoXjnNDh42KJoOx2 +AUgiQBtjhdd9mUix3xJYmENMNYClApCjwEVUhm4ph0u4pbJhKRY6KmVFG6QTHMD57Q7H7xDfgkaw +L7dN3K3JaQ3lVwZKZ9l4+Vh/w60GA1oAoJExHfm8/Zsg8m4zDSaMPLLlUabdqhxN9BfOxBf62Tbl +XYQ9+q/I7+vuGv5Z+0BwWaWILQJKI8eDgo8jozsfo5qbTh1VMpioAOEhArrmcujORxi5UIVPLnUE +HVbdOcblg84RlMMztdf7eno/0LXR5SnjWdLFlTP6aZeZNbocQnKz9dn8fG+5uW9Db+0ZDv/mz6My +4hiuLYGaVYy1pWSKoOyVheP018o5dNrMkZXzxHKdf0MZm+3WdR17L+Jt2KRIVcTMM2EV5k7NCq78 +HkFpN75LzLVLSobyhnZYwhqGy9fU6c6n+Ro2dxv7Dt91ORg2FzJVO07rQZVOh0Lr7oOdHDN63X95 +AGuzC6zwpWmVFeezC9o+vmlVW8p9ZLONWBhhFDDu3ABZUxo4gdqPKgx/3i0x3mggmQQhnLwlqOE7 +MgjtHm9KVeW4Fh3yJ+UgiPFU7JroE1pAqVhstgZz1w1W+JzoG9ti+b0o+CdRziAvIH9HXuN2F8df +Lh3WJDXRLyNcgE55wBtNJOVWeci0DJhoeEyrY1COoyWmC/bllKwOdQWiKdl0GT6frJU9rWLnJbHt +1El3MBoFj9DlkVoKxj9ODvM/oYbUkePkLGkmHSRG7pFx8py8IlPkE1kiv4mF1tNjtJn6aC99SJ/Q +93SGztO97fn30x8aoy/WAAoAAA== EOF then error "Failed to create WIN98DMA.EXE in $desc setup files!" From 11871da240895ec1772ba73f00a48e421a529a2a Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 15:49:01 +0200 Subject: [PATCH 27/29] Update legacy.sh --- src/legacy.sh | 58 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 7452cac63..20d705b32 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -711,10 +711,36 @@ appendRegistry() { local dir="$1" local driver="$2" + if [[ "$driver" == "2k" || "$driver" == "xp" ]]; then + { + printf '%s\n' \ + '[HKEY_CURRENT_USER\Control Panel\Desktop]' \ + '"MenuShowDelay"="100"' \ + '' \ + '[HKEY_CURRENT_USER\Control Panel\Desktop\WindowMetrics]' \ + '"MinAnimate"="0"' \ + '' \ + '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer]' \ + '"link"=hex:00,00,00,00' '' + } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 + fi + + if [[ "$driver" == "xp" ]]; then + { + printf '%s\n' \ + '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]' \ + '"PowerPolicy"="Wscript.exe C:\\OEM\\NT5POWER.VBS"' '' + } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 + fi + if [[ "$driver" == "2k" ]]; then { printf '%s\n' \ - '[HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Runonce]' '"^SetupICWDesktop"=-' '' + '[HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Runonce]' \ + '"^SetupICWDesktop"=-' \ + '' \ + '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]' \ + '"NoActiveDesktop"=dword:00000001' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi @@ -740,8 +766,10 @@ writeVBS() { local dir="$1" local username="$2" local shortcut="$3" + local driver="$4" local balloon="$dir/\$OEM\$/balloon.cmd" local balloonExe="$dir/\$OEM\$/\$1/Drivers/Balloon/blnsvr.exe" + local power="$dir/\$OEM\$/\$1/OEM/NT5POWER.VBS" # Locate the built-in Administrator by its RID 500 SID rather than its # localized display name, then rename that account to the requested username. @@ -793,6 +821,32 @@ writeVBS() { fi } | unix2dos > "$dir/\$OEM\$/install.vbs" || return 1 + if [[ "$driver" == "xp" ]]; then + mkdir -p "$(dirname "$power")" || return 1 + + { + printf '%s\n' \ + 'On Error Resume Next' \ + 'Set Shell = WScript.CreateObject("WScript.Shell")' \ + 'Set FSO = WScript.CreateObject("Scripting.FileSystemObject")' \ + 'PowerCfg = Shell.ExpandEnvironmentStrings("%SystemRoot%\System32\POWERCFG.EXE")' \ + '' \ + 'If FSO.FileExists(PowerCfg) Then' \ + ' Err.Clear' \ + ' Policy = Shell.RegRead("HKCU\Control Panel\PowerCfg\CurrentPowerPolicy")' \ + ' If Err.Number = 0 Then' \ + ' Cmd = Chr(34) & PowerCfg & Chr(34) & " /CHANGE " & Policy & " /NUMERICAL "' \ + ' For Each Setting In Array("/monitor-timeout-ac", "/monitor-timeout-dc", "/disk-timeout-ac", "/disk-timeout-dc", "/standby-timeout-ac", "/standby-timeout-dc")' \ + ' Shell.Run Cmd & Setting & " 0", 0, True' \ + ' Next' \ + ' End If' \ + 'End If' \ + '' + } | unix2dos > "$power" || return 1 + else + rm -f -- "$power" || return 1 + fi + if [ -f "$balloonExe" ]; then { printf '%s\n' \ @@ -995,7 +1049,7 @@ prepareSIFInstall() { writeRegistry "$dir" "$shortcut" "$oem" "$regUsername" "$regPassword" || return 1 appendRegistry "$dir" "$driver" || return 1 - writeVBS "$dir" "$username" "$shortcut" || return 1 + writeVBS "$dir" "$username" "$shortcut" "$driver" || return 1 return 0 } From 170abbefbb9ffa523c29212e306b2d3db041e253 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 15:56:29 +0200 Subject: [PATCH 28/29] Update legacy.sh --- src/legacy.sh | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/legacy.sh b/src/legacy.sh index 20d705b32..97b2efe64 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -711,7 +711,7 @@ appendRegistry() { local dir="$1" local driver="$2" - if [[ "$driver" == "2k" || "$driver" == "xp" ]]; then + if [[ "$driver" == "2k" || "$driver" == "xp" || "$driver" == "2k3" ]]; then { printf '%s\n' \ '[HKEY_CURRENT_USER\Control Panel\Desktop]' \ @@ -725,7 +725,7 @@ appendRegistry() { } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 fi - if [[ "$driver" == "xp" ]]; then + if [[ "$driver" == "xp" || "$driver" == "2k3" ]]; then { printf '%s\n' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\RunOnce]' \ @@ -737,8 +737,13 @@ appendRegistry() { { printf '%s\n' \ '[HKEY_USERS\.DEFAULT\Software\Microsoft\Windows\CurrentVersion\Runonce]' \ - '"^SetupICWDesktop"=-' \ - '' \ + '"^SetupICWDesktop"=-' '' + } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 + fi + + if [[ "$driver" == "2k" || "$driver" == "2k3" ]]; then + { + printf '%s\n' \ '[HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\Explorer]' \ '"NoActiveDesktop"=dword:00000001' '' } | unix2dos >> "$dir/\$OEM\$/install.reg" || return 1 @@ -821,7 +826,7 @@ writeVBS() { fi } | unix2dos > "$dir/\$OEM\$/install.vbs" || return 1 - if [[ "$driver" == "xp" ]]; then + if [[ "$driver" == "xp" || "$driver" == "2k3" ]]; then mkdir -p "$(dirname "$power")" || return 1 { From c3ca97e2d5f667f7628071014b696b7669d638c9 Mon Sep 17 00:00:00 2001 From: Kroese Date: Sat, 15 Aug 2026 16:01:41 +0200 Subject: [PATCH 29/29] Update legacy.sh --- src/legacy.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/legacy.sh b/src/legacy.sh index 97b2efe64..044f1781e 100644 --- a/src/legacy.sh +++ b/src/legacy.sh @@ -2127,7 +2127,7 @@ writeWin9xAnswerFile() { 'Version=3.0 (32-bit)' \ '' \ '[Version]' \ - 'Signature="$CHICAGO$"' \ + "Signature=\"\$CHICAGO\$\"" \ 'AdvancedINF=2.5' \ 'LayoutFile=layout.inf' \ '' \