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