[NTVDM]
[reactos.git] / reactos / subsystems / mvdm / ntvdm / ntvdm.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: ntvdm.c
5 * PURPOSE: Virtual DOS Machine
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "ntvdm.h"
14 #include "emulator.h"
15
16 #include "bios/bios.h"
17 #include "cpu/cpu.h"
18
19 #include "resource.h"
20
21 /* VARIABLES ******************************************************************/
22
23 static HANDLE ConsoleInput = INVALID_HANDLE_VALUE;
24 static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
25 static DWORD OrgConsoleInputMode, OrgConsoleOutputMode;
26
27 HANDLE VdmTaskEvent = NULL;
28
29 // Command line of NTVDM
30 INT NtVdmArgc;
31 WCHAR** NtVdmArgv;
32
33
34 static HMENU hConsoleMenu = NULL;
35 static INT VdmMenuPos = -1;
36 static BOOLEAN ShowPointer = FALSE;
37
38 /*
39 * Those menu helpers were taken from the GUI frontend in winsrv.dll
40 */
41 typedef struct _VDM_MENUITEM
42 {
43 UINT uID;
44 const struct _VDM_MENUITEM *SubMenu;
45 WORD wCmdID;
46 } VDM_MENUITEM, *PVDM_MENUITEM;
47
48 static const VDM_MENUITEM VdmMenuItems[] =
49 {
50 { IDS_VDM_DUMPMEM_TXT, NULL, ID_VDM_DUMPMEM_TXT },
51 { IDS_VDM_DUMPMEM_BIN, NULL, ID_VDM_DUMPMEM_BIN },
52 { IDS_VDM_QUIT , NULL, ID_VDM_QUIT },
53
54 { 0, NULL, 0 } /* End of list */
55 };
56
57 static const VDM_MENUITEM VdmMainMenuItems[] =
58 {
59 { -1, NULL, 0 }, /* Separator */
60 { IDS_HIDE_MOUSE, NULL, ID_SHOWHIDE_MOUSE }, /* Hide mouse; can be renamed to Show mouse */
61 { IDS_VDM_MENU , VdmMenuItems, 0 }, /* ReactOS VDM Menu */
62
63 { 0, NULL, 0 } /* End of list */
64 };
65
66 static VOID
67 AppendMenuItems(HMENU hMenu,
68 const VDM_MENUITEM *Items)
69 {
70 UINT i = 0;
71 WCHAR szMenuString[255];
72 HMENU hSubMenu;
73
74 do
75 {
76 if (Items[i].uID != (UINT)-1)
77 {
78 if (LoadStringW(GetModuleHandle(NULL),
79 Items[i].uID,
80 szMenuString,
81 ARRAYSIZE(szMenuString)) > 0)
82 {
83 if (Items[i].SubMenu != NULL)
84 {
85 hSubMenu = CreatePopupMenu();
86 if (hSubMenu != NULL)
87 {
88 AppendMenuItems(hSubMenu, Items[i].SubMenu);
89
90 if (!AppendMenuW(hMenu,
91 MF_STRING | MF_POPUP,
92 (UINT_PTR)hSubMenu,
93 szMenuString))
94 {
95 DestroyMenu(hSubMenu);
96 }
97 }
98 }
99 else
100 {
101 AppendMenuW(hMenu,
102 MF_STRING,
103 Items[i].wCmdID,
104 szMenuString);
105 }
106 }
107 }
108 else
109 {
110 AppendMenuW(hMenu,
111 MF_SEPARATOR,
112 0,
113 NULL);
114 }
115 i++;
116 } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
117 }
118
119 BOOL
120 VdmMenuExists(HMENU hConsoleMenu)
121 {
122 INT MenuPos, i;
123 MenuPos = GetMenuItemCount(hConsoleMenu);
124
125 /* Check for the presence of one of the VDM menu items */
126 for (i = 0; i <= MenuPos; i++)
127 {
128 if (GetMenuItemID(hConsoleMenu, i) == ID_SHOWHIDE_MOUSE)
129 {
130 /* set VdmMenuPos to the position of the existing menu */
131 VdmMenuPos = i - 1;
132 return TRUE;
133 }
134 }
135 return FALSE;
136 }
137
138 /*static*/ VOID
139 CreateVdmMenu(HANDLE ConOutHandle)
140 {
141 hConsoleMenu = ConsoleMenuControl(ConOutHandle,
142 ID_SHOWHIDE_MOUSE,
143 ID_VDM_QUIT);
144 if (hConsoleMenu == NULL) return;
145
146 /* Get the position where we are going to insert our menu items */
147 VdmMenuPos = GetMenuItemCount(hConsoleMenu);
148
149 /* Really add the menu if it doesn't already exist (in case eg. NTVDM crashed) */
150 if (!VdmMenuExists(hConsoleMenu))
151 {
152 AppendMenuItems(hConsoleMenu, VdmMainMenuItems);
153 DrawMenuBar(GetConsoleWindow());
154 }
155 }
156
157 /*static*/ VOID
158 DestroyVdmMenu(VOID)
159 {
160 UINT i = 0;
161 const VDM_MENUITEM *Items = VdmMainMenuItems;
162
163 do
164 {
165 DeleteMenu(hConsoleMenu, VdmMenuPos, MF_BYPOSITION);
166 i++;
167 } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
168
169 DrawMenuBar(GetConsoleWindow());
170 }
171
172 static VOID ShowHideMousePointer(HANDLE ConOutHandle, BOOLEAN ShowPtr)
173 {
174 WCHAR szMenuString[255] = L"";
175
176 if (ShowPtr)
177 {
178 /* Be sure the cursor will be shown */
179 while (ShowConsoleCursor(ConOutHandle, TRUE) < 0) ;
180 }
181 else
182 {
183 /* Be sure the cursor will be hidden */
184 while (ShowConsoleCursor(ConOutHandle, FALSE) >= 0) ;
185 }
186
187 if (LoadStringW(GetModuleHandle(NULL),
188 (!ShowPtr ? IDS_SHOW_MOUSE : IDS_HIDE_MOUSE),
189 szMenuString,
190 ARRAYSIZE(szMenuString)) > 0)
191 {
192 ModifyMenu(hConsoleMenu, ID_SHOWHIDE_MOUSE,
193 MF_BYCOMMAND, ID_SHOWHIDE_MOUSE, szMenuString);
194 }
195 }
196
197 static VOID EnableExtraHardware(HANDLE ConsoleInput)
198 {
199 DWORD ConInMode;
200
201 if (GetConsoleMode(ConsoleInput, &ConInMode))
202 {
203 #if 0
204 // GetNumberOfConsoleMouseButtons();
205 // GetSystemMetrics(SM_CMOUSEBUTTONS);
206 // GetSystemMetrics(SM_MOUSEPRESENT);
207 if (MousePresent)
208 {
209 #endif
210 /* Support mouse input events if there is a mouse on the system */
211 ConInMode |= ENABLE_MOUSE_INPUT;
212 #if 0
213 }
214 else
215 {
216 /* Do not support mouse input events if there is no mouse on the system */
217 ConInMode &= ~ENABLE_MOUSE_INPUT;
218 }
219 #endif
220
221 SetConsoleMode(ConsoleInput, ConInMode);
222 }
223 }
224
225 /* PUBLIC FUNCTIONS ***********************************************************/
226
227 VOID
228 DisplayMessage(LPCWSTR Format, ...)
229 {
230 #ifndef WIN2K_COMPLIANT
231 WCHAR StaticBuffer[256];
232 LPWSTR Buffer = StaticBuffer; // Use the static buffer by default.
233 #else
234 WCHAR Buffer[2048]; // Large enough. If not, increase it by hand.
235 #endif
236 size_t MsgLen;
237 va_list Parameters;
238
239 va_start(Parameters, Format);
240
241 #ifndef WIN2K_COMPLIANT
242 /*
243 * Retrieve the message length and if it is too long, allocate
244 * an auxiliary buffer; otherwise use the static buffer.
245 */
246 MsgLen = _vscwprintf(Format, Parameters) + 1; // NULL-terminated
247 if (MsgLen > ARRAYSIZE(StaticBuffer))
248 {
249 Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, MsgLen * sizeof(WCHAR));
250 if (Buffer == NULL)
251 {
252 /* Allocation failed, use the static buffer and display a suitable error message */
253 Buffer = StaticBuffer;
254 Format = L"DisplayMessage()\nOriginal message is too long and allocating an auxiliary buffer failed.";
255 MsgLen = wcslen(Format);
256 }
257 }
258 #else
259 MsgLen = ARRAYSIZE(Buffer);
260 #endif
261
262 /* Display the message */
263 _vsnwprintf(Buffer, MsgLen, Format, Parameters);
264 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
265 MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
266
267 #ifndef WIN2K_COMPLIANT
268 /* Free the buffer if needed */
269 if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
270 #endif
271
272 va_end(Parameters);
273 }
274
275 static BOOL
276 WINAPI
277 ConsoleCtrlHandler(DWORD ControlType)
278 {
279 // HACK: Should be removed!
280 #ifndef STANDALONE
281 extern BOOLEAN AcceptCommands;
282 extern HANDLE CommandThread;
283 #endif
284
285 switch (ControlType)
286 {
287 case CTRL_LAST_CLOSE_EVENT:
288 {
289 if (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
290 {
291 /* Nothing runs, so exit immediately */
292 #ifndef STANDALONE
293 if (CommandThread) TerminateThread(CommandThread, 0);
294 #endif
295 EmulatorTerminate();
296 }
297 #ifndef STANDALONE
298 else
299 {
300 /* A command is running, let it run, but stop accepting new commands */
301 AcceptCommands = FALSE;
302 }
303 #endif
304
305 break;
306 }
307
308 default:
309 {
310 /* Stop the VDM if the user logs out or closes the console */
311 EmulatorTerminate();
312 }
313 }
314 return TRUE;
315 }
316
317 static VOID
318 ConsoleInitUI(VOID)
319 {
320 CreateVdmMenu(ConsoleOutput);
321 }
322
323 static VOID
324 ConsoleCleanupUI(VOID)
325 {
326 /* Display again properly the mouse pointer */
327 if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
328
329 DestroyVdmMenu();
330 }
331
332 BOOL
333 ConsoleAttach(VOID)
334 {
335 /* Save the original input and output console modes */
336 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
337 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
338 {
339 CloseHandle(ConsoleOutput);
340 CloseHandle(ConsoleInput);
341 wprintf(L"FATAL: Cannot save console in/out modes\n");
342 // return FALSE;
343 }
344
345 /* Set the console input mode */
346 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
347 // upon console window events (screen buffer resize, ...).
348 SetConsoleMode(ConsoleInput, 0 /* | ENABLE_WINDOW_INPUT */);
349 EnableExtraHardware(ConsoleInput);
350
351 /* Set the console output mode */
352 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
353
354 /* Initialize the UI */
355 ConsoleInitUI();
356
357 return TRUE;
358 }
359
360 VOID
361 ConsoleDetach(VOID)
362 {
363 /* Restore the original input and output console modes */
364 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
365 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
366
367 /* Cleanup the UI */
368 ConsoleCleanupUI();
369 }
370
371 static BOOL
372 ConsoleInit(VOID)
373 {
374 /* Set the handler routine */
375 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
376
377 /* Enable the CTRL_LAST_CLOSE_EVENT */
378 SetLastConsoleEventActive();
379
380 /*
381 * NOTE: The CONIN$ and CONOUT$ "virtual" files
382 * always point to non-redirected console handles.
383 */
384
385 /* Get the input handle to the real console, and check for success */
386 ConsoleInput = CreateFileW(L"CONIN$",
387 GENERIC_READ | GENERIC_WRITE,
388 FILE_SHARE_READ | FILE_SHARE_WRITE,
389 NULL,
390 OPEN_EXISTING,
391 0,
392 NULL);
393 if (ConsoleInput == INVALID_HANDLE_VALUE)
394 {
395 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
396 return FALSE;
397 }
398
399 /* Get the output handle to the real console, and check for success */
400 ConsoleOutput = CreateFileW(L"CONOUT$",
401 GENERIC_READ | GENERIC_WRITE,
402 FILE_SHARE_READ | FILE_SHARE_WRITE,
403 NULL,
404 OPEN_EXISTING,
405 0,
406 NULL);
407 if (ConsoleOutput == INVALID_HANDLE_VALUE)
408 {
409 CloseHandle(ConsoleInput);
410 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
411 return FALSE;
412 }
413
414 /* Effectively attach to the console */
415 return ConsoleAttach();
416 }
417
418 static VOID
419 ConsoleCleanup(VOID)
420 {
421 /* Detach from the console */
422 ConsoleDetach();
423
424 /* Close the console handles */
425 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
426 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
427 }
428
429 VOID MenuEventHandler(PMENU_EVENT_RECORD MenuEvent)
430 {
431 switch (MenuEvent->dwCommandId)
432 {
433 case ID_SHOWHIDE_MOUSE:
434 ShowHideMousePointer(ConsoleOutput, ShowPointer);
435 ShowPointer = !ShowPointer;
436 break;
437
438 case ID_VDM_DUMPMEM_TXT:
439 DumpMemory(TRUE);
440 break;
441
442 case ID_VDM_DUMPMEM_BIN:
443 DumpMemory(FALSE);
444 break;
445
446 case ID_VDM_QUIT:
447 /* Stop the VDM */
448 EmulatorTerminate();
449 break;
450
451 default:
452 break;
453 }
454 }
455
456 VOID FocusEventHandler(PFOCUS_EVENT_RECORD FocusEvent)
457 {
458 DPRINT1("Focus events not handled\n");
459 }
460
461 static BOOL
462 LoadGlobalSettings(VOID)
463 {
464 // FIXME: These strings should be localized.
465 #define ERROR_MEMORYVDD L"Insufficient memory to load installable Virtual Device Drivers."
466 #define ERROR_REGVDD L"Virtual Device Driver format in the registry is invalid."
467 #define ERROR_LOADVDD L"An installable Virtual Device Driver failed Dll initialization."
468
469 BOOL Success = TRUE;
470 LONG Error = 0;
471
472 HKEY hNTVDMKey;
473 LPCWSTR NTVDMKeyName = L"SYSTEM\\CurrentControlSet\\Control\\NTVDM";
474
475 /* Try to open the NTVDM registry key */
476 Error = RegOpenKeyExW(HKEY_LOCAL_MACHINE,
477 NTVDMKeyName,
478 0,
479 KEY_QUERY_VALUE,
480 &hNTVDMKey);
481 if (Error == ERROR_FILE_NOT_FOUND)
482 {
483 /* If the key just doesn't exist, don't do anything else */
484 return TRUE;
485 }
486 else if (Error != ERROR_SUCCESS)
487 {
488 /* The key exists but there was an access error: display an error and quit */
489 DisplayMessage(ERROR_REGVDD);
490 return FALSE;
491 }
492
493 /*
494 * Now we can do:
495 * - CPU core choice
496 * - Video choice
497 * - Sound choice
498 * - Mem?
499 * - ...
500 * - Standalone mode?
501 * - Debug settings
502 */
503
504 // Quit:
505 RegCloseKey(hNTVDMKey);
506 return Success;
507 }
508
509 INT
510 wmain(INT argc, WCHAR *argv[])
511 {
512 NtVdmArgc = argc;
513 NtVdmArgv = argv;
514
515 #ifdef STANDALONE
516
517 if (argc < 2)
518 {
519 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
520 L"Usage: NTVDM <executable> [<parameters>]\n");
521 return 0;
522 }
523
524 #endif
525
526 /* Load global VDM settings */
527 LoadGlobalSettings();
528
529 DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
530
531 /* Create the task event */
532 VdmTaskEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
533 ASSERT(VdmTaskEvent != NULL);
534
535 /* Initialize the console */
536 if (!ConsoleInit())
537 {
538 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
539 goto Cleanup;
540 }
541
542 /* Initialize the emulator */
543 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
544 {
545 wprintf(L"FATAL: Failed to initialize the emulator\n");
546 goto Cleanup;
547 }
548
549 /* Initialize the system BIOS */
550 if (!BiosInitialize(NULL))
551 {
552 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
553 goto Cleanup;
554 }
555
556 /* Let's go! Start simulation */
557 CpuSimulate();
558
559 Cleanup:
560 BiosCleanup();
561 EmulatorCleanup();
562 ConsoleCleanup();
563
564 #ifndef STANDALONE
565 ExitVDM(FALSE, 0);
566 #endif
567
568 /* Quit the VDM */
569 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
570 /* Some VDDs rely on the fact that NTVDM calls ExitProcess on Windows */
571 ExitProcess(0);
572 return 0;
573 }
574
575 /* EOF */