X-Git-Url: https://git.reactos.org/?p=reactos.git;a=blobdiff_plain;f=subsystems%2Fntvdm%2Fdos.c;h=837ae25cfc0d66267dee34948c0dc8c22ff9a101;hp=59f03f08eaa07d8b0ff7f405c8cec6e152bea018;hb=ba466dbba3effc0748f0301cf01b4fd72baabea9;hpb=5db2a4a6dd7ad4db3e4bdf10a24a94b932b23f28 diff --git a/subsystems/ntvdm/dos.c b/subsystems/ntvdm/dos.c index 59f03f08eaa..837ae25cfc0 100644 --- a/subsystems/ntvdm/dos.c +++ b/subsystems/ntvdm/dos.c @@ -6,9 +6,52 @@ * PROGRAMMERS: Aleksandar Andrejevic */ -#include "ntvdm.h" +/* INCLUDES *******************************************************************/ -WORD CurrentPsp = SYSTEM_PSP, LastError = 0; +#define NDEBUG + +#include "dos.h" +#include "bios.h" +#include "emulator.h" + +/* PRIVATE VARIABLES **********************************************************/ + +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; +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) { @@ -38,7 +81,7 @@ static VOID DosCombineFreeBlocks(WORD StartBlock) } } -static WORD DosCopyEnvironmentBlock(WORD SourceSegment) +static WORD DosCopyEnvironmentBlock(WORD SourceSegment, LPCSTR ProgramName) { PCHAR Ptr, SourceBuffer, DestBuffer = NULL; ULONG TotalSize = 0; @@ -54,8 +97,11 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment) } TotalSize++; + /* Add the string buffer size */ + 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; @@ -67,20 +113,165 @@ static WORD DosCopyEnvironmentBlock(WORD SourceSegment) strcpy(DestBuffer, Ptr); /* Advance to the next string */ + DestBuffer += strlen(Ptr); Ptr += strlen(Ptr) + 1; - DestBuffer += strlen(Ptr) + 1; + + /* Put a zero after the string */ + *(DestBuffer++) = 0; } /* Set the final zero */ - *DestBuffer = 0; + *(DestBuffer++) = 0; + + /* Copy the program name after the environment block */ + strcpy(DestBuffer, ProgramName); return DestSegment; } +static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner) +{ + PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1); + + /* Just set the owner */ + Mcb->OwnerPsp = NewOwner; +} + +static WORD DosOpenHandle(HANDLE Handle) +{ + BYTE i; + WORD DosHandle; + PDOS_PSP PspBlock; + LPBYTE HandleTable; + + /* The system PSP has no handle table */ + if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE; + + /* Get a pointer to the handle table */ + PspBlock = SEGMENT_TO_PSP(CurrentPsp); + HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr); + + /* Find a free entry in the JFT */ + for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++) + { + if (HandleTable[DosHandle] == 0xFF) break; + } + + /* If there are no free entries, fail */ + if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE; + + /* Check if the handle is already in the SFT */ + for (i = 0; i < DOS_SFT_SIZE; i++) + { + /* Check if this is the same handle */ + if (DosSystemFileTable[i] != Handle) continue; + + /* Already in the table, reference it */ + DosSftRefCount[i]++; + + /* Set the JFT entry to that SFT index */ + HandleTable[DosHandle] = i; + + /* Return the new handle */ + return DosHandle; + } + + /* Add the handle to the SFT */ + for (i = 0; i < DOS_SFT_SIZE; i++) + { + /* Make sure this is an empty table entry */ + if (DosSystemFileTable[i] != INVALID_HANDLE_VALUE) continue; + + /* Initialize the empty table entry */ + DosSystemFileTable[i] = Handle; + DosSftRefCount[i] = 1; + + /* Set the JFT entry to that SFT index */ + HandleTable[DosHandle] = i; + + /* Return the new handle */ + return DosHandle; + } + + /* The SFT is full */ + return INVALID_DOS_HANDLE; +} + +static HANDLE DosGetRealHandle(WORD DosHandle) +{ + PDOS_PSP PspBlock; + LPBYTE HandleTable; + + /* The system PSP has no handle table */ + if (CurrentPsp == SYSTEM_PSP) return INVALID_HANDLE_VALUE; + + /* Get a pointer to the handle table */ + PspBlock = SEGMENT_TO_PSP(CurrentPsp); + HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr); + + /* Make sure the handle is open */ + if (HandleTable[DosHandle] == 0xFF) return INVALID_HANDLE_VALUE; + + /* Return the Win32 handle */ + return DosSystemFileTable[HandleTable[DosHandle]]; +} + +static VOID DosCopyHandleTable(LPBYTE DestinationTable) +{ + INT i; + PDOS_PSP PspBlock; + LPBYTE SourceTable; + + /* Clear the table first */ + for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF; + + /* Check if this is the initial process */ + if (CurrentPsp == SYSTEM_PSP) + { + /* Set up the standard I/O devices */ + for (i = 0; i <= 2; i++) + { + /* Set the index in the SFT */ + DestinationTable[i] = (BYTE)i; + + /* Increase the reference count */ + DosSftRefCount[i]++; + } + + /* Done */ + return; + } + + /* Get the parent PSP block and handle table */ + PspBlock = SEGMENT_TO_PSP(CurrentPsp); + SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr); + + /* Copy the first 20 handles into the new table */ + for (i = 0; i < 20; i++) + { + DestinationTable[i] = SourceTable[i]; + + /* Increase the reference count */ + DosSftRefCount[SourceTable[i]]++; + } +} + +/* PUBLIC FUNCTIONS ***********************************************************/ + WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable) { WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0; PDOS_MCB CurrentMcb, NextMcb; + BOOLEAN SearchUmb = FALSE; + + DPRINT("DosAllocateMemory: Size 0x%04X\n", Size); + + if (DosUmbLinked && (DosAllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))) + { + /* Search UMB first */ + Segment = UMB_START_SEGMENT; + SearchUmb = TRUE; + } while (TRUE) { @@ -90,6 +281,8 @@ WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable) /* Make sure it's valid */ if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z') { + DPRINT("The DOS memory arena is corrupted!\n"); + DosLastError = ERROR_ARENA_TRASHED; return 0; } @@ -105,23 +298,59 @@ WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable) /* Check if this 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)) + switch (DosAllocStrategy & 0x3F) { - Result = Segment; + case DOS_ALLOC_FIRST_FIT: + { + /* For first fit, stop immediately */ + Result = Segment; + goto Done; + } + + case DOS_ALLOC_BEST_FIT: + { + /* For best fit, update the smallest block found so far */ + if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size)) + { + Result = Segment; + } + + break; + } + + case DOS_ALLOC_LAST_FIT: + { + /* For last fit, make the current block the result, but keep searching */ + Result = Segment; + break; + } } Next: /* If this was the last MCB in the chain, quit */ - if (CurrentMcb->BlockType == 'Z') break; + if (CurrentMcb->BlockType == 'Z') + { + /* Check if nothing was found while searching through UMBs */ + if ((Result == 0) && SearchUmb && (DosAllocStrategy & DOS_ALLOC_HIGH_LOW)) + { + /* Search low memory */ + Segment = FIRST_MCB_SEGMENT; + continue; + } + + break; + } /* Otherwise, update the segment and continue */ Segment += CurrentMcb->Size + 1; } +Done: + /* If we didn't find a free block, return 0 */ if (Result == 0) { + DosLastError = ERROR_NOT_ENOUGH_MEMORY; if (MaxAvailable) *MaxAvailable = MaxSize; return 0; } @@ -148,34 +377,52 @@ Next: /* Take ownership of the block */ CurrentMcb->OwnerPsp = CurrentPsp; - return Result; + /* Return the segment of the data portion of the block */ + return Result + 1; } -WORD DosResizeMemory(WORD Segment, WORD NewSize) +BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable) { - WORD ReturnSize = 0, NextSegment; + BOOLEAN Success = TRUE; + WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment; PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb; + DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n", + BlockData, + NewSize); + /* Make sure this is a valid, allocated block */ if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0) { - return 0; + Success = FALSE; + DosLastError = ERROR_INVALID_HANDLE; + goto Done; } - /* Check if need to expand or contract the block */ + ReturnSize = Mcb->Size; + + /* Check if we need to expand or contract the block */ if (NewSize > Mcb->Size) { /* We can't expand the last block */ - if (Mcb->BlockType != 'M') return Mcb->Size; + if (Mcb->BlockType != 'M') + { + Success = FALSE; + goto Done; + } - ReturnSize = Mcb->Size; - /* Get the pointer and segment of the next MCB */ NextSegment = Segment + Mcb->Size + 1; NextMcb = SEGMENT_TO_MCB(NextSegment); /* Make sure the next segment is free */ - if (NextMcb->OwnerPsp != 0) return Mcb->Size; + if (NextMcb->OwnerPsp != 0) + { + DPRINT("Cannot expand memory block: next segment is not free!\n"); + DosLastError = ERROR_NOT_ENOUGH_MEMORY; + Success = FALSE; + goto Done; + } /* Combine this free block with adjoining free blocks */ DosCombineFreeBlocks(NextSegment); @@ -193,6 +440,10 @@ WORD DosResizeMemory(WORD Segment, WORD NewSize) /* Check if the block is larger than requested */ if (Mcb->Size > NewSize) { + DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n", + Mcb->Size, + NewSize); + /* It is, split it into two blocks */ NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1); @@ -208,6 +459,10 @@ WORD DosResizeMemory(WORD Segment, WORD NewSize) } else if (NewSize < Mcb->Size) { + DPRINT("Shrinking block from 0x%04X to 0x%04X\n", + Mcb->Size, + NewSize); + /* Just split the block */ NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1); NextMcb->BlockType = Mcb->BlockType; @@ -219,15 +474,32 @@ WORD DosResizeMemory(WORD Segment, WORD NewSize) Mcb->Size = NewSize; } - return ReturnSize; +Done: + /* Check if the operation failed */ + if (!Success) + { + DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n", + ReturnSize); + + /* Return the maximum possible size */ + if (MaxAvailable) *MaxAvailable = ReturnSize; + } + + return Success; } -BOOLEAN DosFreeMemory(WORD Segment) +BOOLEAN DosFreeMemory(WORD BlockData) { - PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment); + PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1); + + DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData); /* Make sure the MCB is valid */ - if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') return FALSE; + if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') + { + DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType); + return FALSE; + } /* Mark the block as free */ Mcb->OwnerPsp = 0; @@ -235,21 +507,458 @@ BOOLEAN DosFreeMemory(WORD Segment) return TRUE; } -WORD DosCreateFile(LPCSTR FilePath) +BOOLEAN DosLinkUmb(VOID) { - // TODO: NOT IMPLEMENTED - return 0; + DWORD Segment = FIRST_MCB_SEGMENT; + PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment); + + DPRINT("Linking UMB\n"); + + /* Check if UMBs are already linked */ + if (DosUmbLinked) return FALSE; + + /* Find the last block */ + while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF)) + { + Segment += Mcb->Size + 1; + Mcb = SEGMENT_TO_MCB(Segment); + } + + /* Make sure it's valid */ + if (Mcb->BlockType != 'Z') return FALSE; + + /* Connect the MCB with the UMB chain */ + Mcb->BlockType = 'M'; + + DosUmbLinked = TRUE; + return TRUE; } -WORD DosOpenFile(LPCSTR FilePath) +BOOLEAN DosUnlinkUmb(VOID) { - // TODO: NOT IMPLEMENTED - return 0; + DWORD Segment = FIRST_MCB_SEGMENT; + PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment); + + DPRINT("Unlinking UMB\n"); + + /* Check if UMBs are already unlinked */ + if (!DosUmbLinked) return FALSE; + + /* Find the block preceding the MCB that links it with the UMB chain */ + while (Segment <= 0xFFFF) + { + if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE)) + { + /* This is the last non-UMB segment */ + break; + } + + /* Advance to the next MCB */ + Segment += Mcb->Size + 1; + Mcb = SEGMENT_TO_MCB(Segment); + } + + /* Mark the MCB as the last MCB */ + Mcb->BlockType = 'Z'; + + DosUmbLinked = FALSE; + return TRUE; +} + +WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes) +{ + HANDLE FileHandle; + WORD DosHandle; + + DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n", + FilePath, + Attributes); + + /* Create the file */ + FileHandle = CreateFileA(FilePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + CREATE_ALWAYS, + Attributes, + NULL); + + if (FileHandle == INVALID_HANDLE_VALUE) + { + /* Return the error code */ + return (WORD)GetLastError(); + } + + /* Open the DOS handle */ + DosHandle = DosOpenHandle(FileHandle); + + if (DosHandle == INVALID_DOS_HANDLE) + { + /* Close the handle */ + CloseHandle(FileHandle); + + /* Return the error code */ + return ERROR_TOO_MANY_OPEN_FILES; + } + + /* It was successful */ + *Handle = DosHandle; + return ERROR_SUCCESS; +} + +WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode) +{ + HANDLE FileHandle; + ACCESS_MASK Access = 0; + WORD DosHandle; + + DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n", + FilePath, + AccessMode); + + /* Parse the access mode */ + switch (AccessMode & 3) + { + case 0: + { + /* Read-only */ + Access = GENERIC_READ; + break; + } + + case 1: + { + /* Write only */ + Access = GENERIC_WRITE; + break; + } + + case 2: + { + /* Read and write */ + Access = GENERIC_READ | GENERIC_WRITE; + break; + } + + default: + { + /* Invalid */ + return ERROR_INVALID_PARAMETER; + } + } + + /* Open the file */ + FileHandle = CreateFileA(FilePath, + Access, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + NULL, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (FileHandle == INVALID_HANDLE_VALUE) + { + /* Return the error code */ + return (WORD)GetLastError(); + } + + /* Open the DOS handle */ + DosHandle = DosOpenHandle(FileHandle); + + if (DosHandle == INVALID_DOS_HANDLE) + { + /* Close the handle */ + CloseHandle(FileHandle); + + /* Return the error code */ + return ERROR_TOO_MANY_OPEN_FILES; + } + + /* It was successful */ + *Handle = DosHandle; + return ERROR_SUCCESS; +} + +WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead) +{ + WORD Result = ERROR_SUCCESS; + DWORD BytesRead32 = 0; + HANDLE Handle = DosGetRealHandle(FileHandle); + 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; + + if (IsConsoleHandle(Handle)) + { + for (i = 0; i < Count; i++) + { + /* Call the BIOS function to read the character */ + ((LPBYTE)Buffer)[i] = LOBYTE(BiosGetCharacter()); + BytesRead32++; + } + } + else + { + /* 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 */ + *BytesRead = LOWORD(BytesRead32); + + /* Return the error code */ + return Result; +} + +WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritten) +{ + WORD Result = ERROR_SUCCESS; + DWORD BytesWritten32 = 0; + HANDLE Handle = DosGetRealHandle(FileHandle); + WORD i; + + DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n", + FileHandle, + Count); + + /* Make sure the handle is valid */ + if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE; + + if (IsConsoleHandle(Handle)) + { + 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 */ + *BytesWritten = LOWORD(BytesWritten32); + + /* Return the error code */ + return Result; +} + +WORD DosSeekFile(WORD FileHandle, LONG Offset, BYTE Origin, LPDWORD NewOffset) +{ + WORD Result = ERROR_SUCCESS; + DWORD FilePointer; + HANDLE Handle = DosGetRealHandle(FileHandle); + + DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n", + FileHandle, + Offset, + Origin); + + /* Make sure the handle is valid */ + if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE; + + /* Check if the origin is valid */ + if (Origin != FILE_BEGIN && Origin != FILE_CURRENT && Origin != FILE_END) + { + return ERROR_INVALID_FUNCTION; + } + + /* Move the file pointer */ + FilePointer = SetFilePointer(Handle, Offset, NULL, Origin); + + /* Check if there's a possibility the operation failed */ + if (FilePointer == INVALID_SET_FILE_POINTER) + { + /* Get the real error code */ + Result = (WORD)GetLastError(); + } + + if (Result != ERROR_SUCCESS) + { + /* The operation did fail */ + return Result; + } + + /* Return the file pointer, if requested */ + if (NewOffset) *NewOffset = FilePointer; + + /* Return success */ + return ERROR_SUCCESS; +} + +BOOLEAN DosDuplicateHandle(WORD OldHandle, WORD NewHandle) +{ + BYTE SftIndex; + PDOS_PSP PspBlock; + LPBYTE HandleTable; + + DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n", + OldHandle, + NewHandle); + + /* The system PSP has no handle table */ + if (CurrentPsp == SYSTEM_PSP) return FALSE; + + /* Get a pointer to the handle table */ + PspBlock = SEGMENT_TO_PSP(CurrentPsp); + HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr); + + /* Make sure the old handle is open */ + if (HandleTable[OldHandle] == 0xFF) return FALSE; + + /* Check if the new handle is open */ + if (HandleTable[NewHandle] != 0xFF) + { + /* Close it */ + DosCloseHandle(NewHandle); + } + + /* Increment the reference count of the SFT entry */ + SftIndex = HandleTable[OldHandle]; + DosSftRefCount[SftIndex]++; + + /* Make the new handle point to that SFT entry */ + HandleTable[NewHandle] = SftIndex; + + /* Return success */ + return TRUE; +} + +BOOLEAN DosCloseHandle(WORD DosHandle) +{ + BYTE SftIndex; + PDOS_PSP PspBlock; + LPBYTE HandleTable; + + DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle); + + /* The system PSP has no handle table */ + if (CurrentPsp == SYSTEM_PSP) return FALSE; + + /* Get a pointer to the handle table */ + PspBlock = SEGMENT_TO_PSP(CurrentPsp); + HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr); + + /* Make sure the handle is open */ + if (HandleTable[DosHandle] == 0xFF) return FALSE; + + /* Decrement the reference count of the SFT entry */ + SftIndex = HandleTable[DosHandle]; + DosSftRefCount[SftIndex]--; + + /* Check if the reference count fell to zero */ + if (!DosSftRefCount[SftIndex]) + { + /* Close the file, it's no longer needed */ + CloseHandle(DosSystemFileTable[SftIndex]); + + /* Clear the handle */ + DosSystemFileTable[SftIndex] = INVALID_HANDLE_VALUE; + } + + /* Clear the entry in the JFT */ + HandleTable[DosHandle] = 0xFF; + + 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) { - INT i; PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment); LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress); @@ -259,8 +968,8 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR PspBlock->Exit[0] = 0xCD; // int 0x20 PspBlock->Exit[1] = 0x20; - /* Set the program size */ - PspBlock->MemSize = ProgramSize; + /* Set the number of the last paragraph */ + PspBlock->LastParagraph = PspSegment + ProgramSize - 1; /* Save the interrupt vectors */ PspBlock->TerminateAddress = IntVecTable[0x22]; @@ -270,18 +979,10 @@ VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WOR /* Set the parent PSP */ PspBlock->ParentPsp = CurrentPsp; - /* Initialize the handle table */ - for (i = 0; i < 20; i++) PspBlock->HandleTable[i] = 0xFF; - - /* Did we get an environment segment? */ - if (!Environment) - { - /* No, copy the one from the parent */ - Environment = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP) - ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock - : SYSTEM_ENV_BLOCK); - } + /* Copy the parent handle table */ + DosCopyHandleTable(PspBlock->HandleTable); + /* Set the environment block */ PspBlock->EnvBlock = Environment; /* Set the handle table pointers to the internal handle table */ @@ -297,32 +998,40 @@ 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'; } BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) { - BOOLEAN Success = FALSE; + BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE; HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL; LPBYTE Address = NULL; - LPSTR ProgramFilePath, Parameters[128]; - CHAR CommandLineCopy[128]; + LPSTR ProgramFilePath, Parameters[256]; + CHAR CommandLineCopy[DOS_CMDLINE_LENGTH]; INT ParamCount = 0; - WORD i, Segment, FileSize, ExeSize; + WORD Segment = 0; + WORD MaxAllocSize; + DWORD i, FileSize, ExeSize; PIMAGE_DOS_HEADER Header; PDWORD RelocationTable; PWORD RelocWord; + DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n", + CommandLine, + 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++; @@ -354,6 +1063,19 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0); if (Address == NULL) goto Cleanup; + /* Did we get an environment segment? */ + if (!EnvBlock) + { + /* Set a flag to know if the environment block was allocated here */ + AllocatedEnvBlock = TRUE; + + /* No, copy the one from the parent */ + EnvBlock = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP) + ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock + : SYSTEM_ENV_BLOCK, + ProgramFilePath); + } + /* Check if this is an EXE file or a COM file */ if (Address[0] == 'M' && Address[1] == 'Z') { @@ -362,16 +1084,23 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) /* Get the MZ header */ Header = (PIMAGE_DOS_HEADER)Address; - // TODO: Verify checksum and executable! - /* Get the base size of the file, in paragraphs (rounded up) */ - ExeSize = (((Header->e_cp - 1) << 8) + Header->e_cblp + 0x0F) >> 4; + ExeSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4; - /* Loop from the maximum to the minimum number of extra paragraphs */ - for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--) + /* Add the PSP size, in paragraphs */ + ExeSize += sizeof(DOS_PSP) >> 4; + + /* Add the maximum size that should be allocated */ + ExeSize += Header->e_maxalloc; + + /* Make sure it does not pass 0xFFFF */ + if (ExeSize > 0xFFFF) ExeSize = 0xFFFF; + + /* Reduce the size one by one until the allocation is successful */ + for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--, ExeSize--) { /* Try to allocate that much memory */ - Segment = DosAllocateMemory(ExeSize + (sizeof(DOS_PSP) >> 4) + i, NULL); + Segment = DosAllocateMemory((WORD)ExeSize, NULL); if (Segment != 0) break; } @@ -380,14 +1109,20 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) /* Initialize the PSP */ DosInitializePsp(Segment, - CommandLine, ExeSize + (sizeof(DOS_PSP) >> 4) + i, + CommandLine, + (WORD)ExeSize, EnvBlock); + /* The process owns its own memory */ + DosChangeMemoryOwner(Segment, Segment); + DosChangeMemoryOwner(EnvBlock, Segment); + /* Copy the program to Segment:0100 */ RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress + TO_LINEAR(Segment, 0x100)), Address + (Header->e_cparhdr << 4), - FileSize - (Header->e_cparhdr << 4)); + min(FileSize - (Header->e_cparhdr << 4), + (ExeSize << 4) - sizeof(DOS_PSP))); /* Get the relocation table */ RelocationTable = (PDWORD)(Address + Header->e_lfarlc); @@ -414,7 +1149,9 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) /* Execute */ CurrentPsp = Segment; - EmulatorExecute(Segment + Header->e_cs, sizeof(DOS_PSP) + Header->e_ip); + DiskTransferArea = MAKELONG(0x80, Segment); + EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4), + Header->e_ip); Success = TRUE; } @@ -422,10 +1159,20 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) { /* COM file */ - /* Allocate memory for the whole program and the PSP */ - Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4, NULL); + /* Find the maximum amount of memory that can be allocated */ + DosAllocateMemory(0xFFFF, &MaxAllocSize); + + /* Make sure it's enough for the whole program and the PSP */ + if (((DWORD)MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP))) goto Cleanup; + + /* Allocate all of it */ + Segment = DosAllocateMemory(MaxAllocSize, NULL); if (Segment == 0) goto Cleanup; + /* The process owns its own memory */ + DosChangeMemoryOwner(Segment, Segment); + DosChangeMemoryOwner(EnvBlock, Segment); + /* Copy the program to Segment:0100 */ RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress + TO_LINEAR(Segment, 0x100)), @@ -435,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 */ @@ -447,12 +1194,20 @@ BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock) /* Execute */ CurrentPsp = Segment; + DiskTransferArea = MAKELONG(0x80, Segment); EmulatorExecute(Segment, 0x100); Success = TRUE; } Cleanup: + if (!Success) + { + /* It was not successful, cleanup the DOS memory */ + if (AllocatedEnvBlock) DosFreeMemory(EnvBlock); + if (Segment) DosFreeMemory(Segment); + } + /* Unmap the file*/ if (Address != NULL) UnmapViewOfFile(Address); @@ -467,15 +1222,24 @@ Cleanup: VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode) { + WORD i; WORD McbSegment = FIRST_MCB_SEGMENT; PDOS_MCB CurrentMcb; LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress); PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp); + DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n", + Psp, + ReturnCode); + /* Check if this PSP is it's own parent */ if (PspBlock->ParentPsp == Psp) goto Done; - // TODO: Close all handles opened by the process + for (i = 0; i < PspBlock->HandleTableSize; i++) + { + /* Close the handle */ + DosCloseHandle(i); + } /* Free the memory used by the process */ while (TRUE) @@ -487,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; @@ -509,31 +1273,86 @@ Done: if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE; } - /* Return control to the parent process */ - EmulatorExecute(HIWORD(PspBlock->TerminateAddress), - LOWORD(PspBlock->TerminateAddress)); -} + /* Return control to the parent process */ + EmulatorExecute(HIWORD(PspBlock->TerminateAddress), + LOWORD(PspBlock->TerminateAddress)); +} + +CHAR DosReadCharacter(VOID) +{ + CHAR Character = '\0'; + WORD BytesRead; + + /* Use the file reading function */ + DosReadFile(DOS_INPUT_HANDLE, &Character, sizeof(CHAR), &BytesRead); + + return Character; +} + +VOID DosPrintCharacter(CHAR Character) +{ + WORD BytesWritten; + + /* Use the file writing function */ + DosWriteFile(DOS_OUTPUT_HANDLE, &Character, sizeof(CHAR), &BytesWritten); +} + +BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle) +{ + HANDLE Handle = DosGetRealHandle(FileHandle); + + if (Handle == INVALID_HANDLE_VALUE) + { + /* Doesn't exist */ + DosLastError = ERROR_FILE_NOT_FOUND; + return FALSE; + } + + switch (ControlCode) + { + /* Get Device Information */ + case 0x00: + { + WORD InfoWord = 0; + + if (Handle == DosSystemFileTable[0]) + { + /* Console input */ + InfoWord |= 1 << 0; + } + else if (Handle == DosSystemFileTable[1]) + { + /* Console output */ + InfoWord |= 1 << 1; + } + + /* It is a character device */ + InfoWord |= 1 << 7; + + /* Return the device information word */ + EmulatorSetRegister(EMULATOR_REG_DX, InfoWord); -CHAR DosReadCharacter() -{ - // TODO: STDIN can be redirected under DOS 2.0+ - return _getch(); -} + return TRUE; + } -VOID DosPrintCharacter(CHAR Character) -{ - // TODO: STDOUT can be redirected under DOS 2.0+ - if (Character == '\r') Character = '\n'; - putchar(Character); + /* Unsupported control code */ + default: + { + DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode); + + DosLastError = ERROR_INVALID_PARAMETER; + return FALSE; + } + } } -VOID DosInt20h(WORD CodeSegment) +VOID DosInt20h(LPWORD Stack) { /* This is the exit interrupt */ - DosTerminateProcess(CodeSegment, 0); + DosTerminateProcess(Stack[STACK_CS], 0); } -VOID DosInt21h(WORD CodeSegment) +VOID DosInt21h(LPWORD Stack) { INT i; CHAR Character; @@ -544,8 +1363,8 @@ VOID DosInt21h(WORD CodeSegment) 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)) @@ -553,7 +1372,7 @@ VOID DosInt21h(WORD CodeSegment) /* Terminate Program */ case 0x00: { - DosTerminateProcess(CodeSegment, 0); + DosTerminateProcess(Stack[STACK_CS], 0); break; } @@ -574,6 +1393,7 @@ VOID DosInt21h(WORD CodeSegment) } /* Read Character Without Echo */ + case 0x07: case 0x08: { EmulatorSetRegister(EMULATOR_REG_AX, @@ -616,6 +1436,43 @@ VOID DosInt21h(WORD CodeSegment) 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: + { + DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment); + break; + } + + /* Set Interrupt Vector */ + case 0x25: + { + DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment); + + /* Write the new far pointer to the IDT */ + ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer; + + break; + } + /* Get system date */ case 0x2A: { @@ -691,6 +1548,36 @@ VOID DosInt21h(WORD CodeSegment) break; } + /* Get Disk Transfer Area */ + case 0x2F: + { + EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea)); + EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea)); + + break; + } + + /* Get DOS Version */ + case 0x30: + { + PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp); + + EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion); + break; + } + + /* Get Interrupt Vector */ + case 0x35: + { + DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)]; + + /* Read the address from the IDT into ES:BX */ + EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer)); + EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer)); + + break; + } + /* Create Directory */ case 0x39: { @@ -699,11 +1586,11 @@ VOID DosInt21h(WORD CodeSegment) if (CreateDirectoryA(String, NULL)) { - EmulatorClearFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; } else { - EmulatorSetFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFF0000) | LOWORD(GetLastError())); } @@ -719,11 +1606,11 @@ VOID DosInt21h(WORD CodeSegment) if (RemoveDirectoryA(String)) { - EmulatorClearFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; } else { - EmulatorSetFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFF0000) | LOWORD(GetLastError())); } @@ -738,15 +1625,323 @@ VOID DosInt21h(WORD CodeSegment) String = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DataSegment, LOWORD(Edx))); - if (SetCurrentDirectoryA(String)) + if (DosChangeDirectory(String)) { - EmulatorClearFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; } else { - EmulatorSetFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; EmulatorSetRegister(EMULATOR_REG_AX, - (Eax & 0xFFFF0000) | LOWORD(GetLastError())); + (Eax & 0xFFFF0000) | DosLastError); + } + + break; + } + + /* Create File */ + case 0x3C: + { + WORD FileHandle; + WORD ErrorCode = DosCreateFile(&FileHandle, + (LPCSTR)(ULONG_PTR)BaseAddress + + TO_LINEAR(DataSegment, LOWORD(Edx)), + LOWORD(Ecx)); + + if (ErrorCode == 0) + { + /* Clear CF */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + + /* Return the handle in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | FileHandle); + } + else + { + /* Set CF */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + + /* Return the error code in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | ErrorCode); + } + + break; + } + + /* Open File */ + case 0x3D: + { + WORD FileHandle; + WORD ErrorCode = DosOpenFile(&FileHandle, + (LPCSTR)(ULONG_PTR)BaseAddress + + TO_LINEAR(DataSegment, LOWORD(Edx)), + LOBYTE(Eax)); + + if (ErrorCode == 0) + { + /* Clear CF */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + + /* Return the handle in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | FileHandle); + } + else + { + /* Set CF */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + + /* Return the error code in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | ErrorCode); + } + + break; + } + + /* Close File */ + case 0x3E: + { + if (DosCloseHandle(LOWORD(Ebx))) + { + /* Clear CF */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + /* Set CF */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + + /* Return the error code in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | ERROR_INVALID_HANDLE); + } + + break; + } + + /* Read File */ + case 0x3F: + { + WORD BytesRead = 0; + WORD ErrorCode = DosReadFile(LOWORD(Ebx), + (LPVOID)((ULONG_PTR)BaseAddress + + TO_LINEAR(DataSegment, LOWORD(Edx))), + LOWORD(Ecx), + &BytesRead); + + if (ErrorCode == 0) + { + /* Clear CF */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + + /* Return the number of bytes read in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | BytesRead); + } + else + { + /* Set CF */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + + /* Return the error code in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | ErrorCode); + } + break; + } + + /* Write File */ + case 0x40: + { + WORD BytesWritten = 0; + WORD ErrorCode = DosWriteFile(LOWORD(Ebx), + (LPVOID)((ULONG_PTR)BaseAddress + + TO_LINEAR(DataSegment, LOWORD(Edx))), + LOWORD(Ecx), + &BytesWritten); + + if (ErrorCode == 0) + { + /* Clear CF */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + + /* Return the number of bytes written in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | BytesWritten); + } + else + { + /* Set CF */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + + /* Return the error code in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | ErrorCode); + } + + break; + } + + /* Delete File */ + case 0x41: + { + LPSTR FileName = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(DataSegment, Edx)); + + /* Call the API function */ + if (DeleteFileA(FileName)) Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + else + { + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, GetLastError()); + } + + break; + } + + /* Seek File */ + case 0x42: + { + DWORD NewLocation; + WORD ErrorCode = DosSeekFile(LOWORD(Ebx), + MAKELONG(LOWORD(Edx), LOWORD(Ecx)), + LOBYTE(Eax), + &NewLocation); + + if (ErrorCode == 0) + { + /* Clear CF */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + + /* Return the new offset in DX:AX */ + EmulatorSetRegister(EMULATOR_REG_DX, + (Edx & 0xFFFF0000) | HIWORD(NewLocation)); + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | LOWORD(NewLocation)); + } + else + { + /* Set CF */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + + /* Return the error code in AX */ + EmulatorSetRegister(EMULATOR_REG_AX, + (Eax & 0xFFFF0000) | ErrorCode); + } + + break; + } + + /* Get/Set File Attributes */ + case 0x43: + { + DWORD Attributes; + LPSTR FileName = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(DataSegment, Edx)); + + if (LOBYTE(Eax) == 0x00) + { + /* Get the attributes */ + Attributes = GetFileAttributesA(FileName); + + /* Check if it failed */ + if (Attributes == INVALID_FILE_ATTRIBUTES) + { + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, GetLastError()); + + break; + } + + /* Return the attributes that DOS can understand */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_CX, + (Ecx & 0xFFFFFF00) | LOBYTE(Attributes)); + } + else if (LOBYTE(Eax) == 0x01) + { + /* Try to set the attributes */ + if (SetFileAttributesA(FileName, LOBYTE(Ecx))) + { + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, GetLastError()); + } + } + else + { + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_FUNCTION); + } + + break; + } + + /* IOCTL */ + case 0x44: + { + if (DosHandleIoctl(LOBYTE(Eax), LOWORD(Ebx))) + { + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, DosLastError); + } + + break; + } + + /* Duplicate Handle */ + case 0x45: + { + WORD NewHandle; + HANDLE Handle = DosGetRealHandle(LOWORD(Ebx)); + + if (Handle != INVALID_HANDLE_VALUE) + { + /* The handle is invalid */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_HANDLE); + + break; + } + + /* Open a new handle to the same entry */ + NewHandle = DosOpenHandle(Handle); + + if (NewHandle == INVALID_DOS_HANDLE) + { + /* Too many files open */ + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_TOO_MANY_OPEN_FILES); + + break; + } + + /* Return the result */ + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, NewHandle); + + break; + } + + /* Force Duplicate Handle */ + case 0x46: + { + if (DosDuplicateHandle(LOWORD(Ebx), LOWORD(Ecx))) + { + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_HANDLE); } break; @@ -761,10 +1956,14 @@ VOID DosInt21h(WORD CodeSegment) if (Segment != 0) { EmulatorSetRegister(EMULATOR_REG_AX, Segment); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + EmulatorSetRegister(EMULATOR_REG_AX, DosLastError); EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable); - EmulatorClearFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; } - else EmulatorSetFlag(EMULATOR_FLAG_CF); break; } @@ -774,9 +1973,13 @@ VOID DosInt21h(WORD CodeSegment) { if (DosFreeMemory(ExtSegment)) { - EmulatorClearFlag(EMULATOR_FLAG_CF); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_ARENA_TRASHED); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; } - else EmulatorSetFlag(EMULATOR_FLAG_CF); break; } @@ -784,14 +1987,18 @@ VOID DosInt21h(WORD CodeSegment) /* Resize Memory Block */ case 0x4A: { - WORD Size = DosResizeMemory(ExtSegment, LOWORD(Ebx)); + WORD Size; - if (Size != 0) + if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size)) + { + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else { + EmulatorSetRegister(EMULATOR_REG_AX, DosLastError); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; EmulatorSetRegister(EMULATOR_REG_BX, Size); - EmulatorClearFlag(EMULATOR_FLAG_CF); } - else EmulatorSetFlag(EMULATOR_FLAG_CF); break; } @@ -803,21 +2010,95 @@ VOID DosInt21h(WORD CodeSegment) break; } + /* Get Current Process */ + case 0x51: + { + EmulatorSetRegister(EMULATOR_REG_BX, CurrentPsp); + + break; + } + + /* Get/Set Memory Management Options */ + case 0x58: + { + if (LOBYTE(Eax) == 0x00) + { + /* Get allocation strategy */ + + EmulatorSetRegister(EMULATOR_REG_AX, DosAllocStrategy); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else if (LOBYTE(Eax) == 0x01) + { + /* Set allocation strategy */ + + if ((LOBYTE(Ebx) & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)) + == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)) + { + /* Can't set both */ + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + break; + } + + if ((LOBYTE(Ebx) & 0x3F) > DOS_ALLOC_LAST_FIT) + { + /* Invalid allocation strategy */ + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + break; + } + + DosAllocStrategy = LOBYTE(Ebx); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else if (LOBYTE(Eax) == 0x02) + { + /* Get UMB link state */ + + Eax &= 0xFFFFFF00; + if (DosUmbLinked) Eax |= 1; + EmulatorSetRegister(EMULATOR_REG_AX, Eax); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else if (LOBYTE(Eax) == 0x03) + { + /* Set UMB link state */ + + if (Ebx) DosLinkUmb(); + else DosUnlinkUmb(); + Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF; + } + else + { + /* Invalid or unsupported function */ + + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; + EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_FUNCTION); + } + + break; + } + /* Unsupported */ default: { - EmulatorSetFlag(EMULATOR_FLAG_CF); + DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax)); + Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF; } } } -VOID DosBreakInterrupt() +VOID DosBreakInterrupt(LPWORD Stack) { + UNREFERENCED_PARAMETER(Stack); + VdmRunning = FALSE; } -BOOLEAN DosInitialize() +BOOLEAN DosInitialize(VOID) { + BYTE i; PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT); FILE *Stream; WCHAR Buffer[256]; @@ -825,10 +2106,25 @@ BOOLEAN DosInitialize() 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'; - Mcb->Size = (WORD)USER_MEMORY_SIZE; + Mcb->Size = USER_MEMORY_SIZE; + Mcb->OwnerPsp = 0; + + /* Initialize the link MCB to the UMB area */ + Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT + USER_MEMORY_SIZE + 1); + Mcb->BlockType = 'M'; + Mcb->Size = UMB_START_SEGMENT - FIRST_MCB_SEGMENT - USER_MEMORY_SIZE - 2; + Mcb->OwnerPsp = SYSTEM_PSP; + + /* Initialize the UMB area */ + Mcb = SEGMENT_TO_MCB(UMB_START_SEGMENT); + Mcb->BlockType = 'Z'; + Mcb->Size = UMB_END_SEGMENT - UMB_START_SEGMENT; Mcb->OwnerPsp = 0; /* Get the environment strings */ @@ -869,17 +2165,50 @@ BOOLEAN DosInitialize() /* 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; + DestPtr += strlen(AsciiString); + *(DestPtr++) = 0; + + /* Free the memory */ + HeapFree(GetProcessHeap(), 0, AsciiString); } + *DestPtr = 0; /* 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) @@ -891,6 +2220,18 @@ BOOLEAN DosInitialize() fclose(Stream); } + /* Initialize the SFT */ + for (i = 0; i < DOS_SFT_SIZE; i++) + { + DosSystemFileTable[i] = INVALID_HANDLE_VALUE; + DosSftRefCount[i] = 0; + } + + /* Get handles to standard I/O devices */ + DosSystemFileTable[0] = GetStdHandle(STD_INPUT_HANDLE); + DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE); + DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE); + return TRUE; }