- 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
This commit is contained in:
Hermès Bélusca-Maïto 2015-09-12 20:09:25 +00:00
parent 4ad08f6ee4
commit 1c51b39201
16 changed files with 1922 additions and 548 deletions

View 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)

View file

@ -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)

View file

@ -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__ */

View file

@ -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 */

View 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

File diff suppressed because it is too large Load diff

View file

@ -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

View 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 */

View 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 */

View 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_

View 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);
}
/*
* 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));

View 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,

View 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;
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();

View 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);

View 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;
}

View file

@ -69,8 +69,6 @@ DWORD WINAPI SetLastConsoleEventActive(VOID);
/* FUNCTIONS ******************************************************************/
extern HANDLE VdmTaskEvent;
// Command line of NTVDM
extern INT NtVdmArgc;
extern WCHAR** NtVdmArgv;