UNAUTHORIZED WINDOWS/386

I wanted to share something special, a friend of mine, Will, has been so busy working on this project and I wanted to share it here for everyone here first.

This is pretty technical, but still interesting deep look into one of Microsoft’s early 32bit/386 based programs that would go on to revolutionize the world, Windows/386! It brought the v86 virtual machine to normal people wrapped up in a nice GUI.

By Will Klees (CaptainWillStarblazer)

INTRODUCTION

I’m CaptainWillStarblazer, an author who has previously been featured on VirtuallyFun for my work on EmuWOW, which enabled running Win32 apps compiled for the MIPS and Alpha AXP architectures to run on x86 computers. While I was born in the 21st century, I have a keen interest in the computers of the past, particularly in the history of Microsoft. The foundations for the breakout success of Windows 3.0, 3.1, and 9x were laid with Windows/386, but until recently, the inner-workings of Windows/386 have not been well understood, and beyond the very high-level, exactly how it works have been considered an opaque black box, not ventured into with books (official or otherwise) like its successors. No longer.

FOREWORD

Before I begin, I would like to acknowledge that all of my work here was informed by the research of the late, great Geoff Chappell, who has many in-depth pages on this topic as well as many others that laid the groundwork for this post. His contributions to the scene are immeasurable, and I, along with many of you, stand on the shoulders of giants like him. It is unfortunate that up to this point, Windows/386 has not faced much reverse-engineering work (especially in comparison to the better-documented Windows 3.x and 95), but for the first time, it is being examined.

ARCHITECTURE OF WINDOWS/386

Windows/386 Loader (WIN386.EXE)

The structure of Windows/386 is broadly similar to later versions of Windows running in enhanced mode. The journey begins with WIN386.EXE, which is a standard MZ EXE. WIN386 first performs some checks to make sure that your machine can run Windows/386 (you have enough memory, the right version of DOS, you have an 80386, defending against early buggy 386 steppings, etc.), among them being whether your computer is currently executing in Virtual-8086 Mode. If you are, then that means that another piece of protected-mode software is already controlling the computer. From there, it checks if Windows/386 is already running, and if so, displays an error message. From there, it checks if the resident protected-mode software is a memory manager that it recognizes (either Compaq’s CEMM or Microsoft’s EMM386), and if so, uses the GEMMIS (Global EMM Import Specification) API to suck out all of the EMS mapping page tables from the LIMulator and then switch back into real-mode. If it doesn’t recognize the protected-mode software, it at this point throws another error message.

This check for early buggy 386 steppings was retained by Microsoft even into Windows 8.1, surprisingly enough. The system can also check for 386 chips with bad 32-bit multiplication, though it only warns the user of potential issues, it doesn’t fail to run like if you are running a Model 1 Stepping 0 chip.

[photo of the code checking for the buggy 386]

Finally, it begins loading the Virtual DOS Machine Manager (VDMM) into memory from the file WIN386.386. This file is not an OS/2 Linear Executable like the 386 files from later versions of Windows (that format did not yet exist), rather it is the 32-bit x.out executable format from Xenix-386 (thank you, Michal Necasek!), which makes sense as it was the only 32-bit executable format that Microsoft would have a linker for at the time (and interoperated well with Microsoft’s OMF-based tools, such as MASM). Among the features of this format is that it contains a rather-lengthy symbol table. Not only does this aid reverse-engineering, however, it’s also a key part of the loading process. The WIN386.EXE loader will populate parts of the loaded image with important data using these symbols.

Virtual DOS Machine Manager and Virtual Device Drivers (WIN386.386)

WIN386.386 contains a statically-linked binary image of the VDMM itself as well of all of the virtual device drivers. Disassembling the source code of Windows/386 was an interesting exercise. On my repo, I have a partial disassembly of EGA.3EX, the WIN386.EXE loader for the EGA version of WIN386.386, which is a standard MS-DOS executable and as such easily examined by reverse-engineering tools. However, the 32-bit x.out format used by Windows/386 is not readily supported by any reverse-engineering tools that I am aware of. While it would be possible to write an Ida or Ghidra plugin, I figured the simplest solution was to convert it to a more standard executable format that could be understood; COFF. After extracting the 16-bit entry stub into a small flat binary to be disassembled on its own, the COFF file could finally be opened (in reality, tools didn’t seem to like the COFF file very much, so I had to use GNU objcopy to convert it to ELF so that tools would like it) and examined.

[photo of the conversion program]
[objdump or dumpbin examining the resulting COFF image]

WIN386.386 starts execution in real-mode with a short stub that prepares the Global Descriptor Table, loads the page directory, switches into protected-mode, and does a far jump to the 32-bit entry point. At this point, it starts WIN86.COM (loaded by WIN386.EXE) to start a real-mode copy of Windows in the first VM, otherwise known as the “System VM”.

Two valuable resources for examining the code of Windows/386 have turned out to be the source code for MEMM (Microsoft Expanded Memory Manager, better known by its final name EMM386) from the MS-DOS 4.0 repository, and the Windows 3.0 DDK sample VxDs. It is obvious from comparing the Windows/386 disassembly to portions of the MEMM source code that portions of MEMM, both for EMS emulation and for the V86 monitor in particular, were simply lifted wholesale into Windows/386, and code comments even make reference to this. Amusingly, MEMM was assembled using the MASM 4.00 assembler which has poor support for the 80386, so copious amounts of macros are used to add in 386 instructions. Perhaps the most interesting EMM386-related finding, however, was that parts of EMM386 were written in C. This seemed obvious given the leading underscore and __cdecl-style calling convention in several Windows/386 functions, but examining the code finds it to be true.

[emm386 c & 32-bit corresponding asm]

Based on my examination, it appears that if you took the EMM386 C code and compiled it for a 32-bit flat model (EMM386’s code was compiled for a 16:16 far pointer model), you’d get the assembly in Windows/386. This is interesting because Windows/386 was previously thought to be written entirely in assembly, and the Microsoft 386 C compiler was in its infancy when Windows/386 was being written. It’s not entirely unbelievable, however, since Xenix-386, the earliest known user of the compiler, came out around the same time as Windows/386.

The other handy reference while disassembling Windows/386 has actually been the Windows 3.0 DDK. Since the VDMM contains all of the virtual device drivers statically linked into it, and many of Windows 3.0’s virtual device drivers can trace their beginnings to Windows/386, there’s often a strong correspondence. Many APIs have changed, however, including how VxDs call each other. In Windows/386, it’s just a simple call, while their status as separate modules in Windows 3.0 requires a VxDCall; a special interrupt that causes the VMM to transfer control to another VxD.

[comparing between a Windows 3.0 VxD and a Windows 2.03 VxD]

Examination of the MapLinear function finds that the memory map for Windows/386 2.xx is essentially identical to Windows 3.0. The first 4MB is the private per-VM arena (so chosen as it allows a task-switch to be as simple as altering the first PDE in the page directory, rather than having to switch page directories), then the 4-20MB range identity-maps the first 16MB of physical memory, and the VDMM is loaded at the 20MB mark.

As a quick example of one of the code paths in Windows/386, when Windows/386 needs to return an entry point to a client application (such as through the INT 2FH AX=1602H API), it needs some way to cause a client calling that entry point in Virtual 8086 Mode to trap into protected mode. As documented by Raymond Chen, they found that the quickest way to do this was via the invalid opcode fault, and the invalid opcode they chose for this was 63H, or ARPL. As part of a mechanism that is still in place in Windows 95, when a VM executes an ARPL instruction, it’ll trap into the VDMM, vectoring through the IDT to vm_trap06.

[photo of the IDT]
[photo of vm_trap06]

From there, it determines if the fault came from a VM or not. If not, it executes the Windows/386 error handler, but if it did, it calls into VmFault. VmFault looks up the faulting opcode through a table and invokes the appropriate handler for it. The appropriate handler for ARPL is called Patch_Fault. From there, it determines what kind of call this is, and if you’re lucky, it’ll end up in TS_VMDA_Call, which is described in the next section.

[photo of VmFault]
[photo of Patch_Fault]

The System VM – Windows 2 and WINOLDAP

The code running inside of the system VM is almost identical to a standard real-mode Windows 2.xx install, with one exception: WINOLDAP. Responsible for executing MS-DOS (“old”) applications, WINOLDAP is totally different in Windows/386 (and as such, not functional if you try to load WIN86.COM directly from real-mode on its own, which otherwise provides a perfectly workable real-mode Windows experience), making heavy use of 386 instructions and of the “Virtual DOS Applications” (VDA, otherwise known as VMDOSAPP) API (accessed via INT 2FH AX=1601H in Windows/386 2.03 and 2.11) which is made available exclusively to the system VM, allowing WINOLDAP to control the execution of other virtual machines.

[photos of the dispatch tables and routines for VDA]
[example app using VDA]

While the details of this API certainly changed for Windows 3.0 and for later versions, WINOLDAP continued to work in fundamentally the same way, with the DOS application running in the System VM (intended to be Windows) being uniquely privileged to control operations in other virtual machines. Given that many people have figured out how to make Windows/386 start applications other than Windows itself (such as COMMAND.COM), this means that nothing would stop a sufficiently enterprising developer from developing a text-based MS-DOS application that leveraged this API to provide multitasking. In fact, this is likely how Raymond Chen’s “character-mode task switcher” functioned. WINOLDAP is worthy of further examination to determine exactly how it works, and perhaps to develop a multitasking MS-DOS. Obviously, this API, intended to have only Windows as a client, is totally undocumented other than by myself and Geoff Chappell, but further work could reveal its secrets.

In addition to the VDA API, Windows/386 also provided a much more limited API to callers in other virtual machines (accessed via INT 2FH AX=1602H), that appears to still be available in Windows 3.0 and is primarily responsible for networking.

For most of my experimentation, I actually sidestepped booting Windows altogether so that I could run my own code in the System VM. This is fairly simple; all you need to do is copy COMMAND.COM over WIN86.COM, then start WIN386, and viola! You’re running COMMAND.COM in Virtual 8086 Mode! Probably the most notable change is that if you didn’t already have any LIM EMS memory, you do now.

LOST WINDOWS/386 DDK

While no DDK for Windows/386 2.xx has been located, hints have been scattered for its existence. Most notably, the Windows 3.0 386 Virtual Device Adaptation Guide provides guidance on the differences between Windows/386 2.xx and Windows 3.0, and how to port virtual display drivers from one to the other, suggesting that Microsoft did provide tools to enable third-party developers to write Windows/386 virtual device drivers. It’s not difficult to imagine what this DDK would have looked like. Likely distributed alongside the regular Windows/286 real-mode DDK, the Windows/386-specific portions would include the 32-bit capable MASM5, along with early versions of MAPSYM32, WDEB386, and the Xenix x.out ld link editor. Very likely, Microsoft provided sample code for each of the VxDs included with Windows/386 (including the CGA, EGA, and Hercules VDDs), as well as a precompiled OMF object containing the VDMM itself, and then one would link everything together.

It bears repeating that the documentation on porting virtual device drivers from Windows/386 2.xx to Windows 3.0 was limited solely to virtual display drivers. The only other references to Windows/386 2.xx in the Virtual Device Adaptation Guide discuss the Windows/386 API callable by DOS applications running in a DOS box (many device drivers and applications, including network stacks, were Windows/386 aware). This could mean that other types of drivers could be more easily reassembled for Windows 3.0 without documentation, but I doubt it. As it stands, most of the virtual device drivers included in Windows/386 were fairly generic; the COM port, timer, PIC, keyboard, and other such devices work almost identically in every PC-compatible computer. On the other hand, the display driver is the one major component that Windows would need to interact with and that would significantly change between different types of machines. Additionally, due to the statically-linked nature of Windows/386 at this point, having more than one VxD as the variable factor could balloon into a smorgasbord of different combinations of drivers statically linked into the WIN386.386 image. As such, it stands to reason that the only driver built by third-parties (though no such driver has yet been located) is a virtual display driver. This lines up with Microsoft’s own distribution of Windows/386, as the disks include separate 386 files for each supported display (the appropriate file being copied for your machine based on your selection during setup) and a matching 3EX file that gets copied to become the WIN386.EXE loader, and display drivers (also including their own complete Windows/386 images, obviously based on customizing the EGA/VGA VDD) have been found for other display adapters as well. This is compounded by the fact that during SETUP (including for real-mode Windows), the 16-bit display driver is statically linked into the Windows kernel (in other words, you can only load a display driver during SETUP) for the “fast-boot” configuration (though this can be disabled for a “slow-boot” on debug versions, more similar to how Windows 3.0 and above boot). A lot of reading between the lines is needed here, but it does seem that the only customization Microsoft intended was for OEMs to provide their own virtual display drivers.

BREAKING INTO WINDOWS/386

In absence of the Windows/386 DDK and its associated debugger, options are fairly limited as to peeking into the internals of Windows/386 while it is active. Promise was initially found in WIN386.EXE making a call to INT 68H (the WDEB386 real-mode interface, also used by the Deb386 debugger developed for EMM386 that no doubt was the immediate ancestor of WDEB386, as well as by compatible debuggers such as SoftICE) with AH=43H (D386_Identify, typically the first call made when initializing a program that uses WDEB386), no doubt trying to call out to its version of WDEB386, if present. However, the version of WDEB386 from Windows 3.1 only partially worked. While a CTRL-C could break into WDEB386 at any time, it could only trace through Virtual 8086 Mode code (always breaking at an ARPL VM-86 breakpoint), and whenever you tried to resume execution using the G command, Windows/386 would exit.

As a result, I had to improvise my own debugger, which required me to gain the ability to execute my own 32-bit code within Windows/386, which has never before been achieved. I immediately decided to adopt a similar approach to WDEB386; leave the debugger behind in conventional memory before Windows/386 starts up, and then have it call into me, so I quickly set about writing a small TSR. The TSR hooked INT 69H with a routine called Intrude that would patch the IDT of Windows/386 (found via traversing the image symbol table) to point to my own code for interrupt vector 0 (the divide exception handler). That way, whenever a divide exception occurred, it would vector into my own code.

The next question you may be wondering about is how I got Windows/386 to invoke an INT 69H in the first place? The answer lies in the real-mode initialization stub of WIN386.386; the part that switches into protected-mode. Examine the listing below:

Enable_A20:

01B7 803EBD00F8 CMP BYTE PTR [Computer_Type],0F8H ; Check for fast A20 support

01BC 7707 JA Enable_A20_Slow

01BE E492 IN AL,92H ; Fast A20 enable

01C0 0C02 OR AL,2 ; Set bit 1 (A20 line control)

01C2 E692 OUT 92H,AL ; Output back to port 92H

01C4 C3 RET

Enable_A20_Slow:

01C5 B4DF MOV AH,0DFH

01C7 EB12 JMP Set_A20

By the time Enable_A20 is called, which checks the computer type from the BIOS, most of the data structures needed to enter Windows/386 have already been set up, so I patched Windows/386 to simply remove Fast A20 support and always use the slow code, putting an INT 69H in the slack space. In other words, it replaces the instruction at TEXT16:01B7 with an INT 69H (CD 69). Since the original instruction is 5 bytes long, the remaining three are padded with NOP (90). The instruction at TEXT16:01BC is then altered to be an unconditional jump (EB) to always invoke the slow A20 line control. Since the loaded object is always at offset 400H in the file, and the offsets appear to be the same for all versions of Windows/386 on all devices, the changes are:

5B7: 80 -> CD

5B8: 3E -> 69

5B9: BD -> 90

5BA: 00 -> 90

5BB: F8 -> 90

5BC: 77 -> EB

The trouble at this point was that, while my program did work, it left the protected-mode code sitting in conventional memory, and part of the System VM’s inherited address space and thus subject to corruption. As a result, I wanted to move it up into extended memory, out of the reach of any pesky DOS programs. My first thought was to use XMS memory through HIMEM.SYS, which was introduced with Windows/386 2.11 to facilitate access to the HMA for Windows. Unfortunately, while this did sort of work, it turns out that Windows/386 (which if you’ll recall was initially designed before XMS or HIMEM.SYS) does not respect XMS allocations made before Windows loads, and thus considers them part of its extended memory pool (a fact I was taught by the fact that it corrupts the first two DWORDs of every 64K memory block starting after the HMA as part of its memory test). It is also important to realize that Windows/386 2.11 does not provide virtual XMS services to any client VMs (though Windows 3.0 and later versions do), except for HMA access to the System VM only (Windows/286 2.11 also used the HMA on 80286 and above systems, hence the “286” name, though it otherwise worked fine on XT-class machines, and since Windows/386 ran Windows/286 in the System VM, it made sense to also support the HMA there).

As a result, I used the “expand-down” memory allocation method of determining the amount of installed extended memory using INT 15H AH=88H, and then hooking that interrupt to report 132K less memory than there was before, and using the last 132K of extended memory for my own purposes. Since INT 15H AH=88H can report up to 64MB of installed extended memory, while INT 15H AH=87H to copy into extended memory only supports up to 16MB, I had to write my own routines to copy into extended memory by switching into protected-mode and then back. As a result, W386DBG has to be loaded before any memory manager that places the machine into Virtual 8086 Mode, such as EMM386, or anything that allocates XMS memory (not that any such programs are likely to be used alongside Windows/386, as just as I stated earlier, the XMS memory would be corrupted).

As you can see, if you cause a divide exception in DEBUG.COM, it’ll print out “W386DBG” in the upper-right of the screen and then hang the computer. This won’t work for a software INT 0, because software interrupts from Virtual 8086 Mode vector through the GPF handler.

[photo of the program launching]
[photo of the hang with the VBox debugger showing where we hung]

Note that while we lack any debug version of the VDMM (along with any symbols that it may contain or debug messages it may output), the VDMM itself does as stated earlier have a considerable symbol table, and we have debug versions of Windows 2 as part of its DDK, which were meant to be used with SYMDEB and include symbols, so at least we can have full debugging capabilities for the 16-bit components of Windows, simply by loading debug Windows 2 into the System VM, as no doubt one was intended to do when developing device drivers for Windows/386. Obviously, W386DBG is not yet a functional debugger, but it has gained the ability to grab control from Windows/386, which is perhaps the most important part.

INTO THE FUTURE WITH WINDOWS VERSION 3.0

Lately, I have become interested in turning my attention to the Windows 3.0 version 14 debug release that shipped to ISVs in early 1989. As one would expect, it shows many similarities to Windows 2.xx, but is already well on the way to becoming the Windows 3.0 that we know.

Notably, the WIN386.386 file is now gone, having been merged into WIN386.EXE as with the final version of Windows 3.0, meaning that the same DOS executable both loads the VMM and contains it. However, the VMM itself (pointed to by the e_lfanew field in the MZ header) is not an OS/2 2.0 Cruiser Linear Executable like the final version (or, more properly, the W3 format which contains multiple LE VxDs within it), but rather another bespoke format with a “W386” signature that I have not yet torn into yet. All of the VxDs are still statically linked at this point, but the symbol file is showing movement toward the VMM we know from Windows 3.0.

I haven’t disassembled all of the real-mode entry portion of WIN386 yet (this will allow me to fully understand the file format), but an interesting piece of code new to this build checks to make sure not only that the DOS major version is at least 3 (3 being the minimum DOS version) but also less than 10, as 10 is the major DOS version reported by OS/2 1.x’s 3xBox, making Windows/386 3.0.14 OS/2-aware (and avoidant).

One piece of Windows 3.0-related history that was recently discovered was the manual for Murray Sargent’s Scroll-Screen-Tracer debugger. The debugger is far too rich in features to begin to go over them, but among its incredible DOS-extending features include support for debugging applications in Virtual 8086 Mode (a la SoftICE), debugging Windows, and debugging regular MS-DOS applications running in the 80286’s Protected Mode, much as was described in “Saving Windows from the OS/2 Bulldozer”.

Interestingly, the DOS extender provided by WIN386, along with PKERNEL.EXE (the protected-mode Windows kernel) seem to have more in common with the 80286 DOS extender, DOSX.EXE, from Windows 3.0, along with the 80286 standard mode kernel, KRNL286.EXE, than they do with the enhanced mode counterparts.

For example, like in the final version of Windows 3.0, DOSX (in this case, WIN386) switches into protected-mode before loading PKERNEL/KRNL286, giving it the unique distinction of being an MZ executable that starts in protected-mode, using a stub to start executing the NE portion of the file. By contrast, in Windows 3.1 (and 3.0 enhanced mode), the DOS extender switches back into real / Virtual 8086 Mode before loading the kernel, which then uses DPMI to switch into protected-mode.

Along with translation for DOS API services, according to Michal Necasek of the OS/2 Museum, WIN386 appears to provide some sort of selector management interface via INT 31H that could be considered a sort of proto-DPMI. Disassembling both WIN386 and PKERNEL promises to be an interesting exercise. Not much is known about the early history of DPMI, but the first sign of it outside of Microsoft appears to date to Fall 1989:

I will never forget how startled I was when I encountered the DOS-Protected Mode Interface (DPMI) in its primordial form for the first time. I was sitting in a Microsoft OS/2 2.0 ISV seminar in the Fall of 1989, with my mind only about half-engaged during an uninspiring session about OS/2 2.0’s Multiple Virtual DOS Machines (MVDMs), when the speaker mentioned in passing that OS/2 2.0 would support a new interface for the execution of DOS Extender applications. This casual remark focused my mind remarkably…

After the speaker finished, I went up to him and asked for more information, explaining that his mystery interface was about to have a severe impact on a book project near and dear to my heart. In a couple of hours, the Microsoftie returned with a thick document entitled “DOS Protected Mode Interface Specification, Version 0.04” still warm from the Xerox machine and generously garnished with “CONFIDENTIAL” warning messages. I suspect I made a most amusing spectacle, as I flipped through the pages with my eyes bulging out and my jaw dropping to the floor. The document I had been handed was nothing less than the functional specification of a protected-mode version of MS-DOS!

Microsoft originally defined the DPMI in two layers: a set of low-level functions for interrupt management, mode switching, and extended memory management; and a higher-level interface that provided access to MS-DOS, ROM BIOS, and mouse driver functionality via protected-mode execution of Int 21H, Int 10H, Int 33H, and so on. The higher-level DPMI functions were implemented, of course, in terms of the lower-level DPMI functions and the extant real-mode DOS and ROM BIOS interface.

Ray Duncan, Extending DOS, 2d ed., 1992, pp. 433-438

Obviously, by this point, Microsoft, who was still heavily invested in OS/2, planned to implement DPMI into OS/2 2.0, though they would not do so for about a year afterwards. Desiring crossover with the protected-mode DOS apps that would run on Windows (most crucially, Windows itself) was no doubt a desire for the OS/2 development team. I was surprised to learn that DPMI was already by this point mature enough to have even a preliminary specification released. Moreover, Microsoft went on to, at the behest of DOS Extender vendors, such as Phar Lap and Rational Systems, excise from the DPMI specification all of the higher-level DOS Extender components, and “DPMI 0.9” was born, containing only the low-level building blocks of a DOS Extender. As Andrew Schulman went on to say, the DOS Extender portions of DPMI ended up being split off into their own document:

Microsoft has an internal document (“MS-DOS API Extensions for DPMI Hosts,” October 31, 1990) that devotes about 30 pages to the Windows 3.0 DOS extenders… For example, the 1990 document discusses the 32-bit DOS extender provided by DOSMGR. The DOS file read and write calls (INT 21h functions 3Fh and 40h) have the count register (ECX) extended to 32-bits, allowing 32-bit programs to perform DOS file I/O of more than 64K at a time.

Andrew Schulman, Unauthorized Windows 95, 1994, pp. 151-52

On the PCjs website, Version 0.04 from March 1991 of the MS-DOS API Extensions for DPMI Hosts can be found, and it is obviously quite a preliminary document. What it seems is that DPMI was designed simply to expose the Windows DOS Extender (used by the Windows kernel) to other DOS protected-mode software. DPMI sits on the AH=16H Windows/386 part of the INT 2FH multiplex (W386_Int_Multiplex), with the “Get Protected Mode Switch Entry Point” API from DPMI even being documented as part of INT2FAPI.INC from the Windows 3.0 DDK as W386_Get_PM_Switch_Addr. The “Get Selector to Base of LDT” API from the MS-DOS API Extensions document is even part of INT2FAPI.INC as W386_Get_LDT_Base_Sel. DPMI was defined as an interface for protected-mode DOS software to interface with the Windows (and OS/2) DOS Extenders, and ultimately a subset of the Windows DOS Extender API got standardized and duplicated by other vendors; in effect, DPMI hosts implement a genericized version of the Windows DOS Extender.

If you’re interesting in looking at my code and seeing future developments in the disassembly and W386DBG, check it out at https://github.com/BHTY/WIN386.

How to train your Dragon^H^H AI overlord

It’s no secret to anyone the internet is dead.

Those 2 or 3 of us that still read independant web sites are a stark minority out there on the wild interwebs.

Recenlty however I noticed that the size of my log files for unix.superglobalmegacorp.com, were starting to get out of control. Originally I had this bright idea of taking some popular source code, like Net/2 & GCC/Binutils and putting them online through a CVS explorer in hopes that google would index them, so I could then in turn search through the source code to find things or fragments when I was doing silly and pointless ports. I had achieived part of my dream for a while, and I’d be surprised when other people were suddenly using my site a citations for easily to find code. Then the whole github thing took off and it slid into toal obscurity.

But over the years, I had noticed more and more bots aggressively scouring my UTZoo archives, no doubt training the programs that would give rise to AI, as feeding it materials is absolutey paramount. And the 20GB of the UTZoo usenet archives are a gold mine of popular culture 1981-1991. Although that traffic has dropped off a cliff the last few months, which brings us back to the CVS archives.

Ive been getting quite a few hits, sometimes, as much as 200,000 hits a day from varous AIs. So that begs the question,

What is AI really into?

And here is the top 10 hit results:

  1. Qemu
  2. OSKit
  3. GCC
  4. 4.3 BSD Reno
  5. Hatari
  6. Research Unix v10
  7. 4.2 BSD
  8. 4.3 BSD
  9. Linux
  10. 4. 3 BSD Tahoe

I know, Hatari? The Atari ST emuator? Yeah.

Further cheating by using AI, I had grouped stuff into categories, then made a nice graph in Excel 97 to better visualize this small sample of data:

Graph of AI activity by date over project

So the first thing that popped out was August the 29th. Does the AI take a break on Fridays? Or was there some new bigger priority? Also September 2nd onward has taking on a much more pronounced interest in Qemu. I should add that data for the 6th is incomplete as the day is not over.

The only thing more suitable, was to ask AI what source code would it be interesting in me adding to the easy to scrape site, and it came back with:

Rare or Niche C Projects Worth Surfacing

  • TinyCC (TCC): A small, fast C compiler — great for AI to learn compiler construction.
  • ELKS (Embeddable Linux Kernel Subset): Linux for 16-bit x86 — ideal for constrained environments.
  • LCC (Local C Compiler): A well-documented, retargetable compiler used in academia.
  • Old versions of SQLite: Compact, portable, and full of real-world C patterns.
  • Early GUI toolkits (e.g., XView, Motif): For AI to learn event loops and widget hierarchies.

Which I have to agree is certainly worth adding.

Also it had made this recommendation:

🧠 High-Impact Historical C Codebases to Consider

SourceWhy It Matters
Minix (pre-3.x)Andrew Tanenbaum’s teaching OS — famously inspired Linux. Clean, pedagogical C code.
Plan 9 from Bell LabsSuccessor to UNIX with elegant abstractions. Great for distributed systems and namespace concepts.
Ultrix (DEC)Rare but valuable BSD variant with VAX and MIPS support. Adds diversity to hardware targets.
Apollo Domain/OSA pre-POSIX UNIX with unique ideas. If you can find it, it’s gold for AI learning edge cases.
TENEX or TOPS-20Not strictly C, but fascinating for AI to learn OS design from non-UNIX paradigms.
CP/M or MP/MEarly microcomputer OSes — useful for understanding low-level disk and memory management.
AmigaOS or MorphOS kernelRich in multimedia and event-driven design, with C-heavy internals.
BSD/OS (BSDi)Commercial BSD variant — adds nuance to licensing and code evolution.
OpenVMS C modulesIf available, they offer robust examples of enterprise-grade system programming.
Classic UNIX utilities (e.g., ed, awk, yacc)These are algorithmically rich and often beautifully minimal.

Although not sure of where to get this kind of stuff, and where it could be suitably hosted if it’s even possible to find.

I had actually been trying to source a lot fo the old Minix sources, and although the patches for Bruce Evans 386 port of Minix are around, I cannot find the source code to his 386 compiler & tool chain. Not sure if it was ever publically available, or if anyone saved it.

For the 2-3 humans left alive out there, any other great/useful source bases to feed the machine, so at least it’s got a decent grasp of C?

Today is 30 years since the launch of Windows 95

Yeah I can’t believe it either.

Pretty much everything I wanted to say is something I said a decade ago, when it was only 20 years old. Maybe I’ll expand on it.

In the meantime, check this out!

Windows 95 in under 8MB

It’s tyomitch‘s 2 disk Windows 95 bootable set! That’s right, 2 1.44Mb disks, and be whisked away in the exciting world of Windows 95, complete with working command prompt and notepad! … and not much else.

I’ve installed it onto a VMDK for older emulators that seem to have issues with the long filename mapping somehow. But I love the idea of DOSLFN & long name archives coming together like this!

So Yeah I installed it to a VMDK, and added some Sim City, and yep it runs!

Sim City runs!

So yeah it’s complete enough for some 16bit tasks for sure! Kind of fun to play with so I’ve uploaded the VMDK on archive.org: Windows 95 ‘2disk’ set with Sim City

I guess look forward to the 40th & 50th anniversary?

A quick lookback at Virtual PC for Windows

AKA Yes, of course you can run DooM!

DooM v1.1 at 16 colours

While it’s been … quite some time, I used to use an early beta of VMware 1.0 back in the day early 00’s I believe for Linux, where I could run Lotus notes and some other apps that just of course were not available for Linux. And it was neat but a bit pricey. And as I’d tout the benefits of having desktop virtual computers, getting rid of the need of having a stack of random junk PCs from your desk.

The retail box of Virtual PC for Windows

Connectix was trying new things, and yeah, the Mac was a dying platform, and bringing their tech to the PC was a winning move that would have lasting ramifications for the industry for decades. Well that and getting bought out by Microsoft.

Virtual PC for the PC, was another great, but short-lived solution for those few of us that got the ‘pc virtualization’ bug early on, before the tidal wave of bare metal hypervisors, and the serious business it’d create.

For me, it was the ability to use server class hardware with multiple PSU’s lots of RAM, redundant disks as firewalls that I could easily setup with multiple network cards, and instances allowing full isolation, and hide the mess of wires with something that looked pretty elegant.

Booting PC-DOS 7.01

I’d recently come across a copy of Virtual PC 4.0 for Windows, the system requirements are pretty log, Windows ME (98 is reported to work), NT 4.0 & Windows 2000. As a bonus it includes PC DOS 7.01. Just like the Mac minimal versions.

Running VPC on NT under Qemu is dooable, even under OS X, although you’ll want sound, of course which is a bit tricky now.


qemu-system-i386 -m 256 -cpu pentium -hda nt4wks.vmdk -net nic,model=pcnet -net user -cdrom nt4allinone.iso -device sb16,audiodev=snd,iobase=0x220,irq=7,dma=1 -device adlib,audiodev=snd -audiodev coreaudio,id=snd -parallel none -serial none -drive file=fat:rw:/tmp/vbemp

Although this is a bit involved, I have issues with the later QEMU’s sound blaster emulation and found it best to specify IRQ 7, which means removing the parallel port (who prints anyways?!). I also used the vbempj driver for that full colour experence.

Return to Zork

Although games like Links386, Return to Zork, DooM should have had retail Windows versions IMHO, but I know that getting 32bit games up and running on Windows 3.1 had so many dependancies and hardware requirements that made it quite the ordeal (as mentioned when running DooM on Windows 3.1)

For a long while your options for playing ‘old’ circa mid 90’s MS-DOS games was going all in on NT4/2000/XP with a fast machine and VirtualPC (there was a time when DOSBox wasn’t a thing) or the NTVDM emulated sound blaster driver. At least XP 32bit had one built in (and hidden sadly). But as machines pulled away from ISA and got faster, dual booting to MS-DOS for games with sound became a thing of the past, very quickly.

As shown above, of course I had to run DooM. even on the glorious 16 colour display, but yeah. And yeah it’s great!

I should add, that also for a long while this was my preferred way to play DOS games like DooM, or a multitude of things, also like running OS/2, this was my go-to way to get things done. You can check in the history that I was also a super big fan of Virtual PC 2004 on XP. It’s kind of crazy that Virtual PC for Windows is some 24 years ago now!

I had to limit the CPU to pentium level for the installation, then Virtual PC requires a Pentium II or higher. But luckily that’s a simple switch for Qemu.

So, while on this path, I thought it’d be fun to try the next evolution, Virtual Server 2005 on Windows Server 2003. Because I love old stuff, I ran Qemu 0.15.1 because yeah it’s so ancient. bonus!

qemu.exe -L pc-bios -m 512 -cpu pentium3 -net nic,model=pcnet -net user -hda 2003.vmdk -redir tcp:1024::1024

The good news is that Windows Server installed okay, and so did Virtual Server. However running was a different story:

Changing the CPU flag to any combination got the same result that a Pentium II or greater is required. Bummer.

I have a copy of VMWare Workstation handy, and thought I could just mount the VMDK, and good to go. No. Nooooo. Or at least I failed. Use the install template for 2003, and just deal with it. I know. dissapoint.

However, the best part is that it runs fine on VMware. Although it is SLOW. so slow. I updated the VM to 4 core, single processor, 512Mb of ram, and installed Service Pack 1 for the 2003 server. It helped a bit too!

The big fun of Virtual Server is of course being able to manage it remotely, although the remote console is an ActiveX control, so you need to enable IE mode on Edge. It’s under settings and ‘compatibility’ , turn on the 11 mode, and add the admin site and blamo good to go!

And yeah!

You can kinda use the server remotely from a modern machine. Many menu options don’t render correctly, and I had to copy/paste some of the URL’s directly. I think I had this issue with FireFox 20 years ago, although I don’t feel like digging that far deep. It may have been chrome too, but does it matter? I need that ActiveX hook.

With the control loaded, and DooM set to low resolution and drop the render window and..

Well yeah, it’s almost okay.

I’ll have to dig out a P4 and try it native, as emulation in emulation is of course a finicky thing. But I’m all too happy to run this on my laptop without getting my hands dirty dealing with old hardware.

Kind of what got me into this in the first place, running it all, virtually.

‘Till next time!

quick note on re-purposing old Windows Hyper-V servers

imagine being so pressed for time, not having any OS CDs or a boot server as it’s all gone.

you find these old servers, pull them off the domain and join a new one, and much to your amazement they remember where they were.

endless problems .

but you don’t have the time to format, you need a cluster built of garbage and you need it quickly. Simply run this from PowerShell:

Clear-ClusterNode

running that zapped the cluster config.

which is odd, I thought it lived in AD.

also using TruNAS it doesn’t load for anything over the ilo console. Didn’t have the time again. Using Rufus, use the 2nd option, DD mode, and it installed like a snap.

Enjoying some Real Audio on a working Friday

I have some old stuff transcoded to Real Audio, because why not? It’s like a magical hidden file-format from years past.

Granted the streaming software is so old, it breaks with NAT, but there was an older playable RAM / playlist file that’d just pull down .ra’s from a web server, like a snap. Except I never could get it to work right, but I didn’t spend much energy on it, because who cares?

GET /DSOTM.ram

Well I went for it today, hooked up some tcpdump after getting the stupid ‘error 11’ and yeah, turns out that even the 3.0 client is HTTP 1.0. So that means it doesn’t use named servers, so you can’t really put it on a modern web server. It’s bad enough it doesn’t like modern cryptography, but this is just the worst. So I figured I’d just try to do something raw as I’m also kinda annoyed that I can’t have good old HTTP access to my vpsland archive, since google will penalize you for having HTTP sites/links. But a $1 VPS and a $1 domain, and I’m in business.

The first thing I went to do was to setup haproxy to front all this, as I like to use all kinds of weird backends, like UnixWare, or Windows NT 3.1

+
+frontend http8888
+        bind *:8888
+        mode http
+        http-request set-header Connection "keep-alive" if { req.ver "1.0" }
+        http-request set-header Host "dsotm.rabbitfoxtrot.uk" if { req.ver "1.0" }
+
+        acl dsotm hdr(host) -i dsotm.rabbitfoxtrot.uk:8888
+        acl is_ra path_end .ra .RA
+        acl is_ram path_end .ram .RAM
+        use_backend dsotm if dsotm || is_ra || is_ram
+
+backend dsotm
+        balance leastconn
+        option httpclose
+        option forwardfor
+        server local1 127.0.0.1:80 check inter 500 maxconn 20
a

This diff is for the /etc/haproxy/haproxy.conf which simply adds an 8888 front end. I set it to add a ‘keep-alive’ & ‘Host’ fields in the header to start to make it seem a bit right.

Next it will parse out the extension of what files are being retrieved. More magic would be needed to do multiple RealAudio’s in that different paths/trees for sure, but since this is the only one on that front end, just looking for the .ra/.ram is enough. I could also attach other sites to that front end, dancing around this, but that’s a bit more work.

that’ll attach a name, in this case dsotm.rabbitfoxtrot.uk, and then shove it to the local Apache server which I’ve moved to the loopback adapter.

Configuring Apache is a bit more involved with all the moving parts:

ports.conf

--- ports.conf.dist     2025-04-11 17:28:47.627256671 +0000
+++ ports.conf  2025-04-11 17:28:58.374176767 +0000
@@ -2,7 +2,7 @@
 # have to change the VirtualHost statement in
 # /etc/apache2/sites-enabled/000-default.conf

-Listen 80
+Listen 127.0.0.1:80

 <IfModule ssl_module>
        Listen 443

The whole point is to make Apache listen on the loopback interface, not the public address. This way haproxy gets the requests first so it can alter/direct them as needed.

The final part, which combines the MiME magic, is contained in the sites-availble directory which then of course is linked to the sites-enabled

<VirtualHost *:80>
        # The ServerName directive sets the request scheme, hostname and port that
        # the server uses to identify itself. This is used when creating
        # redirection URLs. In the context of virtual hosts, the ServerName
        # specifies what hostname must appear in the request's Host: header to
        # match this virtual host. For the default virtual host (this file) this
        # value is not decisive as it is used as a last resort host regardless.
        # However, you must set it for any further virtual host explicitly.
        ServerName dsotm.rabbitfoxtrot.uk

        ServerAdmin [email protected]
        DocumentRoot /srv/www/vpsland/low/DSOTM

        AddType audio/x-pn-realaudio            ram
        AddType audio/x-pn-realaudio-plugin     rpm
        AddType audio/x-realaudio               ra

        # Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
        # error, crit, alert, emerg.
        # It is also possible to configure the loglevel for particular
        # modules, e.g.
        #LogLevel info ssl:warn

        ErrorLog ${APACHE_LOG_DIR}/error/dsotm-error.log
        CustomLog ${APACHE_LOG_DIR}/dsotm-access.log combined

        # For most configuration files from conf-available/, which are
        # enabled or disabled at a global level, it is possible to
        # include a line for only one particular virtual host. For example the
        # following line enables the CGI configuration for this host only
        # after it has been globally disabled with "a2disconf".
        #Include conf-available/serve-cgi-bin.conf
    <Directory /srv/www/vpsland/low/DSOTM>
    Options Indexes FollowSymLinks MultiViews
    AllowOverride all
    Order allow,deny
    allow from all
    Require all granted
    </Directory>
</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet

So now I can still host a half dozen other websites on that Apache server but also have that special ‘front door’ access on port 8888 that’ll take notice of requests for .ra/.ram files and it’ll transform those into something more HTTP 1.1 which Apache will enjoy.

Playing!

And even better, it works.

At some point, like the boxed wine project we’ll hit the point where you can just 90’s internet 100% in the browser to other browser ‘server’ nodes. It’ll be awesome.

Build your own Apple DTK and save a fortune!

AI Slop

I am not a cool person.

I don’t get invited to all the cool people events.

I never get to bask in the glory of FOMO Apple glory.

It’ll be 20 years ago the rumors were insane that Apple was going to dump the beloved PowerPC for intel. Darwin (the open-source core to OS X) was publicly available on Intel processors, and the scene was set for one of the most exciting transitions of the time:

The WWDC 2005 announcement

At the 2005 WWDC the bomb was dropped.

The star of the show, of course is that the entire OS X 10.4 Tiger demo was on the intel machine, and for a low price of being invited, belonging to the club & $999 USD you too could be part of the next big FOMO.

So as the big reveal went, not only was OS X on intel now a thing, it “secretly” was always a thing, and had always been the escape hatch from being locked in. And it’s no surprise, it saved NeXT as the i386 “white box” was the cheapest and fastest NeXT ever, just as further transitions to 64 bit then ARM64 would necessitate.

So how does one reasonably acquire one of these mythical beasts, 20 years after the fact? Well basically unless you are a cool kid in the know, you don’t.

however as mentioned in a few places the ATK was quickly out together with standard parts. And if this sounds like the genesis of the IBM 5150 PC, you’d be right! The star of the show is the late SSE3 enabled Pentium 4 processors and the Intel 915 chipset with onboard GMA 900 video.

Thankfully intel sold these parts to whomever basically wanted them, so they were sold on a bunch of partner boards, OEM, and even Intel fabricated boards. It may be my fault as typical board/processor/ram setups can be had for £5 in the UK, the magical 915 chipset has jumped these well north of £100. However, from my searching there are a few OEM systems with the needed chips, and that is the Dell Dimension 3100.

She’s ugly, but she works!

Now I know I got lucky as I got mine for £0.99 + £9 shipping! A huge shout out to my patreons for financing stuff like this! The unit was shown in pictures absolutely filthy, missing an optical drive and “untested”. We all know that it’s code for it was tested, it didn’t work, and it wasn’t worth their time to clean up and fix.

Opening the system up, revealed an ancient mechanical SATA disk, a bunch of dust bunnies, and empty memory sockets where the ram should go. Since I had purchased 7 other boards over the last 2 years (yes! Really! 7!!!), I have ample spare ram, gave it 2x512MB sticks, a new cr2032 CMOS battery, and the hard disk failed to spin or detect, but the machine powered up, did the POST test with no issues! I’ve got to say I was super happy so far! I have a £6 SSD I picked up from CeX, so I placed that into the machine, and now for the OS install.

My first choice was to create a Linux bootable USB stick and just copy the deadmoo image to the SSD. Of course, this came with the caveat that the disk is in the VMDK format which needed to convert using Qemu’s qemu-img utility to a raw disk image, then to compress it with gzip, as the Ubuntu install image seems to only understand gzip. I guess the next pro move is to see about a static standalone iSCSI target, or maybe even rsync? I think there is even Qemu network disk protocols by now, so it may be a way to get around the lack of optical media…? Anyways!

The deadmoo image can be decompressed and copied to the hard disk easily! It’s about 2gb compressed and 6GB uncompressed. A reboot, and we’re quickly and semi glitched in as Curtis to their desktop!

there is a pre-installed driver causing issues which drops us back to the fallback SVGA buffer, and I’m happy to report that the artificing you see under emulation is also present on physical hardware. Delete the TPM driver AppleTPMACPI.kext (the root password is ‘bovinity’), and reboot again. This time there won’t be any further glitches in the video, but there is another change to make, the core graphics needs to be replaced with the SSE3 variant so that after yet another reboot again Rosetta is fully working, giving us access to PowerPC applications (that don’t require Altivec! That wouldn’t arrive until 10.4.4 just in time for the public release!). This lets the screen savers run, and important applications like iTunes and Internet Explorer 5 for Max OS X.

As Steve had demo’d it’s pretty amazing at how much just works. You can really get an appreciation at just how truly portable C is, and how LIBC is the real cross platform winner is. The company behind Rosetta transitive, had a bright future ahead of them as you can’t get a better public endorsement that Steve Jobs at a WWDC! SGi had licensed them for Itanium IRIX, and if the other Unix vendors didn’t partner there was also a Linux path. Honestly, I’m surprised SUN didn’t buy them and do the same thing as Apple and jettison the SPARC, as they can sell a LOT more 1u servers, desktops and laptops than giant E10ks, but IBM equally scared and trapped on their AIX / UnixWare Itanium merge Monterey that sold like 5 units, instead bought the company and quickly disappeared the technology.

What a shame for the industry, but x86_64 still is an unstoppable force. Well at least until someone seriously challenges them.

Getting back to OS X, this is meant for developers, and the deadmoo image has X code installed, although I prefer to use the cli tools. This is a weird time in history as many things may support OS X, but they make really bad platform assumptions, and force endian directions breaking the given stance of all OS X is big endian – even though Intel Darwin has been around the entire time.

Ive had good luck with stuff that is much later than vintage 2005, as I’m lazy and it’s 2025. The fun stuff id built were:

  • SDL 1.15
  • DOSBox SVN
  • Qemu 0.10
  • Classic Cube
  • ssystem-1.6

I had thought that the performance using GCC 3 would be better than GCC 4 for Qemu, but after a lot of work I’d benched it with DooM v1.1 that V4 is faster.

Ssystem-1.6

Compatibility with OpenGL games is atrocious, but I’m pretty sure by the time 10.4.4 went public compatibility was better, although I doubt contemporary machines did all that well as there is a reason there was a rush to get intel versions out.

Building your own:

Intel 915G/915GV/910GL PCI Accelerated SVGA BIOS

The primary ingredient here is a board with the Intel 910/915 Graphics chips, which limits us to the late Intel Pentium 4 boards, with that terrible integrated video. It’s not the best video chipset in the world, but the only one that 10.4.1 had 3d acceleration for.

Apple Development Platform ADP 2,1

I had found out that the Dell 3100 pre-built tower has the supported chipset & CPU, however it doesn’t have the correct onboard network card.

Intel Desktop Board D915PSY

The Intel LAN boards of the era with the 915 moniker & Pentium 4 should be fine enough.

I don’t know why they are so expensive either.

Although in the recent years these boards have gotten rather expensive. I can’t imagine why, as they absolutely suck for retro gaming as you’d 100% use a GPU, Other than 10.4.1 I can’t imagine why anyone would want a P4/915 combo.

While you could dd a deadmoo image onto 2 disks, then play partition games, it’s far easier to use the converted ISO with 10.4.1 to just boot up and install if that is an option.

If you don’t have a 910/915 based board, you can run this under emulation well enough. The weird graphical glitches you’ll experience are present on real hardware as well.

While not terribly useful, it is an interesting glimpse as at least x86 is available to the masses.

Hacking Windows NT 4.0 SMP for more than 2 CPUs

While trying to index a massive dataset, I found that the whole AltaVista process was kind of slow. And what better way to hope to speed it up, than to add some more processors. The catch is that the indexer only works on the Workstation SKU, and as such it’ll limit you to two processors. But what if one of those ‘one simple trick’ tricks actually can unlock all your cores?

From Rairii:

load the registry hive setupreg.hiv from i386 folder
in ControlSet001\Services\setupdd
default registry value
at offset 8, replace the value thats there (probably 00) with 20, ie decimal 32
then when reinstalling it should set up the registry to allow 32 cpus
ControlSet001-Services-setupdd

I didn’t want to bother trying to make a ‘correct’ NT 4 CD, so I just did the dos bootable disk image thing, and the old school “winnt /b” setup from within DOS on VMWare Workstation 17, and yeah behold!

NT 4.0 Workstation on VMware Workstation Pro 17

Although I should add that it’s not all sunshine and rainbows, it fails to boot about 50% of the time with a stop 0x00000078 PHASE0_EXCEPTION. I don’t know what is up with that, if it’s my AMD processor, something with VMware…

PHASE0_EXCEPTION

Sadly, for anyone wondering, it didn’t help my use case. The fundamental issue being that AltaVista tries to actually use filters to interpret the data, and Microsoft Word documents with the .txt extension, or anything with 90’s L337 ascii art just straight up trip up the indexer halting all progress.

ASCII ART BAD!!

Obviously, this is building up to something else, something I think is kind of exciting, but even as a ‘distraction’ adding more cores to NT 4.0 Workstation is always a good thing!

Expanding Windows drives

Granted this isn’t fun. But it’s virtual. We live in an era of exciting expandable disks!

first off start with a full disk. That’s great. In this case the host was proxmox. It doesn’t matter. You apparently have to shut down the VM to expand the disk. Other virtualizers don’t need that. But whatever.

now for the predictable fun. That recovery partition is in the way. Not sure why NTFS needs to be contiguous but here we are.

So now we are stuck. You Google how to expand NTFS drive, how to move recovery partition, how to make disk bigger!! All in vain as everyone is pushing these nifty partition mover programs that don’t run on server. Do you just nuke the partition and hope for the best?

No, we can back it up, delete it, expand our disk and recreate it!

Capture the partition

Dism /Capture-Image /ImageFile:C:\my-system-partition.wim /CaptureDir:S:\ /Name:"My system partition"
Microsoft DiskPart version 10.0.19041.3636

Copyright (C) Microsoft Corporation.
On computer: KOMPUTER

DISKPART> list disk

  Disk ###  Status         Size     Free     Dyn  Gpt
  --------  -------------  -------  -------  ---  ---
  Disk 0    Online          250 GB   200 GB

DISKPART> select disk 0

Disk 0 is now the selected disk.

DISKPART> list partition

  Partition ###  Type              Size     Offset
  -------------  ----------------  -------  -------
  Partition 1    Primary             50 MB  1024 KB
  Partition 2    Primary             49 GB    51 MB
  Partition 3    Recovery           522 MB    49 GB

DISKPART> select partition 3

Partition 3 is now the selected partition.

DISKPART> delete partition OVERRIDE

DiskPart successfully deleted the selected partition.

restore the partition

dism /Apply-Image /ImageFile:c:\my-system-partition.wim /Index:1 /ApplyDir:w:\

Then tag the partition back to being a Recovery partition.

DISKPART> list partition

  Partition ###  Type              Size     Offset
  -------------  ----------------  -------  -------
  Partition 1    Primary             50 MB  1024 KB
  Partition 2    Primary            249 GB    51 MB
  Partition 3    Primary            600 MB   249 GB

DISKPART> select partition 3

Partition 3 is now the selected partition.

DISKPART> set id=27

DiskPart successfully set the partition ID.

DISKPART> list partition

  Partition ###  Type              Size     Offset
  -------------  ----------------  -------  -------
  Partition 1    Primary             50 MB  1024 KB
  Partition 2    Primary            249 GB    51 MB
* Partition 3    Recovery           600 MB   249 GB

DISKPART>

depending on the version the command may fail setting type 27, so try:

set id="de94bba4-06d1-4d40-a16a-bfd50179d6ac" 

and there we go!

One big happy drive! 🎉