mirror of
https://github.com/reactos/reactos.git
synced 2024-09-11 05:12:43 +00:00
1529 lines
44 KiB
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 */
|