[NTVDM]
[reactos.git] / subsystems / ntvdm / dos.c
index c3f4afd..837ae25 100644 (file)
@@ -19,6 +19,9 @@
 static WORD CurrentPsp = SYSTEM_PSP;
 static WORD DosLastError = 0;
 static DWORD DiskTransferArea;
+static BYTE CurrentDrive;
+static CHAR LastDrive = 'E';
+static CHAR CurrentDirectories[NUM_DRIVES][DOS_DIR_LENGTH];
 static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
 static WORD DosSftRefCount[DOS_SFT_SIZE];
 static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
@@ -26,6 +29,30 @@ static BOOLEAN DosUmbLinked = FALSE;
 
 /* PRIVATE FUNCTIONS **********************************************************/
 
+/* Taken from base/shell/cmd/console.c */
+static BOOL IsConsoleHandle(HANDLE hHandle)
+{
+    DWORD dwMode;
+
+    /* Check whether the handle may be that of a console... */
+    if ((GetFileType(hHandle) & FILE_TYPE_CHAR) == 0) return FALSE;
+
+    /*
+     * It may be. Perform another test... The idea comes from the
+     * MSDN description of the WriteConsole API:
+     *
+     * "WriteConsole fails if it is used with a standard handle
+     *  that is redirected to a file. If an application processes
+     *  multilingual output that can be redirected, determine whether
+     *  the output handle is a console handle (one method is to call
+     *  the GetConsoleMode function and check whether it succeeds).
+     *  If the handle is a console handle, call WriteConsole. If the
+     *  handle is not a console handle, the output is redirected and
+     *  you should call WriteFile to perform the I/O."
+     */
+    return GetConsoleMode(hHandle, &dwMode);
+}
+
 static VOID DosCombineFreeBlocks(WORD StartBlock)
 {
     PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
@@ -74,7 +101,7 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment, LPCSTR ProgramName)
     TotalSize += strlen(ProgramName) + 1;
 
     /* Allocate the memory for the environment block */
-    DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
+    DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
     if (!DestSegment) return 0;
 
     Ptr = SourceBuffer;
@@ -205,7 +232,7 @@ static VOID DosCopyHandleTable(LPBYTE DestinationTable)
         for (i = 0; i <= 2; i++)
         {
             /* Set the index in the SFT */
-            DestinationTable[i] = i;
+            DestinationTable[i] = (BYTE)i;
 
             /* Increase the reference count */
             DosSftRefCount[i]++;
@@ -559,7 +586,7 @@ WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes)
     if (FileHandle == INVALID_HANDLE_VALUE)
     {
         /* Return the error code */
-        return GetLastError();
+        return (WORD)GetLastError();
     }
 
     /* Open the DOS handle */
@@ -632,7 +659,7 @@ WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode)
     if (FileHandle == INVALID_HANDLE_VALUE)
     {
         /* Return the error code */
-        return GetLastError();
+        return (WORD)GetLastError();
     }
 
     /* Open the DOS handle */
@@ -657,17 +684,30 @@ WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead)
     WORD Result = ERROR_SUCCESS;
     DWORD BytesRead32 = 0;
     HANDLE Handle = DosGetRealHandle(FileHandle);
+    WORD i;
 
     DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle, Count);
 
     /* Make sure the handle is valid */
     if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
 
-    /* Read the file */
-    if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
+    if (IsConsoleHandle(Handle))
+    {
+        for (i = 0; i < Count; i++)
+        {
+            /* Call the BIOS function to read the character */
+            ((LPBYTE)Buffer)[i] = LOBYTE(BiosGetCharacter());
+            BytesRead32++;
+        }
+    }
+    else
     {
-        /* Store the error code */
-        Result = GetLastError();
+        /* Read the file */
+        if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
+        {
+            /* Store the error code */
+            Result = (WORD)GetLastError();
+        }
     }
 
     /* The number of bytes read is always 16-bit */
@@ -682,6 +722,7 @@ WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritte
     WORD Result = ERROR_SUCCESS;
     DWORD BytesWritten32 = 0;
     HANDLE Handle = DosGetRealHandle(FileHandle);
+    WORD i;
 
     DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
            FileHandle,
@@ -690,11 +731,23 @@ WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritte
     /* Make sure the handle is valid */
     if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
 
-    /* Write the file */
-    if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
+    if (IsConsoleHandle(Handle))
     {
-        /* Store the error code */
-        Result = GetLastError();
+        for (i = 0; i < Count; i++)
+        {
+            /* Call the BIOS to print the character */
+            BiosPrintCharacter(((LPBYTE)Buffer)[i], DOS_CHAR_ATTRIBUTE, Bda->VideoPage);
+            BytesWritten32++;
+        }
+    }
+    else
+    {
+        /* Write the file */
+        if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
+        {
+            /* Store the error code */
+            Result = (WORD)GetLastError();
+        }
     }
 
     /* The number of bytes written is always 16-bit */
@@ -731,7 +784,7 @@ WORD DosSeekFile(WORD FileHandle, LONG Offset, BYTE Origin, LPDWORD NewOffset)
     if (FilePointer == INVALID_SET_FILE_POINTER)
     {
         /* Get the real error code */
-        Result = GetLastError();
+        Result = (WORD)GetLastError();
     }
 
     if (Result != ERROR_SUCCESS)
@@ -823,6 +876,87 @@ BOOLEAN DosCloseHandle(WORD DosHandle)
     return TRUE;
 }
 
+BOOLEAN DosChangeDrive(BYTE Drive)
+{
+    WCHAR DirectoryPath[DOS_CMDLINE_LENGTH];
+
+    /* Make sure the drive exists */
+    if (Drive > (LastDrive - 'A')) return FALSE;
+
+    /* Find the path to the new current directory */
+    swprintf(DirectoryPath, L"%c\\%S", Drive + 'A', CurrentDirectories[Drive]);
+
+    /* Change the current directory of the process */
+    if (!SetCurrentDirectory(DirectoryPath)) return FALSE;
+
+    /* Set the current drive */
+    CurrentDrive = Drive;
+
+    /* Return success */
+    return TRUE;
+}
+
+BOOLEAN DosChangeDirectory(LPSTR Directory)
+{
+    BYTE DriveNumber;
+    DWORD Attributes;
+    LPSTR Path;
+
+    /* Make sure the directory path is not too long */
+    if (strlen(Directory) >= DOS_DIR_LENGTH)
+    {
+        DosLastError = ERROR_PATH_NOT_FOUND;
+        return FALSE;
+    }
+
+    /* Get the drive number */
+    DriveNumber = Directory[0] - 'A';
+
+    /* Make sure the drive exists */
+    if (DriveNumber > (LastDrive - 'A'))
+    {
+        DosLastError = ERROR_PATH_NOT_FOUND;
+        return FALSE;
+    }
+
+    /* Get the file attributes */
+    Attributes = GetFileAttributesA(Directory);
+
+    /* Make sure the path exists and is a directory */
+    if ((Attributes == INVALID_FILE_ATTRIBUTES)
+        || !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
+    {
+        DosLastError = ERROR_PATH_NOT_FOUND;
+        return FALSE;
+    }
+
+    /* Check if this is the current drive */
+    if (DriveNumber == CurrentDrive)
+    {
+        /* Change the directory */
+        if (!SetCurrentDirectoryA(Directory))
+        {
+            DosLastError = LOWORD(GetLastError());
+            return FALSE;
+        }
+    }
+
+    /* Get the directory part of the path */
+    Path = strchr(Directory, '\\');
+    if (Path != NULL)
+    {
+        /* Skip the backslash */
+        Path++;
+    }
+
+    /* Set the directory for the drive */
+    if (Path != NULL) strcpy(CurrentDirectories[DriveNumber], Path);
+    else strcpy(CurrentDirectories[DriveNumber], "");
+    
+    /* Return success */
+    return TRUE;
+}
+
 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
 {
     PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
@@ -864,7 +998,7 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR
     PspBlock->FarCall[2] = 0xCB; // retf
 
     /* Set the command line */
-    PspBlock->CommandLineSize = strlen(CommandLine);
+    PspBlock->CommandLineSize = (BYTE)min(strlen(CommandLine), DOS_CMDLINE_LENGTH - 1);
     RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
     PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
 }
@@ -874,10 +1008,10 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
     BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
     HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
     LPBYTE Address = NULL;
-    LPSTR ProgramFilePath, Parameters[128];
-    CHAR CommandLineCopy[128];
+    LPSTR ProgramFilePath, Parameters[256];
+    CHAR CommandLineCopy[DOS_CMDLINE_LENGTH];
     INT ParamCount = 0;
-    DWORD Segment = 0;
+    WORD Segment = 0;
     WORD MaxAllocSize;
     DWORD i, FileSize, ExeSize;
     PIMAGE_DOS_HEADER Header;
@@ -891,11 +1025,13 @@ 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");
 
     /* Load the parameters in the local array */
-    while ((ParamCount < 256)
+    while ((ParamCount < sizeof(Parameters)/sizeof(Parameters[0]))
            && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
     {
         ParamCount++;
@@ -964,7 +1100,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
         for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--, ExeSize--)
         {
             /* Try to allocate that much memory */
-            Segment = DosAllocateMemory(ExeSize, NULL);
+            Segment = DosAllocateMemory((WORD)ExeSize, NULL);
             if (Segment != 0) break;
         }
 
@@ -974,7 +1110,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
         /* Initialize the PSP */
         DosInitializePsp(Segment,
                          CommandLine,
-                         ExeSize,
+                         (WORD)ExeSize,
                          EnvBlock);
 
         /* The process owns its own memory */
@@ -1027,7 +1163,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
         DosAllocateMemory(0xFFFF, &MaxAllocSize);
 
         /* Make sure it's enough for the whole program and the PSP */
-        if ((MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP))) goto Cleanup;
+        if (((DWORD)MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP))) goto Cleanup;
 
         /* Allocate all of it */
         Segment = DosAllocateMemory(MaxAllocSize, NULL);
@@ -1046,7 +1182,7 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
         /* Initialize the PSP */
         DosInitializePsp(Segment,
                          CommandLine,
-                         (FileSize + sizeof(DOS_PSP)) >> 4,
+                         (WORD)((FileSize + sizeof(DOS_PSP)) >> 4),
                          EnvBlock);
 
         /* Set the initial segment registers */
@@ -1115,7 +1251,7 @@ VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
         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 (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment + 1);
 
         /* If this was the last block, quit */
         if (CurrentMcb->BlockType == 'Z') break;
@@ -1227,8 +1363,8 @@ VOID DosInt21h(LPWORD Stack)
     DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
     DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
     DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
-    WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
-    WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
+    WORD DataSegment = (WORD)EmulatorGetRegister(EMULATOR_REG_DS);
+    WORD ExtSegment = (WORD)EmulatorGetRegister(EMULATOR_REG_ES);
 
     /* Check the value in the AH register */
     switch (HIBYTE(Eax))
@@ -1300,6 +1436,25 @@ VOID DosInt21h(LPWORD Stack)
             break;
         }
 
+        /* Set Default Drive  */
+        case 0x0E:
+        {
+            DosChangeDrive(LOBYTE(Edx));
+            EmulatorSetRegister(EMULATOR_REG_AX,
+                                (Eax & 0xFFFFFF00) | (LastDrive - 'A' + 1));
+
+            break;
+        }
+
+        /* Get Default Drive */
+        case 0x19:
+        {
+            EmulatorSetRegister(EMULATOR_REG_AX,
+                                (Eax & 0xFFFFFF00) | CurrentDrive);
+
+            break;
+        }
+
         /* Set Disk Transfer Area */
         case 0x1A:
         {
@@ -1470,7 +1625,7 @@ VOID DosInt21h(LPWORD Stack)
             String = (PCHAR)((ULONG_PTR)BaseAddress
                      + TO_LINEAR(DataSegment, LOWORD(Edx)));
 
-            if (SetCurrentDirectoryA(String))
+            if (DosChangeDirectory(String))
             {
                 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
             }
@@ -1478,7 +1633,7 @@ VOID DosInt21h(LPWORD Stack)
             {
                 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
                 EmulatorSetRegister(EMULATOR_REG_AX,
-                                    (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
+                                    (Eax & 0xFFFF0000) | DosLastError);
             }
 
             break;
@@ -1519,10 +1674,10 @@ VOID DosInt21h(LPWORD Stack)
         case 0x3D:
         {
             WORD FileHandle;
-            WORD ErrorCode = DosCreateFile(&FileHandle,
-                                           (LPCSTR)(ULONG_PTR)BaseAddress
-                                           + TO_LINEAR(DataSegment, LOWORD(Edx)),
-                                           LOBYTE(Eax));
+            WORD ErrorCode = DosOpenFile(&FileHandle,
+                                         (LPCSTR)(ULONG_PTR)BaseAddress
+                                         + TO_LINEAR(DataSegment, LOWORD(Edx)),
+                                         LOBYTE(Eax));
 
             if (ErrorCode == 0)
             {
@@ -1936,6 +2091,8 @@ VOID DosInt21h(LPWORD Stack)
 
 VOID DosBreakInterrupt(LPWORD Stack)
 {
+    UNREFERENCED_PARAMETER(Stack);
+
     VdmRunning = FALSE;
 }
 
@@ -1949,6 +2106,9 @@ BOOLEAN DosInitialize(VOID)
     LPSTR AsciiString;
     LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
     DWORD AsciiSize;
+    CHAR CurrentDirectory[MAX_PATH];
+    CHAR DosDirectory[DOS_DIR_LENGTH];
+    LPSTR Path;
 
     /* Initialize the MCB */
     Mcb->BlockType = 'Z';
@@ -2018,6 +2178,37 @@ BOOLEAN DosInitialize(VOID)
     /* Free the memory allocated for environment strings */
     FreeEnvironmentStringsW(Environment);
 
+    /* Clear the current directory buffer */
+    ZeroMemory(CurrentDirectories, sizeof(CurrentDirectories));
+
+    /* Get the current directory */
+    if (!GetCurrentDirectoryA(MAX_PATH, CurrentDirectory))
+    {
+        // TODO: Use some kind of default path?
+        return FALSE;
+    }
+
+    /* Convert that to a DOS path */
+    if (!GetShortPathNameA(CurrentDirectory, DosDirectory, DOS_DIR_LENGTH))
+    {
+        // TODO: Use some kind of default path?
+        return FALSE;
+    }
+
+    /* Set the drive */
+    CurrentDrive = DosDirectory[0] - 'A';
+
+    /* Get the path */
+    Path = strchr(DosDirectory, '\\');
+    if (Path != NULL)
+    {
+        /* Skip the backslash */
+        Path++;
+    }
+
+    /* Set the directory */
+    if (Path != NULL) strcpy(CurrentDirectories[CurrentDrive], Path);
+
     /* Read CONFIG.SYS */
     Stream = _wfopen(DOS_CONFIG_PATH, L"r");
     if (Stream != NULL)