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