mirror of
https://github.com/reactos/reactos.git
synced 2025-02-24 09:25:10 +00:00
2418 lines
71 KiB
C
2418 lines
71 KiB
C
/*
|
|
* COPYRIGHT: GPL - See COPYING in the top level directory
|
|
* PROJECT: ReactOS Virtual DOS Machine
|
|
* FILE: subsystems/mvdm/ntvdm/dos/dos32krnl/dos.c
|
|
* PURPOSE: DOS32 Kernel
|
|
* 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 "cpu/cpu.h"
|
|
#include "int32.h"
|
|
|
|
#include "dos.h"
|
|
#include "dos/dem.h"
|
|
#include "country.h"
|
|
#include "device.h"
|
|
#include "handle.h"
|
|
#include "dosfiles.h"
|
|
#include "memory.h"
|
|
#include "process.h"
|
|
#include "himem.h"
|
|
|
|
#include "bios/bios.h"
|
|
|
|
#include "io.h"
|
|
#include "hardware/ps2.h"
|
|
|
|
#include "emsdrv.h"
|
|
|
|
/* PRIVATE VARIABLES **********************************************************/
|
|
|
|
CALLBACK16 DosContext;
|
|
|
|
/* PUBLIC VARIABLES ***********************************************************/
|
|
|
|
/* Global DOS data area contained in guest memory */
|
|
PDOS_DATA DosData;
|
|
/* Easy accessors to useful DOS data area parts */
|
|
PDOS_SYSVARS SysVars;
|
|
PDOS_SDA Sda;
|
|
|
|
/* PRIVATE FUNCTIONS **********************************************************/
|
|
|
|
static BOOLEAN DosChangeDrive(BYTE Drive)
|
|
{
|
|
CHAR DirectoryPath[DOS_CMDLINE_LENGTH + 1];
|
|
|
|
/* Make sure the drive exists */
|
|
if (Drive >= SysVars->NumLocalDrives) return FALSE;
|
|
|
|
RtlZeroMemory(DirectoryPath, sizeof(DirectoryPath));
|
|
|
|
/* Find the path to the new current directory */
|
|
snprintf(DirectoryPath,
|
|
DOS_CMDLINE_LENGTH,
|
|
"%c:\\%s",
|
|
'A' + Drive,
|
|
DosData->CurrentDirectories[Drive]);
|
|
|
|
/* Change the current directory of the process */
|
|
if (!SetCurrentDirectoryA(DirectoryPath)) return FALSE;
|
|
|
|
/* Set the current drive */
|
|
Sda->CurrentDrive = Drive;
|
|
|
|
/* Return success */
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOLEAN DosChangeDirectory(LPSTR Directory)
|
|
{
|
|
BYTE DriveNumber;
|
|
DWORD Attributes;
|
|
LPSTR Path;
|
|
CHAR CurrentDirectory[MAX_PATH];
|
|
CHAR DosDirectory[DOS_DIR_LENGTH];
|
|
|
|
/* Make sure the directory path is not too long */
|
|
if (strlen(Directory) >= DOS_DIR_LENGTH)
|
|
{
|
|
Sda->LastErrorCode = ERROR_PATH_NOT_FOUND;
|
|
return FALSE;
|
|
}
|
|
|
|
/* Check whether the directory string is of format "X:..." */
|
|
if (strlen(Directory) >= 2 && Directory[1] == ':')
|
|
{
|
|
/* Get the drive number */
|
|
DriveNumber = RtlUpperChar(Directory[0]) - 'A';
|
|
|
|
/* Make sure the drive exists */
|
|
if (DriveNumber >= SysVars->NumLocalDrives)
|
|
{
|
|
Sda->LastErrorCode = ERROR_PATH_NOT_FOUND;
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Keep the current drive number */
|
|
DriveNumber = Sda->CurrentDrive;
|
|
}
|
|
|
|
/* Get the file attributes */
|
|
Attributes = GetFileAttributesA(Directory);
|
|
|
|
/* Make sure the path exists and is a directory */
|
|
if ((Attributes == INVALID_FILE_ATTRIBUTES) ||
|
|
!(Attributes & FILE_ATTRIBUTE_DIRECTORY))
|
|
{
|
|
Sda->LastErrorCode = ERROR_PATH_NOT_FOUND;
|
|
return FALSE;
|
|
}
|
|
|
|
/* Check if this is the current drive */
|
|
if (DriveNumber == Sda->CurrentDrive)
|
|
{
|
|
/* Change the directory */
|
|
if (!SetCurrentDirectoryA(Directory))
|
|
{
|
|
Sda->LastErrorCode = LOWORD(GetLastError());
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/* Get the (possibly new) current directory (needed if we specified a relative directory) */
|
|
if (!GetCurrentDirectoryA(sizeof(CurrentDirectory), CurrentDirectory))
|
|
{
|
|
// TODO: Use some kind of default path?
|
|
return FALSE;
|
|
}
|
|
|
|
/* Convert it to a DOS path */
|
|
if (!GetShortPathNameA(CurrentDirectory, DosDirectory, sizeof(DosDirectory)))
|
|
{
|
|
// TODO: Use some kind of default path?
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get the directory part of the path and set the current directory for the drive */
|
|
Path = strchr(DosDirectory, '\\');
|
|
if (Path != NULL)
|
|
{
|
|
Path++; // Skip the backslash
|
|
strncpy(DosData->CurrentDirectories[DriveNumber], Path, DOS_DIR_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
DosData->CurrentDirectories[DriveNumber][0] = '\0';
|
|
}
|
|
|
|
/* Return success */
|
|
return TRUE;
|
|
}
|
|
|
|
/* PUBLIC FUNCTIONS ***********************************************************/
|
|
|
|
BOOLEAN DosControlBreak(VOID)
|
|
{
|
|
setCF(0);
|
|
|
|
/* Print an extra newline */
|
|
DosPrintCharacter(DOS_OUTPUT_HANDLE, '\r');
|
|
DosPrintCharacter(DOS_OUTPUT_HANDLE, '\n');
|
|
|
|
/* Call interrupt 0x23 */
|
|
Int32Call(&DosContext, 0x23);
|
|
|
|
if (getCF())
|
|
{
|
|
DosTerminateProcess(Sda->CurrentPsp, 0, 0);
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID WINAPI DosInt20h(LPWORD Stack)
|
|
{
|
|
/*
|
|
* This is the exit interrupt (alias to INT 21h, AH=00h).
|
|
* CS must be the PSP segment.
|
|
*/
|
|
DosTerminateProcess(Stack[STACK_CS], 0, 0);
|
|
}
|
|
|
|
VOID WINAPI DosInt21h(LPWORD Stack)
|
|
{
|
|
BYTE Character;
|
|
SYSTEMTIME SystemTime;
|
|
PCHAR String;
|
|
|
|
Sda->InDos++;
|
|
|
|
/* Save the value of SS:SP on entry in the PSP */
|
|
SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack =
|
|
MAKELONG(getSP() + (STACK_FLAGS + 1) * 2, getSS());
|
|
|
|
/* Check the value in the AH register */
|
|
switch (getAH())
|
|
{
|
|
/* Terminate Program */
|
|
case 0x00:
|
|
{
|
|
/* CS must be the PSP segment */
|
|
DosTerminateProcess(Stack[STACK_CS], 0, 0);
|
|
break;
|
|
}
|
|
|
|
/* Read Character from STDIN with Echo */
|
|
case 0x01:
|
|
{
|
|
DPRINT("INT 21h, AH = 01h\n");
|
|
|
|
Character = DosReadCharacter(DOS_INPUT_HANDLE, TRUE);
|
|
if (Character == 0x03 && DosControlBreak()) break;
|
|
|
|
setAL(Character);
|
|
break;
|
|
}
|
|
|
|
/* Write Character to STDOUT */
|
|
case 0x02:
|
|
{
|
|
// FIXME: Under DOS 2+, output handle may be redirected!!!!
|
|
Character = getDL();
|
|
DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
|
|
|
|
/*
|
|
* We return the output character (DOS 2.1+).
|
|
* Also, if we're going to output a TAB, then
|
|
* don't return a TAB but a SPACE instead.
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
|
|
* for more information.
|
|
*/
|
|
setAL(Character == '\t' ? ' ' : Character);
|
|
break;
|
|
}
|
|
|
|
/* Read Character from STDAUX */
|
|
case 0x03:
|
|
{
|
|
// FIXME: Really read it from STDAUX!
|
|
DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
|
|
// setAL(DosReadCharacter());
|
|
break;
|
|
}
|
|
|
|
/* Write Character to STDAUX */
|
|
case 0x04:
|
|
{
|
|
// FIXME: Really write it to STDAUX!
|
|
DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
|
|
// DosPrintCharacter(getDL());
|
|
break;
|
|
}
|
|
|
|
/* Write Character to Printer */
|
|
case 0x05:
|
|
{
|
|
// FIXME: Really write it to printer!
|
|
DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
|
|
DPRINT1("0x%p\n", getDL());
|
|
DPRINT1("\n\n-----------\n\n");
|
|
break;
|
|
}
|
|
|
|
/* Direct Console I/O */
|
|
case 0x06:
|
|
{
|
|
Character = getDL();
|
|
|
|
// FIXME: Under DOS 2+, output handle may be redirected!!!!
|
|
|
|
if (Character != 0xFF)
|
|
{
|
|
/* Output */
|
|
DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
|
|
|
|
/*
|
|
* We return the output character (DOS 2.1+).
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
|
|
* for more information.
|
|
*/
|
|
setAL(Character);
|
|
}
|
|
else
|
|
{
|
|
/* Input */
|
|
if (DosCheckInput())
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
|
|
setAL(DosReadCharacter(DOS_INPUT_HANDLE, FALSE));
|
|
}
|
|
else
|
|
{
|
|
/* No character available */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
|
|
setAL(0x00);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Direct Character Input without Echo */
|
|
case 0x07:
|
|
{
|
|
DPRINT("Direct char input without echo\n");
|
|
setAL(DosReadCharacter(DOS_INPUT_HANDLE, FALSE));
|
|
break;
|
|
}
|
|
|
|
/* Character Input without Echo */
|
|
case 0x08:
|
|
{
|
|
DPRINT("Char input without echo\n");
|
|
|
|
Character = DosReadCharacter(DOS_INPUT_HANDLE, FALSE);
|
|
if (Character == 0x03 && DosControlBreak()) break;
|
|
|
|
setAL(Character);
|
|
break;
|
|
}
|
|
|
|
/* Write String to STDOUT */
|
|
case 0x09:
|
|
{
|
|
String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
while (*String != '$')
|
|
{
|
|
DosPrintCharacter(DOS_OUTPUT_HANDLE, *String);
|
|
String++;
|
|
}
|
|
|
|
/*
|
|
* We return the terminating character (DOS 2.1+).
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
|
|
* for more information.
|
|
*/
|
|
setAL('$'); // *String
|
|
break;
|
|
}
|
|
|
|
/* Read Buffered Input */
|
|
case 0x0A:
|
|
{
|
|
PDOS_INPUT_BUFFER InputBuffer = (PDOS_INPUT_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
DPRINT("Read Buffered Input\n");
|
|
if (InputBuffer->MaxLength == 0) break;
|
|
|
|
/* Read from standard input */
|
|
InputBuffer->Length = DosReadLineBuffered(
|
|
DOS_INPUT_HANDLE,
|
|
MAKELONG(getDX() + FIELD_OFFSET(DOS_INPUT_BUFFER, Buffer), getDS()),
|
|
InputBuffer->MaxLength
|
|
);
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get STDIN Status */
|
|
case 0x0B:
|
|
{
|
|
setAL(DosCheckInput() ? 0xFF : 0x00);
|
|
break;
|
|
}
|
|
|
|
/* Flush Buffer and Read STDIN */
|
|
case 0x0C:
|
|
{
|
|
BYTE InputFunction = getAL();
|
|
|
|
/* Flush STDIN buffer */
|
|
DosFlushFileBuffers(DOS_INPUT_HANDLE);
|
|
|
|
/*
|
|
* If the input function number contained in AL is valid, i.e.
|
|
* AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
|
|
* recursively with AL == AH.
|
|
*/
|
|
if (InputFunction == 0x01 || InputFunction == 0x06 ||
|
|
InputFunction == 0x07 || InputFunction == 0x08 ||
|
|
InputFunction == 0x0A)
|
|
{
|
|
/* Call ourselves recursively */
|
|
setAH(InputFunction);
|
|
DosInt21h(Stack);
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Disk Reset */
|
|
case 0x0D:
|
|
{
|
|
PDOS_PSP PspBlock = SEGMENT_TO_PSP(Sda->CurrentPsp);
|
|
|
|
// TODO: Flush what's needed.
|
|
DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
|
|
|
|
/* Clear CF in DOS 6 only */
|
|
if (PspBlock->DosVersion == 0x0006)
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Set Default Drive */
|
|
case 0x0E:
|
|
{
|
|
DosChangeDrive(getDL());
|
|
setAL(SysVars->NumLocalDrives);
|
|
break;
|
|
}
|
|
|
|
/* NULL Function for CP/M Compatibility */
|
|
case 0x18:
|
|
{
|
|
/*
|
|
* This function corresponds to the CP/M BDOS function
|
|
* "get bit map of logged drives", which is meaningless
|
|
* under MS-DOS.
|
|
*
|
|
* For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
|
|
* for more information.
|
|
*/
|
|
setAL(0x00);
|
|
break;
|
|
}
|
|
|
|
/* Get Default Drive */
|
|
case 0x19:
|
|
{
|
|
setAL(Sda->CurrentDrive);
|
|
break;
|
|
}
|
|
|
|
/* Set Disk Transfer Area */
|
|
case 0x1A:
|
|
{
|
|
Sda->DiskTransferArea = MAKELONG(getDX(), getDS());
|
|
break;
|
|
}
|
|
|
|
/* NULL Function for CP/M Compatibility */
|
|
case 0x1D:
|
|
case 0x1E:
|
|
{
|
|
/*
|
|
* Function 0x1D corresponds to the CP/M BDOS function
|
|
* "get bit map of read-only drives", which is meaningless
|
|
* under MS-DOS.
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
|
|
* for more information.
|
|
*
|
|
* Function 0x1E corresponds to the CP/M BDOS function
|
|
* "set file attributes", which was meaningless under MS-DOS 1.x.
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
|
|
* for more information.
|
|
*/
|
|
setAL(0x00);
|
|
break;
|
|
}
|
|
|
|
/* NULL Function for CP/M Compatibility */
|
|
case 0x20:
|
|
{
|
|
/*
|
|
* This function corresponds to the CP/M BDOS function
|
|
* "get/set default user (sublibrary) number", which is meaningless
|
|
* under MS-DOS.
|
|
*
|
|
* For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
|
|
* for more information.
|
|
*/
|
|
setAL(0x00);
|
|
break;
|
|
}
|
|
|
|
/* Set Interrupt Vector */
|
|
case 0x25:
|
|
{
|
|
ULONG FarPointer = MAKELONG(getDX(), getDS());
|
|
DPRINT1("Setting interrupt 0x%02X to %04X:%04X ...\n",
|
|
getAL(), HIWORD(FarPointer), LOWORD(FarPointer));
|
|
|
|
/* Write the new far pointer to the IDT */
|
|
((PULONG)BaseAddress)[getAL()] = FarPointer;
|
|
break;
|
|
}
|
|
|
|
/* Create New PSP */
|
|
case 0x26:
|
|
{
|
|
/* DOS 2+ assumes that the caller's CS is the segment of the PSP to copy */
|
|
DosClonePsp(getDX(), Stack[STACK_CS]);
|
|
break;
|
|
}
|
|
|
|
/* Parse Filename into FCB */
|
|
case 0x29:
|
|
{
|
|
PCHAR FileName = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
|
|
PDOS_FCB Fcb = (PDOS_FCB)SEG_OFF_TO_PTR(getES(), getDI());
|
|
BYTE Options = getAL();
|
|
CHAR FillChar = ' ';
|
|
UINT i;
|
|
|
|
if (FileName[1] == ':')
|
|
{
|
|
/* Set the drive number */
|
|
Fcb->DriveNumber = RtlUpperChar(FileName[0]) - 'A' + 1;
|
|
|
|
/* Skip to the file name part */
|
|
FileName += 2;
|
|
}
|
|
else
|
|
{
|
|
/* No drive number specified */
|
|
if (Options & (1 << 1)) Fcb->DriveNumber = Sda->CurrentDrive + 1;
|
|
else Fcb->DriveNumber = 0;
|
|
}
|
|
|
|
/* Parse the file name */
|
|
i = 0;
|
|
while ((*FileName > 0x20) && (i < 8))
|
|
{
|
|
if (*FileName == '.') break;
|
|
else if (*FileName == '*')
|
|
{
|
|
FillChar = '?';
|
|
break;
|
|
}
|
|
|
|
Fcb->FileName[i++] = RtlUpperChar(*FileName++);
|
|
}
|
|
|
|
/* Fill the whole field with blanks only if bit 2 is not set */
|
|
if ((FillChar != ' ') || (i != 0) || !(Options & (1 << 2)))
|
|
{
|
|
for (; i < 8; i++) Fcb->FileName[i] = FillChar;
|
|
}
|
|
|
|
/* Skip to the extension part */
|
|
while (*FileName > 0x20 && *FileName != '.') FileName++;
|
|
if (*FileName == '.') FileName++;
|
|
|
|
/* Now parse the extension */
|
|
i = 0;
|
|
FillChar = ' ';
|
|
|
|
while ((*FileName > 0x20) && (i < 3))
|
|
{
|
|
if (*FileName == '*')
|
|
{
|
|
FillChar = '?';
|
|
break;
|
|
}
|
|
|
|
Fcb->FileExt[i++] = RtlUpperChar(*FileName++);
|
|
}
|
|
|
|
/* Fill the whole field with blanks only if bit 3 is not set */
|
|
if ((FillChar != ' ') || (i != 0) || !(Options & (1 << 3)))
|
|
{
|
|
for (; i < 3; i++) Fcb->FileExt[i] = FillChar;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get System Date */
|
|
case 0x2A:
|
|
{
|
|
GetLocalTime(&SystemTime);
|
|
setCX(SystemTime.wYear);
|
|
setDX(MAKEWORD(SystemTime.wDay, SystemTime.wMonth));
|
|
setAL(SystemTime.wDayOfWeek);
|
|
break;
|
|
}
|
|
|
|
/* Set System Date */
|
|
case 0x2B:
|
|
{
|
|
GetLocalTime(&SystemTime);
|
|
SystemTime.wYear = getCX();
|
|
SystemTime.wMonth = getDH();
|
|
SystemTime.wDay = getDL();
|
|
|
|
/* Return success or failure */
|
|
setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
|
|
break;
|
|
}
|
|
|
|
/* Get System Time */
|
|
case 0x2C:
|
|
{
|
|
GetLocalTime(&SystemTime);
|
|
setCX(MAKEWORD(SystemTime.wMinute, SystemTime.wHour));
|
|
setDX(MAKEWORD(SystemTime.wMilliseconds / 10, SystemTime.wSecond));
|
|
break;
|
|
}
|
|
|
|
/* Set System Time */
|
|
case 0x2D:
|
|
{
|
|
GetLocalTime(&SystemTime);
|
|
SystemTime.wHour = getCH();
|
|
SystemTime.wMinute = getCL();
|
|
SystemTime.wSecond = getDH();
|
|
SystemTime.wMilliseconds = getDL() * 10; // In hundredths of seconds
|
|
|
|
/* Return success or failure */
|
|
setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
|
|
break;
|
|
}
|
|
|
|
/* Get Disk Transfer Area */
|
|
case 0x2F:
|
|
{
|
|
setES(HIWORD(Sda->DiskTransferArea));
|
|
setBX(LOWORD(Sda->DiskTransferArea));
|
|
break;
|
|
}
|
|
|
|
/* Get DOS Version */
|
|
case 0x30:
|
|
{
|
|
PDOS_PSP PspBlock = SEGMENT_TO_PSP(Sda->CurrentPsp);
|
|
|
|
/*
|
|
* DOS 2+ - GET DOS VERSION
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
|
|
* for more information.
|
|
*/
|
|
|
|
if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
|
|
{
|
|
/*
|
|
* Return DOS OEM number:
|
|
* 0x00 for IBM PC-DOS
|
|
* 0x02 for packaged MS-DOS
|
|
* 0xFF for NT DOS
|
|
*/
|
|
setBH(0xFF);
|
|
}
|
|
|
|
if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
|
|
{
|
|
/*
|
|
* Return version flag:
|
|
* 1 << 3 if DOS is in ROM,
|
|
* 0 (reserved) if not.
|
|
*/
|
|
setBH(0x00);
|
|
}
|
|
|
|
/* Return DOS 24-bit user serial number in BL:CX */
|
|
setBL(0x00);
|
|
setCX(0x0000);
|
|
|
|
/*
|
|
* Return DOS version: Minor:Major in AH:AL
|
|
* The Windows NT DOS box returns version 5.00, subject to SETVER.
|
|
*/
|
|
setAX(PspBlock->DosVersion);
|
|
|
|
break;
|
|
}
|
|
|
|
/* Terminate and Stay Resident */
|
|
case 0x31:
|
|
{
|
|
DPRINT1("Process going resident: %u paragraphs kept\n", getDX());
|
|
DosTerminateProcess(Sda->CurrentPsp, getAL(), getDX());
|
|
break;
|
|
}
|
|
|
|
/* Extended functionalities */
|
|
case 0x33:
|
|
{
|
|
switch (getAL())
|
|
{
|
|
/*
|
|
* DOS 4+ - GET BOOT DRIVE
|
|
*/
|
|
case 0x05:
|
|
{
|
|
setDL(SysVars->BootDrive);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
case 0x06:
|
|
{
|
|
/*
|
|
* 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);
|
|
|
|
break;
|
|
}
|
|
|
|
default: // goto Default;
|
|
{
|
|
DPRINT1("INT 21h, AH = %02Xh, subfunction AL = %02Xh NOT IMPLEMENTED\n",
|
|
getAH(), getAL());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get Address of InDOS flag */
|
|
case 0x34:
|
|
{
|
|
setES(DOS_DATA_SEGMENT);
|
|
setBX(DOS_DATA_OFFSET(Sda.InDos));
|
|
break;
|
|
}
|
|
|
|
/* Get Interrupt Vector */
|
|
case 0x35:
|
|
{
|
|
ULONG FarPointer = ((PULONG)BaseAddress)[getAL()];
|
|
|
|
/* Read the address from the IDT into ES:BX */
|
|
setES(HIWORD(FarPointer));
|
|
setBX(LOWORD(FarPointer));
|
|
break;
|
|
}
|
|
|
|
/* Get Free Disk Space */
|
|
case 0x36:
|
|
{
|
|
CHAR RootPath[] = "?:\\";
|
|
DWORD SectorsPerCluster;
|
|
DWORD BytesPerSector;
|
|
DWORD NumberOfFreeClusters;
|
|
DWORD TotalNumberOfClusters;
|
|
|
|
if (getDL() == 0x00)
|
|
RootPath[0] = 'A' + Sda->CurrentDrive;
|
|
else
|
|
RootPath[0] = 'A' + getDL() - 1;
|
|
|
|
if (GetDiskFreeSpaceA(RootPath,
|
|
&SectorsPerCluster,
|
|
&BytesPerSector,
|
|
&NumberOfFreeClusters,
|
|
&TotalNumberOfClusters))
|
|
{
|
|
setAX(LOWORD(SectorsPerCluster));
|
|
setCX(LOWORD(BytesPerSector));
|
|
setBX(min(NumberOfFreeClusters, 0xFFFF));
|
|
setDX(min(TotalNumberOfClusters, 0xFFFF));
|
|
}
|
|
else
|
|
{
|
|
/* Error */
|
|
setAX(0xFFFF);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* SWITCH character - AVAILDEV */
|
|
case 0x37:
|
|
{
|
|
switch (getAL())
|
|
{
|
|
/*
|
|
* DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
|
|
* This setting is ignored by MS-DOS 4.0+.
|
|
* MS-DOS 5+ always return AL=00h/DL=2Fh.
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
|
|
* for more information.
|
|
*/
|
|
case 0x00:
|
|
setDL('/');
|
|
setAL(0x00);
|
|
break;
|
|
|
|
/*
|
|
* DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
|
|
* This setting is ignored by MS-DOS 5+.
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
|
|
* for more information.
|
|
*/
|
|
case 0x01:
|
|
// getDL();
|
|
setAL(0xFF);
|
|
break;
|
|
|
|
/*
|
|
* DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
|
|
* for more information.
|
|
*/
|
|
case 0x02:
|
|
// setDL();
|
|
setAL(0xFF);
|
|
break;
|
|
|
|
/*
|
|
* DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
|
|
* for more information.
|
|
*/
|
|
case 0x03:
|
|
// getDL();
|
|
setAL(0xFF);
|
|
break;
|
|
|
|
/* Invalid subfunction */
|
|
default:
|
|
setAL(0xFF);
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get/Set Country-dependent Information */
|
|
case 0x38:
|
|
{
|
|
WORD CountryId = getAL() < 0xFF ? getAL() : getBX();
|
|
WORD ErrorCode;
|
|
|
|
ErrorCode = DosGetCountryInfo(&CountryId,
|
|
(PDOS_COUNTRY_INFO)SEG_OFF_TO_PTR(getDS(), getDX()));
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setBX(CountryId);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Create Directory */
|
|
case 0x39:
|
|
{
|
|
String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
if (CreateDirectoryA(String, NULL))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(LOWORD(GetLastError()));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Remove Directory */
|
|
case 0x3A:
|
|
{
|
|
String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
if (RemoveDirectoryA(String))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(LOWORD(GetLastError()));
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Set Current Directory */
|
|
case 0x3B:
|
|
{
|
|
String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
if (DosChangeDirectory(String))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Create or Truncate File */
|
|
case 0x3C:
|
|
{
|
|
WORD FileHandle;
|
|
WORD ErrorCode = DosCreateFile(&FileHandle,
|
|
(LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
|
|
CREATE_ALWAYS,
|
|
getCX());
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(FileHandle);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Open File or Device */
|
|
case 0x3D:
|
|
{
|
|
WORD FileHandle;
|
|
LPCSTR FileName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
WORD ErrorCode = DosOpenFile(&FileHandle, FileName, getAL());
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(FileHandle);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Close File or Device */
|
|
case 0x3E:
|
|
{
|
|
if (DosCloseHandle(getBX()))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_HANDLE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Read from File or Device */
|
|
case 0x3F:
|
|
{
|
|
WORD BytesRead = 0;
|
|
WORD ErrorCode;
|
|
|
|
DPRINT("DosReadFile(0x%04X)\n", getBX());
|
|
|
|
ErrorCode = DosReadFile(getBX(),
|
|
MAKELONG(getDX(), getDS()),
|
|
getCX(),
|
|
&BytesRead);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(BytesRead);
|
|
}
|
|
else if (ErrorCode != ERROR_NOT_READY)
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Write to File or Device */
|
|
case 0x40:
|
|
{
|
|
WORD BytesWritten = 0;
|
|
WORD ErrorCode = DosWriteFile(getBX(),
|
|
MAKELONG(getDX(), getDS()),
|
|
getCX(),
|
|
&BytesWritten);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(BytesWritten);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Delete File */
|
|
case 0x41:
|
|
{
|
|
LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
if (demFileDelete(FileName) == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
/*
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
|
|
* "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
|
|
*/
|
|
setAL(RtlUpperChar(FileName[0]) - 'A');
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Seek File */
|
|
case 0x42:
|
|
{
|
|
DWORD NewLocation;
|
|
WORD ErrorCode = DosSeekFile(getBX(),
|
|
MAKELONG(getDX(), getCX()),
|
|
getAL(),
|
|
&NewLocation);
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
|
|
/* Return the new offset in DX:AX */
|
|
setDX(HIWORD(NewLocation));
|
|
setAX(LOWORD(NewLocation));
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get/Set File Attributes */
|
|
case 0x43:
|
|
{
|
|
DWORD Attributes;
|
|
LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
|
|
if (getAL() == 0x00)
|
|
{
|
|
/* Get the attributes */
|
|
Attributes = GetFileAttributesA(FileName);
|
|
|
|
/* Check if it failed */
|
|
if (Attributes == INVALID_FILE_ATTRIBUTES)
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
}
|
|
else
|
|
{
|
|
/* Return the attributes that DOS can understand */
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setCX(Attributes & 0x00FF);
|
|
}
|
|
}
|
|
else if (getAL() == 0x01)
|
|
{
|
|
/* Try to set the attributes */
|
|
if (SetFileAttributesA(FileName, getCL()))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_FUNCTION);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* IOCTL */
|
|
case 0x44:
|
|
{
|
|
WORD Length = getCX();
|
|
|
|
if (DosDeviceIoControl(getBX(), getAL(), MAKELONG(getDX(), getDS()), &Length))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(Length);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Duplicate Handle */
|
|
case 0x45:
|
|
{
|
|
WORD NewHandle = DosDuplicateHandle(getBX());
|
|
|
|
if (NewHandle != INVALID_DOS_HANDLE)
|
|
{
|
|
setAX(NewHandle);
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Force Duplicate Handle */
|
|
case 0x46:
|
|
{
|
|
if (DosForceDuplicateHandle(getBX(), getCX()))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_HANDLE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get Current Directory */
|
|
case 0x47:
|
|
{
|
|
BYTE DriveNumber = getDL();
|
|
String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
|
|
|
|
/* Get the real drive number */
|
|
if (DriveNumber == 0)
|
|
{
|
|
DriveNumber = Sda->CurrentDrive;
|
|
}
|
|
else
|
|
{
|
|
/* Decrement DriveNumber since it was 1-based */
|
|
DriveNumber--;
|
|
}
|
|
|
|
if (DriveNumber < SysVars->NumLocalDrives)
|
|
{
|
|
/*
|
|
* Copy the current directory into the target buffer.
|
|
* It doesn't contain the drive letter and the backslash.
|
|
*/
|
|
strncpy(String, DosData->CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_DRIVE);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Allocate Memory */
|
|
case 0x48:
|
|
{
|
|
WORD MaxAvailable = 0;
|
|
WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
|
|
|
|
if (Segment != 0)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(Segment);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
setBX(MaxAvailable);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Free Memory */
|
|
case 0x49:
|
|
{
|
|
if (DosFreeMemory(getES()))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Resize Memory Block */
|
|
case 0x4A:
|
|
{
|
|
WORD Size;
|
|
|
|
if (DosResizeMemory(getES(), getBX(), &Size))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
setBX(Size);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Execute */
|
|
case 0x4B:
|
|
{
|
|
BYTE OrgAL = getAL();
|
|
LPSTR ProgramName = SEG_OFF_TO_PTR(getDS(), getDX());
|
|
PDOS_EXEC_PARAM_BLOCK ParamBlock = SEG_OFF_TO_PTR(getES(), getBX());
|
|
WORD ErrorCode;
|
|
|
|
if (OrgAL <= DOS_LOAD_OVERLAY)
|
|
{
|
|
DOS_EXEC_TYPE LoadType = (DOS_EXEC_TYPE)OrgAL;
|
|
|
|
if (LoadType == DOS_LOAD_AND_EXECUTE)
|
|
{
|
|
/* Create a new process */
|
|
ErrorCode = DosCreateProcess(ProgramName,
|
|
ParamBlock,
|
|
MAKELONG(Stack[STACK_IP], Stack[STACK_CS]));
|
|
}
|
|
else
|
|
{
|
|
/* Just load an executable */
|
|
ErrorCode = DosLoadExecutable(LoadType,
|
|
ProgramName,
|
|
ParamBlock,
|
|
NULL,
|
|
NULL,
|
|
MAKELONG(Stack[STACK_IP], Stack[STACK_CS]));
|
|
}
|
|
}
|
|
else if (OrgAL == 0x05)
|
|
{
|
|
// http://www.ctyme.com/intr/rb-2942.htm
|
|
DPRINT1("Set execution state is UNIMPLEMENTED\n");
|
|
ErrorCode = ERROR_CALL_NOT_IMPLEMENTED;
|
|
}
|
|
else
|
|
{
|
|
ErrorCode = ERROR_INVALID_FUNCTION;
|
|
}
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Terminate with Return Code */
|
|
case 0x4C:
|
|
{
|
|
DosTerminateProcess(Sda->CurrentPsp, getAL(), 0);
|
|
break;
|
|
}
|
|
|
|
/* Get Return Code (ERRORLEVEL) */
|
|
case 0x4D:
|
|
{
|
|
/*
|
|
* According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
|
|
* DosErrorLevel is cleared after being read by this function.
|
|
*/
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(Sda->ErrorLevel);
|
|
Sda->ErrorLevel = 0x0000; // Clear it
|
|
break;
|
|
}
|
|
|
|
/* Find First File */
|
|
case 0x4E:
|
|
{
|
|
WORD Result = (WORD)demFileFindFirst(FAR_POINTER(Sda->DiskTransferArea),
|
|
SEG_OFF_TO_PTR(getDS(), getDX()),
|
|
getCX());
|
|
|
|
setAX(Result);
|
|
|
|
if (Result == ERROR_SUCCESS)
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
else
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Find Next File */
|
|
case 0x4F:
|
|
{
|
|
WORD Result = (WORD)demFileFindNext(FAR_POINTER(Sda->DiskTransferArea));
|
|
|
|
setAX(Result);
|
|
|
|
if (Result == ERROR_SUCCESS)
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
else
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Internal - Set Current Process ID (Set PSP Address) */
|
|
case 0x50:
|
|
{
|
|
DosSetProcessContext(getBX());
|
|
break;
|
|
}
|
|
|
|
/* Internal - Get Current Process ID (Get PSP Address) */
|
|
case 0x51:
|
|
/* Get Current PSP Address */
|
|
case 0x62:
|
|
{
|
|
/*
|
|
* 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(Sda->CurrentPsp);
|
|
break;
|
|
}
|
|
|
|
/* Internal - Get "List of lists" (SYSVARS) */
|
|
case 0x52:
|
|
{
|
|
/*
|
|
* On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
|
|
* for more information.
|
|
*/
|
|
|
|
/* Return the DOS "list of lists" in ES:BX */
|
|
setES(DOS_DATA_SEGMENT);
|
|
setBX(DOS_DATA_OFFSET(SysVars.FirstDpb));
|
|
break;
|
|
}
|
|
|
|
/* Create Child PSP */
|
|
case 0x55:
|
|
{
|
|
DosCreatePsp(getDX(), getSI());
|
|
DosSetProcessContext(getDX());
|
|
break;
|
|
}
|
|
|
|
/* Rename File */
|
|
case 0x56:
|
|
{
|
|
LPSTR ExistingFileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
LPSTR NewFileName = (LPSTR)SEG_OFF_TO_PTR(getES(), getDI());
|
|
|
|
/*
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-2990.htm
|
|
* for more information.
|
|
*/
|
|
|
|
if (MoveFileA(ExistingFileName, NewFileName))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* File Attributes */
|
|
case 0x57:
|
|
{
|
|
switch (getAL())
|
|
{
|
|
/* Get File's last-written Date and Time */
|
|
case 0x00:
|
|
{
|
|
PDOS_FILE_DESCRIPTOR Descriptor = DosGetHandleFileDescriptor(getBX());
|
|
FILETIME LastWriteTime;
|
|
WORD FileDate, FileTime;
|
|
|
|
if (Descriptor == NULL)
|
|
{
|
|
/* Invalid handle */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
// Sda->LastErrorCode = ERROR_INVALID_HANDLE;
|
|
setAX(ERROR_INVALID_HANDLE);
|
|
break;
|
|
}
|
|
|
|
if (Descriptor->DeviceInfo & FILE_INFO_DEVICE)
|
|
{
|
|
/* Invalid for devices */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
// setAX(ERROR_INVALID_FUNCTION);
|
|
setAX(ERROR_INVALID_HANDLE);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Retrieve the last-written Win32 date and time,
|
|
* and convert it to DOS format.
|
|
*/
|
|
if (!GetFileTime(Descriptor->Win32Handle,
|
|
NULL, NULL, &LastWriteTime) ||
|
|
!FileTimeToDosDateTime(&LastWriteTime,
|
|
&FileDate, &FileTime))
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
break;
|
|
}
|
|
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setCX(FileTime);
|
|
setDX(FileDate);
|
|
break;
|
|
}
|
|
|
|
/* Set File's last-written Date and Time */
|
|
case 0x01:
|
|
{
|
|
PDOS_FILE_DESCRIPTOR Descriptor = DosGetHandleFileDescriptor(getBX());
|
|
FILETIME LastWriteTime;
|
|
WORD FileDate = getDX();
|
|
WORD FileTime = getCX();
|
|
|
|
if (Descriptor == NULL)
|
|
{
|
|
/* Invalid handle */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
// Sda->LastErrorCode = ERROR_INVALID_HANDLE;
|
|
setAX(ERROR_INVALID_HANDLE);
|
|
break;
|
|
}
|
|
|
|
if (Descriptor->DeviceInfo & FILE_INFO_DEVICE)
|
|
{
|
|
/* Invalid for devices */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
// setAX(ERROR_INVALID_FUNCTION);
|
|
setAX(ERROR_INVALID_HANDLE);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Convert the new last-written DOS date and time
|
|
* to Win32 format and set it.
|
|
*/
|
|
if (!DosDateTimeToFileTime(FileDate, FileTime,
|
|
&LastWriteTime) ||
|
|
!SetFileTime(Descriptor->Win32Handle,
|
|
NULL, NULL, &LastWriteTime))
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
break;
|
|
}
|
|
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
break;
|
|
}
|
|
|
|
default: // goto Default;
|
|
{
|
|
DPRINT1("INT 21h, AH = %02Xh, subfunction AL = %02Xh NOT IMPLEMENTED\n",
|
|
getAH(), getAL());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get/Set Memory Management Options */
|
|
case 0x58:
|
|
{
|
|
switch (getAL())
|
|
{
|
|
/* Get allocation strategy */
|
|
case 0x00:
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(Sda->AllocStrategy);
|
|
break;
|
|
}
|
|
|
|
/* Set allocation strategy */
|
|
case 0x01:
|
|
{
|
|
if ((getBL() & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
|
|
== (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
|
|
{
|
|
/* Can't set both */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_PARAMETER);
|
|
break;
|
|
}
|
|
|
|
if ((getBL() & ~(DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
|
|
> DOS_ALLOC_LAST_FIT)
|
|
{
|
|
/* Invalid allocation strategy */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_PARAMETER);
|
|
break;
|
|
}
|
|
|
|
Sda->AllocStrategy = getBL();
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
break;
|
|
}
|
|
|
|
/* Get UMB link state */
|
|
case 0x02:
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAL(SysVars->UmbLinked ? 0x01 : 0x00);
|
|
break;
|
|
}
|
|
|
|
/* Set UMB link state */
|
|
case 0x03:
|
|
{
|
|
BOOLEAN Success;
|
|
|
|
if (getBX())
|
|
Success = DosLinkUmb();
|
|
else
|
|
Success = DosUnlinkUmb();
|
|
|
|
if (Success)
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
else
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Invalid or unsupported function */
|
|
default:
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_FUNCTION);
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Get Extended Error Information */
|
|
case 0x59:
|
|
{
|
|
DPRINT1("INT 21h, AH = 59h, BX = %04Xh - Get Extended Error Information is UNIMPLEMENTED\n",
|
|
getBX());
|
|
break;
|
|
}
|
|
|
|
/* Create Temporary File */
|
|
case 0x5A:
|
|
{
|
|
LPSTR PathName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
LPSTR FileName = PathName; // The buffer for the path and the full file name is the same.
|
|
UINT uRetVal;
|
|
WORD FileHandle;
|
|
WORD ErrorCode;
|
|
|
|
/*
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-3014.htm
|
|
* for more information.
|
|
*/
|
|
|
|
// FIXME: Check for buffer validity?
|
|
// It should be a ASCIIZ path ending with a '\' + 13 zero bytes
|
|
// to receive the generated filename.
|
|
|
|
/* First create the temporary file */
|
|
uRetVal = GetTempFileNameA(PathName, NULL, 0, FileName);
|
|
if (uRetVal == 0)
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
break;
|
|
}
|
|
|
|
/* Now try to open it in read/write access */
|
|
ErrorCode = DosOpenFile(&FileHandle, FileName, 2);
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(FileHandle);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Create New File */
|
|
case 0x5B:
|
|
{
|
|
WORD FileHandle;
|
|
WORD ErrorCode = DosCreateFile(&FileHandle,
|
|
(LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
|
|
CREATE_NEW,
|
|
getCX());
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(FileHandle);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Lock/Unlock Region of File */
|
|
case 0x5C:
|
|
{
|
|
if (getAL() == 0x00)
|
|
{
|
|
/* Lock region of file */
|
|
if (DosLockFile(getBX(), MAKELONG(getDX(), getCX()), MAKELONG(getDI(), getSI())))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
}
|
|
else if (getAL() == 0x01)
|
|
{
|
|
/* Unlock region of file */
|
|
if (DosUnlockFile(getBX(), MAKELONG(getDX(), getCX()), MAKELONG(getDI(), getSI())))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Invalid subfunction */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_FUNCTION);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Canonicalize File Name or Path */
|
|
case 0x60:
|
|
{
|
|
/*
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-3137.htm
|
|
* for more information.
|
|
*/
|
|
|
|
/*
|
|
* We suppose that the DOS app gave to us a valid
|
|
* 128-byte long buffer for the canonicalized name.
|
|
*/
|
|
DWORD dwRetVal = GetFullPathNameA(SEG_OFF_TO_PTR(getDS(), getSI()),
|
|
128,
|
|
SEG_OFF_TO_PTR(getES(), getDI()),
|
|
NULL);
|
|
if (dwRetVal == 0)
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setAX(0x0000);
|
|
}
|
|
|
|
// FIXME: Convert the full path name into short version.
|
|
// We cannot reliably use GetShortPathName, because it fails
|
|
// if the path name given doesn't exist. However this DOS
|
|
// function AH=60h should be able to work even for non-existing
|
|
// path and file names.
|
|
|
|
break;
|
|
}
|
|
|
|
/* Miscellaneous Internal Functions */
|
|
case 0x5D:
|
|
{
|
|
switch (getAL())
|
|
{
|
|
/* Get Swappable Data Area */
|
|
case 0x06:
|
|
{
|
|
setDS(DOS_DATA_SEGMENT);
|
|
setSI(DOS_DATA_OFFSET(Sda.ErrorMode));
|
|
setCX(sizeof(DOS_SDA));
|
|
setDX(FIELD_OFFSET(DOS_SDA, LastAX));
|
|
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
break;
|
|
}
|
|
|
|
default: // goto Default;
|
|
{
|
|
DPRINT1("INT 21h, AH = %02Xh, subfunction AL = %02Xh NOT IMPLEMENTED\n",
|
|
getAH(), getAL());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Extended Country Information */
|
|
case 0x65:
|
|
{
|
|
switch (getAL())
|
|
{
|
|
case 0x01: case 0x02: case 0x03:
|
|
case 0x04: case 0x05: case 0x06:
|
|
case 0x07:
|
|
{
|
|
WORD BufferSize = getCX();
|
|
WORD ErrorCode;
|
|
ErrorCode = DosGetCountryInfoEx(getAL(),
|
|
getBX(),
|
|
getDX(),
|
|
(PDOS_COUNTRY_INFO_2)SEG_OFF_TO_PTR(getES(), getDI()),
|
|
&BufferSize);
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setCX(BufferSize);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Country-dependent Character Capitalization -- Character */
|
|
case 0x20:
|
|
/* Country-dependent Filename Capitalization -- Character */
|
|
case 0xA0:
|
|
{
|
|
setDL(DosToUpper(getDL()));
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
// setAX(ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
/* Country-dependent Character Capitalization -- Counted ASCII String */
|
|
case 0x21:
|
|
/* Country-dependent Filename Capitalization -- Counted ASCII String */
|
|
case 0xA1:
|
|
{
|
|
PCHAR Str = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
// FIXME: Check for NULL ptr!!
|
|
DosToUpperStrN(Str, Str, getCX());
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
// setAX(ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
/* Country-dependent Character Capitalization -- ASCIIZ String */
|
|
case 0x22:
|
|
/* Country-dependent Filename Capitalization -- ASCIIZ String */
|
|
case 0xA2:
|
|
{
|
|
PSTR Str = (PSTR)SEG_OFF_TO_PTR(getDS(), getDX());
|
|
// FIXME: Check for NULL ptr!!
|
|
DosToUpperStrZ(Str, Str);
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
// setAX(ERROR_SUCCESS);
|
|
break;
|
|
}
|
|
|
|
/* Determine if Character represents YES/NO Response */
|
|
case 0x23:
|
|
{
|
|
setAX(DosIfCharYesNo(MAKEWORD(getDL(), getDH())));
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
break;
|
|
}
|
|
|
|
default: // goto Default;
|
|
{
|
|
DPRINT1("INT 21h, AH = %02Xh, subfunction AL = %02Xh NOT IMPLEMENTED\n",
|
|
getAH(), getAL());
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Set Handle Count */
|
|
case 0x67:
|
|
{
|
|
if (!DosResizeHandleTable(getBX()))
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(Sda->LastErrorCode);
|
|
}
|
|
else Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
|
|
break;
|
|
}
|
|
|
|
/* Commit File */
|
|
case 0x68:
|
|
case 0x6A:
|
|
{
|
|
/*
|
|
* Function 6Ah is identical to function 68h,
|
|
* and sets AH to 68h if success.
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-3176.htm
|
|
* for more information.
|
|
*/
|
|
setAH(0x68);
|
|
|
|
if (DosFlushFileBuffers(getBX()))
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(GetLastError());
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Extended Open/Create */
|
|
case 0x6C:
|
|
{
|
|
WORD FileHandle;
|
|
WORD CreationStatus;
|
|
WORD ErrorCode;
|
|
|
|
/* Check for AL == 00 */
|
|
if (getAL() != 0x00)
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ERROR_INVALID_FUNCTION);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-3179.htm
|
|
* for the full detailed description.
|
|
*
|
|
* WARNING: BH contains some extended flags that are NOT SUPPORTED.
|
|
*/
|
|
|
|
ErrorCode = DosCreateFileEx(&FileHandle,
|
|
&CreationStatus,
|
|
(LPCSTR)SEG_OFF_TO_PTR(getDS(), getSI()),
|
|
getBL(),
|
|
getDL(),
|
|
getCX());
|
|
|
|
if (ErrorCode == ERROR_SUCCESS)
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
setCX(CreationStatus);
|
|
setAX(FileHandle);
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
setAX(ErrorCode);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Unsupported */
|
|
default: // Default:
|
|
{
|
|
DPRINT1("DOS Function INT 21h, AH = %02Xh, AL = %02Xh 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;
|
|
}
|
|
}
|
|
|
|
Sda->InDos--;
|
|
}
|
|
|
|
VOID WINAPI DosBreakInterrupt(LPWORD Stack)
|
|
{
|
|
/* Set CF to terminate the running process */
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
}
|
|
|
|
VOID WINAPI DosAbsoluteRead(LPWORD Stack)
|
|
{
|
|
/*
|
|
* This call should leave the flags on the stack for some reason,
|
|
* so move the stack by one word.
|
|
* See: http://www.techhelpmanual.com/565-int_25h_26h__absolute_disk_read_write.html
|
|
*/
|
|
Stack[STACK_INT_NUM] = Stack[STACK_IP];
|
|
Stack[STACK_IP] = Stack[STACK_CS];
|
|
Stack[STACK_CS] = Stack[STACK_FLAGS];
|
|
setSP(LOWORD(getSP() - 2));
|
|
|
|
// TODO: NOT IMPLEMENTED;
|
|
UNIMPLEMENTED;
|
|
|
|
/* General failure */
|
|
setAX(0x800C);
|
|
Stack[STACK_FLAGS - 1] |= EMULATOR_FLAG_CF;
|
|
}
|
|
|
|
VOID WINAPI DosAbsoluteWrite(LPWORD Stack)
|
|
{
|
|
/*
|
|
* This call should leave the flags on the stack for some reason,
|
|
* so move the stack by one word.
|
|
* See: http://www.techhelpmanual.com/565-int_25h_26h__absolute_disk_read_write.html
|
|
*/
|
|
Stack[STACK_INT_NUM] = Stack[STACK_IP];
|
|
Stack[STACK_IP] = Stack[STACK_CS];
|
|
Stack[STACK_CS] = Stack[STACK_FLAGS];
|
|
setSP(LOWORD(getSP() - 2));
|
|
|
|
// TODO: NOT IMPLEMENTED;
|
|
UNIMPLEMENTED;
|
|
|
|
/* General failure */
|
|
setAX(0x800C);
|
|
Stack[STACK_FLAGS - 1] |= EMULATOR_FLAG_CF;
|
|
}
|
|
|
|
VOID WINAPI DosInt27h(LPWORD Stack)
|
|
{
|
|
WORD KeepResident = (getDX() + 0x0F) >> 4;
|
|
|
|
/* Terminate and Stay Resident. CS must be the PSP segment. */
|
|
DPRINT1("Process going resident: %u paragraphs kept\n", KeepResident);
|
|
DosTerminateProcess(Stack[STACK_CS], 0, KeepResident);
|
|
}
|
|
|
|
VOID WINAPI DosIdle(LPWORD Stack)
|
|
{
|
|
/*
|
|
* This will set the carry flag on the first call (to repeat the BOP),
|
|
* and clear it in the next, so that exactly one HLT occurs.
|
|
*/
|
|
setCF(!getCF());
|
|
}
|
|
|
|
VOID WINAPI DosFastConOut(LPWORD Stack)
|
|
{
|
|
/*
|
|
* This is the DOS 2+ Fast Console Output Interrupt.
|
|
* The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
|
|
*
|
|
* See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
|
|
* for more information.
|
|
*/
|
|
|
|
/* Save AX and BX */
|
|
USHORT AX = getAX();
|
|
USHORT BX = getBX();
|
|
|
|
/*
|
|
* Set the parameters:
|
|
* AL contains the character to print (already set),
|
|
* BL contains the character attribute,
|
|
* BH contains the video page to use.
|
|
*/
|
|
setBL(DOS_CHAR_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 WINAPI DosInt2Ah(LPWORD Stack)
|
|
{
|
|
DPRINT1("INT 2Ah, AX=%4xh called\n", getAX());
|
|
}
|
|
|
|
VOID WINAPI DosInt2Fh(LPWORD Stack)
|
|
{
|
|
switch (getAH())
|
|
{
|
|
/* DOS 3+ Internal Utility Functions */
|
|
case 0x12:
|
|
{
|
|
DPRINT1("INT 2Fh, AX=%4xh DOS Internal Utility Function called\n", getAX());
|
|
|
|
switch (getAL())
|
|
{
|
|
/* Installation Check */
|
|
case 0x00:
|
|
{
|
|
setAL(0xFF);
|
|
break;
|
|
}
|
|
|
|
/* Get DOS Data Segment */
|
|
case 0x03:
|
|
{
|
|
setDS(DOS_DATA_SEGMENT);
|
|
break;
|
|
}
|
|
|
|
/* Compare FAR Pointers */
|
|
case 0x14:
|
|
{
|
|
PVOID PointerFromFarPointer1 = SEG_OFF_TO_PTR(getDS(), getSI());
|
|
PVOID PointerFromFarPointer2 = SEG_OFF_TO_PTR(getES(), getDI());
|
|
BOOLEAN AreEqual = (PointerFromFarPointer1 == PointerFromFarPointer2);
|
|
|
|
if (AreEqual)
|
|
{
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
|
|
}
|
|
else
|
|
{
|
|
Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
}
|
|
break;
|
|
}
|
|
|
|
/* Set DOS Version Number to return */
|
|
case 0x2F:
|
|
{
|
|
WORD DosVersion = getDX();
|
|
|
|
// Special case: return the true DOS version when DX=00h
|
|
if (DosVersion == 0x0000)
|
|
DosData->DosVersion = DOS_VERSION;
|
|
else
|
|
DosData->DosVersion = DosVersion;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
/* Set Disk Interrupt Handler */
|
|
case 0x13:
|
|
{
|
|
/* Save the old values of PrevInt13 and RomBiosInt13 */
|
|
ULONG OldInt13 = BiosData->PrevInt13;
|
|
ULONG OldBiosInt13 = BiosData->RomBiosInt13;
|
|
|
|
/* Set PrevInt13 and RomBiosInt13 to their new values */
|
|
BiosData->PrevInt13 = MAKELONG(getDX(), getDS());
|
|
BiosData->RomBiosInt13 = MAKELONG(getBX(), getES());
|
|
|
|
/* Return in DS:DX the old value of PrevInt13 */
|
|
setDS(HIWORD(OldInt13));
|
|
setDX(LOWORD(OldInt13));
|
|
|
|
/* Return in DS:DX the old value of RomBiosInt13 */
|
|
setES(HIWORD(OldBiosInt13));
|
|
setBX(LOWORD(OldBiosInt13));
|
|
|
|
break;
|
|
}
|
|
|
|
/* Mostly Windows 2.x/3.x/9x support */
|
|
case 0x16:
|
|
{
|
|
/*
|
|
* AL=80h is DOS/Windows/DPMI "Release Current Virtual Machine Time-slice"
|
|
* Just do nothing in this case.
|
|
*/
|
|
if (getAL() != 0x80) goto Default;
|
|
break;
|
|
}
|
|
|
|
/* Extended Memory Specification */
|
|
case 0x43:
|
|
{
|
|
DWORD DriverEntry;
|
|
if (!XmsGetDriverEntry(&DriverEntry)) break;
|
|
|
|
switch (getAL())
|
|
{
|
|
/* Installation Check */
|
|
case 0x00:
|
|
{
|
|
/* The driver is loaded */
|
|
setAL(0x80);
|
|
break;
|
|
}
|
|
|
|
/* Get Driver Address */
|
|
case 0x10:
|
|
{
|
|
setES(HIWORD(DriverEntry));
|
|
setBX(LOWORD(DriverEntry));
|
|
break;
|
|
}
|
|
|
|
default:
|
|
DPRINT1("Unknown DOS XMS Function: INT 2Fh, AH = 43h, AL = %02Xh\n", getAL());
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
default: Default:
|
|
{
|
|
DPRINT1("DOS Internal System Function INT 2Fh, AH = %02Xh, AL = %02Xh NOT IMPLEMENTED!\n",
|
|
getAH(), getAL());
|
|
Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
|
|
}
|
|
}
|
|
}
|
|
|
|
BOOLEAN DosKRNLInitialize(VOID)
|
|
{
|
|
UCHAR i;
|
|
PDOS_SFT Sft;
|
|
LPSTR Path;
|
|
BOOLEAN Success = TRUE;
|
|
DWORD dwRet;
|
|
CHAR CurrentDirectory[MAX_PATH];
|
|
CHAR DosDirectory[DOS_DIR_LENGTH];
|
|
|
|
static const BYTE NullDriverRoutine[] =
|
|
{
|
|
/* Strategy routine entry */
|
|
0x26, // mov [Request.Status], DOS_DEVSTAT_DONE
|
|
0xC7,
|
|
0x47,
|
|
FIELD_OFFSET(DOS_REQUEST_HEADER, Status),
|
|
LOBYTE(DOS_DEVSTAT_DONE),
|
|
HIBYTE(DOS_DEVSTAT_DONE),
|
|
|
|
/* Interrupt routine entry */
|
|
0xCB, // retf
|
|
};
|
|
|
|
/* Set the data segment */
|
|
setDS(DOS_DATA_SEGMENT);
|
|
|
|
/* Initialize the global DOS data area */
|
|
DosData = (PDOS_DATA)SEG_OFF_TO_PTR(DOS_DATA_SEGMENT, 0x0000);
|
|
RtlZeroMemory(DosData, sizeof(*DosData));
|
|
|
|
/* Initialize the DOS stack */
|
|
setSS(DOS_DATA_SEGMENT);
|
|
setSP(DOS_DATA_OFFSET(DosStack) + sizeof(DosData->DosStack) - sizeof(WORD));
|
|
|
|
/* Initialize the list of lists */
|
|
SysVars = &DosData->SysVars;
|
|
RtlZeroMemory(SysVars, sizeof(*SysVars));
|
|
SysVars->FirstSft = MAKELONG(DOS_DATA_OFFSET(Sft), DOS_DATA_SEGMENT);
|
|
SysVars->CurrentDirs = MAKELONG(DOS_DATA_OFFSET(CurrentDirectories),
|
|
DOS_DATA_SEGMENT);
|
|
/*
|
|
* The last drive can be redefined with the LASTDRIVE command.
|
|
* At the moment, set the real maximum possible, 'Z'.
|
|
*/
|
|
SysVars->NumLocalDrives = 'Z' - 'A' + 1; // See #define NUM_DRIVES in dos.h
|
|
|
|
/* The boot drive is initialized to the %SYSTEMDRIVE% value */
|
|
// NOTE: Using the NtSystemRoot system variable might be OS-specific...
|
|
SysVars->BootDrive = RtlUpcaseUnicodeChar(SharedUserData->NtSystemRoot[0]) - 'A' + 1;
|
|
|
|
/* Initialize the NUL device driver */
|
|
SysVars->NullDevice.Link = 0xFFFFFFFF;
|
|
SysVars->NullDevice.DeviceAttributes = DOS_DEVATTR_NUL | DOS_DEVATTR_CHARACTER;
|
|
// Offset from within the DOS data segment
|
|
SysVars->NullDevice.StrategyRoutine = DOS_DATA_OFFSET(NullDriverRoutine);
|
|
// Hardcoded to the RETF inside StrategyRoutine
|
|
SysVars->NullDevice.InterruptRoutine = SysVars->NullDevice.StrategyRoutine + 6;
|
|
RtlFillMemory(SysVars->NullDevice.DeviceName,
|
|
sizeof(SysVars->NullDevice.DeviceName),
|
|
' ');
|
|
RtlCopyMemory(SysVars->NullDevice.DeviceName, "NUL", strlen("NUL"));
|
|
RtlCopyMemory(DosData->NullDriverRoutine,
|
|
NullDriverRoutine,
|
|
sizeof(NullDriverRoutine));
|
|
|
|
/* Default DOS version to report */
|
|
DosData->DosVersion = DOS_VERSION;
|
|
|
|
/* Initialize the swappable data area */
|
|
Sda = &DosData->Sda;
|
|
RtlZeroMemory(Sda, sizeof(*Sda));
|
|
|
|
/* Get the current directory and convert it to a DOS path */
|
|
dwRet = GetCurrentDirectoryA(sizeof(CurrentDirectory), CurrentDirectory);
|
|
if (dwRet == 0)
|
|
{
|
|
Success = FALSE;
|
|
DPRINT1("GetCurrentDirectoryA failed (Error: %u)\n", GetLastError());
|
|
}
|
|
else if (dwRet > sizeof(CurrentDirectory))
|
|
{
|
|
Success = FALSE;
|
|
DPRINT1("Current directory too long (%d > MAX_PATH) for GetCurrentDirectoryA\n", dwRet);
|
|
}
|
|
|
|
if (Success)
|
|
{
|
|
dwRet = GetShortPathNameA(CurrentDirectory, DosDirectory, sizeof(DosDirectory));
|
|
if (dwRet == 0)
|
|
{
|
|
Success = FALSE;
|
|
DPRINT1("GetShortPathNameA failed (Error: %u)\n", GetLastError());
|
|
}
|
|
else if (dwRet > sizeof(DosDirectory))
|
|
{
|
|
Success = FALSE;
|
|
DPRINT1("Short path too long (%d > DOS_DIR_LENGTH) for GetShortPathNameA\n", dwRet);
|
|
}
|
|
}
|
|
|
|
if (!Success)
|
|
{
|
|
/* We failed, use the boot drive instead */
|
|
DosDirectory[0] = SysVars->BootDrive + 'A' - 1;
|
|
DosDirectory[1] = ':';
|
|
DosDirectory[2] = '\\';
|
|
DosDirectory[3] = '\0';
|
|
}
|
|
|
|
/* Set the current drive */
|
|
Sda->CurrentDrive = RtlUpperChar(DosDirectory[0]) - 'A';
|
|
|
|
/* Get the directory part of the path and set the current directory */
|
|
Path = strchr(DosDirectory, '\\');
|
|
if (Path != NULL)
|
|
{
|
|
Path++; // Skip the backslash
|
|
strncpy(DosData->CurrentDirectories[Sda->CurrentDrive], Path, DOS_DIR_LENGTH);
|
|
}
|
|
else
|
|
{
|
|
DosData->CurrentDirectories[Sda->CurrentDrive][0] = '\0';
|
|
}
|
|
|
|
/* Set the current PSP to the system PSP */
|
|
Sda->CurrentPsp = SYSTEM_PSP;
|
|
|
|
/* Initialize the SFT */
|
|
Sft = (PDOS_SFT)FAR_POINTER(SysVars->FirstSft);
|
|
Sft->Link = 0xFFFFFFFF;
|
|
Sft->NumDescriptors = DOS_SFT_SIZE;
|
|
|
|
for (i = 0; i < Sft->NumDescriptors; i++)
|
|
{
|
|
/* Clear the file descriptor entry */
|
|
RtlZeroMemory(&Sft->FileDescriptors[i], sizeof(DOS_FILE_DESCRIPTOR));
|
|
}
|
|
|
|
/* Initialize memory management */
|
|
DosInitializeMemory();
|
|
|
|
/* Initialize the callback context */
|
|
InitializeContext(&DosContext, DOS_CODE_SEGMENT, 0x0000);
|
|
|
|
/* Register the DOS 32-bit Interrupts */
|
|
RegisterDosInt32(0x20, DosInt20h );
|
|
RegisterDosInt32(0x21, DosInt21h );
|
|
// RegisterDosInt32(0x22, DosInt22h ); // Termination
|
|
RegisterDosInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
|
|
// RegisterDosInt32(0x24, DosInt24h ); // Critical Error
|
|
RegisterDosInt32(0x25, DosAbsoluteRead ); // Absolute Disk Read
|
|
RegisterDosInt32(0x26, DosAbsoluteWrite ); // Absolute Disk Write
|
|
RegisterDosInt32(0x27, DosInt27h ); // Terminate and Stay Resident
|
|
RegisterDosInt32(0x28, DosIdle ); // DOS Idle Interrupt
|
|
RegisterDosInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
|
|
RegisterDosInt32(0x2F, DosInt2Fh ); // Multiplex Interrupt
|
|
|
|
/* Unimplemented DOS interrupts */
|
|
RegisterDosInt32(0x2A, DosInt2Ah); // DOS Critical Sections / Network
|
|
// RegisterDosInt32(0x2E, NULL); // COMMAND.COM "Reload Transient"
|
|
// COMMAND.COM adds support for INT 2Fh, AX=AE00h and AE01h "Installable Command - Installation Check & Execute"
|
|
// COMMAND.COM adds support for INT 2Fh, AX=5500h "COMMAND.COM Interface"
|
|
|
|
/* Reserved DOS interrupts */
|
|
RegisterDosInt32(0x2B, NULL);
|
|
RegisterDosInt32(0x2C, NULL);
|
|
RegisterDosInt32(0x2D, NULL);
|
|
|
|
/* Initialize country data */
|
|
DosCountryInitialize();
|
|
|
|
/* Load the CON driver */
|
|
ConDrvInitialize();
|
|
|
|
/* Load the XMS driver (HIMEM) */
|
|
XmsInitialize();
|
|
|
|
/* Load the EMS driver */
|
|
if (!EmsDrvInitialize(EMS_SEGMENT, EMS_TOTAL_PAGES))
|
|
{
|
|
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 */
|
|
DosInitializeUmb();
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* EOF */
|