Git conversion: Make reactos the root directory, move rosapps, rostests, wallpapers...
[reactos.git] / subsystems / mvdm / ntvdm / ntvdm.c
diff --git a/subsystems/mvdm/ntvdm/ntvdm.c b/subsystems/mvdm/ntvdm/ntvdm.c
new file mode 100644 (file)
index 0000000..4804536
--- /dev/null
@@ -0,0 +1,545 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            subsystems/mvdm/ntvdm/ntvdm.c
+ * PURPOSE:         Virtual DOS Machine
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+/* INCLUDES *******************************************************************/
+
+#include "ntvdm.h"
+
+#define NDEBUG
+#include <debug.h>
+
+#include "emulator.h"
+
+#include "bios/bios.h"
+#include "cpu/cpu.h"
+
+#include "dos/dem.h"
+
+/* VARIABLES ******************************************************************/
+
+NTVDM_SETTINGS GlobalSettings;
+
+// Command line of NTVDM
+INT     NtVdmArgc;
+WCHAR** NtVdmArgv;
+
+/* PRIVATE FUNCTIONS **********************************************************/
+
+static NTSTATUS
+NTAPI
+NtVdmConfigureBios(IN PWSTR ValueName,
+                   IN ULONG ValueType,
+                   IN PVOID ValueData,
+                   IN ULONG ValueLength,
+                   IN PVOID Context,
+                   IN PVOID EntryContext)
+{
+    PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context;
+    UNICODE_STRING ValueString;
+
+    /* Check for the type of the value */
+    if (ValueType != REG_SZ)
+    {
+        RtlInitEmptyAnsiString(&Settings->BiosFileName, NULL, 0);
+        return STATUS_SUCCESS;
+    }
+
+    /* Convert the UNICODE string to ANSI and store it */
+    RtlInitEmptyUnicodeString(&ValueString, (PWCHAR)ValueData, ValueLength);
+    ValueString.Length = ValueString.MaximumLength;
+    RtlUnicodeStringToAnsiString(&Settings->BiosFileName, &ValueString, TRUE);
+
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS
+NTAPI
+NtVdmConfigureRom(IN PWSTR ValueName,
+                  IN ULONG ValueType,
+                  IN PVOID ValueData,
+                  IN ULONG ValueLength,
+                  IN PVOID Context,
+                  IN PVOID EntryContext)
+{
+    PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context;
+    UNICODE_STRING ValueString;
+
+    /* Check for the type of the value */
+    if (ValueType != REG_MULTI_SZ)
+    {
+        RtlInitEmptyAnsiString(&Settings->RomFiles, NULL, 0);
+        return STATUS_SUCCESS;
+    }
+
+    /* Convert the UNICODE string to ANSI and store it */
+    RtlInitEmptyUnicodeString(&ValueString, (PWCHAR)ValueData, ValueLength);
+    ValueString.Length = ValueString.MaximumLength;
+    RtlUnicodeStringToAnsiString(&Settings->RomFiles, &ValueString, TRUE);
+
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS
+NTAPI
+NtVdmConfigureFloppy(IN PWSTR ValueName,
+                     IN ULONG ValueType,
+                     IN PVOID ValueData,
+                     IN ULONG ValueLength,
+                     IN PVOID Context,
+                     IN PVOID EntryContext)
+{
+    BOOLEAN Success;
+    PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context;
+    ULONG DiskNumber = (ULONG)EntryContext;
+
+    ASSERT(DiskNumber < ARRAYSIZE(Settings->FloppyDisks));
+
+    /* Check whether the Hard Disk entry was not already configured */
+    if (Settings->FloppyDisks[DiskNumber].Buffer != NULL)
+    {
+        DPRINT1("Floppy Disk %d -- '%wZ' already configured\n", DiskNumber, &Settings->FloppyDisks[DiskNumber]);
+        return STATUS_SUCCESS;
+    }
+
+    /* Check for the type of the value */
+    if (ValueType != REG_SZ)
+    {
+        RtlInitEmptyUnicodeString(&Settings->FloppyDisks[DiskNumber], NULL, 0);
+        return STATUS_SUCCESS;
+    }
+
+    /* Initialize the string */
+    Success = RtlCreateUnicodeString(&Settings->FloppyDisks[DiskNumber], (PCWSTR)ValueData);
+    ASSERT(Success);
+
+    return STATUS_SUCCESS;
+}
+
+static NTSTATUS
+NTAPI
+NtVdmConfigureHDD(IN PWSTR ValueName,
+                  IN ULONG ValueType,
+                  IN PVOID ValueData,
+                  IN ULONG ValueLength,
+                  IN PVOID Context,
+                  IN PVOID EntryContext)
+{
+    BOOLEAN Success;
+    PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context;
+    ULONG DiskNumber = (ULONG)EntryContext;
+
+    ASSERT(DiskNumber < ARRAYSIZE(Settings->HardDisks));
+
+    /* Check whether the Hard Disk entry was not already configured */
+    if (Settings->HardDisks[DiskNumber].Buffer != NULL)
+    {
+        DPRINT1("Hard Disk %d -- '%wZ' already configured\n", DiskNumber, &Settings->HardDisks[DiskNumber]);
+        return STATUS_SUCCESS;
+    }
+
+    /* Check for the type of the value */
+    if (ValueType != REG_SZ)
+    {
+        RtlInitEmptyUnicodeString(&Settings->HardDisks[DiskNumber], NULL, 0);
+        return STATUS_SUCCESS;
+    }
+
+    /* Initialize the string */
+    Success = RtlCreateUnicodeString(&Settings->HardDisks[DiskNumber], (PCWSTR)ValueData);
+    ASSERT(Success);
+
+    return STATUS_SUCCESS;
+}
+
+static RTL_QUERY_REGISTRY_TABLE
+NtVdmConfigurationTable[] =
+{
+    {
+        NtVdmConfigureBios,
+        0,
+        L"BiosFile",
+        NULL,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureRom,
+        RTL_QUERY_REGISTRY_NOEXPAND,
+        L"RomFiles",
+        NULL,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureFloppy,
+        0,
+        L"FloppyDisk0",
+        (PVOID)0,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureFloppy,
+        0,
+        L"FloppyDisk1",
+        (PVOID)1,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureHDD,
+        0,
+        L"HardDisk0",
+        (PVOID)0,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureHDD,
+        0,
+        L"HardDisk1",
+        (PVOID)1,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureHDD,
+        0,
+        L"HardDisk2",
+        (PVOID)2,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    {
+        NtVdmConfigureHDD,
+        0,
+        L"HardDisk3",
+        (PVOID)3,
+        REG_NONE,
+        NULL,
+        0
+    },
+
+    /* End of table */
+    {0}
+};
+
+static BOOL
+LoadGlobalSettings(IN PNTVDM_SETTINGS Settings)
+{
+    NTSTATUS Status;
+
+    ASSERT(Settings);
+
+    /*
+     * Now we can do:
+     * - CPU core choice
+     * - Video choice
+     * - Sound choice
+     * - Mem?
+     * - ...
+     * - Standalone mode?
+     * - Debug settings
+     */
+    Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL,
+                                    L"NTVDM",
+                                    NtVdmConfigurationTable,
+                                    Settings,
+                                    NULL);
+    if (!NT_SUCCESS(Status))
+    {
+        DPRINT1("NTVDM registry settings cannot be fully initialized, using default ones. Status = 0x%08lx\n", Status);
+    }
+
+    return NT_SUCCESS(Status);
+}
+
+static VOID
+FreeGlobalSettings(IN PNTVDM_SETTINGS Settings)
+{
+    USHORT i;
+
+    ASSERT(Settings);
+
+    if (Settings->BiosFileName.Buffer)
+        RtlFreeAnsiString(&Settings->BiosFileName);
+
+    if (Settings->RomFiles.Buffer)
+        RtlFreeAnsiString(&Settings->RomFiles);
+
+    for (i = 0; i < ARRAYSIZE(Settings->FloppyDisks); ++i)
+    {
+        if (Settings->FloppyDisks[i].Buffer)
+            RtlFreeUnicodeString(&Settings->FloppyDisks[i]);
+    }
+
+    for (i = 0; i < ARRAYSIZE(Settings->HardDisks); ++i)
+    {
+        if (Settings->HardDisks[i].Buffer)
+            RtlFreeUnicodeString(&Settings->HardDisks[i]);
+    }
+}
+
+static VOID
+ConsoleCleanup(VOID);
+
+/** HACK!! **/
+#include "./console/console.c"
+/** HACK!! **/
+
+/*static*/ VOID
+VdmShutdown(BOOLEAN Immediate)
+{
+    /*
+     * Immediate = TRUE:  Immediate shutdown;
+     *             FALSE: Delayed shutdown.
+     */
+    static BOOLEAN MustShutdown = FALSE;
+
+    /* If a shutdown is ongoing, just return */
+    if (MustShutdown)
+    {
+        DPRINT1("Shutdown is ongoing...\n");
+        Sleep(INFINITE);
+        return;
+    }
+
+    /* First notify DOS to see whether we can shut down now */
+    MustShutdown = DosShutdown(Immediate);
+    /*
+     * In case we perform an immediate shutdown, or the DOS says
+     * we can shut down, do it now.
+     */
+    MustShutdown = MustShutdown || Immediate;
+
+    if (MustShutdown)
+    {
+        EmulatorTerminate();
+
+        BiosCleanup();
+        EmulatorCleanup();
+        ConsoleCleanup();
+
+        FreeGlobalSettings(&GlobalSettings);
+
+        DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
+        /* Some VDDs rely on the fact that NTVDM calls ExitProcess on Windows */
+        ExitProcess(0);
+    }
+}
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+VOID
+DisplayMessage(IN LPCWSTR Format, ...)
+{
+#ifndef WIN2K_COMPLIANT
+    WCHAR  StaticBuffer[256];
+    LPWSTR Buffer = StaticBuffer; // Use the static buffer by default.
+#else
+    WCHAR  Buffer[2048]; // Large enough. If not, increase it by hand.
+#endif
+    size_t MsgLen;
+    va_list args;
+
+    va_start(args, Format);
+
+#ifndef WIN2K_COMPLIANT
+    /*
+     * Retrieve the message length and if it is too long, allocate
+     * an auxiliary buffer; otherwise use the static buffer.
+     * The string is built to be NULL-terminated.
+     */
+    MsgLen = _vscwprintf(Format, args);
+    if (MsgLen >= ARRAYSIZE(StaticBuffer))
+    {
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(WCHAR));
+        if (Buffer == NULL)
+        {
+            /* Allocation failed, use the static buffer and display a suitable error message */
+            Buffer = StaticBuffer;
+            Format = L"DisplayMessage()\nOriginal message is too long and allocating an auxiliary buffer failed.";
+            MsgLen = wcslen(Format);
+        }
+    }
+#else
+    MsgLen = ARRAYSIZE(Buffer) - 1;
+#endif
+
+    RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(WCHAR));
+    _vsnwprintf(Buffer, MsgLen, Format, args);
+
+    va_end(args);
+
+    /* Display the message */
+    DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
+    MessageBoxW(hConsoleWnd, Buffer, L"NTVDM Subsystem", MB_OK);
+
+#ifndef WIN2K_COMPLIANT
+    /* Free the buffer if needed */
+    if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
+#endif
+}
+
+/*
+ * This function, derived from DisplayMessage, is used by the BIOS and
+ * the DOS to display messages to an output device. A printer function
+ * is given for printing the characters.
+ */
+VOID
+PrintMessageAnsi(IN CHAR_PRINT CharPrint,
+                 IN LPCSTR Format, ...)
+{
+    static CHAR CurChar = 0;
+    LPSTR str;
+
+#ifndef WIN2K_COMPLIANT
+    CHAR  StaticBuffer[256];
+    LPSTR Buffer = StaticBuffer; // Use the static buffer by default.
+#else
+    CHAR  Buffer[2048]; // Large enough. If not, increase it by hand.
+#endif
+    size_t MsgLen;
+    va_list args;
+
+    va_start(args, Format);
+
+#ifndef WIN2K_COMPLIANT
+    /*
+     * Retrieve the message length and if it is too long, allocate
+     * an auxiliary buffer; otherwise use the static buffer.
+     * The string is built to be NULL-terminated.
+     */
+    MsgLen = _vscprintf(Format, args);
+    if (MsgLen >= ARRAYSIZE(StaticBuffer))
+    {
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
+        if (Buffer == NULL)
+        {
+            /* Allocation failed, use the static buffer and display a suitable error message */
+            Buffer = StaticBuffer;
+            Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed.";
+            MsgLen = strlen(Format);
+        }
+    }
+#else
+    MsgLen = ARRAYSIZE(Buffer) - 1;
+#endif
+
+    RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR));
+    _vsnprintf(Buffer, MsgLen, Format, args);
+
+    va_end(args);
+
+    /* Display the message */
+    // DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
+
+    MsgLen = strlen(Buffer);
+    str = Buffer;
+    while (MsgLen--)
+    {
+        if (*str == '\n' && CurChar != '\r')
+            CharPrint('\r');
+
+        CurChar = *str++;
+        CharPrint(CurChar);
+    }
+
+#ifndef WIN2K_COMPLIANT
+    /* Free the buffer if needed */
+    if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
+#endif
+}
+
+INT
+wmain(INT argc, WCHAR *argv[])
+{
+    NtVdmArgc = argc;
+    NtVdmArgv = argv;
+
+#ifdef STANDALONE
+
+    if (argc < 2)
+    {
+        wprintf(L"\nReactOS Virtual DOS Machine\n\n"
+                L"Usage: NTVDM <executable> [<parameters>]\n");
+        return 0;
+    }
+
+#endif
+
+#ifdef ADVANCED_DEBUGGING
+    {
+    INT i = 20;
+
+    printf("Waiting for debugger (10 secs)..");
+    while (i--)
+    {
+        printf(".");
+        if (IsDebuggerPresent())
+        {
+            DbgBreakPoint();
+            break;
+        }
+        Sleep(500);
+    }
+    printf("Continue\n");
+    }
+#endif
+
+    /* Load the global VDM settings */
+    LoadGlobalSettings(&GlobalSettings);
+
+    DPRINT1("\n\n\nNTVDM - Starting...\n\n\n");
+
+    /* 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 and option ROMs */
+    if (!BiosInitialize(GlobalSettings.BiosFileName.Buffer,
+                        GlobalSettings.RomFiles.Buffer))
+    {
+        wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
+        goto Cleanup;
+    }
+
+    /* Let's go! Start simulation */
+    CpuSimulate();
+
+    /* Quit the VDM */
+Cleanup:
+    VdmShutdown(TRUE);
+    return 0;
+}
+
+/* EOF */