What is it
----------
Short version: 
A patch that enables 32bit IN/OUT operations in NTVDM, which seem to never 
been implemented by Microsoft causing some strange bugs when executing 
32bit code in NTVDM DOS, especially the VGA ROM BIOS that gets executed when
you are using fullscreen textmode operations.

Long version:
Recently I bought a new Mainboard with an Intel 4600 Onboard Graphics adapter
card. When starting the NTVDM on this machine, I encountered a 5-10 second 
delay on NTVDM startup. But that wasn't all, things got even worse when trying
to switch to Textmode Fullscreen. The character output was 1 char/second, 
which is obviously an unacceptable speed.
So I researched the Internet and found 2 threads in the product forums of
Microsoft and Intel:

https://social.technet.microsoft.com/Forums/lync/en-US/f4882880-d9eb-4ab0-bfca-e592b27fb86e/ntvdm-slow-startup?forum=w7itproappcompat
https://communities.intel.com/message/234383

Microsoft didn't care to answer to the problem at all and in Intel forums
someone from Intel just answered that their boards were not tested for 16bit
compatibility and therefore they don't care about the problem (which I cannot
really believe, as if that would be true, text output in BIOS would also not
work properly).
Some people in the Intel-forum reported that they now had to order a bunch
of new graphics cards just because of this bug!
The sheer ignorance of both companies regarding this problem really upset me 
and so it was my turn to investigate the problem and fix it, like many times
before :(
First, I needed to find out whom to blame for this mess. Intel or Microsoft?
As other VGA BIOSses worked well in NTVDM, I tended to blame Intel for it,
but after some investigation I found out I was wrong and it turned out to be 
an implementation problem on NTVDM, as they forgot to implement IN/OUT Port
support for 32bit ports which Intel uses in their VGA BIOS.
We could blame Intel for not doing their port check in the VGA BIOS on a
16bit register, but how should they know Microsoft didn't implement 32bit ports
in their NTVDM so that they have to work around it?
Anyway it doesn't hurt to add this feature to NTVDM, so this patch fixes
the missing functionality and therefore allows you to run NTVDM as expected
on your machine with Intel Graphics board.
For more details, see the Technical details section.

System Requirements
-------------------
Shuold work on any Intel PC with Windows XP and above.

How to Install
--------------
Go to the bin-Directory, right click on 32bitiofix.inf and click install.

How to Uninstall
----------------
Open command shell as Administrator (rightclick on Command Prompt in 
Start/Programs/Accessories, Run as Administrator)

Go to bin directory and then:
RUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultUninstall 132 32bitiofix.inf

Compiling
---------
You need the NTDDK Headers to compile.
The application was compiled using Microsoft Visual C 6.0
Just use nmake or use the VC 6 Project file.

Technical details
-----------------
Now how did I find out what was going on here?
Obviously it was a problem with the Video BIOS, as the real Video BIOS only
gets executed when the DOS application is running in full screen mode,
otherwise it just BOPs to the VGA emulation of the NTVDM for windowed
mode. So we know that INT 10h is responsible for doing Video IO.
Therefore I traced through a simple call to INT10h for outputting a
character with the excellent DEBUGX.COM utility from the FreeDOS project
(http://www.freedos.org/software/?prog=debug) which in contrast to
Microsoft's classic DEBUG.EXE also supports 32bit code like used in the
Intel VGA BIOS and many new BIOSes lately. I cut out the register content
view on places where it's not interesting for a better overview:

-rx
386 regs on  
-a
1628:0100 mov ax, 0e20
1628:0103 
-p
DPMI entry hooked, new entry=0F61:02BC
1628:0103 0000              ADD     [BX+SI],AL                       DS:0000=CD
-t =C000:0014 1000
EAX=00000E20 EBX=00000000 ECX=00000000 EDX=00000000 ESP=0000FFFE EBP=00000000
ESI=00000000 EDI=00000000 EIP=000028E8 EFL=00033286 NV UP EI NG NZ NA PE NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:28E8 FB                STI
C000:28E9 FC                CLD

....snip....

C000:28F9 8BEC              MOV     BP,SP
C000:28FB E8F72E            CALL    57F5

C000:57F9 66BE00540400      MOV     ESI,00045400
C000:57FF E88211            CALL    6984

C000:6984 51                PUSH    CX
C000:6985 B502              MOV     CH,02
C000:6987 6652              PUSH    EDX
C000:6989 6650              PUSH    EAX
C000:698B 2E8B161AE3        MOV     DX,CS:[E31A]                   CS:E31A=F000
C000:6990 668BC6            MOV     EAX,ESI
C000:6993 8AC8              MOV     CL,AL
C000:6995 24FC              AND     AL,FC
EAX=00045400 EBX=00000000 ECX=00000200 EDX=0000F000 ESP=0000FFCA EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=00006997 EFL=00033246 NV UP EI PL ZR NA PE NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:6997 66EF              OUT     DX,EAX	; Output 45400 on Port F000
EAX=00045400 EBX=00000000 ECX=00000200 EDX=0000F004 ESP=0000FFCA EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=0000699E EFL=00033246 NV UP EI PL ZR NA PE NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:699E 66ED              IN      EAX,DX	; Read from port F004 to EAX
EAX=0004FFFF EBX=00000000 ECX=00000200 EDX=0000F004 ESP=0000FFCA EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=000069A3 EFL=00033246 NV UP EI PL ZR NA PE NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:69A3 C0E103            SHL     CL,03	; Intersting, only AX is filled
C000:69A6 66D3E8            SHR     EAX,CL	; and not entire EAX??
C000:69A9 6692              XCHG    EAX,EDX
EAX=0000F004 EBX=00000000 ECX=00000200 EDX=0004FFFF ESP=0000FFCA EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=000069AB EFL=00033246 NV UP EI PL ZR NA PE NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:69AB 6658              POP     EAX
C000:69AD 8AC2              MOV     AL,DL
C000:69AF 80FD00            CMP     CH,00
C000:69B2 740A              JZ      69BE
C000:69B4 8BC2              MOV     AX,DX
C000:69B6 80FD01            CMP     CH,01
C000:69B9 7403              JZ      69BE
C000:69BB 668BC2            MOV     EAX,EDX
C000:69BE 665A              POP     EDX
C000:69C0 59                POP     CX
C000:69C1 C3                RET

EAX=0004FFFF EBX=00000000 ECX=00000000 EDX=00000000 ESP=0000FFD6 EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=00005802 EFL=00033202 NV UP EI PL NZ NA PO NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:5802 6683F8FF          CMP     EAX,-01	; Here it is compared with 
C000:5806 665E              POP     ESI		; FFFFFFFF, which fails!
C000:5808 6658              POP     EAX
C000:580A C3                RET

C000:28FE 0F848E00          JZ      2990	; So no jump here, instead it
C000:2902 E81E0B            CALL    3423	; continues....

...snip...

C000:343C E89D38            CALL    6CDC	; Not so important for us
C000:343F B9E803            MOV     CX,03E8	; Init loop counter with 1000!
C000:3442 E83F35            CALL    6984	; we already know function 6984
EAX=0004FFFF EBX=00000000 ECX=000003E8 EDX=80000000 ESP=0000FFD0 EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=00003445 EFL=00033202 NV UP EI PL NZ NA PO NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:3445 66A900000040      TEST    EAX,40000000; High word remains 0004
C000:344B 750A              JNZ     3457	; So we don't get out of here
C000:344D 51                PUSH    CX		; until loop is over
C000:344E B90100            MOV     CX,0001	; Sleep 1ms
C000:3451 E8842E            CALL    62D8	; Sleep routine
C000:3454 59                POP     CX
EAX=0004FFFF EBX=00000000 ECX=000003E8 EDX=80000000 ESP=0000FFD0 EBP=0000FFE0
ESI=00045400 EDI=00000000 EIP=00003455 EFL=00033216 NV UP EI PL NZ AC PE NC
DS=1628 ES=1628 SS=1628 CS=C000 FS=0000 GS=0000
C000:3455 E2EB              LOOPW   3442	; And it loops another 999 times
...


So first of all, what is this dubois Port at F000?
According to 2nd Generation Intel Core Processor Family Desktop Datasheet, 
Vol. 2 - Section: Device 2 I/O Registers:

F000 is the MMIO Address Register
F004 is the MMIO Data Register

So they are specific for the Intel-BOARD and are not available to our NTVDM
emulation and therefore cannot be accessed, always returning FF on read.
Now first thing that comes to our mind when just seeing the loop that loops
for 1000 times is why the VGA BIOS is assuming that these non-Standard 
I/O ports are there and causing a loop for 1000 iterations for every read 
attempt. That doesn't look very smart and is the cause for the huge delay.
But then we can see that it's actually checked above if these ports are 
there and if not (so if EAX is FFFFFFFF), they are not used and code should 
jump over. The check is perfectly fine, except that the IN to the EAX
register just fills the lower AX register and leaves the high word of the 
register untouched! So this leads us to believe that this is a NTVDM problem
and not a BIOS bug then.
When digging through the NTVDM, it can be seen that they just didn't implement
IN/OUT for 32bit registers but just 8 and 16 bit:

.text:0F0087D9 _EventVdmIo@0   proc near               ; CODE XREF: cpu_simulate():loc_F005347p
.text:0F0087D9                                         ; DATA XREF: .data:_EventDispatcho
.text:0F0087D9                 mov     eax, large fs:18h
.text:0F0087DF                 mov     ecx, [eax+0F18h]
.text:0F0087E5                 mov     ax, [ecx+5B2h]
.text:0F0087EC                 cmp     ax, 1			; VdmTib->EventInfo.IoInfo.Size = 1?
.text:0F0087F0                 jnz     short loc_F008836`	; No, jump
.text:0F0087F2                 cmp     byte ptr [ecx+5B4h], 0	; VdmTib->EventInfo.IoInfo.Read?
.text:0F0087F9                 jz      short loc_F008812	; No, jump
.text:0F0087FB                 lea     eax, [ecx+388h]
.text:0F008801                 push    eax
.text:0F008802                 xor     eax, eax
.text:0F008804                 mov     ax, [ecx+5B0h]
.text:0F00880B                 push    eax
.text:0F00880C                 call    _inb@8          ; inb(x,x)	; in 1 byte
.text:0F008811                 retn
.text:0F008812 ; ---------------------------------------------------------------------------
.text:0F008812
.text:0F008812 loc_F008812:                            ; CODE XREF: EventVdmIo()+20j
.text:0F008812                 mov     eax, large fs:18h
.text:0F008818                 mov     eax, [eax+0F18h]
.text:0F00881E                 movzx   eax, byte ptr [eax+388h]
.text:0F008825                 push    eax
.text:0F008826                 xor     eax, eax
.text:0F008828                 mov     ax, [ecx+5B0h]
.text:0F00882F                 push    eax
.text:0F008830                 call    _outb@8         ; outb(x,x)	; out 1 byte
.text:0F008835                 retn
.text:0F008836 ; ---------------------------------------------------------------------------
.text:0F008836
.text:0F008836 loc_F008836:                            ; CODE XREF: EventVdmIo()+17j
.text:0F008836                 cmp     ax, 2			; VdmTib->EventInfo.IoInfo.Size = 1?
.text:0F00883A                 jnz     short locret_F008871	; No, leave function
.text:0F00883C                 cmp     byte ptr [ecx+5B4h], 0	; VdmTib->EventInfo.IoInfo.Read?
.text:0F008843                 jz      short loc_F00885C	; No, jump
.text:0F008845                 lea     eax, [ecx+388h]
.text:0F00884B                 push    eax
.text:0F00884C                 xor     eax, eax
.text:0F00884E                 mov     ax, [ecx+5B0h]
.text:0F008855                 push    eax
.text:0F008856                 call    _inw@8          ; inw(x,x)	; in 1 word
.text:0F00885B                 retn
.text:0F00885C ; ---------------------------------------------------------------------------
.text:0F00885C
.text:0F00885C loc_F00885C:                            ; CODE XREF: EventVdmIo()+6Aj
.text:0F00885C                 call    _getAX@0        ; getAX()
.text:0F008861                 push    eax
.text:0F008862                 xor     eax, eax
.text:0F008864                 mov     ax, [ecx+5B0h]
.text:0F00886B                 push    eax
.text:0F00886C                 call    _outw@8         ; outw(x,x)	; out 1 word
.text:0F008871
.text:0F008871 locret_F008871:                         ; CODE XREF: EventVdmIo()+61j
.text:0F008871                 retn
.text:0F008871 _EventVdmIo@0   endp


So when reading the code, the interesting questin is why AX is fileld with 
FFFF and the register is even touched, when Size of I/O operation would be 4?
In this case, it shoudln't do anything at all.
When placing a breakpoint with the debugger, it can be seen, that even on a
4 byte IN/OU command, Size is 2.
So it seems that the Monitor-Code for V86 emulation in the Windows Kernel just
doesn'T support 4 byte IN/OU operations and therefore skips 0x66 prefix bytes
for such instructions.
Now this makes us think that it is really hard to patch this issue if the 
wrong size is being sent from the Monitor code and may probably be the cause
why it wasn't implemented? But fortunately, the VDM_TIB (Thread Information 
Block) also contains a field InstructionSize and for 4 byte IN/OUT operations,
the size is always 2 bytes instead of one (having the 66 prefix byte).
Therefore, everything that needs to be done is adding code to check for the
InstructionSize in EventVdmIo command and if it is 2 bytes, issue an ind and
outd command for double words, which in turn just means to read 2 words from
Port address and Port address+2 and cobine them in 1 dword.. Easy!
So this means that all the mess could have been prevented by just a few lines
of code that just weren't implemented, possibly because nearly no 
DOS-application did 32bit IN/OUT in ports anyway.

Now when trying to patch this issue, we can write a filter function that does
the missing checks and then place it in the EventDispatch table (first entry)
as a hook so that calls get redirected to our functions.
Unfortunately, these symbols are not exported as public symbols which means
that they need to be fetched from the Microsoft Symbol Server for resolution.
Now there are various different versions of the NTVDM out there with various
different offsets. As symbol resolution and download is complicated on its
own, I just made up a table with the various offsets of these offsets in 
different versions of Windows so that the fix can just look it up on start by
Windows version.
But there is another problem: EventVdmIo in newer versions of Windows starting
with version 8 inlies inw and outw function which we need to read and write
words on IO ports. So in this case, we need to emulate inw and outw behaviour
in our own code, which is fortunately as simple as calling the appropraite
function in the Ios_inw_function and Ios_outw_function tables. So in mentioned
Windows versions, these functions need to be resolved instead.

So the patch tries to consult its internal table according to the Windows
version, therefore it only works on Windows versions it knows
(at the time of writing, they are Windows 2000 - Windows 10 Build 14915).
You can add more offsets from symbols easily by editing the sym.bat in the
symget directory and point to the symbol directories.
The default fallback of the fox module is the Microsoft symbol server which
gets queried by symsrv.dll and gets called by dbghelp.dll library.
Therefore these DLLs should be in path of the fix dll so that they can be
called. Symbols will be stored in %TEMP%\SymbolCache directory, so unless
you delete this directory, no further fetches should be necessary on next
invocation. I haven't tested this feature yet, as most offsets are built
into the fix at the time of writing, but hopefully the fallback, which 
requires an Internet connection and the availability of symserv.dll also works
for unknown future versions of Windows.

For further information, just contact me.

Author
------
Dipl.-Ing. (FH) Ludwig Ertl
mailto: ludwig.ertl@dose.0wnz.at

You can get my other useful DOS-Utilities here:
http://www.waldbauer.com/tmp/reference.php

License
-------
This Application and Sourcecode may be distributed freely.
The Sourcecode is Licensed in Terms of the GNU GPL v3.
See: http://www.gnu.org/licenses/gpl.html

Thanks
------
Thanks to all the users who are still using DOS Applications ;)
