[NTVDM]
[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_DUMPMEM_TXT, NULL, ID_VDM_DUMPMEM_TXT },
55 { IDS_VDM_DUMPMEM_BIN, NULL, ID_VDM_DUMPMEM_BIN },
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(ConOutHandle,
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
179 DisplayMessage(LPCWSTR Format, ...)
180 {
181 WCHAR Buffer[256];
182 va_list Parameters;
183
184 va_start(Parameters, Format);
185 _vsnwprintf(Buffer, 256, Format, Parameters);
186 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
187 MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
188 va_end(Parameters);
189 }
190
191 static BOOL
192 WINAPI
193 ConsoleCtrlHandler(DWORD ControlType)
194 {
195 switch (ControlType)
196 {
197 case CTRL_C_EVENT:
198 case CTRL_BREAK_EVENT:
199 {
200 /* Call INT 23h */
201 EmulatorInterrupt(0x23);
202 break;
203 }
204 case CTRL_LAST_CLOSE_EVENT:
205 {
206 if (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
207 {
208 /* Exit immediately */
209 if (CommandThread) TerminateThread(CommandThread, 0);
210 EmulatorTerminate();
211 }
212 else
213 {
214 /* Stop accepting new commands */
215 AcceptCommands = FALSE;
216 }
217
218 break;
219 }
220 default:
221 {
222 /* Stop the VDM if the user logs out or closes the console */
223 EmulatorTerminate();
224 }
225 }
226 return TRUE;
227 }
228
229 static VOID
230 ConsoleInitUI(VOID)
231 {
232 CreateVdmMenu(ConsoleOutput);
233 }
234
235 static VOID
236 ConsoleCleanupUI(VOID)
237 {
238 /* Display again properly the mouse pointer */
239 if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
240
241 DestroyVdmMenu();
242 }
243
244 static BOOL
245 ConsoleAttach(VOID)
246 {
247 /* Save the original input and output console modes */
248 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
249 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
250 {
251 CloseHandle(ConsoleOutput);
252 CloseHandle(ConsoleInput);
253 wprintf(L"FATAL: Cannot save console in/out modes\n");
254 // return FALSE;
255 }
256
257 /* Initialize the UI */
258 ConsoleInitUI();
259
260 return TRUE;
261 }
262
263 static VOID
264 ConsoleDetach(VOID)
265 {
266 /* Restore the original input and output console modes */
267 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
268 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
269
270 /* Cleanup the UI */
271 ConsoleCleanupUI();
272 }
273
274 static BOOL
275 ConsoleInit(VOID)
276 {
277 /* Set the handler routine */
278 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
279
280 /* Enable the CTRL_LAST_CLOSE_EVENT */
281 SetLastConsoleEventActive();
282
283 /*
284 * NOTE: The CONIN$ and CONOUT$ "virtual" files
285 * always point to non-redirected console handles.
286 */
287
288 /* Get the input handle to the real console, and check for success */
289 ConsoleInput = CreateFileW(L"CONIN$",
290 GENERIC_READ | GENERIC_WRITE,
291 FILE_SHARE_READ | FILE_SHARE_WRITE,
292 NULL,
293 OPEN_EXISTING,
294 0,
295 NULL);
296 if (ConsoleInput == INVALID_HANDLE_VALUE)
297 {
298 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
299 return FALSE;
300 }
301
302 /* Get the output handle to the real console, and check for success */
303 ConsoleOutput = CreateFileW(L"CONOUT$",
304 GENERIC_READ | GENERIC_WRITE,
305 FILE_SHARE_READ | FILE_SHARE_WRITE,
306 NULL,
307 OPEN_EXISTING,
308 0,
309 NULL);
310 if (ConsoleOutput == INVALID_HANDLE_VALUE)
311 {
312 CloseHandle(ConsoleInput);
313 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
314 return FALSE;
315 }
316
317 /* Effectively attach to the console */
318 return ConsoleAttach();
319 }
320
321 static VOID
322 ConsoleCleanup(VOID)
323 {
324 /* Detach from the console */
325 ConsoleDetach();
326
327 /* Close the console handles */
328 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
329 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
330 }
331
332 VOID MenuEventHandler(PMENU_EVENT_RECORD MenuEvent)
333 {
334 switch (MenuEvent->dwCommandId)
335 {
336 case ID_SHOWHIDE_MOUSE:
337 ShowHideMousePointer(ConsoleOutput, ShowPointer);
338 ShowPointer = !ShowPointer;
339 break;
340
341 case ID_VDM_DUMPMEM_TXT:
342 DumpMemory(TRUE);
343 break;
344
345 case ID_VDM_DUMPMEM_BIN:
346 DumpMemory(FALSE);
347 break;
348
349 case ID_VDM_QUIT:
350 /* Stop the VDM */
351 EmulatorTerminate();
352 break;
353
354 default:
355 break;
356 }
357 }
358
359 VOID FocusEventHandler(PFOCUS_EVENT_RECORD FocusEvent)
360 {
361 DPRINT1("Focus events not handled\n");
362 }
363
364 #ifndef STANDALONE
365 static DWORD
366 WINAPI
367 CommandThreadProc(LPVOID Parameter)
368 {
369 BOOLEAN First = TRUE;
370 DWORD Result;
371 VDM_COMMAND_INFO CommandInfo;
372 CHAR CmdLine[MAX_PATH];
373 CHAR AppName[MAX_PATH];
374 CHAR PifFile[MAX_PATH];
375 CHAR Desktop[MAX_PATH];
376 CHAR Title[MAX_PATH];
377 ULONG EnvSize = 256;
378 PVOID Env = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
379
380 UNREFERENCED_PARAMETER(Parameter);
381 ASSERT(Env != NULL);
382
383 do
384 {
385 /* Clear the structure */
386 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
387
388 /* Initialize the structure members */
389 CommandInfo.TaskId = SessionId;
390 CommandInfo.VDMState = VDM_FLAG_DOS;
391 CommandInfo.CmdLine = CmdLine;
392 CommandInfo.CmdLen = sizeof(CmdLine);
393 CommandInfo.AppName = AppName;
394 CommandInfo.AppLen = sizeof(AppName);
395 CommandInfo.PifFile = PifFile;
396 CommandInfo.PifLen = sizeof(PifFile);
397 CommandInfo.Desktop = Desktop;
398 CommandInfo.DesktopLen = sizeof(Desktop);
399 CommandInfo.Title = Title;
400 CommandInfo.TitleLen = sizeof(Title);
401 CommandInfo.Env = Env;
402 CommandInfo.EnvLen = EnvSize;
403
404 if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
405
406 Command:
407 if (!GetNextVDMCommand(&CommandInfo))
408 {
409 if (CommandInfo.EnvLen > EnvSize)
410 {
411 /* Expand the environment size */
412 EnvSize = CommandInfo.EnvLen;
413 Env = HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
414
415 /* Repeat the request */
416 goto Command;
417 }
418
419 break;
420 }
421
422 /* Start the process from the command line */
423 DPRINT1("Starting '%s' ('%s')...\n", AppName, CmdLine);
424 Result = DosStartProcess(AppName, CmdLine, Env);
425 if (Result != ERROR_SUCCESS)
426 {
427 DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result);
428 // break;
429 continue;
430 }
431
432 First = FALSE;
433 }
434 while (AcceptCommands);
435
436 HeapFree(GetProcessHeap(), 0, Env);
437 return 0;
438 }
439 #endif
440
441 INT
442 wmain(INT argc, WCHAR *argv[])
443 {
444 #ifdef STANDALONE
445
446 DWORD Result;
447 CHAR ApplicationName[MAX_PATH];
448 CHAR CommandLine[DOS_CMDLINE_LENGTH];
449
450 if (argc >= 2)
451 {
452 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, ApplicationName, sizeof(ApplicationName), NULL, NULL);
453
454 if (argc >= 3) WideCharToMultiByte(CP_ACP, 0, argv[2], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
455 else strcpy(CommandLine, "");
456 }
457 else
458 {
459 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
460 L"Usage: NTVDM <executable> [<parameters>]\n");
461 return 0;
462 }
463
464 #else
465
466 INT i;
467 WCHAR *endptr;
468
469 /* Parse the command line arguments */
470 for (i = 1; i < argc; i++)
471 {
472 if (wcsncmp(argv[i], L"-i", 2) == 0)
473 {
474 /* This is the session ID */
475 SessionId = wcstoul(argv[i] + 2, &endptr, 10);
476
477 /* The VDM hasn't been started from a console, so quit when the task is done */
478 AcceptCommands = FALSE;
479 }
480 }
481
482 #endif
483
484 DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
485
486 /* Create the task event */
487 VdmTaskEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
488 ASSERT(VdmTaskEvent != NULL);
489
490 /* Initialize the console */
491 if (!ConsoleInit())
492 {
493 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
494 goto Cleanup;
495 }
496
497 /* Initialize the emulator */
498 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
499 {
500 wprintf(L"FATAL: Failed to initialize the emulator\n");
501 goto Cleanup;
502 }
503
504 /* Initialize the system BIOS */
505 if (!BiosInitialize(NULL))
506 {
507 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
508 goto Cleanup;
509 }
510
511 /* Initialize the VDM DOS kernel */
512 if (!DosInitialize(NULL))
513 {
514 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
515 goto Cleanup;
516 }
517
518 #ifndef STANDALONE
519
520 /* Create the GetNextVDMCommand thread */
521 CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
522 if (CommandThread == NULL)
523 {
524 wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
525 goto Cleanup;
526 }
527
528 /* Wait for the command thread to exit */
529 WaitForSingleObject(CommandThread, INFINITE);
530
531 /* Close the thread handle */
532 CloseHandle(CommandThread);
533
534 #else
535
536 /* Start the process from the command line */
537 DPRINT1("Starting '%s' ('%s')...\n", ApplicationName, CommandLine);
538 Result = DosStartProcess(ApplicationName,
539 CommandLine,
540 GetEnvironmentStrings());
541 if (Result != ERROR_SUCCESS)
542 {
543 DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result);
544 goto Cleanup;
545 }
546
547 #endif
548
549 Cleanup:
550 BiosCleanup();
551 EmulatorCleanup();
552 ConsoleCleanup();
553
554 #ifndef STANDALONE
555 ExitVDM(FALSE, 0);
556 #endif
557
558 /* Quit the VDM */
559 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
560
561 return 0;
562 }
563
564 /* EOF */