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