[NTVDM]
[reactos.git] / reactos / subsystems / 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 "clock.h"
17 #include "hardware/ps2.h"
18 #include "hardware/vga.h"
19 #include "bios/bios.h"
20 #include "dos/dem.h"
21
22 #include "resource.h"
23
24 /* VARIABLES ******************************************************************/
25
26 static HANDLE ConsoleInput = INVALID_HANDLE_VALUE;
27 static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
28 static DWORD OrgConsoleInputMode, OrgConsoleOutputMode;
29 static BOOLEAN AcceptCommands = TRUE;
30 static HANDLE CommandThread = NULL;
31
32 static HMENU hConsoleMenu = NULL;
33 static INT VdmMenuPos = -1;
34 static BOOLEAN ShowPointer = FALSE;
35
36 #ifndef STANDALONE
37 ULONG SessionId = 0;
38 #endif
39
40 HANDLE VdmTaskEvent = NULL;
41
42 /*
43 * Those menu helpers were taken from the GUI frontend in winsrv.dll
44 */
45 typedef struct _VDM_MENUITEM
46 {
47 UINT uID;
48 const struct _VDM_MENUITEM *SubMenu;
49 WORD wCmdID;
50 } VDM_MENUITEM, *PVDM_MENUITEM;
51
52 static const VDM_MENUITEM VdmMenuItems[] =
53 {
54 { IDS_VDM_DUMPMEM, NULL, ID_VDM_DUMPMEM },
55 { IDS_VDM_QUIT , NULL, ID_VDM_QUIT },
56
57 { 0, NULL, 0 } /* End of list */
58 };
59
60 static const VDM_MENUITEM VdmMainMenuItems[] =
61 {
62 { -1, NULL, 0 }, /* Separator */
63 { IDS_HIDE_MOUSE, NULL, ID_SHOWHIDE_MOUSE }, /* Hide mouse; can be renamed to Show mouse */
64 { IDS_VDM_MENU , VdmMenuItems, 0 }, /* ReactOS VDM Menu */
65
66 { 0, NULL, 0 } /* End of list */
67 };
68
69 static VOID
70 AppendMenuItems(HMENU hMenu,
71 const VDM_MENUITEM *Items)
72 {
73 UINT i = 0;
74 WCHAR szMenuString[255];
75 HMENU hSubMenu;
76
77 do
78 {
79 if (Items[i].uID != (UINT)-1)
80 {
81 if (LoadStringW(GetModuleHandle(NULL),
82 Items[i].uID,
83 szMenuString,
84 sizeof(szMenuString) / sizeof(szMenuString[0])) > 0)
85 {
86 if (Items[i].SubMenu != NULL)
87 {
88 hSubMenu = CreatePopupMenu();
89 if (hSubMenu != NULL)
90 {
91 AppendMenuItems(hSubMenu, Items[i].SubMenu);
92
93 if (!AppendMenuW(hMenu,
94 MF_STRING | MF_POPUP,
95 (UINT_PTR)hSubMenu,
96 szMenuString))
97 {
98 DestroyMenu(hSubMenu);
99 }
100 }
101 }
102 else
103 {
104 AppendMenuW(hMenu,
105 MF_STRING,
106 Items[i].wCmdID,
107 szMenuString);
108 }
109 }
110 }
111 else
112 {
113 AppendMenuW(hMenu,
114 MF_SEPARATOR,
115 0,
116 NULL);
117 }
118 i++;
119 } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
120 }
121
122 static VOID
123 CreateVdmMenu(HANDLE ConOutHandle)
124 {
125 hConsoleMenu = ConsoleMenuControl(ConOutHandle,
126 ID_SHOWHIDE_MOUSE,
127 ID_VDM_QUIT);
128 if (hConsoleMenu == NULL) return;
129
130 VdmMenuPos = GetMenuItemCount(hConsoleMenu);
131 AppendMenuItems(hConsoleMenu, VdmMainMenuItems);
132 DrawMenuBar(GetConsoleWindow());
133 }
134
135 static VOID
136 DestroyVdmMenu(VOID)
137 {
138 UINT i = 0;
139 const VDM_MENUITEM *Items = VdmMainMenuItems;
140
141 do
142 {
143 DeleteMenu(hConsoleMenu, VdmMenuPos, MF_BYPOSITION);
144 i++;
145 } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
146
147 DrawMenuBar(GetConsoleWindow());
148 }
149
150 static VOID ShowHideMousePointer(HANDLE ConOutHandle, BOOLEAN ShowPtr)
151 {
152 WCHAR szMenuString[255] = L"";
153
154 if (ShowPtr)
155 {
156 /* Be sure the cursor will be shown */
157 while (ShowConsoleCursor(ConOutHandle, TRUE) < 0) ;
158 }
159 else
160 {
161 /* Be sure the cursor will be hidden */
162 while (ShowConsoleCursor(ConOutHandle, FALSE) >= 0) ;
163 }
164
165 if (LoadStringW(GetModuleHandle(NULL),
166 (!ShowPtr ? IDS_SHOW_MOUSE : IDS_HIDE_MOUSE),
167 szMenuString,
168 sizeof(szMenuString) / sizeof(szMenuString[0])) > 0)
169 {
170 ModifyMenu(hConsoleMenu, ID_SHOWHIDE_MOUSE,
171 MF_BYCOMMAND, ID_SHOWHIDE_MOUSE, szMenuString);
172 }
173 }
174
175 /* PUBLIC FUNCTIONS ***********************************************************/
176
177 VOID
178 DisplayMessage(LPCWSTR Format, ...)
179 {
180 WCHAR Buffer[256];
181 va_list Parameters;
182
183 va_start(Parameters, Format);
184 _vsnwprintf(Buffer, 256, Format, Parameters);
185 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
186 MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
187 va_end(Parameters);
188 }
189
190 static BOOL
191 WINAPI
192 ConsoleCtrlHandler(DWORD ControlType)
193 {
194 switch (ControlType)
195 {
196 case CTRL_C_EVENT:
197 case CTRL_BREAK_EVENT:
198 {
199 /* Call INT 23h */
200 EmulatorInterrupt(0x23);
201 break;
202 }
203 case CTRL_LAST_CLOSE_EVENT:
204 {
205 if (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
206 {
207 /* Exit immediately */
208 if (CommandThread) TerminateThread(CommandThread, 0);
209 EmulatorTerminate();
210 }
211 else
212 {
213 /* Stop accepting new commands */
214 AcceptCommands = FALSE;
215 }
216
217 break;
218 }
219 default:
220 {
221 /* Stop the VDM if the user logs out or closes the console */
222 EmulatorTerminate();
223 }
224 }
225 return TRUE;
226 }
227
228 static VOID
229 ConsoleInitUI(VOID)
230 {
231 CreateVdmMenu(ConsoleOutput);
232 }
233
234 static VOID
235 ConsoleCleanupUI(VOID)
236 {
237 /* Display again properly the mouse pointer */
238 if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
239
240 DestroyVdmMenu();
241 }
242
243 static BOOL
244 ConsoleAttach(VOID)
245 {
246 /* Save the original input and output console modes */
247 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
248 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
249 {
250 CloseHandle(ConsoleOutput);
251 CloseHandle(ConsoleInput);
252 wprintf(L"FATAL: Cannot save console in/out modes\n");
253 // return FALSE;
254 }
255
256 /* Initialize the UI */
257 ConsoleInitUI();
258
259 return TRUE;
260 }
261
262 static VOID
263 ConsoleDetach(VOID)
264 {
265 /* Restore the original input and output console modes */
266 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
267 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
268
269 /* Cleanup the UI */
270 ConsoleCleanupUI();
271 }
272
273 static BOOL
274 ConsoleInit(VOID)
275 {
276 /* Set the handler routine */
277 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
278
279 /* Enable the CTRL_LAST_CLOSE_EVENT */
280 SetLastConsoleEventActive();
281
282 /*
283 * NOTE: The CONIN$ and CONOUT$ "virtual" files
284 * always point to non-redirected console handles.
285 */
286
287 /* Get the input handle to the real console, and check for success */
288 ConsoleInput = CreateFileW(L"CONIN$",
289 GENERIC_READ | GENERIC_WRITE,
290 FILE_SHARE_READ | FILE_SHARE_WRITE,
291 NULL,
292 OPEN_EXISTING,
293 0,
294 NULL);
295 if (ConsoleInput == INVALID_HANDLE_VALUE)
296 {
297 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
298 return FALSE;
299 }
300
301 /* Get the output handle to the real console, and check for success */
302 ConsoleOutput = CreateFileW(L"CONOUT$",
303 GENERIC_READ | GENERIC_WRITE,
304 FILE_SHARE_READ | FILE_SHARE_WRITE,
305 NULL,
306 OPEN_EXISTING,
307 0,
308 NULL);
309 if (ConsoleOutput == INVALID_HANDLE_VALUE)
310 {
311 CloseHandle(ConsoleInput);
312 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
313 return FALSE;
314 }
315
316 /* Effectively attach to the console */
317 return ConsoleAttach();
318 }
319
320 static VOID
321 ConsoleCleanup(VOID)
322 {
323 /* Detach from the console */
324 ConsoleDetach();
325
326 /* Close the console handles */
327 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
328 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
329 }
330
331 DWORD
332 WINAPI
333 PumpConsoleInput(LPVOID Parameter)
334 {
335 HANDLE ConsoleInput = (HANDLE)Parameter;
336 INPUT_RECORD InputRecord;
337 DWORD Count;
338
339 while (VdmRunning)
340 {
341 /* Make sure the task event is signaled */
342 WaitForSingleObject(VdmTaskEvent, INFINITE);
343
344 /* Wait for an input record */
345 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
346 {
347 DWORD LastError = GetLastError();
348 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
349 return LastError;
350 }
351
352 ASSERT(Count != 0);
353
354 /* Check the event type */
355 switch (InputRecord.EventType)
356 {
357 case KEY_EVENT:
358 case MOUSE_EVENT:
359 /* Send it to the PS/2 controller */
360 PS2Dispatch(&InputRecord);
361 break;
362
363 case MENU_EVENT:
364 {
365 switch (InputRecord.Event.MenuEvent.dwCommandId)
366 {
367 case ID_SHOWHIDE_MOUSE:
368 ShowHideMousePointer(ConsoleOutput, ShowPointer);
369 ShowPointer = !ShowPointer;
370 break;
371
372 case ID_VDM_DUMPMEM:
373 DumpMemory();
374 break;
375
376 case ID_VDM_QUIT:
377 /* Stop the VDM */
378 EmulatorTerminate();
379 break;
380
381 default:
382 break;
383 }
384
385 break;
386 }
387
388 default:
389 break;
390 }
391 }
392
393 return 0;
394 }
395
396 #ifndef STANDALONE
397 static DWORD
398 WINAPI
399 CommandThreadProc(LPVOID Parameter)
400 {
401 BOOLEAN First = TRUE;
402 DWORD Result;
403 VDM_COMMAND_INFO CommandInfo;
404 CHAR CmdLine[MAX_PATH];
405 CHAR AppName[MAX_PATH];
406 CHAR PifFile[MAX_PATH];
407 CHAR Desktop[MAX_PATH];
408 CHAR Title[MAX_PATH];
409 ULONG EnvSize = 256;
410 PVOID Env = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
411
412 UNREFERENCED_PARAMETER(Parameter);
413 ASSERT(Env != NULL);
414
415 do
416 {
417 /* Clear the structure */
418 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
419
420 /* Initialize the structure members */
421 CommandInfo.TaskId = SessionId;
422 CommandInfo.VDMState = VDM_FLAG_DOS;
423 CommandInfo.CmdLine = CmdLine;
424 CommandInfo.CmdLen = sizeof(CmdLine);
425 CommandInfo.AppName = AppName;
426 CommandInfo.AppLen = sizeof(AppName);
427 CommandInfo.PifFile = PifFile;
428 CommandInfo.PifLen = sizeof(PifFile);
429 CommandInfo.Desktop = Desktop;
430 CommandInfo.DesktopLen = sizeof(Desktop);
431 CommandInfo.Title = Title;
432 CommandInfo.TitleLen = sizeof(Title);
433 CommandInfo.Env = Env;
434 CommandInfo.EnvLen = EnvSize;
435
436 if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
437
438 if (!GetNextVDMCommand(&CommandInfo))
439 {
440 if (CommandInfo.EnvLen > EnvSize)
441 {
442 /* Expand the environment size */
443 EnvSize = CommandInfo.EnvLen;
444 Env = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
445
446 continue;
447 }
448
449 break;
450 }
451
452 /* Start the process from the command line */
453 DPRINT1("Starting '%s' ('%s')...\n", AppName, CmdLine);
454 Result = DosStartProcess(AppName, CmdLine, Env);
455 if (Result != ERROR_SUCCESS)
456 {
457 DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result);
458 // break;
459 continue;
460 }
461
462 First = FALSE;
463 }
464 while (AcceptCommands);
465
466 HeapFree(GetProcessHeap(), 0, Env);
467 return 0;
468 }
469 #endif
470
471 INT
472 wmain(INT argc, WCHAR *argv[])
473 {
474 #ifdef STANDALONE
475
476 DWORD Result;
477 CHAR ApplicationName[MAX_PATH];
478 CHAR CommandLine[DOS_CMDLINE_LENGTH];
479
480 if (argc >= 2)
481 {
482 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, ApplicationName, sizeof(ApplicationName), NULL, NULL);
483
484 if (argc >= 3) WideCharToMultiByte(CP_ACP, 0, argv[2], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
485 else strcpy(CommandLine, "");
486 }
487 else
488 {
489 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
490 L"Usage: NTVDM <executable> [<parameters>]\n");
491 return 0;
492 }
493
494 #else
495
496 INT i;
497 WCHAR *endptr;
498
499 /* Parse the command line arguments */
500 for (i = 1; i < argc; i++)
501 {
502 if (wcsncmp(argv[i], L"-i", 2) == 0)
503 {
504 /* This is the session ID */
505 SessionId = wcstoul(argv[i] + 2, &endptr, 10);
506
507 /* The VDM hasn't been started from a console, so quit when the task is done */
508 AcceptCommands = FALSE;
509 }
510 }
511
512 #endif
513
514 DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
515
516 /* Create the task event */
517 VdmTaskEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
518 ASSERT(VdmTaskEvent != NULL);
519
520 /* Initialize the console */
521 if (!ConsoleInit())
522 {
523 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
524 goto Cleanup;
525 }
526
527 /* Initialize the emulator */
528 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
529 {
530 wprintf(L"FATAL: Failed to initialize the emulator\n");
531 goto Cleanup;
532 }
533
534 /* Initialize the system BIOS */
535 if (!BiosInitialize(NULL))
536 {
537 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
538 goto Cleanup;
539 }
540
541 /* Initialize the VDM DOS kernel */
542 if (!DosInitialize(NULL))
543 {
544 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
545 goto Cleanup;
546 }
547
548 #ifndef STANDALONE
549
550 /* Create the GetNextVDMCommand thread */
551 CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
552 if (CommandThread == NULL)
553 {
554 wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
555 goto Cleanup;
556 }
557
558 /* Wait for the command thread to exit */
559 WaitForSingleObject(CommandThread, INFINITE);
560
561 /* Close the thread handle */
562 CloseHandle(CommandThread);
563
564 #else
565
566 /* Start the process from the command line */
567 DPRINT1("Starting '%s' ('%s')...\n", ApplicationName, CommandLine);
568 Result = DosStartProcess(ApplicationName,
569 CommandLine,
570 GetEnvironmentStrings());
571 if (Result != ERROR_SUCCESS)
572 {
573 DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result);
574 goto Cleanup;
575 }
576
577 #endif
578
579 Cleanup:
580 BiosCleanup();
581 EmulatorCleanup();
582 ConsoleCleanup();
583
584 #ifndef STANDALONE
585 ExitVDM(FALSE, 0);
586 #endif
587
588 /* Quit the VDM */
589 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
590
591 return 0;
592 }
593
594 /* EOF */