5078c6e73c56d0c723ab81cde7e1be95b2a4d8c4
[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 HANDLE VdmTaskEvent = NULL;
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 (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
204 {
205 /* Exit immediately */
206 if (CommandThread) TerminateThread(CommandThread, 0);
207 EmulatorTerminate();
208 }
209 else
210 {
211 /* Stop accepting new commands */
212 AcceptCommands = FALSE;
213 }
214
215 break;
216 }
217 default:
218 {
219 /* Stop the VDM if the user logs out or closes the console */
220 EmulatorTerminate();
221 }
222 }
223 return TRUE;
224 }
225
226 VOID ConsoleInitUI(VOID)
227 {
228 CreateVdmMenu(ConsoleOutput);
229 }
230
231 VOID ConsoleCleanupUI(VOID)
232 {
233 /* Display again properly the mouse pointer */
234 if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
235
236 DestroyVdmMenu();
237 }
238
239 DWORD WINAPI PumpConsoleInput(LPVOID Parameter)
240 {
241 HANDLE ConsoleInput = (HANDLE)Parameter;
242 INPUT_RECORD InputRecord;
243 DWORD Count;
244
245 while (VdmRunning)
246 {
247 /* Make sure the task event is signaled */
248 WaitForSingleObject(VdmTaskEvent, INFINITE);
249
250 /* Wait for an input record */
251 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
252 {
253 DWORD LastError = GetLastError();
254 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
255 return LastError;
256 }
257
258 ASSERT(Count != 0);
259
260 /* Check the event type */
261 switch (InputRecord.EventType)
262 {
263 case KEY_EVENT:
264 case MOUSE_EVENT:
265 /* Send it to the PS/2 controller */
266 PS2Dispatch(&InputRecord);
267 break;
268
269 case MENU_EVENT:
270 {
271 switch (InputRecord.Event.MenuEvent.dwCommandId)
272 {
273 case ID_SHOWHIDE_MOUSE:
274 ShowHideMousePointer(ConsoleOutput, ShowPointer);
275 ShowPointer = !ShowPointer;
276 break;
277
278 case ID_VDM_QUIT:
279 /* Stop the VDM */
280 EmulatorTerminate();
281 break;
282
283 default:
284 break;
285 }
286
287 break;
288 }
289
290 default:
291 break;
292 }
293 }
294
295 return 0;
296 }
297
298 BOOL ConsoleInit(VOID)
299 {
300 /* Set the handler routine */
301 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
302
303 /* Enable the CTRL_LAST_CLOSE_EVENT */
304 SetLastConsoleEventActive();
305
306 /* Get the input handle to the real console, and check for success */
307 ConsoleInput = CreateFileW(L"CONIN$",
308 GENERIC_READ | GENERIC_WRITE,
309 FILE_SHARE_READ | FILE_SHARE_WRITE,
310 NULL,
311 OPEN_EXISTING,
312 0,
313 NULL);
314 if (ConsoleInput == INVALID_HANDLE_VALUE)
315 {
316 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
317 return FALSE;
318 }
319
320 /* Get the output handle to the real console, and check for success */
321 ConsoleOutput = CreateFileW(L"CONOUT$",
322 GENERIC_READ | GENERIC_WRITE,
323 FILE_SHARE_READ | FILE_SHARE_WRITE,
324 NULL,
325 OPEN_EXISTING,
326 0,
327 NULL);
328 if (ConsoleOutput == INVALID_HANDLE_VALUE)
329 {
330 CloseHandle(ConsoleInput);
331 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
332 return FALSE;
333 }
334
335 /* Save the original input and output console modes */
336 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
337 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
338 {
339 CloseHandle(ConsoleOutput);
340 CloseHandle(ConsoleInput);
341 wprintf(L"FATAL: Cannot save console in/out modes\n");
342 return FALSE;
343 }
344
345 /* Initialize the UI */
346 ConsoleInitUI();
347
348 return TRUE;
349 }
350
351 VOID ConsoleCleanup(VOID)
352 {
353 /* Restore the original input and output console modes */
354 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
355 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
356
357 /* Cleanup the UI */
358 ConsoleCleanupUI();
359
360 /* Close the console handles */
361 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
362 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
363 }
364
365 DWORD WINAPI CommandThreadProc(LPVOID Parameter)
366 {
367 BOOLEAN First = TRUE;
368 DWORD Result;
369 VDM_COMMAND_INFO CommandInfo;
370 CHAR CmdLine[MAX_PATH];
371 CHAR AppName[MAX_PATH];
372 CHAR PifFile[MAX_PATH];
373 CHAR Desktop[MAX_PATH];
374 CHAR Title[MAX_PATH];
375 CHAR Env[MAX_PATH];
376
377 UNREFERENCED_PARAMETER(Parameter);
378
379 while (AcceptCommands)
380 {
381 /* Clear the structure */
382 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
383
384 /* Initialize the structure members */
385 CommandInfo.VDMState = VDM_FLAG_DOS;
386 CommandInfo.CmdLine = CmdLine;
387 CommandInfo.CmdLen = sizeof(CmdLine);
388 CommandInfo.AppName = AppName;
389 CommandInfo.AppLen = sizeof(AppName);
390 CommandInfo.PifFile = PifFile;
391 CommandInfo.PifLen = sizeof(PifFile);
392 CommandInfo.Desktop = Desktop;
393 CommandInfo.DesktopLen = sizeof(Desktop);
394 CommandInfo.Title = Title;
395 CommandInfo.TitleLen = sizeof(Title);
396 CommandInfo.Env = Env;
397 CommandInfo.EnvLen = sizeof(Env);
398
399 if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
400
401 /* Wait for the next available VDM */
402 if (!GetNextVDMCommand(&CommandInfo)) break;
403
404 /* Start the process from the command line */
405 DPRINT1("Starting '%s'...\n", AppName);
406
407 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE, AppName, CmdLine, Env, NULL, NULL);
408 if (Result != ERROR_SUCCESS)
409 {
410 DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result);
411 break;
412 }
413
414 /* Attach to the console */
415 if (!First) VgaAttachToConsole();
416
417 /* Perform a screen refresh */
418 VgaRefreshDisplay();
419
420 /* Start simulation */
421 SetEvent(VdmTaskEvent);
422 EmulatorSimulate();
423
424 /* Perform another screen refresh */
425 VgaRefreshDisplay();
426
427 /* Detach from the console */
428 VgaDetachFromConsole(FALSE);
429
430 First = FALSE;
431 }
432
433 return 0;
434 }
435
436 INT wmain(INT argc, WCHAR *argv[])
437 {
438 #ifdef STANDALONE
439
440 DWORD Result;
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 /* Create the task event */
463 VdmTaskEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
464 ASSERT(VdmTaskEvent != NULL);
465
466 /* Initialize the console */
467 if (!ConsoleInit())
468 {
469 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
470 goto Cleanup;
471 }
472
473 /* Initialize the emulator */
474 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
475 {
476 wprintf(L"FATAL: Failed to initialize the emulator\n");
477 goto Cleanup;
478 }
479
480 /* Initialize the system BIOS */
481 if (!BiosInitialize(NULL))
482 {
483 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
484 goto Cleanup;
485 }
486
487 /* Initialize the VDM DOS kernel */
488 if (!DosInitialize(NULL))
489 {
490 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
491 goto Cleanup;
492 }
493
494 #ifndef STANDALONE
495
496 /* Create the GetNextVDMCommand thread */
497 CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
498 if (CommandThread == NULL)
499 {
500 wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
501 goto Cleanup;
502 }
503
504 /* Wait for the command thread to exit */
505 WaitForSingleObject(CommandThread, INFINITE);
506
507 /* Close the thread handle */
508 CloseHandle(CommandThread);
509
510 #else
511
512 /* Start the process from the command line */
513 DPRINT1("Starting '%s'...\n", ApplicationName);
514
515 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
516 ApplicationName,
517 CommandLine,
518 GetEnvironmentStrings(),
519 NULL,
520 NULL);
521 if (Result != ERROR_SUCCESS)
522 {
523 DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result);
524 goto Cleanup;
525 }
526
527 /* Start simulation */
528 SetEvent(VdmTaskEvent);
529 EmulatorSimulate();
530
531 /* Perform another screen refresh */
532 VgaRefreshDisplay();
533
534 #endif
535
536 Cleanup:
537 BiosCleanup();
538 EmulatorCleanup();
539 ConsoleCleanup();
540
541 #ifndef STANDALONE
542 ExitVDM(FALSE, 0);
543 #endif
544
545 /* Quit the VDM */
546 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
547
548 return 0;
549 }
550
551 /* EOF */