3 * Windows NT VDD driver, dynamically loaded by code that needs it
4 * (C) 2009-2012 Jonathan Campbell.
5 * Hackipedia DOS library.
7 * This code is licensed under the LGPL.
8 * <insert LGPL legal text here>
10 * This driver when loaded allows the DOS program to call Windows NT APIs
11 * such as version information or to use the WINMM WAVE API instead of
12 * NTVDM.EXE's shitty Sound Blaster emulation.
15 /* This is a Windows NT VDD driver */
16 #define NTVDM_VDD_DRIVER
31 #include <hw/cpu/cpu.h>
32 #include <hw/dos/dos.h>
33 #include <hw/dos/dosbox.h>
34 #include <windows/ntvdm/ntvdmlib.h>
35 #include <windows/ntvdm/ntvdmvdd.h>
39 /* this is a DLL for Win32 */
40 #if TARGET_MSDOS == 16 || !defined(TARGET_WINDOWS)
41 # error this is a DLL for Win32 only
44 static HMODULE this_vdd = 0;
46 /* If the DOS portion of this code calls NTVDM.EXE to load the same DLL again, NTVDM.EXE
47 * will do it, and allocate another handle. We'd rather not waste handles, so we leave
48 * a "mark" in the BIOS data area for the DOS program to find and know whether we're
49 * already loaded, and what handle to use. */
50 static unsigned int vm_marked = 0;
52 /* since 32-bit DOS builds must thunk to 16-bit mode to make calls, we use "fake" I/O
53 * ports to control other aspects such as sound output to make it easier for the DOS
54 * portion's interrupt controller to do it's job. But we never assume a fixed I/O port
55 * base because, who knows, the user's NTVDM environment may have special VDD drivers
57 static uint16_t vm_io_base = 0;
58 #define VM_IO_PORTS 0x20
60 /* +0x00 (WORD) W/O Function select. */
61 /* +0x01 (WORD) W/O Sub-function select. */
63 /* Interrupt handlers may use these, to save the current state and use the interface themselves
64 * before restoring the interface for the code they interrupted.
66 * The idea being the DOS program should not have to CLI/STI all the time to avoid conflicts with their
67 * own interrupt handlers. */
69 /* +0x00 (INSB) R/O Read current function/subfunction selection [24 bytes] */
70 /* +0x00 (OUTSB) W/O Write current function/subfunction selection [24 bytes] */
74 uint16_t function; /* +0x00 */
75 uint16_t subfunction; /* +0x02 */
76 uint16_t __reserved__1; /* +0x04 */
77 uint16_t __reserved__2; /* +0x06 */
78 uint16_t __reserved__3; /* +0x08 */
79 uint16_t __reserved__4; /* +0x0A */
80 uint16_t __reserved__5; /* +0x0C */
81 uint16_t __reserved__6; /* +0x0E */
82 uint16_t __reserved__7; /* +0x10 */
83 uint16_t __reserved__8; /* +0x12 */
84 uint16_t __reserved__9; /* +0x14 */
85 uint16_t __reserved__A; /* +0x16 */
89 WORD (*iop)(WORD param0);
90 void (*insb)(BYTE *data,WORD count);
91 void (*insw)(WORD *data,WORD count);
92 void (*outsb)(BYTE *data,WORD count);
93 void (*outsw)(WORD *data,WORD count);
97 /* IO interface state */
99 IOIF_COMMAND* io_if_command=NULL; /* what to do on command */
101 /* What the fucking hell Watcom?
102 * This function obviously never gets called, yet if I don't have it here
103 * your linker suddenly decides the standard C libray doesn't exist? What the fuck? */
104 BOOL WINAPI DllMain(HINSTANCE hInstance,DWORD fdwReason,LPVOID lpvReserved) {
105 /* FIXME: If Watcom demands this function, then why the fuck isn't it getting called on DLL load?
106 * What the fuck Open Watcom?!? */
107 if (fdwReason == DLL_PROCESS_ATTACH) {
108 this_vdd = hInstance;
114 /* DOSNTAST_FUNCTION_GENERAL------------------------------------------------------------ */
115 static void ioif_function_general_messagebox_outsb(BYTE *data,WORD count) {
116 if (io_if.subfunction == DOSNTAST_FUN_GEN_SUB_MESSAGEBOX) {
117 /* whatever ASCIIZ string DS:SI points to is passed by NTVDM.EXE to us */
118 MessageBoxA(NULL,data,"DOSNTAST.VDD",MB_OK);
122 static IOIF_COMMAND ioif_command_general_messagebox = {
123 /* If only Watcom C/C++ supported GCC's .name = value structure definition style,
124 * like what they do in the Linux kernel, we could make this a lot more obvious */
128 ioif_function_general_messagebox_outsb, /* outsb */
132 /* this is called when Function == general and selection changes */
133 static void ioif_function_general__SELECT() {
134 switch (io_if.subfunction) {
135 case DOSNTAST_FUN_GEN_SUB_MESSAGEBOX:
136 io_if_command = &ioif_command_general_messagebox;
139 io_if_command = NULL;
143 /* DOSNTAST_FUNCTION_WINMM-------------------------------------------------------------- */
144 static WORD ioif_function_winmm_waveOutGetNumDevs_iop(WORD param0) {
145 return (WORD)waveOutGetNumDevs();
148 static IOIF_COMMAND ioif_command_winmm_waveOutGetNumDevs = {
149 /* If only Watcom C/C++ supported GCC's .name = value structure definition style,
150 * like what they do in the Linux kernel, we could make this a lot more obvious */
151 ioif_function_winmm_waveOutGetNumDevs_iop,/* iop */
158 static void ioif_function_winmm_waveOutOpen_outsb(BYTE *p,WORD count) {
159 /* in: EAX = uDeviceID
160 * EBX = dwCallbackInstance
161 * DS:ESI = pwfx (WAVEFORMATEX*)
162 * out: EAX = handle (or 0xFFFFFFFF if error)
169 static IOIF_COMMAND ioif_command_winmm_waveOutOpen = {
173 ioif_function_winmm_waveOutOpen_outsb, /* outsb */
177 static void ioif_function_winmm_waveOutGetDevCaps_insb(BYTE *p,WORD count) {
179 * EBX = cbwoc (sizeof of WAVEOUTCAPS) */
180 setEAX(waveOutGetDevCaps(getEAX(),(WAVEOUTCAPS*)p,getEBX()));
183 static IOIF_COMMAND ioif_command_winmm_waveOutGetDevCaps = {
185 ioif_function_winmm_waveOutGetDevCaps_insb,/* insb */
191 /* this is called when Function == general and selection changes */
192 static void ioif_function_winmm__SELECT() {
193 switch (io_if.subfunction) {
194 case DOSNTAST_FUN_WINMM_SUB_waveOutGetNumDevs:
195 io_if_command = &ioif_command_winmm_waveOutGetNumDevs;
197 case DOSNTAST_FUN_WINMM_SUB_waveOutGetDevCaps:
198 io_if_command = &ioif_command_winmm_waveOutGetDevCaps;
200 case DOSNTAST_FUN_WINMM_SUB_waveOutOpen:
201 io_if_command = &ioif_command_winmm_waveOutOpen;
204 io_if_command = NULL;
208 /* ------------------------------------------------------------------------------------- */
209 void ioif_function__SELECT() {
210 switch (io_if.function) {
211 case DOSNTAST_FUNCTION_GENERAL:
212 ioif_function_general__SELECT();
214 case DOSNTAST_FUNCTION_WINMM:
215 ioif_function_winmm__SELECT();
218 io_if_command = NULL;
222 WORD ioif_command(WORD param0) {
223 if (io_if_command != NULL && io_if_command->iop != NULL)
224 return io_if_command->iop(param0);
229 void ioif_command_insb(BYTE *data,WORD count) {
230 if (io_if_command != NULL && io_if_command->insb != NULL)
231 io_if_command->insb(data,count);
234 void ioif_command_insw(WORD *data,WORD count) {
235 if (io_if_command != NULL && io_if_command->insw != NULL)
236 io_if_command->insw(data,count);
239 void ioif_command_outsb(BYTE *data,WORD count) {
240 if (io_if_command != NULL && io_if_command->outsb != NULL)
241 io_if_command->outsb(data,count);
244 void ioif_command_outsw(WORD *data,WORD count) {
245 if (io_if_command != NULL && io_if_command->outsw != NULL)
246 io_if_command->outsw(data,count);
249 void save_ioif_state(IOIF_STATE *data,WORD count) {
250 if (count < sizeof(IOIF_STATE)) return;
254 void restore_ioif_state(IOIF_STATE *data,WORD count) {
255 if (count < sizeof(IOIF_STATE)) return;
257 ioif_function__SELECT();
260 void ioif_function_select(WORD f) {
261 /* setting the function resets subfunction to zero */
263 io_if.subfunction = 0;
264 ioif_function__SELECT();
267 void ioif_subfunction_select(WORD f) {
268 io_if.subfunction = f;
269 ioif_function__SELECT();
272 /* when a DOS program does REP OUTSW, NTVDM.EXE provides the translated DS:SI for us.
273 * Nice. Except... when done from a 32-bit DOS program, it only translates DS:SI for us.
274 * Not very good when our DOS code uses *DS:ESI* as a flat 32-bit program!
276 * Speaking of which Microsoft why the hell isn't there a function to tell if the DS
277 * segment is 16-bit or 32-bit?!?
279 * NTS: This code assumes x86 32-bit NTVDM.EXE behavior where DOS memory is mapped to
280 * the 0x00000-0xFFFFF area within the NTVDM process. */
281 BYTE *NTVDM_DS_ESI_correct(BYTE *p) {
282 /* if protected mode, replace the pointer given with a proper pointer to DS:ESI */
284 /* NTS: x86 behavior: VdmMapFlat() just returns a pointer. There's an
285 * "unmap" function but it's a stub. We take advantage of that.
286 * No punishment for "mapping" without "unmapping" */
287 return (BYTE*)VdmMapFlat(getDS(),getESI(),VDM_PM);
293 BYTE *NTVDM_ES_EDI_correct(BYTE *p) {
294 /* if protected mode, replace the pointer given with a proper pointer to DS:ESI */
296 /* NTS: x86 behavior: VdmMapFlat() just returns a pointer. There's an
297 * "unmap" function but it's a stub. We take advantage of that.
298 * No punishment for "mapping" without "unmapping" */
299 return (BYTE*)VdmMapFlat(getES(),getEDI(),VDM_PM);
306 VOID WINAPI io_inw(WORD iport,WORD *data) {
307 if (iport == (vm_io_base+0x00)) /* function select */
308 *data = io_if.function;
309 else if (iport == (vm_io_base+0x01)) /* subfunction select */
310 *data = io_if.subfunction;
311 else if (iport == (vm_io_base+0x02)) /* command (param 0 in data) */
312 *data = ioif_command(0xFFFFU);
314 *data = 0xFFFF; /* default */
317 VOID WINAPI io_inb(WORD iport,BYTE *data) {
321 *data = (BYTE)w; /* default: do whatever our word-size version would and return lower bits */
325 VOID WINAPI io_insw(WORD iport,WORD *data,WORD count) {
326 data = (WORD*)NTVDM_ES_EDI_correct((BYTE*)data);
328 if (iport == (vm_io_base+0x00))
329 save_ioif_state((IOIF_STATE*)data,count*2U); /* FIXME: Microsoft isn't clear: is "count" the count of WORDs? or BYTEs? */
330 else if (iport == (vm_io_base+0x02)) /* command (param 0 in data) */
331 ioif_command_insw(data,count);
334 VOID WINAPI io_insb(WORD iport,BYTE *data,WORD count) {
335 data = NTVDM_ES_EDI_correct(data);
337 if (iport == (vm_io_base+0x00))
338 save_ioif_state((IOIF_STATE*)data,count);
339 else if (iport == (vm_io_base+0x02)) /* command (param 0 in data) */
340 ioif_command_insb(data,count);
343 VOID WINAPI io_outw(WORD iport,WORD data) {
344 if (iport == (vm_io_base+0x00)) /* function select */
345 ioif_function_select(data);
346 else if (iport == (vm_io_base+0x01)) /* subfunction select */
347 ioif_subfunction_select(data);
348 else if (iport == (vm_io_base+0x02)) /* command (param 0 in data) */
352 VOID WINAPI io_outb(WORD iport,BYTE data) {
353 io_outw(iport,data); /* default: pass the byte value up to the word-size callback */
356 VOID WINAPI io_outsw(WORD iport,WORD *data,WORD count) {
357 data = (WORD*)NTVDM_DS_ESI_correct((BYTE*)data);
359 if (iport == (vm_io_base+0x00))
360 restore_ioif_state((IOIF_STATE*)data,count*2U); /* FIXME: Microsoft isn't clear: is "count" the count of WORDs? or BYTEs? */
361 else if (iport == (vm_io_base+0x02)) /* command (param 0 in data) */
362 ioif_command_outsw(data,count);
365 VOID WINAPI io_outsb(WORD iport,BYTE *data,WORD count) {
366 data = NTVDM_DS_ESI_correct(data);
368 if (iport == (vm_io_base+0x00))
369 restore_ioif_state((IOIF_STATE*)data,count);
370 else if (iport == (vm_io_base+0x02)) /* command (param 0 in data) */
371 ioif_command_outsb(data,count);
374 static void mark_vm() {
376 unsigned int i=0xF0/*start at 0x40:0xF0*/,max=0x200;
378 if (vm_marked) return;
380 ptr = VdmMapFlat(0x40,0x00,VDM_V86);
381 if (ptr == NULL) return;
383 /* find an empty spot to place our signature. the client is expected
384 * to write the handle value next to it */
386 if (!memcmp(ptr+i,"\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0" "\0\0\0\0",20))
393 memcpy(ptr+i,"DOSNTAST.VDD\xFF\xFF\xFF\xFF",16);
395 VdmUnmapFlat(0x40,0x00,(DWORD)ptr,VDM_V86);
399 static void remove_vm_mark() {
402 if (vm_marked >= 0x400 && vm_marked <= 0x600) {
403 ptr = VdmMapFlat(0x40,0x00,VDM_V86);
404 if (ptr == NULL) return;
406 memset(ptr+vm_marked-0x400,0,16);
408 VdmUnmapFlat(0x40,0x00,(DWORD)ptr,VDM_V86);
413 static void choose_io_port() {
417 if (vm_io_base != 0) return;
419 /* FIXME: Remove this when Watcom C/C++ actually calls up to DllMain on entry point */
420 if (this_vdd == NULL)
421 this_vdd = GetModuleHandle("DOSNTAST.VDD");
422 if (this_vdd == NULL) {
423 MessageBox(NULL,"NO!","",MB_OK);
427 h.inb_handler = io_inb;
428 h.inw_handler = io_inw;
429 h.insb_handler = io_insb;
430 h.insw_handler = io_insw;
431 h.outb_handler = io_outb;
432 h.outw_handler = io_outw;
433 h.outsb_handler = io_outsb;
434 h.outsw_handler = io_outsw;
436 /* choose an I/O port */
437 for (vm_io_base=0x1000;vm_io_base <= 0xF000;vm_io_base += 0x80) {
438 pr.First = vm_io_base;
439 pr.Last = vm_io_base + VM_IO_PORTS - 1;
441 if (VDDInstallIOHook(this_vdd,1/*cPortRange*/,&pr,&h)) {
447 if (vm_io_base > 0xF000) {
448 /* didn't find any */
449 MessageBox(NULL,"Failed","",MB_OK);
454 static void remove_io_port() {
457 if (vm_io_base == 0) return;
458 r.First = vm_io_base;
459 r.Last = vm_io_base + VM_IO_PORTS - 1;
460 VDDDeInstallIOHook(this_vdd,1,&r);
464 /* Microsoft documentation on this "init" routine is lacking */
465 __declspec(dllexport) void WINAPI Init() {
466 if (!vm_marked) mark_vm();
467 if (vm_io_base == 0) choose_io_port();
470 __declspec(dllexport) void WINAPI Dispatch() {
475 if (command == DOSNTAST_INIT_REPORT_HANDLE) {
479 else if (command == DOSNTAST_GETVERSIONEX) {
480 unsigned char *ptr = NULL;
487 mode = (getCX() == 1) ? VDM_PM : VDM_V86;
489 ptr = VdmMapFlat(seg,ofs,mode);
491 setEBX(GetVersionEx((OSVERSIONINFO*)ptr));
492 VdmUnmapFlat(seg,ofs,(DWORD)ptr,mode);
495 else if (command == DOSNTAST_GET_TICK_COUNT) {
496 setEBX(GetTickCount());
498 /* the DOS program sends this when it's about to unload us.
499 * I originally wanted DllMain to do this on PROCESS_DETACH but,
500 * for some reason, that function isn't getting called. */
501 else if (command == DOSNTAST_GET_IO_PORT) {
505 else if (command == DOSNTAST_NOTIFY_UNLOAD) {
506 if (vm_io_base) remove_io_port();
507 if (vm_marked) remove_vm_mark();
511 sprintf(tmp,"0x%08lX\n",(unsigned long)command);
512 MessageBox(NULL,tmp,"Unknown command",MB_OK);