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