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