[NTVDM]
[reactos.git] / subsystems / ntvdm / dos.c
index e2402bb..71ec4d7 100644 (file)
@@ -6,42 +6,46 @@
  * 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;
+        }
     }
 }
 
@@ -62,7 +66,7 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
     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;
@@ -75,7 +79,10 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
 
         /* 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 */
@@ -84,12 +91,140 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
     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 */
@@ -104,7 +239,13 @@ WORD DosAllocateMemory(WORD Size)
         /* 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 */
@@ -114,15 +255,19 @@ WORD DosAllocateMemory(WORD Size)
         }
 
 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);
@@ -135,103 +280,113 @@ Next:
 
         /* 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;
@@ -239,27 +394,196 @@ BOOLEAN DosFreeMemory(WORD Segment)
     /* 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);
 
@@ -269,8 +593,8 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR
     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];
@@ -280,18 +604,10 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR
     /* 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 */
@@ -314,13 +630,17 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR
 
 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);
@@ -361,20 +681,104 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
     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 */
@@ -398,12 +802,20 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
 
         /* 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);
 
@@ -467,15 +879,21 @@ Done:
 
 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)
@@ -488,9 +906,11 @@ 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);
@@ -565,13 +985,346 @@ VOID DosInt21h(WORD CodeSegment)
             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);
@@ -594,14 +1347,17 @@ VOID DosInt21h(WORD CodeSegment)
         /* 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;
         }
@@ -616,6 +1372,7 @@ VOID DosInt21h(WORD CodeSegment)
         /* Unsupported */
         default:
         {
+            DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
             EmulatorSetFlag(EMULATOR_FLAG_CF);
         }
     }
@@ -628,6 +1385,7 @@ VOID DosBreakInterrupt()
 
 BOOLEAN DosInitialize()
 {
+    BYTE i;
     PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
     FILE *Stream;
     WCHAR Buffer[256];
@@ -638,7 +1396,7 @@ BOOLEAN DosInitialize()
 
     /* Initialize the MCB */
     Mcb->BlockType = 'Z';
-    Mcb->Size = (WORD)USER_MEMORY_SIZE;
+    Mcb->Size = USER_MEMORY_SIZE;
     Mcb->OwnerPsp = 0;
 
     /* Get the environment strings */
@@ -684,8 +1442,10 @@ BOOLEAN DosInitialize()
 
         /* 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);
@@ -701,6 +1461,18 @@ BOOLEAN DosInitialize()
         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;
 }