1 
2 /* INCLUDES *******************************************************************/
3 
4 // #include "ntvdm.h"
5 
6 // #define NDEBUG
7 // #include <debug.h>
8 
9 // #include "emulator.h"
10 #include "resource.h"
11 
12 /* VARIABLES ******************************************************************/
13 
14 static HANDLE CurrentConsoleOutput = INVALID_HANDLE_VALUE;
15 
16 static HANDLE ConsoleInput  = INVALID_HANDLE_VALUE;
17 static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
18 static DWORD  OrgConsoleInputMode, OrgConsoleOutputMode;
19 
20 HWND hConsoleWnd = NULL;
21 static HMENU hConsoleMenu = NULL;
22 static INT   VdmMenuPos   = -1;
23 static BOOL  CaptureMouse = FALSE;
24 
25 /*
26  * Those menu helpers were taken from the GUI frontend in winsrv.dll
27  */
28 typedef struct _VDM_MENUITEM
29 {
30     UINT uID;
31     const struct _VDM_MENUITEM *SubMenu;
32     UINT_PTR uCmdID;
33 } VDM_MENUITEM, *PVDM_MENUITEM;
34 
35 static const VDM_MENUITEM VdmMenuItems[] =
36 {
37     { IDS_VDM_DUMPMEM_TXT, NULL, ID_VDM_DUMPMEM_TXT },
38     { IDS_VDM_DUMPMEM_BIN, NULL, ID_VDM_DUMPMEM_BIN },
39     { -1, NULL, 0 },    /* Separator */
40     // { IDS_VDM_MOUNT_FLOPPY, NULL, ID_VDM_DRIVES },
41     // { IDS_VDM_EJECT_FLOPPY, NULL, ID_VDM_DRIVES },
42     { -1, NULL, 0 },    /* Separator */
43     { IDS_VDM_QUIT       , NULL, ID_VDM_QUIT        },
44 
45     { 0, NULL, 0 }      /* End of list */
46 };
47 
48 static const VDM_MENUITEM VdmMainMenuItems[] =
49 {
50     { -1, NULL, 0 },    /* Separator */
51     { IDS_CAPTURE_MOUSE, NULL, ID_CAPTURE_MOUSE },  /* "Capture mouse"; can be renamed to "Release mouse" */
52     { IDS_VDM_MENU , VdmMenuItems, 0 },             /* ReactOS VDM Menu */
53 
54     { 0, NULL, 0 }      /* End of list */
55 };
56 
57 static VOID
AppendMenuItems(HMENU hMenu,const VDM_MENUITEM * Items)58 AppendMenuItems(HMENU hMenu,
59                 const VDM_MENUITEM *Items)
60 {
61     UINT i = 0;
62     WCHAR szMenuString[256];
63     HMENU hSubMenu;
64 
65     do
66     {
67         if (Items[i].uID != (UINT)-1)
68         {
69             if (LoadStringW(GetModuleHandle(NULL),
70                             Items[i].uID,
71                             szMenuString,
72                             ARRAYSIZE(szMenuString)) > 0)
73             {
74                 if (Items[i].SubMenu != NULL)
75                 {
76                     hSubMenu = CreatePopupMenu();
77                     if (hSubMenu != NULL)
78                     {
79                         AppendMenuItems(hSubMenu, Items[i].SubMenu);
80 
81                         if (!AppendMenuW(hMenu,
82                                          MF_STRING | MF_POPUP,
83                                          (UINT_PTR)hSubMenu,
84                                          szMenuString))
85                         {
86                             DestroyMenu(hSubMenu);
87                         }
88                     }
89                 }
90                 else
91                 {
92                     AppendMenuW(hMenu,
93                                 MF_STRING,
94                                 Items[i].uCmdID,
95                                 szMenuString);
96                 }
97             }
98         }
99         else
100         {
101             AppendMenuW(hMenu,
102                         MF_SEPARATOR,
103                         0,
104                         NULL);
105         }
106         i++;
107     } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].uCmdID == 0));
108 }
109 
110 static BOOL
VdmMenuExists(HMENU hConsoleMenu)111 VdmMenuExists(HMENU hConsoleMenu)
112 {
113     INT MenuPos, i;
114     MenuPos = GetMenuItemCount(hConsoleMenu);
115 
116     /* Check for the presence of one of the VDM menu items */
117     for (i = 0; i <= MenuPos; i++)
118     {
119         if (GetMenuItemID(hConsoleMenu, i) == ID_CAPTURE_MOUSE)
120         {
121             /* Set VdmMenuPos to the position of the existing menu */
122             VdmMenuPos = i - 1;
123             return TRUE;
124         }
125     }
126     return FALSE;
127 }
128 
129 static VOID
UpdateVdmMenuMouse(VOID)130 UpdateVdmMenuMouse(VOID)
131 {
132     WCHAR szMenuString[256];
133 
134     /* Update "Capture/Release mouse" menu item */
135     if (LoadStringW(GetModuleHandle(NULL),
136                     (CaptureMouse ? IDS_RELEASE_MOUSE : IDS_CAPTURE_MOUSE),
137                     szMenuString,
138                     ARRAYSIZE(szMenuString)) > 0)
139     {
140         ModifyMenuW(hConsoleMenu, ID_CAPTURE_MOUSE,
141                     MF_BYCOMMAND, ID_CAPTURE_MOUSE, szMenuString);
142     }
143 }
144 
145 /*static*/ VOID
UpdateVdmMenuDisks(VOID)146 UpdateVdmMenuDisks(VOID)
147 {
148     UINT_PTR ItemID;
149     USHORT i;
150 
151     WCHAR szNoMedia[100];
152     WCHAR szMenuString1[256], szMenuString2[256];
153 
154     /* Update the disks menu items */
155 
156     LoadStringW(GetModuleHandle(NULL),
157                 IDS_NO_MEDIA,
158                 szNoMedia,
159                 ARRAYSIZE(szNoMedia));
160 
161     LoadStringW(GetModuleHandle(NULL),
162                 IDS_VDM_MOUNT_FLOPPY,
163                 szMenuString1,
164                 ARRAYSIZE(szMenuString1));
165 
166     for (i = 0; i < ARRAYSIZE(GlobalSettings.FloppyDisks); ++i)
167     {
168         ItemID = ID_VDM_DRIVES + (2 * i);
169 
170         if (GlobalSettings.FloppyDisks[i].Length != 0 &&
171             GlobalSettings.FloppyDisks[i].Buffer      &&
172            *GlobalSettings.FloppyDisks[i].Buffer != L'\0')
173         {
174             /* Update item text */
175             _snwprintf(szMenuString2, ARRAYSIZE(szMenuString2), szMenuString1, i, GlobalSettings.FloppyDisks[i].Buffer);
176             szMenuString2[ARRAYSIZE(szMenuString2) - 1] = UNICODE_NULL;
177             ModifyMenuW(hConsoleMenu, ItemID, MF_BYCOMMAND | MF_STRING, ItemID, szMenuString2);
178 
179             /* Enable the eject item */
180             EnableMenuItem(hConsoleMenu, ItemID + 1, MF_BYCOMMAND | MF_ENABLED);
181         }
182         else
183         {
184             /* Update item text */
185             _snwprintf(szMenuString2, ARRAYSIZE(szMenuString2), szMenuString1, i, szNoMedia);
186             szMenuString2[ARRAYSIZE(szMenuString2) - 1] = UNICODE_NULL;
187             ModifyMenuW(hConsoleMenu, ItemID, MF_BYCOMMAND | MF_STRING, ItemID, szMenuString2);
188 
189             /* Disable the eject item */
190             EnableMenuItem(hConsoleMenu, ItemID + 1, MF_BYCOMMAND | MF_GRAYED);
191         }
192     }
193 }
194 
195 static VOID
UpdateVdmMenu(VOID)196 UpdateVdmMenu(VOID)
197 {
198     UpdateVdmMenuMouse();
199     UpdateVdmMenuDisks();
200 }
201 
202 static VOID
CreateVdmMenu(HANDLE ConOutHandle)203 CreateVdmMenu(HANDLE ConOutHandle)
204 {
205     HMENU hVdmSubMenu;
206     UINT_PTR ItemID;
207     UINT Pos;
208     USHORT i;
209     WCHAR szNoMedia[100];
210     WCHAR szMenuString1[256], szMenuString2[256];
211 
212     hConsoleMenu = ConsoleMenuControl(ConOutHandle,
213                                       ID_CAPTURE_MOUSE,
214                                       ID_VDM_DRIVES + (2 * ARRAYSIZE(GlobalSettings.FloppyDisks)));
215     if (hConsoleMenu == NULL) return;
216 
217     /* Get the position where we are going to insert our menu items */
218     VdmMenuPos = GetMenuItemCount(hConsoleMenu);
219 
220     /* Really add the menu if it doesn't already exist (in case eg. NTVDM crashed) */
221     if (!VdmMenuExists(hConsoleMenu))
222     {
223         /* Add all the menu entries */
224         AppendMenuItems(hConsoleMenu, VdmMainMenuItems);
225 
226         /* Add the removable drives menu entries */
227         hVdmSubMenu = GetSubMenu(hConsoleMenu, VdmMenuPos + 2); // VdmMenuItems
228         Pos = 3; // After the 2 items and the separator in VdmMenuItems
229 
230         LoadStringW(GetModuleHandle(NULL),
231                     IDS_NO_MEDIA,
232                     szNoMedia,
233                     ARRAYSIZE(szNoMedia));
234 
235         LoadStringW(GetModuleHandle(NULL),
236                     IDS_VDM_MOUNT_FLOPPY,
237                     szMenuString1,
238                     ARRAYSIZE(szMenuString1));
239 
240         /* Drive 'x' -- Mount */
241         for (i = 0; i < ARRAYSIZE(GlobalSettings.FloppyDisks); ++i)
242         {
243             ItemID = ID_VDM_DRIVES + (2 * i);
244 
245             /* Add the item */
246             _snwprintf(szMenuString2, ARRAYSIZE(szMenuString2), szMenuString1, i, szNoMedia);
247             szMenuString2[ARRAYSIZE(szMenuString2) - 1] = UNICODE_NULL;
248             InsertMenuW(hVdmSubMenu, Pos++, MF_STRING | MF_BYPOSITION, ItemID, szMenuString2);
249         }
250 
251         LoadStringW(GetModuleHandle(NULL),
252                     IDS_VDM_EJECT_FLOPPY,
253                     szMenuString1,
254                     ARRAYSIZE(szMenuString1));
255 
256         /* Drive 'x' -- Eject */
257         for (i = 0; i < ARRAYSIZE(GlobalSettings.FloppyDisks); ++i)
258         {
259             ItemID = ID_VDM_DRIVES + (2 * i);
260 
261             /* Add the item */
262             _snwprintf(szMenuString2, ARRAYSIZE(szMenuString2), szMenuString1, i);
263             szMenuString2[ARRAYSIZE(szMenuString2) - 1] = UNICODE_NULL;
264             InsertMenuW(hVdmSubMenu, Pos++, MF_STRING | MF_BYPOSITION, ItemID + 1, szMenuString2);
265         }
266 
267         /* Refresh the menu state */
268         UpdateVdmMenu();
269         DrawMenuBar(hConsoleWnd);
270     }
271 }
272 
273 static VOID
DestroyVdmMenu(VOID)274 DestroyVdmMenu(VOID)
275 {
276     UINT i = 0;
277     const VDM_MENUITEM *Items = VdmMainMenuItems;
278 
279     do
280     {
281         DeleteMenu(hConsoleMenu, VdmMenuPos, MF_BYPOSITION);
282         i++;
283     } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].uCmdID == 0));
284 
285     DrawMenuBar(hConsoleWnd);
286 }
287 
CaptureMousePointer(HANDLE ConOutHandle,BOOLEAN Capture)288 static VOID CaptureMousePointer(HANDLE ConOutHandle, BOOLEAN Capture)
289 {
290     static BOOL IsClipped = FALSE; // For debugging purposes
291     UNREFERENCED_PARAMETER(IsClipped);
292 
293     if (Capture)
294     {
295         RECT rcClip;
296 
297         // if (IsClipped) return;
298 
299         /* Be sure the cursor will be hidden */
300         while (ShowConsoleCursor(ConOutHandle, FALSE) >= 0) ;
301 
302         GetClientRect(hConsoleWnd, &rcClip);
303         MapWindowPoints(hConsoleWnd, HWND_DESKTOP /*NULL*/, (LPPOINT)&rcClip, 2 /* Magic value when the LPPOINT parameter is a RECT */);
304         IsClipped = ClipCursor(&rcClip);
305     }
306     else
307     {
308         // if (!IsClipped) return;
309 
310         ClipCursor(NULL);
311         IsClipped = FALSE;
312 
313         /* Be sure the cursor will be shown */
314         while (ShowConsoleCursor(ConOutHandle, TRUE) < 0) ;
315     }
316 }
317 
EnableExtraHardware(HANDLE ConsoleInput)318 static VOID EnableExtraHardware(HANDLE ConsoleInput)
319 {
320     DWORD ConInMode;
321 
322     if (GetConsoleMode(ConsoleInput, &ConInMode))
323     {
324 #if 0
325         // GetNumberOfConsoleMouseButtons();
326         // GetSystemMetrics(SM_CMOUSEBUTTONS);
327         // GetSystemMetrics(SM_MOUSEPRESENT);
328         if (MousePresent)
329         {
330 #endif
331             /* Support mouse input events if there is a mouse on the system */
332             ConInMode |= ENABLE_MOUSE_INPUT;
333 #if 0
334         }
335         else
336         {
337             /* Do not support mouse input events if there is no mouse on the system */
338             ConInMode &= ~ENABLE_MOUSE_INPUT;
339         }
340 #endif
341 
342         SetConsoleMode(ConsoleInput, ConInMode);
343     }
344 }
345 
346 
347 
348 
349 
350 
351 
352 /* PUBLIC FUNCTIONS ***********************************************************/
353 
354 /*static*/ VOID
355 VdmShutdown(BOOLEAN Immediate);
356 
357 static BOOL
358 WINAPI
ConsoleCtrlHandler(DWORD ControlType)359 ConsoleCtrlHandler(DWORD ControlType)
360 {
361     switch (ControlType)
362     {
363         case CTRL_LAST_CLOSE_EVENT:
364         {
365             /* Delayed shutdown */
366             DPRINT1("NTVDM delayed killing in the CTRL_LAST_CLOSE_EVENT CtrlHandler!\n");
367             VdmShutdown(FALSE);
368             break;
369         }
370 
371         default:
372         {
373             /* Stop the VDM if the user logs out or closes the console */
374             DPRINT1("Killing NTVDM in the 'default' CtrlHandler!\n");
375             VdmShutdown(TRUE);
376         }
377     }
378     return TRUE;
379 }
380 
381 static VOID
ConsoleInitUI(VOID)382 ConsoleInitUI(VOID)
383 {
384     hConsoleWnd = GetConsoleWindow();
385     CreateVdmMenu(ConsoleOutput);
386 }
387 
388 static VOID
ConsoleCleanupUI(VOID)389 ConsoleCleanupUI(VOID)
390 {
391     /* Display again properly the mouse pointer */
392     if (CaptureMouse) CaptureMousePointer(ConsoleOutput, !CaptureMouse);
393 
394     DestroyVdmMenu();
395 }
396 
397 BOOL
ConsoleAttach(VOID)398 ConsoleAttach(VOID)
399 {
400     /* Save the original input and output console modes */
401     if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
402         !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
403     {
404         CloseHandle(ConsoleOutput);
405         CloseHandle(ConsoleInput);
406         wprintf(L"FATAL: Cannot save console in/out modes\n");
407         return FALSE;
408     }
409 
410     /* Set the console input mode */
411     SetConsoleMode(ConsoleInput, ENABLE_WINDOW_INPUT);
412     EnableExtraHardware(ConsoleInput);
413 
414     /* Set the console output mode */
415     // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
416 
417     /* Initialize the UI */
418     ConsoleInitUI();
419 
420     return TRUE;
421 }
422 
423 VOID
ConsoleDetach(VOID)424 ConsoleDetach(VOID)
425 {
426     /* Cleanup the UI */
427     ConsoleCleanupUI();
428 
429     /* Restore the original input and output console modes */
430     SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
431     SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
432 }
433 
434 VOID
ConsoleReattach(HANDLE ConOutHandle)435 ConsoleReattach(HANDLE ConOutHandle)
436 {
437     DestroyVdmMenu();
438     CurrentConsoleOutput = ConOutHandle;
439     CreateVdmMenu(ConOutHandle);
440 
441     /* Synchronize mouse cursor display with console screenbuffer switches */
442     CaptureMousePointer(CurrentConsoleOutput, CaptureMouse);
443 }
444 
445 static BOOL
ConsoleInit(VOID)446 ConsoleInit(VOID)
447 {
448     /* Set the handler routine */
449     SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
450 
451     /* Enable the CTRL_LAST_CLOSE_EVENT */
452     SetLastConsoleEventActive();
453 
454     /*
455      * NOTE: The CONIN$ and CONOUT$ "virtual" files
456      * always point to non-redirected console handles.
457      */
458 
459     /* Get the input handle to the real console, and check for success */
460     ConsoleInput = CreateFileW(L"CONIN$",
461                                GENERIC_READ | GENERIC_WRITE,
462                                FILE_SHARE_READ | FILE_SHARE_WRITE,
463                                NULL,
464                                OPEN_EXISTING,
465                                0,
466                                NULL);
467     if (ConsoleInput == INVALID_HANDLE_VALUE)
468     {
469         wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
470         return FALSE;
471     }
472 
473     /* Get the output handle to the real console, and check for success */
474     ConsoleOutput = CreateFileW(L"CONOUT$",
475                                 GENERIC_READ | GENERIC_WRITE,
476                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
477                                 NULL,
478                                 OPEN_EXISTING,
479                                 0,
480                                 NULL);
481     if (ConsoleOutput == INVALID_HANDLE_VALUE)
482     {
483         CloseHandle(ConsoleInput);
484         wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
485         return FALSE;
486     }
487 
488     /* Effectively attach to the console */
489     return ConsoleAttach();
490 }
491 
492 static VOID
ConsoleCleanup(VOID)493 ConsoleCleanup(VOID)
494 {
495     /* Detach from the console */
496     ConsoleDetach();
497 
498     /* Close the console handles */
499     if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
500     if (ConsoleInput  != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
501 }
502 
IsConsoleHandle(HANDLE hHandle)503 BOOL IsConsoleHandle(HANDLE hHandle)
504 {
505     DWORD dwMode;
506 
507     /* Check whether the handle may be that of a console... */
508     if ((GetFileType(hHandle) & ~FILE_TYPE_REMOTE) != FILE_TYPE_CHAR)
509         return FALSE;
510 
511     /*
512      * It may be. Perform another test... The idea comes from the
513      * MSDN description of the WriteConsole API:
514      *
515      * "WriteConsole fails if it is used with a standard handle
516      *  that is redirected to a file. If an application processes
517      *  multilingual output that can be redirected, determine whether
518      *  the output handle is a console handle (one method is to call
519      *  the GetConsoleMode function and check whether it succeeds).
520      *  If the handle is a console handle, call WriteConsole. If the
521      *  handle is not a console handle, the output is redirected and
522      *  you should call WriteFile to perform the I/O."
523      */
524     return GetConsoleMode(hHandle, &dwMode);
525 }
526 
MenuEventHandler(PMENU_EVENT_RECORD MenuEvent)527 VOID MenuEventHandler(PMENU_EVENT_RECORD MenuEvent)
528 {
529     switch (MenuEvent->dwCommandId)
530     {
531         /*
532          * System-defined menu commands
533          */
534 
535         case WM_INITMENU:
536         case WM_MENUSELECT:
537         {
538             /*
539              * If the mouse is captured, release it or recapture it
540              * when the menu opens or closes, respectively.
541              */
542             if (!CaptureMouse) break;
543             CaptureMousePointer(CurrentConsoleOutput, MenuEvent->dwCommandId == WM_INITMENU ? FALSE : TRUE);
544             break;
545         }
546 
547 
548         /*
549          * User-defined menu commands
550          */
551 
552         case ID_CAPTURE_MOUSE:
553             CaptureMouse = !CaptureMouse;
554             CaptureMousePointer(CurrentConsoleOutput, CaptureMouse);
555             UpdateVdmMenuMouse();
556             break;
557 
558         case ID_VDM_DUMPMEM_TXT:
559             DumpMemory(TRUE);
560             break;
561 
562         case ID_VDM_DUMPMEM_BIN:
563             DumpMemory(FALSE);
564             break;
565 
566         /* Drive 0 -- Mount */
567         /* Drive 1 -- Mount */
568         case ID_VDM_DRIVES + 0:
569         case ID_VDM_DRIVES + 2:
570         {
571             ULONG DiskNumber = (MenuEvent->dwCommandId - ID_VDM_DRIVES) / 2;
572             MountFloppy(DiskNumber);
573             break;
574         }
575 
576         /* Drive 0 -- Eject */
577         /* Drive 1 -- Eject */
578         case ID_VDM_DRIVES + 1:
579         case ID_VDM_DRIVES + 3:
580         {
581             ULONG DiskNumber = (MenuEvent->dwCommandId - ID_VDM_DRIVES - 1) / 2;
582             EjectFloppy(DiskNumber);
583             break;
584         }
585 
586         case ID_VDM_QUIT:
587             /* Stop the VDM */
588             // EmulatorTerminate();
589 
590             /* Nothing runs, so exit immediately */
591             DPRINT1("Killing NTVDM via console menu!\n");
592             VdmShutdown(TRUE);
593             break;
594 
595         default:
596             break;
597     }
598 }
599 
FocusEventHandler(PFOCUS_EVENT_RECORD FocusEvent)600 VOID FocusEventHandler(PFOCUS_EVENT_RECORD FocusEvent)
601 {
602     /*
603      * If the mouse is captured, release it or recapture it
604      * when we lose or regain focus, respectively.
605      */
606     if (!CaptureMouse) return;
607     CaptureMousePointer(CurrentConsoleOutput, FocusEvent->bSetFocus);
608 }
609