Index: subsystems/ntvdm/dos.c
===================================================================
--- subsystems/ntvdm/dos.c	(revision 0)
+++ subsystems/ntvdm/dos.c	(revision 0)
@@ -0,0 +1,705 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            dos.c
+ * PURPOSE:         VDM DOS Kernel
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+#include "ntvdm.h"
+
+WORD CurrentPsp = SYSTEM_PSP, LastError = 0;
+
+static VOID DosCombineFreeBlocks()
+{
+    WORD Segment = FIRST_MCB_SEGMENT;
+    PDOS_MCB CurrentMcb, NextMcb;
+
+    /* 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);
+        
+        /* If both this block and the next one are free, combine them */
+        if ((CurrentMcb->OwnerPsp == 0) && (NextMcb->OwnerPsp == 0))
+        {
+            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;
+    }
+}
+
+static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
+{
+    PCHAR Ptr, SourceBuffer, DestBuffer = NULL;
+    ULONG TotalSize = 0;
+    WORD DestSegment;
+
+    Ptr = SourceBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(SourceSegment, 0));
+
+    /* Calculate the size of the environment block */
+    while (*Ptr)
+    {
+        TotalSize += strlen(Ptr) + 1;
+        Ptr += strlen(Ptr) + 1;
+    }
+    TotalSize++;
+
+    /* Allocate the memory for the environment block */
+    DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4);
+    if (!DestSegment) return 0;
+
+    Ptr = SourceBuffer;
+
+    DestBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DestSegment, 0));
+    while (*Ptr)
+    {
+        /* Copy the string */
+        strcpy(DestBuffer, Ptr);
+
+        /* Advance to the next string */
+        Ptr += strlen(Ptr) + 1;
+        DestBuffer += strlen(Ptr) + 1;
+    }
+
+    /* Set the final zero */
+    *DestBuffer = 0;
+
+    return DestSegment;
+}
+
+WORD DosAllocateMemory(WORD Size)
+{
+    WORD Result = 0, Segment = FIRST_MCB_SEGMENT;
+    PDOS_MCB CurrentMcb, NextMcb;
+
+    /* Find an unallocated block */
+    while (TRUE)
+    {
+        /* Get a pointer to the MCB */
+        CurrentMcb = SEGMENT_TO_MCB(Segment);
+
+        /* Make sure it's valid */
+        if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
+        {
+            return 0;
+        }
+
+        /* Only check free blocks */
+        if (CurrentMcb->OwnerPsp != 0) goto Next;
+
+        /* Check if the 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))
+        {
+            Result = Segment;
+        }
+
+Next:
+        /* 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;
+
+    /* Get a pointer to the MCB */
+    CurrentMcb = SEGMENT_TO_MCB(Result);
+
+    /* Check if the block is larger than requested */
+    if (CurrentMcb->Size > Size)
+    {
+        /* It is, split it into two blocks */
+        NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
+
+        /* Initialize the new MCB structure */
+        NextMcb->BlockType = CurrentMcb->BlockType;
+        NextMcb->Size = Size - CurrentMcb->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;
+}
+
+WORD DosResizeMemory(WORD Segment, WORD NewSize)
+{
+    WORD ReturnSize = 0, CurrentSeg;
+    PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), CurrentMcb;
+    BOOLEAN FinalBlockUsed = FALSE;
+
+    /* We can't expand the last block */
+    if (Mcb->BlockType != 'M') return 0;
+
+    /* Check if need to expand or contract the block */
+    if (NewSize > Mcb->Size)
+    {
+        ReturnSize = Mcb->Size;
+
+        /* Get the segment of the next MCB */
+        CurrentSeg = Segment + Mcb->Size + 1;
+    
+        /* Calculate the maximum amount of memory this block could expand to */
+        while (ReturnSize < NewSize)
+        {
+            /* Get the MCB */
+            CurrentMcb = SEGMENT_TO_MCB(CurrentSeg);
+    
+            /* We can't expand the block over an allocated block */
+            if (CurrentMcb->OwnerPsp != 0) break;
+    
+            ReturnSize += CurrentMcb->Size + 1;
+    
+            /* Check if this is the last block */
+            if (CurrentMcb->BlockType == 'Z')
+            {
+                FinalBlockUsed = TRUE;
+                break;
+            }
+    
+            /* Update the segment and continue */
+            CurrentSeg += CurrentMcb->Size + 1;
+        }
+    
+        /* Check if we need to split the last block */
+        if (ReturnSize > NewSize)
+        {
+            /* 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;
+        }
+    
+        /* Calculate the new size of the block */
+        ReturnSize = min(ReturnSize, NewSize);
+
+        /* Update the MCB */
+        if (FinalBlockUsed) Mcb->BlockType = 'Z';
+        Mcb->Size = ReturnSize;
+    }
+    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;
+
+        /* Update the MCB */
+        Mcb->BlockType = 'M';
+        Mcb->Size = NewSize;
+
+        ReturnSize = NewSize;
+    }
+
+    /* Combine consecutive free blocks into larger blocks */
+    DosCombineFreeBlocks(); 
+
+    return ReturnSize;
+}
+
+BOOLEAN DosFreeMemory(WORD Segment)
+{
+    PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
+
+    /* 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)
+{
+    // TODO: NOT IMPLEMENTED
+    return 0;
+}
+
+WORD DosOpenFile(LPCSTR FilePath)
+{
+    // TODO: NOT IMPLEMENTED
+    return 0;
+}
+
+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);
+
+    ZeroMemory(PspBlock, sizeof(DOS_PSP));
+
+    /* Set the exit interrupt */
+    PspBlock->Exit[0] = 0xCD; // int 0x20
+    PspBlock->Exit[1] = 0x20;
+
+    /* Set the program size */
+    PspBlock->MemSize = ProgramSize;
+
+    /* Save the interrupt vectors */
+    PspBlock->TerminateAddress = IntVecTable[0x22];
+    PspBlock->BreakAddress = IntVecTable[0x23];
+    PspBlock->CriticalAddress = IntVecTable[0x24];
+
+    /* 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);
+    }
+
+    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 = strlen(CommandLine);
+    RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
+    PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
+}
+
+BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
+{
+    BOOLEAN Success = FALSE;
+    HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
+    LPBYTE Address = NULL;
+    LPSTR ProgramFilePath, Parameters[128];
+    CHAR CommandLineCopy[128];
+    INT ParamCount = 0;
+    WORD Segment, FileSize;
+
+    /* 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 < 256)
+           && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
+    {
+        ParamCount++;
+    }
+
+    /* 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;
+
+    /* 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!");
+    }
+    else
+    {
+        /* COM file */
+
+        /* Allocate memory for the whole program and the PSP */
+        Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4);
+        if (Segment == 0) goto Cleanup;
+
+        /* Copy the program to Segment:0100 */
+        RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
+                      + TO_LINEAR(Segment, 0x100)),
+                      Address,
+                      FileSize);
+
+        /* Initialize the PSP */
+        DosInitializePsp(Segment,
+                         CommandLine,
+                         (FileSize + sizeof(DOS_PSP)) >> 4,
+                         EnvBlock);
+        
+        /* Set the initial segment registers */
+        EmulatorSetRegister(EMULATOR_REG_DS, Segment);
+        EmulatorSetRegister(EMULATOR_REG_ES, Segment);
+
+        /* Set the stack to the last word of the segment */
+        EmulatorSetStack(Segment, 0xFFFE);
+
+        /* Execute */
+        CurrentPsp = Segment;
+        EmulatorExecute(Segment, 0x100);
+
+        Success = TRUE;
+    }
+
+Cleanup:
+    /* Unmap the file*/
+    if (Address != NULL) UnmapViewOfFile(Address);
+
+    /* Close the file mapping object */
+    if (FileMapping != NULL) CloseHandle(FileMapping);
+
+    /* Close the file handle */
+    if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
+
+    return Success;
+}
+
+VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
+{   
+    WORD McbSegment = FIRST_MCB_SEGMENT;
+    PDOS_MCB CurrentMcb;
+    LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
+    PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
+
+    /* Check if this PSP is it's own parent */
+    if (PspBlock->ParentPsp == Psp) goto Done;
+
+    // TODO: Close all handles opened by the process
+
+    /* Free the memory used by the process */
+    while (TRUE)
+    {
+        /* Get a pointer to the MCB */
+        CurrentMcb = SEGMENT_TO_MCB(McbSegment);
+
+        /* Make sure the MCB is valid */
+        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 this was the last block, quit */
+        if (CurrentMcb->BlockType == 'Z') break;
+
+        /* 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;
+    }
+
+    /* Return control to the parent process */
+    EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
+                    LOWORD(PspBlock->TerminateAddress));
+}
+
+CHAR DosReadCharacter()
+{
+    // TODO: STDIN can be redirected under DOS 2.0+
+    return _getch();
+}
+
+VOID DosPrintCharacter(CHAR Character)
+{
+    // TODO: STDOUT can be redirected under DOS 2.0+
+    if (Character == '\r') Character = '\n';
+    putchar(Character);
+}
+
+VOID DosInt20h(WORD CodeSegment)
+{
+    /* This is the exit interrupt */
+    DosTerminateProcess(CodeSegment, 0);
+}
+
+VOID DosInt21h(WORD CodeSegment)
+{
+    INT i;
+    CHAR Character;
+    PCHAR String;
+    PDOS_INPUT_BUFFER InputBuffer;
+    DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
+    DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
+    DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
+    WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
+    WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
+
+    /* Check the value in the AH register */
+    switch (HIBYTE(Eax))
+    {
+        /* Terminate Program */
+        case 0x00:
+        {
+            DosTerminateProcess(CodeSegment, 0);
+            break;
+        }
+
+        /* Read Character And Echo */
+        case 0x01:
+        {
+            Character = DosReadCharacter();
+            DosPrintCharacter(Character);
+            EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
+            break;
+        }
+
+        /* Print Character */
+        case 0x02:
+        {
+            DosPrintCharacter(LOBYTE(Edx));
+            break;
+        }
+
+        /* Read Character Without Echo */
+        case 0x08:
+        {
+            EmulatorSetRegister(EMULATOR_REG_AX,
+                               (Eax & 0xFFFFFF00) | DosReadCharacter());
+            break;
+        }
+
+        /* Print String */
+        case 0x09:
+        {
+            String = (PCHAR)((ULONG_PTR)BaseAddress
+                     + TO_LINEAR(DataSegment, LOWORD(Edx)));
+
+            while ((*String) != '$')
+            {
+                DosPrintCharacter(*String);
+                String++;
+            }
+
+            break;
+        }
+
+        /* Read Buffered Input */
+        case 0x0A:
+        {
+            InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
+                                              + TO_LINEAR(DataSegment,
+                                                          LOWORD(Edx)));
+
+            InputBuffer->Length = 0;
+            for (i = 0; i < InputBuffer->MaxLength; i ++)
+            {
+                Character = DosReadCharacter();
+                DosPrintCharacter(Character);
+                InputBuffer->Buffer[InputBuffer->Length] = Character;
+                if (Character == '\r') break;
+                InputBuffer->Length++;
+            }
+
+            break;
+        }
+
+        /* Allocate Memory */
+        case 0x48:
+        {
+            WORD Segment = DosAllocateMemory(LOWORD(Ebx));
+            if (Segment != 0)
+            {
+                EmulatorSetRegister(EMULATOR_REG_AX, Segment);
+                EmulatorClearFlag(EMULATOR_FLAG_CF);
+            }
+            else EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+            break;
+        }
+
+        /* Free Memory */
+        case 0x49:
+        {
+            if (DosFreeMemory(ExtSegment))
+            {
+                EmulatorClearFlag(EMULATOR_FLAG_CF);
+            }
+            else EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+            break;
+        }
+
+        /* Resize Memory Block */
+        case 0x4A:
+        {
+            WORD Size = DosResizeMemory(ExtSegment, LOWORD(Ebx));
+
+            if (Size != 0)
+            {
+                EmulatorSetRegister(EMULATOR_REG_BX, Size);
+                EmulatorClearFlag(EMULATOR_FLAG_CF);
+            }
+            else EmulatorSetFlag(EMULATOR_FLAG_CF);
+
+            break;
+        }
+
+        /* Terminate With Return Code */
+        case 0x4C:
+        {
+            DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
+            break;
+        }
+
+        /* Unsupported */
+        default:
+        {
+            EmulatorSetFlag(EMULATOR_FLAG_CF);
+        }
+    }
+}
+
+VOID DosBreakInterrupt()
+{
+    VdmRunning = FALSE;
+}
+
+BOOLEAN DosInitialize()
+{
+    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));
+    DWORD AsciiSize;
+
+    /* Initialize the MCB */
+    Mcb->BlockType = 'Z';
+    Mcb->Size = (WORD)USER_MEMORY_SIZE;
+    Mcb->OwnerPsp = 0;
+
+    /* Get the environment strings */
+    SourcePtr = Environment = GetEnvironmentStringsW();
+    if (Environment == NULL) return FALSE;
+
+    /* Fill the DOS system environment block */
+    while (*SourcePtr)
+    {
+        /* Get the size of the ASCII string */
+        AsciiSize = WideCharToMultiByte(CP_ACP,
+                                        0,
+                                        SourcePtr,
+                                        -1,
+                                        NULL,
+                                        0,
+                                        NULL,
+                                        NULL);
+
+        /* Allocate memory for the ASCII string */
+        AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
+        if (AsciiString == NULL)
+        {
+            FreeEnvironmentStringsW(Environment);
+            return FALSE;
+        }
+
+        /* Convert to ASCII */
+        WideCharToMultiByte(CP_ACP,
+                            0,
+                            SourcePtr,
+                            -1,
+                            AsciiString,
+                            AsciiSize,
+                            NULL,
+                            NULL);
+
+        /* 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;
+    }
+    
+    /* Free the memory allocated for environment strings */
+    FreeEnvironmentStringsW(Environment);
+
+    /* Read CONFIG.SYS */
+    Stream = _wfopen(DOS_CONFIG_PATH, L"r");
+    if (Stream != NULL)
+    {
+        while (fgetws(Buffer, 256, Stream))
+        {
+            // TODO: Parse the line
+        }
+        fclose(Stream);
+    }
+    
+    return TRUE;
+}
Index: subsystems/ntvdm/emulator.c
===================================================================
--- subsystems/ntvdm/emulator.c	(revision 0)
+++ subsystems/ntvdm/emulator.c	(revision 0)
@@ -0,0 +1,253 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            emulator.c
+ * PURPOSE:         Minimal x86 machine emulator for the VDM
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+/* INCLUDES ******************************************************************/
+
+#include "ntvdm.h"
+#include <softx86/softx86.h>
+#include <softx86/softx87.h>
+
+softx86_ctx EmulatorContext;
+softx87_ctx FpuEmulatorContext;
+
+static VOID EmulatorReadMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
+{
+    /* Make sure the requested address is valid */
+    if ((Address + Size) >= MAX_ADDRESS) return;
+
+    /* Are we reading some of the console video memory? */
+    if (((Address + Size) >= CONSOLE_VIDEO_MEM_START)
+        && (Address < CONSOLE_VIDEO_MEM_END))
+    {
+        /* Call the VDM BIOS to update the video memory */
+        BiosUpdateConsole(max(Address, CONSOLE_VIDEO_MEM_START),
+                          min(Address + Size, CONSOLE_VIDEO_MEM_END));
+    }
+
+    /* Read the data from the virtual address space and store it in the buffer */
+    RtlCopyMemory(Buffer, (LPVOID)((ULONG_PTR)BaseAddress + Address), Size);
+}
+
+static VOID EmulatorWriteMemory(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
+{
+    /* Make sure the requested address is valid */
+    if ((Address + Size) >= MAX_ADDRESS) return;
+
+    /* Make sure we don't write to the ROM area */
+    if ((Address + Size) >= ROM_AREA_START && (Address < ROM_AREA_END)) return;
+
+    /* Read the data from the buffer and store it in the virtual address space */
+    RtlCopyMemory((LPVOID)((ULONG_PTR)BaseAddress + Address), Buffer, Size);
+
+    /* Check if we modified the console video memory */
+    if (((Address + Size) >= CONSOLE_VIDEO_MEM_START)
+        && (Address < CONSOLE_VIDEO_MEM_END))
+    {
+        /* Call the VDM BIOS to update the screen */
+        BiosUpdateConsole(max(Address, CONSOLE_VIDEO_MEM_START),
+                          min(Address + Size, CONSOLE_VIDEO_MEM_END));
+    }
+}
+
+static VOID EmulatorReadIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
+{
+    // TODO: NOT IMPLEMENTED!
+}
+
+static VOID EmulatorWriteIo(PVOID Context, UINT Address, LPBYTE Buffer, INT Size)
+{
+    // TODO: NOT IMPLEMENTED!
+}
+
+static VOID EmulatorSoftwareInt(PVOID Context, BYTE Number)
+{
+    WORD StackSegment, StackPointer, CodeSegment, InstructionPointer;
+    BYTE IntNum;
+
+    /* Check if this is the special interrupt */
+    if (Number == SPECIAL_INT_NUM)
+    {
+        /* Get the SS:SP */
+        StackSegment = EmulatorContext.state->segment_reg[SX86_SREG_SS].val;
+        StackPointer = EmulatorContext.state->general_reg[SX86_REG_SP].val;
+
+        /* Get the interrupt number */
+        IntNum = *(LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(StackSegment, StackPointer));
+
+        /* Move the stack pointer forward one word to skip the interrupt number */
+        StackPointer += sizeof(WORD);
+
+        /* Get the CS:IP */
+        InstructionPointer = *(LPWORD)((ULONG_PTR)BaseAddress
+                             + TO_LINEAR(StackSegment, StackPointer));
+        CodeSegment = *(LPWORD)((ULONG_PTR)BaseAddress
+                      + TO_LINEAR(StackSegment, StackPointer + sizeof(WORD)));
+
+        /* Check if this was an exception */
+        if (IntNum < 8)
+        {
+            /* Display a message to the user */
+            DisplayMessage(L"Exception: %s occured at %04X:%04X",
+                           ExceptionName[IntNum],
+                           CodeSegment,
+                           InstructionPointer);
+
+            /* Stop the VDM */
+            VdmRunning = FALSE;
+            return;
+        }
+
+        switch (IntNum)
+        {
+            case VIDEO_BIOS_INTERRUPT:
+            {
+                /* This is the video BIOS interrupt, call the BIOS */
+                BiosVideoService();
+                break;
+            }
+            case 0x20:
+            {
+                DosInt20h(CodeSegment);
+                break;
+            }
+            case 0x21:
+            {
+                DosInt21h(CodeSegment);
+                break;
+            }
+            case 0x23:
+            {
+                DosBreakInterrupt();
+                break;
+            }
+        }
+    }
+}
+
+/* PUBLIC FUNCTIONS **********************************************************/
+
+BOOLEAN EmulatorInitialize()
+{
+    /* Allocate memory for the 16-bit address space */
+    BaseAddress = HeapAlloc(GetProcessHeap(), 0, MAX_ADDRESS);
+    if (BaseAddress == NULL) return FALSE;
+
+    /* Initialize the softx86 CPU emulator */
+    if (!softx86_init(&EmulatorContext, SX86_CPULEVEL_80186))
+    {
+        HeapFree(GetProcessHeap(), 0, BaseAddress);
+        return FALSE;
+    }
+
+    /* Initialize the softx87 FPU emulator*/
+    if(!softx87_init(&FpuEmulatorContext, SX87_FPULEVEL_8087))
+    {
+        softx86_free(&EmulatorContext);
+        HeapFree(GetProcessHeap(), 0, BaseAddress);
+        return FALSE;
+    }
+
+    /* Set memory read/write callbacks */
+    EmulatorContext.callbacks->on_read_memory = EmulatorReadMemory;
+    EmulatorContext.callbacks->on_write_memory = EmulatorWriteMemory;
+
+    /* Set MMIO read/write callbacks */
+    EmulatorContext.callbacks->on_read_io = EmulatorReadIo;
+    EmulatorContext.callbacks->on_write_io = EmulatorWriteIo;
+
+    /* Set interrupt callbacks */
+    EmulatorContext.callbacks->on_sw_int = EmulatorSoftwareInt;
+
+    /* Connect the emulated FPU to the emulated CPU */
+    softx87_connect_to_CPU(&EmulatorContext, &FpuEmulatorContext);
+
+    return TRUE;
+}
+
+VOID EmulatorSetStack(WORD Segment, WORD Offset)
+{
+    /* Call the softx86 API */
+    softx86_set_stack_ptr(&EmulatorContext, Segment, Offset);
+}
+
+VOID EmulatorExecute(WORD Segment, WORD Offset)
+{
+    /* Call the softx86 API */
+    softx86_set_instruction_ptr(&EmulatorContext, Segment, Offset);
+}
+
+VOID EmulatorInterrupt(BYTE Number)
+{
+    LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
+    UINT Segment, Offset;
+
+    /* Get the segment and offset */
+    Segment = HIWORD(IntVecTable[Number]);
+    Offset = LOWORD(IntVecTable[Number]);
+
+    /* Call the softx86 API */
+    softx86_make_simple_interrupt_call(&EmulatorContext, &Segment, &Offset);
+}
+
+ULONG EmulatorGetRegister(ULONG Register)
+{
+    if (Register < EMULATOR_REG_CS)
+    {
+        return EmulatorContext.state->general_reg[Register].val;
+    }
+    else
+    {
+        return EmulatorContext.state->segment_reg[(Register >> 3) - 1].val;
+    }
+}
+
+VOID EmulatorSetRegister(ULONG Register, ULONG Value)
+{
+    if (Register < EMULATOR_REG_CS)
+    {
+        EmulatorContext.state->general_reg[Register].val = Value;
+    }
+    else
+    {
+        EmulatorContext.state->segment_reg[(Register >> 3) - 1].val = Value;
+    }
+}
+
+BOOLEAN EmulatorGetFlag(ULONG Flag)
+{
+    return (EmulatorContext.state->reg_flags.val & Flag);
+}
+
+VOID EmulatorSetFlag(ULONG Flag)
+{
+    EmulatorContext.state->reg_flags.val |= Flag;
+}
+
+VOID EmulatorClearFlag(ULONG Flag)
+{
+    EmulatorContext.state->reg_flags.val &= ~Flag;
+}
+
+VOID EmulatorStep()
+{
+    /* Call the softx86 API */
+    softx86_step(&EmulatorContext);
+}
+
+VOID EmulatorCleanup()
+{
+    /* Free the memory allocated for the 16-bit address space */
+    if (BaseAddress != NULL) HeapFree(GetProcessHeap(), 0, BaseAddress);
+
+    /* Free the softx86 CPU and FPU emulator */
+    softx86_free(&EmulatorContext);
+    softx87_free(&FpuEmulatorContext);
+}
+
+/* EOF */
+
Index: subsystems/ntvdm/ntvdm.c
===================================================================
--- subsystems/ntvdm/ntvdm.c	(revision 0)
+++ subsystems/ntvdm/ntvdm.c	(revision 0)
@@ -0,0 +1,128 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            ntvdm.c
+ * PURPOSE:         Virtual DOS Machine
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+#include "ntvdm.h"
+
+BOOLEAN VdmRunning = TRUE;
+LPVOID BaseAddress = NULL;
+LPCWSTR ExceptionName[] = 
+{
+    L"Division By Zero",
+    L"Debug",
+    L"Unexpected Error",
+    L"Breakpoint",
+    L"Integer Overflow",
+    L"Bound Range Exceeded",
+    L"Invalid Opcode",
+    L"FPU Not Available"
+};
+
+VOID DisplayMessage(LPCWSTR Format, ...)
+{
+    WCHAR Buffer[256];
+    va_list Parameters;
+
+    va_start(Parameters, Format);
+    _vsnwprintf(Buffer, 256, Format, Parameters);
+    MessageBox(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
+    va_end(Parameters);
+}
+
+BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
+{
+    switch (ControlType)
+    {
+        case CTRL_C_EVENT:
+        case CTRL_BREAK_EVENT:
+        {
+            /* Perform interrupt 0x23 */
+            EmulatorInterrupt(0x23);
+        }
+        default:
+        {
+            /* Stop the VDM if the user logs out or closes the console */
+            VdmRunning = FALSE;
+        }
+    }
+    return TRUE;
+}
+
+INT wmain(INT argc, WCHAR *argv[])
+{
+    INT i;
+    BOOLEAN PrintUsage = TRUE;
+    CHAR CommandLine[128];
+
+    /* Set the handler routine */
+    SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
+
+    /* Parse the command line arguments */
+    for (i = 1; i < argc; i++)
+    {
+        if (argv[i][0] != L'-' && argv[i][0] != L'/') continue;
+        
+        switch (argv[i][1])
+        {
+            case L'f':
+            case L'F':
+            {
+                if (argv[i+1] != NULL)
+                {
+                    /* The DOS command line must be ASCII */
+                    WideCharToMultiByte(CP_ACP, 0, argv[i+1], -1, CommandLine, 128, NULL, NULL);
+                
+                    /* This is the only mandatory parameter */
+                    PrintUsage = FALSE;
+                }
+                break;
+            }
+            default:
+            {
+                wprintf(L"Unknown option: %s", argv[i]);
+            }
+        }
+    }
+
+    if (PrintUsage)
+    {
+        wprintf(L"ReactOS Virtual DOS Machine\n\n");
+        wprintf(L"Usage: NTVDM /F <PROGRAM>\n");
+        return 0;
+    }
+
+    if (!EmulatorInitialize()) return 1;
+
+    /* Initialize the system BIOS */
+    if (!BiosInitialize())
+    {
+        wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
+        goto Cleanup;
+    }
+
+    /* Initialize the VDM DOS kernel */
+    if (!DosInitialize())
+    {
+        wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
+        goto Cleanup;
+    }
+
+    /* Start the process from the command line */
+    if (!DosCreateProcess(CommandLine, 0))
+    {
+        DisplayMessage(L"Could not start program: %S", CommandLine);
+        return -1;
+    }
+
+    /* Main loop */
+    while (VdmRunning) EmulatorStep();
+
+Cleanup:
+    EmulatorCleanup();
+
+    return 0;
+}
Index: subsystems/ntvdm/ntvdm.rc
===================================================================
--- subsystems/ntvdm/ntvdm.rc	(revision 0)
+++ subsystems/ntvdm/ntvdm.rc	(revision 0)
@@ -0,0 +1,6 @@
+
+#include <windows.h>
+#define REACTOS_STR_FILE_DESCRIPTION	"ReactOS Virtual DOS Machine\0"
+#define REACTOS_STR_INTERNAL_NAME	"ntvdm\0"
+#define REACTOS_STR_ORIGINAL_FILENAME	"ntvdm.exe\0"
+#include <reactos/version.rc>
Index: subsystems/ntvdm/CMakeLists.txt
===================================================================
--- subsystems/ntvdm/CMakeLists.txt	(revision 0)
+++ subsystems/ntvdm/CMakeLists.txt	(revision 0)
@@ -0,0 +1,15 @@
+
+include_directories(.)
+
+add_executable(ntvdm
+    ntvdm.c
+    emulator.c
+    bios.c
+    dos.c
+    ntvdm.rc)
+
+set_module_type(ntvdm win32cui UNICODE)
+target_link_libraries(ntvdm softx86 softx87)
+add_importlibs(ntvdm msvcrt user32 kernel32)
+add_dependencies(ntvdm softx86 softx87)
+add_cd_file(TARGET ntvdm DESTINATION reactos/system32 FOR all)
Index: subsystems/ntvdm/bios.c
===================================================================
--- subsystems/ntvdm/bios.c	(revision 0)
+++ subsystems/ntvdm/bios.c	(revision 0)
@@ -0,0 +1,223 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            bios.c
+ * PURPOSE:         VDM BIOS
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+#include "ntvdm.h"
+
+BYTE CursorRow, CursorCol;
+WORD ConsoleWidth, ConsoleHeight;
+
+BOOLEAN BiosInitialize()
+{
+    INT i;
+    WORD Offset = 0;
+    HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+    CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
+    LPWORD IntVecTable = (LPWORD)((ULONG_PTR)BaseAddress);
+    LPBYTE BiosCode = (LPBYTE)((ULONG_PTR)BaseAddress + TO_LINEAR(BIOS_SEGMENT, 0));
+
+    /* Generate ISR stubs and fill the IVT */
+    for (i = 0; i < 256; i++)
+    {
+        IntVecTable[i * 2] = Offset;
+        IntVecTable[i * 2 + 1] = BIOS_SEGMENT;
+        
+        if (i != SPECIAL_INT_NUM)
+        {
+            BiosCode[Offset++] = 0xFA; // cli
+
+            BiosCode[Offset++] = 0x6A; // push i
+            BiosCode[Offset++] = (BYTE)i;
+
+            BiosCode[Offset++] = 0xCD; // int SPECIAL_INT_NUM
+            BiosCode[Offset++] = SPECIAL_INT_NUM;
+
+            BiosCode[Offset++] = 0x83; // add sp, 2
+            BiosCode[Offset++] = 0xC4;
+            BiosCode[Offset++] = 0x02;
+        }
+
+        BiosCode[Offset++] = 0xCF; // iret
+    }
+
+    /* Get the console buffer info */
+    if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo))
+    {
+        return FALSE;
+    }
+
+    /* Set the initial cursor position and console size */
+    CursorCol = ConsoleInfo.dwCursorPosition.X;
+    CursorRow = ConsoleInfo.dwCursorPosition.Y;
+    ConsoleWidth = ConsoleInfo.dwSize.X;
+    ConsoleHeight = ConsoleInfo.dwSize.Y;
+
+    return TRUE;
+}
+
+static COORD BiosVideoAddressToCoord(ULONG Address)
+{
+    COORD Result = {0, 0};
+    CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
+    HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+
+    if (!GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo))
+    {
+        assert(0);
+        return Result;
+    }
+
+    Result.X = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) % ConsoleInfo.dwSize.X;
+    Result.Y = ((Address - CONSOLE_VIDEO_MEM_START) >> 1) / ConsoleInfo.dwSize.X;
+
+    return Result;
+}
+
+VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress)
+{
+    ULONG i;
+    COORD Coordinates;
+    DWORD CharsWritten;
+    HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+
+    /* Loop through all the addresses */
+    for (i = StartAddress; i < EndAddress; i++)
+    {
+        /* Get the coordinates */
+        Coordinates = BiosVideoAddressToCoord(i);
+        
+        /* Check if this is a character byte or an attribute byte */
+        if ((i - CONSOLE_VIDEO_MEM_START) % 2 == 0)
+        {
+            /* This is a regular character */
+            FillConsoleOutputCharacterA(ConsoleOutput,
+                                        *(PCHAR)((ULONG_PTR)BaseAddress + i),
+                                        sizeof(CHAR),
+                                        Coordinates,
+                                        &CharsWritten);
+        }
+        else
+        {
+            /*  This is an attribute */
+            FillConsoleOutputAttribute(ConsoleOutput,
+                                       *(PCHAR)((ULONG_PTR)BaseAddress + i),
+                                       sizeof(CHAR),
+                                       Coordinates,
+                                       &CharsWritten);
+        }
+    }
+}
+
+VOID BiosUpdateVideoMemory(ULONG StartAddress, ULONG EndAddress)
+{
+    ULONG i;
+    COORD Coordinates;
+    WORD Attribute;
+    DWORD CharsWritten;
+    HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+
+    /* Loop through all the addresses */
+    for (i = StartAddress; i < EndAddress; i++)
+    {
+        /* Get the coordinates */
+        Coordinates = BiosVideoAddressToCoord(i);
+        
+        /* Check if this is a character byte or an attribute byte */
+        if ((i - CONSOLE_VIDEO_MEM_START) % 2 == 0)
+        {
+            /* This is a regular character */
+            ReadConsoleOutputCharacterA(ConsoleOutput,
+                                        (LPSTR)((ULONG_PTR)BaseAddress + i),
+                                        sizeof(CHAR),
+                                        Coordinates,
+                                        &CharsWritten);
+        }
+        else
+        {
+            /*  This is an attribute */
+            ReadConsoleOutputAttribute(ConsoleOutput,
+                                       &Attribute,
+                                       sizeof(CHAR),
+                                       Coordinates,
+                                       &CharsWritten);
+
+            *(PCHAR)((ULONG_PTR)BaseAddress + i) = LOBYTE(Attribute);
+        }
+    }
+}
+
+VOID BiosVideoService()
+{
+    HANDLE ConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
+    INT CursorHeight;
+    BOOLEAN Invisible = FALSE;
+    COORD CursorPosition;
+    CONSOLE_CURSOR_INFO CursorInfo;
+    DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
+    DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
+    DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
+
+    switch (LOBYTE(Eax))
+    {
+        /* Set Text-Mode Cursor Shape */
+        case 0x01:
+        {
+            /* Retrieve and validate the input */
+            Invisible = ((HIBYTE(Ecx) >> 5) & 0x03) ? TRUE : FALSE;
+            CursorHeight = (HIBYTE(Ecx) & 0x1F) - (LOBYTE(Ecx) & 0x1F);
+            if (CursorHeight < 1) CursorHeight = 1;
+            if (CursorHeight > 100) CursorHeight = 100;
+
+            /* Set the cursor */
+            CursorInfo.dwSize = (CursorHeight * 100) / CONSOLE_FONT_HEIGHT;
+            CursorInfo.bVisible = !Invisible;
+            SetConsoleCursorInfo(ConsoleOutput, &CursorInfo);
+
+            break;
+        }
+
+        /* Set Cursor Position */
+        case 0x02:
+        {
+            CursorPosition.X = LOBYTE(Edx);
+            CursorPosition.Y = HIBYTE(Edx);
+
+            SetConsoleCursorPosition(ConsoleOutput, CursorPosition);
+            break;
+        }
+
+        /* Scroll Up Window */
+        case 0x06:
+        {
+            break;
+        }
+
+        /* Scroll Down Window */
+        case 0x07:
+        {
+            break;
+        }
+
+        /* Read Character And Attribute At Cursor Position */
+        case 0x08:
+        {
+            break;
+        }
+
+        /* Write Character And Attribute At Cursor Position */
+        case 0x09:
+        {
+            break;
+        }
+
+        /* Write Character Only At Cursor Position */
+        case 0x0A:
+        {
+            break;
+        }
+    }
+}
Index: subsystems/ntvdm/ntvdm.h
===================================================================
--- subsystems/ntvdm/ntvdm.h	(revision 0)
+++ subsystems/ntvdm/ntvdm.h	(revision 0)
@@ -0,0 +1,200 @@
+/*
+ * COPYRIGHT:       GPL - See COPYING in the top level directory
+ * PROJECT:         ReactOS Virtual DOS Machine
+ * FILE:            ntvdm.h
+ * PURPOSE:         Header file to define commonly used stuff
+ * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
+ */
+
+/* INCLUDES ******************************************************************/
+
+#include <windows.h>
+#include <tchar.h>
+#include <stdio.h>
+#include <conio.h>
+#include <assert.h>
+#include <stdarg.h>
+
+/* DEFINES *******************************************************************/ 
+
+#define TO_LINEAR(seg, off) (((seg) << 4) + (off))
+#define MAX_SEGMENT 0xFFFF
+#define MAX_OFFSET 0xFFFF
+#define MAX_ADDRESS TO_LINEAR(MAX_SEGMENT, MAX_OFFSET)
+#define ROM_AREA_START 0xC0000
+#define ROM_AREA_END 0xFFFFF
+#define BIOS_SEGMENT 0xF000
+#define VIDEO_BIOS_INTERRUPT 0x10
+#define SPECIAL_INT_NUM 0xFF
+#define SEGMENT_TO_MCB(seg) ((PDOS_MCB)((ULONG_PTR)BaseAddress + TO_LINEAR((seg), 0)))
+#define SEGMENT_TO_PSP(seg) ((PDOS_PSP)((ULONG_PTR)BaseAddress + TO_LINEAR((seg), 0)))
+
+/* DOS constants */
+#define DOS_VERSION 0x0600
+#define DOS_CONFIG_PATH L"%SystemRoot%\\system32\\CONFIG.NT"
+#define DOS_COMMAND_INTERPRETER L"%SystemRoot%\\system32\\COMMAND.COM /k %SystemRoot%\\system32\\AUTOEXEC.NT"
+#define FIRST_MCB_SEGMENT 0x1000
+#define USER_MEMORY_SIZE 0x8FFFF
+#define SYSTEM_PSP 0x08
+#define SYSTEM_ENV_BLOCK 0x800
+
+/* System console constants */
+#define CONSOLE_FONT_HEIGHT 8
+#define CONSOLE_VIDEO_MEM_START 0xB8000
+#define CONSOLE_VIDEO_MEM_END 0xBFFFF
+
+#define EMULATOR_FLAG_CF (1 << 0)
+#define EMULATOR_FLAG_PF (1 << 2)
+#define EMULATOR_FLAG_AF (1 << 4)
+#define EMULATOR_FLAG_ZF (1 << 6)
+#define EMULATOR_FLAG_SF (1 << 7)
+#define EMULATOR_FLAG_TF (1 << 8)
+#define EMULATOR_FLAG_IF (1 << 9)
+#define EMULATOR_FLAG_DF (1 << 10)
+#define EMULATOR_FLAG_OF (1 << 11)
+#define EMULATOR_FLAG_NT (1 << 14)
+#define EMULATOR_FLAG_RF (1 << 16)
+#define EMULATOR_FLAG_VM (1 << 17)
+#define EMULATOR_FLAG_AC (1 << 18)
+#define EMULATOR_FLAG_VIF (1 << 19)
+#define EMULATOR_FLAG_VIP (1 << 20)
+#define EMULATOR_FLAG_ID (1 << 21)
+
+typedef enum
+{
+    EMULATOR_REG_AX,
+    EMULATOR_REG_CX,
+    EMULATOR_REG_DX,
+    EMULATOR_REG_BX,
+    EMULATOR_REG_SI,
+    EMULATOR_REG_DI,
+    EMULATOR_REG_SP,
+    EMULATOR_REG_BP,
+    EMULATOR_REG_CS,
+    EMULATOR_REG_SS,
+    EMULATOR_REG_DS,
+    EMULATOR_REG_ES,
+    EMULATOR_REG_FS,
+    EMULATOR_REG_GS
+} EMULATOR_REGISTER;
+
+#pragma pack(push, 1)
+
+typedef struct _DOS_MCB
+{
+    CHAR BlockType;
+    WORD OwnerPsp;
+    WORD Size;
+    BYTE Unused[3];
+    CHAR Name[8];
+} DOS_MCB, *PDOS_MCB;
+
+typedef struct _DOS_FCB
+{
+    BYTE DriveNumber;
+    CHAR FileName[8];
+    CHAR FileExt[3];
+    WORD BlockNumber;
+    WORD RecordSize;
+    DWORD FileSize;
+    WORD LastWriteDate;
+    WORD LastWriteTime;
+    BYTE Reserved[8];
+    BYTE BlockRecord;
+    BYTE RecordNumber[3];
+} DOS_FCB, *PDOS_FCB;
+
+typedef struct _DOS_PSP
+{
+    BYTE Exit[2];
+    WORD MemSize;
+    BYTE Reserved0[6];
+    DWORD TerminateAddress;
+    DWORD BreakAddress;
+    DWORD CriticalAddress;
+    WORD ParentPsp;
+    BYTE HandleTable[20];
+    WORD EnvBlock;
+    DWORD LastStack;
+    WORD HandleTableSize;
+    DWORD HandleTablePtr;
+    DWORD PreviousPsp;
+    DWORD Reserved1;
+    WORD DosVersion;
+    BYTE Reserved2[14];
+    BYTE FarCall[3];
+    BYTE Reserved3[9];
+    DOS_FCB Fcb;
+    BYTE CommandLineSize;
+    CHAR CommandLine[127];
+} DOS_PSP, *PDOS_PSP;
+
+typedef struct _DOS_SFT_ENTRY
+{
+    WORD ReferenceCount;
+    WORD Mode;
+    BYTE Attribute;
+    WORD DeviceInfo;
+    DWORD DriveParamBlock;
+    WORD FirstCluster;
+    WORD FileTime;
+    WORD FileDate;
+    DWORD FileSize;
+    DWORD CurrentOffset;
+    WORD LastClusterAccessed;
+    DWORD DirEntSector;
+    BYTE DirEntryIndex;
+    CHAR FileName[11];
+    BYTE Reserved0[6];
+    WORD OwnerPsp;
+    BYTE Reserved1[8];
+} DOS_SFT_ENTRY, *PDOS_SFT_ENTRY;
+
+typedef struct _DOS_SFT
+{
+    DWORD NextTablePtr;
+    WORD FileCount;
+    DOS_SFT_ENTRY Entry[ANYSIZE_ARRAY];
+} DOS_SFT, *PDOS_SFT;
+
+typedef struct _DOS_INPUT_BUFFER
+{
+    BYTE MaxLength, Length;
+    CHAR Buffer[ANYSIZE_ARRAY];
+} DOS_INPUT_BUFFER, *PDOS_INPUT_BUFFER;
+
+#pragma pack(pop)
+
+/* FUNCTIONS *****************************************************************/
+
+extern LPVOID BaseAddress;
+extern BOOLEAN VdmRunning;
+extern LPCWSTR ExceptionName[];
+
+VOID DisplayMessage(LPCWSTR Format, ...);
+BOOLEAN BiosInitialize();
+VOID BiosUpdateConsole(ULONG StartAddress, ULONG EndAddress);
+VOID BiosPrintCharacter(CHAR Character, BYTE Attribute);
+BOOLEAN DosInitialize();
+WORD DosAllocateMemory(WORD Size);
+BOOLEAN DosFreeMemory(WORD Segment);
+WORD DosResizeMemory(WORD Segment, WORD NewSize);
+BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock);
+VOID DosInt20h(WORD CodeSegment);
+VOID DosInt21h(WORD CodeSegment);
+VOID DosBreakInterrupt();
+VOID BiosVideoService();
+VOID EmulatorSetStack(WORD Segment, WORD Offset);
+VOID EmulatorExecute(WORD Segment, WORD Offset);
+VOID EmulatorInterrupt(BYTE Number);
+ULONG EmulatorGetRegister(ULONG Register);
+VOID EmulatorSetRegister(ULONG Register, ULONG Value);
+VOID EmulatorSetFlag(ULONG Flag);
+VOID EmulatorClearFlag(ULONG Flag);
+BOOLEAN EmulatorGetFlag(ULONG Flag);
+BOOLEAN EmulatorInitialize();
+VOID EmulatorStep();
+VOID EmulatorCleanup();
+
+/* EOF */
+
