diff --git a/README.md b/README.md index d4f4ed1..386359d 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ Each of the outputs in this flake have their own image builders and `runScript`. - `makeWin30Image` - `makeWfwg311Image` - `makeWin98Image` +- `makeWin2kImage` They can each be passed the `dosPostInstall` argument arbitrary **dos commands** to be ran after Windows has been installed, for example here's how diff --git a/flake.nix b/flake.nix index 14ca955..4589a89 100644 --- a/flake.nix +++ b/flake.nix @@ -60,6 +60,8 @@ makeWin30Image = pkgs.callPackage ./makeWin30Image {}; makeWfwg311Image = pkgs.callPackage ./makeWfwg311Image {}; makeWin98Image = pkgs.callPackage ./makeWin98Image {}; + makeWin2kImage = pkgs.callPackage ./makeWin2kImage {}; + makeWinXPImage = pkgs.callPackage ./makeWinXPImage {}; # makeSystem7Image = pkgs.callPackage ./makeSystem7Image {}; }; apps = { @@ -83,6 +85,14 @@ type = "app"; program = config.packages.win98-image.runScript; }; + win2k = { + type = "app"; + program = config.packages.win2k-image.runScript; + }; + winxp = { + type = "app"; + program = config.packages.winxp-image.runScript; + }; }; packages = rec { macos-ventura-image = config.legacyPackages.makeDarwinImage {}; @@ -90,8 +100,11 @@ win30-image = config.legacyPackages.makeWin30Image {}; wfwg311-image = config.legacyPackages.makeWfwg311Image {}; win98-image = config.legacyPackages.makeWin98Image {}; + win2k-image = config.legacyPackages.makeWin2kImage {}; + winxp-image = config.legacyPackages.makeWinXPImage {}; #system7-image = config.legacyPackages.makeSystem7Image {}; #macos-repeatability-test = genOverridenDrvLinkFarm (macos-ventura-image.overrideAttrs { repeatabilityTest = true; }) 3; + win2k-repeatability-test = genOverridenDrvLinkFarm win2k-image 100; win98-repeatability-test = genOverridenDrvLinkFarm win98-image 100; wfwg311-repeatability-test = genOverridenDrvLinkFarm wfwg311-image 100; win30-repeatability-test = genOverridenDrvLinkFarm win30-image 100; diff --git a/makeWin2kImage/answers.nix b/makeWin2kImage/answers.nix new file mode 100644 index 0000000..328f4bf --- /dev/null +++ b/makeWin2kImage/answers.nix @@ -0,0 +1,43 @@ +# Windows 2000's unattended installation feature is a bit tricky to figure out. +# The SUPPORT/TOOLS/SETUP.EXE in the install disk ISO crashes DOSBox-X, but +# SUPPORT/TOOLS/SREADME.DOC says that it doesn't install the relevant deployment +# tools in DEPLOY.CAB anyway. If you extract SUPPORT/TOOLS/DEPLOY.CAB with +# cabextract, there is setupmgr.exe inside that has a GUI for creating the +# answer files. It must be run in the same directory as setupmgx.dll, also +# included in DEPLOY.CAB. There is also documentation in DEPLOY.CAB in the +# deptool.chm, readme.txt, and unattend.doc files. The setupmgr.exe tool +# doesn't ask for a product key, so that has to be added to UserData.ProductID +# separately. Otherwise, during the install there will be an error asking the +# user to input it. The unattend.doc file also contains documentation of the +# different answer file options. A web version of unattend.doc is at +# https://web.archive.org/web/20040314065512/https://www.microsoft.com/technet/prodtechnol/Windows2000Pro/deploy/unattend/default.mspx + +{ + Data = { + AutoPartition = 1; + MsDosInitiated = "0"; + UnattendedInstall = "Yes"; + }; + Unattended = { + UnattendMode = "FullUnattended"; + OemSkipEula = "Yes"; + OemPreinstall = "No"; + TargetPath = "WINDOWS"; + }; + GuiUnattended = { + AdminPassword = "*"; + AutoLogon = "Yes"; + OEMSkipRegional = 1; + TimeZone = 4; + OemSkipWelcome = 1; + }; + UserData = { + FullName = "user"; + OrgName = "NixThePlanet"; + ComputerName = "*"; + ProductID = "RBDC9-VTRC8-D7972-J97JY-PRVMG"; + }; + RegionalSettings.LanguageGroup = 1; + Identification.JoinWorkgroup = "WORKGROUP"; + Networking.InstallDefaultComponents = "Yes"; +} diff --git a/makeWin2kImage/default.nix b/makeWin2kImage/default.nix new file mode 100644 index 0000000..c9f5189 --- /dev/null +++ b/makeWin2kImage/default.nix @@ -0,0 +1,106 @@ +# Fabulous.systems demonstrated that installing Windows 2000 in DOSBox-X is possible in +# https://fabulous.systems/posts/2023/07/installing-windows-2000-in-dosbox-x/ +# but this package uses a different approach, installing from scratch instead of +# from Windows 98 and using an answer file for unattended installation. + +{ lib, fetchurl, runCommand, p7zip, dosbox-x, x11vnc, tesseract, vncdo, xvfb-run +, writeText, writeShellScript, callPackage }: +{ dosPostInstall ? "", imageType ? "hd_1gig", answerFile ? + writeText "answers.ini" (lib.generators.toINI { } (import ./answers.nix)) }: +let + win2k-installer = fetchurl { + name = "win2k.7z"; + urls = [ + "https://winworldpc.com/download/413638c2-8d18-c39a-11c3-a4e284a2c3a5/from/c39ac2af-c381-c2bf-1b25-11c3a4e284a2" + "https://winworldpc.com/download/413638c2-8d18-c39a-11c3-a4e284a2c3a5/from/c3ae6ee2-8099-713d-3411-c3a6e280947e" + "https://cloudflare-ipfs.com/ipfs/QmT7rGKU4WzQxwpfZqFgGwGSCrBbBm7SejUSPgDzxAgPye/Microsoft%20Windows%202000%20Professional%20(5.00.2195).7z" + ]; + sha512 = + "9cb026d8eaa3933d7ca0447c7e1b05fd1504a6063b16a86d5cca4dc04ef5d598bd8ae95dac6f10671422ece192b1aff94ecd505d2cd7a15981eaa4fd691f1489"; + }; + dosboxConf = stage: + writeText "dosbox.conf" '' + [dosbox] + memsize = 32 + + [dos] + hard drive data rate limit = 0 + floppy drive data rate limit = 0 + + [cpu] + # Turbo prevents the boot screen from progressing in stage 2 + turbo = ${if stage == 2 then "off" else "on"} + + [autoexec] + mount a . + if not exist a:\win2k.img imgmake win2k.img -t ${imageType} + imgmount c win2k.img -t hdd + imgmount d win2k.iso + if not exist c:\ntldr d:\i386\winnt /s:d: /u:a:answers.ini + boot -l c + ''; + iso = runCommand "win2k.iso" { } '' + echo "win2k-installer src: ${win2k-installer}" + mkdir win2k + ${p7zip}/bin/7z x -owin2k ${win2k-installer} + ls -lah win2k + mv win2k/*/*.iso $out + ''; + tesseractScript = writeShellScript "tesseractScript" '' + export OMP_THREAD_LIMIT=1 + cd $(mktemp -d) + TEXT="" + while true + do + sleep 3 + ${vncdo}/bin/vncdo -s 127.0.0.1::5900 capture cap.png + NEW_TEXT="$(${tesseract}/bin/tesseract cap.png stdout 2>/dev/null)" + if [ "$TEXT" != "$NEW_TEXT" ]; then + echo "$NEW_TEXT" + TEXT="$NEW_TEXT" + fi + done + ''; + installedImage = runCommand "win2k.img" { + # set __impure = true; for debugging + # __impure = true; + buildInputs = [ dosbox-x xvfb-run x11vnc ]; + passthru = rec { + makeRunScript = callPackage ./run.nix; + runScript = makeRunScript { }; + }; + } '' + echo "iso src: ${iso}" + cp --no-preserve=mode ${iso} win2k.iso + cp --no-preserve=mode ${answerFile} answers.ini + # This install is fully unattended, but a VNC server and tesseract script are still started for log output and debugging + ( + while true; do + DISPLAY=:99 XAUTHORITY=/tmp/xvfb.auth x11vnc -many -shared -display :99 >/dev/null 2>&1 || true + echo RESTARTING VNC + done + ) & + ${tesseractScript} & + ${lib.strings.concatMapStrings (stage: '' + echo STAGE ${toString stage} + xvfb-run -l -s ":99 -auth /tmp/xvfb.auth -ac -screen 0 800x600x24" \ + dosbox-x -conf ${dosboxConf stage} || true + '') [ 1 2 ]} + cp win2k.img $out + ''; + postInstalledImage = let + dosboxConf-postInstall = writeText "dosbox.conf" '' + [dosbox] + memsize = 32 + + [autoexec] + imgmount c win2k.img + ${dosPostInstall} + exit + ''; + in runCommand "win2k.img" { inherit (installedImage) passthru; } '' + cp --no-preserve=mode ${installedImage} ./win2k.img + SDL_VIDEODRIVER=dummy ${lib.getExe dosbox-x} -conf ${dosboxConf-postInstall} + mv win2k.img $out + ''; +in if (dosPostInstall != "") then postInstalledImage else installedImage diff --git a/makeWin2kImage/run.nix b/makeWin2kImage/run.nix new file mode 100644 index 0000000..2b261cb --- /dev/null +++ b/makeWin2kImage/run.nix @@ -0,0 +1,38 @@ +{ writeShellScriptBin, writeText, lib, dosbox-x, makeWin2kImage +, extraDosboxFlags ? [ ], diskImage ? makeWin2kImage { } }: +let + dosboxConf = writeText "dosbox.conf" '' + [dosbox] + memsize = 32 + + [sdl] + autolock = true + + [autoexec] + imgmount C win2k.img + boot -l C + ''; +in writeShellScriptBin "run-win2k.sh" '' + args=( + -conf ${dosboxConf} + ${lib.concatStringsSep " " extraDosboxFlags} + "$@" + ) + + if [ ! -f win2k.img ]; then + echo "win2k.img not found, making disk image ./win2k.img" + cp --no-preserve=mode ${diskImage} ./win2k.img + fi + + run_dosbox() { + ${dosbox-x}/bin/dosbox-x "''${args[@]}" + } + + run_dosbox + + if [ $? -ne 0 ]; then + echo "Dosbox crashed. Re-running with SDL_VIDEODRIVER=x11." + SDL_VIDEODRIVER=x11 run_dosbox + fi +'' + diff --git a/makeWinXPImage/answers.nix b/makeWinXPImage/answers.nix new file mode 100644 index 0000000..c404de3 --- /dev/null +++ b/makeWinXPImage/answers.nix @@ -0,0 +1,28 @@ +{ + Data = { + AutoPartition = 1; + MsDosInitiated = "0"; + UnattendedInstall = "Yes"; + }; + Unattended = { + UnattendMode = "FullUnattended"; + OemSkipEula = "Yes"; + OemPreinstall = "No"; + TargetPath = "WINDOWS"; + }; + GuiUnattended = { + AdminPassword = "*"; + EncryptedAdminPassword = "NO"; + OEMSkipRegional = 1; + TimeZone = 4; + OemSkipWelcome = 1; + }; + UserData = { + ProductKey = "MRX3F-47B9T-2487J-KWKMF-RPWBY"; + FullName = "user"; + OrgName = "NixThePlanet"; + ComputerName = "*"; + }; + Identification.JoinWorkgroup = "WORKGROUP"; + Networking.InstallDefaultComponents = "Yes"; +} diff --git a/makeWinXPImage/default.nix b/makeWinXPImage/default.nix new file mode 100644 index 0000000..afe13c2 --- /dev/null +++ b/makeWinXPImage/default.nix @@ -0,0 +1,132 @@ +# The installer from DOS (winnt.exe) doesn't work, so use winnt32.exe from Windows 2000 + +{ lib, fetchtorrent, runCommand, dosbox-x, xvfb-run, x11vnc, vncdo, tesseract +, expect, writeText, writeShellScript, writeScript, makeWin2kImage, callPackage +}: +{ dosPostInstall ? "", answerFile ? + writeText "answers.ini" (lib.generators.toINI { } (import ./answers.nix)) }: +let + win2k = makeWin2kImage { imageType = "hd_2gig"; }; + winxp-installer = fetchtorrent { + url = + "https://archive.org/download/WinXPProSP3x86/WinXPProSP3x86_archive.torrent"; + hash = "sha256-NDCPO4gT4rgfB76HrF/HtaRNzSfpXJUSHbqLqECvkpU="; + }; + dosboxConf = stage: + writeText "dosbox.conf" '' + [dosbox] + memsize = 128 + + [dos] + ver = 7.0 # Need long filenames support to edit the C drive in autoexec + hard drive data rate limit = 0 + floppy drive data rate limit = 0 + + [cpu] + cputype = ppro_slow + # Turbo breaks win2k boot and final stage + turbo = ${if builtins.elem stage [ 1 3 ] then "off" else "on"} + + [autoexec] + imgmount c win2k.img + imgmount d winxp.iso + ${lib.optionalString (stage == 2) '' + # After the XP install is bootstrapped, remove the old Windows 2000 files to make space for XP + deltree /y c:\WINDOWS + deltree /y "c:\Documents and Settings" + deltree /y "c:\Program Files" + ''} + boot -l c + ''; + tesseractScript = writeShellScript "tesseractScript" '' + export OMP_THREAD_LIMIT=1 + cd $(mktemp -d) + TEXT="" + while true + do + sleep 3 + ${vncdo}/bin/vncdo -s 127.0.0.1::5900 capture cap.png + NEW_TEXT="$(${tesseract}/bin/tesseract cap.png stdout 2>/dev/null)" + if [ "$TEXT" != "$NEW_TEXT" ]; then + echo "$NEW_TEXT" + TEXT="$NEW_TEXT" + fi + done + ''; + expectScript = let + vncdoWrapper = writeScript "vncdoWrapper" '' + sleep 3 + ${vncdo}/bin/vncdo --force-caps -s 127.0.0.1::5900 "$@" + ''; + in writeScript "expect.sh" '' + #!${expect}/bin/expect -f + set debug 5 + set timeout -1 + spawn ${tesseractScript} + expect "ENTER-Install" + exec ${vncdoWrapper} key enter + # Keep running until killed so the entire build gets tesseract log output + while { 1 } { sleep 10000 } + ''; + installedImage = runCommand "winxp.img" { + # set __impure = true; for debugging + # __impure = true; + buildInputs = [ dosbox-x xvfb-run x11vnc ]; + passthru = rec { + makeRunScript = callPackage ./run.nix; + runScript = makeRunScript { }; + }; + } '' + ln -s ${winxp-installer}/*.iso winxp.iso + cp --no-preserve=mode ${win2k} win2k.img + cp --no-preserve=mode ${answerFile} answers.ini + # Copy answer file to win2k.img and add autostart script that runs the XP installer + ( + SDL_VIDEODRIVER=dummy dosbox-x -conf ${ + writeText "dosbox.conf" '' + [dos] + ver = 7.0 # Need long filenames support to edit the C drive in autoexec + + [autoexec] + mount a . + imgmount c win2k.img + copy a:\answers.ini c:\ + echo d:\i386\winnt32.exe /unattend:c:\answers.ini > "c:\Documents and Settings\All Users\Start Menu\Programs\Startup\start-xp-install.bat" + exit + '' + } + ) + ( + while true; do + DISPLAY=:99 XAUTHORITY=/tmp/xvfb.auth x11vnc -many -shared -display :99 >/dev/null 2>&1 || true + echo RESTARTING VNC + done + ) & + ${expectScript} & + ${lib.strings.concatMapStrings (stage: '' + echo STAGE ${toString stage} + xvfb-run -l -s ":99 -auth /tmp/xvfb.auth -ac -screen 0 800x600x24" \ + dosbox-x -conf ${dosboxConf stage} || true + '') (lib.range 1 3)} + cp win2k.img $out + ''; + postInstalledImage = let + dosboxConf-postInstall = writeText "dosbox.conf" '' + [cpu] + turbo = on + stop turbo on key = false + + [autoexec] + imgmount c winxp.img + ${dosPostInstall} + exit + ''; + in runCommand "winxp.img" { + buildInputs = [ dosbox-x ]; + inherit (installedImage) passthru; + } '' + cp --no-preserve=mode ${installedImage} ./winxp.img + SDL_VIDEODRIVER=dummy dosbox-x -conf ${dosboxConf-postInstall} + mv winxp.img $out + ''; +in if (dosPostInstall != "") then postInstalledImage else installedImage diff --git a/makeWinXPImage/run.nix b/makeWinXPImage/run.nix new file mode 100644 index 0000000..336ff30 --- /dev/null +++ b/makeWinXPImage/run.nix @@ -0,0 +1,41 @@ +{ writeShellScriptBin, writeText, lib, dosbox-x, makeWinXPImage +, extraDosboxFlags ? [ ], diskImage ? makeWinXPImage { } }: +let + dosboxConf = writeText "dosbox.conf" '' + [dosbox] + memsize = 128 + + [cpu] + cputype = ppro_slow + + [sdl] + autolock = true + + [autoexec] + imgmount C winxp.img + boot -l C + ''; +in writeShellScriptBin "run-winxp.sh" '' + args=( + -conf ${dosboxConf} + ${lib.concatStringsSep " " extraDosboxFlags} + "$@" + ) + + if [ ! -f winxp.img ]; then + echo "winxp.img not found, making disk image ./winxp.img" + cp --no-preserve=mode ${diskImage} ./winxp.img + fi + + run_dosbox() { + ${dosbox-x}/bin/dosbox-x "''${args[@]}" + } + + run_dosbox + + if [ $? -ne 0 ]; then + echo "Dosbox crashed. Re-running with SDL_VIDEODRIVER=x11." + SDL_VIDEODRIVER=x11 run_dosbox + fi +'' +