* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
*/
-#include "ntvdm.h"
+/* INCLUDES *******************************************************************/
-WORD CurrentPsp = SYSTEM_PSP, LastError = 0;
+#define NDEBUG
+
+#include "emulator.h"
+#include "dos.h"
+
+#include "bios.h"
+#include "bop.h"
+#include "int32.h"
+#include "registers.h"
+
+/* PRIVATE VARIABLES **********************************************************/
+
+static WORD CurrentPsp = SYSTEM_PSP;
+static WORD DosLastError = 0;
+static DWORD DiskTransferArea;
+static BYTE CurrentDrive;
+static CHAR LastDrive = 'E';
+static CHAR CurrentDirectories[NUM_DRIVES][DOS_DIR_LENGTH];
+static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
+static WORD DosSftRefCount[DOS_SFT_SIZE];
+static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
+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 */
+static BOOL IsConsoleHandle(HANDLE hHandle)
+{
+ DWORD dwMode;
+
+ /* Check whether the handle may be that of a console... */
+ if ((GetFileType(hHandle) & FILE_TYPE_CHAR) == 0) return FALSE;
+
+ /*
+ * It may be. Perform another test... The idea comes from the
+ * MSDN description of the WriteConsole API:
+ *
+ * "WriteConsole fails if it is used with a standard handle
+ * that is redirected to a file. If an application processes
+ * multilingual output that can be redirected, determine whether
+ * the output handle is a console handle (one method is to call
+ * the GetConsoleMode function and check whether it succeeds).
+ * If the handle is a console handle, call WriteConsole. If the
+ * handle is not a console handle, the output is redirected and
+ * you should call WriteFile to perform the I/O."
+ */
+ return GetConsoleMode(hHandle, &dwMode);
+}
static VOID DosCombineFreeBlocks(WORD StartBlock)
{
}
}
-static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
+static WORD DosCopyEnvironmentBlock(WORD SourceSegment, LPCSTR ProgramName)
{
PCHAR Ptr, SourceBuffer, DestBuffer = NULL;
ULONG TotalSize = 0;
WORD DestSegment;
- Ptr = SourceBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(SourceSegment, 0));
+ Ptr = SourceBuffer = (PCHAR)SEG_OFF_TO_PTR(SourceSegment, 0);
/* Calculate the size of the environment block */
while (*Ptr)
}
TotalSize++;
+ /* Add the string buffer size */
+ TotalSize += strlen(ProgramName) + 1;
+
/* Allocate the memory for the environment block */
- DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
+ DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
if (!DestSegment) return 0;
Ptr = SourceBuffer;
- DestBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DestSegment, 0));
+ DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0);
while (*Ptr)
{
/* Copy the string */
strcpy(DestBuffer, Ptr);
/* Advance to the next string */
+ DestBuffer += strlen(Ptr);
Ptr += strlen(Ptr) + 1;
- DestBuffer += strlen(Ptr) + 1;
+
+ /* Put a zero after the string */
+ *(DestBuffer++) = 0;
}
/* Set the final zero */
- *DestBuffer = 0;
+ *(DestBuffer++) = 0;
+
+ /* Copy the program name after the environment block */
+ strcpy(DestBuffer, ProgramName);
return DestSegment;
}
+static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
+{
+ PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
+
+ /* Just set the owner */
+ Mcb->OwnerPsp = NewOwner;
+}
+
+static WORD DosOpenHandle(HANDLE Handle)
+{
+ BYTE i;
+ WORD DosHandle;
+ PDOS_PSP PspBlock;
+ LPBYTE HandleTable;
+
+ /* The system PSP has no handle table */
+ if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
+
+ /* Get a pointer to the handle table */
+ PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+ HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
+
+ /* Find a free entry in the JFT */
+ for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
+ {
+ if (HandleTable[DosHandle] == 0xFF) break;
+ }
+
+ /* If there are no free entries, fail */
+ if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
+
+ /* Check if the handle is already in the SFT */
+ for (i = 0; i < DOS_SFT_SIZE; i++)
+ {
+ /* Check if this is the same handle */
+ if (DosSystemFileTable[i] != Handle) continue;
+
+ /* Already in the table, reference it */
+ DosSftRefCount[i]++;
+
+ /* Set the JFT entry to that SFT index */
+ HandleTable[DosHandle] = i;
+
+ /* Return the new handle */
+ return DosHandle;
+ }
+
+ /* Add the handle to the SFT */
+ for (i = 0; i < DOS_SFT_SIZE; i++)
+ {
+ /* Make sure this is an empty table entry */
+ if (DosSystemFileTable[i] != INVALID_HANDLE_VALUE) continue;
+
+ /* Initialize the empty table entry */
+ DosSystemFileTable[i] = Handle;
+ DosSftRefCount[i] = 1;
+
+ /* Set the JFT entry to that SFT index */
+ HandleTable[DosHandle] = i;
+
+ /* Return the new handle */
+ return DosHandle;
+ }
+
+ /* The SFT is full */
+ return INVALID_DOS_HANDLE;
+}
+
+static HANDLE DosGetRealHandle(WORD DosHandle)
+{
+ PDOS_PSP PspBlock;
+ LPBYTE HandleTable;
+
+ /* The system PSP has no handle table */
+ if (CurrentPsp == SYSTEM_PSP) return INVALID_HANDLE_VALUE;
+
+ /* Get a pointer to the handle table */
+ PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+ HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
+
+ /* Make sure the handle is open */
+ if (HandleTable[DosHandle] == 0xFF) return INVALID_HANDLE_VALUE;
+
+ /* Return the Win32 handle */
+ return DosSystemFileTable[HandleTable[DosHandle]];
+}
+
+static VOID DosCopyHandleTable(LPBYTE DestinationTable)
+{
+ INT i;
+ PDOS_PSP PspBlock;
+ LPBYTE SourceTable;
+
+ /* Clear the table first */
+ for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF;
+
+ /* Check if this is the initial process */
+ if (CurrentPsp == SYSTEM_PSP)
+ {
+ /* Set up the standard I/O devices */
+ for (i = 0; i <= 2; i++)
+ {
+ /* Set the index in the SFT */
+ DestinationTable[i] = (BYTE)i;
+
+ /* Increase the reference count */
+ DosSftRefCount[i]++;
+ }
+
+ /* Done */
+ return;
+ }
+
+ /* Get the parent PSP block and handle table */
+ PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+ SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
+
+ /* Copy the first 20 handles into the new table */
+ for (i = 0; i < 20; i++)
+ {
+ DestinationTable[i] = SourceTable[i];
+
+ /* Increase the reference count */
+ DosSftRefCount[SourceTable[i]]++;
+ }
+}
+
+/* PUBLIC FUNCTIONS ***********************************************************/
+
WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
{
WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
PDOS_MCB CurrentMcb, NextMcb;
+ BOOLEAN SearchUmb = FALSE;
+
+ DPRINT("DosAllocateMemory: Size 0x%04X\n", Size);
+
+ if (DosUmbLinked && (DosAllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)))
+ {
+ /* Search UMB first */
+ Segment = UMB_START_SEGMENT;
+ SearchUmb = TRUE;
+ }
while (TRUE)
{
/* Make sure it's valid */
if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
{
+ DPRINT("The DOS memory arena is corrupted!\n");
+ DosLastError = ERROR_ARENA_TRASHED;
return 0;
}
/* Check if this block is big enough */
if (CurrentMcb->Size < Size) goto Next;
- /* It is, update the smallest found so far */
- if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
+ switch (DosAllocStrategy & 0x3F)
{
- Result = Segment;
+ case DOS_ALLOC_FIRST_FIT:
+ {
+ /* For first fit, stop immediately */
+ Result = Segment;
+ goto Done;
+ }
+
+ case DOS_ALLOC_BEST_FIT:
+ {
+ /* For best fit, update the smallest block found so far */
+ if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
+ {
+ Result = Segment;
+ }
+
+ break;
+ }
+
+ case DOS_ALLOC_LAST_FIT:
+ {
+ /* For last fit, make the current block the result, but keep searching */
+ Result = Segment;
+ break;
+ }
}
Next:
/* If this was the last MCB in the chain, quit */
- if (CurrentMcb->BlockType == 'Z') break;
+ if (CurrentMcb->BlockType == 'Z')
+ {
+ /* Check if nothing was found while searching through UMBs */
+ if ((Result == 0) && SearchUmb && (DosAllocStrategy & DOS_ALLOC_HIGH_LOW))
+ {
+ /* Search low memory */
+ Segment = FIRST_MCB_SEGMENT;
+ continue;
+ }
+
+ break;
+ }
/* Otherwise, update the segment and continue */
Segment += CurrentMcb->Size + 1;
}
+Done:
+
/* If we didn't find a free block, return 0 */
if (Result == 0)
{
+ DosLastError = ERROR_NOT_ENOUGH_MEMORY;
if (MaxAvailable) *MaxAvailable = MaxSize;
return 0;
}
/* Take ownership of the block */
CurrentMcb->OwnerPsp = CurrentPsp;
- return Result;
+ /* Return the segment of the data portion of the block */
+ return Result + 1;
}
-WORD DosResizeMemory(WORD Segment, WORD NewSize)
+BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
{
- WORD ReturnSize = 0, NextSegment;
+ BOOLEAN Success = TRUE;
+ WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
+ DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
+ BlockData,
+ NewSize);
+
/* Make sure this is a valid, allocated block */
if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
{
- return 0;
+ Success = FALSE;
+ DosLastError = ERROR_INVALID_HANDLE;
+ goto Done;
}
- /* Check if need to expand or contract the block */
+ ReturnSize = Mcb->Size;
+
+ /* Check if we need to expand or contract the block */
if (NewSize > Mcb->Size)
{
/* We can't expand the last block */
- if (Mcb->BlockType != 'M') return Mcb->Size;
+ if (Mcb->BlockType != 'M')
+ {
+ Success = FALSE;
+ goto Done;
+ }
- ReturnSize = Mcb->Size;
-
/* Get the pointer and segment of the next MCB */
NextSegment = Segment + Mcb->Size + 1;
NextMcb = SEGMENT_TO_MCB(NextSegment);
/* Make sure the next segment is free */
- if (NextMcb->OwnerPsp != 0) return Mcb->Size;
+ if (NextMcb->OwnerPsp != 0)
+ {
+ DPRINT("Cannot expand memory block: next segment is not free!\n");
+ DosLastError = ERROR_NOT_ENOUGH_MEMORY;
+ Success = FALSE;
+ goto Done;
+ }
/* Combine this free block with adjoining free blocks */
DosCombineFreeBlocks(NextSegment);
/* Check if the block is larger than requested */
if (Mcb->Size > NewSize)
{
+ DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
+ Mcb->Size,
+ NewSize);
+
/* It is, split it into two blocks */
NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
}
else if (NewSize < Mcb->Size)
{
+ DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
+ Mcb->Size,
+ NewSize);
+
/* Just split the block */
NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
NextMcb->BlockType = Mcb->BlockType;
Mcb->Size = NewSize;
}
- return ReturnSize;
+Done:
+ /* Check if the operation failed */
+ if (!Success)
+ {
+ DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
+ ReturnSize);
+
+ /* Return the maximum possible size */
+ if (MaxAvailable) *MaxAvailable = ReturnSize;
+ }
+
+ return Success;
}
-BOOLEAN DosFreeMemory(WORD Segment)
+BOOLEAN DosFreeMemory(WORD BlockData)
{
- PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
+ PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
+
+ DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
/* Make sure the MCB is valid */
- if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') return FALSE;
+ if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
+ {
+ DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
+ return FALSE;
+ }
/* Mark the block as free */
Mcb->OwnerPsp = 0;
return TRUE;
}
-WORD DosCreateFile(LPCSTR FilePath)
+BOOLEAN DosLinkUmb(VOID)
{
- // TODO: NOT IMPLEMENTED
- return 0;
-}
+ DWORD Segment = FIRST_MCB_SEGMENT;
+ PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
-WORD DosOpenFile(LPCSTR FilePath)
-{
- // TODO: NOT IMPLEMENTED
- return 0;
+ DPRINT("Linking UMB\n");
+
+ /* Check if UMBs are already linked */
+ if (DosUmbLinked) return FALSE;
+
+ /* Find the last block */
+ while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
+ {
+ Segment += Mcb->Size + 1;
+ Mcb = SEGMENT_TO_MCB(Segment);
+ }
+
+ /* Make sure it's valid */
+ if (Mcb->BlockType != 'Z') return FALSE;
+
+ /* Connect the MCB with the UMB chain */
+ Mcb->BlockType = 'M';
+
+ DosUmbLinked = TRUE;
+ return TRUE;
}
-VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
+BOOLEAN DosUnlinkUmb(VOID)
{
- INT i;
- PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
- LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
+ DWORD Segment = FIRST_MCB_SEGMENT;
+ PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
- ZeroMemory(PspBlock, sizeof(DOS_PSP));
+ DPRINT("Unlinking UMB\n");
- /* Set the exit interrupt */
- PspBlock->Exit[0] = 0xCD; // int 0x20
- PspBlock->Exit[1] = 0x20;
+ /* Check if UMBs are already unlinked */
+ if (!DosUmbLinked) return FALSE;
+
+ /* Find the block preceding the MCB that links it with the UMB chain */
+ while (Segment <= 0xFFFF)
+ {
+ if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
+ {
+ /* This is the last non-UMB segment */
+ break;
+ }
- /* Set the program size */
- PspBlock->MemSize = ProgramSize;
+ /* Advance to the next MCB */
+ Segment += Mcb->Size + 1;
+ Mcb = SEGMENT_TO_MCB(Segment);
+ }
- /* Save the interrupt vectors */
- PspBlock->TerminateAddress = IntVecTable[0x22];
- PspBlock->BreakAddress = IntVecTable[0x23];
- PspBlock->CriticalAddress = IntVecTable[0x24];
+ /* Mark the MCB as the last MCB */
+ Mcb->BlockType = 'Z';
- /* Set the parent PSP */
- PspBlock->ParentPsp = CurrentPsp;
+ DosUmbLinked = FALSE;
+ return TRUE;
+}
- /* Initialize the handle table */
- for (i = 0; i < 20; i++) PspBlock->HandleTable[i] = 0xFF;
+WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes)
+{
+ HANDLE FileHandle;
+ WORD DosHandle;
- /* Did we get an environment segment? */
- if (!Environment)
+ DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
+ FilePath,
+ Attributes);
+
+ /* Create the file */
+ FileHandle = CreateFileA(FilePath,
+ GENERIC_READ | GENERIC_WRITE,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
+ NULL,
+ CREATE_ALWAYS,
+ Attributes,
+ NULL);
+
+ if (FileHandle == INVALID_HANDLE_VALUE)
{
- /* No, copy the one from the parent */
- Environment = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
- ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
- : SYSTEM_ENV_BLOCK);
+ /* Return the error code */
+ return (WORD)GetLastError();
}
- PspBlock->EnvBlock = Environment;
-
- /* Set the handle table pointers to the internal handle table */
- PspBlock->HandleTableSize = 20;
- PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
+ /* Open the DOS handle */
+ DosHandle = DosOpenHandle(FileHandle);
- /* Set the DOS version */
- PspBlock->DosVersion = DOS_VERSION;
+ if (DosHandle == INVALID_DOS_HANDLE)
+ {
+ /* Close the handle */
+ CloseHandle(FileHandle);
- /* Set the far call opcodes */
- PspBlock->FarCall[0] = 0xCD; // int 0x21
- PspBlock->FarCall[1] = 0x21;
- PspBlock->FarCall[2] = 0xCB; // retf
+ /* Return the error code */
+ return ERROR_TOO_MANY_OPEN_FILES;
+ }
- /* Set the command line */
- PspBlock->CommandLineSize = strlen(CommandLine);
- RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
- PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
+ /* It was successful */
+ *Handle = DosHandle;
+ return ERROR_SUCCESS;
}
-BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
+WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode)
{
- BOOLEAN Success = FALSE;
- HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
- LPBYTE Address = NULL;
- LPSTR ProgramFilePath, Parameters[128];
- CHAR CommandLineCopy[128];
- INT ParamCount = 0;
- WORD i, Segment, FileSize, ExeSize;
- PIMAGE_DOS_HEADER Header;
- PDWORD RelocationTable;
- PWORD RelocWord;
+ HANDLE FileHandle;
+ ACCESS_MASK Access = 0;
+ WORD DosHandle;
- /* Save a copy of the command line */
- strcpy(CommandLineCopy, CommandLine);
-
- /* Get the file name of the executable */
- ProgramFilePath = strtok(CommandLineCopy, " \t");
+ DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
+ FilePath,
+ AccessMode);
- /* Load the parameters in the local array */
- while ((ParamCount < 256)
- && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
+ /* Parse the access mode */
+ switch (AccessMode & 3)
{
- ParamCount++;
+ case 0:
+ {
+ /* Read-only */
+ Access = GENERIC_READ;
+ break;
+ }
+
+ case 1:
+ {
+ /* Write only */
+ Access = GENERIC_WRITE;
+ break;
+ }
+
+ case 2:
+ {
+ /* Read and write */
+ Access = GENERIC_READ | GENERIC_WRITE;
+ break;
+ }
+
+ default:
+ {
+ /* Invalid */
+ return ERROR_INVALID_PARAMETER;
+ }
}
- /* Open a handle to the executable */
- FileHandle = CreateFileA(ProgramFilePath,
- GENERIC_READ,
- 0,
+ /* Open the file */
+ FileHandle = CreateFileA(FilePath,
+ Access,
+ FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
- if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
- /* Get the file size */
- FileSize = GetFileSize(FileHandle, NULL);
-
- /* Create a mapping object for the file */
- FileMapping = CreateFileMapping(FileHandle,
- NULL,
- PAGE_READONLY,
- 0,
- 0,
- NULL);
- if (FileMapping == NULL) goto Cleanup;
+ if (FileHandle == INVALID_HANDLE_VALUE)
+ {
+ /* Return the error code */
+ return (WORD)GetLastError();
+ }
- /* Map the file into memory */
- Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
- if (Address == NULL) goto Cleanup;
+ /* Open the DOS handle */
+ DosHandle = DosOpenHandle(FileHandle);
- /* Check if this is an EXE file or a COM file */
- if (Address[0] == 'M' && Address[1] == 'Z')
+ if (DosHandle == INVALID_DOS_HANDLE)
{
- /* EXE file */
+ /* Close the handle */
+ CloseHandle(FileHandle);
- /* Get the MZ header */
- Header = (PIMAGE_DOS_HEADER)Address;
+ /* Return the error code */
+ return ERROR_TOO_MANY_OPEN_FILES;
+ }
- // TODO: Verify checksum and executable!
+ /* It was successful */
+ *Handle = DosHandle;
+ return ERROR_SUCCESS;
+}
- /* Get the base size of the file, in paragraphs (rounded up) */
- ExeSize = (((Header->e_cp - 1) << 8) + Header->e_cblp + 0x0F) >> 4;
+WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead)
+{
+ WORD Result = ERROR_SUCCESS;
+ DWORD BytesRead32 = 0;
+ HANDLE Handle = DosGetRealHandle(FileHandle);
- /* Loop from the maximum to the minimum number of extra paragraphs */
- for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--)
- {
- /* Try to allocate that much memory */
- Segment = DosAllocateMemory(ExeSize + (sizeof(DOS_PSP) >> 4) + i, NULL);
- if (Segment != 0) break;
- }
+ DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle, Count);
- /* Check if at least the lowest allocation was successful */
- if (Segment == 0) goto Cleanup;
+ /* Make sure the handle is valid */
+ if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
- /* Initialize the PSP */
- DosInitializePsp(Segment,
- CommandLine, ExeSize + (sizeof(DOS_PSP) >> 4) + i,
- EnvBlock);
+ /* Read the file */
+ if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
+ {
+ /* Store the error code */
+ Result = (WORD)GetLastError();
+ }
- /* Copy the program to Segment:0100 */
- RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
- + TO_LINEAR(Segment, 0x100)),
- Address + (Header->e_cparhdr << 4),
- FileSize - (Header->e_cparhdr << 4));
+ /* The number of bytes read is always 16-bit */
+ *BytesRead = LOWORD(BytesRead32);
- /* Get the relocation table */
- RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
+ /* Return the error code */
+ return Result;
+}
- /* Perform relocations */
- for (i = 0; i < Header->e_crlc; i++)
- {
- /* Get a pointer to the word that needs to be patched */
- RelocWord = (PWORD)((ULONG_PTR)BaseAddress
- + TO_LINEAR(Segment + HIWORD(RelocationTable[i]),
- 0x100 + LOWORD(RelocationTable[i])));
+WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritten)
+{
+ WORD Result = ERROR_SUCCESS;
+ DWORD BytesWritten32 = 0;
+ HANDLE Handle = DosGetRealHandle(FileHandle);
+ WORD i;
- /* Add the number of the EXE segment to it */
- *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
+ DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
+ FileHandle,
+ Count);
+
+ /* Make sure the handle is valid */
+ if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
+
+ if (IsConsoleHandle(Handle))
+ {
+ for (i = 0; i < Count; i++)
+ {
+ /* Call the BIOS to print the character */
+ BiosPrintCharacter(((LPBYTE)Buffer)[i], DOS_CHAR_ATTRIBUTE, Bda->VideoPage);
+ BytesWritten32++;
+ }
+ }
+ else
+ {
+ /* Write the file */
+ if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
+ {
+ /* Store the error code */
+ Result = (WORD)GetLastError();
}
+ }
- /* Set the initial segment registers */
- EmulatorSetRegister(EMULATOR_REG_DS, Segment);
- EmulatorSetRegister(EMULATOR_REG_ES, Segment);
+ /* The number of bytes written is always 16-bit */
+ *BytesWritten = LOWORD(BytesWritten32);
+
+ /* Return the error code */
+ return Result;
+}
+
+WORD DosSeekFile(WORD FileHandle, LONG Offset, BYTE Origin, LPDWORD NewOffset)
+{
+ WORD Result = ERROR_SUCCESS;
+ DWORD FilePointer;
+ HANDLE Handle = DosGetRealHandle(FileHandle);
+
+ DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n",
+ FileHandle,
+ Offset,
+ Origin);
+
+ /* Make sure the handle is valid */
+ if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
+
+ /* Check if the origin is valid */
+ if (Origin != FILE_BEGIN && Origin != FILE_CURRENT && Origin != FILE_END)
+ {
+ return ERROR_INVALID_FUNCTION;
+ }
+
+ /* Move the file pointer */
+ FilePointer = SetFilePointer(Handle, Offset, NULL, Origin);
+
+ /* Check if there's a possibility the operation failed */
+ if (FilePointer == INVALID_SET_FILE_POINTER)
+ {
+ /* Get the real error code */
+ Result = (WORD)GetLastError();
+ }
+
+ if (Result != ERROR_SUCCESS)
+ {
+ /* The operation did fail */
+ return Result;
+ }
+
+ /* Return the file pointer, if requested */
+ if (NewOffset) *NewOffset = FilePointer;
+
+ /* Return success */
+ return ERROR_SUCCESS;
+}
+
+BOOLEAN DosFlushFileBuffers(WORD FileHandle)
+{
+ HANDLE Handle = DosGetRealHandle(FileHandle);
+
+ /* Make sure the handle is valid */
+ if (Handle == INVALID_HANDLE_VALUE) return FALSE;
+
+ /*
+ * No need to check whether the handle is a console handle since
+ * FlushFileBuffers() automatically does this check and calls
+ * FlushConsoleInputBuffer() for us.
+ */
+ // if (IsConsoleHandle(Handle))
+ // return (BOOLEAN)FlushConsoleInputBuffer(Handle);
+ // else
+ return (BOOLEAN)FlushFileBuffers(Handle);
+}
+
+BOOLEAN DosDuplicateHandle(WORD OldHandle, WORD NewHandle)
+{
+ BYTE SftIndex;
+ PDOS_PSP PspBlock;
+ LPBYTE HandleTable;
+
+ DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
+ OldHandle,
+ NewHandle);
+
+ /* The system PSP has no handle table */
+ if (CurrentPsp == SYSTEM_PSP) return FALSE;
+
+ /* Get a pointer to the handle table */
+ PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+ HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
+
+ /* Make sure the old handle is open */
+ if (HandleTable[OldHandle] == 0xFF) return FALSE;
+
+ /* Check if the new handle is open */
+ if (HandleTable[NewHandle] != 0xFF)
+ {
+ /* Close it */
+ DosCloseHandle(NewHandle);
+ }
+
+ /* Increment the reference count of the SFT entry */
+ SftIndex = HandleTable[OldHandle];
+ DosSftRefCount[SftIndex]++;
+
+ /* Make the new handle point to that SFT entry */
+ HandleTable[NewHandle] = SftIndex;
+
+ /* Return success */
+ return TRUE;
+}
+
+BOOLEAN DosCloseHandle(WORD DosHandle)
+{
+ BYTE SftIndex;
+ PDOS_PSP PspBlock;
+ LPBYTE HandleTable;
+
+ DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle);
+
+ /* The system PSP has no handle table */
+ if (CurrentPsp == SYSTEM_PSP) return FALSE;
+
+ /* Get a pointer to the handle table */
+ PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+ HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
+
+ /* Make sure the handle is open */
+ if (HandleTable[DosHandle] == 0xFF) return FALSE;
+
+ /* Decrement the reference count of the SFT entry */
+ SftIndex = HandleTable[DosHandle];
+ DosSftRefCount[SftIndex]--;
+
+ /* Check if the reference count fell to zero */
+ if (!DosSftRefCount[SftIndex])
+ {
+ /* Close the file, it's no longer needed */
+ CloseHandle(DosSystemFileTable[SftIndex]);
+
+ /* Clear the handle */
+ DosSystemFileTable[SftIndex] = INVALID_HANDLE_VALUE;
+ }
+
+ /* Clear the entry in the JFT */
+ HandleTable[DosHandle] = 0xFF;
+
+ return TRUE;
+}
+
+BOOLEAN DosChangeDrive(BYTE Drive)
+{
+ WCHAR DirectoryPath[DOS_CMDLINE_LENGTH];
+
+ /* Make sure the drive exists */
+ if (Drive > (LastDrive - 'A')) return FALSE;
+
+ /* Find the path to the new current directory */
+ swprintf(DirectoryPath, L"%c\\%S", Drive + 'A', CurrentDirectories[Drive]);
+
+ /* Change the current directory of the process */
+ if (!SetCurrentDirectory(DirectoryPath)) return FALSE;
+
+ /* Set the current drive */
+ CurrentDrive = Drive;
+
+ /* Return success */
+ return TRUE;
+}
+
+BOOLEAN DosChangeDirectory(LPSTR Directory)
+{
+ BYTE DriveNumber;
+ DWORD Attributes;
+ LPSTR Path;
+
+ /* Make sure the directory path is not too long */
+ if (strlen(Directory) >= DOS_DIR_LENGTH)
+ {
+ DosLastError = ERROR_PATH_NOT_FOUND;
+ return FALSE;
+ }
+
+ /* Get the drive number */
+ DriveNumber = Directory[0] - 'A';
+
+ /* Make sure the drive exists */
+ if (DriveNumber > (LastDrive - 'A'))
+ {
+ DosLastError = ERROR_PATH_NOT_FOUND;
+ return FALSE;
+ }
+
+ /* 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))
+ {
+ DosLastError = ERROR_PATH_NOT_FOUND;
+ return FALSE;
+ }
+
+ /* Check if this is the current drive */
+ if (DriveNumber == CurrentDrive)
+ {
+ /* Change the directory */
+ if (!SetCurrentDirectoryA(Directory))
+ {
+ DosLastError = LOWORD(GetLastError());
+ return FALSE;
+ }
+ }
+
+ /* Get the directory part of the path */
+ Path = strchr(Directory, '\\');
+ if (Path != NULL)
+ {
+ /* Skip the backslash */
+ Path++;
+ }
+
+ /* Set the directory for the drive */
+ if (Path != NULL)
+ {
+ strncpy(CurrentDirectories[DriveNumber], Path, DOS_DIR_LENGTH);
+ }
+ else
+ {
+ CurrentDirectories[DriveNumber][0] = '\0';
+ }
+
+ /* Return success */
+ return TRUE;
+}
+
+VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
+{
+ PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
+ LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
+
+ ZeroMemory(PspBlock, sizeof(DOS_PSP));
+
+ /* Set the exit interrupt */
+ PspBlock->Exit[0] = 0xCD; // int 0x20
+ PspBlock->Exit[1] = 0x20;
+
+ /* Set the number of the last paragraph */
+ PspBlock->LastParagraph = PspSegment + ProgramSize - 1;
+
+ /* Save the interrupt vectors */
+ PspBlock->TerminateAddress = IntVecTable[0x22];
+ PspBlock->BreakAddress = IntVecTable[0x23];
+ PspBlock->CriticalAddress = IntVecTable[0x24];
+
+ /* Set the parent PSP */
+ PspBlock->ParentPsp = CurrentPsp;
+
+ /* Copy the parent handle table */
+ DosCopyHandleTable(PspBlock->HandleTable);
+
+ /* Set the environment block */
+ PspBlock->EnvBlock = Environment;
+
+ /* Set the handle table pointers to the internal handle table */
+ PspBlock->HandleTableSize = 20;
+ PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
+
+ /* Set the DOS version */
+ PspBlock->DosVersion = DOS_VERSION;
+
+ /* Set the far call opcodes */
+ PspBlock->FarCall[0] = 0xCD; // int 0x21
+ PspBlock->FarCall[1] = 0x21;
+ PspBlock->FarCall[2] = 0xCB; // retf
+
+ /* Set the command line */
+ PspBlock->CommandLineSize = (BYTE)min(strlen(CommandLine), DOS_CMDLINE_LENGTH - 1);
+ RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
+ PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
+}
+
+BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
+{
+ BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
+ HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
+ 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;
+ DWORD i, FileSize, ExeSize;
+ PIMAGE_DOS_HEADER Header;
+ PDWORD RelocationTable;
+ PWORD RelocWord;
+
+ DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n",
+ CommandLine,
+ EnvBlock);
+
+ /* Save a copy of the command line */
+ strcpy(CommandLineCopy, CommandLine);
+
+ /* Get the file name of the executable */
+ ProgramFilePath = strtok(CommandLineCopy, " \t");
+
+ /* Load the parameters in the local array */
+ while ((ParamCount < sizeof(Parameters)/sizeof(Parameters[0]))
+ && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
+ {
+ 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,
+ 0,
+ NULL,
+ OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL,
+ NULL);
+ if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
+
+ /* Get the file size */
+ FileSize = GetFileSize(FileHandle, NULL);
+
+ /* Create a mapping object for the file */
+ FileMapping = CreateFileMapping(FileHandle,
+ NULL,
+ PAGE_READONLY,
+ 0,
+ 0,
+ NULL);
+ if (FileMapping == NULL) goto Cleanup;
+
+ /* Map the file into memory */
+ Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
+ if (Address == NULL) goto Cleanup;
+
+ /* Did we get an environment segment? */
+ if (!EnvBlock)
+ {
+ /* Set a flag to know if the environment block was allocated here */
+ AllocatedEnvBlock = TRUE;
+
+ /* No, copy the one from the parent */
+ EnvBlock = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
+ ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
+ : SYSTEM_ENV_BLOCK,
+ ProgramFilePath);
+ }
+
+ /* Check if this is an EXE file or a COM file */
+ if (Address[0] == 'M' && Address[1] == 'Z')
+ {
+ /* EXE file */
+
+ /* Get the MZ header */
+ Header = (PIMAGE_DOS_HEADER)Address;
+
+ /* Get the base size of the file, in paragraphs (rounded up) */
+ ExeSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4;
+
+ /* Add the PSP size, in paragraphs */
+ ExeSize += sizeof(DOS_PSP) >> 4;
+
+ /* Add the maximum size that should be allocated */
+ ExeSize += Header->e_maxalloc;
+
+ /* Make sure it does not pass 0xFFFF */
+ if (ExeSize > 0xFFFF) ExeSize = 0xFFFF;
+
+ /* Reduce the size one by one until the allocation is successful */
+ for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--, ExeSize--)
+ {
+ /* Try to allocate that much memory */
+ Segment = DosAllocateMemory((WORD)ExeSize, NULL);
+ if (Segment != 0) break;
+ }
+
+ /* Check if at least the lowest allocation was successful */
+ if (Segment == 0) goto Cleanup;
+
+ /* Initialize the PSP */
+ DosInitializePsp(Segment,
+ ParamString,
+ (WORD)ExeSize,
+ EnvBlock);
+
+ /* The process owns its own memory */
+ DosChangeMemoryOwner(Segment, Segment);
+ DosChangeMemoryOwner(EnvBlock, Segment);
+
+ /* Copy the program to Segment:0100 */
+ RtlCopyMemory(SEG_OFF_TO_PTR(Segment, 0x100),
+ Address + (Header->e_cparhdr << 4),
+ min(FileSize - (Header->e_cparhdr << 4),
+ (ExeSize << 4) - sizeof(DOS_PSP)));
+
+ /* Get the relocation table */
+ RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
+
+ /* Perform relocations */
+ for (i = 0; i < Header->e_crlc; i++)
+ {
+ /* Get a pointer to the word that needs to be patched */
+ RelocWord = (PWORD)SEG_OFF_TO_PTR(Segment + HIWORD(RelocationTable[i]),
+ 0x100 + LOWORD(RelocationTable[i]));
+
+ /* Add the number of the EXE segment to it */
+ *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
+ }
+
+ /* Set the initial segment registers */
+ setDS(Segment);
+ setES(Segment);
/* Set the stack to the location from the header */
EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
/* Execute */
CurrentPsp = Segment;
- EmulatorExecute(Segment + Header->e_cs, sizeof(DOS_PSP) + Header->e_ip);
+ DiskTransferArea = MAKELONG(0x80, Segment);
+ EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
+ Header->e_ip);
Success = TRUE;
}
{
/* COM file */
- /* Allocate memory for the whole program and the PSP */
- Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4, NULL);
+ /* Find the maximum amount of memory that can be allocated */
+ DosAllocateMemory(0xFFFF, &MaxAllocSize);
+
+ /* Make sure it's enough for the whole program and the PSP */
+ if (((DWORD)MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP))) goto Cleanup;
+
+ /* Allocate all of it */
+ Segment = DosAllocateMemory(MaxAllocSize, NULL);
if (Segment == 0) goto Cleanup;
+ /* The process owns its own memory */
+ DosChangeMemoryOwner(Segment, Segment);
+ DosChangeMemoryOwner(EnvBlock, Segment);
+
/* Copy the program to Segment:0100 */
- RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
- + TO_LINEAR(Segment, 0x100)),
+ RtlCopyMemory(SEG_OFF_TO_PTR(Segment, 0x100),
Address,
FileSize);
/* Initialize the PSP */
DosInitializePsp(Segment,
- CommandLine,
- (FileSize + sizeof(DOS_PSP)) >> 4,
+ ParamString,
+ MaxAllocSize,
EnvBlock);
/* Set the initial segment registers */
- EmulatorSetRegister(EMULATOR_REG_DS, Segment);
- EmulatorSetRegister(EMULATOR_REG_ES, Segment);
+ setDS(Segment);
+ setES(Segment);
/* 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);
EmulatorExecute(Segment, 0x100);
Success = TRUE;
}
Cleanup:
+ if (!Success)
+ {
+ /* It was not successful, cleanup the DOS memory */
+ if (AllocatedEnvBlock) DosFreeMemory(EnvBlock);
+ if (Segment) DosFreeMemory(Segment);
+ }
+
/* Unmap the file*/
if (Address != NULL) UnmapViewOfFile(Address);
VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
{
+ WORD i;
WORD McbSegment = FIRST_MCB_SEGMENT;
PDOS_MCB CurrentMcb;
LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
+ DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
+ Psp,
+ ReturnCode);
+
/* Check if this PSP is it's own parent */
if (PspBlock->ParentPsp == Psp) goto Done;
- // TODO: Close all handles opened by the process
+ for (i = 0; i < PspBlock->HandleTableSize; i++)
+ {
+ /* Close the handle */
+ DosCloseHandle(i);
+ }
/* Free the memory used by the process */
while (TRUE)
if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
/* If this block was allocated by the process, free it */
- if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment);
+ if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment + 1);
/* If this was the last block, quit */
if (CurrentMcb->BlockType == 'Z') break;
- /* Update the segment and continue */
- McbSegment += CurrentMcb->Size + 1;
- }
+ /* Update the segment and continue */
+ McbSegment += CurrentMcb->Size + 1;
+ }
+
+Done:
+ /* Restore the interrupt vectors */
+ IntVecTable[0x22] = PspBlock->TerminateAddress;
+ IntVecTable[0x23] = PspBlock->BreakAddress;
+ IntVecTable[0x24] = PspBlock->CriticalAddress;
+
+ /* Update the current PSP */
+ if (Psp == CurrentPsp)
+ {
+ CurrentPsp = PspBlock->ParentPsp;
+ if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
+ }
+
+ /* Save the return code - Normal termination */
+ DosErrorLevel = MAKEWORD(ReturnCode, 0x00);
+
+ /* Return control to the parent process */
+ EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
+ LOWORD(PspBlock->TerminateAddress));
+}
+
+CHAR DosReadCharacter(VOID)
+{
+ CHAR Character = '\0';
+ WORD BytesRead;
+
+ if (IsConsoleHandle(DosGetRealHandle(DOS_INPUT_HANDLE)))
+ {
+ /* Call the BIOS */
+ Character = LOBYTE(BiosGetCharacter());
+ }
+ else
+ {
+ /* Use the file reading function */
+ DosReadFile(DOS_INPUT_HANDLE, &Character, sizeof(CHAR), &BytesRead);
+ }
+
+ return Character;
+}
+
+BOOLEAN DosCheckInput(VOID)
+{
+ HANDLE Handle = DosGetRealHandle(DOS_INPUT_HANDLE);
+
+ if (IsConsoleHandle(Handle))
+ {
+ /* Call the BIOS */
+ return (BiosPeekCharacter() != 0xFFFF);
+ }
+ else
+ {
+ DWORD FileSizeHigh;
+ DWORD FileSize = GetFileSize(Handle, &FileSizeHigh);
+ LONG LocationHigh = 0;
+ DWORD Location = SetFilePointer(Handle, 0, &LocationHigh, FILE_CURRENT);
+
+ return ((Location != FileSize) || (LocationHigh != FileSizeHigh));
+ }
+}
+
+VOID DosPrintCharacter(CHAR Character)
+{
+ WORD BytesWritten;
+
+ /* Use the file writing function */
+ DosWriteFile(DOS_OUTPUT_HANDLE, &Character, sizeof(CHAR), &BytesWritten);
+}
+
+BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
+{
+ HANDLE Handle = DosGetRealHandle(FileHandle);
+
+ if (Handle == INVALID_HANDLE_VALUE)
+ {
+ /* Doesn't exist */
+ DosLastError = ERROR_FILE_NOT_FOUND;
+ return FALSE;
+ }
+
+ switch (ControlCode)
+ {
+ /* Get Device Information */
+ case 0x00:
+ {
+ WORD InfoWord = 0;
+
+ if (Handle == DosSystemFileTable[0])
+ {
+ /* Console input */
+ InfoWord |= 1 << 0;
+ }
+ else if (Handle == DosSystemFileTable[1])
+ {
+ /* Console output */
+ InfoWord |= 1 << 1;
+ }
+
+ /* It is a character device */
+ InfoWord |= 1 << 7;
+
+ /* Return the device information word */
+ setDX(InfoWord);
+ return TRUE;
+ }
+
+ /* Unsupported control code */
+ default:
+ {
+ DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode);
+
+ DosLastError = ERROR_INVALID_PARAMETER;
+ return FALSE;
+ }
+ }
+}
+
+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 WINAPI DosInt21h(LPWORD Stack)
+{
+ BYTE Character;
+ SYSTEMTIME SystemTime;
+ PCHAR String;
+ PDOS_INPUT_BUFFER InputBuffer;
+
+ /* Check the value in the AH register */
+ switch (getAH())
+ {
+ /* Terminate Program */
+ case 0x00:
+ {
+ DosTerminateProcess(Stack[STACK_CS], 0);
+ break;
+ }
+
+ /* Read Character from STDIN with Echo */
+ case 0x01:
+ {
+ Character = DosReadCharacter();
+ DosPrintCharacter(Character);
+
+ /* Let the BOP repeat if needed */
+ if (getCF()) break;
+
+ setAL(Character);
+ break;
+ }
+
+ /* Write Character to STDOUT */
+ case 0x02:
+ {
+ Character = getDL();
+ DosPrintCharacter(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();
+
+ if (Character != 0xFF)
+ {
+ /* Output */
+ DosPrintCharacter(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());
+ }
+ else
+ {
+ /* No character available */
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
+ setAL(0x00);
+ }
+ }
+
+ break;
+ }
+
+ /* Character Input without Echo */
+ case 0x07:
+ case 0x08:
+ {
+ Character = DosReadCharacter();
+
+ /* Let the BOP repeat if needed */
+ if (getCF()) break;
+
+ setAL(Character);
+ break;
+ }
+
+ /* Write string to STDOUT */
+ case 0x09:
+ {
+ String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
+
+ while (*String != '$')
+ {
+ DosPrintCharacter(*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('$');
+ break;
+ }
+
+ /* Read Buffered Input */
+ case 0x0A:
+ {
+ InputBuffer = (PDOS_INPUT_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
+
+ while (Stack[STACK_COUNTER] < InputBuffer->MaxLength)
+ {
+ /* Try to read a character */
+ Character = DosReadCharacter();
+
+ /* If it's not ready yet, let the BOP repeat */
+ if (getCF()) break;
+
+ /* Echo the character and append it to the buffer */
+ DosPrintCharacter(Character);
+ InputBuffer->Buffer[Stack[STACK_COUNTER]] = Character;
+
+ if (Character == '\r') break;
+ Stack[STACK_COUNTER]++;
+ }
+
+ /* Update the length */
+ InputBuffer->Length = Stack[STACK_COUNTER];
+ 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); // Maybe just create a DosFlushInputBuffer...
+
+ /*
+ * 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)
+ {
+ setAH(InputFunction);
+ /*
+ * Instead of calling ourselves really recursively as in:
+ * DosInt21h(Stack);
+ * prefer resetting the CF flag to let the BOP repeat.
+ */
+ setCF(1);
+ }
+ break;
+ }
+
+ /* Disk Reset */
+ case 0x0D:
+ {
+ PDOS_PSP PspBlock = SEGMENT_TO_PSP(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(LastDrive - 'A' + 1);
+ 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(CurrentDrive);
+ break;
+ }
+
+ /* Set Disk Transfer Area */
+ case 0x1A:
+ {
+ 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:
+ {
+ DWORD FarPointer = MAKELONG(getDX(), getDS());
+ DPRINT1("Setting interrupt 0x%x ...\n", getAL());
+
+ /* Write the new far pointer to the IDT */
+ ((PDWORD)BaseAddress)[getAL()] = FarPointer;
+ break;
+ }
+
+ /* Create New PSP */
+ case 0x26:
+ {
+ DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
+ 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(DiskTransferArea));
+ setBX(LOWORD(DiskTransferArea));
+ break;
+ }
+
+ /* Get DOS Version */
+ case 0x30:
+ {
+ 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.
+ */
-Done:
- /* Restore the interrupt vectors */
- IntVecTable[0x22] = PspBlock->TerminateAddress;
- IntVecTable[0x23] = PspBlock->BreakAddress;
- IntVecTable[0x24] = PspBlock->CriticalAddress;
+ if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
+ {
+ /*
+ * Return DOS OEM number:
+ * 0x00 for IBM PC-DOS
+ * 0x02 for packaged MS-DOS
+ */
+ setBH(0x02);
+ }
- /* Update the current PSP */
- if (Psp == CurrentPsp)
- {
- CurrentPsp = PspBlock->ParentPsp;
- if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
- }
+ if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
+ {
+ /*
+ * Return version flag:
+ * 1 << 3 if DOS is in ROM,
+ * 0 (reserved) if not.
+ */
+ setBH(0x00);
+ }
- /* Return control to the parent process */
- EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
- LOWORD(PspBlock->TerminateAddress));
-}
+ /* Return DOS 24-bit user serial number in BL:CX */
+ setBL(0x00);
+ setCX(0x0000);
-CHAR DosReadCharacter()
-{
- // TODO: STDIN can be redirected under DOS 2.0+
- return _getch();
-}
+ /*
+ * Return DOS version: Minor:Major in AH:AL
+ * The Windows NT DOS box returns version 5.00, subject to SETVER.
+ */
+ setAX(PspBlock->DosVersion);
-VOID DosPrintCharacter(CHAR Character)
-{
- // TODO: STDOUT can be redirected under DOS 2.0+
- if (Character == '\r') Character = '\n';
- putchar(Character);
-}
+ break;
+ }
-VOID DosInt20h(WORD CodeSegment)
-{
- /* This is the exit interrupt */
- DosTerminateProcess(CodeSegment, 0);
-}
+ /* 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);
+ // }
-VOID DosInt21h(WORD CodeSegment)
-{
- INT i;
- CHAR Character;
- SYSTEMTIME SystemTime;
- PCHAR String;
- PDOS_INPUT_BUFFER InputBuffer;
- DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
- DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
- DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
- DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
- WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
- WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
+ break;
+ }
- /* Check the value in the AH register */
- switch (HIBYTE(Eax))
- {
- /* Terminate Program */
- case 0x00:
+ /* Get Interrupt Vector */
+ case 0x35:
{
- DosTerminateProcess(CodeSegment, 0);
+ DWORD FarPointer = ((PDWORD)BaseAddress)[getAL()];
+
+ /* Read the address from the IDT into ES:BX */
+ setES(HIWORD(FarPointer));
+ setBX(LOWORD(FarPointer));
break;
}
- /* Read Character And Echo */
- case 0x01:
+ /* SWITCH character - AVAILDEV */
+ case 0x37:
{
- Character = DosReadCharacter();
- DosPrintCharacter(Character);
- EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
+ if (getAL() == 0x00)
+ {
+ /*
+ * 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.
+ */
+ setDL('/');
+ setAL(0x00);
+ }
+ else if (getAL() == 0x01)
+ {
+ /*
+ * 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.
+ */
+ // getDL();
+ setAL(0xFF);
+ }
+ else if (getAL() == 0x02)
+ {
+ /*
+ * 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.
+ */
+ // setDL();
+ setAL(0xFF);
+ }
+ else if (getAL() == 0x03)
+ {
+ /*
+ * 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.
+ */
+ // getDL();
+ setAL(0xFF);
+ }
+ else
+ {
+ /* Invalid subfunction */
+ setAL(0xFF);
+ }
+
break;
}
- /* Print Character */
- case 0x02:
+ /* Create Directory */
+ case 0x39:
{
- DosPrintCharacter(LOBYTE(Edx));
+ 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;
}
- /* Read Character Without Echo */
- case 0x08:
+ /* Remove Directory */
+ case 0x3A:
{
- EmulatorSetRegister(EMULATOR_REG_AX,
- (Eax & 0xFFFFFF00) | DosReadCharacter());
+ 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;
}
- /* Print String */
- case 0x09:
+ /* Set Current Directory */
+ case 0x3B:
{
- String = (PCHAR)((ULONG_PTR)BaseAddress
- + TO_LINEAR(DataSegment, LOWORD(Edx)));
+ String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
- while ((*String) != '$')
+ if (DosChangeDirectory(String))
{
- DosPrintCharacter(*String);
- String++;
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(DosLastError);
}
break;
}
- /* Read Buffered Input */
- case 0x0A:
+ /* Create File */
+ case 0x3C:
{
- InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
- + TO_LINEAR(DataSegment,
- LOWORD(Edx)));
+ WORD FileHandle;
+ WORD ErrorCode = DosCreateFile(&FileHandle,
+ (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
+ getCX());
- InputBuffer->Length = 0;
- for (i = 0; i < InputBuffer->MaxLength; i ++)
+ if (ErrorCode == 0)
{
- Character = DosReadCharacter();
- DosPrintCharacter(Character);
- InputBuffer->Buffer[InputBuffer->Length] = Character;
- if (Character == '\r') break;
- InputBuffer->Length++;
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAX(FileHandle);
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ErrorCode);
}
break;
}
- /* Get system date */
- case 0x2A:
+ /* Open File */
+ case 0x3D:
{
- GetLocalTime(&SystemTime);
- EmulatorSetRegister(EMULATOR_REG_CX,
- (Ecx & 0xFFFF0000) | SystemTime.wYear);
- EmulatorSetRegister(EMULATOR_REG_DX,
- (Edx & 0xFFFF0000)
- | (SystemTime.wMonth << 8)
- | SystemTime.wDay);
- EmulatorSetRegister(EMULATOR_REG_AX,
- (Eax & 0xFFFFFF00) | SystemTime.wDayOfWeek);
+ WORD FileHandle;
+ WORD ErrorCode = DosOpenFile(&FileHandle,
+ (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
+ getAL());
+
+ if (ErrorCode == 0)
+ {
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAX(FileHandle);
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ErrorCode);
+ }
+
break;
}
- /* Set system date */
- case 0x2B:
+ /* Close File */
+ case 0x3E:
{
- GetLocalTime(&SystemTime);
- SystemTime.wYear = LOWORD(Ecx);
- SystemTime.wMonth = HIBYTE(Edx);
- SystemTime.wDay = LOBYTE(Edx);
+ if (DosCloseHandle(getBX()))
+ {
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_INVALID_HANDLE);
+ }
- if (SetLocalTime(&SystemTime))
+ break;
+ }
+
+ /* Read from File or Device */
+ case 0x3F:
+ {
+ WORD Handle = getBX();
+ LPBYTE Buffer = (LPBYTE)SEG_OFF_TO_PTR(getDS(), getDX());
+ WORD Count = getCX();
+ WORD BytesRead = 0;
+ WORD ErrorCode = ERROR_SUCCESS;
+ CHAR Character;
+
+ if (IsConsoleHandle(DosGetRealHandle(Handle)))
{
- /* Return success */
- EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
+ while (Stack[STACK_COUNTER] < Count)
+ {
+ /* Read a character from the BIOS */
+ Character = LOBYTE(BiosGetCharacter());
+
+ /* Stop if the BOP needs to be repeated */
+ if (getCF()) break;
+
+ // FIXME: Security checks!
+ DosPrintCharacter(Character);
+ Buffer[Stack[STACK_COUNTER]++] = Character;
+
+ if (Character == '\r')
+ {
+ /* Stop on first carriage return */
+ DosPrintCharacter('\n');
+ break;
+ }
+ }
+
+ if (Character != '\r')
+ {
+ if (Stack[STACK_COUNTER] < Count) ErrorCode = ERROR_NOT_READY;
+ else BytesRead = Count;
+ }
+ else BytesRead = Stack[STACK_COUNTER];
}
else
{
- /* Return failure */
- EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
+ /* Use the file reading function */
+ ErrorCode = DosReadFile(Handle, Buffer, Count, &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;
}
- /* Get system time */
- case 0x2C:
+ /* Write to File or Device */
+ case 0x40:
{
- GetLocalTime(&SystemTime);
- EmulatorSetRegister(EMULATOR_REG_CX,
- (Ecx & 0xFFFF0000)
- | (SystemTime.wHour << 8)
- | SystemTime.wMinute);
- EmulatorSetRegister(EMULATOR_REG_DX,
- (Edx & 0xFFFF0000)
- | (SystemTime.wSecond << 8)
- | (SystemTime.wMilliseconds / 10));
+ WORD BytesWritten = 0;
+ WORD ErrorCode = DosWriteFile(getBX(),
+ SEG_OFF_TO_PTR(getDS(), getDX()),
+ getCX(),
+ &BytesWritten);
+
+ if (ErrorCode == ERROR_SUCCESS)
+ {
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAX(BytesWritten);
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ErrorCode);
+ }
+
break;
}
- /* Set system time */
- case 0x2D:
+ /* Delete File */
+ case 0x41:
{
- GetLocalTime(&SystemTime);
- SystemTime.wHour = HIBYTE(Ecx);
- SystemTime.wMinute = LOBYTE(Ecx);
- SystemTime.wSecond = HIBYTE(Edx);
- SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
+ LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
- if (SetLocalTime(&SystemTime))
+ /* Call the API function */
+ if (DeleteFileA(FileName))
{
- /* Return success */
- EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
+ 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(FileName[0] - 'A');
}
else
{
- /* Return failure */
- EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(GetLastError());
}
break;
}
- /* Create Directory */
- case 0x39:
+ /* Seek File */
+ case 0x42:
{
- String = (PCHAR)((ULONG_PTR)BaseAddress
- + TO_LINEAR(DataSegment, LOWORD(Edx)));
+ DWORD NewLocation;
+ WORD ErrorCode = DosSeekFile(getBX(),
+ MAKELONG(getDX(), getCX()),
+ getAL(),
+ &NewLocation);
- if (CreateDirectoryA(String, NULL))
+ if (ErrorCode == ERROR_SUCCESS)
{
- EmulatorClearFlag(EMULATOR_FLAG_CF);
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+
+ /* Return the new offset in DX:AX */
+ setDX(HIWORD(NewLocation));
+ setAX(LOWORD(NewLocation));
}
else
{
- EmulatorSetFlag(EMULATOR_FLAG_CF);
- EmulatorSetRegister(EMULATOR_REG_AX,
- (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ErrorCode);
}
break;
}
- /* Remove Directory */
- case 0x3A:
+ /* Get/Set File Attributes */
+ case 0x43:
{
- String = (PCHAR)((ULONG_PTR)BaseAddress
- + TO_LINEAR(DataSegment, LOWORD(Edx)));
+ DWORD Attributes;
+ LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
- if (RemoveDirectoryA(String))
+ 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)
{
- EmulatorClearFlag(EMULATOR_FLAG_CF);
+ /* 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
{
- EmulatorSetFlag(EMULATOR_FLAG_CF);
- EmulatorSetRegister(EMULATOR_REG_AX,
- (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_INVALID_FUNCTION);
}
+ break;
+ }
+
+ /* IOCTL */
+ case 0x44:
+ {
+ if (DosHandleIoctl(getAL(), getBX()))
+ {
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(DosLastError);
+ }
break;
}
- /* Set Current Directory */
- case 0x3B:
+ /* Duplicate Handle */
+ case 0x45:
+ {
+ WORD NewHandle;
+ HANDLE Handle = DosGetRealHandle(getBX());
+
+ if (Handle != INVALID_HANDLE_VALUE)
+ {
+ /* The handle is invalid */
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_INVALID_HANDLE);
+ break;
+ }
+
+ /* Open a new handle to the same entry */
+ NewHandle = DosOpenHandle(Handle);
+
+ if (NewHandle == INVALID_DOS_HANDLE)
+ {
+ /* Too many files open */
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_TOO_MANY_OPEN_FILES);
+ break;
+ }
+
+ /* Return the result */
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAX(NewHandle);
+ break;
+ }
+
+ /* Force Duplicate Handle */
+ case 0x46:
+ {
+ if (DosDuplicateHandle(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:
{
- String = (PCHAR)((ULONG_PTR)BaseAddress
- + TO_LINEAR(DataSegment, LOWORD(Edx)));
+ BYTE DriveNumber = getDL();
+ String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
+
+ /* Get the real drive number */
+ if (DriveNumber == 0)
+ {
+ DriveNumber = CurrentDrive;
+ }
+ else
+ {
+ /* Decrement DriveNumber since it was 1-based */
+ DriveNumber--;
+ }
- if (SetCurrentDirectoryA(String))
+ if (DriveNumber <= LastDrive - 'A')
{
- EmulatorClearFlag(EMULATOR_FLAG_CF);
+ /*
+ * Copy the current directory into the target buffer.
+ * It doesn't contain the drive letter and the backslash.
+ */
+ strncpy(String, 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
{
- EmulatorSetFlag(EMULATOR_FLAG_CF);
- EmulatorSetRegister(EMULATOR_REG_AX,
- (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_INVALID_DRIVE);
}
break;
case 0x48:
{
WORD MaxAvailable = 0;
- WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
+ WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
if (Segment != 0)
{
- EmulatorSetRegister(EMULATOR_REG_AX, Segment);
- EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
- EmulatorClearFlag(EMULATOR_FLAG_CF);
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAX(Segment);
+ }
+ else
+ {
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(DosLastError);
+ setBX(MaxAvailable);
}
- else EmulatorSetFlag(EMULATOR_FLAG_CF);
break;
}
/* Free Memory */
case 0x49:
{
- if (DosFreeMemory(ExtSegment))
+ if (DosFreeMemory(getES()))
+ {
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else
{
- EmulatorClearFlag(EMULATOR_FLAG_CF);
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_ARENA_TRASHED);
}
- else EmulatorSetFlag(EMULATOR_FLAG_CF);
break;
}
/* Resize Memory Block */
case 0x4A:
{
- WORD Size = DosResizeMemory(ExtSegment, LOWORD(Ebx));
+ WORD Size;
- if (Size != 0)
+ if (DosResizeMemory(getES(), getBX(), &Size))
+ {
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else
{
- EmulatorSetRegister(EMULATOR_REG_BX, Size);
- EmulatorClearFlag(EMULATOR_FLAG_CF);
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(DosLastError);
+ setBX(Size);
}
- else EmulatorSetFlag(EMULATOR_FLAG_CF);
break;
}
/* Terminate With Return Code */
case 0x4C:
{
- DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
+ DosTerminateProcess(CurrentPsp, getAL());
+ 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(DosErrorLevel);
+ DosErrorLevel = 0x0000; // Clear it
+ break;
+ }
+
+ /* Internal - Set Current Process ID (Set PSP Address) */
+ case 0x50:
+ {
+ // FIXME: Is it really what it's done ??
+ CurrentPsp = 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(CurrentPsp);
+ break;
+ }
+
+ /* Get/Set Memory Management Options */
+ case 0x58:
+ {
+ if (getAL() == 0x00)
+ {
+ /* Get allocation strategy */
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAX(DosAllocStrategy);
+ }
+ else if (getAL() == 0x01)
+ {
+ /* Set allocation strategy */
+
+ 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() & 0x3F) > DOS_ALLOC_LAST_FIT)
+ {
+ /* Invalid allocation strategy */
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_INVALID_PARAMETER);
+ break;
+ }
+
+ DosAllocStrategy = getBL();
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else if (getAL() == 0x02)
+ {
+ /* Get UMB link state */
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ setAL(DosUmbLinked ? 0x01 : 0x00);
+ }
+ else if (getAL() == 0x03)
+ {
+ /* Set UMB link state */
+ if (getBX()) DosLinkUmb();
+ else DosUnlinkUmb();
+ Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+ }
+ else
+ {
+ /* Invalid or unsupported function */
+ Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+ setAX(ERROR_INVALID_FUNCTION);
+ }
+
break;
}
/* Unsupported */
default:
{
- EmulatorSetFlag(EMULATOR_FLAG_CF);
+ 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()
+VOID WINAPI DosBreakInterrupt(LPWORD Stack)
{
+ UNREFERENCED_PARAMETER(Stack);
+
VdmRunning = FALSE;
}
-BOOLEAN DosInitialize()
+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;
PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
FILE *Stream;
WCHAR Buffer[256];
LPWSTR SourcePtr, Environment;
LPSTR AsciiString;
- LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
+ LPSTR DestPtr = (LPSTR)SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0);
DWORD AsciiSize;
+ CHAR CurrentDirectory[MAX_PATH];
+ CHAR DosDirectory[DOS_DIR_LENGTH];
+ LPSTR Path;
/* Initialize the MCB */
Mcb->BlockType = 'Z';
- Mcb->Size = (WORD)USER_MEMORY_SIZE;
+ Mcb->Size = USER_MEMORY_SIZE;
+ Mcb->OwnerPsp = 0;
+
+ /* Initialize the link MCB to the UMB area */
+ Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT + USER_MEMORY_SIZE + 1);
+ Mcb->BlockType = 'M';
+ Mcb->Size = UMB_START_SEGMENT - FIRST_MCB_SEGMENT - USER_MEMORY_SIZE - 2;
+ Mcb->OwnerPsp = SYSTEM_PSP;
+
+ /* Initialize the UMB area */
+ Mcb = SEGMENT_TO_MCB(UMB_START_SEGMENT);
+ Mcb->BlockType = 'Z';
+ Mcb->Size = UMB_END_SEGMENT - UMB_START_SEGMENT;
Mcb->OwnerPsp = 0;
/* Get the environment strings */
/* Copy the string into DOS memory */
strcpy(DestPtr, AsciiString);
- /* Free the memory */
- HeapFree(GetProcessHeap(), 0, AsciiString);
-
/* Move to the next string */
SourcePtr += wcslen(SourcePtr) + 1;
- DestPtr += strlen(AsciiString) + 1;
+ DestPtr += strlen(AsciiString);
+ *(DestPtr++) = 0;
+
+ /* Free the memory */
+ HeapFree(GetProcessHeap(), 0, AsciiString);
}
+ *DestPtr = 0;
/* Free the memory allocated for environment strings */
FreeEnvironmentStringsW(Environment);
+ /* Clear the current directory buffer */
+ ZeroMemory(CurrentDirectories, sizeof(CurrentDirectories));
+
+ /* Get the current directory */
+ if (!GetCurrentDirectoryA(MAX_PATH, CurrentDirectory))
+ {
+ // TODO: Use some kind of default path?
+ return FALSE;
+ }
+
+ /* Convert that to a DOS path */
+ if (!GetShortPathNameA(CurrentDirectory, DosDirectory, DOS_DIR_LENGTH))
+ {
+ // TODO: Use some kind of default path?
+ return FALSE;
+ }
+
+ /* Set the drive */
+ CurrentDrive = DosDirectory[0] - 'A';
+
+ /* Get the directory part of the path */
+ Path = strchr(DosDirectory, '\\');
+ if (Path != NULL)
+ {
+ /* Skip the backslash */
+ Path++;
+ }
+
+ /* Set the directory */
+ if (Path != NULL)
+ {
+ strncpy(CurrentDirectories[CurrentDrive], Path, DOS_DIR_LENGTH);
+ }
+
/* Read CONFIG.SYS */
Stream = _wfopen(DOS_CONFIG_PATH, L"r");
if (Stream != NULL)
fclose(Stream);
}
+ /* Initialize the SFT */
+ for (i = 0; i < DOS_SFT_SIZE; i++)
+ {
+ DosSystemFileTable[i] = INVALID_HANDLE_VALUE;
+ DosSftRefCount[i] = 0;
+ }
+
+ /* Get handles to standard I/O devices */
+ DosSystemFileTable[0] = GetStdHandle(STD_INPUT_HANDLE);
+ 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;
}