]> 4ch.mooo.com Git - 16.git/blob - src/lib/doslib/dos/dosntast.c
added a bunch of things~ and midi stuff~
[16.git] / src / lib / doslib / dos / dosntast.c
1 /* dosntast.c
2  *
3  * Windows NT VDD driver, dynamically loaded by code that needs it
4  * (C) 2009-2012 Jonathan Campbell.
5  * Hackipedia DOS library.
6  *
7  * This code is licensed under the LGPL.
8  * <insert LGPL legal text here>
9  *
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.
13  */
14
15 /* This is a Windows NT VDD driver */
16 #define NTVDM_VDD_DRIVER
17
18 #include <stdlib.h>
19 #include <string.h>
20 #include <stdint.h>
21 #include <assert.h>
22 #include <stdlib.h>
23 #include <stdarg.h>
24 #include <unistd.h>
25 #include <stdio.h>
26 #include <conio.h>
27 #include <fcntl.h>
28 #include <dos.h>
29 #include <i86.h>
30
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>
36
37 #define DEBUG
38
39 /* this is a DLL for Win32 */
40 #if TARGET_MSDOS == 16 || !defined(TARGET_WINDOWS)
41 # error this is a DLL for Win32 only
42 #endif
43
44 static HMODULE                          this_vdd = 0;
45
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;
51
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
56  * loaded. */
57 static uint16_t                         vm_io_base = 0;
58 #define VM_IO_PORTS                     0x20
59
60 /* +0x00 (WORD)  W/O   Function select. */
61 /* +0x01 (WORD)  W/O   Sub-function select. */
62
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.
65  *
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. */
68
69 /* +0x00 (INSB)  R/O   Read current function/subfunction selection [24 bytes] */
70 /* +0x00 (OUTSB) W/O   Write current function/subfunction selection [24 bytes] */
71
72 #pragma pack(push,1)
73 typedef struct {
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 */
86 } IOIF_STATE;
87
88 typedef struct {
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);
94 } IOIF_COMMAND;
95 #pragma pack(pop)
96
97 /* IO interface state */
98 IOIF_STATE                              io_if={0};
99 IOIF_COMMAND*                           io_if_command=NULL; /* what to do on command */
100
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;
109         }
110
111         return TRUE;
112 }
113
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);
119         }
120 }
121
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 */
125         NULL,                                   /* iop */
126         NULL,                                   /* insb */
127         NULL,                                   /* insw */
128         ioif_function_general_messagebox_outsb, /* outsb */
129         NULL                                    /* outsw */
130 };
131
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;
137                         break;
138                 default:
139                         io_if_command = NULL;
140                         break;
141         }
142 }
143 /* DOSNTAST_FUNCTION_WINMM-------------------------------------------------------------- */
144 static WORD ioif_function_winmm_waveOutGetNumDevs_iop(WORD param0) {
145         return (WORD)waveOutGetNumDevs();
146 }
147
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 */
152         NULL,                                   /* insb */
153         NULL,                                   /* insw */
154         NULL,                                   /* outsb */
155         NULL                                    /* outsw */
156 };
157
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)
163          *      EBX = error */
164         /* TODO */
165         setEAX(0xFFFFFFFF);
166         setEBX(0xFFFFFFFF);
167 }
168
169 static IOIF_COMMAND ioif_command_winmm_waveOutOpen = {
170         NULL,                                   /* iop */
171         NULL,                                   /* insb */
172         NULL,                                   /* insw */
173         ioif_function_winmm_waveOutOpen_outsb,  /* outsb */
174         NULL                                    /* outsw */
175 };
176
177 static void ioif_function_winmm_waveOutGetDevCaps_insb(BYTE *p,WORD count) {
178         /* EAX = uDeviceID
179          * EBX = cbwoc (sizeof of WAVEOUTCAPS) */
180         setEAX(waveOutGetDevCaps(getEAX(),(WAVEOUTCAPS*)p,getEBX()));
181 }
182
183 static IOIF_COMMAND ioif_command_winmm_waveOutGetDevCaps = {
184         NULL,                                   /* iop */
185         ioif_function_winmm_waveOutGetDevCaps_insb,/* insb */
186         NULL,                                   /* insw */
187         NULL,                                   /* outsb */
188         NULL                                    /* outsw */
189 };
190
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;
196                         break;
197                 case DOSNTAST_FUN_WINMM_SUB_waveOutGetDevCaps:
198                         io_if_command = &ioif_command_winmm_waveOutGetDevCaps;
199                         break;
200                 case DOSNTAST_FUN_WINMM_SUB_waveOutOpen:
201                         io_if_command = &ioif_command_winmm_waveOutOpen;
202                         break;
203                 default:
204                         io_if_command = NULL;
205                         break;
206         }
207 }
208 /* ------------------------------------------------------------------------------------- */
209 void ioif_function__SELECT() {
210         switch (io_if.function) {
211                 case DOSNTAST_FUNCTION_GENERAL:
212                         ioif_function_general__SELECT();
213                         break;
214                 case DOSNTAST_FUNCTION_WINMM:
215                         ioif_function_winmm__SELECT();
216                         break;
217                 default:
218                         io_if_command = NULL;
219         }
220 }
221
222 WORD ioif_command(WORD param0) {
223         if (io_if_command != NULL && io_if_command->iop != NULL)
224                 return io_if_command->iop(param0);
225
226         return 0xFFFFU;
227 }
228
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);
232 }
233
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);
237 }
238
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);
242 }
243
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);
247 }
248
249 void save_ioif_state(IOIF_STATE *data,WORD count) {
250         if (count < sizeof(IOIF_STATE)) return;
251         *data = io_if;
252 }
253
254 void restore_ioif_state(IOIF_STATE *data,WORD count) {
255         if (count < sizeof(IOIF_STATE)) return;
256         io_if = *data;
257         ioif_function__SELECT();
258 }
259
260 void ioif_function_select(WORD f) {
261         /* setting the function resets subfunction to zero */
262         io_if.function = f;
263         io_if.subfunction = 0;
264         ioif_function__SELECT();
265 }
266
267 void ioif_subfunction_select(WORD f) {
268         io_if.subfunction = f;
269         ioif_function__SELECT();
270 }
271
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!
275  *
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?!?
278  *
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 */
283         if (getMSW() & 1) {
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);
288         }
289
290         return (BYTE*)p;
291 }
292
293 BYTE *NTVDM_ES_EDI_correct(BYTE *p) {
294         /* if protected mode, replace the pointer given with a proper pointer to DS:ESI */
295         if (getMSW() & 1) {
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);
300         }
301
302         return (BYTE*)p;
303 }
304
305 /* IO handler */
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);
313         else
314                 *data = 0xFFFF; /* default */
315 }
316
317 VOID WINAPI io_inb(WORD iport,BYTE *data) {
318         {
319                 WORD w;
320                 io_inw(iport,&w);
321                 *data = (BYTE)w; /* default: do whatever our word-size version would and return lower bits */
322         }
323 }
324
325 VOID WINAPI io_insw(WORD iport,WORD *data,WORD count) {
326         data = (WORD*)NTVDM_ES_EDI_correct((BYTE*)data);
327
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);
332 }
333
334 VOID WINAPI io_insb(WORD iport,BYTE *data,WORD count) {
335         data = NTVDM_ES_EDI_correct(data);
336
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);
341 }
342
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) */
349                 ioif_command(data);
350 }
351
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 */
354 }
355
356 VOID WINAPI io_outsw(WORD iport,WORD *data,WORD count) {
357         data = (WORD*)NTVDM_DS_ESI_correct((BYTE*)data);
358
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);
363 }
364
365 VOID WINAPI io_outsb(WORD iport,BYTE *data,WORD count) {
366         data = NTVDM_DS_ESI_correct(data);
367
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);
372 }
373
374 static void mark_vm() {
375         unsigned char *ptr;
376         unsigned int i=0xF0/*start at 0x40:0xF0*/,max=0x200;
377
378         if (vm_marked) return;
379
380         ptr = VdmMapFlat(0x40,0x00,VDM_V86);
381         if (ptr == NULL) return;
382
383         /* find an empty spot to place our signature. the client is expected
384          * to write the handle value next to it */
385         while (i < max) {
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))
387                         break;
388
389                 i++;
390         }
391
392         if (i < max)
393                 memcpy(ptr+i,"DOSNTAST.VDD\xFF\xFF\xFF\xFF",16);
394
395         VdmUnmapFlat(0x40,0x00,(DWORD)ptr,VDM_V86);
396         vm_marked = i+0x400;
397 }
398
399 static void remove_vm_mark() {
400         unsigned char *ptr;
401
402         if (vm_marked >= 0x400 && vm_marked <= 0x600) {
403                 ptr = VdmMapFlat(0x40,0x00,VDM_V86);
404                 if (ptr == NULL) return;
405
406                 memset(ptr+vm_marked-0x400,0,16);
407
408                 VdmUnmapFlat(0x40,0x00,(DWORD)ptr,VDM_V86);
409                 vm_marked = 0;
410         }
411 }
412
413 static void choose_io_port() {
414         VDD_IO_HANDLERS h;
415         VDD_IO_PORTRANGE pr;
416
417         if (vm_io_base != 0) return;
418
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);
424                 return;
425         }
426
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;
435
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;
440
441                 if (VDDInstallIOHook(this_vdd,1/*cPortRange*/,&pr,&h)) {
442                         /* got it */
443                         break;
444                 }
445         }
446
447         if (vm_io_base > 0xF000) {
448                 /* didn't find any */
449                 MessageBox(NULL,"Failed","",MB_OK);
450                 vm_io_base = 0;
451         }
452 }
453
454 static void remove_io_port() {
455         VDD_IO_PORTRANGE r;
456
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);
461         vm_io_base = 0;
462 }
463
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();
468 }
469
470 __declspec(dllexport) void WINAPI Dispatch() {
471         uint32_t command;
472         char tmp[64];
473
474         command = getEBX();
475         if (command == DOSNTAST_INIT_REPORT_HANDLE) {
476                 setEBX(0x55AA55AA);
477                 setECX(vm_marked);
478         }
479         else if (command == DOSNTAST_GETVERSIONEX) {
480                 unsigned char *ptr = NULL;
481                 uint16_t seg;
482                 uint32_t ofs;
483                 uint8_t mode;
484
485                 seg = getDS();
486                 ofs = getESI();
487                 mode = (getCX() == 1) ? VDM_PM : VDM_V86;
488
489                 ptr = VdmMapFlat(seg,ofs,mode);
490                 if (ptr != NULL) {
491                         setEBX(GetVersionEx((OSVERSIONINFO*)ptr));
492                         VdmUnmapFlat(seg,ofs,(DWORD)ptr,mode);
493                 }
494         }
495         else if (command == DOSNTAST_GET_TICK_COUNT) {
496                 setEBX(GetTickCount());
497         }
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) {
502                 setEBX(0x55AA55AA);
503                 setEDX(vm_io_base);
504         }
505         else if (command == DOSNTAST_NOTIFY_UNLOAD) {
506                 if (vm_io_base) remove_io_port();
507                 if (vm_marked) remove_vm_mark();
508                 setEBX(0x55AA55AA);
509         }
510         else {
511                 sprintf(tmp,"0x%08lX\n",(unsigned long)command);
512                 MessageBox(NULL,tmp,"Unknown command",MB_OK);
513         }
514 }
515