Sync with trunk rev.61910 to get latest improvements and bugfixes.
[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 be able to test NTVDM with:
26 * ntvdm.exe <program>
27 */
28 #define TESTING
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)
128 {
129 VdmMenuPos = GetMenuItemCount(hConsoleMenu);
130 AppendMenuItems(hConsoleMenu, VdmMainMenuItems);
131 DrawMenuBar(GetConsoleWindow());
132 }
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 ShowConsoleCursor(ConOutHandle, ShowPtr);
155
156 if (LoadStringW(GetModuleHandle(NULL),
157 (!ShowPtr ? IDS_SHOW_MOUSE : IDS_HIDE_MOUSE),
158 szMenuString,
159 sizeof(szMenuString) / sizeof(szMenuString[0])) > 0)
160 {
161 ModifyMenu(hConsoleMenu, ID_SHOWHIDE_MOUSE,
162 MF_BYCOMMAND, ID_SHOWHIDE_MOUSE, szMenuString);
163 }
164 }
165
166 /* PUBLIC FUNCTIONS ***********************************************************/
167
168 VOID DisplayMessage(LPCWSTR Format, ...)
169 {
170 WCHAR Buffer[256];
171 va_list Parameters;
172
173 va_start(Parameters, Format);
174 _vsnwprintf(Buffer, 256, Format, Parameters);
175 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
176 MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
177 va_end(Parameters);
178 }
179
180 BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
181 {
182 switch (ControlType)
183 {
184 case CTRL_C_EVENT:
185 case CTRL_BREAK_EVENT:
186 {
187 /* Call INT 23h */
188 EmulatorInterrupt(0x23);
189 break;
190 }
191 default:
192 {
193 /* Stop the VDM if the user logs out or closes the console */
194 VdmRunning = FALSE;
195 }
196 }
197 return TRUE;
198 }
199
200 VOID ConsoleInitUI(VOID)
201 {
202 CreateVdmMenu(ConsoleOutput);
203 }
204
205 VOID ConsoleCleanupUI(VOID)
206 {
207 /* Display again properly the mouse pointer */
208 if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
209
210 DestroyVdmMenu();
211 }
212
213 DWORD WINAPI PumpConsoleInput(LPVOID Parameter)
214 {
215 HANDLE ConsoleInput = (HANDLE)Parameter;
216 INPUT_RECORD InputRecord;
217 DWORD Count;
218
219 while (VdmRunning)
220 {
221 /* Wait for an input record */
222 if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
223 {
224 DWORD LastError = GetLastError();
225 DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
226 return LastError;
227 }
228
229 ASSERT(Count != 0);
230
231 /* Check the event type */
232 switch (InputRecord.EventType)
233 {
234 case KEY_EVENT:
235 case MOUSE_EVENT:
236 /* Send it to the PS/2 controller */
237 PS2Dispatch(&InputRecord);
238 break;
239
240 case MENU_EVENT:
241 {
242 switch (InputRecord.Event.MenuEvent.dwCommandId)
243 {
244 case ID_SHOWHIDE_MOUSE:
245 ShowHideMousePointer(ConsoleOutput, ShowPointer);
246 ShowPointer = !ShowPointer;
247 break;
248
249 case ID_VDM_QUIT:
250 VdmRunning = FALSE;
251 break;
252
253 default:
254 break;
255 }
256
257 break;
258 }
259
260 default:
261 break;
262 }
263 }
264
265 return 0;
266 }
267
268 BOOL ConsoleInit(VOID)
269 {
270 /* Set the handler routine */
271 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
272
273 /* Get the input handle to the real console, and check for success */
274 ConsoleInput = CreateFileW(L"CONIN$",
275 GENERIC_READ | GENERIC_WRITE,
276 FILE_SHARE_READ | FILE_SHARE_WRITE,
277 NULL,
278 OPEN_EXISTING,
279 0,
280 NULL);
281 if (ConsoleInput == INVALID_HANDLE_VALUE)
282 {
283 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
284 return FALSE;
285 }
286
287 /* Get the output handle to the real console, and check for success */
288 ConsoleOutput = CreateFileW(L"CONOUT$",
289 GENERIC_READ | GENERIC_WRITE,
290 FILE_SHARE_READ | FILE_SHARE_WRITE,
291 NULL,
292 OPEN_EXISTING,
293 0,
294 NULL);
295 if (ConsoleOutput == INVALID_HANDLE_VALUE)
296 {
297 CloseHandle(ConsoleInput);
298 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
299 return FALSE;
300 }
301
302 /* Save the original input and output console modes */
303 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
304 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
305 {
306 CloseHandle(ConsoleOutput);
307 CloseHandle(ConsoleInput);
308 wprintf(L"FATAL: Cannot save console in/out modes\n");
309 return FALSE;
310 }
311
312 /* Save the original cursor and console screen buffer information */
313 if (!GetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo) ||
314 !GetConsoleScreenBufferInfo(ConsoleOutput, &OrgConsoleBufferInfo))
315 {
316 CloseHandle(ConsoleOutput);
317 CloseHandle(ConsoleInput);
318 wprintf(L"FATAL: Cannot save console cursor/screen-buffer info\n");
319 return FALSE;
320 }
321
322 /* Initialize the UI */
323 ConsoleInitUI();
324
325 return TRUE;
326 }
327
328 VOID ConsoleCleanup(VOID)
329 {
330 SMALL_RECT ConRect;
331 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
332
333 /* Restore the old screen buffer */
334 SetConsoleActiveScreenBuffer(ConsoleOutput);
335
336 /* Restore the original console size */
337 GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo);
338 ConRect.Left = 0; // OrgConsoleBufferInfo.srWindow.Left;
339 // ConRect.Top = ConsoleInfo.dwCursorPosition.Y / (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
340 // ConRect.Top *= (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
341 ConRect.Top = ConsoleInfo.dwCursorPosition.Y;
342 ConRect.Right = ConRect.Left + OrgConsoleBufferInfo.srWindow.Right - OrgConsoleBufferInfo.srWindow.Left;
343 ConRect.Bottom = ConRect.Top + OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top ;
344 /* See the following trick explanation in vga.c:VgaEnterTextMode() */
345 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
346 SetConsoleWindowInfo(ConsoleOutput, TRUE, &ConRect);
347 // SetConsoleWindowInfo(ConsoleOutput, TRUE, &OrgConsoleBufferInfo.srWindow);
348 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
349
350 /* Restore the original cursor shape */
351 SetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo);
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 INT wmain(INT argc, WCHAR *argv[])
366 {
367 CHAR CommandLine[DOS_CMDLINE_LENGTH];
368
369 #ifndef TESTING
370 UNREFERENCED_PARAMETER(argc);
371 UNREFERENCED_PARAMETER(argv);
372
373 /* The DOS command line must be ASCII */
374 WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, sizeof(CommandLine), NULL, NULL);
375 #else
376 if (argc == 2 && argv[1] != NULL)
377 {
378 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
379 }
380 else
381 {
382 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
383 L"Usage: NTVDM <executable>\n");
384 return 0;
385 }
386 #endif
387
388 DPRINT1("\n\n\nNTVDM - Starting '%s'...\n\n\n", CommandLine);
389
390 /* Initialize the console */
391 if (!ConsoleInit())
392 {
393 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
394 goto Cleanup;
395 }
396
397 /* Initialize the emulator */
398 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
399 {
400 wprintf(L"FATAL: Failed to initialize the emulator\n");
401 goto Cleanup;
402 }
403
404 /* Initialize the system BIOS */
405 if (!BiosInitialize(ConsoleInput, ConsoleOutput))
406 {
407 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
408 goto Cleanup;
409 }
410
411 /* Initialize the VDM DOS kernel */
412 if (!DosInitialize(NULL))
413 {
414 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
415 goto Cleanup;
416 }
417
418 /* Start the process from the command line */
419 if (!DosCreateProcess(CommandLine, 0))
420 {
421 DisplayMessage(L"Could not start program: %S", CommandLine);
422 goto Cleanup;
423 }
424
425 /* Main loop */
426 while (VdmRunning) ClockUpdate();
427
428 /* Perform another screen refresh */
429 VgaRefreshDisplay();
430
431 Cleanup:
432 BiosCleanup();
433 EmulatorCleanup();
434 ConsoleCleanup();
435
436 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
437
438 return 0;
439 }
440
441 /* EOF */