[NTVDM]
authorHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Sat, 12 Sep 2015 20:09:25 +0000 (20:09 +0000)
committerHermès Bélusca-Maïto <hermes.belusca-maito@reactos.org>
Sat, 12 Sep 2015 20:09:25 +0000 (20:09 +0000)
- Add support for DOS VDM reentry, i.e. the fact that a DOS app can start a 32-bit app, which in turn can start another DOS app, ad infinitum...
CORE-9711 CORE-9773 #resolve
- Add a small COMMAND.COM which is, for now, completely included in NTVDM. This COMMAND.COM is needed in order to support reentrancy. The fact that I chose to put it inside NTVDM is that any user can use instead his/her own COMMAND.COM, while retaining the possibility to perform VDM reentrancy (on Windows, if you remove the COMMAND.COM in windows\system32 and replace it with your own, you will break VDM reentrancy on windows' ntvdm).
CORE-5221 #resolve #comment I choose for the moment an internal COMMAND.COM, but you can recompile NTVDM to use it externally instead.

Global remarks:
- Quite a few DPRINTs were added for diagnostic purposes (since DOS reentrancy is a new feature), to be sure everything behaves as expected when being used with a large panel of applications. They will be removed when everything is OK.
- Support for current directories and 16/32-bit environment translation (in ntvdm + basevdm-side) remain to be implemented.

Other changes:
- Improve a bit the VDM shutdown code by gathering it at one place (there's still room for other improvements).
- Add suppport for properly pausing/resuming NTVDM.
- Bufferize some console events before dispatching.

Have fun ;^)

svn path=/trunk/; revision=69204

16 files changed:
reactos/subsystems/mvdm/CMakeLists.txt
reactos/subsystems/mvdm/dos/CMakeLists.txt [new file with mode: 0644]
reactos/subsystems/mvdm/dos/asmxtras.inc [new file with mode: 0644]
reactos/subsystems/mvdm/dos/command.S [new file with mode: 0644]
reactos/subsystems/mvdm/ntvdm/CMakeLists.txt
reactos/subsystems/mvdm/ntvdm/dos/dem.c
reactos/subsystems/mvdm/ntvdm/dos/dem.h
reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/bios.c
reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.c
reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.h
reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.c
reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.h
reactos/subsystems/mvdm/ntvdm/emulator.c
reactos/subsystems/mvdm/ntvdm/emulator.h
reactos/subsystems/mvdm/ntvdm/ntvdm.c
reactos/subsystems/mvdm/ntvdm/ntvdm.h

index 662fd3d..853db34 100644 (file)
@@ -3,7 +3,7 @@
 include(asm16.cmake)
 
 add_subdirectory(config)
-#add_subdirectory(dos)
+add_subdirectory(dos)
 add_subdirectory(ntvdm)
 if(ARCH STREQUAL "i386")
 add_subdirectory(samples)
diff --git a/reactos/subsystems/mvdm/dos/CMakeLists.txt b/reactos/subsystems/mvdm/dos/CMakeLists.txt
new file mode 100644 (file)
index 0000000..bd81c47
--- /dev/null
@@ -0,0 +1,3 @@
+
+include_directories(${REACTOS_SOURCE_DIR}/subsystems/mvdm/dos)
+add_asm16_bin(command ${CMAKE_CURRENT_BINARY_DIR}/command.com 0x0100 command.S)
diff --git a/reactos/subsystems/mvdm/dos/asmxtras.inc b/reactos/subsystems/mvdm/dos/asmxtras.inc
new file mode 100644 (file)
index 0000000..731c108
--- /dev/null
@@ -0,0 +1,82 @@
+/*
+ * COPYRIGHT:       See COPYING in the top level directory
+ * PROJECT:         ReactOS Kernel
+ * FILE:            asmxtras.inc
+ * PURPOSE:         Extended ASM macros for GAS and MASM/ML64
+ * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
+ *
+ * NOTE: This file is an extension to our well-known asm.inc that defines
+ *       a set of macros allowing us to assemble specially-crafted ASM files
+ *       with both GAS and MASM/ML.
+ *
+ *       The additions introduced here are:
+ *       - a 'long' define for MASM/ML that aliases to DWORD, and a 'dword'
+ *         and 'DWORD' defines for GAS that alias to long.
+ *       - an OFF(...) macro that is used for initializing global symbols with
+ *         the offset value of another symbol.
+ *       - a set of macros for defining and using structures.
+ */
+
+#ifndef __ASMXTRAS_INC__
+#define __ASMXTRAS_INC__
+
+/* 'long' / 'dword'|'DWORD' macros for MASM/ML and GAS */
+#ifdef _USE_ML
+    #define long dword
+#else
+    #define dword long
+    #define DWORD long
+#endif
+
+/* OFFset macro */
+#ifdef _USE_ML
+    #define OFF(x) offset x
+#else
+    #define OFF(x) x
+#endif
+
+/*
+ * Set of macros for defining and using structures:
+ * - STRUCT(name, ...) defines a structure of name 'name'.
+ * - FIELD_DECL(field, type, value) adds a new structure member 'field'
+ *   of type 'type', with the default value 'value'.
+ * - ENDS(name) terminates the definition of the structure 'name'.
+ * - A symbol 'name' of type 'struct' is declared with VAR_STRUCT(name, struct).
+ * - Referencing a member 'field' of a symbol 'name' is done with FIELD(name, field).
+ */
+#ifdef _USE_ML
+
+#define STRUCT(name, ...) \
+    name STRUCT __VA_ARGS__
+
+#define FIELD_DECL(field, type, value) \
+    field   type   value
+
+#define ENDS(name) \
+    name ENDS
+
+#define VAR_STRUCT(name, struct) \
+    name struct <>
+
+#define FIELD(name, field) \
+    name##.##field
+
+#else
+
+#define STRUCT(name, ...) \
+    MACRO(name, VarName)
+
+#define FIELD_DECL(field, type, value) \
+    VarName\()_\()field:   .type   value
+
+#define ENDS(...) ENDM
+
+#define VAR_STRUCT(name, struct) \
+    struct name
+
+#define FIELD(name, field) \
+    name##_##field
+
+#endif
+
+#endif /* __ASMXTRAS_INC__ */
diff --git a/reactos/subsystems/mvdm/dos/command.S b/reactos/subsystems/mvdm/dos/command.S
new file mode 100644 (file)
index 0000000..deeee84
--- /dev/null
@@ -0,0 +1,335 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            command.S
+ * PURPOSE:         DOS32 command.com for NTVDM
+ * PROGRAMMERS:     Hermes Belusca-Maito (hermes.belusca@sfr.fr)
+ */
+
+//
+// See http://www.ganino.com/games/scripts/dos-6.0%20source%20code%3F/dev/smartdrv/umbload.asm
+// and https://books.google.fr/books?id=rtmJgtfaxz8C&pg=PA256&lpg=PA256&dq=int+21+4b+program+exec+block&source=bl&ots=OfAF7qFwfl&sig=PrW73CE1dzFm3rwrTsYZkC77U4Y&hl=en&sa=X&redir_esc=y#v=onepage&q=int%2021%204b%20program%20exec%20block&f=false
+//
+
+/* INCLUDES *******************************************************************/
+
+#include <asm.inc>
+#include "asmxtras.inc"
+#include <isvbop.inc>
+
+/* DEFINES ********************************************************************/
+
+#define MAX_PATH            260
+#define DOS_CMDLINE_LENGTH  127
+
+#define DOS_VERSION     HEX(0005)   // MAKEWORD(5, 00)
+#define NTDOS_VERSION   HEX(3205)   // MAKEWORD(5, 50)
+
+#define SYSTEM_PSP      HEX(08)
+
+#define BOP                 .byte HEX(C4), HEX(C4),
+// #define BOP_START_DOS       HEX(2C)
+#define BOP_CMD             HEX(54)
+
+/**** PSP MEMBERS ****/
+#define PSP_VAR(x)  es:[x]
+#define PSP         HEX(0000)
+#define ParentPsp   HEX(0016)   // word
+#define EnvBlock    HEX(002C)   // word
+#define CmdLineLen  HEX(0080)   // byte
+#define CmdLineStr  HEX(0081)   // byte[DOS_CMDLINE_LENGTH]
+
+/**** DATA stored inside the stack ****/
+#define CmdLine BaseStack   // Command line for the program to be started
+#define PgmName [CmdLine + (1 + DOS_CMDLINE_LENGTH)]
+
+
+// WARNING! Using the VAL(x) macro doesn't work with GCC/GAS (because it inserts
+// a spurious space in front of the parameter when the macro is expanded).
+// So that: 'VAL(foo)' is expanded to: '\ foo', instead of '\foo' as one would expect.
+
+/* NEXT_CMD structure */
+STRUCT(NEXT_CMD, 2)
+    FIELD_DECL(EnvBlockSeg, word, 0)
+    FIELD_DECL(EnvBlockLen, word, 0)
+    FIELD_DECL(CurDrive   , word, 0)
+    FIELD_DECL(NumDrives  , word, 1)
+    FIELD_DECL(CmdLineSeg , word, 0)
+    FIELD_DECL(CmdLineOff , word, 0)
+    FIELD_DECL(Unknown0   , word, 2)
+    FIELD_DECL(ExitCode   , word, 3)
+    FIELD_DECL(Unknown1   , word, 4)
+    FIELD_DECL(Unknown2   , long, 5)
+    FIELD_DECL(CodePage   , word, 6)
+    FIELD_DECL(Unknown3   , word, 7)
+    FIELD_DECL(Unknown4   , word, 8)
+    FIELD_DECL(AppNameSeg , word, 0)
+    FIELD_DECL(AppNameOff , word, 0)
+    FIELD_DECL(AppNameLen , word, 0)
+    FIELD_DECL(Flags      , word, 0)
+ENDS(NEXT_CMD)
+
+/* DOS_EXEC_PARAM_BLOCK structure */
+STRUCT(DOS_EXEC_PARAM_BLOCK, 2)
+    FIELD_DECL(EnvSeg, word, 0)    // Use parent's environment (ours)
+    FIELD_DECL(CmdLineOff, word, OFF(CmdLine))
+    FIELD_DECL(CmdLineSeg, word, 0)
+    FIELD_DECL(Fcb1Off, word, OFF(Fcb1))
+    FIELD_DECL(Fcb1Seg, word, 0)
+    FIELD_DECL(Fcb2Off, word, OFF(Fcb2))
+    FIELD_DECL(Fcb2Seg, word, 0)
+ENDS(DOS_EXEC_PARAM_BLOCK)
+
+
+
+/* RESIDENT CODE AND DATA *****************************************************/
+
+.code16
+// .org HEX(0100)
+ASSUME CS:.text, DS:.text, ES:.text
+
+
+/* CODE *******************************/
+
+EntryPoint:
+    jmp Main
+.align 2
+
+ResidentMain:
+    /*
+     * Relocate our stack.
+     * Our local stack is big enough for holding the command line and the path
+     * to the executable to start, plus a full DOS_REGISTER_STATE and for one pusha 16-bit.
+     *
+     * FIXME: enlarge it for pushing the needed Load&Exec data.
+     */
+    cli
+    mov ax, ds
+    mov ss, ax
+    mov bp, offset BaseStack
+    lea sp, [bp + (1 + DOS_CMDLINE_LENGTH) + MAX_PATH + 255]
+    sti
+
+    /* Resize ourselves */
+    mov bx, sp                  // Get size in bytes...
+//  sub bx, offset PSP_VAR(PSP)
+    add bx, HEX(0F)             // (for rounding to the next paragraph)
+    shr bx, 4                   // ... then the number of paragraphs
+    mov ah, HEX(4A)
+    int HEX(21)
+
+    /* Check whether we need to start the 32-bit command interpreter */
+    BOP BOP_CMD, HEX(10)        // Check whether we were started from a new console (SessionId != 0)
+    test al, al                 // and we are not reentering (32-bit process starting a 16-bit process).
+    jz Run
+    cmp word ptr OldParentPsp, SYSTEM_PSP   // Check whether our parent is SYSTEM
+    je Run
+/********************************/
+    mov dx, offset Msg1
+    mov ah, HEX(09)
+    int HEX(21)
+/********************************/
+    BOP BOP_CMD, HEX(0A)        // Start 32-bit COMSPEC
+    jnc Quit
+
+    /* Loop for new commands to run */
+Run:
+    /* Initialize the NextCmd structure */
+    mov word ptr FIELD(NextCmd, EnvBlockSeg), ds
+    mov word ptr FIELD(NextCmd, EnvBlockLen), 0 // FIXME
+    mov word ptr FIELD(NextCmd, CmdLineSeg), ds
+    mov word ptr FIELD(NextCmd, CmdLineOff), offset CmdLine
+    mov word ptr FIELD(NextCmd, AppNameSeg), ds
+    mov word ptr FIELD(NextCmd, AppNameOff), offset PgmName
+
+    /* Wait for the next command */
+/********************************/
+    mov dx, offset Msg2
+    mov ah, HEX(09)
+    int HEX(21)
+/********************************/
+    // FIXME: Initialize memory with structure for holding CmdLine etc...
+//  mov ds, seg NextCmd
+    mov dx, offset NextCmd
+    BOP BOP_CMD, HEX(01)
+    /* Quit if we shell-out */
+    jc Quit
+
+    /* Initialize the DosLoadExec structure */
+//  mov word ptr FIELD(DosLoadExec, EnvSeg), 0
+    mov word ptr FIELD(DosLoadExec, CmdLineSeg), ds
+    mov word ptr FIELD(DosLoadExec, Fcb1Seg), ds
+    mov word ptr FIELD(DosLoadExec, Fcb2Seg), ds
+
+    /* Run the command */
+    mov ds, word ptr FIELD(NextCmd, AppNameSeg)
+    mov dx, word ptr FIELD(NextCmd, AppNameOff)
+//  mov es, seg DosLoadExec
+    mov bx, offset DosLoadExec
+    pusha // Save the registers in case stuff go wrong
+    // FIXME: Save also SS !!
+    mov ax, HEX(4B00)
+    int HEX(21)
+    popa  // Restore the registers
+    // FIXME: Restore also SS !!
+
+    /* Retrieve and set its exit code. Also detect whether
+     * we need to continue or whether we need to quit. */
+    // xor ax, ax
+    // mov ah, HEX(4D)
+    mov ax, HEX(4D00)
+    int HEX(21)
+    /* Send exit code back to NTVDM */
+    mov dx, ax
+    BOP BOP_CMD, HEX(0B)
+
+    /* If we don't shell-out, go and get a new app! */
+    jc Run
+
+    mov al, HEX(00) // ERROR_SUCCESS
+
+Quit:
+    mov bl, al // Save AL in BL
+/********************************/
+    cmp al, HEX(0A)
+    jne XXXX
+    mov dx, offset Msg3
+    mov ah, HEX(09)
+    int HEX(21)
+XXXX:
+/********************************/
+    /* Say bye-bye */
+//  mov ds, seg QuitMsg
+    mov dx, offset QuitMsg
+    mov ah, HEX(09)
+    int HEX(21)
+
+    /* Restore our old parent PSP */
+    mov ax, word ptr OldParentPsp
+    mov PSP_VAR(ParentPsp), ax
+
+    mov al, bl // Restore AL from BL
+
+Exit:
+    /* Return to caller (with possible error code in AL) */
+    mov ah, HEX(4C)
+    int HEX(21)
+    int 3
+
+    /* Does not return */
+
+/* DATA *******************************/
+
+QuitMsg:
+    .ascii "Bye bye!", CR, LF, "$"
+
+/********************************/
+Msg1: .ascii "Starting COMSPEC...", CR, LF, "$"
+Msg2: .ascii "Waiting for new command...", CR, LF, "$"
+Msg3: .ascii "Bad environment!", CR, LF, "$"
+/********************************/
+
+OldParentPsp:   .word 0
+CurrentPsp:     .word 0
+
+// BOP_CMD, HEX(01) "Get a new app to start" structure
+VAR_STRUCT(NextCmd, NEXT_CMD)
+
+// DOS INT 21h, AH=4Bh "Load and Execute" structure
+VAR_STRUCT(DosLoadExec, DOS_EXEC_PARAM_BLOCK)
+
+// Blank FCB blocks needed for DOS INT 21h, AH=4Bh
+Fcb1:
+    .byte 0
+    .space 11, ' '
+    .space 25, 0
+Fcb2:
+    .byte 0
+    .space 11, ' '
+    .space 25, 0
+
+// The stack resides at the end of the resident code+data
+// and it overwrites the transient part.
+BaseStack:
+
+
+/* TRANSIENT CODE AND DATA ****************************************************/
+
+/* DATA *******************************/
+
+WelcomeMsg:
+    .ascii "ReactOS DOS32 Command", CR, LF, \
+           "Copyright (C) ReactOS Team 2015" , CR, LF, "$"
+VerErrMsg:
+    .ascii "Incorrect DOS version", CR, LF, "$"
+
+/* CODE *******************************/
+
+.align 2
+
+Main:
+    /* Setup segment registers */
+    mov ax, cs  // cs contains the PSP segment on entry
+    mov ds, ax
+    mov es, ax
+//  mov fs, ax
+//  mov gs, ax
+    /* Stack is set to cs:FFFE down to cs:09xx by the DOS.
+     * We will need to relocate it before we resize ourselves. */
+
+    /*
+     * Verify DOS version:
+     * - Check whether we are on DOS 5+ (subject to SETVER);
+     * - If so, check our real DOS version and see
+     *   whether we are running on NTVDM (version 5.50).
+     */
+    mov ax, HEX(3000)
+    int HEX(21)
+    cmp ax, DOS_VERSION     // AH:AL contains minor:major version number
+    jne VerErr
+
+    mov ax, HEX(3306)
+    int HEX(21)
+    cmp bx, NTDOS_VERSION   // BH:BL contains minor:major version number
+    je Continue
+
+VerErr:
+    /* Display wrong version error message and exit */
+//  mov ds, seg VerErrMsg
+    mov dx, offset VerErrMsg
+    mov ah, HEX(09)
+    int HEX(21)
+    jmp Exit
+
+Continue:
+    /* Save our PSP */
+    mov word ptr CurrentPsp, cs
+/*
+ * The DOS way:
+ *
+ * mov ah, HEX(51) // DOS 2+ internal, or HEX(62) since DOS 3+
+ * int HEX(21)
+ * mov word ptr CurrentPsp, bx
+ */
+
+    /* Save our old parent PSP */
+    mov ax, PSP_VAR(ParentPsp)
+    mov word ptr OldParentPsp, ax
+
+    /* Give us shell privileges: set our PSP as our new parent */
+    mov ax, word ptr CurrentPsp
+    mov PSP_VAR(ParentPsp), ax
+
+    /* Say hello */
+//  mov ds, seg WelcomeMsg
+    mov dx, offset WelcomeMsg
+    mov ah, HEX(09)
+    int HEX(21)
+
+    /* Jump to resident code */
+    jmp ResidentMain
+
+.endcode16
+END
+
+/* EOF */
index 5765e76..baa0327 100644 (file)
@@ -1,6 +1,15 @@
 
 PROJECT(NTVDM)
 
+#####################################
+# Generate the integrated COMMAND.COM
+#
+add_custom_command(
+    OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/command_com.c ${CMAKE_CURRENT_BINARY_DIR}/command_com.h
+    COMMAND native-bin2c ${CMAKE_CURRENT_BINARY_DIR}/../dos/command.com ${CMAKE_CURRENT_BINARY_DIR}/command_com.c ${CMAKE_CURRENT_BINARY_DIR}/command_com.h BIN CommandCom
+    DEPENDS native-bin2c command)
+#####################################
+
 include_directories(${REACTOS_SOURCE_DIR}/include/reactos/libs/fast486)
 
 spec2def(ntvdm.exe ntvdm.spec ADD_IMPORTLIB)
@@ -43,6 +52,7 @@ list(APPEND SOURCE
     dos/dos32krnl/process.c
     dos/dem.c
     dos/mouse32.c
+    ${CMAKE_CURRENT_BINARY_DIR}/command_com.c
     clock.c
     emulator.c
     int32.c
index c0c00e1..1a487d6 100644 (file)
 
 #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 PDOS_DATA DosData;
+/* 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))
     {
-        /* Load the DOS kernel */
-        case 0x11:
+        Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR));
+        if (Buffer == NULL)
         {
-            BOOLEAN Success = FALSE;
-            LPCSTR  DosKernelFileName = "ntdos.sys";
-            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(DosKernelFileName, &ulDosKernelSize);
-            if (hDosKernel == NULL) goto Quit;
+    /* Display the message */
+    DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer);
 
-            /*
-             * 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);
+    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
+}
+
+VOID
+DemDisplayMessage(IN CHAR_PRINT CharPrint,
+                  IN LPCSTR Format, ...)
+{
+    va_list Parameters;
+
+    va_start(Parameters, Format);
+    DisplayMessageAnsiV(CharPrint, Format, Parameters);
+    va_end(Parameters);
+}
 
-            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);
+static VOID DemLoadNTDOSKernel(VOID)
+{
+    BOOLEAN Success = FALSE;
+    LPCSTR  DosKernelFileName = "ntdos.sys";
+    HANDLE  hDosKernel;
+    ULONG   ulDosKernelSize = 0;
+
+    DPRINT1("You are loading Windows NT DOS!\n");
+
+    /* 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 */
-                DisplayMessage(L"Windows NT DOS kernel file '%S' loading failed (Error: %u). The VDM will shut down.",
-                               DosKernelFileName, GetLastError());
-                EmulatorTerminate();
-                return;
-            }
+    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;
         }
 
@@ -112,6 +242,426 @@ Quit:
     }
 }
 
+
+
+
+/******************************************************************************\
+|**                      DOS Command Process management                      **|
+\******************************************************************************/
+
+
+#ifndef STANDALONE
+static ULONG SessionId = 0;
+
+/*
+ * 16-bit Command Interpreter information for DOS reentry
+ */
+typedef struct _COMSPEC_INFO
+{
+    LIST_ENTRY Entry;
+    DWORD dwExitCode;
+    WORD ComSpecPsp;
+    BOOLEAN Terminated;
+} COMSPEC_INFO, *PCOMSPEC_INFO;
+
+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))
+    {
+        DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
+        if (CommandInfo.EnvLen > EnvSize)
+        {
+            /* Expand the environment size */
+            EnvSize = CommandInfo.EnvLen;
+            CommandInfo.Env = Env = RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
+
+            /* Repeat the request */
+            Repeat = TRUE;
+            goto Command;
+        }
+
+        /* Shouldn't happen */
+        DisplayMessage(L"An unrecoverable failure happened from start-app BOP; exiting DOS.");
+        setCF(1);
+        goto Quit;
+    }
+
+    // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
+
+    DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
+
+#else
+
+    if (!First)
+    {
+        DPRINT1("Exit DOS from start-app BOP\n");
+        setCF(1);
+        goto Quit;
+    }
+
+#endif
+
+    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);
+
+#ifndef STANDALONE
+    /* Update console title if we run in a separate console */
+    if (SessionId != 0)
+        SetConsoleTitleA(AppName);
+#endif
+
+    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
+    }
+}
+
+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 */
@@ -120,77 +670,162 @@ static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
 
     switch (FuncNum)
     {
-        case 0x08:  // Launch external command
+        /* Kill the VDM */
+        case 0x00:
         {
-            BOOL Result;
-            DWORD dwExitCode;
-
-            LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
-            CHAR CmdLine[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH + 1] = "";
-            LPSTR CmdLinePtr;
-            ULONG CmdLineLen;
-            STARTUPINFOA StartupInfo;
-            PROCESS_INFORMATION ProcessInformation;
-
-            /* 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);
-
-            RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
-            RtlZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
-
-            StartupInfo.cb = sizeof(StartupInfo);
-
-            VidBiosDetachFromConsole();
-
-            Result = CreateProcessA(NULL,
-                                    CmdLine,
-                                    NULL,
-                                    NULL,
-                                    TRUE,
-                                    0,
-                                    NULL,
-                                    NULL,
-                                    &StartupInfo,
-                                    &ProcessInformation);
-            if (Result)
-            {
-                DPRINT1("Command '%s' ('%s') launched successfully\n", Command, CmdLine);
+            /* Stop the VDM */
+            EmulatorTerminate();
+            return;
+        }
 
-                /* Wait for process termination */
-                WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
+        /*
+         * 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' ('%s')\n", Command, CmdLine);
-                dwExitCode = GetLastError();
+                /* An error happened, bail out */
+                setCF(1);
+                setAX(LOWORD(ERROR_BAD_EXE_FORMAT));
+                break;
             }
 
-            VidBiosAttachToConsole();
+            /* Return success: DOS application */
+            setCF(0);
+            break;
+        }
+
+        /*
+         * 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;
+        }
 
-            setAL((UCHAR)dwExitCode);
+        /*
+         * 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;
         }
 
@@ -203,100 +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;
-    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);
-
-    UNREFERENCED_PARAMETER(Parameter);
-    ASSERT(Env);
+    PCOMSPEC_INFO ComSpecInfo = DosStartProc32->ComSpecInfo;
+#endif
 
-    /* Clear the structure */
+    /* 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);
 
-    /* Get the initial information */
-    CommandInfo.TaskId = SessionId;
-    CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS;
-    GetNextVDMCommand(&CommandInfo);
+    // FIXME: Build suitable 32-bit environment!!
 
-    do
+#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)
     {
-        /* 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;
+        /* Resume the process */
+        ResumeThread(ProcessInfo.hThread);
 
-Command:
-        if (!GetNextVDMCommand(&CommandInfo))
+        /* 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;
+#endif
+
+    DosStartProc32.ExecutablePath = (LPSTR)ExecutablePath;
+    DosStartProc32.CommandLine    = (LPSTR)CommandLine;
+    DosStartProc32.Environment    = (LPSTR)Environment;
+
+#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)
+    {
+        DisplayMessage(L"FATAL: Failed to create the command processing thread: %d", GetLastError());
+        Result = GetLastError();
+        goto Quit;
+    }
+
+#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 */
-        Result = DosStartProcess(AppName, CmdLine, Env, MAKELONG(getIP(), getCS()));
-        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;
-}
-#endif
+    /* Close the thread handle */
+    CloseHandle(CommandThread);
 
-/* PUBLIC VARIABLES ***********************************************************/
+    DPRINT1("32-bit app stopped\n");
+#endif
 
+Quit:
 #ifndef STANDALONE
-BOOLEAN AcceptCommands = TRUE;
-HANDLE CommandThread = NULL;
-ULONG SessionId = 0;
+    CloseHandle(DosStartProc32.hEvent);
 #endif
 
-/* PUBLIC FUNCTIONS ***********************************************************/
+    /* 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
@@ -323,7 +1166,8 @@ 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);
+    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");
@@ -334,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);
@@ -402,8 +1251,8 @@ static VOID WINAPI DosInitialize(LPWORD Stack)
 Quit:
         if (!Success)
         {
-            DisplayMessage(L"DOS BIOS file '%S' loading failed (Error: %u). The VDM will shut down.",
-                           DosBiosFileName, GetLastError());
+            BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
+                               DosBiosFileName, GetLastError());
             return;
         }
     }
@@ -425,11 +1274,8 @@ Quit:
 static VOID WINAPI DosStart(LPWORD Stack)
 {
     BOOLEAN Success;
-#ifdef STANDALONE
     DWORD Result;
-    CHAR ApplicationName[MAX_PATH];
-    CHAR CommandLine[DOS_CMDLINE_LENGTH];
-#else
+#ifndef STANDALONE
     INT i;
 #endif
 
@@ -441,17 +1287,20 @@ 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)
     {
-        DisplayMessage(L"DOS32 loading failed (Error: %u). The VDM will shut down.", GetLastError());
+        BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
         EmulatorTerminate();
         return;
     }
 
     /* Load the mouse driver */
-    DosMouseInitialize();
+    // DosMouseInitialize();
 
 #ifndef STANDALONE
 
@@ -462,60 +1311,147 @@ static VOID WINAPI DosStart(LPWORD Stack)
         {
             /* This is the session ID (hex format) */
             SessionId = wcstoul(NtVdmArgv[i] + 2, NULL, 16);
-
-            /* The VDM hasn't been started from a console, so quit when the task is done */
-            AcceptCommands = FALSE;
         }
     }
 
-    /* Create the GetNextVDMCommand thread */
-    CommandThread = CreateThread(NULL, 0, &CommandThreadProc, NULL, 0, NULL);
-    if (CommandThread == NULL)
+    /* Initialize Win32-VDM environment */
+    Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
+    if (Env == NULL)
     {
-        wprintf(L"FATAL: Failed to create the command processing thread: %d\n", GetLastError());
-        goto Quit;
+        DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
+        EmulatorTerminate();
+        return;
     }
 
-    /* Wait for the command thread to exit */
-    WaitForSingleObject(CommandThread, INFINITE);
+    /* Clear the structure */
+    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
 
-    /* Close the thread handle */
-    CloseHandle(CommandThread);
+    /* 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 */
-    Result = DosStartProcess(ApplicationName, CommandLine,
-                             SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0),
-                             MAKELONG(getIP(), getCS()));
+#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
index 868e3ea..d3df779 100644 (file)
 
 /* VARIABLES ******************************************************************/
 
-#ifndef STANDALONE
-extern BOOLEAN AcceptCommands;
-extern HANDLE CommandThread;
-extern ULONG SessionId;
-#endif
-
 /* FUNCTIONS ******************************************************************/
 
+typedef VOID (*CHAR_PRINT)(IN CHAR Character);
+VOID BiosCharPrint(CHAR Character);
+VOID DosCharPrint(CHAR Character);
+
+VOID DemDisplayMessage(IN CHAR_PRINT CharPrint,
+                       IN LPCSTR Format, ...);
+
+#define BiosDisplayMessage(Format, ...) \
+    DemDisplayMessage(BiosCharPrint, (Format), ##__VA_ARGS__)
+
+#define DosDisplayMessage(Format, ...)  \
+    DemDisplayMessage(DosCharPrint, (Format), ##__VA_ARGS__)
+
+
+BOOLEAN DosShutdown(BOOLEAN Immediate);
+
+DWORD DosStartProcess32(IN LPCSTR ExecutablePath,
+                        IN LPCSTR CommandLine,
+                        IN LPCSTR Environment OPTIONAL,
+                        IN DWORD ReturnAddress OPTIONAL,
+                        IN BOOLEAN StartComSpec);
+
 DWORD
 WINAPI
 demClientErrorEx
index bbe3a24..9442d53 100644 (file)
@@ -14,6 +14,7 @@
 #include "emulator.h"
 #include "int32.h"
 
+#include "../dem.h"
 #include "dos.h"
 #include "dosfiles.h"
 #include "memory.h"
@@ -32,8 +33,6 @@
 
 /* PRIVATE VARIABLES **********************************************************/
 
-// CALLBACK16 BiosContext;
-
 /* PUBLIC VARIABLES ***********************************************************/
 
 /* Global DOS BIOS data area */
@@ -175,6 +174,7 @@ BOOLEAN DosBIOSInitialize(VOID)
     // FIXME: Add a block of fixed size for the stack in BIOS/DOS_DATA instead!
     setSS(0x0F00);
     setSP(0x0FF0);
+/// setBP(0x091E); // DOS base stack pointer relic value
 
     /*
      * Initialize the INT 13h (BIOS Disk Services) handler chain support.
@@ -207,21 +207,19 @@ BOOLEAN DosBIOSInitialize(VOID)
      * SysInit part...
      */
 
-    // InitializeContext(&DosContext, BIOS_CODE_SEGMENT, 0x0010);
-
     /* Initialize the DOS kernel (DosInit) */
     if (!DosKRNLInitialize())
     {
-        DisplayMessage(L"Failed to load the DOS kernel! Exiting...");
+        BiosDisplayMessage("Failed to load the DOS kernel! Exiting...\n");
         return FALSE;
     }
 
     /* DOS kernel loading succeeded, we can finish the initialization */
 
-    /* Build the system master environment block (inherited by the shell) */
+    /* Build the system master (pre-) environment block (inherited by the shell) */
     if (!DosBuildSysEnvBlock())
     {
-        DPRINT1("An error occurred when setting up the system environment block.\n");
+        DosDisplayMessage("An error occurred when setting up the system environment block.\n");
     }
 
     /* TODO: Read CONFIG.NT/SYS */
index d3ff081..f8ded90 100644 (file)
@@ -1351,7 +1351,6 @@ VOID WINAPI DosInt21h(LPWORD Stack)
             {
                 DOS_EXEC_TYPE LoadType = (DOS_EXEC_TYPE)OrgAL;
 
-#ifndef STANDALONE
                 if (LoadType == DOS_LOAD_AND_EXECUTE)
                 {
                     /* Create a new process */
@@ -1360,7 +1359,6 @@ VOID WINAPI DosInt21h(LPWORD Stack)
                                                  MAKELONG(Stack[STACK_IP], Stack[STACK_CS]));
                 }
                 else
-#endif
                 {
                     /* Just load an executable */
                     ErrorCode = DosLoadExecutable(LoadType,
@@ -2464,8 +2462,8 @@ BOOLEAN DosKRNLInitialize(VOID)
     /* Load the EMS driver */
     if (!EmsDrvInitialize(EMS_SEGMENT, EMS_TOTAL_PAGES))
     {
-        DPRINT1("Could not initialize EMS. EMS will not be available.\n"
-                "Page frame segment or number of EMS pages invalid.\n");
+        DosDisplayMessage("Could not initialize EMS. EMS will not be available.\n"
+                          "Page frame segment or number of EMS pages invalid.\n");
     }
 
     /* Finally initialize the UMBs */
index f320828..db548e9 100644 (file)
@@ -65,6 +65,7 @@ typedef struct _DOS_FCB
     BYTE RecordNumber[3];
 } DOS_FCB, *PDOS_FCB;
 
+// http://www.ctyme.com/intr/rb-2983.htm
 typedef struct _DOS_SYSVARS
 {
     DWORD OemHandler;
@@ -134,6 +135,7 @@ typedef struct _DOS_FIND_FILE_BLOCK
     CHAR FileName[13];
 } DOS_FIND_FILE_BLOCK, *PDOS_FIND_FILE_BLOCK;
 
+// http://www.ctyme.com/intr/rb-3023.htm
 typedef struct _DOS_SDA
 {
     BYTE PrinterEchoFlag;
@@ -309,6 +311,9 @@ do { \
                   (IntNumber), (IntHandler), NULL);         \
 } while(0);
 
+VOID ConDrvInitialize(VOID);
+VOID ConDrvCleanup(VOID);
+
 /*
  * DOS BIOS Functions
  * See bios.c
@@ -318,14 +323,11 @@ BOOLEAN DosCheckInput(VOID);
 VOID DosPrintCharacter(WORD FileHandle, CHAR Character);
 
 BOOLEAN DosBIOSInitialize(VOID);
-VOID ConDrvInitialize(VOID);
-VOID ConDrvCleanup(VOID);
 
 /*
  * DOS Kernel Functions
  * See dos.c
  */
-
 BOOLEAN DosKRNLInitialize(VOID);
 
 #endif // _DOS_H_
index 22f26af..7dc8277 100644 (file)
@@ -84,6 +84,16 @@ static inline VOID DosSaveState(VOID)
     PDOS_REGISTER_STATE State;
     WORD StackPointer = getSP();
 
+    DPRINT1("\n"
+            "DosSaveState(before) -- SS:SP == %04X:%04X\n"
+            "Original CPU State =\n"
+            "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
+            "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
+            "\n",
+            getSS(), getSP(),
+            getDS(), getES(), getAX(), getCX(),
+            getDX(), getBX(), getBP(), getSI(), getDI());
+
     /*
      * Allocate stack space for the registers. Note that we
      * already have one word allocated (the interrupt number).
@@ -102,6 +112,16 @@ static inline VOID DosSaveState(VOID)
     State->BP = getBP();
     State->SI = getSI();
     State->DI = getDI();
+
+    DPRINT1("\n"
+            "DosSaveState(after) -- SS:SP == %04X:%04X\n"
+            "Saved State =\n"
+            "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
+            "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
+            "\n",
+            getSS(), getSP(),
+            State->DS, State->ES, State->AX, State->CX,
+            State->DX, State->BX, State->BP, State->SI, State->DI);
 }
 
 static inline VOID DosRestoreState(VOID)
@@ -113,6 +133,17 @@ static inline VOID DosRestoreState(VOID)
      * already have one word allocated (the interrupt number).
      */
     State = SEG_OFF_TO_PTR(getSS(), getSP());
+
+    DPRINT1("\n"
+            "DosRestoreState(before) -- SS:SP == %04X:%04X\n"
+            "Saved State =\n"
+            "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
+            "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
+            "\n",
+            getSS(), getSP(),
+            State->DS, State->ES, State->AX, State->CX,
+            State->DX, State->BX, State->BP, State->SI, State->DI);
+
     setSP(getSP() + sizeof(DOS_REGISTER_STATE) - sizeof(WORD));
 
     /* Restore */
@@ -125,6 +156,16 @@ static inline VOID DosRestoreState(VOID)
     setBP(State->BP);
     setSI(State->SI);
     setDI(State->DI);
+
+    DPRINT1("\n"
+            "DosRestoreState(after) -- SS:SP == %04X:%04X\n"
+            "Restored CPU State =\n"
+            "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
+            "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
+            "\n",
+            getSS(), getSP(),
+            getDS(), getES(), getAX(), getCX(),
+            getDX(), getBX(), getBP(), getSI(), getDI());
 }
 
 static WORD DosCopyEnvironmentBlock(IN LPCSTR Environment OPTIONAL,
@@ -251,6 +292,12 @@ VOID DosCreatePsp(WORD Segment, WORD ProgramSize)
         /* Link to the parent's environment block */
         PspBlock->EnvBlock = SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock;
     }
+/*
+    else
+    {
+        PspBlock->EnvBlock = SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0);
+    }
+*/
 
     /* Copy the parent handle table */
     DosCopyHandleTable(PspBlock->HandleTable);
@@ -560,6 +607,9 @@ DWORD DosLoadExecutableInternal(IN DOS_EXEC_TYPE LoadType,
             /* Push the task state */
             DosSaveState();
 
+            DPRINT1("Sda->CurrentPsp = 0x%04x; Old LastStack = 0x%08x, New LastStack = 0x%08x\n",
+                   Sda->CurrentPsp, SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack, MAKELONG(getSP(), getSS()));
+
             /* Update the last stack in the PSP */
             SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack = MAKELONG(getSP(), getSS());
         }
@@ -705,190 +755,38 @@ Cleanup:
     return Result;
 }
 
-DWORD DosStartProcess(IN LPCSTR ExecutablePath,
-                      IN LPCSTR CommandLine,
-                      IN LPCSTR Environment OPTIONAL,
-                      IN DWORD ReturnAddress OPTIONAL)
-{
-    DWORD Result;
-
-    SIZE_T CmdLen = strlen(CommandLine);
-    DPRINT1("Starting '%s' ('%.*s')...\n",
-            ExecutablePath,
-            /* Display the command line without the terminating 0d 0a (and skip the terminating NULL) */
-            CmdLen >= 2 ? (CommandLine[CmdLen - 2] == '\r' ? CmdLen - 2
-                                                           : CmdLen)
-                        : CmdLen,
-            CommandLine);
-
-    Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
-                               ExecutablePath,
-                               NULL,
-                               CommandLine,
-                               Environment,
-                               ReturnAddress);
-
-    if (Result != ERROR_SUCCESS) goto Quit;
-
-#ifndef STANDALONE
-    /* Update console title if we run in a separate console */
-    if (SessionId != 0)
-        SetConsoleTitleA(ExecutablePath);
-#endif
-
-    /* Attach to the console */
-    ConsoleAttach();
-    VidBiosAttachToConsole();
-
-    /* Start simulation */
-    SetEvent(VdmTaskEvent);
-    CpuSimulate();
-
-    /* Detach from the console */
-    VidBiosDetachFromConsole();
-    ConsoleDetach();
-
-Quit:
-    return Result;
-}
-
-#ifndef STANDALONE
 WORD DosCreateProcess(IN LPCSTR ProgramName,
                       IN PDOS_EXEC_PARAM_BLOCK Parameters,
                       IN DWORD ReturnAddress OPTIONAL)
 {
-    DWORD Result;
+    DWORD Result = ERROR_SUCCESS;
     DWORD BinaryType;
-    LPVOID Environment = NULL;
-    VDM_COMMAND_INFO CommandInfo;
-    CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1];
-    CHAR AppName[MAX_PATH];
-    CHAR PifFile[MAX_PATH];
-    CHAR Desktop[MAX_PATH];
-    CHAR Title[MAX_PATH];
-    LPSTR CmdLinePtr;
-    ULONG CmdLineSize;
-    ULONG EnvSize = 256;
-    PVOID Env;
-    STARTUPINFOA StartupInfo;
-    PROCESS_INFORMATION ProcessInfo;
 
     /* Get the binary type */
     if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
 
-    /* Initialize Win32-VDM environment */
-    Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
-    if (Env == NULL) return GetLastError();
-
-    /* Did the caller specify an environment segment? */
-    if (Parameters->Environment)
-    {
-        /* Yes, use it instead of the parent one */
-        Environment = SEG_OFF_TO_PTR(Parameters->Environment, 0);
-    }
-
-    /* Set up the startup info structure */
-    RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
-    StartupInfo.cb = sizeof(StartupInfo);
-
-    /*
-     * Convert the DOS command line to Win32-compatible format, by concatenating
-     * the program name with the converted command line.
-     * Format of the DOS command line: 1 byte for size; 127 bytes for contents.
-     */
-    CmdLinePtr = CmdLine;
-    strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name
-    CmdLinePtr += strlen(CmdLinePtr);
-    *CmdLinePtr++ = ' ';                        // Add separating space
-
-    CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH);
-    RtlCopyMemory(CmdLinePtr,
-                  (LPSTR)FAR_POINTER(Parameters->CommandLine) + 1,
-                  CmdLineSize);
-    /* NULL-terminate it */
-    CmdLinePtr[CmdLineSize] = '\0';
-
-    /* Remove any trailing return carriage character and NULL-terminate the command line */
-    while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
-    *CmdLinePtr = '\0';
-
-    /* Create the process */
-    if (!CreateProcessA(ProgramName,
-                        CmdLine,
-                        NULL,
-                        NULL,
-                        FALSE,
-                        0,
-                        Environment,
-                        NULL,
-                        &StartupInfo,
-                        &ProcessInfo))
-    {
-        RtlFreeHeap(RtlGetProcessHeap(), 0, Env);
-        return GetLastError();
-    }
-
     /* Check the type of the program */
     switch (BinaryType)
     {
-        /* These are handled by NTVDM */
-        case SCS_DOS_BINARY:
+        /* Those are handled by NTVDM */
         case SCS_WOW_BINARY:
         {
-            /* Clear the structure */
-            RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
-
-            /* Initialize the structure members */
-            CommandInfo.TaskId = SessionId;
-            CommandInfo.VDMState = VDM_FLAG_NESTED_TASK | VDM_FLAG_DONT_WAIT;
-            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;
-
-Command:
-            /* Get the VDM command information */
-            if (!GetNextVDMCommand(&CommandInfo))
-            {
-                if (CommandInfo.EnvLen > EnvSize)
-                {
-                    /* 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;
-                }
-
-                /* Shouldn't happen */
-                ASSERT(FALSE);
-            }
-
+            DisplayMessage(L"Trying to load '%S'. WOW16 applications are not supported at the moment.",
+                           ProgramName);
+            // Fall through
+        }
+        case SCS_DOS_BINARY:
+        {
             /* Load the executable */
             Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
-                                       AppName,
+                                       ProgramName,
                                        Parameters,
-                                       CmdLine,
-                                       Env,
+                                       NULL,
+                                       NULL,
                                        ReturnAddress);
-            if (Result == ERROR_SUCCESS)
+            if (Result != ERROR_SUCCESS)
             {
-                /* Increment the re-entry count */
-                CommandInfo.VDMState = VDM_INC_REENTER_COUNT;
-                GetNextVDMCommand(&CommandInfo);
-            }
-            else
-            {
-                DisplayMessage(L"Could not load '%S'. Error: %u", AppName, Result);
+                DisplayMessage(L"Could not load '%S'. Error: %u", ProgramName, Result);
             }
 
             break;
@@ -897,20 +795,53 @@ Command:
         /* Not handled by NTVDM */
         default:
         {
-            /* Wait for the process to finish executing */
-            WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
-        }
-    }
+            LPSTR Environment = NULL;
+            CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1];
+            LPSTR CmdLinePtr;
+            ULONG CmdLineSize;
+
+            /* Did the caller specify an environment segment? */
+            if (Parameters->Environment)
+            {
+                /* Yes, use it instead of the parent one */
+                Environment = (LPSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
+            }
 
-    RtlFreeHeap(RtlGetProcessHeap(), 0, Env);
+            /*
+             * Convert the DOS command line to Win32-compatible format, by concatenating
+             * the program name with the converted command line.
+             * Format of the DOS command line: 1 byte for size; 127 bytes for contents.
+             */
+            CmdLinePtr = CmdLine;
+            strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name
+            CmdLinePtr += strlen(CmdLinePtr);
+            *CmdLinePtr++ = ' ';                        // Add separating space
+
+            CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH);
+            RtlCopyMemory(CmdLinePtr,
+                          (LPSTR)FAR_POINTER(Parameters->CommandLine) + 1,
+                          CmdLineSize);
+            /* NULL-terminate it */
+            CmdLinePtr[CmdLineSize] = '\0';
+
+            /* Remove any trailing return carriage character and NULL-terminate the command line */
+            while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
+            *CmdLinePtr = '\0';
+
+            Result = DosStartProcess32(ProgramName, CmdLine,
+                                       Environment, ReturnAddress,
+                                       TRUE);
+            if (Result != ERROR_SUCCESS)
+            {
+                DisplayMessage(L"Could not load 32-bit '%S'. Error: %u", ProgramName, Result);
+            }
 
-    /* Close the handles */
-    CloseHandle(ProcessInfo.hProcess);
-    CloseHandle(ProcessInfo.hThread);
+            break;
+        }
+    }
 
-    return ERROR_SUCCESS;
+    return Result;
 }
-#endif
 
 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
 {
@@ -920,9 +851,6 @@ VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
     LPWORD Stack;
     BYTE TerminationType;
-#ifndef STANDALONE
-    VDM_COMMAND_INFO CommandInfo;
-#endif
 
     DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
            Psp, ReturnCode, KeepResident);
@@ -991,35 +919,24 @@ Done:
         DosSetProcessContext(PspBlock->ParentPsp);
         if (Sda->CurrentPsp == SYSTEM_PSP)
         {
-            ResetEvent(VdmTaskEvent);
+            // NOTE: we can also use the DOS BIOS exit code.
             CpuUnsimulate();
             return;
         }
     }
 
-#ifndef STANDALONE
-
-    /* Decrement the re-entry count */
-    CommandInfo.TaskId = SessionId;
-    CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
-    GetNextVDMCommand(&CommandInfo);
-
-    /* Clear the structure */
-    RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
-
-    /* Update the VDM state of the task */
-    CommandInfo.TaskId = SessionId;
-    CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
-    GetNextVDMCommand(&CommandInfo);
-
-#endif
-
     /* Save the return code - Normal termination or TSR */
     TerminationType = (KeepResident != 0 ? 0x03 : 0x00);
     Sda->ErrorLevel = MAKEWORD(ReturnCode, TerminationType);
 
+    DPRINT1("PspBlock->ParentPsp = 0x%04x; Sda->CurrentPsp = 0x%04x\n",
+           PspBlock->ParentPsp, Sda->CurrentPsp);
+
     if (Sda->CurrentPsp != SYSTEM_PSP)
     {
+        DPRINT1("Sda->CurrentPsp = 0x%04x; Old SS:SP = %04X:%04X going to be LastStack = 0x%08x\n",
+               Sda->CurrentPsp, getSS(), getSP(), SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack);
+
         /* Restore the parent's stack */
         setSS(HIWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
         setSP(LOWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
index 7bb675c..b11b1b3 100644 (file)
@@ -109,13 +109,6 @@ DWORD DosLoadExecutable
     IN DWORD ReturnAddress OPTIONAL
 );
 
-DWORD DosStartProcess(
-    IN LPCSTR ExecutablePath,
-    IN LPCSTR CommandLine,
-    IN LPCSTR Environment OPTIONAL,
-    IN DWORD ReturnAddress OPTIONAL
-);
-
 WORD DosCreateProcess
 (
     LPCSTR ProgramName,
index 481afe1..4aca4a7 100644 (file)
@@ -42,6 +42,7 @@
 LPVOID  BaseAddress = NULL;
 BOOLEAN VdmRunning  = TRUE;
 
+HANDLE VdmTaskEvent = NULL;
 static HANDLE InputThread = NULL;
 
 LPCWSTR ExceptionName[] =
@@ -110,13 +111,6 @@ VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack)
     EmulatorTerminate();
 }
 
-VOID EmulatorTerminate(VOID)
-{
-    /* Stop the VDM */
-    CpuUnsimulate(); // Halt the CPU
-    VdmRunning = FALSE;
-}
-
 VOID EmulatorInterruptSignal(VOID)
 {
     /* Call the Fast486 API */
@@ -186,64 +180,118 @@ static VOID WINAPI PitChan2Out(LPVOID Param, BOOLEAN State)
 
 static DWORD
 WINAPI
-PumpConsoleInput(LPVOID Parameter)
+ConsoleEventThread(LPVOID Parameter)
 {
     HANDLE ConsoleInput = (HANDLE)Parameter;
-    INPUT_RECORD InputRecord;
-    DWORD Count;
+    HANDLE WaitHandles[2];
+    DWORD  WaitResult;
+
+    /*
+     * For optimization purposes, Windows (and hence ReactOS, too, for
+     * compatibility reasons) uses a static buffer if no more than five
+     * input records are read. Otherwise a new buffer is used.
+     * The client-side expects that we know this behaviour.
+     * See consrv/coninput.c
+     *
+     * We exploit here this optimization by also using a buffer of 5 records.
+     */
+    INPUT_RECORD InputRecords[5];
+    ULONG NumRecords, i;
+
+    WaitHandles[0] = VdmTaskEvent;
+    WaitHandles[1] = GetConsoleInputWaitHandle();
 
     while (VdmRunning)
     {
         /* Make sure the task event is signaled */
-        WaitForSingleObject(VdmTaskEvent, INFINITE);
+        WaitResult = WaitForMultipleObjects(ARRAYSIZE(WaitHandles),
+                                            WaitHandles,
+                                            TRUE,
+                                            INFINITE);
+        switch (WaitResult)
+        {
+            case WAIT_OBJECT_0 + 0:
+            case WAIT_OBJECT_0 + 1:
+                break;
+            default:
+                return GetLastError();
+        }
 
         /* Wait for an input record */
-        if (!ReadConsoleInput(ConsoleInput, &InputRecord, 1, &Count))
+        if (!ReadConsoleInputExW(ConsoleInput,
+                                 InputRecords,
+                                 ARRAYSIZE(InputRecords),
+                                 &NumRecords,
+                                 CONSOLE_READ_CONTINUE))
         {
             DWORD LastError = GetLastError();
-            DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, Count, LastError);
+            DPRINT1("Error reading console input (0x%p, %lu) - Error %lu\n", ConsoleInput, NumRecords, LastError);
             return LastError;
         }
 
-        ASSERT(Count != 0);
-
-        /* Check the event type */
-        switch (InputRecord.EventType)
+        // ASSERT(NumRecords != 0);
+        if (NumRecords == 0)
         {
-            /*
-             * Hardware events
-             */
-            case KEY_EVENT:
-                KeyboardEventHandler(&InputRecord.Event.KeyEvent);
-                break;
-
-            case MOUSE_EVENT:
-                MouseEventHandler(&InputRecord.Event.MouseEvent);
-                break;
-
-            case WINDOW_BUFFER_SIZE_EVENT:
-                ScreenEventHandler(&InputRecord.Event.WindowBufferSizeEvent);
-                break;
-
-            /*
-             * Interface events
-             */
-            case MENU_EVENT:
-                MenuEventHandler(&InputRecord.Event.MenuEvent);
-                break;
-
-            case FOCUS_EVENT:
-                FocusEventHandler(&InputRecord.Event.FocusEvent);
-                break;
+            DPRINT1("Got NumRecords == 0!\n");
+            continue;
+        }
 
-            default:
-                break;
+        /* Dispatch the events */
+        for (i = 0; i < NumRecords; i++)
+        {
+            /* Check the event type */
+            switch (InputRecords[i].EventType)
+            {
+                /*
+                 * Hardware events
+                 */
+                case KEY_EVENT:
+                    KeyboardEventHandler(&InputRecords[i].Event.KeyEvent);
+                    break;
+
+                case MOUSE_EVENT:
+                    MouseEventHandler(&InputRecords[i].Event.MouseEvent);
+                    break;
+
+                case WINDOW_BUFFER_SIZE_EVENT:
+                    ScreenEventHandler(&InputRecords[i].Event.WindowBufferSizeEvent);
+                    break;
+
+                /*
+                 * Interface events
+                 */
+                case MENU_EVENT:
+                    MenuEventHandler(&InputRecords[i].Event.MenuEvent);
+                    break;
+
+                case FOCUS_EVENT:
+                    FocusEventHandler(&InputRecords[i].Event.FocusEvent);
+                    break;
+
+                default:
+                    DPRINT1("Unknown input event type 0x%04x\n", InputRecords[i].EventType);
+                    break;
+            }
         }
+
+        /* Let the console subsystem queue some new events */
+        Sleep(10);
     }
 
     return 0;
 }
 
+static VOID PauseEventThread(VOID)
+{
+    ResetEvent(VdmTaskEvent);
+}
+
+static VOID ResumeEventThread(VOID)
+{
+    SetEvent(VdmTaskEvent);
+}
+
+
 /* PUBLIC FUNCTIONS ***********************************************************/
 
 static VOID
@@ -356,6 +404,29 @@ VOID DumpMemory(BOOLEAN TextFormat)
     DPRINT1("Memory dump done\n");
 }
 
+VOID EmulatorPause(VOID)
+{
+    /* Pause the VDM */
+    VDDBlockUserHook();
+    VgaRefreshDisplay();
+    PauseEventThread();
+}
+
+VOID EmulatorResume(VOID)
+{
+    /* Resume the VDM */
+    ResumeEventThread();
+    VgaRefreshDisplay();
+    VDDResumeUserHook();
+}
+
+VOID EmulatorTerminate(VOID)
+{
+    /* Stop the VDM */
+    CpuUnsimulate(); // Halt the CPU
+    VdmRunning = FALSE;
+}
+
 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
 {
     /* Initialize memory */
@@ -403,11 +474,15 @@ BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
     MouseInit(1);
 
     /**************** ATTACH INPUT WITH CONSOLE *****************/
+    /* Create the task event */
+    VdmTaskEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+    ASSERT(VdmTaskEvent != NULL);
+
     /* Start the input thread */
-    InputThread = CreateThread(NULL, 0, &PumpConsoleInput, ConsoleInput, 0, NULL);
+    InputThread = CreateThread(NULL, 0, &ConsoleEventThread, ConsoleInput, 0, NULL);
     if (InputThread == NULL)
     {
-        DisplayMessage(L"Failed to create the console input thread.");
+        wprintf(L"FATAL: Failed to create the console input thread.\n");
         EmulatorCleanup();
         return FALSE;
     }
@@ -416,7 +491,7 @@ BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput)
     /* Initialize the VGA */
     if (!VgaInitialize(ConsoleOutput))
     {
-        DisplayMessage(L"Failed to initialize VGA support.");
+        wprintf(L"FATAL: Failed to initialize VGA support.\n");
         EmulatorCleanup();
         return FALSE;
     }
@@ -440,6 +515,10 @@ VOID EmulatorCleanup(VOID)
     if (InputThread != NULL) CloseHandle(InputThread);
     InputThread = NULL;
 
+    /* Close the task event */
+    if (VdmTaskEvent != NULL) CloseHandle(VdmTaskEvent);
+    VdmTaskEvent = NULL;
+
     PS2Cleanup();
 
     SpeakerCleanup();
index 3f9e08a..2e0d4b7 100644 (file)
@@ -107,12 +107,13 @@ VOID FASTCALL EmulatorFpu
     PFAST486_STATE State
 );
 
+VOID EmulatorInterruptSignal(VOID);
 VOID EmulatorException(BYTE ExceptionNumber, LPWORD Stack);
 
+VOID EmulatorPause(VOID);
+VOID EmulatorResume(VOID);
 VOID EmulatorTerminate(VOID);
 
-VOID EmulatorInterruptSignal(VOID);
-
 BOOLEAN EmulatorInitialize(HANDLE ConsoleInput, HANDLE ConsoleOutput);
 VOID EmulatorCleanup(VOID);
 
index 84bf6fe..dc1a6f3 100644 (file)
@@ -16,6 +16,8 @@
 #include "bios/bios.h"
 #include "cpu/cpu.h"
 
+#include "dos/dem.h"
+
 #include "resource.h"
 
 /* VARIABLES ******************************************************************/
@@ -24,8 +26,6 @@ static HANDLE ConsoleInput  = INVALID_HANDLE_VALUE;
 static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
 static DWORD  OrgConsoleInputMode, OrgConsoleOutputMode;
 
-HANDLE VdmTaskEvent = NULL;
-
 // Command line of NTVDM
 INT     NtVdmArgc;
 WCHAR** NtVdmArgv;
@@ -275,43 +275,59 @@ DisplayMessage(IN LPCWSTR Format, ...)
 #endif
 }
 
+static VOID
+ConsoleCleanup(VOID);
+
+static VOID
+VdmShutdown(BOOLEAN Immediate)
+{
+    /*
+     * Immediate = TRUE:  Immediate shutdown;
+     *             FALSE: Delayed shutdown.
+     */
+    BOOLEAN MustShutdown;
+
+    /* 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();
+
+        DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
+        /* Some VDDs rely on the fact that NTVDM calls ExitProcess on Windows */
+        ExitProcess(0);
+    }
+}
+
 static BOOL
 WINAPI
 ConsoleCtrlHandler(DWORD ControlType)
 {
-// HACK: Should be removed!
-#ifndef STANDALONE
-extern BOOLEAN AcceptCommands;
-extern HANDLE CommandThread;
-#endif
-
     switch (ControlType)
     {
         case CTRL_LAST_CLOSE_EVENT:
         {
-            if (WaitForSingleObject(VdmTaskEvent, 0) == WAIT_TIMEOUT)
-            {
-                /* Nothing runs, so exit immediately */
-#ifndef STANDALONE
-                if (CommandThread) TerminateThread(CommandThread, 0);
-#endif
-                EmulatorTerminate();
-            }
-#ifndef STANDALONE
-            else
-            {
-                /* A command is running, let it run, but stop accepting new commands */
-                AcceptCommands = FALSE;
-            }
-#endif
-
+            /* Delayed shutdown */
+            DPRINT1("NTVDM delayed killing in the CTRL_LAST_CLOSE_EVENT CtrlHandler!\n");
+            VdmShutdown(FALSE);
             break;
         }
 
         default:
         {
             /* Stop the VDM if the user logs out or closes the console */
-            EmulatorTerminate();
+            DPRINT1("Killing NTVDM in the 'default' CtrlHandler!\n");
+            VdmShutdown(TRUE);
         }
     }
     return TRUE;
@@ -342,7 +358,7 @@ ConsoleAttach(VOID)
         CloseHandle(ConsoleOutput);
         CloseHandle(ConsoleInput);
         wprintf(L"FATAL: Cannot save console in/out modes\n");
-        // return FALSE;
+        return FALSE;
     }
 
     /* Set the console input mode */
@@ -363,12 +379,12 @@ ConsoleAttach(VOID)
 VOID
 ConsoleDetach(VOID)
 {
+    /* Cleanup the UI */
+    ConsoleCleanupUI();
+
     /* Restore the original input and output console modes */
     SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
     SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
-
-    /* Cleanup the UI */
-    ConsoleCleanupUI();
 }
 
 static BOOL
@@ -448,7 +464,11 @@ VOID MenuEventHandler(PMENU_EVENT_RECORD MenuEvent)
 
         case ID_VDM_QUIT:
             /* Stop the VDM */
-            EmulatorTerminate();
+            // EmulatorTerminate();
+
+            /* Nothing runs, so exit immediately */
+            DPRINT1("Killing NTVDM via console menu!\n");
+            VdmShutdown(TRUE);
             break;
 
         default:
@@ -550,10 +570,6 @@ wmain(INT argc, WCHAR *argv[])
 
     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())
     {
@@ -578,19 +594,9 @@ wmain(INT argc, WCHAR *argv[])
     /* Let's go! Start simulation */
     CpuSimulate();
 
-Cleanup:
-    BiosCleanup();
-    EmulatorCleanup();
-    ConsoleCleanup();
-
-#ifndef STANDALONE
-    ExitVDM(FALSE, 0);
-#endif
-
     /* Quit the VDM */
-    DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
-    /* Some VDDs rely on the fact that NTVDM calls ExitProcess on Windows */
-    ExitProcess(0);
+Cleanup:
+    VdmShutdown(TRUE);
     return 0;
 }
 
index 825971e..f4d10a6 100644 (file)
@@ -69,8 +69,6 @@ DWORD WINAPI SetLastConsoleEventActive(VOID);
 
 /* FUNCTIONS ******************************************************************/
 
-extern HANDLE VdmTaskEvent;
-
 // Command line of NTVDM
 extern INT     NtVdmArgc;
 extern WCHAR** NtVdmArgv;