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