[NTVDM]: Implement and export VDDTerminateVDM.
[reactos.git] / subsystems / ntvdm / dos.c
index 80a330d..e478070 100644 (file)
 
 #define NDEBUG
 
-#include "dos.h"
-#include "bios.h"
 #include "emulator.h"
+#include "dos.h"
 
+#include "bios.h"
+#include "bop.h"
+#include "int32.h"
 #include "registers.h"
 
 /* PRIVATE VARIABLES **********************************************************/
@@ -28,6 +30,11 @@ static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
 static WORD DosSftRefCount[DOS_SFT_SIZE];
 static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
 static BOOLEAN DosUmbLinked = FALSE;
+static WORD DosErrorLevel = 0x0000;
+
+/* BOP Identifiers */
+#define BOP_DOS 0x50    // DOS System BOP (for NTIO.SYS and NTDOS.SYS)
+#define BOP_CMD 0x54    // DOS Command Interpreter BOP (for COMMAND.COM)
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
@@ -789,6 +796,24 @@ WORD DosSeekFile(WORD FileHandle, LONG Offset, BYTE Origin, LPDWORD NewOffset)
     return ERROR_SUCCESS;
 }
 
+BOOLEAN DosFlushFileBuffers(WORD FileHandle)
+{
+    HANDLE Handle = DosGetRealHandle(FileHandle);
+
+    /* Make sure the handle is valid */
+    if (Handle == INVALID_HANDLE_VALUE) return FALSE;
+
+    /*
+     * No need to check whether the handle is a console handle since
+     * FlushFileBuffers() automatically does this check and calls
+     * FlushConsoleInputBuffer() for us.
+     */
+    // if (IsConsoleHandle(Handle))
+    //    return (BOOLEAN)FlushConsoleInputBuffer(Handle);
+    // else
+    return (BOOLEAN)FlushFileBuffers(Handle);
+}
+
 BOOLEAN DosDuplicateHandle(WORD OldHandle, WORD NewHandle)
 {
     BYTE SftIndex;
@@ -939,9 +964,15 @@ BOOLEAN DosChangeDirectory(LPSTR Directory)
     }
 
     /* Set the directory for the drive */
-    if (Path != NULL) strcpy(CurrentDirectories[DriveNumber], Path);
-    else strcpy(CurrentDirectories[DriveNumber], "");
-    
+    if (Path != NULL)
+    {
+        strncpy(CurrentDirectories[DriveNumber], Path, DOS_DIR_LENGTH);
+    }
+    else
+    {
+        CurrentDirectories[DriveNumber][0] = '\0';
+    }
+
     /* Return success */
     return TRUE;
 }
@@ -962,8 +993,8 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR
 
     /* Save the interrupt vectors */
     PspBlock->TerminateAddress = IntVecTable[0x22];
-    PspBlock->BreakAddress = IntVecTable[0x23];
-    PspBlock->CriticalAddress = IntVecTable[0x24];
+    PspBlock->BreakAddress     = IntVecTable[0x23];
+    PspBlock->CriticalAddress  = IntVecTable[0x24];
 
     /* Set the parent PSP */
     PspBlock->ParentPsp = CurrentPsp;
@@ -999,6 +1030,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
     LPBYTE Address = NULL;
     LPSTR ProgramFilePath, Parameters[256];
     CHAR CommandLineCopy[DOS_CMDLINE_LENGTH];
+    CHAR ParamString[DOS_CMDLINE_LENGTH];
     INT ParamCount = 0;
     WORD Segment = 0;
     WORD MaxAllocSize;
@@ -1014,8 +1046,6 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
     /* Save a copy of the command line */
     strcpy(CommandLineCopy, CommandLine);
 
-    // FIXME: Improve parsing (especially: "some_path\with spaces\program.exe" options)
-
     /* Get the file name of the executable */
     ProgramFilePath = strtok(CommandLineCopy, " \t");
 
@@ -1026,6 +1056,15 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
         ParamCount++;
     }
 
+    ZeroMemory(ParamString, sizeof(ParamString));
+
+    /* Store the parameters in a string */
+    for (i = 0; i < ParamCount; i++)
+    {
+        strncat(ParamString, Parameters[i], DOS_CMDLINE_LENGTH - strlen(ParamString) - 1);
+        strncat(ParamString, " ", DOS_CMDLINE_LENGTH - strlen(ParamString) - 1);
+    }
+
     /* Open a handle to the executable */
     FileHandle = CreateFileA(ProgramFilePath,
                              GENERIC_READ,
@@ -1098,7 +1137,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
 
         /* Initialize the PSP */
         DosInitializePsp(Segment,
-                         CommandLine,
+                         ParamString,
                          (WORD)ExeSize,
                          EnvBlock);
 
@@ -1167,8 +1206,8 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
 
         /* Initialize the PSP */
         DosInitializePsp(Segment,
-                         CommandLine,
-                         (WORD)((FileSize + sizeof(DOS_PSP)) >> 4),
+                         ParamString,
+                         MaxAllocSize,
                          EnvBlock);
 
         /* Set the initial segment registers */
@@ -1178,6 +1217,12 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
         /* Set the stack to the last word of the segment */
         EmulatorSetStack(Segment, 0xFFFE);
 
+        /*
+         * Set the value on the stack to 0, so that a near return
+         * jumps to PSP:0000 which has the exit code.
+         */
+        *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0;
+
         /* Execute */
         CurrentPsp = Segment;
         DiskTransferArea = MAKELONG(0x80, Segment);
@@ -1259,6 +1304,9 @@ Done:
         if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
     }
 
+    /* Save the return code - Normal termination */
+    DosErrorLevel = MAKEWORD(ReturnCode, 0x00);
+
     /* Return control to the parent process */
     EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
                     LOWORD(PspBlock->TerminateAddress));
@@ -1359,15 +1407,103 @@ BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
     }
 }
 
-VOID DosInt20h(LPWORD Stack)
+VOID WINAPI DosSystemBop(LPWORD Stack)
+{
+    /* Get the Function Number and skip it */
+    BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
+    setIP(getIP() + 1);
+
+    DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum);
+}
+
+VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
+{
+    /* Get the Function Number and skip it */
+    BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
+    setIP(getIP() + 1);
+
+    switch (FuncNum)
+    {
+        case 0x08: // Launch external command
+        {
+#define CMDLINE_LENGTH  1024
+
+            BOOL Result;
+            DWORD dwExitCode;
+
+            LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
+            CHAR CommandLine[CMDLINE_LENGTH] = "";
+            STARTUPINFOA StartupInfo;
+            PROCESS_INFORMATION ProcessInformation;
+            DPRINT1("CMD Run Command '%s'\n", Command);
+
+            Command[strlen(Command)-1] = 0;
+            
+            strcpy(CommandLine, "cmd.exe /c ");
+            strcat(CommandLine, Command);
+
+            ZeroMemory(&StartupInfo, sizeof(StartupInfo));
+            ZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
+
+            StartupInfo.cb = sizeof(StartupInfo);
+
+            DosPrintCharacter('\n');
+
+            Result = CreateProcessA(NULL,
+                                    CommandLine,
+                                    NULL,
+                                    NULL,
+                                    TRUE,
+                                    0,
+                                    NULL,
+                                    NULL,
+                                    &StartupInfo,
+                                    &ProcessInformation);
+            if (Result)
+            {
+                DPRINT1("Command '%s' launched successfully\n");
+
+                /* Wait for process termination */
+                WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
+
+                /* Get the exit code */
+                GetExitCodeProcess(ProcessInformation.hProcess, &dwExitCode);
+
+                /* Close handles */
+                CloseHandle(ProcessInformation.hThread);
+                CloseHandle(ProcessInformation.hProcess);
+            }
+            else
+            {
+                DPRINT1("Failed when launched command '%s'\n");
+                dwExitCode = GetLastError();
+            }
+            
+            DosPrintCharacter('\n');
+
+            setAL((UCHAR)dwExitCode);
+
+            break;
+        }
+
+        default:
+        {
+            DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum);
+            // setCF(1); // Disable, otherwise we enter an infinite loop
+            break;
+        }
+    }
+}
+
+VOID WINAPI DosInt20h(LPWORD Stack)
 {
     /* This is the exit interrupt */
     DosTerminateProcess(Stack[STACK_CS], 0);
 }
 
-VOID DosInt21h(LPWORD Stack)
+VOID WINAPI DosInt21h(LPWORD Stack)
 {
-    CHAR Character;
+    BYTE Character;
     SYSTEMTIME SystemTime;
     PCHAR String;
     PDOS_INPUT_BUFFER InputBuffer;
@@ -1382,38 +1518,68 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Read Character And Echo */
+        /* Read Character from STDIN with Echo */
         case 0x01:
         {
             Character = DosReadCharacter();
             DosPrintCharacter(Character);
 
             /* Let the BOP repeat if needed */
-            if (EmulatorGetFlag(EMULATOR_FLAG_CF)) break;
+            if (getCF()) break;
 
             setAL(Character);
             break;
         }
 
-        /* Print Character */
+        /* Write Character to STDOUT */
         case 0x02:
         {
-            BYTE Character = getDL();
+            Character = getDL();
             DosPrintCharacter(Character);
 
             /*
-             * We return the output character (DOS 2.1+), see:
-             * http://www.delorie.com/djgpp/doc/rbinter/id/65/25.html
+             * We return the output character (DOS 2.1+).
+             * Also, if we're going to output a TAB, then
+             * don't return a TAB but a SPACE instead.
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
              * for more information.
              */
-            setAL(Character);
+            setAL(Character == '\t' ? ' ' : Character);
+            break;
+        }
+
+        /* Read Character from STDAUX */
+        case 0x03:
+        {
+            // FIXME: Really read it from STDAUX!
+            DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
+            setAL(DosReadCharacter());
+            break;
+        }
+
+        /* Write Character to STDAUX */
+        case 0x04:
+        {
+            // FIXME: Really write it to STDAUX!
+            DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
+            DosPrintCharacter(getDL());
+            break;
+        }
+
+        /* Write Character to Printer */
+        case 0x05:
+        {
+            // FIXME: Really write it to printer!
+            DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
+            DPRINT1("0x%p\n", getDL());
+            DPRINT1("\n\n-----------\n\n");
             break;
         }
 
         /* Direct Console I/O */
         case 0x06:
         {
-            BYTE Character = getDL();
+            Character = getDL();
 
             if (Character != 0xFF)
             {
@@ -1421,8 +1587,8 @@ VOID DosInt21h(LPWORD Stack)
                 DosPrintCharacter(Character);
 
                 /*
-                 * We return the output character (DOS 2.1+), see:
-                 * http://www.delorie.com/djgpp/doc/rbinter/id/69/25.html
+                 * We return the output character (DOS 2.1+).
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
                  * for more information.
                  */
                 setAL(Character);
@@ -1439,27 +1605,27 @@ VOID DosInt21h(LPWORD Stack)
                 {
                     /* No character available */
                     Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
-                    setAL(0);
+                    setAL(0x00);
                 }
             }
 
             break;
         }
 
-        /* Read Character Without Echo */
+        /* Character Input without Echo */
         case 0x07:
         case 0x08:
         {
             Character = DosReadCharacter();
 
             /* Let the BOP repeat if needed */
-            if (EmulatorGetFlag(EMULATOR_FLAG_CF)) break;
+            if (getCF()) break;
 
             setAL(Character);
             break;
         }
 
-        /* Print String */
+        /* Write string to STDOUT */
         case 0x09:
         {
             String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
@@ -1471,8 +1637,8 @@ VOID DosInt21h(LPWORD Stack)
             }
 
             /*
-             * We return the output character (DOS 2.1+), see:
-             * http://www.delorie.com/djgpp/doc/rbinter/id/73/25.html
+             * We return the terminating character (DOS 2.1+).
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
              * for more information.
              */
             setAL('$');
@@ -1490,7 +1656,7 @@ VOID DosInt21h(LPWORD Stack)
                 Character = DosReadCharacter();
 
                 /* If it's not ready yet, let the BOP repeat */
-                if (EmulatorGetFlag(EMULATOR_FLAG_CF)) break;
+                if (getCF()) break;
 
                 /* Echo the character and append it to the buffer */
                 DosPrintCharacter(Character);
@@ -1512,6 +1678,49 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
+        /* Flush Buffer and Read STDIN */
+        case 0x0C:
+        {
+            BYTE InputFunction = getAL();
+
+            /* Flush STDIN buffer */
+            DosFlushFileBuffers(DOS_INPUT_HANDLE); // Maybe just create a DosFlushInputBuffer...
+
+            /*
+             * If the input function number contained in AL is valid, i.e.
+             * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
+             * recursively with AL == AH.
+             */
+            if (InputFunction == 0x01 || InputFunction == 0x06 ||
+                InputFunction == 0x07 || InputFunction == 0x08 ||
+                InputFunction == 0x0A)
+            {
+                setAH(InputFunction);
+                /*
+                 * Instead of calling ourselves really recursively as in:
+                 * DosInt21h(Stack);
+                 * prefer resetting the CF flag to let the BOP repeat.
+                 */
+                setCF(1);
+            }
+            break;
+        }
+
+        /* Disk Reset */
+        case 0x0D:
+        {
+            PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
+
+            // TODO: Flush what's needed.
+            DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
+
+            /* Clear CF in DOS 6 only */
+            if (PspBlock->DosVersion == 0x0006)
+                Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+
+            break;
+        }
+
         /* Set Default Drive  */
         case 0x0E:
         {
@@ -1520,6 +1729,22 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
+        /* NULL Function for CP/M Compatibility */
+        case 0x18:
+        {
+            /*
+             * This function corresponds to the CP/M BDOS function
+             * "get bit map of logged drives", which is meaningless
+             * under MS-DOS.
+             *
+             * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
+             * for more information.
+             */
+            setAL(0x00);
+            break;
+        }
+
         /* Get Default Drive */
         case 0x19:
         {
@@ -1534,18 +1759,61 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
+        /* NULL Function for CP/M Compatibility */
+        case 0x1D:
+        case 0x1E:
+        {
+            /*
+             * Function 0x1D corresponds to the CP/M BDOS function
+             * "get bit map of read-only drives", which is meaningless
+             * under MS-DOS.
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
+             * for more information.
+             *
+             * Function 0x1E corresponds to the CP/M BDOS function
+             * "set file attributes", which was meaningless under MS-DOS 1.x.
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
+             * for more information.
+             */
+            setAL(0x00);
+            break;
+        }
+
+        /* NULL Function for CP/M Compatibility */
+        case 0x20:
+        {
+            /*
+             * This function corresponds to the CP/M BDOS function
+             * "get/set default user (sublibrary) number", which is meaningless
+             * under MS-DOS.
+             *
+             * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
+             * for more information.
+             */
+            setAL(0x00);
+            break;
+        }
+
         /* Set Interrupt Vector */
         case 0x25:
         {
             DWORD FarPointer = MAKELONG(getDX(), getDS());
+            DPRINT1("Setting interrupt 0x%x ...\n", getAL());
 
             /* Write the new far pointer to the IDT */
             ((PDWORD)BaseAddress)[getAL()] = FarPointer;
+            break;
+        }
 
+        /* Create New PSP */
+        case 0x26:
+        {
+            DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
             break;
         }
 
-        /* Get system date */
+        /* Get System Date */
         case 0x2A:
         {
             GetLocalTime(&SystemTime);
@@ -1555,7 +1823,7 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Set system date */
+        /* Set System Date */
         case 0x2B:
         {
             GetLocalTime(&SystemTime);
@@ -1568,7 +1836,7 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Get system time */
+        /* Get System Time */
         case 0x2C:
         {
             GetLocalTime(&SystemTime);
@@ -1577,7 +1845,7 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Set system time */
+        /* Set System Time */
         case 0x2D:
         {
             GetLocalTime(&SystemTime);
@@ -1604,29 +1872,79 @@ VOID DosInt21h(LPWORD Stack)
         {
             PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
 
-            if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0)
-            {
-                /* Return DOS 24-bit user serial number in BL:CX */
-                setBL(0x00);
-                setCX(0x0000);
-            }
+            /*
+             * DOS 2+ - GET DOS VERSION
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
+             * for more information.
+             */
 
-            if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 1)
+            if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
             {
                 /*
                  * Return DOS OEM number:
                  * 0x00 for IBM PC-DOS
-                 * 0xFF for MS-DOS
+                 * 0x02 for packaged MS-DOS
+                 */
+                setBH(0x02);
+            }
+
+            if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
+            {
+                /*
+                 * Return version flag:
+                 * 1 << 3 if DOS is in ROM,
+                 * 0 (reserved) if not.
                  */
-                setBH(0xFF);
+                setBH(0x00);
             }
 
-            /* Return DOS version: Minor:Major in AH:AL */
+            /* Return DOS 24-bit user serial number in BL:CX */
+            setBL(0x00);
+            setCX(0x0000);
+
+            /*
+             * Return DOS version: Minor:Major in AH:AL
+             * The Windows NT DOS box returns version 5.00, subject to SETVER.
+             */
             setAX(PspBlock->DosVersion);
 
             break;
         }
 
+        /* Extended functionalities */
+        case 0x33:
+        {
+            if (getAL() == 0x06)
+            {
+                /*
+                 * DOS 5+ - GET TRUE VERSION NUMBER
+                 * This function always returns the true version number, unlike
+                 * AH=30h, whose return value may be changed with SETVER.
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
+                 * for more information.
+                 */
+
+                /*
+                 * Return the true DOS version: Minor:Major in BH:BL
+                 * The Windows NT DOS box returns BX=3205h (version 5.50).
+                 */
+                setBX(NTDOS_VERSION);
+
+                /* DOS revision 0 */
+                setDL(0x00);
+
+                /* Unpatched DOS */
+                setDH(0x00);
+            }
+            // else
+            // {
+                // /* Invalid subfunction */
+                // setAL(0xFF);
+            // }
+
+            break;
+        }
+
         /* Get Interrupt Vector */
         case 0x35:
         {
@@ -1638,6 +1956,61 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
+        /* SWITCH character - AVAILDEV */
+        case 0x37:
+        {
+            if (getAL() == 0x00)
+            {
+                /*
+                 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
+                 * This setting is ignored by MS-DOS 4.0+.
+                 * MS-DOS 5+ always return AL=00h/DL=2Fh.
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
+                 * for more information.
+                 */
+                setDL('/');
+                setAL(0x00);
+            }
+            else if (getAL() == 0x01)
+            {
+                /*
+                 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
+                 * This setting is ignored by MS-DOS 5+.
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
+                 * for more information.
+                 */
+                // getDL();
+                setAL(0xFF);
+            }
+            else if (getAL() == 0x02)
+            {
+                /*
+                 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
+                 * for more information.
+                 */
+                // setDL();
+                setAL(0xFF);
+            }
+            else if (getAL() == 0x03)
+            {
+                /*
+                 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
+                 * for more information.
+                 */
+                // getDL();
+                setAL(0xFF);
+            }
+            else
+            {
+                /* Invalid subfunction */
+                setAL(0xFF);
+            }
+
+            break;
+        }
+
         /* Create Directory */
         case 0x39:
         {
@@ -1752,7 +2125,7 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Read File */
+        /* Read from File or Device */
         case 0x3F:
         {
             WORD Handle    = getBX();
@@ -1760,23 +2133,36 @@ VOID DosInt21h(LPWORD Stack)
             WORD Count     = getCX();
             WORD BytesRead = 0;
             WORD ErrorCode = ERROR_SUCCESS;
+            CHAR Character;
 
             if (IsConsoleHandle(DosGetRealHandle(Handle)))
             {
                 while (Stack[STACK_COUNTER] < Count)
                 {
                     /* Read a character from the BIOS */
-                    Buffer[Stack[STACK_COUNTER]] = LOBYTE(BiosGetCharacter()); // FIXME: Security checks!
+                    Character = LOBYTE(BiosGetCharacter());
 
                     /* Stop if the BOP needs to be repeated */
-                    if (EmulatorGetFlag(EMULATOR_FLAG_CF)) break;
-
-                    /* Increment the counter */
-                    Stack[STACK_COUNTER]++;
+                    if (getCF()) break;
+
+                    // FIXME: Security checks!
+                    DosPrintCharacter(Character);
+                    Buffer[Stack[STACK_COUNTER]++] = Character;
+
+                    if (Character == '\r')
+                    {
+                        /* Stop on first carriage return */
+                        DosPrintCharacter('\n');
+                        break;
+                    }
                 }
 
-                if (Stack[STACK_COUNTER] < Count) ErrorCode = ERROR_NOT_READY;
-                else BytesRead = Count;
+                if (Character != '\r')
+                {
+                    if (Stack[STACK_COUNTER] < Count) ErrorCode = ERROR_NOT_READY;
+                    else BytesRead = Count;
+                }
+                else BytesRead = Stack[STACK_COUNTER];
             }
             else
             {
@@ -1784,7 +2170,7 @@ VOID DosInt21h(LPWORD Stack)
                 ErrorCode = DosReadFile(Handle, Buffer, Count, &BytesRead);
             }
 
-            if (ErrorCode == 0)
+            if (ErrorCode == ERROR_SUCCESS)
             {
                 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
                 setAX(BytesRead);
@@ -1797,7 +2183,7 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Write File */
+        /* Write to File or Device */
         case 0x40:
         {
             WORD BytesWritten = 0;
@@ -1806,7 +2192,7 @@ VOID DosInt21h(LPWORD Stack)
                                           getCX(),
                                           &BytesWritten);
 
-            if (ErrorCode == 0)
+            if (ErrorCode == ERROR_SUCCESS)
             {
                 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
                 setAX(BytesWritten);
@@ -1829,6 +2215,11 @@ VOID DosInt21h(LPWORD Stack)
             if (DeleteFileA(FileName))
             {
                 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+                /*
+                 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
+                 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
+                 */
+                setAL(FileName[0] - 'A');
             }
             else
             {
@@ -1848,7 +2239,7 @@ VOID DosInt21h(LPWORD Stack)
                                          getAL(),
                                          &NewLocation);
 
-            if (ErrorCode == 0)
+            if (ErrorCode == ERROR_SUCCESS)
             {
                 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
 
@@ -1881,12 +2272,13 @@ VOID DosInt21h(LPWORD Stack)
                 {
                     Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
                     setAX(GetLastError());
-                    break;
                 }
-
-                /* Return the attributes that DOS can understand */
-                Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
-                setCL(LOBYTE(Attributes));
+                else
+                {
+                    /* Return the attributes that DOS can understand */
+                    Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+                    setCX(Attributes & 0x00FF);
+                }
             }
             else if (getAL() == 0x01)
             {
@@ -1973,6 +2365,42 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
+        /* Get Current Directory */
+        case 0x47:
+        {
+            BYTE DriveNumber = getDL();
+            String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
+
+            /* Get the real drive number */
+            if (DriveNumber == 0)
+            {
+                DriveNumber = CurrentDrive;
+            }
+            else
+            {
+                /* Decrement DriveNumber since it was 1-based */
+                DriveNumber--;
+            }
+
+            if (DriveNumber <= LastDrive - 'A')
+            {
+                /*
+                 * Copy the current directory into the target buffer.
+                 * It doesn't contain the drive letter and the backslash.
+                 */
+                strncpy(String, CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
+                Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+                setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
+            }
+            else
+            {
+                Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+                setAX(ERROR_INVALID_DRIVE);
+            }
+
+            break;
+        }
+
         /* Allocate Memory */
         case 0x48:
         {
@@ -2036,9 +2464,38 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Get Current Process */
+        /* Get Return Code (ERRORLEVEL) */
+        case 0x4D:
+        {
+            /*
+             * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
+             * DosErrorLevel is cleared after being read by this function.
+             */
+            Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
+            setAX(DosErrorLevel);
+            DosErrorLevel = 0x0000; // Clear it
+            break;
+        }
+
+        /* Internal - Set Current Process ID (Set PSP Address) */
+        case 0x50:
+        {
+            // FIXME: Is it really what it's done ??
+            CurrentPsp = getBX();
+            break;
+        }
+
+        /* Internal - Get Current Process ID (Get PSP Address) */
         case 0x51:
+        /* Get Current PSP Address */
+        case 0x62:
         {
+            /*
+             * Undocumented AH=51h is identical to the documented AH=62h.
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
+             * and http://www.ctyme.com/intr/rb-3140.htm
+             * for more information.
+             */
             setBX(CurrentPsp);
             break;
         }
@@ -2102,19 +2559,48 @@ VOID DosInt21h(LPWORD Stack)
         /* Unsupported */
         default:
         {
-            DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", getAH());
+            DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
+                    getAH(), getAL());
+
+            setAL(0); // Some functions expect AL to be 0 when it's not supported.
             Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
         }
     }
 }
 
-VOID DosBreakInterrupt(LPWORD Stack)
+VOID WINAPI DosBreakInterrupt(LPWORD Stack)
 {
     UNREFERENCED_PARAMETER(Stack);
 
+    /* Stop the VDM */
     VdmRunning = FALSE;
 }
 
+VOID WINAPI DosFastConOut(LPWORD Stack)
+{
+    /*
+     * This is the DOS 2+ Fast Console Output Interrupt.
+     * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
+     * for more information.
+     */
+    UNREFERENCED_PARAMETER(Stack);
+
+    /*
+     * The default handler under DOS 2.x and 3.x simply calls INT 10/AH=0Eh.
+     * Do better and call directly BiosPrintCharacter: it's what INT 10/AH=0Eh
+     * does. Otherwise we would have to set BL to DOS_CHAR_ATTRIBUTE and
+     * BH to Bda->VideoPage.
+     */
+    BiosPrintCharacter(getAL(), DOS_CHAR_ATTRIBUTE, Bda->VideoPage);
+}
+
+VOID WINAPI DosInt2Fh(LPWORD Stack)
+{
+    DPRINT1("DOS System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
+            getAH(), getAL());
+    Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
+}
+
 BOOLEAN DosInitialize(VOID)
 {
     BYTE i;
@@ -2217,7 +2703,7 @@ BOOLEAN DosInitialize(VOID)
     /* Set the drive */
     CurrentDrive = DosDirectory[0] - 'A';
 
-    /* Get the path */
+    /* Get the directory part of the path */
     Path = strchr(DosDirectory, '\\');
     if (Path != NULL)
     {
@@ -2226,7 +2712,10 @@ BOOLEAN DosInitialize(VOID)
     }
 
     /* Set the directory */
-    if (Path != NULL) strcpy(CurrentDirectories[CurrentDrive], Path);
+    if (Path != NULL)
+    {
+        strncpy(CurrentDirectories[CurrentDrive], Path, DOS_DIR_LENGTH);
+    }
 
     /* Read CONFIG.SYS */
     Stream = _wfopen(DOS_CONFIG_PATH, L"r");
@@ -2251,6 +2740,19 @@ BOOLEAN DosInitialize(VOID)
     DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE);
     DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE);
 
+    /* Register the DOS BOPs */
+    RegisterBop(BOP_DOS, DosSystemBop        );
+    RegisterBop(BOP_CMD, DosCmdInterpreterBop);
+
+    /* Register the DOS 32-bit Interrupts */
+    RegisterInt32(0x20, DosInt20h        );
+    RegisterInt32(0x21, DosInt21h        );
+//  RegisterInt32(0x22, DosInt22h        ); // Termination
+    RegisterInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
+//  RegisterInt32(0x24, DosInt24h        ); // Critical Error
+    RegisterInt32(0x29, DosFastConOut    ); // DOS 2+ Fast Console Output
+    RegisterInt32(0x2F, DosInt2Fh        );
+
     return TRUE;
 }