VOGONS

Common searches


First post, by LSS10999

User metadata
Rank Oldbie
Rank
Oldbie

Recently I've been experimenting with various apps and games on Windows NT 3.51 SP5 to find out how compatible it could be.

I'm currently running it on a VirtualBox VM, but I intend to run it on actual machines when ready, thanks to the guides on this thread.

So far, NT 3.51 is indeed readily able to run a lot of 32-bit stuffs, comparable to that of Win95 and NT4, but it does lack some essential APIs in comparison. While most of the results I could achieve are similar to the information on bearwindows, there might be more stuffs that could be made work on NT 3.51.

1. CPU and RAM limits

As tested with VBox, NT 3.51 can handle up to 8 CPU cores/threads and ~4GB RAM just fine. One needs to configure this registry variable to enable more CPU cores/threads (the default is 2 for Workstation). This corresponds to bearwindows' information. It seems bearwindows had a private kernel patch that could allow using even more CPU cores/threads (the theoretical limit is 32), but little is known about that, though 8 cores/threads are more than enough for most use cases.

UPDATE: Sadly it looks like the MPS 1.1 HAL may not work on some real hardware configurations. For now multi-core is only something achievable with VBox as it emulates a complaint system... You may have a bit more luck with Intel CPUs/chipsets as the vendor string "GenuineIntel" was being checked on several different occasions.

PS: After some searching looks like it's okay for an ACPI system to have invalid MP tables (thus not MPS complaint). In this case, the issue might be a per-motherboard basis.

2. General Incompatibilities

The following general incompatibilities apply for NT 3.51. These are the major blockers, and this does not cover programs that don't run for other reasons (like "Cannot open program").
- Explicit SHLWAPI.DLL dependency (and related stuffs).
SHLWAPI.DLL from 32-bit Internet Explorer 4 and onwards cannot function due to the absence of QueueUserAPC which is only available since 95/NT4. This is the case with SHLWAPI.DLL from IE 4.0 up to IE 5.5, and is the only missing symbol. IE 6 version of the library appears to import a lot more missing symbols, which kind of explained the rationale behind the dropping of Win95 support.
Such software generally shipped with newer Internet Explorer versions for install on Windows 95/NT4, and openly stated "IE4 or higher installed" as system requirement.
Note that while some software itself doesn't depend on SHLWAPI.DLL, it might have optional libraries/plugins that do. In that case you'll get errors about SHLWAPI.DLL but otherwise can continue using the program as long as you don't use features that actually involve the libraries/plugins in question.

UPDATE: It seems SHLWAPI.DLL existed since IE 3.x along with WININET.DLL (4.70.xxxx). While NT4 versions of IE3 existed, trying to use SHLWAPI.DLL from IE3 gave me the "not a valid Windows NT image" error, despite it reported no errors from Dependency Walker. Not sure if the matching version of WININET.DLL would work or not.

- Use of DirectX (Designed for Windows 95)
Software/Games that make use of DirectX will usually not work, and in many cases you'd be explicitly blocked from installing (citing unsupported OS).
It seems DirectX is what really made Windows 95 different from the NT family during that period, as these games usually claimed to be "Designed for Windows 95" and nothing else. Some also supported NT4 SP3 which included DirectX 3, though.
An exception to this would be games that can be run optionally using OpenGL or pure software rendering, and Unreal Engine was a popular example. However, some games (or installers) also explicitly check about OS version (>=4.0), and that needs to be worked around as well.

EDIT: I walked over a good amount of DirectX 8.0a DLLs (last Win95 supported version) and found that almost all DirectSound/DirectMusic 8.0a libraries have no dependency issues on NT 3.51 and might be able to get it working to some extent if there's a trivial method to insert all the necessary registry values in the respective INF files (dsound.inf/dmusic.inf).
DirectInput 8 can't work (dependency issues), but a portion of DirectPlay 8 DLLs may be usable.

Additionally, there's an alternative DDRAW called DDHACK, and this German forum has a custom build of it that can run on NT 3.51. You need a usable Mesa 3D library for it to work, though. Walking dependencies with this DDHACK present resolves dependencies with DirectX 3 D3DIM.DLL and up to DirectX 8 D3DRM.DLL. I haven't tested this in detail and it doesn't appear to work correctly on VM, maybe it can work with VBEMP on physical systems to some extent.

Should note that the discovery might also benefit Windows NT 4.0 (preferrably SP6a) to some extent. NT4 has obviously more APIs than 3.51 and should theoretically be able to run a lot more DirectX 8.0a libraries than 3.51, contradicting what M$ officially stated at that time (no new DirectX was ever officially released for NT4 after all).

- 64-bit integer arithmetic
Right now this problem is mostly encountered in device drivers. NT 3.51 doesn't natively support this in kernel and NTDLL, but NT3.51 DDK had a static library for the purpose. The symbols were introduced since NT4, and with NT4 DDK there's a chance the produced driver binary may accidentally import them. While it may be worked around when building the drivers, I'm not sure if it's possible for existing driver binaries to work around this issue.
A while ago I examined several audio cards' NT4 drivers and found quite a few have imported _alldiv mainly in their DLL portion. Most drivers' .sys parts do not import this symbol, fortunately. Taking Aureal's drivers (AU88NT30.DLL) for example, initial inspection with Ghidra revealed the _alldiv was introduced in a MIDI-related subroutine where it was trying to divide something by 10000 and that was the only place _alldiv was used. Aside from _alldiv, some other drivers may involve different symbols.
Interestingly, similar issues also affected UniATA since v0.47. The driver imported those symbols and not usable on 3.51, and building with NT4 DDK as instructed always produced a binary importing those missing symbols. I was able to work that around by substituting the LIB folder of NT4 DDK with that from NT3.51 DDK when building the driver, which produced a binary that doesn't import symbols not present on 3.51 and can be used there.

- USB Input Issues
NT 3.51 doesn't have a USB stack so USB input devices will behave just the same as they would in DOS (or other places that don't have USB stack drivers), if you have Legacy USB Support enabled in BIOS.
While USB keyboards can work fine, USB mice cannot work when using MPS Multiprocessor PC. See this thread (also on MSFN).
Should note that the behavior of USB input devices depends entirely on the devices' own legacy emulation capabilities. If the USB keyboard or mouse in question misbehaves or does not work at all in DOS, then the same will apply to NT 3.51.

3. Font related issues

Windows NT 3.51 installs fonts to C:\WINNT35\SYSTEM (depending on your installation path) unlike newer Windows, but it appears some software are hardcoded to look for fonts at C:\WINNT35\FONTS which in this case would throw errors (such as failing assertions). In that case, simply create a FONTS folder and make a copy of the fonts already present in SYSTEM there should be enough to address it. Be sure to sync the fonts in both directories so all programs can correctly access them.

An example of this issue is the Modern skin of Winamp (up to 5.13). After addressing this issue I can use Modern skin without major issues, although some non-essential libraries may throw errors due to other incompatibilities, including SHLWAPI.DLL dependency.

UPDATE: It seems there isn't a convenient way to properly install fonts on NT 3.51 unlike newer Windows, where you can install and register new fonts to the system just by pasting into the FONTS folder. The fonts have to be added into the registry by hand.

4. Some minor glitches

It seems there are some other issues with NT 3.51 APIs that could cause programs to misbehave, but not critical enough to lead to crashes.

For example, Total Commander (up to latest version, 10.0) 32-bit can run on NT 3.51, but only the upper left part (about 1/4) of its dialog windows (such as configuration) is visible for some reasons, so it can't be easily used. One can still make configuration through INI file, though. (EDIT: This, as well as some other graphical glitches, may be somewhat related to video drivers, so your mileage may vary on different physical systems or VM hosts)

Also, while Dependency Walker (which I've been using extensively to inspect program compatibility) works fine on NT 3.51, I always have to drag and drop the files I want to inspect (from File Manager), because its open dialog doesn't work for some reasons (nothing happens when I clicked Open).

5. Development

Refer to this for VC++ history as well as versions that can be run or target Windows NT 3.51.
Additionally, it appears that Open Watcom 1.9/2.0 x86 can also be installed and used for Windows NT 3.51 development.
DOSBox Compilation Guide may contain hints on how to target NT 3.51 with MinGW as well as making compatible SDL-based programs.

6. Possible NTVDM issues

While NTVDM has been proven sufficient to run a good amount of 16-bit software and games, there indeed are a few that don't work, though rare in overall.

Spiderweb's Exile Trilogy is one of such cases I discovered: Exile 1 and 2 can't run (causes an exception in the Win16 subsystem). Exile 3 and Blades of Exile runs okay. Additionally, it seems Exile 2 and 3 would change the current titlebar color to something gray-ish upon startup (despite Exile 2 can't run). The change of titlebar color is permanent so I have to manually change it back from Control Panel.

Probably something is missing or flawed in NT 3.51's VDM implementation that has been addressed in later Windows versions, as when I tested the games on Windows 2000 both ran okay there. Additionally, Exile 2/3's titlebar color change does not affect the rest of the system on Windows 2000.

7. SSE Support

A while ago I found this interesting medium post about getting a C# program running on as low as NT 3.51 (using an experimental .NET feature called CoreRT, now NativeAOT). During the progress he encountered an issue that there were SSE opcodes in the executable, which NT 3.51 cannot run out-of-box, as the CPU needs to have some registers set before it could run SSE instructions, and that requires kernel mode code (such as drivers). After setting those registers, he was able to properly run the test program.

Actually he modified the VBEMP driver to inject the SSE initialization code, though if using VirtualBox, it's also possible to insert the initialization in the boxvnt driver as inline assembly. The boxvnt driver can be compiled using Open Watcom 1.9/2.0 and the build process is straightforward.

I suspect the "Cannot Run Program" error is probably due to the presence of SSE/SSE2 instructions in the program. It was already known that many development toolchains started enforcing SSE2 requirements at some point.

NOTE: Sadly this trick only works for single core/thread scenario as it only changes the registers on a single core/thread. If running NT 3.51 with 2 or more cores/threads, this will NOT work, as you need to somehow write your own driver to set the registers across ALL cores/threads.

I'm still new to driver development and I've been looking for documentations on how this can be done on NT 3.51. Linux has this on_each_cpu function that could do the job, but I haven't found anything similar for NT 3.51 yet.

On the other hand, Windows NT4 has a intlfxsr.sys driver that does this, but it is not compatible with NT 3.51, and it appeared to have a hard GenuineIntel check, which roytam1 managed to have it removed so it can be used on any x86 CPU.

8. OS Version checks within software or runtime libraries

After some experiments I think I should indeed put this just in case. Some software or runtime libraries actively check your system version with varying extents, even though the executables or libraries involved have no dependency issues. As a result, care must be taken when attempting to use a higher version of a given library and be prepared to restore the known good version should anything goes wrong.

Simple ones just check if your system version is >= 4 which is enough to just bar NT3.51 from using it. Complex ones might also check your system minor version, build number and service pack level.

If Dependency Walker reports no issues then it should be trivial to work around by hooking the executable or library in question into a debugger/disassembler, look for version related checks such as comparisons against dwMajorVersion, and adjust (hex-edit) those comparisons accordingly to work around the version check (may also need to use EDITBIN to recalculate checksum just in case).

I can confirm that MSVBVM60.DLL will work on NT3.51 after modification. Previously it checks against 4.00.1381 Service Pack 3 for WinNT platform.

Windows Installer 1.x (from Office 2000/XP) can theoretically run on NT3.51 as it has no dependency issues, but its installer (MSIINST.EXE) contained a OS major version check, and it doesn't seem to be able to correctly write into the registry even after working around it by hex-editing. The registry entries as well as file association might have to be done by hand... maybe inspecting how the installer works from a NT4 environment could provide some useful hints.

Windows Installer 2.0, however, cannot work on NT3.51 due to a missing import NdrClientCall2 from RPCRT4.DLL. There was a MSFN question about this that didn't appear to have gone anywhere.

NOTE: NewShell apparently modified the version reporting mechanism to make Windows report a 4.00 version instead of 3.51. How it achieved this needs further investigation but that should be enough to bypass basic (major only) version checks without hex-editing anything (untested though). Complex checks may still fail as they could require you to be at build 1381 (real NT4). While NewShell does work as intended on NT3.51, I don't really recommend this as an actual NT4 would do much better.

EDIT: As there are now a lot of stuffs in the OP I'm refactoring it using spoilers for better readability.

Last edited by LSS10999 on 2023-02-21, 01:53. Edited 22 times in total.

Reply 1 of 9, by DosFreak

User metadata
Rank l33t++
Rank
l33t++

For the DOSBox Compilation Guides it's important to keep it mind that both MinGW (i386) and MinGW-W64 (i686) can compile for NT 3.5x as long as you use win32 thread which Mingw-64 on Linux is by default. MSYS2 mingw-w64 from pacman is posix so you'll need to download the win32 ver from their website. Also beware using the pacman or vcpkg repos since likely everything else is compiled with posix.
If you want to compile a working executable for <i686 with Mingw-W64 you'd need to recompile mingw-w64. I never got around to it but it's been done.
Also for MinGW-W64 you'll need to use a newer msvcrt.dll than the one that comes with the OS for XP and below.

For SSE on NT3.51 this may provide a clue, doesn't seem like there's much to it:
https://medium.com/@MStrehovsky/doom-fire-eff … 51-fad6ee839345

Currently redoing 2 of my offsite NAS for with ZFS instead of ext4 (bleh) and experimenting with hardware solutions for EDID and my 980ti on XP so I can hopefully get HDMI working with scaling and/or DP forced at 60hz so I don't have to jump through hoops with software solutions so vogonsorg github is currently on the backburner but these kinds of things are on my list.

How To Ask Questions The Smart Way
Make your games work offline

Reply 2 of 9, by LSS10999

User metadata
Rank Oldbie
Rank
Oldbie
DosFreak wrote on 2022-02-22, 21:09:

For SSE on NT3.51 this may provide a clue, doesn't seem like there's much to it:
https://medium.com/@MStrehovsky/doom-fire-eff … 51-fad6ee839345

Yes, I did refer to this (as well as the OSDev page) about enabling SSE on NT 3.51. The asm example only works when using one processor core/thread.

If these registers are correctly set, the system should be able to execute SSE instructions without issues, which can extend compatibility a bit, but you still have to deal with dependencies and missing symbols.

The problem is how to execute these instructions on each processor core/thread available to the OS (if 2 or more processor cores/threads). I'm still looking in to the NT 3.51 DDK for clues.

Reply 3 of 9, by javispedro1

User metadata
Rank Member
Rank
Member
LSS10999 wrote on 2022-02-22, 07:21:

During the progress he encountered an issue that there were SSE opcodes in the executable, which NT 3.51 cannot run out-of-box, as the CPU needs to have some registers set before it could run SSE instructions, and that requires kernel mode code (such as drivers). After setting those registers, he was able to properly run the test program.

Actually he modified the VBEMP driver to inject the SSE initialization code, though if using VirtualBox, it's also possible to insert the initialization in the boxvnt driver as inline assembly.

Heh. Nice hack, but note it will still be a hack. The entire point of preventing SSE instructions from being executed on non-SSE aware operating systems is because SSE is adding extra registers that need to saved during context switch (see https://en.wikipedia.org/wiki/Streaming_SIMD_ … sions#Registers , FXSAVE).
If the operating system says it is SSE aware but does not save these registers (that is what will happen with this patch), then mayhem will happen once you start any two programs using SSE simultaneously...
Likewise, if the operating system decides to migrate a thread from one CPU to another in a SMP build, the program is going to crash or give incorrect results because the SSE registers will not be migrated alongside.

i.e. The hack is only going to work if you have one processor and only one task using SSE. Anything else and you risk crashes, silent data corruption or worse.

Reply 4 of 9, by LSS10999

User metadata
Rank Oldbie
Rank
Oldbie
javispedro1 wrote on 2022-02-23, 12:53:
Heh. Nice hack, but note it will still be a hack. The entire point of preventing SSE instructions from being executed on non-SSE […]
Show full quote

Heh. Nice hack, but note it will still be a hack. The entire point of preventing SSE instructions from being executed on non-SSE aware operating systems is because SSE is adding extra registers that need to saved during context switch (see https://en.wikipedia.org/wiki/Streaming_SIMD_ … sions#Registers , FXSAVE).
If the operating system says it is SSE aware but does not save these registers (that is what will happen with this patch), then mayhem will happen once you start any two programs using SSE simultaneously...
Likewise, if the operating system decides to migrate a thread from one CPU to another in a SMP build, the program is going to crash or give incorrect results because the SSE registers will not be migrated alongside.

i.e. The hack is only going to work if you have one processor and only one task using SSE. Anything else and you risk crashes, silent data corruption or worse.

Yeah, looks like a lot more to consider. The hack at least proved SSE can work, if using only one processor core/thread.

Maybe INTLFXSR.SYS (that came with newer NT4 service packs) took what you mentioned (like FXSAVE/FXRSTOR) into consideration, but that driver obviously cannot be used on 3.51 as it depends on some missing symbols. The question would be whether or not the same functionalities can be achieved with what 3.51 could offer.

On the other hand, I came across this thread which is about some GUI modifications. The thread is mainly about changing some hardcoded color schemes, but keeping core UI behaviors as-is. It appears some newer usable DLLs were involved to achieve the purpose, though there are probably more DLLs from newer Windows versions that can work here.

Reply 5 of 9, by javispedro1

User metadata
Rank Member
Rank
Member

Do you know what kernel symbols are missing ?

I can see that FXSAVE opcode is present in INTLFXSR, so my wild guess is that NT4 has a private API that allows setting a task switching callback . This would be very heavyweight so I rather doubt they would put this API in NT3 for no reason, but it does not hurt to try...

Reply 6 of 9, by LSS10999

User metadata
Rank Oldbie
Rank
Oldbie
javispedro1 wrote on 2022-02-24, 14:27:

Do you know what kernel symbols are missing ?

I can see that FXSAVE opcode is present in INTLFXSR, so my wild guess is that NT4 has a private API that allows setting a task switching callback . This would be very heavyweight so I rather doubt they would put this API in NT3 for no reason, but it does not hurt to try...

Here are the missing symbols from NTOSKRNL 3.51, that INTLFXSR imported:

ExInterlockedPopEntrySList
ExInterlockedPushEntrySList
KeSetAffinityThread
KeSetSwapContextNotifyRoutine
PsSetCreateThreadNotifyRoutine
PsSetLegoNotifyRoutine

Looks like some APIs related to threads and context switching.

I recall seeing documentations mentioning KeSetAffinityThread (and its newer derivatives) to specify which CPU core a thread is allowed to run. This function looks essential to ensure configuring the registers necessary to enable SSE on all CPU cores.

Since KeSetAffinityThread doesn't exist on NT 3.51 I've been looking for alternatives that can be used, to configure the registers on all CPUs, but still nothing really helpful and reliable.

Reply 7 of 9, by javispedro1

User metadata
Rank Member
Rank
Member

KeSetSwapContextNotifyRoutine sounds like the one which sets the task switching callback (which will run FXSAVE). Sadly the fact it is lacking may be a dealkiller.

LSS10999 wrote on 2022-02-24, 16:15:

Since KeSetAffinityThread doesn't exist on NT 3.51 I've been looking for alternatives that can be used, to configure the registers on all CPUs, but still nothing really helpful and reliable.

If necessary you could do the "for_each_cpu" ourely on userspace and just call into the driver each time (e.g. via an ioctl or the like) which would set the flag on the current CPU. However without the ability to hook into the task switch, I don't think this will be stable at all.

Reply 8 of 9, by LSS10999

User metadata
Rank Oldbie
Rank
Oldbie
javispedro1 wrote on 2022-02-24, 17:55:

KeSetSwapContextNotifyRoutine sounds like the one which sets the task switching callback (which will run FXSAVE). Sadly the fact it is lacking may be a dealkiller.

If necessary you could do the "for_each_cpu" ourely on userspace and just call into the driver each time (e.g. via an ioctl or the like) which would set the flag on the current CPU. However without the ability to hook into the task switch, I don't think this will be stable at all.

Windows NT 3.51 does support using more than one processor core/thread... but it looks very basic given the absence of several essential functions which were only introduced since 4.0.

Still, NT 3.51 is pretty much something between Win3.x and Win95/NT4, with a good portion of the latter's functionalities, while retaining most (if not all) of the former's glory.

Reply 9 of 9, by LSS10999

User metadata
Rank Oldbie
Rank
Oldbie

A little bump on this as I updated the OP for a few more things.

Apparently some 16-bit games don't work correctly with NT 3.51 NTVDM. In my case, I found that Exile 1 and 2 causing exceptions to the Win16 subsystem, and the error message dialog can't be normally closed for some reasons.

Close and Ignore buttons do nothing. Cancel button is supposed to bring it to a debugger, but nothing happens after that. I cannot run another Exile instance from now on (until I reboot), as NTVDM would complain that I cannot run more than one instance of such a program, indicating the previous NTVDM process, which may or may not be defunct, is still there.

I tried looking for the NTVDM process in the Task List, but it did not show up in the list. The error seems to be much deeper than I expected.

So far this is the first and the only case I encountered that a 16-bit software/game would not run correctly on NT 3.51. The games work correctly on Windows 2000's NTVDM as far as I've tested... so there probably are issues with older NTVDM versions that are corrected in later Windows versions.

PS: It's not possible to run NTVDM from newer Windows versions here, as newer NTVDM imports additional symbols not present on NT 3.51.

UPDATE: If you use a newer version of IMAGEHLP/PSAPI (up to Win2K ones) you will additionally need to replace DRWTSN32.EXE with the NT4 version, as the original NT3 version imports certain symbols that no longer exists with newer IMAGEHLP anymore, breaking system-wide error reporting, which might be related to some of the issues with error display (the system tried calling DRWTSN32 but due to the missing import, that failed as well and the system doesn't know what to do next).

EDIT: Exile 2/3 appears to change the system titlebar color on startup. On NT 3.51 this titlebar color change is global and permanent (and must be reverted manually from Control Panel), but on Windows 2000 only the game window's titlebar color gets changed (the color for the rest of the system remains unchanged). May have to check whether the related API changes were documented or not. The titlebar color change only affects the first color (the left part of the gradient). The second color (the right part of the gradient), first introduced in Win98, uses a different API and is not involved here.