#define NDEBUG
-#include "dos.h"
-#include "bios.h"
#include "emulator.h"
+#include "dos.h"
+#include "bios.h"
+#include "bop.h"
+#include "int32.h"
#include "registers.h"
/* PRIVATE VARIABLES **********************************************************/
static BOOLEAN DosUmbLinked = FALSE;
static WORD DosErrorLevel = 0x0000;
+/* BOP Identifiers */
+#define BOP_DOS 0x50 // DOS System BOP (for NTIO.SYS and NTDOS.SYS)
+#define BOP_CMD 0x54 // DOS Command Interpreter BOP (for COMMAND.COM)
+
/* PRIVATE FUNCTIONS **********************************************************/
/* Taken from base/shell/cmd/console.c */
LPBYTE Address = NULL;
LPSTR ProgramFilePath, Parameters[256];
CHAR CommandLineCopy[DOS_CMDLINE_LENGTH];
+ CHAR ParamString[DOS_CMDLINE_LENGTH];
INT ParamCount = 0;
WORD Segment = 0;
WORD MaxAllocSize;
/* Save a copy of the command line */
strcpy(CommandLineCopy, CommandLine);
- // FIXME: Improve parsing (especially: "some_path\with spaces\program.exe" options)
-
/* Get the file name of the executable */
ProgramFilePath = strtok(CommandLineCopy, " \t");
ParamCount++;
}
+ ZeroMemory(ParamString, sizeof(ParamString));
+
+ /* Store the parameters in a string */
+ for (i = 0; i < ParamCount; i++)
+ {
+ strncat(ParamString, Parameters[i], DOS_CMDLINE_LENGTH - strlen(ParamString) - 1);
+ strncat(ParamString, " ", DOS_CMDLINE_LENGTH - strlen(ParamString) - 1);
+ }
+
/* Open a handle to the executable */
FileHandle = CreateFileA(ProgramFilePath,
GENERIC_READ,
/* Initialize the PSP */
DosInitializePsp(Segment,
- CommandLine,
+ ParamString,
(WORD)ExeSize,
EnvBlock);
/* Initialize the PSP */
DosInitializePsp(Segment,
- CommandLine,
- (WORD)((FileSize + sizeof(DOS_PSP)) >> 4),
+ ParamString,
+ MaxAllocSize,
EnvBlock);
/* Set the initial segment registers */
/* Set the stack to the last word of the segment */
EmulatorSetStack(Segment, 0xFFFE);
+ /*
+ * Set the value on the stack to 0, so that a near return
+ * jumps to PSP:0000 which has the exit code.
+ */
+ *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0;
+
/* Execute */
CurrentPsp = Segment;
DiskTransferArea = MAKELONG(0x80, Segment);
{
WORD InfoWord = 0;
+ /*
+ * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
+ * for a list of possible flags.
+ */
+
if (Handle == DosSystemFileTable[0])
{
/* Console input */
InfoWord |= 1 << 1;
}
- /* It is a character device */
+ /* It is a device */
InfoWord |= 1 << 7;
/* Return the device information word */
}
}
-VOID DosInt20h(LPWORD Stack)
+VOID WINAPI DosSystemBop(LPWORD Stack)
+{
+ /* Get the Function Number and skip it */
+ BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
+ setIP(getIP() + 1);
+
+ DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum);
+}
+
+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)
+ {
+ case 0x08: // Launch external command
+ {
+#define CMDLINE_LENGTH 1024
+
+ BOOL Result;
+ DWORD dwExitCode;
+
+ LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
+ CHAR CommandLine[CMDLINE_LENGTH] = "";
+ STARTUPINFOA StartupInfo;
+ PROCESS_INFORMATION ProcessInformation;
+ DPRINT1("CMD Run Command '%s'\n", Command);
+
+ Command[strlen(Command)-1] = 0;
+
+ strcpy(CommandLine, "cmd.exe /c ");
+ strcat(CommandLine, Command);
+
+ ZeroMemory(&StartupInfo, sizeof(StartupInfo));
+ ZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
+
+ StartupInfo.cb = sizeof(StartupInfo);
+
+ DosPrintCharacter('\n');
+
+ Result = CreateProcessA(NULL,
+ CommandLine,
+ NULL,
+ NULL,
+ TRUE,
+ 0,
+ NULL,
+ NULL,
+ &StartupInfo,
+ &ProcessInformation);
+ if (Result)
+ {
+ DPRINT1("Command '%s' launched successfully\n");
+
+ /* Wait for process termination */
+ WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
+
+ /* Get the exit code */
+ GetExitCodeProcess(ProcessInformation.hProcess, &dwExitCode);
+
+ /* Close handles */
+ CloseHandle(ProcessInformation.hThread);
+ CloseHandle(ProcessInformation.hProcess);
+ }
+ else
+ {
+ DPRINT1("Failed when launched command '%s'\n");
+ dwExitCode = GetLastError();
+ }
+
+ DosPrintCharacter('\n');
+
+ setAL((UCHAR)dwExitCode);
+
+ break;
+ }
+
+ default:
+ {
+ DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum);
+ // setCF(1); // Disable, otherwise we enter an infinite loop
+ break;
+ }
+ }
+}
+
+VOID WINAPI DosInt20h(LPWORD Stack)
{
/* This is the exit interrupt */
DosTerminateProcess(Stack[STACK_CS], 0);
}
-VOID DosInt21h(LPWORD Stack)
+VOID WINAPI DosInt21h(LPWORD Stack)
{
BYTE Character;
SYSTEMTIME SystemTime;
PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
/*
+ * DOS 2+ - GET DOS VERSION
* See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
* for more information.
*/
setBL(0x00);
setCX(0x0000);
- /* Return DOS version: Minor:Major in AH:AL */
+ /*
+ * Return DOS version: Minor:Major in AH:AL
+ * The Windows NT DOS box returns version 5.00, subject to SETVER.
+ */
setAX(PspBlock->DosVersion);
break;
}
+ /* Extended functionalities */
+ case 0x33:
+ {
+ if (getAL() == 0x06)
+ {
+ /*
+ * DOS 5+ - GET TRUE VERSION NUMBER
+ * This function always returns the true version number, unlike
+ * AH=30h, whose return value may be changed with SETVER.
+ * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
+ * for more information.
+ */
+
+ /*
+ * Return the true DOS version: Minor:Major in BH:BL
+ * The Windows NT DOS box returns BX=3205h (version 5.50).
+ */
+ setBX(NTDOS_VERSION);
+
+ /* DOS revision 0 */
+ setDL(0x00);
+
+ /* Unpatched DOS */
+ setDH(0x00);
+ }
+ // else
+ // {
+ // /* Invalid subfunction */
+ // setAL(0xFF);
+ // }
+
+ break;
+ }
+
/* Get Interrupt Vector */
case 0x35:
{
}
else
{
+ /* Invalid subfunction */
setAL(0xFF);
}
- break;
+ break;
}
/* Create Directory */
WORD Count = getCX();
WORD BytesRead = 0;
WORD ErrorCode = ERROR_SUCCESS;
+ CHAR Character;
if (IsConsoleHandle(DosGetRealHandle(Handle)))
{
while (Stack[STACK_COUNTER] < Count)
{
/* Read a character from the BIOS */
- // FIXME: Security checks!
- Buffer[Stack[STACK_COUNTER]] = LOBYTE(BiosGetCharacter());
+ Character = LOBYTE(BiosGetCharacter());
/* Stop if the BOP needs to be repeated */
if (getCF()) break;
- /* Increment the counter */
- Stack[STACK_COUNTER]++;
+ // FIXME: Security checks!
+ DosPrintCharacter(Character);
+ Buffer[Stack[STACK_COUNTER]++] = Character;
+
+ if (Character == '\r')
+ {
+ /* Stop on first carriage return */
+ DosPrintCharacter('\n');
+ break;
+ }
}
- if (Stack[STACK_COUNTER] < Count)
- ErrorCode = ERROR_NOT_READY;
- else
- BytesRead = Count;
+ if (Character != '\r')
+ {
+ if (Stack[STACK_COUNTER] < Count) ErrorCode = ERROR_NOT_READY;
+ else BytesRead = Count;
+ }
+ else BytesRead = Stack[STACK_COUNTER];
}
else
{
/* Internal - Get Current Process ID (Get PSP Address) */
case 0x51:
+ /* Get Current PSP Address */
+ case 0x62:
{
/*
- * Identical to the documented AH=62h.
+ * Undocumented AH=51h is identical to the documented AH=62h.
* See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
+ * and http://www.ctyme.com/intr/rb-3140.htm
* for more information.
*/
setBX(CurrentPsp);
break;
}
- /* Get Current PSP Address */
- case 0x62:
- {
- /*
- * Identical to the undocumented AH=51h.
- * See Ralf Brown: http://www.ctyme.com/intr/rb-3140.htm
- * for more information.
- */
- setAH(0x51); // Call the internal function.
- /*
- * Instead of calling ourselves really recursively as in:
- * DosInt21h(Stack);
- * prefer resetting the CF flag to let the BOP repeat.
- */
- setCF(1);
- break;
- }
-
/* Unsupported */
default:
{
DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
getAH(), getAL());
+
+ setAL(0); // Some functions expect AL to be 0 when it's not supported.
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
}
}
}
-VOID DosBreakInterrupt(LPWORD Stack)
+VOID WINAPI DosBreakInterrupt(LPWORD Stack)
{
UNREFERENCED_PARAMETER(Stack);
+ /* Stop the VDM */
VdmRunning = FALSE;
}
+VOID WINAPI DosFastConOut(LPWORD Stack)
+{
+ /*
+ * This is the DOS 2+ Fast Console Output Interrupt.
+ * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
+ * for more information.
+ */
+ UNREFERENCED_PARAMETER(Stack);
+
+ /*
+ * The default handler under DOS 2.x and 3.x simply calls INT 10/AH=0Eh.
+ * Do better and call directly BiosPrintCharacter: it's what INT 10/AH=0Eh
+ * does. Otherwise we would have to set BL to DOS_CHAR_ATTRIBUTE and
+ * BH to Bda->VideoPage.
+ */
+ BiosPrintCharacter(getAL(), DOS_CHAR_ATTRIBUTE, Bda->VideoPage);
+}
+
+VOID WINAPI DosInt2Fh(LPWORD Stack)
+{
+ DPRINT1("DOS System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
+ getAH(), getAL());
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+}
+
BOOLEAN DosInitialize(VOID)
{
BYTE i;
DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE);
DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE);
+ /* Register the DOS BOPs */
+ RegisterBop(BOP_DOS, DosSystemBop );
+ RegisterBop(BOP_CMD, DosCmdInterpreterBop);
+
+ /* Register the DOS 32-bit Interrupts */
+ RegisterInt32(0x20, DosInt20h );
+ RegisterInt32(0x21, DosInt21h );
+// RegisterInt32(0x22, DosInt22h ); // Termination
+ RegisterInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
+// RegisterInt32(0x24, DosInt24h ); // Critical Error
+ RegisterInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
+ RegisterInt32(0x2F, DosInt2Fh );
+
return TRUE;
}