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