[NTVDM]: Fix parentheses. Thanks GCC.
[reactos.git] / reactos / subsystems / mvdm / ntvdm / dos / dem.c
index 92ef845..ebe4459 100644 (file)
 
 #include "ntvdm.h"
 #include "emulator.h"
+#include <isvbop.h>
+
 #include "utils.h"
 
 #include "dem.h"
 #include "dos/dos32krnl/device.h"
+#include "dos/dos32krnl/memory.h"
+#include "dos/dos32krnl/process.h"
 #include "cpu/bop.h"
+#include "cpu/cpu.h"
 
 #include "bios/bios.h"
 #include "mouse32.h"
 
-/* PRIVATE VARIABLES **********************************************************/
+#include "vddsup.h"
+
+/*
+ * EXPERIMENTAL!
+ * Activate this line if you want to have COMMAND.COM completely external.
+ */
+// #define COMSPEC_FULLY_EXTERNAL
 
-/**/extern BYTE CurrentDrive;/**/
+/* PRIVATE VARIABLES **********************************************************/
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
-static VOID WINAPI DosSystemBop(LPWORD Stack)
+/* PUBLIC VARIABLES ***********************************************************/
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
+
+/******************************************************************************\
+|**                          DOS DEM Kernel helpers                          **|
+\******************************************************************************/
+
+
+VOID BiosCharPrint(CHAR Character)
 {
-    /* Get the Function Number and skip it */
-    BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
-    setIP(getIP() + 1);
+    /* Save AX and BX */
+    USHORT AX = getAX();
+    USHORT BX = getBX();
 
-    switch (FuncNum)
+    /*
+     * Set the parameters:
+     * AL contains the character to print,
+     * BL contains the character attribute,
+     * BH contains the video page to use.
+     */
+    setAL(Character);
+    setBL(DEFAULT_ATTRIBUTE);
+    setBH(Bda->VideoPage);
+
+    /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
+    setAH(0x0E);
+    Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
+
+    /* Restore AX and BX */
+    setBX(BX);
+    setAX(AX);
+}
+
+VOID DosCharPrint(CHAR Character)
+{
+    DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
+}
+
+/*
+ * This function, derived from ntvdm.c!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.
+ */
+static VOID
+DisplayMessageAnsiV(IN CHAR_PRINT CharPrint,
+                    IN LPCSTR Format,
+                    IN va_list args)
+{
+    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;
+
+#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))
     {
-        case 0x11:  // Load the DOS kernel
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
+        if (Buffer == NULL)
         {
-            BOOLEAN Success = FALSE;
-            HANDLE  hDosKernel;
-            ULONG   ulDosKernelSize = 0;
+            /* 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
 
-            DPRINT1("You are loading Windows NT DOS!\n");
+    RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR));
+    _vsnprintf(Buffer, MsgLen, Format, args);
 
-            /* Open the DOS kernel file */
-            hDosKernel = FileOpen("ntdos.sys", &ulDosKernelSize);
+    /* Display the message */
+    DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
 
-            /* If we failed, bail out */
-            if (hDosKernel == NULL) goto Quit;
+    MsgLen = strlen(Buffer);
+    str = Buffer;
+    while (MsgLen--)
+    {
+        if (*str == '\n' && CurChar != '\r')
+            CharPrint('\r');
 
-            /*
-             * Attempt to load the DOS kernel into memory.
-             * The segment where to load the DOS kernel is defined
-             * by the DOS BIOS and is found in DI:0000 .
-             */
-            Success = FileLoadByHandle(hDosKernel,
-                                       REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
-                                       ulDosKernelSize,
-                                       &ulDosKernelSize);
+        CurChar = *str++;
+        CharPrint(CurChar);
+    }
+
+#ifndef WIN2K_COMPLIANT
+    /* Free the buffer if needed */
+    if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer);
+#endif
+}
 
-            DPRINT1("Windows NT DOS loading %s at %04X:%04X, size 0x%X ; GetLastError() = %u\n",
-                    (Success ? "succeeded" : "failed"),
-                    getDI(), 0x0000,
-                    ulDosKernelSize,
-                    GetLastError());
+VOID
+DemDisplayMessage(IN CHAR_PRINT CharPrint,
+                  IN LPCSTR Format, ...)
+{
+    va_list Parameters;
+
+    va_start(Parameters, Format);
+    DisplayMessageAnsiV(CharPrint, Format, Parameters);
+    va_end(Parameters);
+}
+
+
+static VOID DemLoadNTDOSKernel(VOID)
+{
+    BOOLEAN Success = FALSE;
+    LPCSTR  DosKernelFileName = "ntdos.sys";
+    HANDLE  hDosKernel;
+    ULONG   ulDosKernelSize = 0;
+
+    DPRINT1("You are loading Windows NT DOS!\n");
 
-            /* Close the DOS kernel file */
-            FileClose(hDosKernel);
+    /* Open the DOS kernel file */
+    hDosKernel = FileOpen(DosKernelFileName, &ulDosKernelSize);
+    if (hDosKernel == NULL) goto Quit;
+
+    /*
+     * Attempt to load the DOS kernel into memory.
+     * The segment where to load the DOS kernel is defined
+     * by the DOS BIOS and is found in DI:0000 .
+     */
+    Success = FileLoadByHandle(hDosKernel,
+                               REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
+                               ulDosKernelSize,
+                               &ulDosKernelSize);
+
+    DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
+            DosKernelFileName,
+            (Success ? "succeeded" : "failed"),
+            getDI(), 0x0000,
+            ulDosKernelSize,
+            GetLastError());
+
+    /* Close the DOS kernel file */
+    FileClose(hDosKernel);
 
 Quit:
-            if (!Success)
-            {
-                /* We failed everything, stop the VDM */
-                EmulatorTerminate();
-            }
+    if (!Success)
+    {
+        /* We failed everything, stop the VDM */
+        BiosDisplayMessage("Windows NT DOS kernel file '%s' loading failed (Error: %u). The VDM will shut down.\n",
+                           DosKernelFileName, GetLastError());
+        EmulatorTerminate();
+        return;
+    }
+}
+
+static VOID WINAPI DosSystemBop(LPWORD Stack)
+{
+    /* Get the Function Number and skip it */
+    BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
+    setIP(getIP() + 1);
 
+    switch (FuncNum)
+    {
+        /* Load the DOS kernel */
+        case 0x11:
+        {
+            DemLoadNTDOSKernel();
             break;
         }
 
@@ -105,81 +242,590 @@ Quit:
     }
 }
 
-static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
+
+
+
+/******************************************************************************\
+|**                      DOS Command Process management                      **|
+\******************************************************************************/
+
+
+#ifndef STANDALONE
+static ULONG SessionId = 0;
+
+/*
+ * 16-bit Command Interpreter information for DOS reentry
+ */
+typedef struct _COMSPEC_INFO
 {
-    /* Get the Function Number and skip it */
-    BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
-    setIP(getIP() + 1);
+    LIST_ENTRY Entry;
+    DWORD dwExitCode;
+    WORD ComSpecPsp;
+    BOOLEAN Terminated;
+} COMSPEC_INFO, *PCOMSPEC_INFO;
 
-    switch (FuncNum)
+static COMSPEC_INFO RootCmd;
+static DWORD ReentrancyCount = 0;
+
+// FIXME: Should we need list locking?
+static LIST_ENTRY ComSpecInfoList = { &ComSpecInfoList, &ComSpecInfoList };
+
+static PCOMSPEC_INFO
+FindComSpecInfoByPsp(WORD Psp)
+{
+    PLIST_ENTRY Pointer;
+    PCOMSPEC_INFO ComSpecInfo;
+
+    for (Pointer = ComSpecInfoList.Flink; Pointer != &ComSpecInfoList; Pointer = Pointer->Flink)
+    {
+        ComSpecInfo = CONTAINING_RECORD(Pointer, COMSPEC_INFO, Entry);
+        if (ComSpecInfo->ComSpecPsp == Psp) return ComSpecInfo;
+    }
+
+    return NULL;
+}
+
+static VOID
+InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
+{
+    InsertHeadList(&ComSpecInfoList, &ComSpecInfo->Entry);
+}
+
+static VOID
+RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
+{
+    RemoveEntryList(&ComSpecInfo->Entry);
+    if (ComSpecInfo != &RootCmd)
+        RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo);
+}
+#endif
+
+static VOID DosProcessConsoleAttach(VOID)
+{
+    /* Attach to the console */
+    ConsoleAttach();
+    VidBiosAttachToConsole();
+}
+
+static VOID DosProcessConsoleDetach(VOID)
+{
+    /* Detach from the console */
+    VidBiosDetachFromConsole();
+    ConsoleDetach();
+}
+
+/*
+ * Data for the next DOS command to run
+ */
+#ifndef STANDALONE
+static VDM_COMMAND_INFO CommandInfo;
+static BOOLEAN Repeat = FALSE;
+static BOOLEAN Reentry = FALSE;
+#endif
+static BOOLEAN First = TRUE;
+static CHAR CmdLine[MAX_PATH] = ""; // DOS_CMDLINE_LENGTH
+static CHAR AppName[MAX_PATH] = "";
+#ifndef STANDALONE
+static CHAR PifFile[MAX_PATH] = "";
+static CHAR CurDirectory[MAX_PATH] = "";
+static CHAR Desktop[MAX_PATH] = "";
+static CHAR Title[MAX_PATH] = "";
+static ULONG EnvSize = 256;
+static PVOID Env = NULL;
+#endif
+
+#pragma pack(push, 2)
+
+/*
+ * This structure is compatible with Windows NT DOS
+ */
+typedef struct _NEXT_CMD
+{
+    USHORT EnvBlockSeg;
+    USHORT EnvBlockLen;
+    USHORT CurDrive;
+    USHORT NumDrives;
+    USHORT CmdLineSeg;
+    USHORT CmdLineOff;
+    USHORT Unknown0;
+    USHORT ExitCode;
+    USHORT Unknown1;
+    ULONG  Unknown2;
+    USHORT CodePage;
+    USHORT Unknown3;
+    USHORT Unknown4;
+    USHORT AppNameSeg;
+    USHORT AppNameOff;
+    USHORT AppNameLen;
+    USHORT Flags;
+} NEXT_CMD, *PNEXT_CMD;
+
+#pragma pack(pop)
+
+static VOID CmdStartProcess(VOID)
+{
+#ifndef STANDALONE
+    PCOMSPEC_INFO ComSpecInfo;
+#endif
+    SIZE_T CmdLen;
+    PNEXT_CMD DataStruct = (PNEXT_CMD)SEG_OFF_TO_PTR(getDS(), getDX());
+
+    DPRINT1("CmdStartProcess -- DS:DX = %04X:%04X (DataStruct = 0x%p)\n",
+            getDS(), getDX(), DataStruct);
+
+    /* Pause the VM */
+    EmulatorPause();
+
+#ifndef STANDALONE
+    /* Check whether we need to shell out now in case we were started by a 32-bit app */
+    ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
+    if (ComSpecInfo && ComSpecInfo->Terminated)
+    {
+        RemoveComSpecInfo(ComSpecInfo);
+
+        DPRINT1("Exit DOS from start-app BOP\n");
+        setCF(1);
+        goto Quit;
+    }
+
+    /* Clear the structure */
+    RtlZeroMemory(&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.CurDirectory = CurDirectory;
+    CommandInfo.CurDirectoryLen = sizeof(CurDirectory);
+    CommandInfo.Desktop = Desktop;
+    CommandInfo.DesktopLen = sizeof(Desktop);
+    CommandInfo.Title = Title;
+    CommandInfo.TitleLen = sizeof(Title);
+    CommandInfo.Env = Env;
+    CommandInfo.EnvLen = EnvSize;
+
+    if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
+
+Command:
+
+    if (Repeat) CommandInfo.VDMState |= VDM_FLAG_RETRY;
+    Repeat = FALSE;
+
+    /* Get the VDM command information */
+    DPRINT1("Calling GetNextVDMCommand in CmdStartProcess: wait for new VDM task...\n");
+    if (!GetNextVDMCommand(&CommandInfo))
     {
-        case 0x08:  // Launch external command
+        DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
+        if (CommandInfo.EnvLen > EnvSize)
         {
-#define CMDLINE_LENGTH  1024
+            /* Expand the environment size */
+            EnvSize = CommandInfo.EnvLen;
+            CommandInfo.Env = Env = RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
 
-            BOOL Result;
-            DWORD dwExitCode;
+            /* Repeat the request */
+            Repeat = TRUE;
+            goto Command;
+        }
 
-            LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
-            LPSTR CmdPtr  = Command;
-            CHAR CommandLine[CMDLINE_LENGTH] = "";
-            STARTUPINFOA StartupInfo;
-            PROCESS_INFORMATION ProcessInformation;
+        /* Shouldn't happen */
+        DisplayMessage(L"An unrecoverable failure happened from start-app BOP; exiting DOS.");
+        setCF(1);
+        goto Quit;
+    }
 
-            /* NULL-terminate the command line by removing the return carriage character */
-            while (*CmdPtr && *CmdPtr != '\r') CmdPtr++;
-            *CmdPtr = '\0';
+    // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
 
-            DPRINT1("CMD Run Command '%s'\n", Command);
+    DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
 
-            /* Spawn a user-defined 32-bit command preprocessor */
+#else
 
-            /* Build the command line */
-            // FIXME: Use COMSPEC env var!!
-            strcpy(CommandLine, "cmd.exe /c ");
-            strcat(CommandLine, Command);
+    if (!First)
+    {
+        DPRINT1("Exit DOS from start-app BOP\n");
+        setCF(1);
+        goto Quit;
+    }
 
-            RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
-            RtlZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
+#endif
 
-            StartupInfo.cb = sizeof(StartupInfo);
+    CmdLen = strlen(CmdLine);
+    DPRINT1("Starting '%s' ('%.*s')...\n",
+            AppName,
+            /* Display the command line without the terminating 0d 0a (and skip the terminating NULL) */
+            CmdLen >= 2 ? (CmdLine[CmdLen - 2] == '\r' ? CmdLen - 2
+                                                       : CmdLen)
+                        : CmdLen,
+            CmdLine);
+
+    /* Start the process */
+    // FIXME: Merge 'Env' with the master environment SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0)
+    // FIXME: Environment
+    RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct->AppNameSeg, DataStruct->AppNameOff), AppName, MAX_PATH);
+    *(PBYTE)(SEG_OFF_TO_PTR(DataStruct->CmdLineSeg, DataStruct->CmdLineOff)) = (BYTE)(strlen(CmdLine) - 2);
+    RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct->CmdLineSeg, DataStruct->CmdLineOff + 1), CmdLine, DOS_CMDLINE_LENGTH);
 
-            VidBiosDetachFromConsole();
+#ifndef STANDALONE
+    /* Update console title if we run in a separate console */
+    if (SessionId != 0)
+        SetConsoleTitleA(AppName);
+#endif
 
-            Result = CreateProcessA(NULL,
-                                    CommandLine,
-                                    NULL,
-                                    NULL,
-                                    TRUE,
-                                    0,
-                                    NULL,
-                                    NULL,
-                                    &StartupInfo,
-                                    &ProcessInformation);
-            if (Result)
-            {
-                DPRINT1("Command '%s' launched successfully\n", Command);
+    First = FALSE;
+    setCF(0);
+
+    DPRINT1("App started!\n");
+
+Quit:
+    /* Resume the VM */
+    EmulatorResume();
+}
+
+static VOID CmdStartExternalCommand(VOID)
+{
+    DWORD Result;
+
+    // TODO: improve: this code has strong similarities
+    // with the 'default' case of DosCreateProcess.
+
+    LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
+    CHAR CmdLine[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH + 1] = "";
+    LPSTR CmdLinePtr;
+    ULONG CmdLineLen;
+
+    /* Spawn a user-defined 32-bit command preprocessor */
+
+    // FIXME: Use COMSPEC env var!!
+    CmdLinePtr = CmdLine;
+    strcpy(CmdLinePtr, "cmd.exe /c ");
+    CmdLinePtr += strlen(CmdLinePtr);
+
+    /* Build a Win32-compatible command-line */
+    CmdLineLen = min(strlen(Command), sizeof(CmdLine) - strlen(CmdLinePtr) - 1);
+    RtlCopyMemory(CmdLinePtr, Command, CmdLineLen);
+    CmdLinePtr[CmdLineLen] = '\0';
+
+    /* Remove any trailing return carriage character and NULL-terminate the command line */
+    while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
+    *CmdLinePtr = '\0';
+
+    DPRINT1("CMD Run Command '%s' ('%s')\n", Command, CmdLine);
+
+    /*
+     * No need to prepare the stack for DosStartComSpec since we won't start it.
+     */
+    Result = DosStartProcess32(Command, CmdLine,
+                               SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
+                               MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
+                               FALSE);
+    if (Result != ERROR_SUCCESS)
+    {
+        DosDisplayMessage("Failed to start command '%s' ('%s'). Error: %u\n", Command, CmdLine, Result);
+        setCF(0);
+        setAL((UCHAR)Result);
+    }
+    else
+    {
+        DosDisplayMessage("Command '%s' ('%s') started successfully.\n", Command, CmdLine);
+#ifndef STANDALONE
+        setCF(Repeat); // Set CF if we need to start a 16-bit process
+#else
+        setCF(0);
+#endif
+    }
+}
+
+static VOID CmdStartComSpec32(VOID)
+{
+    DWORD Result;
+
+    // TODO: improve: this code has strong similarities with the
+    // 'default' case of DosCreateProcess and with the 'case 0x08'.
+
+    CHAR CmdLine[sizeof("cmd.exe") + 1] = "";
+
+    /* Spawn a user-defined 32-bit command preprocessor */
+
+    // FIXME: Use COMSPEC env var!!
+    strcpy(CmdLine, "cmd.exe");
+
+    DPRINT1("CMD Run 32-bit Command Interpreter '%s'\n", CmdLine);
+
+    /*
+     * No need to prepare the stack for DosStartComSpec since we won't start it.
+     */
+    Result = DosStartProcess32(CmdLine, CmdLine,
+                               SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
+                               MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
+                               FALSE);
+    if (Result != ERROR_SUCCESS)
+    {
+        DosDisplayMessage("Failed to start 32-bit Command Interpreter '%s'. Error: %u\n", CmdLine, Result);
+        setCF(0);
+        setAL((UCHAR)Result);
+    }
+    else
+    {
+        DosDisplayMessage("32-bit Command Interpreter '%s' started successfully.\n", CmdLine);
+#ifndef STANDALONE
+        setCF(Repeat); // Set CF if we need to start a 16-bit process
+#else
+        setCF(0);
+#endif
+    }
+}
 
-                /* Wait for process termination */
-                WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
+static VOID CmdSetExitCode(VOID)
+{
+#ifndef STANDALONE
+    BOOL Success;
+    PCOMSPEC_INFO ComSpecInfo;
+    VDM_COMMAND_INFO CommandInfo;
+#endif
+
+    /* Pause the VM */
+    EmulatorPause();
+
+#ifndef STANDALONE
+    /*
+     * Check whether we need to shell out now in case we were started by a 32-bit app,
+     * or we were started alone along with the root 32-bit app.
+     */
+    ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
+    if ((ComSpecInfo && ComSpecInfo->Terminated) ||
+        (ComSpecInfo == &RootCmd && SessionId != 0))
+    {
+        RemoveComSpecInfo(ComSpecInfo);
+#endif
+        DPRINT1("Exit DOS from ExitCode (prologue)!");
+        setCF(0);
+        goto Quit;
+#ifndef STANDALONE
+    }
+
+    /* Clear the VDM structure */
+    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
+
+Retry:
+    /* Update the VDM state of the task */
+    // CommandInfo.TaskId = SessionId;
+    CommandInfo.ExitCode = getDX();
+    CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
+    DPRINT1("Calling GetNextVDMCommand 32bit end of VDM task\n");
+    Success = GetNextVDMCommand(&CommandInfo);
+    DPRINT1("GetNextVDMCommand 32bit end of VDM task success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
+
+    /*
+     * Check whether we were awaited because the 32-bit process was stopped,
+     * or because it started a new DOS application.
+     */
+    if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
+    {
+        DPRINT1("GetNextVDMCommand end-of-app, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
+                CommandInfo.CmdLen, CommandInfo.AppLen, CommandInfo.PifLen);
+
+        /* Repeat the request */
+        Repeat = TRUE;
+        setCF(1);
+    }
+    else
+    {
+        DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
+
+        /* Check whether we need to shell out now in case we were started by a 32-bit app */
+        ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
+        if (!ComSpecInfo || !ComSpecInfo->Terminated)
+        {
+            DPRINT1("Not our 32-bit app, retrying...\n");
+            goto Retry;
+        }
+
+        ASSERT(ComSpecInfo->Terminated == TRUE);
+
+        /* Record found, remove it and exit now */
+        RemoveComSpecInfo(ComSpecInfo);
+
+        DPRINT1("Exit DOS from ExitCode wait!\n");
+        setCF(0);
+    }
+#endif
+
+    // FIXME: Use the retrieved exit code as the value of our exit code
+    // when COMMAND.COM will shell-out ??
+
+Quit:
+    /* Resume the VM */
+    EmulatorResume();
+}
+
+static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
+{
+    /* Get the Function Number and skip it */
+    BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
+    setIP(getIP() + 1);
+
+    switch (FuncNum)
+    {
+        /* Kill the VDM */
+        case 0x00:
+        {
+            /* Stop the VDM */
+            EmulatorTerminate();
+            return;
+        }
+
+        /*
+         * Get a new app to start
+         *
+         * Input
+         *     DS:DX : Data block.
+         *
+         * Output
+         *     CF    : 0: Success; 1: Failure.
+         */
+        case 0x01:
+        {
+            CmdStartProcess();
+            break;
+        }
 
-                /* Get the exit code */
-                GetExitCodeProcess(ProcessInformation.hProcess, &dwExitCode);
+        /*
+         * Check binary format
+         *
+         * Input
+         *     DS:DX : Program to check.
+         *
+         * Output
+         *     CF    : 0: Success; 1: Failure.
+         *     AX    : Error code.
+         */
+        case 0x07:
+        {
+            DWORD BinaryType;
+            LPSTR ProgramName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
 
-                /* Close handles */
-                CloseHandle(ProcessInformation.hThread);
-                CloseHandle(ProcessInformation.hProcess);
+            if (!GetBinaryTypeA(ProgramName, &BinaryType))
+            {
+                /* An error happened, bail out */
+                setCF(1);
+                setAX(LOWORD(GetLastError()));
+                break;
             }
-            else
+
+            // FIXME: We only support DOS binaries for now...
+            ASSERT(BinaryType == SCS_DOS_BINARY);
+            if (BinaryType != SCS_DOS_BINARY)
             {
-                DPRINT1("Failed when launched command '%s'\n");
-                dwExitCode = GetLastError();
+                /* An error happened, bail out */
+                setCF(1);
+                setAX(LOWORD(ERROR_BAD_EXE_FORMAT));
+                break;
             }
 
-            VidBiosAttachToConsole();
+            /* Return success: DOS application */
+            setCF(0);
+            break;
+        }
 
-            setAL((UCHAR)dwExitCode);
+        /*
+         * Start an external command
+         *
+         * Input
+         *     DS:SI : Command to start.
+         *     ES    : Environment block segment.
+         *     AL    : Current drive number.
+         *     AH    : 0: Directly start the command;
+         *             1: Use "cmd.exe /c" to start the command.
+         *
+         * Output
+         *     CF    : 0: Shell-out; 1: Continue.
+         *     AL    : Error/Exit code.
+         */
+        case 0x08:
+        {
+            CmdStartExternalCommand();
+            break;
+        }
 
+        /*
+         * Start the default 32-bit command interpreter (COMSPEC)
+         *
+         * Input
+         *     ES    : Environment block segment.
+         *     AL    : Current drive number.
+         *
+         * Output
+         *     CF    : 0: Shell-out; 1: Continue.
+         *     AL    : Error/Exit code.
+         */
+        case 0x0A:
+        {
+            CmdStartComSpec32();
+            break;
+        }
+
+        /*
+         * Set exit code
+         *
+         * Input
+         *     DX    : Exit code
+         *
+         * Output
+         *     CF    : 0: Shell-out; 1: Continue.
+         */
+        case 0x0B:
+        {
+            CmdSetExitCode();
+            break;
+        }
+
+        /*
+         * Get start information
+         *
+         * Output
+         *     AL    : 0 (resp. 1): Started from (resp. without) an existing console.
+         */
+        case 0x10:
+        {
+#ifndef STANDALONE
+            /*
+             * When a new instance of our (internal) COMMAND.COM is started,
+             * we check whether we need to run a 32-bit COMSPEC. This goes by
+             * checking whether we were started in a new console (no parent
+             * console process) or from an existing one.
+             *
+             * However COMMAND.COM can also be started in the case where a
+             * 32-bit process (started by a 16-bit parent) wants to start a new
+             * 16-bit process: to ensure DOS reentry we need to start a new
+             * instance of COMMAND.COM. On Windows the COMMAND.COM is started
+             * just before the 32-bit process (in fact, it is this COMMAND.COM
+             * which starts the 32-bit process via an undocumented command-line
+             * switch '/z', which syntax is:
+             *     COMMAND.COM /z\bAPPNAME.EXE
+             * notice the '\b' character inserted in-between. Then COMMAND.COM
+             * issues a BOP_CMD 08h with AH=00h to start the process).
+             *
+             * Instead, we do the reverse, i.e. we start the 32-bit process,
+             * and *only* if needed, i.e. if this process wants to start a
+             * new 16-bit process, we start our COMMAND.COM.
+             *
+             * The problem we then face is that our COMMAND.COM will possibly
+             * want to start a new COMSPEC, however we do not want this.
+             * The chosen solution is to flag this case -- done with the 'Reentry'
+             * boolean -- so that COMMAND.COM will not attempt to start COMSPEC
+             * but instead will directly try to start the 16-bit process.
+             */
+            // setAL(SessionId != 0);
+            setAL((SessionId != 0) && !Reentry);
+            /* Reset 'Reentry' */
+            Reentry = FALSE;
+#else
+            setAL(0);
+#endif
             break;
         }
 
@@ -192,85 +838,308 @@ static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
     }
 }
 
+#ifndef COMSPEC_FULLY_EXTERNAL
+/*
+ * Internal COMMAND.COM binary data in the CommandCom array.
+ */
+#include "command_com.h"
+#endif
+
+static
+DWORD DosStartComSpec(IN BOOLEAN Permanent,
+                      IN LPCSTR Environment OPTIONAL,
+                      IN DWORD ReturnAddress OPTIONAL,
+                      OUT PWORD ComSpecPsp OPTIONAL)
+{
+    /*
+     * BOOLEAN Permanent
+     *   TRUE to simulate the /P switch of command.com: starts AUTOEXEC.BAT/NT
+     *   and makes the interpreter permanent (cannot exit).
+     */
+
+    DWORD Result;
+
+    if (ComSpecPsp) *ComSpecPsp = 0;
+
+    Result =
+#ifndef COMSPEC_FULLY_EXTERNAL
+    DosLoadExecutableInternal(DOS_LOAD_AND_EXECUTE,
+                              CommandCom,
+                              sizeof(CommandCom),
+                              "COMMAND.COM",
+#else
+            DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
+#ifndef STANDALONE  // FIXME: Those values are hardcoded paths on my local test machines!!
+                              "C:\\CMDCMD.COM",
+#else
+                              "H:\\DOS_tests\\CMDCMD.COM",
+#endif // STANDALONE
+#endif // COMSPEC_FULLY_EXTERNAL
+                              NULL,
+                              Permanent ? "/P" : "",
+                              Environment ? Environment : "", // FIXME: Default environment!
+                              ReturnAddress);
+    if (Result != ERROR_SUCCESS) return Result;
+
+    /* TODO: Read AUTOEXEC.NT/BAT */
+
+    /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
+    if (ComSpecPsp) *ComSpecPsp = Sda->CurrentPsp;
+
+    return Result;
+}
+
+typedef struct _DOS_START_PROC32
+{
+    LPSTR ExecutablePath;
+    LPSTR CommandLine;
+    LPSTR Environment OPTIONAL;
 #ifndef STANDALONE
+    PCOMSPEC_INFO ComSpecInfo;
+    HANDLE hEvent;
+#endif
+} DOS_START_PROC32, *PDOS_START_PROC32;
+
 static DWORD
 WINAPI
 CommandThreadProc(LPVOID Parameter)
 {
-    BOOLEAN First = TRUE;
-    DWORD Result;
+    BOOL Success;
+    PROCESS_INFORMATION ProcessInfo;
+    STARTUPINFOA StartupInfo;
+    DWORD dwExitCode;
+    PDOS_START_PROC32 DosStartProc32 = (PDOS_START_PROC32)Parameter;
+#ifndef STANDALONE
+    VDM_COMMAND_INFO CommandInfo;
+    PCOMSPEC_INFO ComSpecInfo = DosStartProc32->ComSpecInfo;
+#endif
+
+    /* Set up the VDM, startup and process info structures */
+#ifndef STANDALONE
+    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
+#endif
+    RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
+    RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
+    StartupInfo.cb = sizeof(StartupInfo);
+
+    // FIXME: Build suitable 32-bit environment!!
+
+#ifndef STANDALONE
+    /*
+     * Wait for signaling a new VDM task and increment the VDM re-entry count so
+     * that we can handle 16-bit apps that may be possibly started by the 32-bit app.
+     */
+    CommandInfo.VDMState = VDM_INC_REENTER_COUNT;
+    DPRINT1("Calling GetNextVDMCommand reenter++\n");
+    Success = GetNextVDMCommand(&CommandInfo);
+    DPRINT1("GetNextVDMCommand reenter++ success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
+    ++ReentrancyCount;
+#endif
+
+    /* Start the process */
+    Success = CreateProcessA(NULL, // ProgramName,
+                             DosStartProc32->CommandLine,
+                             NULL,
+                             NULL,
+                             TRUE, // Inherit handles
+                             CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED,
+                             DosStartProc32->Environment,
+                             NULL, // lpCurrentDirectory, see "START" command in cmd.exe
+                             &StartupInfo,
+                             &ProcessInfo);
+
+#ifndef STANDALONE
+    /* Signal our caller the process was started */
+    SetEvent(DosStartProc32->hEvent);
+    // After this point, 'DosStartProc32' is not valid anymore.
+#endif
+
+    if (Success)
+    {
+        /* Resume the process */
+        ResumeThread(ProcessInfo.hThread);
+
+        /* Wait for the process to finish running and retrieve its exit code */
+        WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
+        GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
+
+        /* Close the handles */
+        CloseHandle(ProcessInfo.hThread);
+        CloseHandle(ProcessInfo.hProcess);
+    }
+    else
+    {
+        dwExitCode = GetLastError();
+    }
+
+#ifndef STANDALONE
+    ASSERT(ComSpecInfo);
+    ComSpecInfo->Terminated = TRUE;
+    ComSpecInfo->dwExitCode = dwExitCode;
+
+    /* Decrement the VDM re-entry count */
+    CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
+    DPRINT1("Calling GetNextVDMCommand reenter--\n");
+    Success = GetNextVDMCommand(&CommandInfo);
+    DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
+    --ReentrancyCount;
+
+    return 0;
+#else
+    return dwExitCode;
+#endif
+}
+
+DWORD DosStartProcess32(IN LPCSTR ExecutablePath,
+                        IN LPCSTR CommandLine,
+                        IN LPCSTR Environment OPTIONAL,
+                        IN DWORD ReturnAddress OPTIONAL,
+                        IN BOOLEAN StartComSpec)
+{
+    DWORD Result = ERROR_SUCCESS;
+    HANDLE CommandThread;
+    DOS_START_PROC32 DosStartProc32;
+#ifndef STANDALONE
+    BOOL Success;
     VDM_COMMAND_INFO CommandInfo;
-    CHAR CmdLine[MAX_PATH];
-    CHAR AppName[MAX_PATH];
-    CHAR PifFile[MAX_PATH];
-    CHAR Desktop[MAX_PATH];
-    CHAR Title[MAX_PATH];
-    ULONG EnvSize = 256;
-    PVOID Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
+#endif
 
-    UNREFERENCED_PARAMETER(Parameter);
-    ASSERT(Env != NULL);
+    DosStartProc32.ExecutablePath = (LPSTR)ExecutablePath;
+    DosStartProc32.CommandLine    = (LPSTR)CommandLine;
+    DosStartProc32.Environment    = (LPSTR)Environment;
 
-    do
+#ifndef STANDALONE
+    DosStartProc32.ComSpecInfo =
+        RtlAllocateHeap(RtlGetProcessHeap(),
+                        HEAP_ZERO_MEMORY,
+                        sizeof(*DosStartProc32.ComSpecInfo));
+    ASSERT(DosStartProc32.ComSpecInfo);
+
+    DosStartProc32.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+    ASSERT(DosStartProc32.hEvent);
+#endif
+
+    /* Pause the VM and detach from the console */
+    EmulatorPause();
+    DosProcessConsoleDetach();
+
+    /* Start the 32-bit process via another thread */
+    CommandThread = CreateThread(NULL, 0, &CommandThreadProc, &DosStartProc32, 0, NULL);
+    if (CommandThread == NULL)
     {
-        /* Clear the structure */
-        RtlZeroMemory(&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 = EnvSize;
-
-        if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
+        DisplayMessage(L"FATAL: Failed to create the command processing thread: %d", GetLastError());
+        Result = GetLastError();
+        goto Quit;
+    }
 
-Command:
-        if (!GetNextVDMCommand(&CommandInfo))
+#ifndef STANDALONE
+    /* Close the thread handle */
+    CloseHandle(CommandThread);
+
+    /* Wait for the process to be ready to start */
+    WaitForSingleObject(DosStartProc32.hEvent, INFINITE);
+
+    /* Wait for any potential new DOS app started by the 32-bit process */
+    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
+
+Retry:
+    CommandInfo.VDMState = VDM_FLAG_NESTED_TASK | VDM_FLAG_DONT_WAIT;
+    DPRINT1("Calling GetNextVDMCommand 32bit for possible new VDM task...\n");
+    Success = GetNextVDMCommand(&CommandInfo);
+    DPRINT1("GetNextVDMCommand 32bit awaited, success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
+
+    /*
+     * Check whether we were awaited because the 32-bit process was stopped,
+     * or because it started a new DOS application.
+     */
+    if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
+    {
+        DPRINT1("GetNextVDMCommand 32bit, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
+                CommandInfo.CmdLen, CommandInfo.AppLen, CommandInfo.PifLen);
+
+        /* Repeat the request */
+        Repeat = TRUE;
+
+        /*
+         * Set 'Reentry' to TRUE or FALSE depending on whether we are going
+         * to reenter with a new COMMAND.COM. See the comment for:
+         *     BOP_CMD 0x10 'Get start information'
+         * (dem.c!DosCmdInterpreterBop) for more details.
+         */
+        Reentry = StartComSpec;
+
+        /* If needed, start a new command interpreter to handle the possible incoming DOS commands */
+        if (StartComSpec)
         {
-            if (CommandInfo.EnvLen > EnvSize)
+            //
+            // DosStartProcess32 was only called by DosCreateProcess, called from INT 21h,
+            // so the caller stack is already prepared for running a new DOS program
+            // (Flags, CS and IP, and the extra interrupt number, are already pushed).
+            //
+            Result = DosStartComSpec(FALSE, Environment, ReturnAddress,
+                                     &DosStartProc32.ComSpecInfo->ComSpecPsp);
+            if (Result != ERROR_SUCCESS)
             {
-                /* Expand the environment size */
-                EnvSize = CommandInfo.EnvLen;
-                CommandInfo.Env = Env = RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
-
-                /* Repeat the request */
-                CommandInfo.VDMState |= VDM_FLAG_RETRY;
-                goto Command;
+                DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result);
+                goto Quit;
             }
-
-            break;
         }
+        else
+        {
+            /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
+            DosStartProc32.ComSpecInfo->ComSpecPsp = Sda->CurrentPsp;
+            Result = ERROR_SUCCESS;
+        }
+
+        /* Insert the new entry in the list; it will be freed when needed by COMMAND.COM */
+        InsertComSpecInfo(DosStartProc32.ComSpecInfo);
+    }
+    else
+    {
+        DPRINT1("GetNextVDMCommand 32bit, 32-bit app stopped\n");
 
-        /* Start the process from the command line */
-        DPRINT1("Starting '%s' ('%s')...\n", AppName, CmdLine);
-        Result = DosStartProcess(AppName, CmdLine, Env);
-        if (Result != ERROR_SUCCESS)
+        /* Check whether this was our 32-bit app which was killed */
+        if (!DosStartProc32.ComSpecInfo->Terminated)
         {
-            DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result);
-            // break;
-            continue;
+            DPRINT1("Not our 32-bit app, retrying...\n");
+            goto Retry;
         }
 
-        First = FALSE;
+        Result = DosStartProc32.ComSpecInfo->dwExitCode;
+
+        /* Delete the entry */
+        RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32.ComSpecInfo);
     }
-    while (AcceptCommands);
+#else
+    /* Wait for the thread to finish */
+    WaitForSingleObject(CommandThread, INFINITE);
+    GetExitCodeThread(CommandThread, &Result);
 
-    RtlFreeHeap(RtlGetProcessHeap(), 0, Env);
-    return 0;
-}
+    /* Close the thread handle */
+    CloseHandle(CommandThread);
+
+    DPRINT1("32-bit app stopped\n");
 #endif
 
-/* PUBLIC FUNCTIONS ***********************************************************/
+Quit:
+#ifndef STANDALONE
+    CloseHandle(DosStartProc32.hEvent);
+#endif
+
+    /* Attach to the console and resume the VM */
+    DosProcessConsoleAttach();
+    EmulatorResume();
+
+    return Result;
+}
+
+
+
+
+/******************************************************************************\
+|**              DOS Bootloader emulation, Startup and Shutdown              **|
+\******************************************************************************/
+
 
 //
 // This function (equivalent of the DOS bootsector) is called by the bootstrap
@@ -280,16 +1149,16 @@ Command:
 //
 
 /* 16-bit bootstrap code at 0000:7C00 */
-/* Of course, this is not in real bootsector format, because we don't care */
+/* Of course, this is not in real bootsector format, because we don't care about it for now */
 static BYTE Bootsector1[] =
 {
-    LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_LOAD_DOS,  // Call DOS Loading
+    LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_LOAD_DOS
 };
 /* This portion of code is run if we failed to load the DOS */
+// NOTE: This may also be done by the BIOS32.
 static BYTE Bootsector2[] =
 {
-    0xEA,                   // jmp far ptr
-    0x5B, 0xE0, 0x00, 0xF0, // F000:E05B /** HACK! What to do instead?? **/
+    LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
 };
 
 static VOID WINAPI DosInitialize(LPWORD Stack);
@@ -297,8 +1166,9 @@ static VOID WINAPI DosInitialize(LPWORD Stack);
 VOID DosBootsectorInitialize(VOID)
 {
     /* We write the bootsector at 0000:7C00 */
-    ULONG_PTR Address = (ULONG_PTR)SEG_OFF_TO_PTR(0x0000, 0x7C00);
-    CHAR DosKernelFileName[] = ""; // No DOS file name, therefore we'll load DOS32
+    ULONG_PTR StartAddress = (ULONG_PTR)SEG_OFF_TO_PTR(0x0000, 0x7C00);
+    ULONG_PTR Address = StartAddress;
+    CHAR DosKernelFileName[] = ""; // No DOS BIOS file name, therefore we will load DOS32
 
     DPRINT("DosBootsectorInitialize\n");
 
@@ -308,6 +1178,11 @@ VOID DosBootsectorInitialize(VOID)
     RtlCopyMemory((PVOID)Address, DosKernelFileName, sizeof(DosKernelFileName));
     Address += sizeof(DosKernelFileName);
     RtlCopyMemory((PVOID)Address, Bootsector2, sizeof(Bootsector2));
+    Address += sizeof(Bootsector2);
+
+    /* Initialize the callback context */
+    InitializeContext(&DosContext, 0x0000,
+                      (ULONG_PTR)MEM_ALIGN_UP(0x7C00 + Address - StartAddress, sizeof(WORD)));
 
     /* Register the DOS Loading BOP */
     RegisterBop(BOP_LOAD_DOS, DosInitialize);
@@ -315,43 +1190,47 @@ VOID DosBootsectorInitialize(VOID)
 
 
 //
-// This function is called by the DOS bootsector. We finish to load
-// the DOS, then we jump to 0070:0000.
+// This function is called by the DOS bootsector in case we load DOS32.
+// It sets up the DOS32 start code then jumps to 0070:0000.
 //
 
-/* 16-bit startup code at 0070:0000 */
+/* 16-bit startup code for DOS32 at 0070:0000 */
 static BYTE Startup[] =
 {
-    LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_START_DOS,  // Call DOS Start
+    LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_START_DOS,
+    LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
 };
 
 static VOID WINAPI DosStart(LPWORD Stack);
 
 static VOID WINAPI DosInitialize(LPWORD Stack)
 {
-    BOOLEAN Success = FALSE;
-
-    /* Get the DOS kernel file name (NULL-terminated) */
+    /* Get the DOS BIOS file name (NULL-terminated) */
     // FIXME: Isn't it possible to use some DS:SI instead??
-    LPCSTR DosKernelFileName = (LPCSTR)SEG_OFF_TO_PTR(getCS(), getIP());
-    setIP(getIP() + strlen(DosKernelFileName) + 1); // Skip it
+    LPCSTR DosBiosFileName = (LPCSTR)SEG_OFF_TO_PTR(getCS(), getIP());
+    setIP(getIP() + strlen(DosBiosFileName) + 1); // Skip it
 
-    DPRINT("DosInitialize('%s')\n", DosKernelFileName);
+    DPRINT("DosInitialize('%s')\n", DosBiosFileName);
+
+    /*
+     * We succeeded, deregister the DOS Loading BOP
+     * so that no app will be able to call us back.
+     */
+    RegisterBop(BOP_LOAD_DOS, NULL);
 
     /* Register the DOS BOPs */
     RegisterBop(BOP_DOS, DosSystemBop        );
     RegisterBop(BOP_CMD, DosCmdInterpreterBop);
 
-    if (DosKernelFileName && DosKernelFileName[0] != '\0')
+    if (DosBiosFileName[0] != '\0')
     {
+        BOOLEAN Success = FALSE;
         HANDLE  hDosBios;
         ULONG   ulDosBiosSize = 0;
 
         /* Open the DOS BIOS file */
-        hDosBios = FileOpen(DosKernelFileName, &ulDosBiosSize);
-
-        /* If we failed, bail out */
-        if (hDosBios == NULL) goto QuitCustom;
+        hDosBios = FileOpen(DosBiosFileName, &ulDosBiosSize);
+        if (hDosBios == NULL) goto Quit;
 
         /* Attempt to load the DOS BIOS into memory */
         Success = FileLoadByHandle(hDosBios,
@@ -359,7 +1238,8 @@ static VOID WINAPI DosInitialize(LPWORD Stack)
                                    ulDosBiosSize,
                                    &ulDosBiosSize);
 
-        DPRINT1("DOS BIOS loading %s at %04X:%04X, size 0x%X ; GetLastError() = %u\n",
+        DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
+                DosBiosFileName,
                 (Success ? "succeeded" : "failed"),
                 0x0070, 0x0000,
                 ulDosBiosSize,
@@ -368,56 +1248,35 @@ static VOID WINAPI DosInitialize(LPWORD Stack)
         /* Close the DOS BIOS file */
         FileClose(hDosBios);
 
-        if (!Success) goto QuitCustom;
-
-        /* Position execution pointers and return */
-        setCS(0x0070);
-        setIP(0x0000);
-
-        /* Return control */
-QuitCustom:
+Quit:
         if (!Success)
-            DisplayMessage(L"Custom DOS '%S' loading failed, what to do??", DosKernelFileName);
+        {
+            BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
+                               DosBiosFileName, GetLastError());
+            return;
+        }
     }
     else
     {
-        Success = DosBIOSInitialize();
-        // Success &= DosKRNLInitialize();
-
-        if (!Success) goto Quit32;
-
-        /* Write the "bootsector" */
+        /* Load the 16-bit startup code for DOS32 and register its Starting BOP */
         RtlCopyMemory(SEG_OFF_TO_PTR(0x0070, 0x0000), Startup, sizeof(Startup));
 
-        /* Register the DOS Starting BOP */
+        // This is the equivalent of BOP_LOAD_DOS, function 0x11 "Load the DOS kernel"
+        // for the Windows NT DOS.
         RegisterBop(BOP_START_DOS, DosStart);
-
-        /* Position execution pointers and return */
-        setCS(0x0070);
-        setIP(0x0000);
-
-        /* Return control */
-Quit32:
-        if (!Success)
-            DisplayMessage(L"DOS32 loading failed, what to do??");
     }
 
-    if (Success)
-    {
-        /*
-         * We succeeded, deregister the DOS Loading BOP
-         * so that no app will be able to call us back.
-         */
-        RegisterBop(BOP_LOAD_DOS, NULL);
-    }
+    /* Position execution pointers for DOS startup and return */
+    setCS(0x0070);
+    setIP(0x0000);
 }
 
 static VOID WINAPI DosStart(LPWORD Stack)
 {
-#ifdef STANDALONE
+    BOOLEAN Success;
     DWORD Result;
-    CHAR ApplicationName[MAX_PATH];
-    CHAR CommandLine[DOS_CMDLINE_LENGTH];
+#ifndef STANDALONE
+    INT i;
 #endif
 
     DPRINT("DosStart\n");
@@ -428,60 +1287,171 @@ static VOID WINAPI DosStart(LPWORD Stack)
      */
     RegisterBop(BOP_START_DOS, NULL);
 
+    /* Initialize the callback context */
+    InitializeContext(&DosContext, BIOS_CODE_SEGMENT, 0x0010);
+
+    Success  = DosBIOSInitialize();
+//  Success &= DosKRNLInitialize();
+    if (!Success)
+    {
+        BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
+        EmulatorTerminate();
+        return;
+    }
+
     /* Load the mouse driver */
-    DosMouseInitialize();
+    // DosMouseInitialize();
 
 #ifndef STANDALONE
 
-    /* Create the GetNextVDMCommand thread */
-    CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
-    if (CommandThread == NULL)
+    /* Parse the command line arguments */
+    for (i = 1; i < NtVdmArgc; i++)
     {
-        wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
-        goto Quit;
+        if (wcsncmp(NtVdmArgv[i], L"-i", 2) == 0)
+        {
+            /* This is the session ID (hex format) */
+            SessionId = wcstoul(NtVdmArgv[i] + 2, NULL, 16);
+        }
     }
 
-    /* Wait for the command thread to exit */
-    WaitForSingleObject(CommandThread, INFINITE);
+    /* Initialize Win32-VDM environment */
+    Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
+    if (Env == NULL)
+    {
+        DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
+        EmulatorTerminate();
+        return;
+    }
 
-    /* Close the thread handle */
-    CloseHandle(CommandThread);
+    /* Clear the structure */
+    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
+
+    /* Get the initial information */
+    CommandInfo.TaskId = SessionId;
+    CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS;
+    GetNextVDMCommand(&CommandInfo);
 
 #else
 
+    /* Retrieve the command to start */
     if (NtVdmArgc >= 2)
     {
-        WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, ApplicationName, sizeof(ApplicationName), NULL, NULL);
+        WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, AppName, sizeof(AppName), NULL, NULL);
 
         if (NtVdmArgc >= 3)
-            WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
+            WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CmdLine, sizeof(CmdLine), NULL, NULL);
         else
-            strcpy(CommandLine, "");
+            strcpy(CmdLine, "");
     }
     else
     {
-        DisplayMessage(L"Invalid DOS command line\n");
-        goto Quit;
+        DosDisplayMessage("Invalid DOS command line\n");
+        EmulatorTerminate();
+        return;
     }
 
-    /* Start the process from the command line */
-    DPRINT1("Starting '%s' ('%s')...\n", ApplicationName, CommandLine);
-    Result = DosStartProcess(ApplicationName,
-                             CommandLine,
-                             SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0));
+#endif
+
+    /*
+     * At this point, CS:IP points to the DOS BIOS exit code. If the
+     * root command interpreter fails to start (or if it exits), DOS
+     * exits and the VDM terminates.
+     */
+
+    /* Start the root command interpreter */
+    // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
+
+    /*
+     * Prepare the stack for DosStartComSpec:
+     * push Flags, CS and IP, and an extra WORD.
+     */
+    setSP(getSP() - sizeof(WORD));
+    *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD)getEFLAGS();
+    setSP(getSP() - sizeof(WORD));
+    *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS();
+    setSP(getSP() - sizeof(WORD));
+    *((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP();
+    setSP(getSP() - sizeof(WORD));
+
+    Result = DosStartComSpec(TRUE, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0),
+                             MAKELONG(getIP(), getCS()),
+#ifndef STANDALONE
+                             &RootCmd.ComSpecPsp
+#else
+                             NULL
+#endif
+                             );
     if (Result != ERROR_SUCCESS)
     {
-        DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result);
-        goto Quit;
+        /* Unprepare the stack for DosStartComSpec */
+        setSP(getSP() + 4*sizeof(WORD));
+
+        DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result);
+        EmulatorTerminate();
+        return;
     }
 
+#ifndef STANDALONE
+    RootCmd.Terminated = FALSE;
+    InsertComSpecInfo(&RootCmd);
 #endif
 
-Quit:
-    /* Stop the VDM */
-    EmulatorTerminate();
+    /**/
+    /* Attach to the console and resume the VM */
+    DosProcessConsoleAttach();
+    EmulatorResume();
+    /**/
+
+    return;
 }
 
+BOOLEAN DosShutdown(BOOLEAN Immediate)
+{
+    /*
+     * Immediate = TRUE:  Immediate shutdown;
+     *             FALSE: Delayed shutdown (notification).
+     */
+
+#ifndef STANDALONE
+    if (Immediate)
+    {
+        ExitVDM(FALSE, 0);
+        return TRUE;
+    }
+    else
+    {
+// HACK!
+extern HANDLE VdmTaskEvent; // see emulator.c
+
+        /*
+         * Signal the root COMMAND.COM that it should terminate
+         * when it checks for a new command.
+         */
+        RootCmd.Terminated = TRUE;
+
+        /* If the list is already empty, or just contains only one element, bail out */
+        // FIXME: Question: if the list has only one element, is it ALWAYS RootCmd ??
+        // FIXME: The following is hackish.
+        if ((IsListEmpty(&ComSpecInfoList) ||
+            (ComSpecInfoList.Flink == &RootCmd.Entry   &&
+             ComSpecInfoList.Blink == &RootCmd.Entry)) &&
+            ReentrancyCount == 0 &&
+            WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
+        {
+            /* Nothing runs, so exit immediately */
+            ExitVDM(FALSE, 0);
+            return TRUE;
+        }
+
+        return FALSE;
+    }
+#else
+    UNREFERENCED_PARAMETER(Immediate);
+    return TRUE;
+#endif
+}
+
+
 /* PUBLIC EXPORTED APIS *******************************************************/
 
 // demLFNCleanup
@@ -521,36 +1491,48 @@ demFileFindFirst(OUT PVOID  lpFindFileData,
 {
     BOOLEAN Success = TRUE;
     WIN32_FIND_DATAA FindData;
+    HANDLE SearchHandle;
     PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
 
-    /* Fill the block */
-    FindFileBlock->DriveLetter  = CurrentDrive + 'A';
-    FindFileBlock->AttribMask   = AttribMask;
-    FindFileBlock->SearchHandle = FindFirstFileA(FileName, &FindData);
-    if (FindFileBlock->SearchHandle == INVALID_HANDLE_VALUE) return GetLastError();
+    /* Start a search */
+    SearchHandle = FindFirstFileA(FileName, &FindData);
+    if (SearchHandle == INVALID_HANDLE_VALUE) return GetLastError();
 
     do
     {
-        /* Check the attributes */
+        /* Check the attributes and retry as long as we haven't found a matching file */
         if (!((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
                                             FILE_ATTRIBUTE_SYSTEM |
                                             FILE_ATTRIBUTE_DIRECTORY))
-            & ~AttribMask))
+             & ~AttribMask))
         {
             break;
         }
     }
-    while ((Success = FindNextFileA(FindFileBlock->SearchHandle, &FindData)));
+    while ((Success = FindNextFileA(SearchHandle, &FindData)));
 
-    if (!Success) return GetLastError();
+    /* If we failed at some point, close the search and return an error */
+    if (!Success)
+    {
+        FindClose(SearchHandle);
+        return GetLastError();
+    }
 
-    FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
+    /* Fill the block */
+    FindFileBlock->DriveLetter  = DosData->Sda.CurrentDrive + 'A';
+    FindFileBlock->AttribMask   = AttribMask;
+    FindFileBlock->SearchHandle = SearchHandle;
+    FindFileBlock->Attributes   = LOBYTE(FindData.dwFileAttributes);
     FileTimeToDosDateTime(&FindData.ftLastWriteTime,
                           &FindFileBlock->FileDate,
                           &FindFileBlock->FileTime);
     FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
                                                      : FindData.nFileSizeLow;
-    strcpy(FindFileBlock->FileName, FindData.cAlternateFileName);
+    /* Build a short path name */
+    if (*FindData.cAlternateFileName)
+        strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
+    else
+        GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
 
     return ERROR_SUCCESS;
 }
@@ -564,22 +1546,32 @@ demFileFindNext(OUT PVOID lpFindFileData)
 
     do
     {
+        /* Continue searching as long as we haven't found a matching file */
+
+        /* If we failed at some point, close the search and return an error */
         if (!FindNextFileA(FindFileBlock->SearchHandle, &FindData))
+        {
+            FindClose(FindFileBlock->SearchHandle);
             return GetLastError();
+        }
+    }
+    while ((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
+                                         FILE_ATTRIBUTE_SYSTEM |
+                                         FILE_ATTRIBUTE_DIRECTORY))
+           & ~FindFileBlock->AttribMask);
 
-        /* Update the block */
-        FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
-        FileTimeToDosDateTime(&FindData.ftLastWriteTime,
-                              &FindFileBlock->FileDate,
-                              &FindFileBlock->FileTime);
-        FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
-                                                         : FindData.nFileSizeLow;
-        strcpy(FindFileBlock->FileName, FindData.cAlternateFileName);
-    }
-    while((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
-                                        FILE_ATTRIBUTE_SYSTEM |
-                                        FILE_ATTRIBUTE_DIRECTORY))
-          & ~FindFileBlock->AttribMask);
+    /* Update the block */
+    FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
+    FileTimeToDosDateTime(&FindData.ftLastWriteTime,
+                          &FindFileBlock->FileDate,
+                          &FindFileBlock->FileTime);
+    FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
+                                                     : FindData.nFileSizeLow;
+    /* Build a short path name */
+    if (*FindData.cAlternateFileName)
+        strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
+    else
+        GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
 
     return ERROR_SUCCESS;
 }