* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
*/
-#include "ntvdm.h"
+/* INCLUDES *******************************************************************/
-WORD CurrentPsp = SYSTEM_PSP, LastError = 0;
+#include "dos.h"
+#include "bios.h"
+#include "emulator.h"
-static VOID DosCombineFreeBlocks()
+/* PRIVATE VARIABLES **********************************************************/
+
+static WORD CurrentPsp = SYSTEM_PSP;
+static DWORD DiskTransferArea;
+static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
+static WORD DosSftRefCount[DOS_SFT_SIZE];
+
+/* PRIVATE FUNCTIONS **********************************************************/
+
+static VOID DosCombineFreeBlocks(WORD StartBlock)
{
- WORD Segment = FIRST_MCB_SEGMENT;
- PDOS_MCB CurrentMcb, NextMcb;
+ PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
+
+ /* If this is the last block or it's not free, quit */
+ if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
- /* Loop through all the blocks */
while (TRUE)
{
- /* Get a pointer to the MCB */
- CurrentMcb = SEGMENT_TO_MCB(Segment);
-
- /* Ignore the last block */
- if (CurrentMcb->BlockType == 'Z') break;
-
/* Get a pointer to the next MCB */
- NextMcb = SEGMENT_TO_MCB(Segment + CurrentMcb->Size + 1);
+ NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
- /* If both this block and the next one are free, combine them */
- if ((CurrentMcb->OwnerPsp == 0) && (NextMcb->OwnerPsp == 0))
+ /* Check if the next MCB is free */
+ if (NextMcb->OwnerPsp == 0)
{
+ /* Combine them */
CurrentMcb->Size += NextMcb->Size + 1;
CurrentMcb->BlockType = NextMcb->BlockType;
-
- /* Invalidate the next MCB */
NextMcb->BlockType = 'I';
-
- /* Try to combine the current block again with the next one */
- continue;
}
-
- /* Update the segment and continue */
- Segment += CurrentMcb->Size + 1;
+ else
+ {
+ /* No more adjoining free blocks */
+ break;
+ }
}
}
TotalSize++;
/* Allocate the memory for the environment block */
- DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4);
+ DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
if (!DestSegment) return 0;
Ptr = SourceBuffer;
/* Advance to the next string */
Ptr += strlen(Ptr) + 1;
- DestBuffer += strlen(Ptr) + 1;
+ DestBuffer += strlen(Ptr);
+
+ /* Put a zero after the string */
+ *(DestBuffer++) = 0;
}
/* Set the final zero */
return DestSegment;
}
-WORD DosAllocateMemory(WORD Size)
+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)
{
- WORD Result = 0, Segment = FIRST_MCB_SEGMENT;
+ 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] = 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;
- /* Find an unallocated block */
while (TRUE)
{
/* Get a pointer to the MCB */
/* Only check free blocks */
if (CurrentMcb->OwnerPsp != 0) goto Next;
- /* Check if the block is big enough */
+ /* Combine this free block with adjoining free blocks */
+ DosCombineFreeBlocks(Segment);
+
+ /* Update the maximum block size */
+ if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
+
+ /* Check if this block is big enough */
if (CurrentMcb->Size < Size) goto Next;
/* It is, update the smallest found so far */
}
Next:
- /* If this was the last MCB in the chain, quit. */
+ /* If this was the last MCB in the chain, quit */
if (CurrentMcb->BlockType == 'Z') break;
/* Otherwise, update the segment and continue */
Segment += CurrentMcb->Size + 1;
}
- /* If we didn't find a free block, return zero */
- if (Result == 0) return 0;
+ /* If we didn't find a free block, return 0 */
+ if (Result == 0)
+ {
+ if (MaxAvailable) *MaxAvailable = MaxSize;
+ return 0;
+ }
/* Get a pointer to the MCB */
CurrentMcb = SEGMENT_TO_MCB(Result);
/* Initialize the new MCB structure */
NextMcb->BlockType = CurrentMcb->BlockType;
- NextMcb->Size = Size - CurrentMcb->Size - 1;
+ NextMcb->Size = CurrentMcb->Size - Size - 1;
NextMcb->OwnerPsp = 0;
/* Update the current block */
CurrentMcb->BlockType = 'M';
CurrentMcb->Size = Size;
-
- /* Combine consecutive free blocks into larger blocks */
- DosCombineFreeBlocks();
}
/* 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, CurrentSeg;
- PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), CurrentMcb;
- BOOLEAN FinalBlockUsed = FALSE;
+ BOOLEAN Success = TRUE;
+ WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
+ PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
+
+ /* Make sure this is a valid, allocated block */
+ if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
+ {
+ Success = FALSE;
+ goto Done;
+ }
- /* We can't expand the last block */
- if (Mcb->BlockType != 'M') return 0;
+ ReturnSize = Mcb->Size;
- /* Check if need to expand or contract the block */
+ /* Check if we need to expand or contract the block */
if (NewSize > Mcb->Size)
{
- ReturnSize = Mcb->Size;
+ /* We can't expand the last block */
+ if (Mcb->BlockType != 'M')
+ {
+ Success = FALSE;
+ goto Done;
+ }
- /* Get the segment of the next MCB */
- CurrentSeg = Segment + Mcb->Size + 1;
+ /* Get the pointer and segment of the next MCB */
+ NextSegment = Segment + Mcb->Size + 1;
+ NextMcb = SEGMENT_TO_MCB(NextSegment);
- /* Calculate the maximum amount of memory this block could expand to */
- while (ReturnSize < NewSize)
+ /* Make sure the next segment is free */
+ if (NextMcb->OwnerPsp != 0)
{
- /* Get the MCB */
- CurrentMcb = SEGMENT_TO_MCB(CurrentSeg);
+ Success = FALSE;
+ goto Done;
+ }
- /* We can't expand the block over an allocated block */
- if (CurrentMcb->OwnerPsp != 0) break;
+ /* Combine this free block with adjoining free blocks */
+ DosCombineFreeBlocks(NextSegment);
- ReturnSize += CurrentMcb->Size + 1;
+ /* Set the maximum possible size of the block */
+ ReturnSize += NextMcb->Size + 1;
- /* Check if this is the last block */
- if (CurrentMcb->BlockType == 'Z')
- {
- FinalBlockUsed = TRUE;
- break;
- }
+ /* Maximize the current block */
+ Mcb->Size = ReturnSize;
+ Mcb->BlockType = NextMcb->BlockType;
- /* Update the segment and continue */
- CurrentSeg += CurrentMcb->Size + 1;
- }
+ /* Invalidate the next block */
+ NextMcb->BlockType = 'I';
- /* Check if we need to split the last block */
- if (ReturnSize > NewSize)
+ /* Check if the block is larger than requested */
+ if (Mcb->Size > NewSize)
{
+ /* It is, split it into two blocks */
+ NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
+
/* Initialize the new MCB structure */
- CurrentMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
- CurrentMcb->BlockType = (FinalBlockUsed) ? 'Z' : 'M';
- CurrentMcb->Size = ReturnSize - NewSize - 1;
- CurrentMcb->OwnerPsp = 0;
- }
+ NextMcb->BlockType = Mcb->BlockType;
+ NextMcb->Size = Mcb->Size - NewSize - 1;
+ NextMcb->OwnerPsp = 0;
- /* Calculate the new size of the block */
- ReturnSize = min(ReturnSize, NewSize);
-
- /* Update the MCB */
- if (FinalBlockUsed) Mcb->BlockType = 'Z';
- Mcb->Size = ReturnSize;
+ /* Update the current block */
+ Mcb->BlockType = 'M';
+ Mcb->Size = NewSize;
+ }
}
else if (NewSize < Mcb->Size)
{
/* Just split the block */
- CurrentMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
- CurrentMcb->BlockType = Mcb->BlockType;
- CurrentMcb->Size = Mcb->Size - NewSize - 1;
- CurrentMcb->OwnerPsp = 0;
+ NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
+ NextMcb->BlockType = Mcb->BlockType;
+ NextMcb->Size = Mcb->Size - NewSize - 1;
+ NextMcb->OwnerPsp = 0;
/* Update the MCB */
Mcb->BlockType = 'M';
Mcb->Size = NewSize;
-
- ReturnSize = NewSize;
}
- /* Combine consecutive free blocks into larger blocks */
- DosCombineFreeBlocks();
-
- return ReturnSize;
+Done:
+ /* Check if the operation failed */
+ if (!Success)
+ {
+ /* 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);
/* Make sure the MCB is valid */
if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') return FALSE;
/* Mark the block as free */
Mcb->OwnerPsp = 0;
- /* Combine consecutive free blocks into larger blocks */
- DosCombineFreeBlocks();
-
return TRUE;
}
-WORD DosCreateFile(LPCSTR FilePath)
+WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes)
{
- // TODO: NOT IMPLEMENTED
- return 0;
+ HANDLE FileHandle;
+ WORD DosHandle;
+
+ /* 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)
+ {
+ /* Return the error code */
+ return GetLastError();
+ }
+
+ /* Open the DOS handle */
+ DosHandle = DosOpenHandle(FileHandle);
+
+ if (DosHandle == INVALID_DOS_HANDLE)
+ {
+ /* Close the handle */
+ CloseHandle(FileHandle);
+
+ /* Return the error code */
+ return ERROR_TOO_MANY_OPEN_FILES;
+ }
+
+ /* It was successful */
+ *Handle = DosHandle;
+ return ERROR_SUCCESS;
+}
+
+WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode)
+{
+ HANDLE FileHandle;
+ ACCESS_MASK Access = 0;
+ WORD DosHandle;
+
+ /* Parse the access mode */
+ switch (AccessMode & 3)
+ {
+ 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 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)
+ {
+ /* Return the error code */
+ return GetLastError();
+ }
+
+ /* Open the DOS handle */
+ DosHandle = DosOpenHandle(FileHandle);
+
+ if (DosHandle == INVALID_DOS_HANDLE)
+ {
+ /* Close the handle */
+ CloseHandle(FileHandle);
+
+ /* Return the error code */
+ return ERROR_TOO_MANY_OPEN_FILES;
+ }
+
+ /* It was successful */
+ *Handle = DosHandle;
+ return ERROR_SUCCESS;
+}
+
+WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead)
+{
+ WORD Result = ERROR_SUCCESS;
+ DWORD BytesRead32 = 0;
+ HANDLE Handle = DosGetRealHandle(FileHandle);
+
+ /* Make sure the handle is valid */
+ if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_PARAMETER;
+
+ /* Read the file */
+ if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
+ {
+ /* Store the error code */
+ Result = GetLastError();
+ }
+
+ /* The number of bytes read is always 16-bit */
+ *BytesRead = LOWORD(BytesRead32);
+
+ /* Return the error code */
+ return Result;
}
-WORD DosOpenFile(LPCSTR FilePath)
+WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritten)
{
- // TODO: NOT IMPLEMENTED
- return 0;
+ WORD Result = ERROR_SUCCESS;
+ DWORD BytesWritten32 = 0;
+ HANDLE Handle = DosGetRealHandle(FileHandle);
+
+ /* Make sure the handle is valid */
+ if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_PARAMETER;
+
+ /* Write the file */
+ if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
+ {
+ /* Store the error code */
+ Result = GetLastError();
+ }
+
+ /* The number of bytes written is always 16-bit */
+ *BytesWritten = LOWORD(BytesWritten32);
+
+ /* Return the error code */
+ return Result;
+}
+
+BOOLEAN DosCloseHandle(WORD DosHandle)
+{
+ BYTE SftIndex;
+ PDOS_PSP PspBlock;
+ LPBYTE HandleTable;
+
+ /* 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;
+ }
+
+ return TRUE;
}
VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
{
- INT i;
PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
PspBlock->Exit[0] = 0xCD; // int 0x20
PspBlock->Exit[1] = 0x20;
- /* Set the program size */
- PspBlock->MemSize = ProgramSize;
+ /* Set the number of the last paragraph */
+ PspBlock->LastParagraph = PspSegment + ProgramSize - 1;
/* Save the interrupt vectors */
PspBlock->TerminateAddress = IntVecTable[0x22];
/* Set the parent PSP */
PspBlock->ParentPsp = CurrentPsp;
- /* Initialize the handle table */
- for (i = 0; i < 20; i++) PspBlock->HandleTable[i] = 0xFF;
-
- /* Did we get an environment segment? */
- if (!Environment)
- {
- /* No, copy the one from the parent */
- Environment = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
- ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
- : SYSTEM_ENV_BLOCK);
- }
+ /* 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 */
BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
{
- BOOLEAN Success = FALSE;
+ BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
LPBYTE Address = NULL;
LPSTR ProgramFilePath, Parameters[128];
CHAR CommandLineCopy[128];
INT ParamCount = 0;
- WORD Segment, FileSize;
+ DWORD Segment = 0;
+ DWORD i, FileSize, ExeSize;
+ PIMAGE_DOS_HEADER Header;
+ PDWORD RelocationTable;
+ PWORD RelocWord;
/* Save a copy of the command line */
strcpy(CommandLineCopy, CommandLine);
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);
+ }
+
/* Check if this is an EXE file or a COM file */
if (Address[0] == 'M' && Address[1] == 'Z')
{
/* EXE file */
- // TODO: NOT IMPLEMENTED
- DisplayMessage(L"EXE files are not yet supported!");
+ /* Get the MZ header */
+ Header = (PIMAGE_DOS_HEADER)Address;
+
+ // TODO: Verify checksum and executable!
+
+ /* 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(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,
+ CommandLine,
+ ExeSize,
+ EnvBlock);
+
+ /* 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)),
+ Address + (Header->e_cparhdr << 4),
+ FileSize - (Header->e_cparhdr << 4));
+
+ /* 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)((ULONG_PTR)BaseAddress
+ + TO_LINEAR(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 */
+ EmulatorSetRegister(EMULATOR_REG_DS, Segment);
+ EmulatorSetRegister(EMULATOR_REG_ES, Segment);
+
+ /* Set the stack to the location from the header */
+ EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
+ Header->e_sp);
+
+ /* Execute */
+ CurrentPsp = Segment;
+ DiskTransferArea = MAKELONG(0x80, Segment);
+ EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
+ Header->e_ip);
+
+ Success = TRUE;
}
else
{
/* COM file */
/* Allocate memory for the whole program and the PSP */
- Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4);
+ Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4, NULL);
if (Segment == 0) goto Cleanup;
/* Copy the program to Segment:0100 */
/* 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);
CHAR DosReadCharacter()
{
- // TODO: STDIN can be redirected under DOS 2.0+
- return _getch();
+ CHAR Character = '\0';
+ WORD BytesRead;
+
+ /* Use the file reading function */
+ DosReadFile(DOS_INPUT_HANDLE, &Character, sizeof(CHAR), &BytesRead);
+
+ return Character;
}
VOID DosPrintCharacter(CHAR Character)
{
- // TODO: STDOUT can be redirected under DOS 2.0+
- if (Character == '\r') Character = '\n';
- putchar(Character);
+ WORD BytesWritten;
+
+ /* Use the file writing function */
+ DosWriteFile(DOS_OUTPUT_HANDLE, &Character, sizeof(CHAR), &BytesWritten);
}
VOID DosInt20h(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);
break;
}
+ /* Set Disk Transfer Area */
+ case 0x1A:
+ {
+ DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment);
+ break;
+ }
+
+ /* Set Interrupt Vector */
+ case 0x25:
+ {
+ DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment);
+
+ /* Write the new far pointer to the IDT */
+ ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer;
+
+ break;
+ }
+
+ /* Get system date */
+ case 0x2A:
+ {
+ 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);
+ break;
+ }
+
+ /* Set system date */
+ case 0x2B:
+ {
+ GetLocalTime(&SystemTime);
+ SystemTime.wYear = LOWORD(Ecx);
+ SystemTime.wMonth = HIBYTE(Edx);
+ SystemTime.wDay = LOBYTE(Edx);
+
+ if (SetLocalTime(&SystemTime))
+ {
+ /* Return success */
+ EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
+ }
+ else
+ {
+ /* Return failure */
+ EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
+ }
+
+ break;
+ }
+
+ /* Get system time */
+ case 0x2C:
+ {
+ 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));
+ break;
+ }
+
+ /* Set system time */
+ case 0x2D:
+ {
+ GetLocalTime(&SystemTime);
+ SystemTime.wHour = HIBYTE(Ecx);
+ SystemTime.wMinute = LOBYTE(Ecx);
+ SystemTime.wSecond = HIBYTE(Edx);
+ SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
+
+ if (SetLocalTime(&SystemTime))
+ {
+ /* Return success */
+ EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
+ }
+ else
+ {
+ /* Return failure */
+ EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
+ }
+
+ break;
+ }
+
+ /* Get Disk Transfer Area */
+ case 0x2F:
+ {
+ EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea));
+ EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea));
+
+ break;
+ }
+
+ /* Get DOS Version */
+ case 0x30:
+ {
+ PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+
+ EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion);
+ break;
+ }
+
+ /* Get Interrupt Vector */
+ case 0x35:
+ {
+ DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)];
+
+ /* Read the address from the IDT into ES:BX */
+ EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer));
+ EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer));
+
+ break;
+ }
+
+ /* Create Directory */
+ case 0x39:
+ {
+ String = (PCHAR)((ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx)));
+
+ if (CreateDirectoryA(String, NULL))
+ {
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+ }
+ else
+ {
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+ }
+
+ break;
+ }
+
+ /* Remove Directory */
+ case 0x3A:
+ {
+ String = (PCHAR)((ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx)));
+
+ if (RemoveDirectoryA(String))
+ {
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+ }
+ else
+ {
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+ }
+
+
+ break;
+ }
+
+ /* Set Current Directory */
+ case 0x3B:
+ {
+ String = (PCHAR)((ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx)));
+
+ if (SetCurrentDirectoryA(String))
+ {
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+ }
+ else
+ {
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+ }
+
+ break;
+ }
+
+ /* Create File */
+ case 0x3C:
+ {
+ WORD FileHandle;
+ WORD ErrorCode = DosCreateFile(&FileHandle,
+ (LPCSTR)(ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx)),
+ LOWORD(Ecx));
+
+ if (ErrorCode == 0)
+ {
+ /* Clear CF */
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+
+ /* Return the handle in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | FileHandle);
+ }
+ else
+ {
+ /* Set CF */
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+ /* Return the error code in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | ErrorCode);
+ }
+
+ break;
+ }
+
+ /* Open File */
+ case 0x3D:
+ {
+ WORD FileHandle;
+ WORD ErrorCode = DosCreateFile(&FileHandle,
+ (LPCSTR)(ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx)),
+ LOBYTE(Eax));
+
+ if (ErrorCode == 0)
+ {
+ /* Clear CF */
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+
+ /* Return the handle in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | FileHandle);
+ }
+ else
+ {
+ /* Set CF */
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+ /* Return the error code in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | ErrorCode);
+ }
+
+ break;
+ }
+
+ /* Close File */
+ case 0x3E:
+ {
+ if (DosCloseHandle(LOWORD(Ebx)))
+ {
+ /* Clear CF */
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+ }
+ else
+ {
+ /* Set CF */
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+ /* Return the error code in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | ERROR_INVALID_PARAMETER);
+ }
+
+ break;
+ }
+
+ /* Read File */
+ case 0x3F:
+ {
+ WORD BytesRead = 0;
+ WORD ErrorCode = DosReadFile(LOWORD(Ebx),
+ (LPVOID)((ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx))),
+ LOWORD(Ecx),
+ &BytesRead);
+
+ if (ErrorCode == 0)
+ {
+ /* Clear CF */
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+
+ /* Return the number of bytes read in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | BytesRead);
+ }
+ else
+ {
+ /* Set CF */
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+ /* Return the error code in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | ErrorCode);
+ }
+ break;
+ }
+
+ /* Write File */
+ case 0x40:
+ {
+ WORD BytesWritten = 0;
+ WORD ErrorCode = DosWriteFile(LOWORD(Ebx),
+ (LPVOID)((ULONG_PTR)BaseAddress
+ + TO_LINEAR(DataSegment, LOWORD(Edx))),
+ LOWORD(Ecx),
+ &BytesWritten);
+
+ if (ErrorCode == 0)
+ {
+ /* Clear CF */
+ EmulatorClearFlag(EMULATOR_FLAG_CF);
+
+ /* Return the number of bytes written in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | BytesWritten);
+ }
+ else
+ {
+ /* Set CF */
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+ /* Return the error code in AX */
+ EmulatorSetRegister(EMULATOR_REG_AX,
+ (Eax & 0xFFFF0000) | ErrorCode);
+ }
+
+ break;
+ }
+
/* Allocate Memory */
case 0x48:
{
- WORD Segment = DosAllocateMemory(LOWORD(Ebx));
+ WORD MaxAvailable = 0;
+ WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
+
if (Segment != 0)
{
EmulatorSetRegister(EMULATOR_REG_AX, Segment);
+ EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
EmulatorClearFlag(EMULATOR_FLAG_CF);
}
else EmulatorSetFlag(EMULATOR_FLAG_CF);
/* Resize Memory Block */
case 0x4A:
{
- WORD Size = DosResizeMemory(ExtSegment, LOWORD(Ebx));
+ WORD Size;
- if (Size != 0)
+ if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size))
{
- EmulatorSetRegister(EMULATOR_REG_BX, Size);
EmulatorClearFlag(EMULATOR_FLAG_CF);
}
- else EmulatorSetFlag(EMULATOR_FLAG_CF);
+ else
+ {
+ EmulatorSetFlag(EMULATOR_FLAG_CF);
+ EmulatorSetRegister(EMULATOR_REG_BX, Size);
+ }
break;
}
/* Unsupported */
default:
{
+ DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
EmulatorSetFlag(EMULATOR_FLAG_CF);
}
}
BOOLEAN DosInitialize()
{
+ BYTE i;
PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
FILE *Stream;
WCHAR Buffer[256];
/* Initialize the MCB */
Mcb->BlockType = 'Z';
- Mcb->Size = (WORD)USER_MEMORY_SIZE;
+ Mcb->Size = USER_MEMORY_SIZE;
Mcb->OwnerPsp = 0;
/* Get the environment strings */
/* Move to the next string */
SourcePtr += wcslen(SourcePtr) + 1;
- DestPtr += strlen(AsciiString) + 1;
+ DestPtr += strlen(AsciiString);
+ *(DestPtr++) = 0;
}
+ *DestPtr = 0;
/* Free the memory allocated for environment strings */
FreeEnvironmentStringsW(Environment);
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);
+
return TRUE;
}