* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
*/
+/* INCLUDES *******************************************************************/
+
+#define NDEBUG
+
#include "ntvdm.h"
+#include "emulator.h"
+
+#include "clock.h"
+#include "hardware/ps2.h"
+#include "hardware/vga.h"
+#include "bios/bios.h"
+#include "dos/dem.h"
+
+#include "resource.h"
+
+/*
+ * Activate this line if you want to run NTVDM in standalone mode with:
+ * ntvdm.exe <program>
+ */
+// #define STANDALONE
+
+/* VARIABLES ******************************************************************/
+
+static HANDLE ConsoleInput = INVALID_HANDLE_VALUE;
+static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
+static DWORD OrgConsoleInputMode, OrgConsoleOutputMode;
+static BOOLEAN AcceptCommands = TRUE;
+static HANDLE CommandThread = NULL;
+
+static HMENU hConsoleMenu = NULL;
+static INT VdmMenuPos = -1;
+static BOOLEAN ShowPointer = FALSE;
+
+ULONG SessionId = 0;
+HANDLE VdmTaskEvent = NULL;
-BOOLEAN VdmRunning = TRUE;
-LPVOID BaseAddress = NULL;
-LPCWSTR ExceptionName[] =
-{
- L"Division By Zero",
- L"Debug",
- L"Unexpected Error",
- L"Breakpoint",
- L"Integer Overflow",
- L"Bound Range Exceeded",
- L"Invalid Opcode",
- L"FPU Not Available"
+/*
+ * Those menu helpers were taken from the GUI frontend in winsrv.dll
+ */
+typedef struct _VDM_MENUITEM
+{
+ UINT uID;
+ const struct _VDM_MENUITEM *SubMenu;
+ WORD wCmdID;
+} VDM_MENUITEM, *PVDM_MENUITEM;
+
+static const VDM_MENUITEM VdmMenuItems[] =
+{
+ { IDS_VDM_QUIT, NULL, ID_VDM_QUIT },
+
+ { 0, NULL, 0 } /* End of list */
};
+static const VDM_MENUITEM VdmMainMenuItems[] =
+{
+ { -1, NULL, 0 }, /* Separator */
+ { IDS_HIDE_MOUSE, NULL, ID_SHOWHIDE_MOUSE }, /* Hide mouse; can be renamed to Show mouse */
+ { IDS_VDM_MENU , VdmMenuItems, 0 }, /* ReactOS VDM Menu */
+
+ { 0, NULL, 0 } /* End of list */
+};
+
+static VOID
+AppendMenuItems(HMENU hMenu,
+ const VDM_MENUITEM *Items)
+{
+ UINT i = 0;
+ WCHAR szMenuString[255];
+ HMENU hSubMenu;
+
+ do
+ {
+ if (Items[i].uID != (UINT)-1)
+ {
+ if (LoadStringW(GetModuleHandle(NULL),
+ Items[i].uID,
+ szMenuString,
+ sizeof(szMenuString) / sizeof(szMenuString[0])) > 0)
+ {
+ if (Items[i].SubMenu != NULL)
+ {
+ hSubMenu = CreatePopupMenu();
+ if (hSubMenu != NULL)
+ {
+ AppendMenuItems(hSubMenu, Items[i].SubMenu);
+
+ if (!AppendMenuW(hMenu,
+ MF_STRING | MF_POPUP,
+ (UINT_PTR)hSubMenu,
+ szMenuString))
+ {
+ DestroyMenu(hSubMenu);
+ }
+ }
+ }
+ else
+ {
+ AppendMenuW(hMenu,
+ MF_STRING,
+ Items[i].wCmdID,
+ szMenuString);
+ }
+ }
+ }
+ else
+ {
+ AppendMenuW(hMenu,
+ MF_SEPARATOR,
+ 0,
+ NULL);
+ }
+ i++;
+ } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
+}
+
+static VOID
+CreateVdmMenu(HANDLE ConOutHandle)
+{
+ hConsoleMenu = ConsoleMenuControl(ConsoleOutput,
+ ID_SHOWHIDE_MOUSE,
+ ID_VDM_QUIT);
+ if (hConsoleMenu == NULL) return;
+
+ VdmMenuPos = GetMenuItemCount(hConsoleMenu);
+ AppendMenuItems(hConsoleMenu, VdmMainMenuItems);
+ DrawMenuBar(GetConsoleWindow());
+}
+
+static VOID
+DestroyVdmMenu(VOID)
+{
+ UINT i = 0;
+ const VDM_MENUITEM *Items = VdmMainMenuItems;
+
+ do
+ {
+ DeleteMenu(hConsoleMenu, VdmMenuPos, MF_BYPOSITION);
+ i++;
+ } while (!(Items[i].uID == 0 && Items[i].SubMenu == NULL && Items[i].wCmdID == 0));
+
+ DrawMenuBar(GetConsoleWindow());
+}
+
+static VOID ShowHideMousePointer(HANDLE ConOutHandle, BOOLEAN ShowPtr)
+{
+ WCHAR szMenuString[255] = L"";
+
+ if (ShowPtr)
+ {
+ /* Be sure the cursor will be shown */
+ while (ShowConsoleCursor(ConOutHandle, TRUE) < 0) ;
+ }
+ else
+ {
+ /* Be sure the cursor will be hidden */
+ while (ShowConsoleCursor(ConOutHandle, FALSE) >= 0) ;
+ }
+
+ if (LoadStringW(GetModuleHandle(NULL),
+ (!ShowPtr ? IDS_SHOW_MOUSE : IDS_HIDE_MOUSE),
+ szMenuString,
+ sizeof(szMenuString) / sizeof(szMenuString[0])) > 0)
+ {
+ ModifyMenu(hConsoleMenu, ID_SHOWHIDE_MOUSE,
+ MF_BYCOMMAND, ID_SHOWHIDE_MOUSE, szMenuString);
+ }
+}
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
VOID DisplayMessage(LPCWSTR Format, ...)
{
WCHAR Buffer[256];
va_start(Parameters, Format);
_vsnwprintf(Buffer, 256, Format, Parameters);
- MessageBox(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
+ DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
+ MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
va_end(Parameters);
}
case CTRL_C_EVENT:
case CTRL_BREAK_EVENT:
{
- /* Perform interrupt 0x23 */
+ /* Call INT 23h */
EmulatorInterrupt(0x23);
break;
}
+ case CTRL_LAST_CLOSE_EVENT:
+ {
+ if (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
+ {
+ /* Exit immediately */
+ if (CommandThread) TerminateThread(CommandThread, 0);
+ EmulatorTerminate();
+ }
+ else
+ {
+ /* Stop accepting new commands */
+ AcceptCommands = FALSE;
+ }
+
+ break;
+ }
default:
{
/* Stop the VDM if the user logs out or closes the console */
- VdmRunning = FALSE;
+ EmulatorTerminate();
}
}
return TRUE;
}
-INT wmain(INT argc, WCHAR *argv[])
+VOID ConsoleInitUI(VOID)
{
- INT i;
- CHAR CommandLine[128];
- DWORD CurrentTickCount, LastTickCount = 0, Cycles = 0, LastCyclePrintout = 0;
- LARGE_INTEGER Frequency, LastTimerTick, Counter;
- LONGLONG TimerTicks;
+ CreateVdmMenu(ConsoleOutput);
+}
+VOID ConsoleCleanupUI(VOID)
+{
+ /* Display again properly the mouse pointer */
+ if (ShowPointer) ShowHideMousePointer(ConsoleOutput, ShowPointer);
+
+ DestroyVdmMenu();
+}
+
+DWORD WINAPI PumpConsoleInput(LPVOID Parameter)
+{
+ HANDLE ConsoleInput = (HANDLE)Parameter;
+ INPUT_RECORD InputRecord;
+ DWORD Count;
+
+ while (VdmRunning)
+ {
+ /* Make sure the task event is signaled */
+ WaitForSingleObject(VdmTaskEvent, INFINITE);
+
+ /* Wait for an input record */
+ if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
+ {
+ DWORD LastError = GetLastError();
+ DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
+ return LastError;
+ }
+
+ ASSERT(Count != 0);
+
+ /* Check the event type */
+ switch (InputRecord.EventType)
+ {
+ case KEY_EVENT:
+ case MOUSE_EVENT:
+ /* Send it to the PS/2 controller */
+ PS2Dispatch(&InputRecord);
+ break;
+
+ case MENU_EVENT:
+ {
+ switch (InputRecord.Event.MenuEvent.dwCommandId)
+ {
+ case ID_SHOWHIDE_MOUSE:
+ ShowHideMousePointer(ConsoleOutput, ShowPointer);
+ ShowPointer = !ShowPointer;
+ break;
+
+ case ID_VDM_QUIT:
+ /* Stop the VDM */
+ EmulatorTerminate();
+ break;
+
+ default:
+ break;
+ }
+
+ break;
+ }
+
+ default:
+ break;
+ }
+ }
+
+ return 0;
+}
+
+BOOL ConsoleInit(VOID)
+{
/* Set the handler routine */
SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
- /* The DOS command line must be ASCII */
- WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, 128, NULL, NULL);
+ /* Enable the CTRL_LAST_CLOSE_EVENT */
+ SetLastConsoleEventActive();
- if (!EmulatorInitialize()) return 1;
-
- /* Initialize the performance counter (needed for hardware timers) */
- if (!QueryPerformanceFrequency(&Frequency))
+ /* Get the input handle to the real console, and check for success */
+ ConsoleInput = CreateFileW(L"CONIN$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (ConsoleInput == INVALID_HANDLE_VALUE)
{
- wprintf(L"FATAL: Performance counter not available\n");
+ wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
+ return FALSE;
+ }
+
+ /* Get the output handle to the real console, and check for success */
+ ConsoleOutput = CreateFileW(L"CONOUT$",
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL,
+ OPEN_EXISTING,
+ 0,
+ NULL);
+ if (ConsoleOutput == INVALID_HANDLE_VALUE)
+ {
+ CloseHandle(ConsoleInput);
+ wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
+ return FALSE;
+ }
+
+ /* Save the original input and output console modes */
+ if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
+ !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
+ {
+ CloseHandle(ConsoleOutput);
+ CloseHandle(ConsoleInput);
+ wprintf(L"FATAL: Cannot save console in/out modes\n");
+ return FALSE;
+ }
+
+ /* Initialize the UI */
+ ConsoleInitUI();
+
+ return TRUE;
+}
+
+VOID ConsoleCleanup(VOID)
+{
+ /* Restore the original input and output console modes */
+ SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
+ SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
+
+ /* Cleanup the UI */
+ ConsoleCleanupUI();
+
+ /* Close the console handles */
+ if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
+ if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
+}
+
+DWORD WINAPI CommandThreadProc(LPVOID Parameter)
+{
+ BOOLEAN First = TRUE;
+ DWORD Result;
+ VDM_COMMAND_INFO CommandInfo;
+ CHAR CmdLine[MAX_PATH];
+ CHAR AppName[MAX_PATH];
+ CHAR PifFile[MAX_PATH];
+ CHAR Desktop[MAX_PATH];
+ CHAR Title[MAX_PATH];
+ CHAR Env[MAX_PATH];
+
+ UNREFERENCED_PARAMETER(Parameter);
+
+ while (AcceptCommands)
+ {
+ /* Clear the structure */
+ ZeroMemory(&CommandInfo, sizeof(CommandInfo));
+
+ /* Initialize the structure members */
+ CommandInfo.TaskId = SessionId;
+ CommandInfo.VDMState = VDM_FLAG_DOS;
+ CommandInfo.CmdLine = CmdLine;
+ CommandInfo.CmdLen = sizeof(CmdLine);
+ CommandInfo.AppName = AppName;
+ CommandInfo.AppLen = sizeof(AppName);
+ CommandInfo.PifFile = PifFile;
+ CommandInfo.PifLen = sizeof(PifFile);
+ CommandInfo.Desktop = Desktop;
+ CommandInfo.DesktopLen = sizeof(Desktop);
+ CommandInfo.Title = Title;
+ CommandInfo.TitleLen = sizeof(Title);
+ CommandInfo.Env = Env;
+ CommandInfo.EnvLen = sizeof(Env);
+
+ if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
+
+ /* Wait for the next available VDM */
+ if (!GetNextVDMCommand(&CommandInfo)) break;
+
+ /* Start the process from the command line */
+ DPRINT1("Starting '%s'...\n", AppName);
+
+ Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE, AppName, CmdLine, Env, NULL, NULL);
+ if (Result != ERROR_SUCCESS)
+ {
+ DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result);
+ break;
+ }
+
+ /* Attach to the console */
+ if (!First) VgaAttachToConsole();
+
+ /* Perform a screen refresh */
+ VgaRefreshDisplay();
+
+ /* Start simulation */
+ SetEvent(VdmTaskEvent);
+ EmulatorSimulate();
+
+ /* Perform another screen refresh */
+ VgaRefreshDisplay();
+
+ /* Detach from the console */
+ VgaDetachFromConsole(FALSE);
+
+ First = FALSE;
+ }
+
+ return 0;
+}
+
+INT wmain(INT argc, WCHAR *argv[])
+{
+#ifdef STANDALONE
+
+ DWORD Result;
+ CHAR ApplicationName[MAX_PATH];
+ CHAR CommandLine[DOS_CMDLINE_LENGTH];
+
+ if (argc >= 2)
+ {
+ WideCharToMultiByte(CP_ACP, 0, argv[1], -1, ApplicationName, sizeof(ApplicationName), NULL, NULL);
+
+ if (argc >= 3) WideCharToMultiByte(CP_ACP, 0, argv[2], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
+ else strcpy(CommandLine, "");
+ }
+ else
+ {
+ wprintf(L"\nReactOS Virtual DOS Machine\n\n"
+ L"Usage: NTVDM <executable> [<parameters>]\n");
+ return 0;
+ }
+
+#else
+ INT i;
+ WCHAR *endptr;
+
+ /* Parse the command line arguments */
+ for (i = 1; i < argc; i++)
+ {
+ if (wcsncmp(argv[i], L"-i", 2) == 0)
+ {
+ /* This is the session ID */
+ SessionId = wcstoul(argv[i] + 2, &endptr, 10);
+ }
+ }
+
+#endif
+
+ DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
+
+ /* Create the task event */
+ VdmTaskEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+ ASSERT(VdmTaskEvent != NULL);
+
+ /* Initialize the console */
+ if (!ConsoleInit())
+ {
+ wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
+ goto Cleanup;
+ }
+
+ /* Initialize the emulator */
+ if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
+ {
+ wprintf(L"FATAL: Failed to initialize the emulator\n");
goto Cleanup;
}
/* Initialize the system BIOS */
- if (!BiosInitialize())
+ if (!BiosInitialize(NULL))
{
wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
goto Cleanup;
}
/* Initialize the VDM DOS kernel */
- if (!DosInitialize())
+ if (!DosInitialize(NULL))
{
wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
goto Cleanup;
}
- /* Start the process from the command line */
- if (!DosCreateProcess(CommandLine, 0))
+#ifndef STANDALONE
+
+ /* Create the GetNextVDMCommand thread */
+ CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
+ if (CommandThread == NULL)
{
- DisplayMessage(L"Could not start program: %S", CommandLine);
- return -1;
+ wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
+ goto Cleanup;
}
-
- /* Set the last timer tick to the current time */
- QueryPerformanceCounter(&LastTimerTick);
- /* Main loop */
- while (VdmRunning)
+ /* Wait for the command thread to exit */
+ WaitForSingleObject(CommandThread, INFINITE);
+
+ /* Close the thread handle */
+ CloseHandle(CommandThread);
+
+#else
+
+ /* Start the process from the command line */
+ DPRINT1("Starting '%s'...\n", ApplicationName);
+
+ Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
+ ApplicationName,
+ CommandLine,
+ GetEnvironmentStrings(),
+ NULL,
+ NULL);
+ if (Result != ERROR_SUCCESS)
{
- /* Get the current number of ticks */
- CurrentTickCount = GetTickCount();
-
- /* Get the current performance counter value */
- QueryPerformanceCounter(&Counter);
-
- /* Get the number of PIT ticks that have passed */
- TimerTicks = ((Counter.QuadPart - LastTimerTick.QuadPart)
- * PIT_BASE_FREQUENCY) / Frequency.QuadPart;
-
- /* Update the PIT */
- for (i = 0; i < TimerTicks; i++) PitDecrementCount();
- LastTimerTick = Counter;
-
- /* Check for console input events every millisecond */
- if (CurrentTickCount != LastTickCount)
- {
- CheckForInputEvents();
- LastTickCount = CurrentTickCount;
- }
-
- /* Continue CPU emulation */
- for (i = 0; (i < STEPS_PER_CYCLE) && VdmRunning; i++)
- {
- EmulatorStep();
- Cycles++;
- }
-
- if ((CurrentTickCount - LastCyclePrintout) >= 1000)
- {
- DPRINT1("NTVDM: %d Instructions Per Second\n", Cycles);
- LastCyclePrintout = CurrentTickCount;
- Cycles = 0;
- }
+ DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result);
+ goto Cleanup;
}
+ /* Start simulation */
+ SetEvent(VdmTaskEvent);
+ EmulatorSimulate();
+
+ /* Perform another screen refresh */
+ VgaRefreshDisplay();
+
+#endif
+
Cleanup:
+ BiosCleanup();
EmulatorCleanup();
+ ConsoleCleanup();
+
+#ifndef STANDALONE
+ ExitVDM(FALSE, 0);
+#endif
+
+ /* Quit the VDM */
+ DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
return 0;
}