2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/ntvdm.c
5 * PURPOSE: Virtual DOS Machine
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
9 /* INCLUDES *******************************************************************/
16 #include "bios/bios.h"
23 /* VARIABLES ******************************************************************/
25 static HANDLE ConsoleInput
= INVALID_HANDLE_VALUE
;
26 static HANDLE ConsoleOutput
= INVALID_HANDLE_VALUE
;
27 static DWORD OrgConsoleInputMode
, OrgConsoleOutputMode
;
29 NTVDM_SETTINGS GlobalSettings
;
31 // Command line of NTVDM
35 HWND hConsoleWnd
= NULL
;
36 static HMENU hConsoleMenu
= NULL
;
37 static INT VdmMenuPos
= -1;
38 static BOOLEAN ShowPointer
= FALSE
;
41 * Those menu helpers were taken from the GUI frontend in winsrv.dll
43 typedef struct _VDM_MENUITEM
46 const struct _VDM_MENUITEM
*SubMenu
;
48 } VDM_MENUITEM
, *PVDM_MENUITEM
;
50 static const VDM_MENUITEM VdmMenuItems
[] =
52 { IDS_VDM_DUMPMEM_TXT
, NULL
, ID_VDM_DUMPMEM_TXT
},
53 { IDS_VDM_DUMPMEM_BIN
, NULL
, ID_VDM_DUMPMEM_BIN
},
54 { -1, NULL
, 0 }, /* Separator */
55 // { IDS_VDM_MOUNT_FLOPPY, NULL, ID_VDM_DRIVES },
56 // { IDS_VDM_EJECT_FLOPPY, NULL, ID_VDM_DRIVES },
57 { -1, NULL
, 0 }, /* Separator */
58 { IDS_VDM_QUIT
, NULL
, ID_VDM_QUIT
},
60 { 0, NULL
, 0 } /* End of list */
63 static const VDM_MENUITEM VdmMainMenuItems
[] =
65 { -1, NULL
, 0 }, /* Separator */
66 { IDS_HIDE_MOUSE
, NULL
, ID_SHOWHIDE_MOUSE
}, /* "Hide mouse"; can be renamed to "Show mouse" */
67 { IDS_VDM_MENU
, VdmMenuItems
, 0 }, /* ReactOS VDM Menu */
69 { 0, NULL
, 0 } /* End of list */
73 AppendMenuItems(HMENU hMenu
,
74 const VDM_MENUITEM
*Items
)
77 WCHAR szMenuString
[256];
82 if (Items
[i
].uID
!= (UINT
)-1)
84 if (LoadStringW(GetModuleHandle(NULL
),
87 ARRAYSIZE(szMenuString
)) > 0)
89 if (Items
[i
].SubMenu
!= NULL
)
91 hSubMenu
= CreatePopupMenu();
94 AppendMenuItems(hSubMenu
, Items
[i
].SubMenu
);
96 if (!AppendMenuW(hMenu
,
101 DestroyMenu(hSubMenu
);
122 } while (!(Items
[i
].uID
== 0 && Items
[i
].SubMenu
== NULL
&& Items
[i
].uCmdID
== 0));
126 VdmMenuExists(HMENU hConsoleMenu
)
129 MenuPos
= GetMenuItemCount(hConsoleMenu
);
131 /* Check for the presence of one of the VDM menu items */
132 for (i
= 0; i
<= MenuPos
; i
++)
134 if (GetMenuItemID(hConsoleMenu
, i
) == ID_SHOWHIDE_MOUSE
)
136 /* Set VdmMenuPos to the position of the existing menu */
145 CreateVdmMenu(HANDLE ConOutHandle
)
148 UINT_PTR ItemID
= ID_VDM_DRIVES
;
150 WCHAR szNoMedia
[100];
151 WCHAR szMenuString1
[256], szMenuString2
[256];
153 hConsoleMenu
= ConsoleMenuControl(ConOutHandle
,
156 if (hConsoleMenu
== NULL
) return;
158 /* Get the position where we are going to insert our menu items */
159 VdmMenuPos
= GetMenuItemCount(hConsoleMenu
);
161 /* Really add the menu if it doesn't already exist (in case eg. NTVDM crashed) */
162 if (!VdmMenuExists(hConsoleMenu
))
164 /* Add all the menu entries */
165 AppendMenuItems(hConsoleMenu
, VdmMainMenuItems
);
167 /* Add the removable drives menu entries */
168 hVdmSubMenu
= GetSubMenu(hConsoleMenu
, VdmMenuPos
+ 2); // VdmMenuItems
169 Pos
= 3; // After the 2 items and the separator in VdmMenuItems
171 LoadStringW(GetModuleHandle(NULL
),
174 ARRAYSIZE(szNoMedia
));
176 LoadStringW(GetModuleHandle(NULL
),
177 IDS_VDM_MOUNT_FLOPPY
,
179 ARRAYSIZE(szMenuString1
));
181 /* Drive 0 -- Mount */
182 _snwprintf(szMenuString2
, ARRAYSIZE(szMenuString2
), szMenuString1
, 0, szNoMedia
);
183 szMenuString2
[ARRAYSIZE(szMenuString2
) - 1] = UNICODE_NULL
;
184 InsertMenuW(hVdmSubMenu
, Pos
++, MF_STRING
| MF_BYPOSITION
, ItemID
+ 0, szMenuString2
);
186 /* Drive 1 -- Mount */
187 _snwprintf(szMenuString2
, ARRAYSIZE(szMenuString2
), szMenuString1
, 1, szNoMedia
);
188 szMenuString2
[ARRAYSIZE(szMenuString2
) - 1] = UNICODE_NULL
;
189 InsertMenuW(hVdmSubMenu
, Pos
++, MF_STRING
| MF_BYPOSITION
, ItemID
+ 2, szMenuString2
);
191 LoadStringW(GetModuleHandle(NULL
),
192 IDS_VDM_EJECT_FLOPPY
,
194 ARRAYSIZE(szMenuString1
));
196 /* Drive 0 -- Eject */
197 _snwprintf(szMenuString2
, ARRAYSIZE(szMenuString2
), szMenuString1
, 0);
198 szMenuString2
[ARRAYSIZE(szMenuString2
) - 1] = UNICODE_NULL
;
199 InsertMenuW(hVdmSubMenu
, Pos
++, MF_STRING
| MF_BYPOSITION
, ItemID
+ 1, szMenuString2
);
201 /* Drive 1 -- Eject */
202 _snwprintf(szMenuString2
, ARRAYSIZE(szMenuString2
), szMenuString1
, 1);
203 szMenuString2
[ARRAYSIZE(szMenuString2
) - 1] = UNICODE_NULL
;
204 InsertMenuW(hVdmSubMenu
, Pos
++, MF_STRING
| MF_BYPOSITION
, ItemID
+ 3, szMenuString2
);
206 // TODO: Refresh the menu state
208 /* Refresh the menu */
209 DrawMenuBar(hConsoleWnd
);
217 const VDM_MENUITEM
*Items
= VdmMainMenuItems
;
221 DeleteMenu(hConsoleMenu
, VdmMenuPos
, MF_BYPOSITION
);
223 } while (!(Items
[i
].uID
== 0 && Items
[i
].SubMenu
== NULL
&& Items
[i
].uCmdID
== 0));
225 DrawMenuBar(hConsoleWnd
);
228 static VOID
ShowHideMousePointer(HANDLE ConOutHandle
, BOOLEAN ShowPtr
)
230 WCHAR szMenuString
[256];
234 /* Be sure the cursor will be shown */
235 while (ShowConsoleCursor(ConOutHandle
, TRUE
) < 0) ;
239 /* Be sure the cursor will be hidden */
240 while (ShowConsoleCursor(ConOutHandle
, FALSE
) >= 0) ;
243 if (LoadStringW(GetModuleHandle(NULL
),
244 (!ShowPtr
? IDS_SHOW_MOUSE
: IDS_HIDE_MOUSE
),
246 ARRAYSIZE(szMenuString
)) > 0)
248 ModifyMenu(hConsoleMenu
, ID_SHOWHIDE_MOUSE
,
249 MF_BYCOMMAND
, ID_SHOWHIDE_MOUSE
, szMenuString
);
253 static VOID
EnableExtraHardware(HANDLE ConsoleInput
)
257 if (GetConsoleMode(ConsoleInput
, &ConInMode
))
260 // GetNumberOfConsoleMouseButtons();
261 // GetSystemMetrics(SM_CMOUSEBUTTONS);
262 // GetSystemMetrics(SM_MOUSEPRESENT);
266 /* Support mouse input events if there is a mouse on the system */
267 ConInMode
|= ENABLE_MOUSE_INPUT
;
272 /* Do not support mouse input events if there is no mouse on the system */
273 ConInMode
&= ~ENABLE_MOUSE_INPUT
;
277 SetConsoleMode(ConsoleInput
, ConInMode
);
284 NtVdmConfigureBios(IN PWSTR ValueName
,
287 IN ULONG ValueLength
,
289 IN PVOID EntryContext
)
291 PNTVDM_SETTINGS Settings
= (PNTVDM_SETTINGS
)Context
;
292 UNICODE_STRING ValueString
;
294 /* Check for the type of the value */
295 if (ValueType
!= REG_SZ
)
297 RtlInitEmptyAnsiString(&Settings
->BiosFileName
, NULL
, 0);
298 return STATUS_SUCCESS
;
301 /* Convert the UNICODE string to ANSI and store it */
302 RtlInitEmptyUnicodeString(&ValueString
, (PWCHAR
)ValueData
, ValueLength
);
303 ValueString
.Length
= ValueString
.MaximumLength
;
304 RtlUnicodeStringToAnsiString(&Settings
->BiosFileName
, &ValueString
, TRUE
);
306 return STATUS_SUCCESS
;
311 NtVdmConfigureRom(IN PWSTR ValueName
,
314 IN ULONG ValueLength
,
316 IN PVOID EntryContext
)
318 PNTVDM_SETTINGS Settings
= (PNTVDM_SETTINGS
)Context
;
319 UNICODE_STRING ValueString
;
321 /* Check for the type of the value */
322 if (ValueType
!= REG_MULTI_SZ
)
324 RtlInitEmptyAnsiString(&Settings
->RomFiles
, NULL
, 0);
325 return STATUS_SUCCESS
;
328 /* Convert the UNICODE string to ANSI and store it */
329 RtlInitEmptyUnicodeString(&ValueString
, (PWCHAR
)ValueData
, ValueLength
);
330 ValueString
.Length
= ValueString
.MaximumLength
;
331 RtlUnicodeStringToAnsiString(&Settings
->RomFiles
, &ValueString
, TRUE
);
333 return STATUS_SUCCESS
;
338 NtVdmConfigureFloppy(IN PWSTR ValueName
,
341 IN ULONG ValueLength
,
343 IN PVOID EntryContext
)
345 PNTVDM_SETTINGS Settings
= (PNTVDM_SETTINGS
)Context
;
346 UNICODE_STRING ValueString
;
347 ULONG DiskNumber
= (ULONG
)EntryContext
;
349 ASSERT(DiskNumber
< ARRAYSIZE(Settings
->FloppyDisks
));
351 /* Check whether the Hard Disk entry was not already configured */
352 if (Settings
->FloppyDisks
[DiskNumber
].Buffer
!= NULL
)
354 DPRINT1("Floppy Disk %d -- '%Z' already configured\n", DiskNumber
, &Settings
->FloppyDisks
[DiskNumber
]);
355 return STATUS_SUCCESS
;
358 /* Check for the type of the value */
359 if (ValueType
!= REG_SZ
)
361 RtlInitEmptyAnsiString(&Settings
->FloppyDisks
[DiskNumber
], NULL
, 0);
362 return STATUS_SUCCESS
;
365 /* Convert the UNICODE string to ANSI and store it */
366 RtlInitEmptyUnicodeString(&ValueString
, (PWCHAR
)ValueData
, ValueLength
);
367 ValueString
.Length
= ValueString
.MaximumLength
;
368 RtlUnicodeStringToAnsiString(&Settings
->FloppyDisks
[DiskNumber
], &ValueString
, TRUE
);
370 return STATUS_SUCCESS
;
375 NtVdmConfigureHDD(IN PWSTR ValueName
,
378 IN ULONG ValueLength
,
380 IN PVOID EntryContext
)
382 PNTVDM_SETTINGS Settings
= (PNTVDM_SETTINGS
)Context
;
383 UNICODE_STRING ValueString
;
384 ULONG DiskNumber
= (ULONG
)EntryContext
;
386 ASSERT(DiskNumber
< ARRAYSIZE(Settings
->HardDisks
));
388 /* Check whether the Hard Disk entry was not already configured */
389 if (Settings
->HardDisks
[DiskNumber
].Buffer
!= NULL
)
391 DPRINT1("Hard Disk %d -- '%Z' already configured\n", DiskNumber
, &Settings
->HardDisks
[DiskNumber
]);
392 return STATUS_SUCCESS
;
395 /* Check for the type of the value */
396 if (ValueType
!= REG_SZ
)
398 RtlInitEmptyAnsiString(&Settings
->HardDisks
[DiskNumber
], NULL
, 0);
399 return STATUS_SUCCESS
;
402 /* Convert the UNICODE string to ANSI and store it */
403 RtlInitEmptyUnicodeString(&ValueString
, (PWCHAR
)ValueData
, ValueLength
);
404 ValueString
.Length
= ValueString
.MaximumLength
;
405 RtlUnicodeStringToAnsiString(&Settings
->HardDisks
[DiskNumber
], &ValueString
, TRUE
);
407 return STATUS_SUCCESS
;
410 static RTL_QUERY_REGISTRY_TABLE
411 NtVdmConfigurationTable
[] =
425 RTL_QUERY_REGISTRY_NOEXPAND
,
434 NtVdmConfigureFloppy
,
444 NtVdmConfigureFloppy
,
498 LoadGlobalSettings(IN PNTVDM_SETTINGS Settings
)
514 Status
= RtlQueryRegistryValues(RTL_REGISTRY_CONTROL
,
516 NtVdmConfigurationTable
,
519 if (!NT_SUCCESS(Status
))
521 DPRINT1("NTVDM registry settings cannot be fully initialized, using default ones. Status = 0x%08lx\n", Status
);
524 return NT_SUCCESS(Status
);
528 FreeGlobalSettings(IN PNTVDM_SETTINGS Settings
)
534 if (Settings
->BiosFileName
.Buffer
)
535 RtlFreeAnsiString(&Settings
->BiosFileName
);
537 if (Settings
->RomFiles
.Buffer
)
538 RtlFreeAnsiString(&Settings
->RomFiles
);
540 for (i
= 0; i
< ARRAYSIZE(Settings
->FloppyDisks
); ++i
)
542 if (Settings
->FloppyDisks
[i
].Buffer
)
543 RtlFreeAnsiString(&Settings
->FloppyDisks
[i
]);
546 for (i
= 0; i
< ARRAYSIZE(Settings
->HardDisks
); ++i
)
548 if (Settings
->HardDisks
[i
].Buffer
)
549 RtlFreeAnsiString(&Settings
->HardDisks
[i
]);
553 /* PUBLIC FUNCTIONS ***********************************************************/
556 DisplayMessage(IN LPCWSTR Format
, ...)
558 #ifndef WIN2K_COMPLIANT
559 WCHAR StaticBuffer
[256];
560 LPWSTR Buffer
= StaticBuffer
; // Use the static buffer by default.
562 WCHAR Buffer
[2048]; // Large enough. If not, increase it by hand.
567 va_start(Parameters
, Format
);
569 #ifndef WIN2K_COMPLIANT
571 * Retrieve the message length and if it is too long, allocate
572 * an auxiliary buffer; otherwise use the static buffer.
573 * The string is built to be NULL-terminated.
575 MsgLen
= _vscwprintf(Format
, Parameters
);
576 if (MsgLen
>= ARRAYSIZE(StaticBuffer
))
578 Buffer
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, (MsgLen
+ 1) * sizeof(WCHAR
));
581 /* Allocation failed, use the static buffer and display a suitable error message */
582 Buffer
= StaticBuffer
;
583 Format
= L
"DisplayMessage()\nOriginal message is too long and allocating an auxiliary buffer failed.";
584 MsgLen
= wcslen(Format
);
588 MsgLen
= ARRAYSIZE(Buffer
) - 1;
591 RtlZeroMemory(Buffer
, (MsgLen
+ 1) * sizeof(WCHAR
));
592 _vsnwprintf(Buffer
, MsgLen
, Format
, Parameters
);
596 /* Display the message */
597 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer
);
598 MessageBoxW(hConsoleWnd
, Buffer
, L
"NTVDM Subsystem", MB_OK
);
600 #ifndef WIN2K_COMPLIANT
601 /* Free the buffer if needed */
602 if (Buffer
!= StaticBuffer
) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer
);
607 ConsoleCleanup(VOID
);
610 VdmShutdown(BOOLEAN Immediate
)
613 * Immediate = TRUE: Immediate shutdown;
614 * FALSE: Delayed shutdown.
616 static BOOLEAN MustShutdown
= FALSE
;
618 /* If a shutdown is ongoing, just return */
621 DPRINT1("Shutdown is ongoing...\n");
626 /* First notify DOS to see whether we can shut down now */
627 MustShutdown
= DosShutdown(Immediate
);
629 * In case we perform an immediate shutdown, or the DOS says
630 * we can shut down, do it now.
632 MustShutdown
= MustShutdown
|| Immediate
;
642 FreeGlobalSettings(&GlobalSettings
);
644 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
645 /* Some VDDs rely on the fact that NTVDM calls ExitProcess on Windows */
652 ConsoleCtrlHandler(DWORD ControlType
)
656 case CTRL_LAST_CLOSE_EVENT
:
658 /* Delayed shutdown */
659 DPRINT1("NTVDM delayed killing in the CTRL_LAST_CLOSE_EVENT CtrlHandler!\n");
666 /* Stop the VDM if the user logs out or closes the console */
667 DPRINT1("Killing NTVDM in the 'default' CtrlHandler!\n");
677 hConsoleWnd
= GetConsoleWindow();
678 CreateVdmMenu(ConsoleOutput
);
682 ConsoleCleanupUI(VOID
)
684 /* Display again properly the mouse pointer */
685 if (ShowPointer
) ShowHideMousePointer(ConsoleOutput
, ShowPointer
);
693 /* Save the original input and output console modes */
694 if (!GetConsoleMode(ConsoleInput
, &OrgConsoleInputMode
) ||
695 !GetConsoleMode(ConsoleOutput
, &OrgConsoleOutputMode
))
697 CloseHandle(ConsoleOutput
);
698 CloseHandle(ConsoleInput
);
699 wprintf(L
"FATAL: Cannot save console in/out modes\n");
703 /* Set the console input mode */
704 // FIXME: Activate ENABLE_WINDOW_INPUT when we will want to perform actions
705 // upon console window events (screen buffer resize, ...).
706 SetConsoleMode(ConsoleInput
, 0 /* | ENABLE_WINDOW_INPUT */);
707 EnableExtraHardware(ConsoleInput
);
709 /* Set the console output mode */
710 // SetConsoleMode(ConsoleOutput, ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
712 /* Initialize the UI */
724 /* Restore the original input and output console modes */
725 SetConsoleMode(ConsoleOutput
, OrgConsoleOutputMode
);
726 SetConsoleMode(ConsoleInput
, OrgConsoleInputMode
);
732 /* Set the handler routine */
733 SetConsoleCtrlHandler(ConsoleCtrlHandler
, TRUE
);
735 /* Enable the CTRL_LAST_CLOSE_EVENT */
736 SetLastConsoleEventActive();
739 * NOTE: The CONIN$ and CONOUT$ "virtual" files
740 * always point to non-redirected console handles.
743 /* Get the input handle to the real console, and check for success */
744 ConsoleInput
= CreateFileW(L
"CONIN$",
745 GENERIC_READ
| GENERIC_WRITE
,
746 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
751 if (ConsoleInput
== INVALID_HANDLE_VALUE
)
753 wprintf(L
"FATAL: Cannot retrieve a handle to the console input\n");
757 /* Get the output handle to the real console, and check for success */
758 ConsoleOutput
= CreateFileW(L
"CONOUT$",
759 GENERIC_READ
| GENERIC_WRITE
,
760 FILE_SHARE_READ
| FILE_SHARE_WRITE
,
765 if (ConsoleOutput
== INVALID_HANDLE_VALUE
)
767 CloseHandle(ConsoleInput
);
768 wprintf(L
"FATAL: Cannot retrieve a handle to the console output\n");
772 /* Effectively attach to the console */
773 return ConsoleAttach();
779 /* Detach from the console */
782 /* Close the console handles */
783 if (ConsoleOutput
!= INVALID_HANDLE_VALUE
) CloseHandle(ConsoleOutput
);
784 if (ConsoleInput
!= INVALID_HANDLE_VALUE
) CloseHandle(ConsoleInput
);
787 VOID
MenuEventHandler(PMENU_EVENT_RECORD MenuEvent
)
789 switch (MenuEvent
->dwCommandId
)
791 case ID_SHOWHIDE_MOUSE
:
792 ShowHideMousePointer(ConsoleOutput
, ShowPointer
);
793 ShowPointer
= !ShowPointer
;
796 case ID_VDM_DUMPMEM_TXT
:
800 case ID_VDM_DUMPMEM_BIN
:
804 /* Drive 0 -- Mount */
805 /* Drive 1 -- Mount */
806 case ID_VDM_DRIVES
+ 0:
807 case ID_VDM_DRIVES
+ 2:
809 ULONG DiskNumber
= (MenuEvent
->dwCommandId
- ID_VDM_DRIVES
) / 2;
810 MountFloppy(DiskNumber
);
814 /* Drive 0 -- Eject */
815 /* Drive 1 -- Eject */
816 case ID_VDM_DRIVES
+ 1:
817 case ID_VDM_DRIVES
+ 3:
819 ULONG DiskNumber
= (MenuEvent
->dwCommandId
- ID_VDM_DRIVES
- 1) / 2;
820 EjectFloppy(DiskNumber
);
826 // EmulatorTerminate();
828 /* Nothing runs, so exit immediately */
829 DPRINT1("Killing NTVDM via console menu!\n");
838 VOID
FocusEventHandler(PFOCUS_EVENT_RECORD FocusEvent
)
840 DPRINT1("Focus events not handled\n");
845 wmain(INT argc
, WCHAR
*argv
[])
854 wprintf(L
"\nReactOS Virtual DOS Machine\n\n"
855 L
"Usage: NTVDM <executable> [<parameters>]\n");
861 #ifdef ADVANCED_DEBUGGING
865 printf("Waiting for debugger (10 secs)..");
869 if (IsDebuggerPresent())
876 printf("Continue\n");
880 /* Load global VDM settings */
881 LoadGlobalSettings(&GlobalSettings
);
883 DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
885 /* Initialize the console */
888 wprintf(L
"FATAL: A problem occurred when trying to initialize the console\n");
892 /* Initialize the emulator */
893 if (!EmulatorInitialize(ConsoleInput
, ConsoleOutput
))
895 wprintf(L
"FATAL: Failed to initialize the emulator\n");
899 /* Initialize the system BIOS and option ROMs */
900 if (!BiosInitialize(GlobalSettings
.BiosFileName
.Buffer
,
901 GlobalSettings
.RomFiles
.Buffer
))
903 wprintf(L
"FATAL: Failed to initialize the VDM BIOS.\n");
907 /* Let's go! Start simulation */