[NTVDM]: DOS: Add some description / fix a comment.
[reactos.git] / subsystems / ntvdm / dos.c
index bd531ca..f094fd4 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 **********************************************************/
@@ -30,6 +32,10 @@ static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
 static BOOLEAN DosUmbLinked = FALSE;
 static WORD DosErrorLevel = 0x0000;
 
+/* BOP Identifiers */
+#define BOP_DOS 0x50    // DOS System BOP (for NTIO.SYS and NTDOS.SYS)
+#define BOP_CMD 0x54    // DOS Command Interpreter BOP (for COMMAND.COM)
+
 /* PRIVATE FUNCTIONS **********************************************************/
 
 /* Taken from base/shell/cmd/console.c */
@@ -1024,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;
@@ -1039,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");
 
@@ -1051,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,
@@ -1123,7 +1137,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
 
         /* Initialize the PSP */
         DosInitializePsp(Segment,
-                         CommandLine,
+                         ParamString,
                          (WORD)ExeSize,
                          EnvBlock);
 
@@ -1192,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 */
@@ -1203,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);
@@ -1357,6 +1377,11 @@ BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
         {
             WORD InfoWord = 0;
 
+            /*
+             * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
+             * for a list of possible flags.
+             */
+
             if (Handle == DosSystemFileTable[0])
             {
                 /* Console input */
@@ -1368,7 +1393,7 @@ BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
                 InfoWord |= 1 << 1;
             }
 
-            /* It is a character device */
+            /* It is a device */
             InfoWord |= 1 << 7;
 
             /* Return the device information word */
@@ -1387,13 +1412,101 @@ 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)
 {
     BYTE Character;
     SYSTEMTIME SystemTime;
@@ -1765,6 +1878,7 @@ VOID DosInt21h(LPWORD Stack)
             PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
 
             /*
+             * DOS 2+ - GET DOS VERSION
              * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
              * for more information.
              */
@@ -1793,12 +1907,49 @@ VOID DosInt21h(LPWORD Stack)
             setBL(0x00);
             setCX(0x0000);
 
-            /* Return DOS version: Minor:Major in AH:AL */
+            /*
+             * 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:
         {
@@ -1858,10 +2009,11 @@ VOID DosInt21h(LPWORD Stack)
             }
             else
             {
+                /* Invalid subfunction */
                 setAL(0xFF);
             }
 
-             break;
+            break;
         }
 
         /* Create Directory */
@@ -1986,26 +2138,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 */
-                    // FIXME: Security checks!
-                    Buffer[Stack[STACK_COUNTER]] = LOBYTE(BiosGetCharacter());
+                    Character = LOBYTE(BiosGetCharacter());
 
                     /* Stop if the BOP needs to be repeated */
                     if (getCF()) break;
 
-                    /* Increment the counter */
-                    Stack[STACK_COUNTER]++;
+                    // 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
             {
@@ -2330,10 +2492,13 @@ VOID DosInt21h(LPWORD Stack)
 
         /* Internal - Get Current Process ID (Get PSP Address) */
         case 0x51:
+        /* Get Current PSP Address */
+        case 0x62:
         {
             /*
-             * Identical to the documented AH=62h.
+             * 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);
@@ -2396,41 +2561,51 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
-        /* Get Current PSP Address */
-        case 0x62:
-        {
-            /*
-             * Identical to the undocumented AH=51h.
-             * See Ralf Brown: http://www.ctyme.com/intr/rb-3140.htm
-             * for more information.
-             */
-            setAH(0x51); // Call the internal function.
-            /*
-             * Instead of calling ourselves really recursively as in:
-             * DosInt21h(Stack);
-             * prefer resetting the CF flag to let the BOP repeat.
-             */
-            setCF(1);
-            break;
-        }
-
         /* Unsupported */
         default:
         {
             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;
@@ -2570,6 +2745,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;
 }