reactos/subsystems/mvdm/ntvdm/dos/dem.c

1529 lines
44 KiB
C

/*
* COPYRIGHT: GPL - See COPYING in the top level directory
* PROJECT: ReactOS Virtual DOS Machine
* FILE: subsystems/mvdm/ntvdm/dos/dem.c
* PURPOSE: DOS 32-bit Emulation Support Library -
* This library is used by the built-in NTVDM DOS32 and by
* the NT 16-bit DOS in Windows (via BOPs). It also exposes
* exported functions that can be used by VDDs.
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
* Hermes Belusca-Maito (hermes.belusca@sfr.fr)
*/
/* INCLUDES *******************************************************************/
#include "ntvdm.h"
#define NDEBUG
#include <debug.h>
#include "emulator.h"
#include <isvbop.h>
#include "utils.h"
#include "dem.h"
#include "dos/dos32krnl/device.h"
#include "dos/dos32krnl/memory.h"
#include "dos/dos32krnl/process.h"
#include "cpu/bop.h"
#include "cpu/cpu.h"
#include "bios/bios.h"
#include "mouse32.h"
#include "vddsup.h"
/*
* EXPERIMENTAL!
* Activate this line if you want to have COMMAND.COM completely external.
*/
// #define COMSPEC_FULLY_EXTERNAL
/* PRIVATE VARIABLES **********************************************************/
/* PRIVATE FUNCTIONS **********************************************************/
/* PUBLIC VARIABLES ***********************************************************/
/* PUBLIC FUNCTIONS ***********************************************************/
/******************************************************************************\
|** DOS DEM Kernel helpers **|
\******************************************************************************/
VOID Dem_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);
}
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 */
BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
setIP(getIP() + 1);
switch (FuncNum)
{
/* Load the DOS kernel */
case 0x11:
{
DemLoadNTDOSKernel();
break;
}
/* Call 32-bit Driver Strategy Routine */
case BOP_DRV_STRATEGY:
{
DeviceStrategyBop();
break;
}
/* Call 32-bit Driver Interrupt Routine */
case BOP_DRV_INTERRUPT:
{
DeviceInterruptBop();
break;
}
default:
{
DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum);
// setCF(1); // Disable, otherwise we enter an infinite loop
break;
}
}
}
/******************************************************************************\
|** 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
/* Compute the command line length, not counting the terminating "\r\n" */
CmdLen = strlen(CmdLine);
if (CmdLen >= 2 && CmdLine[CmdLen - 2] == '\r')
CmdLen -= 2;
DPRINT1("Starting '%s' ('%.*s')...\n", AppName, 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)CmdLen;
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)!\n");
setCF(0);
goto Quit;
#ifndef STANDALONE
}
/* Clear the VDM structure */
RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
Retry:
/* Update the VDM state of the task */
// CommandInfo.TaskId = SessionId;
CommandInfo.ExitCode = getDX();
CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
DPRINT1("Calling GetNextVDMCommand 32bit end of VDM task\n");
Success = GetNextVDMCommand(&CommandInfo);
DPRINT1("GetNextVDMCommand 32bit end of VDM task success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
/*
* Check whether we were awaited because the 32-bit process was stopped,
* or because it started a new DOS application.
*/
if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
{
DPRINT1("GetNextVDMCommand end-of-app, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
CommandInfo.CmdLen, CommandInfo.AppLen, CommandInfo.PifLen);
/* Repeat the request */
Repeat = TRUE;
setCF(1);
}
else
{
DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
/* Check whether we need to shell out now in case we were started by a 32-bit app */
ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
if (!ComSpecInfo || !ComSpecInfo->Terminated)
{
DPRINT1("Not our 32-bit app, retrying...\n");
goto Retry;
}
ASSERT(ComSpecInfo->Terminated == TRUE);
/* Record found, remove it and exit now */
RemoveComSpecInfo(ComSpecInfo);
DPRINT1("Exit DOS from ExitCode wait!\n");
setCF(0);
}
#endif
// FIXME: Use the retrieved exit code as the value of our exit code
// when COMMAND.COM will shell-out ??
Quit:
/* Resume the VM */
EmulatorResume();
}
static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
{
/* Get the Function Number and skip it */
BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
setIP(getIP() + 1);
switch (FuncNum)
{
/* Kill the VDM */
case 0x00:
{
/* Stop the VDM */
EmulatorTerminate();
return;
}
/*
* Get a new app to start
*
* Input
* DS:DX : Data block.
*
* Output
* CF : 0: Success; 1: Failure.
*/
case 0x01:
{
CmdStartProcess();
break;
}
/*
* 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());
if (!GetBinaryTypeA(ProgramName, &BinaryType))
{
/* An error happened, bail out */
setCF(1);
setAX(LOWORD(GetLastError()));
break;
}
// 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;
}
/* 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;
}
default:
{
DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum);
// setCF(1); // Disable, otherwise we enter an infinite loop
break;
}
}
}
#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)
{
BOOL Success;
PROCESS_INFORMATION ProcessInfo;
STARTUPINFOA StartupInfo;
DWORD dwExitCode;
PDOS_START_PROC32 DosStartProc32 = (PDOS_START_PROC32)Parameter;
#ifndef STANDALONE
VDM_COMMAND_INFO CommandInfo;
PCOMSPEC_INFO ComSpecInfo = DosStartProc32->ComSpecInfo;
#endif
/* Set up the VDM, startup and process info structures */
#ifndef STANDALONE
RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
#endif
RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
StartupInfo.cb = sizeof(StartupInfo);
// FIXME: Build suitable 32-bit environment!!
#ifndef STANDALONE
/*
* Wait for signaling a new VDM task and increment the VDM re-entry count so
* that we can handle 16-bit apps that may be possibly started by the 32-bit app.
*/
CommandInfo.VDMState = VDM_INC_REENTER_COUNT;
DPRINT1("Calling GetNextVDMCommand reenter++\n");
Success = GetNextVDMCommand(&CommandInfo);
DPRINT1("GetNextVDMCommand reenter++ success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
++ReentrancyCount;
#endif
/* Start the process */
Success = CreateProcessA(NULL, // ProgramName,
DosStartProc32->CommandLine,
NULL,
NULL,
TRUE, // Inherit handles
CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED,
DosStartProc32->Environment,
NULL, // lpCurrentDirectory, see "START" command in cmd.exe
&StartupInfo,
&ProcessInfo);
#ifndef STANDALONE
/* Signal our caller the process was started */
SetEvent(DosStartProc32->hEvent);
// After this point, 'DosStartProc32' is not valid anymore.
#endif
if (Success)
{
/* Resume the process */
ResumeThread(ProcessInfo.hThread);
/* Wait for the process to finish running and retrieve its exit code */
WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
/* Close the handles */
CloseHandle(ProcessInfo.hThread);
CloseHandle(ProcessInfo.hProcess);
}
else
{
dwExitCode = GetLastError();
}
#ifndef STANDALONE
ASSERT(ComSpecInfo);
ComSpecInfo->Terminated = TRUE;
ComSpecInfo->dwExitCode = dwExitCode;
/* Decrement the VDM re-entry count */
CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
DPRINT1("Calling GetNextVDMCommand reenter--\n");
Success = GetNextVDMCommand(&CommandInfo);
DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success ? "true" : "false", GetLastError());
--ReentrancyCount;
return 0;
#else
return dwExitCode;
#endif
}
DWORD DosStartProcess32(IN LPCSTR ExecutablePath,
IN LPCSTR CommandLine,
IN LPCSTR Environment OPTIONAL,
IN DWORD ReturnAddress OPTIONAL,
IN BOOLEAN StartComSpec)
{
DWORD Result = ERROR_SUCCESS;
HANDLE CommandThread;
DOS_START_PROC32 DosStartProc32;
#ifndef STANDALONE
BOOL Success;
VDM_COMMAND_INFO CommandInfo;
#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 = CreateEventW(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
// loader *BEFORE* jumping at 0000:7C00. What we should do is to write at 0000:7C00
// a BOP call that calls DosInitialize back. Then the bootstrap loader jumps at
// 0000:7C00, our BOP gets called and then we can initialize the 32-bit part of the DOS.
//
/* 16-bit bootstrap code at 0000:7C00 */
/* Of course, this is not in real bootsector format, because we don't care about it for now */
static BYTE Bootsector1[] =
{
LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_LOAD_DOS
};
/* This portion of code is run if we failed to load the DOS */
// NOTE: This may also be done by the BIOS32.
static BYTE Bootsector2[] =
{
LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
};
static VOID WINAPI DosInitialize(LPWORD Stack);
VOID DosBootsectorInitialize(VOID)
{
/* We write the bootsector at 0000:7C00 */
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");
/* Write the "bootsector" */
RtlCopyMemory((PVOID)Address, Bootsector1, sizeof(Bootsector1));
Address += sizeof(Bootsector1);
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);
}
//
// This function is called by the DOS bootsector in case we load DOS32.
// It sets up the DOS32 start code then jumps to 0070:0000.
//
/* 16-bit startup code for DOS32 at 0070:0000 */
static BYTE Startup[] =
{
LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_START_DOS,
LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
};
static VOID WINAPI DosStart(LPWORD Stack);
static VOID WINAPI DosInitialize(LPWORD Stack)
{
/* Get the DOS BIOS file name (NULL-terminated) */
// FIXME: Isn't it possible to use some DS:SI instead??
LPCSTR DosBiosFileName = (LPCSTR)SEG_OFF_TO_PTR(getCS(), getIP());
setIP(getIP() + strlen(DosBiosFileName) + 1); // Skip it
DPRINT("DosInitialize('%s')\n", DosBiosFileName);
/*
* We succeeded, deregister the DOS Loading BOP
* so that no app will be able to call us back.
*/
RegisterBop(BOP_LOAD_DOS, NULL);
/* Register the DOS BOPs */
RegisterBop(BOP_DOS, DosSystemBop );
RegisterBop(BOP_CMD, DosCmdInterpreterBop);
if (DosBiosFileName[0] != '\0')
{
BOOLEAN Success = FALSE;
HANDLE hDosBios;
ULONG ulDosBiosSize = 0;
/* Open the DOS BIOS file */
hDosBios = FileOpen(DosBiosFileName, &ulDosBiosSize);
if (hDosBios == NULL) goto Quit;
/* Attempt to load the DOS BIOS into memory */
Success = FileLoadByHandle(hDosBios,
REAL_TO_PHYS(TO_LINEAR(0x0070, 0x0000)),
ulDosBiosSize,
&ulDosBiosSize);
DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
DosBiosFileName,
(Success ? "succeeded" : "failed"),
0x0070, 0x0000,
ulDosBiosSize,
GetLastError());
/* Close the DOS BIOS file */
FileClose(hDosBios);
Quit:
if (!Success)
{
BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
DosBiosFileName, GetLastError());
return;
}
}
else
{
/* Load the 16-bit startup code for DOS32 and register its Starting BOP */
RtlCopyMemory(SEG_OFF_TO_PTR(0x0070, 0x0000), Startup, sizeof(Startup));
// This is the equivalent of BOP_LOAD_DOS, function 0x11 "Load the DOS kernel"
// for the Windows NT DOS.
RegisterBop(BOP_START_DOS, DosStart);
}
/* Position execution pointers for DOS startup and return */
setCS(0x0070);
setIP(0x0000);
}
static VOID WINAPI DosStart(LPWORD Stack)
{
BOOLEAN Success;
DWORD Result;
#ifndef STANDALONE
INT i;
#endif
DPRINT("DosStart\n");
/*
* We succeeded, deregister the DOS Starting BOP
* so that no app will be able to call us back.
*/
RegisterBop(BOP_START_DOS, NULL);
/* Initialize the callback context */
InitializeContext(&DosContext, BIOS_CODE_SEGMENT, 0x0010);
Success = DosBIOSInitialize();
// Success &= DosKRNLInitialize();
if (!Success)
{
BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
EmulatorTerminate();
return;
}
/* Load the mouse driver */
DosMouseInitialize();
#ifndef STANDALONE
/* Parse the command line arguments */
for (i = 1; i < NtVdmArgc; i++)
{
if (wcsncmp(NtVdmArgv[i], L"-i", 2) == 0)
{
/* This is the session ID (hex format) */
SessionId = wcstoul(NtVdmArgv[i] + 2, NULL, 16);
}
}
/* Initialize Win32-VDM environment */
Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
if (Env == NULL)
{
DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
EmulatorTerminate();
return;
}
/* Clear the structure */
RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
/* Get the initial information */
CommandInfo.TaskId = SessionId;
CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS;
GetNextVDMCommand(&CommandInfo);
#else
/* Retrieve the command to start */
if (NtVdmArgc >= 2)
{
WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, AppName, sizeof(AppName), NULL, NULL);
if (NtVdmArgc >= 3)
WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CmdLine, sizeof(CmdLine), NULL, NULL);
else
strcpy(CmdLine, "");
}
else
{
DosDisplayMessage("Invalid DOS command line\n");
EmulatorTerminate();
return;
}
#endif
/*
* At this point, CS:IP points to the DOS BIOS exit code. If the
* root command interpreter fails to start (or if it exits), DOS
* exits and the VDM terminates.
*/
/* Start the root command interpreter */
// TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
/*
* Prepare the stack for DosStartComSpec:
* push Flags, CS and IP, and an extra WORD.
*/
setSP(getSP() - sizeof(WORD));
*((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD)getEFLAGS();
setSP(getSP() - sizeof(WORD));
*((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS();
setSP(getSP() - sizeof(WORD));
*((LPWORD)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP();
setSP(getSP() - sizeof(WORD));
Result = DosStartComSpec(TRUE, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0),
MAKELONG(getIP(), getCS()),
#ifndef STANDALONE
&RootCmd.ComSpecPsp
#else
NULL
#endif
);
if (Result != ERROR_SUCCESS)
{
/* 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
// demLFNGetCurrentDirectory
// demGetFileTimeByHandle_WOW
// demWOWLFNAllocateSearchHandle
// demWOWLFNCloseSearchHandle
// demWOWLFNEntry
// demWOWLFNGetSearchHandle
// demWOWLFNInit
DWORD
WINAPI
demClientErrorEx(IN HANDLE FileHandle,
IN CHAR Unknown,
IN BOOL Flag)
{
UNIMPLEMENTED;
return GetLastError();
}
DWORD
WINAPI
demFileDelete(IN LPCSTR FileName)
{
if (DeleteFileA(FileName)) SetLastError(ERROR_SUCCESS);
return GetLastError();
}
DWORD
WINAPI
demFileFindFirst(OUT PVOID lpFindFileData,
IN LPCSTR FileName,
IN WORD AttribMask)
{
BOOLEAN Success = TRUE;
WIN32_FIND_DATAA FindData;
HANDLE SearchHandle;
PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
/* Start a search */
SearchHandle = FindFirstFileA(FileName, &FindData);
if (SearchHandle == INVALID_HANDLE_VALUE) return GetLastError();
do
{
/* Check the attributes and retry as long as we haven't found a matching file */
if (!((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_DIRECTORY))
& ~AttribMask))
{
break;
}
}
while ((Success = FindNextFileA(SearchHandle, &FindData)));
/* If we failed at some point, close the search and return an error */
if (!Success)
{
FindClose(SearchHandle);
return GetLastError();
}
/* Fill the block */
FindFileBlock->DriveLetter = DosData->Sda.CurrentDrive + 'A';
FindFileBlock->AttribMask = AttribMask;
FindFileBlock->SearchHandle = SearchHandle;
FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
FileTimeToDosDateTime(&FindData.ftLastWriteTime,
&FindFileBlock->FileDate,
&FindFileBlock->FileTime);
FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
: FindData.nFileSizeLow;
/* Build a short path name */
if (*FindData.cAlternateFileName)
strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
else
GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
return ERROR_SUCCESS;
}
DWORD
WINAPI
demFileFindNext(OUT PVOID lpFindFileData)
{
WIN32_FIND_DATAA FindData;
PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
do
{
/* Continue searching as long as we haven't found a matching file */
/* If we failed at some point, close the search and return an error */
if (!FindNextFileA(FindFileBlock->SearchHandle, &FindData))
{
FindClose(FindFileBlock->SearchHandle);
return GetLastError();
}
}
while ((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
FILE_ATTRIBUTE_SYSTEM |
FILE_ATTRIBUTE_DIRECTORY))
& ~FindFileBlock->AttribMask);
/* Update the block */
FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
FileTimeToDosDateTime(&FindData.ftLastWriteTime,
&FindFileBlock->FileDate,
&FindFileBlock->FileTime);
FindFileBlock->FileSize = FindData.nFileSizeHigh ? 0xFFFFFFFF
: FindData.nFileSizeLow;
/* Build a short path name */
if (*FindData.cAlternateFileName)
strncpy(FindFileBlock->FileName, FindData.cAlternateFileName, sizeof(FindFileBlock->FileName));
else
GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
return ERROR_SUCCESS;
}
UCHAR
WINAPI
demGetPhysicalDriveType(IN UCHAR DriveNumber)
{
UNIMPLEMENTED;
return DOSDEVICE_DRIVE_UNKNOWN;
}
BOOL
WINAPI
demIsShortPathName(IN LPCSTR Path,
IN BOOL Unknown)
{
UNIMPLEMENTED;
return FALSE;
}
DWORD
WINAPI
demSetCurrentDirectoryGetDrive(IN LPCSTR CurrentDirectory,
OUT PUCHAR DriveNumber)
{
UNIMPLEMENTED;
return ERROR_SUCCESS;
}
/* EOF */