[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 HANDLE CommandThread = NULL;
38
39 static HMENU hConsoleMenu = NULL;
40 static INT VdmMenuPos = -1;
41 static BOOLEAN ShowPointer = FALSE;
42
43 /*
44 * Those menu helpers were taken from the GUI frontend in winsrv.dll
45 */
46 typedef struct _VDM_MENUITEM
47 {
48 UINT uID;
49 const struct _VDM_MENUITEM *SubMenu;
50 WORD wCmdID;
51 } VDM_MENUITEM, *PVDM_MENUITEM;
52
53 static const VDM_MENUITEM VdmMenuItems[] =
54 {
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(ConsoleOutput,
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 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 BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
190 {
191 switch (ControlType)
192 {
193 case CTRL_C_EVENT:
194 case CTRL_BREAK_EVENT:
195 {
196 /* Call INT 23h */
197 EmulatorInterrupt(0x23);
198 break;
199 }
200 case CTRL_LAST_CLOSE_EVENT:
201 {
202 if (CommandThread) TerminateThread(CommandThread, 0);
203 break;
204 }
205 default:
206 {
207 /* Stop the VDM if the user logs out or closes the console */
208 EmulatorTerminate();
209 }
210 }
211 return TRUE;
212 }
213
214 VOID ConsoleInitUI(VOID)
215 {
216 CreateVdmMenu(ConsoleOutput);
217 }
218
219 VOID ConsoleCleanupUI(VOID)
220 {
221 /* Display again properly the mouse pointer */
222 if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
223
224 DestroyVdmMenu();
225 }
226
227 DWORD WINAPI PumpConsoleInput(LPVOID Parameter)
228 {
229 HANDLE ConsoleInput = (HANDLE)Parameter;
230 INPUT_RECORD InputRecord;
231 DWORD Count;
232
233 while (VdmRunning)
234 {
235 /* Wait for an input record */
236 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
237 {
238 DWORD LastError = GetLastError();
239 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
240 return LastError;
241 }
242
243 ASSERT(Count != 0);
244
245 /* Check the event type */
246 switch (InputRecord.EventType)
247 {
248 case KEY_EVENT:
249 case MOUSE_EVENT:
250 /* Send it to the PS/2 controller */
251 PS2Dispatch(&InputRecord);
252 break;
253
254 case MENU_EVENT:
255 {
256 switch (InputRecord.Event.MenuEvent.dwCommandId)
257 {
258 case ID_SHOWHIDE_MOUSE:
259 ShowHideMousePointer(ConsoleOutput, ShowPointer);
260 ShowPointer = !ShowPointer;
261 break;
262
263 case ID_VDM_QUIT:
264 /* Stop the VDM */
265 EmulatorTerminate();
266 break;
267
268 default:
269 break;
270 }
271
272 break;
273 }
274
275 default:
276 break;
277 }
278 }
279
280 return 0;
281 }
282
283 BOOL ConsoleInit(VOID)
284 {
285 /* Set the handler routine */
286 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
287
288 /* Enable the CTRL_LAST_CLOSE_EVENT */
289 SetLastConsoleEventActive();
290
291 /* Get the input handle to the real console, and check for success */
292 ConsoleInput = CreateFileW(L"CONIN$",
293 GENERIC_READ | GENERIC_WRITE,
294 FILE_SHARE_READ | FILE_SHARE_WRITE,
295 NULL,
296 OPEN_EXISTING,
297 0,
298 NULL);
299 if (ConsoleInput == INVALID_HANDLE_VALUE)
300 {
301 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
302 return FALSE;
303 }
304
305 /* Get the output handle to the real console, and check for success */
306 ConsoleOutput = CreateFileW(L"CONOUT$",
307 GENERIC_READ | GENERIC_WRITE,
308 FILE_SHARE_READ | FILE_SHARE_WRITE,
309 NULL,
310 OPEN_EXISTING,
311 0,
312 NULL);
313 if (ConsoleOutput == INVALID_HANDLE_VALUE)
314 {
315 CloseHandle(ConsoleInput);
316 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
317 return FALSE;
318 }
319
320 /* Save the original input and output console modes */
321 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
322 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
323 {
324 CloseHandle(ConsoleOutput);
325 CloseHandle(ConsoleInput);
326 wprintf(L"FATAL: Cannot save console in/out modes\n");
327 return FALSE;
328 }
329
330 /* Save the original cursor and console screen buffer information */
331 if (!GetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo) ||
332 !GetConsoleScreenBufferInfo(ConsoleOutput, &OrgConsoleBufferInfo))
333 {
334 CloseHandle(ConsoleOutput);
335 CloseHandle(ConsoleInput);
336 wprintf(L"FATAL: Cannot save console cursor/screen-buffer info\n");
337 return FALSE;
338 }
339
340 /* Initialize the UI */
341 ConsoleInitUI();
342
343 return TRUE;
344 }
345
346 VOID ConsoleCleanup(VOID)
347 {
348 SMALL_RECT ConRect;
349
350 /* Restore the old screen buffer */
351 SetConsoleActiveScreenBuffer(ConsoleOutput);
352
353 /* Restore the original console size */
354 ConRect.Left = 0;
355 ConRect.Top = 0;
356 ConRect.Right = ConRect.Left + OrgConsoleBufferInfo.srWindow.Right - OrgConsoleBufferInfo.srWindow.Left;
357 ConRect.Bottom = ConRect.Top + OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top ;
358 /*
359 * See the following trick explanation in vga.c:VgaEnterTextMode() .
360 */
361 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
362 SetConsoleWindowInfo(ConsoleOutput, TRUE, &ConRect);
363 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
364
365 /* Restore the original cursor shape */
366 SetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo);
367
368 /* Restore the original input and output console modes */
369 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
370 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
371
372 /* Cleanup the UI */
373 ConsoleCleanupUI();
374
375 /* Close the console handles */
376 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
377 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
378 }
379
380 DWORD WINAPI CommandThreadProc(LPVOID Parameter)
381 {
382 DWORD Result;
383 VDM_COMMAND_INFO CommandInfo;
384 CHAR CmdLine[MAX_PATH];
385 CHAR AppName[MAX_PATH];
386 CHAR PifFile[MAX_PATH];
387 CHAR Desktop[MAX_PATH];
388 CHAR Title[MAX_PATH];
389 CHAR Env[MAX_PATH];
390
391 UNREFERENCED_PARAMETER(Parameter);
392
393 while (TRUE)
394 {
395 /* Clear the structure */
396 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
397
398 /* Initialize the structure members */
399 CommandInfo.VDMState = VDM_NOT_LOADED;
400 CommandInfo.CmdLine = CmdLine;
401 CommandInfo.CmdLen = sizeof(CmdLine);
402 CommandInfo.AppName = AppName;
403 CommandInfo.AppLen = sizeof(AppName);
404 CommandInfo.PifFile = PifFile;
405 CommandInfo.PifLen = sizeof(PifFile);
406 CommandInfo.Desktop = Desktop;
407 CommandInfo.DesktopLen = sizeof(Desktop);
408 CommandInfo.Title = Title;
409 CommandInfo.TitleLen = sizeof(Title);
410 CommandInfo.Env = Env;
411 CommandInfo.EnvLen = sizeof(Env);
412
413 /* Wait for the next available VDM */
414 if (!GetNextVDMCommand(&CommandInfo)) break;
415
416 /* Start the process from the command line */
417 DPRINT1("Starting '%s'...\n", AppName);
418
419 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE, AppName, CmdLine, Env, NULL, NULL);
420 if (Result != ERROR_SUCCESS)
421 {
422 DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result);
423 break;
424 }
425
426 /* Start simulation */
427 EmulatorSimulate();
428
429 /* Perform another screen refresh */
430 VgaRefreshDisplay();
431 }
432
433 return 0;
434 }
435
436 INT wmain(INT argc, WCHAR *argv[])
437 {
438 DWORD Result;
439
440 #ifdef STANDALONE
441 CHAR ApplicationName[MAX_PATH];
442 CHAR CommandLine[DOS_CMDLINE_LENGTH];
443
444 if (argc >= 2)
445 {
446 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, ApplicationName, sizeof(ApplicationName), NULL, NULL);
447
448 if (argc >= 3) WideCharToMultiByte(CP_ACP, 0, argv[2], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
449 else strcpy(CommandLine, "");
450 }
451 else
452 {
453 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
454 L"Usage: NTVDM <executable> [<parameters>]\n");
455 return 0;
456 }
457
458 #endif
459
460 DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
461
462 /* Initialize the console */
463 if (!ConsoleInit())
464 {
465 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
466 goto Cleanup;
467 }
468
469 /* Initialize the emulator */
470 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
471 {
472 wprintf(L"FATAL: Failed to initialize the emulator\n");
473 goto Cleanup;
474 }
475
476 /* Initialize the system BIOS */
477 if (!BiosInitialize(NULL))
478 {
479 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
480 goto Cleanup;
481 }
482
483 /* Initialize the VDM DOS kernel */
484 if (!DosInitialize(NULL))
485 {
486 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
487 goto Cleanup;
488 }
489
490 #ifndef STANDALONE
491
492 /* Create the GetNextVDMCommand thread */
493 CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
494 if (CommandThread == NULL)
495 {
496 wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
497 goto Cleanup;
498 }
499
500 /* Wait for the command thread to exit */
501 WaitForSingleObject(CommandThread, INFINITE);
502
503 /* Close the thread handle */
504 CloseHandle(CommandThread);
505
506 #else
507
508 /* Start the process from the command line */
509 DPRINT1("Starting '%s'...\n", ApplicationName);
510
511 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
512 ApplicationName,
513 CommandLine,
514 GetEnvironmentStrings(),
515 NULL,
516 NULL);
517 if (Result != ERROR_SUCCESS)
518 {
519 DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result);
520 goto Cleanup;
521 }
522
523 /* Start simulation */
524 EmulatorSimulate();
525
526 /* Perform another screen refresh */
527 VgaRefreshDisplay();
528
529 #endif
530
531 Cleanup:
532 BiosCleanup();
533 EmulatorCleanup();
534 ConsoleCleanup();
535
536 #ifndef STANDALONE
537 ExitVDM(FALSE, 0);
538 #endif
539
540 /* Quit the VDM */
541 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
542
543 return 0;
544 }
545
546 /* EOF */