xref: /reactos/subsystems/mvdm/ntvdm/vddsup.c (revision 9cfd8dd9)
1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/vddsup.c
5  * PURPOSE:         Virtual Device Drivers (VDD) Support
6  * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
7  */
8 
9 /* INCLUDES *******************************************************************/
10 
11 #include "ntvdm.h"
12 
13 #define NDEBUG
14 #include <debug.h>
15 
16 #include "emulator.h"
17 #include "vddsup.h"
18 
19 #include "cpu/bop.h"
20 #include <isvbop.h>
21 
22 typedef VOID (WINAPI *VDD_PROC)(VOID);
23 
24 typedef struct _VDD_MODULE
25 {
26     HMODULE  hDll;
27     VDD_PROC DispatchRoutine;
28 } VDD_MODULE, *PVDD_MODULE;
29 
30 // WARNING: A structure with the same name exists in nt_vdd.h,
31 // however it is not declared because its inclusion was prevented
32 // with #define NO_NTVDD_COMPAT, see ntvdm.h
33 typedef struct _VDD_USER_HANDLERS
34 {
35     LIST_ENTRY Entry;
36 
37     HANDLE            hVdd;
38     PFNVDD_UCREATE    Ucr_Handler;
39     PFNVDD_UTERMINATE Uterm_Handler;
40     PFNVDD_UBLOCK     Ublock_Handler;
41     PFNVDD_URESUME    Uresume_Handler;
42 } VDD_USER_HANDLERS, *PVDD_USER_HANDLERS;
43 
44 /* PRIVATE VARIABLES **********************************************************/
45 
46 // TODO: Maybe use a linked list.
47 // But the number of elements must be <= MAXUSHORT (MAXWORD)
48 #define MAX_VDD_MODULES 0xFF + 1
49 static VDD_MODULE VDDList[MAX_VDD_MODULES] = {{NULL}};
50 
51 // Valid handles of VDD DLLs start at 1 and finish at MAX_VDD_MODULES
52 #define ENTRY_TO_HANDLE(Entry)  ((Entry)  + 1)
53 #define HANDLE_TO_ENTRY(Handle) ((Handle) - 1)
54 #define IS_VALID_HANDLE(Handle) ((Handle) > 0 && (Handle) <= MAX_VDD_MODULES)
55 
56 static LIST_ENTRY VddUserHooksList = {&VddUserHooksList, &VddUserHooksList};
57 
58 /* PRIVATE FUNCTIONS **********************************************************/
59 
60 static USHORT GetNextFreeVDDEntry(VOID)
61 {
62     USHORT Entry;
63     for (Entry = 0; Entry < ARRAYSIZE(VDDList); ++Entry)
64     {
65         if (VDDList[Entry].hDll == NULL) break;
66     }
67     return Entry;
68 }
69 
70 static VOID WINAPI ThirdPartyVDDBop(LPWORD Stack)
71 {
72     /* Get the Function Number and skip it */
73     BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
74     setIP(getIP() + 1);
75 
76     switch (FuncNum)
77     {
78         /* RegisterModule */
79         case 0:
80         {
81             BOOL Success = TRUE;
82             WORD RetVal  = 0;
83             WORD Entry   = 0;
84             LPCSTR DllName = NULL,
85                    InitRoutineName     = NULL,
86                    DispatchRoutineName = NULL;
87             HMODULE hDll = NULL;
88             VDD_PROC InitRoutine     = NULL,
89                      DispatchRoutine = NULL;
90 
91             DPRINT("RegisterModule() called\n");
92 
93             /* Clear the Carry Flag (no error happened so far) */
94             setCF(0);
95 
96             /* Retrieve the next free entry in the table (used later on) */
97             Entry = GetNextFreeVDDEntry();
98             if (Entry >= MAX_VDD_MODULES)
99             {
100                 DPRINT1("Failed to create a new VDD module entry\n");
101                 Success = FALSE;
102                 RetVal = 4;
103                 goto Quit;
104             }
105 
106             /* Retrieve the VDD name in DS:SI */
107             DllName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getSI());
108 
109             /* Retrieve the initialization routine API name in ES:DI (optional --> ES=DI=0) */
110             if (TO_LINEAR(getES(), getDI()) != 0)
111                 InitRoutineName = (LPCSTR)SEG_OFF_TO_PTR(getES(), getDI());
112 
113             /* Retrieve the dispatch routine API name in DS:BX */
114             DispatchRoutineName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getBX());
115 
116             DPRINT1("DllName = '%s' - InitRoutineName = '%s' - DispatchRoutineName = '%s'\n",
117                     (DllName ? DllName : "n/a"),
118                     (InitRoutineName ? InitRoutineName : "n/a"),
119                     (DispatchRoutineName ? DispatchRoutineName : "n/a"));
120 
121             /* Load the VDD DLL */
122             hDll = LoadLibraryA(DllName);
123             if (hDll == NULL)
124             {
125                 DWORD LastError = GetLastError();
126                 Success = FALSE;
127 
128                 if (LastError == ERROR_NOT_ENOUGH_MEMORY)
129                 {
130                     DPRINT1("Not enough memory to load DLL '%s'\n", DllName);
131                     RetVal = 4;
132                     goto Quit;
133                 }
134                 else
135                 {
136                     DPRINT1("Failed to load DLL '%s'; last error = %d\n", DllName, LastError);
137                     RetVal = 1;
138                     goto Quit;
139                 }
140             }
141 
142             /* Load the initialization routine if needed */
143             if (InitRoutineName)
144             {
145                 InitRoutine = (VDD_PROC)GetProcAddress(hDll, InitRoutineName);
146                 if (InitRoutine == NULL)
147                 {
148                     DPRINT1("Failed to load the initialization routine '%s'\n", InitRoutineName);
149                     Success = FALSE;
150                     RetVal = 3;
151                     goto Quit;
152                 }
153             }
154 
155             /* Load the dispatch routine */
156             DispatchRoutine = (VDD_PROC)GetProcAddress(hDll, DispatchRoutineName);
157             if (DispatchRoutine == NULL)
158             {
159                 DPRINT1("Failed to load the dispatch routine '%s'\n", DispatchRoutineName);
160                 Success = FALSE;
161                 RetVal = 2;
162                 goto Quit;
163             }
164 
165             /* If we reached this point, that means everything is OK */
166 
167             /* Register the VDD DLL */
168             VDDList[Entry].hDll = hDll;
169             VDDList[Entry].DispatchRoutine = DispatchRoutine;
170 
171             /* Call the initialization routine if needed */
172             if (InitRoutine) InitRoutine();
173 
174             /* We succeeded. RetVal will contain a valid VDD DLL handle */
175             Success = TRUE;
176             RetVal  = ENTRY_TO_HANDLE(Entry); // Convert the entry to a valid handle
177 
178 Quit:
179             if (!Success)
180             {
181                 /* Unload the VDD DLL */
182                 if (hDll) FreeLibrary(hDll);
183 
184                 /* Set the Carry Flag to indicate that an error happened */
185                 setCF(1);
186             }
187             // else
188             // {
189                 // /* Clear the Carry Flag (success) */
190                 // setCF(0);
191             // }
192             setAX(RetVal);
193             break;
194         }
195 
196         /* UnRegisterModule */
197         case 1:
198         {
199             WORD Handle = getAX();
200             WORD Entry  = HANDLE_TO_ENTRY(Handle); // Convert the handle to a valid entry
201 
202             DPRINT("UnRegisterModule() called\n");
203 
204             /* Sanity checks */
205             if (!IS_VALID_HANDLE(Handle) || VDDList[Entry].hDll == NULL)
206             {
207                 DPRINT1("Invalid VDD DLL Handle: %d\n", Entry);
208                 /* Stop the VDM */
209                 EmulatorTerminate();
210                 return;
211             }
212 
213             /* Unregister the VDD DLL */
214             FreeLibrary(VDDList[Entry].hDll);
215             VDDList[Entry].hDll = NULL;
216             VDDList[Entry].DispatchRoutine = NULL;
217             break;
218         }
219 
220         /* DispatchCall */
221         case 2:
222         {
223             WORD Handle = getAX();
224             WORD Entry  = HANDLE_TO_ENTRY(Handle); // Convert the handle to a valid entry
225 
226             DPRINT("DispatchCall() called\n");
227 
228             /* Sanity checks */
229             if (!IS_VALID_HANDLE(Handle)    ||
230                 VDDList[Entry].hDll == NULL ||
231                 VDDList[Entry].DispatchRoutine == NULL)
232             {
233                 DPRINT1("Invalid VDD DLL Handle: %d\n", Entry);
234                 /* Stop the VDM */
235                 EmulatorTerminate();
236                 return;
237             }
238 
239             /* Call the dispatch routine */
240             VDDList[Entry].DispatchRoutine();
241             break;
242         }
243 
244         default:
245         {
246             DPRINT1("Unknown 3rd-party VDD BOP Function: 0x%02X\n", FuncNum);
247             setCF(1);
248             break;
249         }
250     }
251 }
252 
253 static BOOL LoadInstallableVDD(VOID)
254 {
255 // FIXME: These strings should be localized.
256 #define ERROR_MEMORYVDD L"Insufficient memory to load installable Virtual Device Drivers."
257 #define ERROR_REGVDD    L"Virtual Device Driver format in the registry is invalid."
258 #define ERROR_LOADVDD   L"An installable Virtual Device Driver failed Dll initialization."
259 
260     BOOL  Success = TRUE;
261     LONG  Error   = 0;
262     DWORD Type    = 0;
263     DWORD BufSize = 0;
264 
265     HKEY    hVDDKey;
266     LPCWSTR VDDKeyName   = L"SYSTEM\\CurrentControlSet\\Control\\VirtualDeviceDrivers";
267     LPWSTR  VDDValueName = L"VDD";
268     LPWSTR  VDDList      = NULL;
269 
270     HANDLE hVDD;
271 
272     /* Try to open the VDD registry key */
273     Error = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
274                           VDDKeyName,
275                           0,
276                           KEY_QUERY_VALUE,
277                           &hVDDKey);
278     if (Error == ERROR_FILE_NOT_FOUND)
279     {
280         /* If the key just doesn't exist, don't do anything else */
281         return TRUE;
282     }
283     else if (Error != ERROR_SUCCESS)
284     {
285         /* The key exists but there was an access error: display an error and quit */
286         DisplayMessage(ERROR_REGVDD);
287         return FALSE;
288     }
289 
290     /*
291      * Retrieve the size of the VDD registry value
292      * and check that it's of REG_MULTI_SZ type.
293      */
294     Error = RegQueryValueExW(hVDDKey,
295                              VDDValueName,
296                              NULL,
297                              &Type,
298                              NULL,
299                              &BufSize);
300     if (Error == ERROR_FILE_NOT_FOUND)
301     {
302         /* If the value just doesn't exist, don't do anything else */
303         Success = TRUE;
304         goto Quit;
305     }
306     else if (Error != ERROR_SUCCESS || Type != REG_MULTI_SZ)
307     {
308         /*
309          * The value exists but there was an access error or
310          * is of the wrong type: display an error and quit.
311          */
312         DisplayMessage(ERROR_REGVDD);
313         Success = FALSE;
314         goto Quit;
315     }
316 
317     /* Allocate the buffer */
318     BufSize = (BufSize < 2*sizeof(WCHAR) ? 2*sizeof(WCHAR) : BufSize);
319     VDDList = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, BufSize);
320     if (VDDList == NULL)
321     {
322         DisplayMessage(ERROR_MEMORYVDD);
323         Success = FALSE;
324         goto Quit;
325     }
326 
327     /* Retrieve the list of VDDs to load */
328     if (RegQueryValueExW(hVDDKey,
329                          VDDValueName,
330                          NULL,
331                          NULL,
332                          (LPBYTE)VDDList,
333                          &BufSize) != ERROR_SUCCESS)
334     {
335         DisplayMessage(ERROR_REGVDD);
336         Success = FALSE;
337         goto Quit;
338     }
339 
340     /* Load the VDDs */
341     VDDValueName = VDDList;
342     while (*VDDList)
343     {
344         DPRINT1("Loading VDD '%S'... ", VDDList);
345         hVDD = LoadLibraryW(VDDList);
346         if (hVDD == NULL)
347         {
348             DbgPrint("Failed\n");
349             DisplayMessage(ERROR_LOADVDD);
350         }
351         else
352         {
353             DbgPrint("Succeeded\n");
354         }
355         /* Go to next string */
356         VDDList += wcslen(VDDList) + 1;
357     }
358     VDDList = VDDValueName;
359 
360 Quit:
361     if (VDDList) RtlFreeHeap(RtlGetProcessHeap(), 0, VDDList);
362     RegCloseKey(hVDDKey);
363     return Success;
364 }
365 
366 /* PUBLIC FUNCTIONS ***********************************************************/
367 
368 /*
369  * NOTE: This function can be called multiple times by the same VDD, if
370  * it wants to install different hooks for a same action. The most recent
371  * registered hooks are called first.
372  */
373 BOOL
374 WINAPI
375 VDDInstallUserHook(IN HANDLE hVdd,
376                    IN PFNVDD_UCREATE Ucr_Handler,
377                    IN PFNVDD_UTERMINATE Uterm_Handler,
378                    IN PFNVDD_UBLOCK Ublock_Handler,
379                    IN PFNVDD_URESUME Uresume_Handler)
380 {
381     PVDD_USER_HANDLERS UserHook;
382 
383     /* Check validity of the VDD handle */
384     if (hVdd == NULL || hVdd == INVALID_HANDLE_VALUE)
385     {
386         SetLastError(ERROR_INVALID_PARAMETER);
387         return FALSE;
388     }
389 
390     // NOTE: If we want that a VDD can install hooks only once, it's here
391     // that we need to check whether a hook entry is already registered.
392 
393     /* Create and initialize a new hook entry... */
394     UserHook = RtlAllocateHeap(RtlGetProcessHeap(), 0, sizeof(*UserHook));
395     if (UserHook == NULL)
396     {
397         SetLastError(ERROR_OUTOFMEMORY);
398         return FALSE;
399     }
400 
401     UserHook->hVdd            = hVdd;
402     UserHook->Ucr_Handler     = Ucr_Handler;
403     UserHook->Uterm_Handler   = Uterm_Handler;
404     UserHook->Ublock_Handler  = Ublock_Handler;
405     UserHook->Uresume_Handler = Uresume_Handler;
406 
407     /* ... and add it at the top of the list of hooks */
408     InsertHeadList(&VddUserHooksList, &UserHook->Entry);
409 
410     return TRUE;
411 }
412 
413 /*
414  * NOTE: This function uninstalls the latest installed hooks for a given VDD.
415  * It can be called multiple times by the same VDD to uninstall many hooks
416  * installed by multiple invocations of VDDInstallUserHook.
417  */
418 BOOL
419 WINAPI
420 VDDDeInstallUserHook(IN HANDLE hVdd)
421 {
422     PLIST_ENTRY Pointer;
423     PVDD_USER_HANDLERS UserHook;
424 
425     /* Check validity of the VDD handle */
426     if (hVdd == NULL || hVdd == INVALID_HANDLE_VALUE)
427     {
428         SetLastError(ERROR_INVALID_PARAMETER);
429         return FALSE;
430     }
431 
432     /* Uninstall the latest installed hooks */
433     for (Pointer = VddUserHooksList.Flink; Pointer != &VddUserHooksList; Pointer = Pointer->Flink)
434     {
435         UserHook = CONTAINING_RECORD(Pointer, VDD_USER_HANDLERS, Entry);
436         if (UserHook->hVdd == hVdd)
437         {
438             RemoveEntryList(&UserHook->Entry);
439             RtlFreeHeap(RtlGetProcessHeap(), 0, UserHook);
440             return TRUE;
441         }
442     }
443 
444     SetLastError(ERROR_INVALID_PARAMETER);
445     return FALSE;
446 }
447 
448 /*
449  * Internal functions for calling the VDD user hooks.
450  * Their names come directly from the Windows 2kX DDK.
451  */
452 
453 VOID VDDCreateUserHook(USHORT DosPDB)
454 {
455     PLIST_ENTRY Pointer;
456     PVDD_USER_HANDLERS UserHook;
457 
458     /* Call the hooks starting from the most recent ones */
459     for (Pointer = VddUserHooksList.Flink; Pointer != &VddUserHooksList; Pointer = Pointer->Flink)
460     {
461         UserHook = CONTAINING_RECORD(Pointer, VDD_USER_HANDLERS, Entry);
462         if (UserHook->Ucr_Handler) UserHook->Ucr_Handler(DosPDB);
463     }
464 }
465 
466 VOID VDDTerminateUserHook(USHORT DosPDB)
467 {
468     PLIST_ENTRY Pointer;
469     PVDD_USER_HANDLERS UserHook;
470 
471     /* Call the hooks starting from the most recent ones */
472     for (Pointer = VddUserHooksList.Flink; Pointer != &VddUserHooksList; Pointer = Pointer->Flink)
473     {
474         UserHook = CONTAINING_RECORD(Pointer, VDD_USER_HANDLERS, Entry);
475         if (UserHook->Uterm_Handler) UserHook->Uterm_Handler(DosPDB);
476     }
477 }
478 
479 VOID VDDBlockUserHook(VOID)
480 {
481     PLIST_ENTRY Pointer;
482     PVDD_USER_HANDLERS UserHook;
483 
484     /* Call the hooks starting from the most recent ones */
485     for (Pointer = VddUserHooksList.Flink; Pointer != &VddUserHooksList; Pointer = Pointer->Flink)
486     {
487         UserHook = CONTAINING_RECORD(Pointer, VDD_USER_HANDLERS, Entry);
488         if (UserHook->Ublock_Handler) UserHook->Ublock_Handler();
489     }
490 }
491 
492 VOID VDDResumeUserHook(VOID)
493 {
494     PLIST_ENTRY Pointer;
495     PVDD_USER_HANDLERS UserHook;
496 
497     /* Call the hooks starting from the most recent ones */
498     for (Pointer = VddUserHooksList.Flink; Pointer != &VddUserHooksList; Pointer = Pointer->Flink)
499     {
500         UserHook = CONTAINING_RECORD(Pointer, VDD_USER_HANDLERS, Entry);
501         if (UserHook->Uresume_Handler) UserHook->Uresume_Handler();
502     }
503 }
504 
505 
506 VOID VDDSupInitialize(VOID)
507 {
508     /* Register the 3rd-party VDD BOP Handler */
509     RegisterBop(BOP_3RDPARTY, ThirdPartyVDDBop);
510 
511     /* Load the installable VDDs from the registry */
512     LoadInstallableVDD();
513 }
514 
515 /* EOF */
516