a079e2debd75b796e2fb87a9088dc3eb36b2dfb3
[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 VDM_COMMAND_INFO CommandInfo;
383 CHAR CmdLine[MAX_PATH];
384 CHAR AppName[MAX_PATH];
385 CHAR PifFile[MAX_PATH];
386 CHAR Desktop[MAX_PATH];
387 CHAR Title[MAX_PATH];
388
389 UNREFERENCED_PARAMETER(Parameter);
390
391 while (TRUE)
392 {
393 /* Clear the structure */
394 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
395
396 /* Initialize the structure members */
397 CommandInfo.VDMState = VDM_NOT_LOADED;
398 CommandInfo.CmdLine = CmdLine;
399 CommandInfo.CmdLen = sizeof(CmdLine);
400 CommandInfo.AppName = AppName;
401 CommandInfo.AppLen = sizeof(AppName);
402 CommandInfo.PifFile = PifFile;
403 CommandInfo.PifLen = sizeof(PifFile);
404 CommandInfo.Desktop = Desktop;
405 CommandInfo.DesktopLen = sizeof(Desktop);
406 CommandInfo.Title = Title;
407 CommandInfo.TitleLen = sizeof(Title);
408
409 /* Wait for the next available VDM */
410 if (!GetNextVDMCommand(&CommandInfo)) break;
411
412 /* Start the process from the command line */
413 DPRINT1("Starting '%s'...\n", AppName);
414 if (!DosCreateProcess(AppName, 0))
415 {
416 DisplayMessage(L"Could not start '%S'", AppName);
417 break;
418 }
419
420 /* Start simulation */
421 EmulatorSimulate();
422
423 /* Perform another screen refresh */
424 VgaRefreshDisplay();
425 }
426
427 return 0;
428 }
429
430 INT wmain(INT argc, WCHAR *argv[])
431 {
432 #ifdef STANDALONE
433
434 CHAR CommandLine[DOS_CMDLINE_LENGTH];
435
436 if (argc == 2 && argv[1] != NULL)
437 {
438 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
439 }
440 else
441 {
442 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
443 L"Usage: NTVDM <executable>\n");
444 return 0;
445 }
446
447 #endif
448
449 DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
450
451 /* Initialize the console */
452 if (!ConsoleInit())
453 {
454 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
455 goto Cleanup;
456 }
457
458 /* Initialize the emulator */
459 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
460 {
461 wprintf(L"FATAL: Failed to initialize the emulator\n");
462 goto Cleanup;
463 }
464
465 /* Initialize the system BIOS */
466 if (!BiosInitialize(NULL))
467 {
468 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
469 goto Cleanup;
470 }
471
472 /* Initialize the VDM DOS kernel */
473 if (!DosInitialize(NULL))
474 {
475 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
476 goto Cleanup;
477 }
478
479 #ifndef STANDALONE
480
481 /* Create the GetNextVDMCommand thread */
482 CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
483 if (CommandThread == NULL)
484 {
485 wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
486 goto Cleanup;
487 }
488
489 /* Wait for the command thread to exit */
490 WaitForSingleObject(CommandThread, INFINITE);
491
492 /* Close the thread handle */
493 CloseHandle(CommandThread);
494
495 #else
496
497 /* Start the process from the command line */
498 DPRINT1("Starting '%s'...\n", CommandLine);
499 if (!DosCreateProcess(CommandLine, 0))
500 {
501 DisplayMessage(L"Could not start '%S'", CommandLine);
502 goto Cleanup;
503 }
504
505 /* Start simulation */
506 EmulatorSimulate();
507
508 /* Perform another screen refresh */
509 VgaRefreshDisplay();
510
511 #endif
512
513 Cleanup:
514 BiosCleanup();
515 EmulatorCleanup();
516 ConsoleCleanup();
517
518 #ifndef STANDALONE
519 ExitVDM(FALSE, 0);
520 #endif
521
522 /* Quit the VDM */
523 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
524
525 return 0;
526 }
527
528 /* EOF */