diff --git a/reactos/subsystems/mvdm/CMakeLists.txt b/reactos/subsystems/mvdm/CMakeLists.txt index 662fd3d0027..853db34c977 100644 --- a/reactos/subsystems/mvdm/CMakeLists.txt +++ b/reactos/subsystems/mvdm/CMakeLists.txt @@ -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 index 00000000000..bd81c47be0c --- /dev/null +++ b/reactos/subsystems/mvdm/dos/CMakeLists.txt @@ -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 index 00000000000..731c1086c8c --- /dev/null +++ b/reactos/subsystems/mvdm/dos/asmxtras.inc @@ -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 index 00000000000..deeee8488de --- /dev/null +++ b/reactos/subsystems/mvdm/dos/command.S @@ -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 +#include "asmxtras.inc" +#include + +/* 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 */ diff --git a/reactos/subsystems/mvdm/ntvdm/CMakeLists.txt b/reactos/subsystems/mvdm/ntvdm/CMakeLists.txt index 5765e76a125..baa0327608a 100644 --- a/reactos/subsystems/mvdm/ntvdm/CMakeLists.txt +++ b/reactos/subsystems/mvdm/ntvdm/CMakeLists.txt @@ -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 diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dem.c b/reactos/subsystems/mvdm/ntvdm/dos/dem.c index c0c00e1fd25..1a487d6c759 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dem.c +++ b/reactos/subsystems/mvdm/ntvdm/dos/dem.c @@ -22,18 +22,188 @@ #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" +#include "vddsup.h" + +/* + * EXPERIMENTAL! + * Activate this line if you want to have COMMAND.COM completely external. + */ +// #define COMSPEC_FULLY_EXTERNAL + /* PRIVATE VARIABLES **********************************************************/ -extern PDOS_DATA DosData; - /* PRIVATE FUNCTIONS **********************************************************/ +/* PUBLIC VARIABLES ***********************************************************/ + +/* PUBLIC FUNCTIONS ***********************************************************/ + + +/******************************************************************************\ +|** DOS DEM Kernel helpers **| +\******************************************************************************/ + + +VOID BiosCharPrint(CHAR Character) +{ + /* Save AX and BX */ + USHORT AX = getAX(); + USHORT BX = getBX(); + + /* + * 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)) + { + Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR)); + if (Buffer == NULL) + { + /* Allocation failed, use the static buffer and display a suitable error message */ + Buffer = StaticBuffer; + Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed."; + MsgLen = strlen(Format); + } + } +#else + MsgLen = ARRAYSIZE(Buffer) - 1; +#endif + + RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR)); + _vsnprintf(Buffer, MsgLen, Format, args); + + /* Display the message */ + DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer); + + MsgLen = strlen(Buffer); + str = Buffer; + while (MsgLen--) + { + if (*str == '\n' && CurChar != '\r') + CharPrint('\r'); + + CurChar = *str++; + CharPrint(CurChar); + } + +#ifndef WIN2K_COMPLIANT + /* Free the buffer if needed */ + if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer); +#endif +} + +VOID +DemDisplayMessage(IN CHAR_PRINT CharPrint, + IN LPCSTR Format, ...) +{ + va_list Parameters; + + va_start(Parameters, Format); + DisplayMessageAnsiV(CharPrint, Format, Parameters); + va_end(Parameters); +} + + +static VOID DemLoadNTDOSKernel(VOID) +{ + BOOLEAN Success = FALSE; + LPCSTR DosKernelFileName = "ntdos.sys"; + HANDLE hDosKernel; + ULONG ulDosKernelSize = 0; + + DPRINT1("You are loading Windows NT DOS!\n"); + + /* 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 */ + 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 */ @@ -45,47 +215,7 @@ static VOID WINAPI DosSystemBop(LPWORD Stack) /* Load the DOS kernel */ case 0x11: { - 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; - } - + 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; + /* Stop the VDM */ + EmulatorTerminate(); + return; + } - 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; + /* + * Get a new app to start + * + * Input + * DS:DX : Data block. + * + * Output + * CF : 0: Success; 1: Failure. + */ + case 0x01: + { + CmdStartProcess(); + break; + } - /* Spawn a user-defined 32-bit command preprocessor */ + /* + * 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()); - // 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) + if (!GetBinaryTypeA(ProgramName, &BinaryType)) { - DPRINT1("Command '%s' ('%s') launched successfully\n", Command, CmdLine); - - /* Wait for process termination */ - WaitForSingleObject(ProcessInformation.hProcess, INFINITE); - - /* Get the exit code */ - GetExitCodeProcess(ProcessInformation.hProcess, &dwExitCode); - - /* Close handles */ - CloseHandle(ProcessInformation.hThread); - CloseHandle(ProcessInformation.hProcess); - } - else - { - DPRINT1("Failed when launched command '%s' ('%s')\n", Command, CmdLine); - dwExitCode = GetLastError(); + /* An error happened, bail out */ + setCF(1); + setAX(LOWORD(GetLastError())); + break; } - VidBiosAttachToConsole(); + // FIXME: We only support DOS binaries for now... + ASSERT(BinaryType == SCS_DOS_BINARY); + if (BinaryType != SCS_DOS_BINARY) + { + /* An error happened, bail out */ + setCF(1); + setAX(LOWORD(ERROR_BAD_EXE_FORMAT)); + break; + } - setAL((UCHAR)dwExitCode); + /* 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; + } + + /* + * 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); - - /* Clear the structure */ - RtlZeroMemory(&CommandInfo, sizeof(CommandInfo)); - - /* Get the initial information */ - CommandInfo.TaskId = SessionId; - CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS; - GetNextVDMCommand(&CommandInfo); - - do - { - /* 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; - -Command: - 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; - } - - break; - } - - /* Start the process from the command line */ - Result = DosStartProcess(AppName, CmdLine, Env, MAKELONG(getIP(), getCS())); - if (Result != ERROR_SUCCESS) - { - DisplayMessage(L"Could not start '%S'. Error: %u", AppName, Result); - // break; - continue; - } - - First = FALSE; - } - while (AcceptCommands); - - RtlFreeHeap(RtlGetProcessHeap(), 0, Env); - return 0; -} + PCOMSPEC_INFO ComSpecInfo = DosStartProc32->ComSpecInfo; #endif -/* PUBLIC VARIABLES ***********************************************************/ + /* Set up the VDM, startup and process info structures */ +#ifndef STANDALONE + RtlZeroMemory(&CommandInfo, sizeof(CommandInfo)); +#endif + RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo)); + RtlZeroMemory(&StartupInfo, sizeof(StartupInfo)); + StartupInfo.cb = sizeof(StartupInfo); + + // FIXME: Build suitable 32-bit environment!! #ifndef STANDALONE -BOOLEAN AcceptCommands = TRUE; -HANDLE CommandThread = NULL; -ULONG SessionId = 0; + /* + * 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 -/* PUBLIC FUNCTIONS ***********************************************************/ + /* Start the process */ + Success = CreateProcessA(NULL, // ProgramName, + DosStartProc32->CommandLine, + NULL, + NULL, + TRUE, // Inherit handles + CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED, + DosStartProc32->Environment, + NULL, // lpCurrentDirectory, see "START" command in cmd.exe + &StartupInfo, + &ProcessInfo); + +#ifndef STANDALONE + /* Signal our caller the process was started */ + SetEvent(DosStartProc32->hEvent); + // After this point, 'DosStartProc32' is not valid anymore. +#endif + + if (Success) + { + /* Resume the process */ + ResumeThread(ProcessInfo.hThread); + + /* Wait for the process to finish running and retrieve its exit code */ + WaitForSingleObject(ProcessInfo.hProcess, INFINITE); + GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode); + + /* Close the handles */ + CloseHandle(ProcessInfo.hThread); + CloseHandle(ProcessInfo.hProcess); + } + else + { + dwExitCode = GetLastError(); + } + +#ifndef STANDALONE + ASSERT(ComSpecInfo); + ComSpecInfo->Terminated = TRUE; + ComSpecInfo->dwExitCode = dwExitCode; + + /* Decrement the VDM re-entry count */ + CommandInfo.VDMState = VDM_DEC_REENTER_COUNT; + DPRINT1("Calling GetNextVDMCommand reenter--\n"); + Success = GetNextVDMCommand(&CommandInfo); + DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success ? "true" : "false", GetLastError()); + --ReentrancyCount; + + return 0; +#else + return dwExitCode; +#endif +} + +DWORD DosStartProcess32(IN LPCSTR ExecutablePath, + IN LPCSTR CommandLine, + IN LPCSTR Environment OPTIONAL, + IN DWORD ReturnAddress OPTIONAL, + IN BOOLEAN StartComSpec) +{ + DWORD Result = ERROR_SUCCESS; + HANDLE CommandThread; + DOS_START_PROC32 DosStartProc32; +#ifndef STANDALONE + BOOL Success; + VDM_COMMAND_INFO CommandInfo; +#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) + { + // + // 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) + { + DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result); + goto Quit; + } + } + 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"); + + /* Check whether this was our 32-bit app which was killed */ + if (!DosStartProc32.ComSpecInfo->Terminated) + { + DPRINT1("Not our 32-bit app, retrying...\n"); + goto Retry; + } + + Result = DosStartProc32.ComSpecInfo->dwExitCode; + + /* Delete the entry */ + RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32.ComSpecInfo); + } +#else + /* Wait for the thread to finish */ + WaitForSingleObject(CommandThread, INFINITE); + GetExitCodeThread(CommandThread, &Result); + + /* Close the thread handle */ + CloseHandle(CommandThread); + + DPRINT1("32-bit app stopped\n"); +#endif + +Quit: +#ifndef STANDALONE + CloseHandle(DosStartProc32.hEvent); +#endif + + /* Attach to the console and resume the VM */ + DosProcessConsoleAttach(); + EmulatorResume(); + + return Result; +} + + + + +/******************************************************************************\ +|** DOS Bootloader emulation, Startup and Shutdown **| +\******************************************************************************/ + // // This function (equivalent of the DOS bootsector) is called by the bootstrap @@ -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; - } - - /* Start the process from the command line */ - Result = DosStartProcess(ApplicationName, CommandLine, - SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0), - MAKELONG(getIP(), getCS())); - if (Result != ERROR_SUCCESS) - { - DisplayMessage(L"Could not start '%S'. Error: %u", ApplicationName, Result); - goto Quit; + DosDisplayMessage("Invalid DOS command line\n"); + EmulatorTerminate(); + return; } #endif -Quit: - /* Stop the VDM */ - EmulatorTerminate(); + /* + * 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) + { + /* 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 + + /**/ + /* 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 diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dem.h b/reactos/subsystems/mvdm/ntvdm/dos/dem.h index 868e3ea1b78..d3df779d7d8 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dem.h +++ b/reactos/subsystems/mvdm/ntvdm/dos/dem.h @@ -27,14 +27,30 @@ /* 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 diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/bios.c b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/bios.c index bbe3a242c29..9442d53c790 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/bios.c +++ b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/bios.c @@ -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 */ diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.c b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.c index d3ff081e631..f8ded908813 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.c +++ b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.c @@ -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 */ diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.h b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.h index f3208284806..db548e91346 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.h +++ b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/dos.h @@ -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_ diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.c b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.c index 22f26af7ffb..7dc8277dbe8 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.c +++ b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.c @@ -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); + } + + /* + * 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); + } + + break; } } - RtlFreeHeap(RtlGetProcessHeap(), 0, Env); - - /* Close the handles */ - CloseHandle(ProcessInfo.hProcess); - CloseHandle(ProcessInfo.hThread); - - 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)); diff --git a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.h b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.h index 7bb675ca9b1..b11b1b3c77e 100644 --- a/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.h +++ b/reactos/subsystems/mvdm/ntvdm/dos/dos32krnl/process.h @@ -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, diff --git a/reactos/subsystems/mvdm/ntvdm/emulator.c b/reactos/subsystems/mvdm/ntvdm/emulator.c index 481afe1271f..4aca4a73a46 100644 --- a/reactos/subsystems/mvdm/ntvdm/emulator.c +++ b/reactos/subsystems/mvdm/ntvdm/emulator.c @@ -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; - - default: - break; + DPRINT1("Got NumRecords == 0!\n"); + continue; } + + /* 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(); diff --git a/reactos/subsystems/mvdm/ntvdm/emulator.h b/reactos/subsystems/mvdm/ntvdm/emulator.h index 3f9e08a6f94..2e0d4b7402b 100644 --- a/reactos/subsystems/mvdm/ntvdm/emulator.h +++ b/reactos/subsystems/mvdm/ntvdm/emulator.h @@ -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); diff --git a/reactos/subsystems/mvdm/ntvdm/ntvdm.c b/reactos/subsystems/mvdm/ntvdm/ntvdm.c index 84bf6feb349..dc1a6f35b1b 100644 --- a/reactos/subsystems/mvdm/ntvdm/ntvdm.c +++ b/reactos/subsystems/mvdm/ntvdm/ntvdm.c @@ -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; } diff --git a/reactos/subsystems/mvdm/ntvdm/ntvdm.h b/reactos/subsystems/mvdm/ntvdm/ntvdm.h index 825971ef5a9..f4d10a606d3 100644 --- a/reactos/subsystems/mvdm/ntvdm/ntvdm.h +++ b/reactos/subsystems/mvdm/ntvdm/ntvdm.h @@ -69,8 +69,6 @@ DWORD WINAPI SetLastConsoleEventActive(VOID); /* FUNCTIONS ******************************************************************/ -extern HANDLE VdmTaskEvent; - // Command line of NTVDM extern INT NtVdmArgc; extern WCHAR** NtVdmArgv;