[NTVDM]
[reactos.git] / subsystems / ntvdm / ntvdm.c
index 40a5cdd..b919c81 100644 (file)
@@ -6,7 +6,26 @@
  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
  */
 
+/* INCLUDES *******************************************************************/
+
+#define NDEBUG
+
 #include "ntvdm.h"
+#include "emulator.h"
+#include "bios.h"
+#include "vga.h"
+#include "dos.h"
+#include "timer.h"
+#include "pic.h"
+#include "ps2.h"
+
+/*
+ * Activate this line if you want to be able to test NTVDM with:
+ * ntvdm.exe <program>
+ */
+#define TESTING
+
+/* PUBLIC VARIABLES ***********************************************************/
 
 BOOLEAN VdmRunning = TRUE;
 LPVOID BaseAddress = NULL;
@@ -22,6 +41,8 @@ LPCWSTR ExceptionName[] =
     L"FPU Not Available"
 };
 
+/* PUBLIC FUNCTIONS ***********************************************************/
+
 VOID DisplayMessage(LPCWSTR Format, ...)
 {
     WCHAR Buffer[256];
@@ -29,7 +50,7 @@ VOID DisplayMessage(LPCWSTR Format, ...)
 
     va_start(Parameters, Format);
     _vsnwprintf(Buffer, 256, Format, Parameters);
-    MessageBox(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
+    MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
     va_end(Parameters);
 }
 
@@ -42,6 +63,7 @@ BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
         {
             /* Perform interrupt 0x23 */
             EmulatorInterrupt(0x23);
+            break;
         }
         default:
         {
@@ -55,47 +77,49 @@ BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
 INT wmain(INT argc, WCHAR *argv[])
 {
     INT i;
-    BOOLEAN PrintUsage = TRUE;
-    CHAR CommandLine[128];
+    CHAR CommandLine[DOS_CMDLINE_LENGTH];
+    DWORD CurrentTickCount;
+    DWORD Cycles = 0;
+    DWORD LastCyclePrintout = GetTickCount();
+    DWORD LastVerticalRefresh = GetTickCount();
+    LARGE_INTEGER Frequency, LastTimerTick, Counter;
+    LONGLONG TimerTicks;
+    HANDLE InputThread = NULL;
 
     /* Set the handler routine */
     SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
 
-    /* Parse the command line arguments */
-    for (i = 1; i < argc; i++)
-    {
-        if (argv[i][0] != L'-' && argv[i][0] != L'/') continue;
+#ifndef TESTING
+    UNREFERENCED_PARAMETER(argc);
+    UNREFERENCED_PARAMETER(argv);
 
-        switch (argv[i][1])
-        {
-            case L'f':
-            case L'F':
-            {
-                if (argv[i+1] != NULL)
-                {
-                    /* The DOS command line must be ASCII */
-                    WideCharToMultiByte(CP_ACP, 0, argv[i+1], -1, CommandLine, 128, NULL, NULL);
-
-                    /* This is the only mandatory parameter */
-                    PrintUsage = FALSE;
-                }
-                break;
-            }
-            default:
-            {
-                wprintf(L"Unknown option: %s", argv[i]);
-            }
-        }
+    /* The DOS command line must be ASCII */
+    WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, sizeof(CommandLine), NULL, NULL);
+#else
+    if (argc == 2 && argv[1] != NULL)
+    {
+        WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
     }
-
-    if (PrintUsage)
+    else
     {
-        wprintf(L"ReactOS Virtual DOS Machine\n\n");
-        wprintf(L"Usage: NTVDM /F <PROGRAM>\n");
+        wprintf(L"\nReactOS Virtual DOS Machine\n\n"
+                L"Usage: NTVDM <executable>\n");
         return 0;
     }
+#endif
 
-    if (!EmulatorInitialize()) return 1;
+    if (!EmulatorInitialize())
+    {
+        wprintf(L"FATAL: Failed to initialize the CPU emulator\n");
+        goto Cleanup;
+    }
+    
+    /* Initialize the performance counter (needed for hardware timers) */
+    if (!QueryPerformanceFrequency(&Frequency))
+    {
+        wprintf(L"FATAL: Performance counter not available\n");
+        goto Cleanup;
+    }
 
     /* Initialize the system BIOS */
     if (!BiosInitialize())
@@ -118,10 +142,78 @@ INT wmain(INT argc, WCHAR *argv[])
         return -1;
     }
 
+    /* Start the input thread */
+    InputThread = CreateThread(NULL, 0, &InputThreadProc, NULL, 0, NULL);
+    /* Set the last timer tick to the current time */
+    QueryPerformanceCounter(&LastTimerTick);
+
     /* Main loop */
-    while (VdmRunning) EmulatorStep();
+    while (VdmRunning)
+    {
+        /* Get the resolution of the system timer */
+        DWORD TimerResolution = PitGetResolution();
+
+        /* Get the current number of ticks */
+        CurrentTickCount = GetTickCount();
+        if (TimerResolution > 1000)
+        {
+            /* 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;
+        }
+        else
+        {
+            /* Use the standard tick count */
+            Counter.QuadPart = CurrentTickCount;
+
+            /* Get the number of PIT ticks that have passed */
+            TimerTicks = ((Counter.QuadPart - LastTimerTick.QuadPart)
+                         * PIT_BASE_FREQUENCY) / 1000;
+        }
+        /* Update the PIT */
+        if (TimerTicks > 0)
+        {
+            for (i = 0; i < TimerTicks; i++) PitDecrementCount();
+            LastTimerTick = Counter;
+        }
+
+        /* Check for vertical retrace */
+        if ((CurrentTickCount - LastVerticalRefresh) >= 16)
+        {
+            VgaRefreshDisplay();
+            LastVerticalRefresh = CurrentTickCount;
+        }
+
+        /* Horizontal retrace occurs as fast as possible */
+        VgaHorizontalRetrace();
+
+        /* Continue CPU emulation */
+        for (i = 0; (i < STEPS_PER_CYCLE) && VdmRunning; i++)
+        {
+            EmulatorStep();
+            Cycles++;
+        }
+
+        if ((CurrentTickCount - LastCyclePrintout) >= 1000)
+        {
+            DPRINT1("NTVDM: %lu Instructions Per Second\n", Cycles);
+            LastCyclePrintout = CurrentTickCount;
+            Cycles = 0;
+        }
+    }
+
+    /* Perform another screen refresh */
+    VgaRefreshDisplay();
 
 Cleanup:
+    if (InputThread != NULL) CloseHandle(InputThread);
+    BiosCleanup();
     EmulatorCleanup();
 
     return 0;