Switch to Windows 95
Saturday, February 3 2018, 14:10 Windows 95 Permalink
In November last year I wrote about the forgotten and obscure feature of early Windows 95 builds that lets you run Windows 3.1 in a window on Windows 95. Since then I was wondering if this would still work on the final build (950) of Windows 95, considering so much has changed since build 58s.
As mentioned in my previous post about this feature, it hasn't changed since build 81 (except for a branding updated right before its removal) and was removed between builds 328 and 331. Considering just how much Windows 95 has changed between builds 81 and 950 (or even between 81 and 328, for that matter), I honestly didn't expect this to work. But of course I had to try it anyway.
Setting up the system works much like it does with build 58s - install Windows 3.1 and 95 RTM in separate directories, copy the required files into proper locations and modify Windows 3.1's SYSTEM.INI to use special drivers that make this work. And when I tried to run it...
Can't do, you're not using Windows 4.0! Or are you?
As it turns out, WIN31.EXE performs a version check on Windows 95. These are usually relatively easy to bypass, so I decided to fire up IDA Pro and see what exactly is being checked and compared. You probably know Windows 95 reports version 3.95 to legacy (16-bit) Windows applications for compatibility reasons (I recommend you read this brief post by Raymond Chen to see why). However, this program wants Windows version 4.0. You see, early builds of Chicago simply reported version 4.0 to all applications. 3.95 wasn't introduced until build 122 or so, I think.
IDA Pro reveals the check is performed not once, but twice - first in the DOS section of the program, and then again in the Windows section. This is done with interrupt 2Fh function 1600h, which checks for the presence of Enhanced mode Windows. Aside from some special values for old versions of Windows, it otherwise returns Windows' major version in register AL and minor version in AH. This information is provided by Ralf Brown's Interrupt list, though it incorrectly claims Windows 95 returns version 4.0 via this function. If it did, WIN31.EXE would work and there would be no need for this post. The program then performs a compare (CMP) operation on the returned value in register AX and 4. If the two don't match, the program will quit with the error message you see above. Otherwise, it performs a short jump and continues. This is done with the "jump if zero" (JZ) instruction and that's where the problem is - Windows 95 RTM will return 3 in AL and 95 in AH, which together makes up the value 0x5F03 in register AX. If you're interested in the specifics of compare and conditional jump instructions on the x86, there's plenty of sources available online.
Left: the check in Windows section, right: the check in DOS section
Luckily, this is easy to fix. All you have to do is replace the "jump if zero" instruction with "jump if not below or equal/above" (JNBE aka JA), which will in our case perform the jump to the specified address for all AX values greater than 4. Alternatively, a regular jump (JMP) can also be used, which ignores the reported version entirely and always performs jump. Patching with JMP instead of JNBE would make this program work on all builds, as using JNBE prevents it from starting on early builds that report version 4.0.
As mentioned above, this has to be done twice, since there are two checks. The opcodes of both can be found in IDA's hex view. They're immediately followed by JZ, the opcode is 0x74. Opcode for JNBE is 0x77, for JMP it's 0xEB. A hex editor, such as HxD, can be used to find and replace the two relevant values.
Left: patched conditional jump in Windows section, right: patched conditional jump in DOS section
After replacing the original WIN31.EXE with our patched copy and running it, Windows 3.1 will start as expected, with an icon for Switch to Chicago on the desktop. Clicking it takes us back to Windows 95 (basically it minimizes the window in which Windows 3.1 is running). Interestingly, when running WIN31.EXE on Windows 95 RTM, the control panel in Windows 3.1 doesn't cause a GPF in DUALMOUS.DRV like it does on build 58s.
Works like a charm after the patch
Thanks to Rai-chan from RoL for pointing WIN31.EXE performs two version checks.
Update on 26th November 2020: I made some minor updates to the post in light of build 328 leak.
Followed your instructions, I run Windows 3.1 in Windows 95 and 98. But two glitches I found.
First is this can execute Windows 3.1 English Version only (may not, but I am a Chinese, I only test Windows 3.2 Chinese and 3.1 English.), replace "system\user.exe" in 3.2 by 3.1 English Version, it works, but Chinese characters doesn't show correctly.
(By the way, I tried replace it by other versions. 3.1 Chinese doesn't work, 3.1 Japanese doesn't work, 3.1 Trad-Chinese doesn't work, 3.1 Korean works but crashed.)
Second is run this in Windows 98, the "return to Chicago" icon doesn't work. If you double-clicked this icon, you will get a black screen, press Alt+Tab again, you will back to Windows 3.1.
Call me silly or tell me to do my own experiment, but I have not myself been able to get Windows 3.1 or Windows 95 running under Hyper-V - not yet. This is frustrating since I have a relevant 20-something year-old screen-shot and no notes about how I got it. See https://www.geoffchappell.com/notes... Of course, back then I had the ease of these things working on physical machines.
If you can test easily, what happens if you open an MS-DOS Prompt, change directory to your Windows 3.1 installation and run SYSTEM\KRNL386?
There may be more to it, but I can't help thinking there won't be much.
Put another way, the better hacking may be less about making a pre-release WIN31.EXE work on the retail Windows 95 than about finding why you need WIN31.EXE at all.
Since first reading your page, I've been puzzled at how you can have seen int 2Fh function 1600h return 5F03h in ax. The VMM from Windows 95 cannot return this. The page that you cite by Raymond Chen is about a Windows API function, not int 2Fh function 1600h.
Ralf Brown's list is not incorrect about 0004h as the return from the Windows 95 VMM, ordinarily, but it does miss that the function can instead return 0A03h or leave 1600h unchanged. If you got 5F03h, some other software must be involved.
I propose a different reason that your WIN31.EXE fails its version check. It's because of WIN31.PIF. If you inspect this PIF on the retail Windows 95, you'll find that it has the "Prevent MS-DOS-based programs from detecting Windows" property. It's not meant to, but the definition of some PIF flags changed during development. Because this property is set in the PIF, int 2Fh function 1600h leaves 1600h in ax and the EXE then fails the version check.
Editing the PIF is surely preferable to patching the EXE.
If this theory is correct, the bigger question is: why use the PIF? Why even use the EXE? Or the drivers? If the feature is interesting after all these years, isn't it worth finding out how it works?