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