2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
5 * PURPOSE: VDM DOS Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
9 /* INCLUDES *******************************************************************/
17 #include "bios/bios.h"
19 #include "registers.h"
21 /* PRIVATE VARIABLES **********************************************************/
23 static WORD CurrentPsp
= SYSTEM_PSP
;
24 static WORD DosLastError
= 0;
25 static DWORD DiskTransferArea
;
26 /*static*/ BYTE CurrentDrive
;
27 static CHAR LastDrive
= 'E';
28 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
29 static HANDLE DosSystemFileTable
[DOS_SFT_SIZE
];
30 static WORD DosSftRefCount
[DOS_SFT_SIZE
];
31 static BYTE DosAllocStrategy
= DOS_ALLOC_BEST_FIT
;
32 static BOOLEAN DosUmbLinked
= FALSE
;
33 static WORD DosErrorLevel
= 0x0000;
35 /* PRIVATE FUNCTIONS **********************************************************/
38 * Memory management functions
40 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
42 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
44 /* If this is the last block or it's not free, quit */
45 if (CurrentMcb
->BlockType
== 'Z' || CurrentMcb
->OwnerPsp
!= 0) return;
49 /* Get a pointer to the next MCB */
50 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
52 /* Check if the next MCB is free */
53 if (NextMcb
->OwnerPsp
== 0)
56 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
57 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
58 NextMcb
->BlockType
= 'I';
62 /* No more adjoining free blocks */
68 static WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
70 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
, MaxSize
= 0;
71 PDOS_MCB CurrentMcb
, NextMcb
;
72 BOOLEAN SearchUmb
= FALSE
;
74 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
76 if (DosUmbLinked
&& (DosAllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
78 /* Search UMB first */
79 Segment
= UMB_START_SEGMENT
;
85 /* Get a pointer to the MCB */
86 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
88 /* Make sure it's valid */
89 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
91 DPRINT("The DOS memory arena is corrupted!\n");
92 DosLastError
= ERROR_ARENA_TRASHED
;
96 /* Only check free blocks */
97 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
99 /* Combine this free block with adjoining free blocks */
100 DosCombineFreeBlocks(Segment
);
102 /* Update the maximum block size */
103 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
105 /* Check if this block is big enough */
106 if (CurrentMcb
->Size
< Size
) goto Next
;
108 switch (DosAllocStrategy
& 0x3F)
110 case DOS_ALLOC_FIRST_FIT
:
112 /* For first fit, stop immediately */
117 case DOS_ALLOC_BEST_FIT
:
119 /* For best fit, update the smallest block found so far */
120 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
128 case DOS_ALLOC_LAST_FIT
:
130 /* For last fit, make the current block the result, but keep searching */
137 /* If this was the last MCB in the chain, quit */
138 if (CurrentMcb
->BlockType
== 'Z')
140 /* Check if nothing was found while searching through UMBs */
141 if ((Result
== 0) && SearchUmb
&& (DosAllocStrategy
& DOS_ALLOC_HIGH_LOW
))
143 /* Search low memory */
144 Segment
= FIRST_MCB_SEGMENT
;
151 /* Otherwise, update the segment and continue */
152 Segment
+= CurrentMcb
->Size
+ 1;
157 /* If we didn't find a free block, return 0 */
160 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
161 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
165 /* Get a pointer to the MCB */
166 CurrentMcb
= SEGMENT_TO_MCB(Result
);
168 /* Check if the block is larger than requested */
169 if (CurrentMcb
->Size
> Size
)
171 /* It is, split it into two blocks */
172 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
174 /* Initialize the new MCB structure */
175 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
176 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
177 NextMcb
->OwnerPsp
= 0;
179 /* Update the current block */
180 CurrentMcb
->BlockType
= 'M';
181 CurrentMcb
->Size
= Size
;
184 /* Take ownership of the block */
185 CurrentMcb
->OwnerPsp
= CurrentPsp
;
187 /* Return the segment of the data portion of the block */
191 static BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
193 BOOLEAN Success
= TRUE
;
194 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
195 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
197 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
201 /* Make sure this is a valid, allocated block */
202 if ((Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') || Mcb
->OwnerPsp
== 0)
205 DosLastError
= ERROR_INVALID_HANDLE
;
209 ReturnSize
= Mcb
->Size
;
211 /* Check if we need to expand or contract the block */
212 if (NewSize
> Mcb
->Size
)
214 /* We can't expand the last block */
215 if (Mcb
->BlockType
!= 'M')
221 /* Get the pointer and segment of the next MCB */
222 NextSegment
= Segment
+ Mcb
->Size
+ 1;
223 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
225 /* Make sure the next segment is free */
226 if (NextMcb
->OwnerPsp
!= 0)
228 DPRINT("Cannot expand memory block: next segment is not free!\n");
229 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
234 /* Combine this free block with adjoining free blocks */
235 DosCombineFreeBlocks(NextSegment
);
237 /* Set the maximum possible size of the block */
238 ReturnSize
+= NextMcb
->Size
+ 1;
240 /* Maximize the current block */
241 Mcb
->Size
= ReturnSize
;
242 Mcb
->BlockType
= NextMcb
->BlockType
;
244 /* Invalidate the next block */
245 NextMcb
->BlockType
= 'I';
247 /* Check if the block is larger than requested */
248 if (Mcb
->Size
> NewSize
)
250 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
254 /* It is, split it into two blocks */
255 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
257 /* Initialize the new MCB structure */
258 NextMcb
->BlockType
= Mcb
->BlockType
;
259 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
260 NextMcb
->OwnerPsp
= 0;
262 /* Update the current block */
263 Mcb
->BlockType
= 'M';
267 else if (NewSize
< Mcb
->Size
)
269 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
273 /* Just split the block */
274 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
275 NextMcb
->BlockType
= Mcb
->BlockType
;
276 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
277 NextMcb
->OwnerPsp
= 0;
280 Mcb
->BlockType
= 'M';
285 /* Check if the operation failed */
288 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
291 /* Return the maximum possible size */
292 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
298 static BOOLEAN
DosFreeMemory(WORD BlockData
)
300 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
302 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
304 /* Make sure the MCB is valid */
305 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z')
307 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
311 /* Mark the block as free */
317 static BOOLEAN
DosLinkUmb(VOID
)
319 DWORD Segment
= FIRST_MCB_SEGMENT
;
320 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
322 DPRINT("Linking UMB\n");
324 /* Check if UMBs are already linked */
325 if (DosUmbLinked
) return FALSE
;
327 /* Find the last block */
328 while ((Mcb
->BlockType
== 'M') && (Segment
<= 0xFFFF))
330 Segment
+= Mcb
->Size
+ 1;
331 Mcb
= SEGMENT_TO_MCB(Segment
);
334 /* Make sure it's valid */
335 if (Mcb
->BlockType
!= 'Z') return FALSE
;
337 /* Connect the MCB with the UMB chain */
338 Mcb
->BlockType
= 'M';
344 static BOOLEAN
DosUnlinkUmb(VOID
)
346 DWORD Segment
= FIRST_MCB_SEGMENT
;
347 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
349 DPRINT("Unlinking UMB\n");
351 /* Check if UMBs are already unlinked */
352 if (!DosUmbLinked
) return FALSE
;
354 /* Find the block preceding the MCB that links it with the UMB chain */
355 while (Segment
<= 0xFFFF)
357 if ((Segment
+ Mcb
->Size
) == (FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
))
359 /* This is the last non-UMB segment */
363 /* Advance to the next MCB */
364 Segment
+= Mcb
->Size
+ 1;
365 Mcb
= SEGMENT_TO_MCB(Segment
);
368 /* Mark the MCB as the last MCB */
369 Mcb
->BlockType
= 'Z';
371 DosUmbLinked
= FALSE
;
375 static VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
377 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
379 /* Just set the owner */
380 Mcb
->OwnerPsp
= NewOwner
;
385 static WORD
DosCopyEnvironmentBlock(WORD SourceSegment
, LPCSTR ProgramName
)
387 PCHAR Ptr
, SourceBuffer
, DestBuffer
= NULL
;
391 Ptr
= SourceBuffer
= (PCHAR
)SEG_OFF_TO_PTR(SourceSegment
, 0);
393 /* Calculate the size of the environment block */
396 TotalSize
+= strlen(Ptr
) + 1;
397 Ptr
+= strlen(Ptr
) + 1;
401 /* Add the string buffer size */
402 TotalSize
+= strlen(ProgramName
) + 1;
404 /* Allocate the memory for the environment block */
405 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
406 if (!DestSegment
) return 0;
410 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
413 /* Copy the string */
414 strcpy(DestBuffer
, Ptr
);
416 /* Advance to the next string */
417 DestBuffer
+= strlen(Ptr
);
418 Ptr
+= strlen(Ptr
) + 1;
420 /* Put a zero after the string */
424 /* Set the final zero */
427 /* Copy the program name after the environment block */
428 strcpy(DestBuffer
, ProgramName
);
433 /* Taken from base/shell/cmd/console.c */
434 BOOL
IsConsoleHandle(HANDLE hHandle
)
438 /* Check whether the handle may be that of a console... */
439 if ((GetFileType(hHandle
) & FILE_TYPE_CHAR
) == 0) return FALSE
;
442 * It may be. Perform another test... The idea comes from the
443 * MSDN description of the WriteConsole API:
445 * "WriteConsole fails if it is used with a standard handle
446 * that is redirected to a file. If an application processes
447 * multilingual output that can be redirected, determine whether
448 * the output handle is a console handle (one method is to call
449 * the GetConsoleMode function and check whether it succeeds).
450 * If the handle is a console handle, call WriteConsole. If the
451 * handle is not a console handle, the output is redirected and
452 * you should call WriteFile to perform the I/O."
454 return GetConsoleMode(hHandle
, &dwMode
);
457 static WORD
DosOpenHandle(HANDLE Handle
)
464 /* The system PSP has no handle table */
465 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_DOS_HANDLE
;
467 /* Get a pointer to the handle table */
468 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
469 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
471 /* Find a free entry in the JFT */
472 for (DosHandle
= 0; DosHandle
< PspBlock
->HandleTableSize
; DosHandle
++)
474 if (HandleTable
[DosHandle
] == 0xFF) break;
477 /* If there are no free entries, fail */
478 if (DosHandle
== PspBlock
->HandleTableSize
) return INVALID_DOS_HANDLE
;
480 /* Check if the handle is already in the SFT */
481 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
483 /* Check if this is the same handle */
484 if (DosSystemFileTable
[i
] != Handle
) continue;
486 /* Already in the table, reference it */
489 /* Set the JFT entry to that SFT index */
490 HandleTable
[DosHandle
] = i
;
492 /* Return the new handle */
496 /* Add the handle to the SFT */
497 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
499 /* Make sure this is an empty table entry */
500 if (DosSystemFileTable
[i
] != INVALID_HANDLE_VALUE
) continue;
502 /* Initialize the empty table entry */
503 DosSystemFileTable
[i
] = Handle
;
504 DosSftRefCount
[i
] = 1;
506 /* Set the JFT entry to that SFT index */
507 HandleTable
[DosHandle
] = i
;
509 /* Return the new handle */
513 /* The SFT is full */
514 return INVALID_DOS_HANDLE
;
517 HANDLE
DosGetRealHandle(WORD DosHandle
)
522 /* The system PSP has no handle table */
523 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_HANDLE_VALUE
;
525 /* Get a pointer to the handle table */
526 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
527 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
529 /* Make sure the handle is open */
530 if (HandleTable
[DosHandle
] == 0xFF) return INVALID_HANDLE_VALUE
;
532 /* Return the Win32 handle */
533 return DosSystemFileTable
[HandleTable
[DosHandle
]];
536 static VOID
DosCopyHandleTable(LPBYTE DestinationTable
)
542 /* Clear the table first */
543 for (i
= 0; i
< 20; i
++) DestinationTable
[i
] = 0xFF;
545 /* Check if this is the initial process */
546 if (CurrentPsp
== SYSTEM_PSP
)
548 /* Set up the standard I/O devices */
549 for (i
= 0; i
<= 2; i
++)
551 /* Set the index in the SFT */
552 DestinationTable
[i
] = (BYTE
)i
;
554 /* Increase the reference count */
562 /* Get the parent PSP block and handle table */
563 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
564 SourceTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
566 /* Copy the first 20 handles into the new table */
567 for (i
= 0; i
< 20; i
++)
569 DestinationTable
[i
] = SourceTable
[i
];
571 /* Increase the reference count */
572 DosSftRefCount
[SourceTable
[i
]]++;
576 static BOOLEAN
DosCloseHandle(WORD DosHandle
)
582 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle
);
584 /* The system PSP has no handle table */
585 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
587 /* Get a pointer to the handle table */
588 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
589 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
591 /* Make sure the handle is open */
592 if (HandleTable
[DosHandle
] == 0xFF) return FALSE
;
594 /* Decrement the reference count of the SFT entry */
595 SftIndex
= HandleTable
[DosHandle
];
596 DosSftRefCount
[SftIndex
]--;
598 /* Check if the reference count fell to zero */
599 if (!DosSftRefCount
[SftIndex
])
601 /* Close the file, it's no longer needed */
602 CloseHandle(DosSystemFileTable
[SftIndex
]);
604 /* Clear the handle */
605 DosSystemFileTable
[SftIndex
] = INVALID_HANDLE_VALUE
;
608 /* Clear the entry in the JFT */
609 HandleTable
[DosHandle
] = 0xFF;
614 static BOOLEAN
DosDuplicateHandle(WORD OldHandle
, WORD NewHandle
)
620 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
624 /* The system PSP has no handle table */
625 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
627 /* Get a pointer to the handle table */
628 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
629 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
631 /* Make sure the old handle is open */
632 if (HandleTable
[OldHandle
] == 0xFF) return FALSE
;
634 /* Check if the new handle is open */
635 if (HandleTable
[NewHandle
] != 0xFF)
638 DosCloseHandle(NewHandle
);
641 /* Increment the reference count of the SFT entry */
642 SftIndex
= HandleTable
[OldHandle
];
643 DosSftRefCount
[SftIndex
]++;
645 /* Make the new handle point to that SFT entry */
646 HandleTable
[NewHandle
] = SftIndex
;
652 static WORD
DosCreateFile(LPWORD Handle
, LPCSTR FilePath
, WORD Attributes
)
657 DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
661 /* Create the file */
662 FileHandle
= CreateFileA(FilePath
,
663 GENERIC_READ
| GENERIC_WRITE
,
664 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
670 if (FileHandle
== INVALID_HANDLE_VALUE
)
672 /* Return the error code */
673 return (WORD
)GetLastError();
676 /* Open the DOS handle */
677 DosHandle
= DosOpenHandle(FileHandle
);
679 if (DosHandle
== INVALID_DOS_HANDLE
)
681 /* Close the handle */
682 CloseHandle(FileHandle
);
684 /* Return the error code */
685 return ERROR_TOO_MANY_OPEN_FILES
;
688 /* It was successful */
690 return ERROR_SUCCESS
;
693 static WORD
DosOpenFile(LPWORD Handle
, LPCSTR FilePath
, BYTE AccessMode
)
696 ACCESS_MASK Access
= 0;
699 DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
703 /* Parse the access mode */
704 switch (AccessMode
& 3)
709 Access
= GENERIC_READ
;
716 Access
= GENERIC_WRITE
;
723 Access
= GENERIC_READ
| GENERIC_WRITE
;
730 return ERROR_INVALID_PARAMETER
;
735 FileHandle
= CreateFileA(FilePath
,
737 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
740 FILE_ATTRIBUTE_NORMAL
,
743 if (FileHandle
== INVALID_HANDLE_VALUE
)
745 /* Return the error code */
746 return (WORD
)GetLastError();
749 /* Open the DOS handle */
750 DosHandle
= DosOpenHandle(FileHandle
);
752 if (DosHandle
== INVALID_DOS_HANDLE
)
754 /* Close the handle */
755 CloseHandle(FileHandle
);
757 /* Return the error code */
758 return ERROR_TOO_MANY_OPEN_FILES
;
761 /* It was successful */
763 return ERROR_SUCCESS
;
766 WORD
DosReadFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesRead
)
768 WORD Result
= ERROR_SUCCESS
;
769 DWORD BytesRead32
= 0;
770 HANDLE Handle
= DosGetRealHandle(FileHandle
);
772 DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle
, Count
);
774 /* Make sure the handle is valid */
775 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
778 if (!ReadFile(Handle
, Buffer
, Count
, &BytesRead32
, NULL
))
780 /* Store the error code */
781 Result
= (WORD
)GetLastError();
784 /* The number of bytes read is always 16-bit */
785 *BytesRead
= LOWORD(BytesRead32
);
787 /* Return the error code */
791 WORD
DosWriteFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesWritten
)
793 WORD Result
= ERROR_SUCCESS
;
794 DWORD BytesWritten32
= 0;
795 HANDLE Handle
= DosGetRealHandle(FileHandle
);
798 DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
802 /* Make sure the handle is valid */
803 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
805 if (IsConsoleHandle(Handle
))
807 for (i
= 0; i
< Count
; i
++)
809 /* Call the BIOS to print the character */
810 VidBiosPrintCharacter(((LPBYTE
)Buffer
)[i
], DOS_CHAR_ATTRIBUTE
, Bda
->VideoPage
);
817 if (!WriteFile(Handle
, Buffer
, Count
, &BytesWritten32
, NULL
))
819 /* Store the error code */
820 Result
= (WORD
)GetLastError();
824 /* The number of bytes written is always 16-bit */
825 *BytesWritten
= LOWORD(BytesWritten32
);
827 /* Return the error code */
831 static WORD
DosSeekFile(WORD FileHandle
, LONG Offset
, BYTE Origin
, LPDWORD NewOffset
)
833 WORD Result
= ERROR_SUCCESS
;
835 HANDLE Handle
= DosGetRealHandle(FileHandle
);
837 DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n",
842 /* Make sure the handle is valid */
843 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
845 /* Check if the origin is valid */
846 if (Origin
!= FILE_BEGIN
&& Origin
!= FILE_CURRENT
&& Origin
!= FILE_END
)
848 return ERROR_INVALID_FUNCTION
;
851 /* Move the file pointer */
852 FilePointer
= SetFilePointer(Handle
, Offset
, NULL
, Origin
);
854 /* Check if there's a possibility the operation failed */
855 if (FilePointer
== INVALID_SET_FILE_POINTER
)
857 /* Get the real error code */
858 Result
= (WORD
)GetLastError();
861 if (Result
!= ERROR_SUCCESS
)
863 /* The operation did fail */
867 /* Return the file pointer, if requested */
868 if (NewOffset
) *NewOffset
= FilePointer
;
871 return ERROR_SUCCESS
;
874 static BOOLEAN
DosFlushFileBuffers(WORD FileHandle
)
876 HANDLE Handle
= DosGetRealHandle(FileHandle
);
878 /* Make sure the handle is valid */
879 if (Handle
== INVALID_HANDLE_VALUE
) return FALSE
;
882 * No need to check whether the handle is a console handle since
883 * FlushFileBuffers() automatically does this check and calls
884 * FlushConsoleInputBuffer() for us.
886 // if (IsConsoleHandle(Handle))
887 // return (BOOLEAN)FlushConsoleInputBuffer(Handle);
889 return (BOOLEAN
)FlushFileBuffers(Handle
);
892 static BOOLEAN
DosChangeDrive(BYTE Drive
)
894 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
896 /* Make sure the drive exists */
897 if (Drive
> (LastDrive
- 'A')) return FALSE
;
899 /* Find the path to the new current directory */
900 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
902 /* Change the current directory of the process */
903 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
905 /* Set the current drive */
906 CurrentDrive
= Drive
;
912 static BOOLEAN
DosChangeDirectory(LPSTR Directory
)
918 /* Make sure the directory path is not too long */
919 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
921 DosLastError
= ERROR_PATH_NOT_FOUND
;
925 /* Get the drive number */
926 DriveNumber
= Directory
[0] - 'A';
928 /* Make sure the drive exists */
929 if (DriveNumber
> (LastDrive
- 'A'))
931 DosLastError
= ERROR_PATH_NOT_FOUND
;
935 /* Get the file attributes */
936 Attributes
= GetFileAttributesA(Directory
);
938 /* Make sure the path exists and is a directory */
939 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
940 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
942 DosLastError
= ERROR_PATH_NOT_FOUND
;
946 /* Check if this is the current drive */
947 if (DriveNumber
== CurrentDrive
)
949 /* Change the directory */
950 if (!SetCurrentDirectoryA(Directory
))
952 DosLastError
= LOWORD(GetLastError());
957 /* Get the directory part of the path */
958 Path
= strchr(Directory
, '\\');
961 /* Skip the backslash */
965 /* Set the directory for the drive */
968 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
972 CurrentDirectories
[DriveNumber
][0] = '\0';
979 /* PUBLIC FUNCTIONS ***********************************************************/
981 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
983 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
984 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
986 ZeroMemory(PspBlock
, sizeof(DOS_PSP
));
988 /* Set the exit interrupt */
989 PspBlock
->Exit
[0] = 0xCD; // int 0x20
990 PspBlock
->Exit
[1] = 0x20;
992 /* Set the number of the last paragraph */
993 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
995 /* Save the interrupt vectors */
996 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
997 PspBlock
->BreakAddress
= IntVecTable
[0x23];
998 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
1000 /* Set the parent PSP */
1001 PspBlock
->ParentPsp
= CurrentPsp
;
1003 /* Copy the parent handle table */
1004 DosCopyHandleTable(PspBlock
->HandleTable
);
1006 /* Set the environment block */
1007 PspBlock
->EnvBlock
= Environment
;
1009 /* Set the handle table pointers to the internal handle table */
1010 PspBlock
->HandleTableSize
= 20;
1011 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
1013 /* Set the DOS version */
1014 PspBlock
->DosVersion
= DOS_VERSION
;
1016 /* Set the far call opcodes */
1017 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
1018 PspBlock
->FarCall
[1] = 0x21;
1019 PspBlock
->FarCall
[2] = 0xCB; // retf
1021 /* Set the command line */
1022 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
1023 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
1024 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
1027 BOOLEAN
DosCreateProcess(LPCSTR CommandLine
, WORD EnvBlock
)
1029 BOOLEAN Success
= FALSE
, AllocatedEnvBlock
= FALSE
;
1030 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
1031 LPBYTE Address
= NULL
;
1032 LPSTR ProgramFilePath
, Parameters
[256];
1033 CHAR CommandLineCopy
[DOS_CMDLINE_LENGTH
];
1034 CHAR ParamString
[DOS_CMDLINE_LENGTH
];
1035 DWORD ParamCount
= 0;
1038 DWORD i
, FileSize
, ExeSize
;
1039 PIMAGE_DOS_HEADER Header
;
1040 PDWORD RelocationTable
;
1043 DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n",
1047 /* Save a copy of the command line */
1048 strcpy(CommandLineCopy
, CommandLine
);
1050 /* Get the file name of the executable */
1051 ProgramFilePath
= strtok(CommandLineCopy
, " \t");
1053 /* Load the parameters in the local array */
1054 while ((ParamCount
< sizeof(Parameters
)/sizeof(Parameters
[0]))
1055 && ((Parameters
[ParamCount
] = strtok(NULL
, " \t")) != NULL
))
1060 ZeroMemory(ParamString
, sizeof(ParamString
));
1062 /* Store the parameters in a string */
1063 for (i
= 0; i
< ParamCount
; i
++)
1065 strncat(ParamString
, Parameters
[i
], DOS_CMDLINE_LENGTH
- strlen(ParamString
) - 1);
1066 strncat(ParamString
, " ", DOS_CMDLINE_LENGTH
- strlen(ParamString
) - 1);
1069 /* Open a handle to the executable */
1070 FileHandle
= CreateFileA(ProgramFilePath
,
1075 FILE_ATTRIBUTE_NORMAL
,
1077 if (FileHandle
== INVALID_HANDLE_VALUE
) goto Cleanup
;
1079 /* Get the file size */
1080 FileSize
= GetFileSize(FileHandle
, NULL
);
1082 /* Create a mapping object for the file */
1083 FileMapping
= CreateFileMapping(FileHandle
,
1089 if (FileMapping
== NULL
) goto Cleanup
;
1091 /* Map the file into memory */
1092 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
1093 if (Address
== NULL
) goto Cleanup
;
1095 /* Did we get an environment segment? */
1098 /* Set a flag to know if the environment block was allocated here */
1099 AllocatedEnvBlock
= TRUE
;
1101 /* No, copy the one from the parent */
1102 EnvBlock
= DosCopyEnvironmentBlock((CurrentPsp
!= SYSTEM_PSP
)
1103 ? SEGMENT_TO_PSP(CurrentPsp
)->EnvBlock
1108 /* Check if this is an EXE file or a COM file */
1109 if (Address
[0] == 'M' && Address
[1] == 'Z')
1113 /* Get the MZ header */
1114 Header
= (PIMAGE_DOS_HEADER
)Address
;
1116 /* Get the base size of the file, in paragraphs (rounded up) */
1117 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
1119 /* Add the PSP size, in paragraphs */
1120 ExeSize
+= sizeof(DOS_PSP
) >> 4;
1122 /* Add the maximum size that should be allocated */
1123 ExeSize
+= Header
->e_maxalloc
;
1125 /* Make sure it does not pass 0xFFFF */
1126 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
1128 /* Reduce the size one by one until the allocation is successful */
1129 for (i
= Header
->e_maxalloc
; i
>= Header
->e_minalloc
; i
--, ExeSize
--)
1131 /* Try to allocate that much memory */
1132 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
1133 if (Segment
!= 0) break;
1136 /* Check if at least the lowest allocation was successful */
1137 if (Segment
== 0) goto Cleanup
;
1139 /* Initialize the PSP */
1140 DosInitializePsp(Segment
,
1145 /* The process owns its own memory */
1146 DosChangeMemoryOwner(Segment
, Segment
);
1147 DosChangeMemoryOwner(EnvBlock
, Segment
);
1149 /* Copy the program to Segment:0100 */
1150 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1151 Address
+ (Header
->e_cparhdr
<< 4),
1152 min(FileSize
- (Header
->e_cparhdr
<< 4),
1153 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
1155 /* Get the relocation table */
1156 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
1158 /* Perform relocations */
1159 for (i
= 0; i
< Header
->e_crlc
; i
++)
1161 /* Get a pointer to the word that needs to be patched */
1162 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
1163 0x100 + LOWORD(RelocationTable
[i
]));
1165 /* Add the number of the EXE segment to it */
1166 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
1169 /* Set the initial segment registers */
1173 /* Set the stack to the location from the header */
1174 EmulatorSetStack(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
,
1178 CurrentPsp
= Segment
;
1179 DiskTransferArea
= MAKELONG(0x80, Segment
);
1180 EmulatorExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
1189 /* Find the maximum amount of memory that can be allocated */
1190 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
1192 /* Make sure it's enough for the whole program and the PSP */
1193 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
))) goto Cleanup
;
1195 /* Allocate all of it */
1196 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
1197 if (Segment
== 0) goto Cleanup
;
1199 /* The process owns its own memory */
1200 DosChangeMemoryOwner(Segment
, Segment
);
1201 DosChangeMemoryOwner(EnvBlock
, Segment
);
1203 /* Copy the program to Segment:0100 */
1204 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1208 /* Initialize the PSP */
1209 DosInitializePsp(Segment
,
1214 /* Set the initial segment registers */
1218 /* Set the stack to the last word of the segment */
1219 EmulatorSetStack(Segment
, 0xFFFE);
1222 * Set the value on the stack to 0, so that a near return
1223 * jumps to PSP:0000 which has the exit code.
1225 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
1228 CurrentPsp
= Segment
;
1229 DiskTransferArea
= MAKELONG(0x80, Segment
);
1230 EmulatorExecute(Segment
, 0x100);
1238 /* It was not successful, cleanup the DOS memory */
1239 if (AllocatedEnvBlock
) DosFreeMemory(EnvBlock
);
1240 if (Segment
) DosFreeMemory(Segment
);
1244 if (Address
!= NULL
) UnmapViewOfFile(Address
);
1246 /* Close the file mapping object */
1247 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
1249 /* Close the file handle */
1250 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
1255 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
1258 WORD McbSegment
= FIRST_MCB_SEGMENT
;
1259 PDOS_MCB CurrentMcb
;
1260 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
1261 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
1263 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1267 /* Check if this PSP is it's own parent */
1268 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
1270 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
1272 /* Close the handle */
1276 /* Free the memory used by the process */
1279 /* Get a pointer to the MCB */
1280 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
1282 /* Make sure the MCB is valid */
1283 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
1285 /* If this block was allocated by the process, free it */
1286 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
+ 1);
1288 /* If this was the last block, quit */
1289 if (CurrentMcb
->BlockType
== 'Z') break;
1291 /* Update the segment and continue */
1292 McbSegment
+= CurrentMcb
->Size
+ 1;
1296 /* Restore the interrupt vectors */
1297 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
1298 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
1299 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
1301 /* Update the current PSP */
1302 if (Psp
== CurrentPsp
)
1304 CurrentPsp
= PspBlock
->ParentPsp
;
1305 if (CurrentPsp
== SYSTEM_PSP
) VdmRunning
= FALSE
;
1308 /* Save the return code - Normal termination */
1309 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
1311 /* Return control to the parent process */
1312 EmulatorExecute(HIWORD(PspBlock
->TerminateAddress
),
1313 LOWORD(PspBlock
->TerminateAddress
));
1316 BOOLEAN
DosHandleIoctl(BYTE ControlCode
, WORD FileHandle
)
1318 HANDLE Handle
= DosGetRealHandle(FileHandle
);
1320 if (Handle
== INVALID_HANDLE_VALUE
)
1323 DosLastError
= ERROR_FILE_NOT_FOUND
;
1327 switch (ControlCode
)
1329 /* Get Device Information */
1335 * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
1336 * for a list of possible flags.
1339 if (Handle
== DosSystemFileTable
[0])
1344 else if (Handle
== DosSystemFileTable
[1])
1346 /* Console output */
1350 /* It is a device */
1353 /* Return the device information word */
1358 /* Unsupported control code */
1361 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode
);
1363 DosLastError
= ERROR_INVALID_PARAMETER
;
1369 VOID WINAPI
DosInt20h(LPWORD Stack
)
1371 /* This is the exit interrupt */
1372 DosTerminateProcess(Stack
[STACK_CS
], 0);
1375 VOID WINAPI
DosInt21h(LPWORD Stack
)
1378 SYSTEMTIME SystemTime
;
1380 PDOS_INPUT_BUFFER InputBuffer
;
1382 /* Check the value in the AH register */
1385 /* Terminate Program */
1388 DosTerminateProcess(Stack
[STACK_CS
], 0);
1392 /* Read Character from STDIN with Echo */
1395 Character
= DosReadCharacter();
1396 DosPrintCharacter(Character
);
1398 /* Let the BOP repeat if needed */
1405 /* Write Character to STDOUT */
1408 Character
= getDL();
1409 DosPrintCharacter(Character
);
1412 * We return the output character (DOS 2.1+).
1413 * Also, if we're going to output a TAB, then
1414 * don't return a TAB but a SPACE instead.
1415 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1416 * for more information.
1418 setAL(Character
== '\t' ? ' ' : Character
);
1422 /* Read Character from STDAUX */
1425 // FIXME: Really read it from STDAUX!
1426 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1427 setAL(DosReadCharacter());
1431 /* Write Character to STDAUX */
1434 // FIXME: Really write it to STDAUX!
1435 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1436 DosPrintCharacter(getDL());
1440 /* Write Character to Printer */
1443 // FIXME: Really write it to printer!
1444 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1445 DPRINT1("0x%p\n", getDL());
1446 DPRINT1("\n\n-----------\n\n");
1450 /* Direct Console I/O */
1453 Character
= getDL();
1455 if (Character
!= 0xFF)
1458 DosPrintCharacter(Character
);
1461 * We return the output character (DOS 2.1+).
1462 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1463 * for more information.
1470 if (DosCheckInput())
1472 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
1473 setAL(DosReadCharacter());
1477 /* No character available */
1478 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
1486 /* Character Input without Echo */
1490 Character
= DosReadCharacter();
1492 /* Let the BOP repeat if needed */
1499 /* Write string to STDOUT */
1502 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1504 while (*String
!= '$')
1506 DosPrintCharacter(*String
);
1511 * We return the terminating character (DOS 2.1+).
1512 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1513 * for more information.
1519 /* Read Buffered Input */
1522 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1524 while (Stack
[STACK_COUNTER
] < InputBuffer
->MaxLength
)
1526 /* Try to read a character */
1527 Character
= DosReadCharacter();
1529 /* If it's not ready yet, let the BOP repeat */
1532 /* Echo the character and append it to the buffer */
1533 DosPrintCharacter(Character
);
1534 InputBuffer
->Buffer
[Stack
[STACK_COUNTER
]] = Character
;
1536 if (Character
== '\r') break;
1537 Stack
[STACK_COUNTER
]++;
1540 /* Update the length */
1541 InputBuffer
->Length
= Stack
[STACK_COUNTER
];
1545 /* Get STDIN Status */
1548 setAL(DosCheckInput() ? 0xFF : 0x00);
1552 /* Flush Buffer and Read STDIN */
1555 BYTE InputFunction
= getAL();
1557 /* Flush STDIN buffer */
1558 DosFlushFileBuffers(DOS_INPUT_HANDLE
); // Maybe just create a DosFlushInputBuffer...
1561 * If the input function number contained in AL is valid, i.e.
1562 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1563 * recursively with AL == AH.
1565 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1566 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1567 InputFunction
== 0x0A)
1569 setAH(InputFunction
);
1571 * Instead of calling ourselves really recursively as in:
1573 * prefer resetting the CF flag to let the BOP repeat.
1583 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1585 // TODO: Flush what's needed.
1586 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1588 /* Clear CF in DOS 6 only */
1589 if (PspBlock
->DosVersion
== 0x0006)
1590 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1595 /* Set Default Drive */
1598 DosChangeDrive(getDL());
1599 setAL(LastDrive
- 'A' + 1);
1603 /* NULL Function for CP/M Compatibility */
1607 * This function corresponds to the CP/M BDOS function
1608 * "get bit map of logged drives", which is meaningless
1611 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1612 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1613 * for more information.
1619 /* Get Default Drive */
1622 setAL(CurrentDrive
);
1626 /* Set Disk Transfer Area */
1629 DiskTransferArea
= MAKELONG(getDX(), getDS());
1633 /* NULL Function for CP/M Compatibility */
1638 * Function 0x1D corresponds to the CP/M BDOS function
1639 * "get bit map of read-only drives", which is meaningless
1641 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1642 * for more information.
1644 * Function 0x1E corresponds to the CP/M BDOS function
1645 * "set file attributes", which was meaningless under MS-DOS 1.x.
1646 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1647 * for more information.
1653 /* NULL Function for CP/M Compatibility */
1657 * This function corresponds to the CP/M BDOS function
1658 * "get/set default user (sublibrary) number", which is meaningless
1661 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1662 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1663 * for more information.
1669 /* Set Interrupt Vector */
1672 DWORD FarPointer
= MAKELONG(getDX(), getDS());
1673 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1675 /* Write the new far pointer to the IDT */
1676 ((PDWORD
)BaseAddress
)[getAL()] = FarPointer
;
1680 /* Create New PSP */
1683 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1687 /* Get System Date */
1690 GetLocalTime(&SystemTime
);
1691 setCX(SystemTime
.wYear
);
1692 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1693 setAL(SystemTime
.wDayOfWeek
);
1697 /* Set System Date */
1700 GetLocalTime(&SystemTime
);
1701 SystemTime
.wYear
= getCX();
1702 SystemTime
.wMonth
= getDH();
1703 SystemTime
.wDay
= getDL();
1705 /* Return success or failure */
1706 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1710 /* Get System Time */
1713 GetLocalTime(&SystemTime
);
1714 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1715 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1719 /* Set System Time */
1722 GetLocalTime(&SystemTime
);
1723 SystemTime
.wHour
= getCH();
1724 SystemTime
.wMinute
= getCL();
1725 SystemTime
.wSecond
= getDH();
1726 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1728 /* Return success or failure */
1729 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1733 /* Get Disk Transfer Area */
1736 setES(HIWORD(DiskTransferArea
));
1737 setBX(LOWORD(DiskTransferArea
));
1741 /* Get DOS Version */
1744 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1747 * DOS 2+ - GET DOS VERSION
1748 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1749 * for more information.
1752 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1755 * Return DOS OEM number:
1756 * 0x00 for IBM PC-DOS
1757 * 0x02 for packaged MS-DOS
1762 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1765 * Return version flag:
1766 * 1 << 3 if DOS is in ROM,
1767 * 0 (reserved) if not.
1772 /* Return DOS 24-bit user serial number in BL:CX */
1777 * Return DOS version: Minor:Major in AH:AL
1778 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1780 setAX(PspBlock
->DosVersion
);
1785 /* Extended functionalities */
1788 if (getAL() == 0x06)
1791 * DOS 5+ - GET TRUE VERSION NUMBER
1792 * This function always returns the true version number, unlike
1793 * AH=30h, whose return value may be changed with SETVER.
1794 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1795 * for more information.
1799 * Return the true DOS version: Minor:Major in BH:BL
1800 * The Windows NT DOS box returns BX=3205h (version 5.50).
1802 setBX(NTDOS_VERSION
);
1804 /* DOS revision 0 */
1812 // /* Invalid subfunction */
1819 /* Get Interrupt Vector */
1822 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1824 /* Read the address from the IDT into ES:BX */
1825 setES(HIWORD(FarPointer
));
1826 setBX(LOWORD(FarPointer
));
1830 /* SWITCH character - AVAILDEV */
1833 if (getAL() == 0x00)
1836 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1837 * This setting is ignored by MS-DOS 4.0+.
1838 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1839 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1840 * for more information.
1845 else if (getAL() == 0x01)
1848 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1849 * This setting is ignored by MS-DOS 5+.
1850 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1851 * for more information.
1856 else if (getAL() == 0x02)
1859 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1860 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1861 * for more information.
1866 else if (getAL() == 0x03)
1869 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1870 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1871 * for more information.
1878 /* Invalid subfunction */
1885 /* Create Directory */
1888 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1890 if (CreateDirectoryA(String
, NULL
))
1892 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1896 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1897 setAX(LOWORD(GetLastError()));
1903 /* Remove Directory */
1906 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1908 if (RemoveDirectoryA(String
))
1910 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1914 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1915 setAX(LOWORD(GetLastError()));
1921 /* Set Current Directory */
1924 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1926 if (DosChangeDirectory(String
))
1928 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1932 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1933 setAX(DosLastError
);
1943 WORD ErrorCode
= DosCreateFile(&FileHandle
,
1944 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1949 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1954 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1965 WORD ErrorCode
= DosOpenFile(&FileHandle
,
1966 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1971 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1976 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1986 if (DosCloseHandle(getBX()))
1988 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1992 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1993 setAX(ERROR_INVALID_HANDLE
);
1999 /* Read from File or Device */
2002 WORD Handle
= getBX();
2003 LPBYTE Buffer
= (LPBYTE
)SEG_OFF_TO_PTR(getDS(), getDX());
2004 WORD Count
= getCX();
2006 WORD ErrorCode
= ERROR_SUCCESS
;
2009 if (IsConsoleHandle(DosGetRealHandle(Handle
)))
2011 while (Stack
[STACK_COUNTER
] < Count
)
2013 /* Read a character from the BIOS */
2014 Character
= LOBYTE(BiosGetCharacter());
2016 /* Stop if the BOP needs to be repeated */
2019 // FIXME: Security checks!
2020 DosPrintCharacter(Character
);
2021 Buffer
[Stack
[STACK_COUNTER
]++] = Character
;
2023 if (Character
== '\r')
2025 /* Stop on first carriage return */
2026 DosPrintCharacter('\n');
2031 if (Character
!= '\r')
2033 if (Stack
[STACK_COUNTER
] < Count
) ErrorCode
= ERROR_NOT_READY
;
2034 else BytesRead
= Count
;
2036 else BytesRead
= Stack
[STACK_COUNTER
];
2040 /* Use the file reading function */
2041 ErrorCode
= DosReadFile(Handle
, Buffer
, Count
, &BytesRead
);
2044 if (ErrorCode
== ERROR_SUCCESS
)
2046 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2049 else if (ErrorCode
!= ERROR_NOT_READY
)
2051 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2057 /* Write to File or Device */
2060 WORD BytesWritten
= 0;
2061 WORD ErrorCode
= DosWriteFile(getBX(),
2062 SEG_OFF_TO_PTR(getDS(), getDX()),
2066 if (ErrorCode
== ERROR_SUCCESS
)
2068 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2069 setAX(BytesWritten
);
2073 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2083 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2085 if (demFileDelete(FileName
) == ERROR_SUCCESS
)
2087 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2089 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2090 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2092 setAL(FileName
[0] - 'A');
2096 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2097 setAX(GetLastError());
2107 WORD ErrorCode
= DosSeekFile(getBX(),
2108 MAKELONG(getDX(), getCX()),
2112 if (ErrorCode
== ERROR_SUCCESS
)
2114 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2116 /* Return the new offset in DX:AX */
2117 setDX(HIWORD(NewLocation
));
2118 setAX(LOWORD(NewLocation
));
2122 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2129 /* Get/Set File Attributes */
2133 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2135 if (getAL() == 0x00)
2137 /* Get the attributes */
2138 Attributes
= GetFileAttributesA(FileName
);
2140 /* Check if it failed */
2141 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
2143 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2144 setAX(GetLastError());
2148 /* Return the attributes that DOS can understand */
2149 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2150 setCX(Attributes
& 0x00FF);
2153 else if (getAL() == 0x01)
2155 /* Try to set the attributes */
2156 if (SetFileAttributesA(FileName
, getCL()))
2158 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2162 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2163 setAX(GetLastError());
2168 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2169 setAX(ERROR_INVALID_FUNCTION
);
2178 if (DosHandleIoctl(getAL(), getBX()))
2180 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2184 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2185 setAX(DosLastError
);
2191 /* Duplicate Handle */
2195 HANDLE Handle
= DosGetRealHandle(getBX());
2197 if (Handle
!= INVALID_HANDLE_VALUE
)
2199 /* The handle is invalid */
2200 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2201 setAX(ERROR_INVALID_HANDLE
);
2205 /* Open a new handle to the same entry */
2206 NewHandle
= DosOpenHandle(Handle
);
2208 if (NewHandle
== INVALID_DOS_HANDLE
)
2210 /* Too many files open */
2211 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2212 setAX(ERROR_TOO_MANY_OPEN_FILES
);
2216 /* Return the result */
2217 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2222 /* Force Duplicate Handle */
2225 if (DosDuplicateHandle(getBX(), getCX()))
2227 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2231 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2232 setAX(ERROR_INVALID_HANDLE
);
2238 /* Get Current Directory */
2241 BYTE DriveNumber
= getDL();
2242 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
2244 /* Get the real drive number */
2245 if (DriveNumber
== 0)
2247 DriveNumber
= CurrentDrive
;
2251 /* Decrement DriveNumber since it was 1-based */
2255 if (DriveNumber
<= LastDrive
- 'A')
2258 * Copy the current directory into the target buffer.
2259 * It doesn't contain the drive letter and the backslash.
2261 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
2262 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2263 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2267 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2268 setAX(ERROR_INVALID_DRIVE
);
2274 /* Allocate Memory */
2277 WORD MaxAvailable
= 0;
2278 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
2282 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2287 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2288 setAX(DosLastError
);
2289 setBX(MaxAvailable
);
2298 if (DosFreeMemory(getES()))
2300 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2304 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2305 setAX(ERROR_ARENA_TRASHED
);
2311 /* Resize Memory Block */
2316 if (DosResizeMemory(getES(), getBX(), &Size
))
2318 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2322 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2323 setAX(DosLastError
);
2330 /* Terminate With Return Code */
2333 DosTerminateProcess(CurrentPsp
, getAL());
2337 /* Get Return Code (ERRORLEVEL) */
2341 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2342 * DosErrorLevel is cleared after being read by this function.
2344 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2345 setAX(DosErrorLevel
);
2346 DosErrorLevel
= 0x0000; // Clear it
2350 /* Find First File */
2353 WORD Result
= (WORD
)demFileFindFirst(FAR_POINTER(DiskTransferArea
),
2354 SEG_OFF_TO_PTR(getDS(), getDX()),
2358 if (Result
== ERROR_SUCCESS
) Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2359 else Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2364 /* Find Next File */
2367 WORD Result
= (WORD
)demFileFindNext(FAR_POINTER(DiskTransferArea
));
2370 if (Result
== ERROR_SUCCESS
) Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2371 else Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2376 /* Internal - Set Current Process ID (Set PSP Address) */
2379 // FIXME: Is it really what it's done ??
2380 CurrentPsp
= getBX();
2384 /* Internal - Get Current Process ID (Get PSP Address) */
2386 /* Get Current PSP Address */
2390 * Undocumented AH=51h is identical to the documented AH=62h.
2391 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2392 * and http://www.ctyme.com/intr/rb-3140.htm
2393 * for more information.
2399 /* Get/Set Memory Management Options */
2402 if (getAL() == 0x00)
2404 /* Get allocation strategy */
2405 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2406 setAX(DosAllocStrategy
);
2408 else if (getAL() == 0x01)
2410 /* Set allocation strategy */
2412 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2413 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2415 /* Can't set both */
2416 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2417 setAX(ERROR_INVALID_PARAMETER
);
2421 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2423 /* Invalid allocation strategy */
2424 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2425 setAX(ERROR_INVALID_PARAMETER
);
2429 DosAllocStrategy
= getBL();
2430 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2432 else if (getAL() == 0x02)
2434 /* Get UMB link state */
2435 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2436 setAL(DosUmbLinked
? 0x01 : 0x00);
2438 else if (getAL() == 0x03)
2440 /* Set UMB link state */
2441 if (getBX()) DosLinkUmb();
2442 else DosUnlinkUmb();
2443 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2447 /* Invalid or unsupported function */
2448 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2449 setAX(ERROR_INVALID_FUNCTION
);
2458 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2461 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2462 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2467 VOID WINAPI
DosBreakInterrupt(LPWORD Stack
)
2469 UNREFERENCED_PARAMETER(Stack
);
2475 VOID WINAPI
DosFastConOut(LPWORD Stack
)
2478 * This is the DOS 2+ Fast Console Output Interrupt.
2479 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2480 * for more information.
2483 if (Stack
[STACK_COUNTER
] == 0)
2485 Stack
[STACK_COUNTER
]++;
2487 /* Save AX and BX */
2488 Stack
[STACK_VAR_A
] = getAX();
2489 Stack
[STACK_VAR_B
] = getBX();
2491 /* Rewind the BOP manually, we can't use CF because the interrupt could modify it */
2492 EmulatorExecute(getCS(), getIP() - 4);
2494 /* Call INT 0x10, AH = 0x0E */
2496 setBL(DOS_CHAR_ATTRIBUTE
);
2497 setBH(Bda
->VideoPage
);
2499 EmulatorInterrupt(0x10);
2503 /* Restore AX and BX */
2504 setAX(Stack
[STACK_VAR_A
]);
2505 setBX(Stack
[STACK_VAR_B
]);
2509 VOID WINAPI
DosInt2Fh(LPWORD Stack
)
2511 DPRINT1("DOS System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2513 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2516 BOOLEAN
DosKRNLInitialize(VOID
)
2522 CHAR CurrentDirectory
[MAX_PATH
];
2523 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2529 /* Clear the current directory buffer */
2530 ZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2532 /* Get the current directory */
2533 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2535 // TODO: Use some kind of default path?
2539 /* Convert that to a DOS path */
2540 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2542 // TODO: Use some kind of default path?
2547 CurrentDrive
= DosDirectory
[0] - 'A';
2549 /* Get the directory part of the path */
2550 Path
= strchr(DosDirectory
, '\\');
2553 /* Skip the backslash */
2557 /* Set the directory */
2560 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
2563 /* Read CONFIG.SYS */
2564 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
2567 while (fgetws(Buffer
, 256, Stream
))
2569 // TODO: Parse the line
2574 /* Initialize the SFT */
2575 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
2577 DosSystemFileTable
[i
] = INVALID_HANDLE_VALUE
;
2578 DosSftRefCount
[i
] = 0;
2581 /* Get handles to standard I/O devices */
2582 DosSystemFileTable
[0] = GetStdHandle(STD_INPUT_HANDLE
);
2583 DosSystemFileTable
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
2584 DosSystemFileTable
[2] = GetStdHandle(STD_ERROR_HANDLE
);
2589 /* Register the DOS 32-bit Interrupts */
2590 RegisterInt32(0x20, DosInt20h
);
2591 RegisterInt32(0x21, DosInt21h
);
2592 // RegisterInt32(0x22, DosInt22h ); // Termination
2593 RegisterInt32(0x23, DosBreakInterrupt
); // Ctrl-C / Ctrl-Break
2594 // RegisterInt32(0x24, DosInt24h ); // Critical Error
2595 RegisterInt32(0x29, DosFastConOut
); // DOS 2+ Fast Console Output
2596 RegisterInt32(0x2F, DosInt2Fh
);