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 "registers.h"
19 /* PRIVATE VARIABLES **********************************************************/
21 static WORD CurrentPsp
= SYSTEM_PSP
;
22 static WORD DosLastError
= 0;
23 static DWORD DiskTransferArea
;
24 static BYTE CurrentDrive
;
25 static CHAR LastDrive
= 'E';
26 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
27 static HANDLE DosSystemFileTable
[DOS_SFT_SIZE
];
28 static WORD DosSftRefCount
[DOS_SFT_SIZE
];
29 static BYTE DosAllocStrategy
= DOS_ALLOC_BEST_FIT
;
30 static BOOLEAN DosUmbLinked
= FALSE
;
31 static WORD DosErrorLevel
= 0x0000;
33 /* PRIVATE FUNCTIONS **********************************************************/
35 /* Taken from base/shell/cmd/console.c */
36 static BOOL
IsConsoleHandle(HANDLE hHandle
)
40 /* Check whether the handle may be that of a console... */
41 if ((GetFileType(hHandle
) & FILE_TYPE_CHAR
) == 0) return FALSE
;
44 * It may be. Perform another test... The idea comes from the
45 * MSDN description of the WriteConsole API:
47 * "WriteConsole fails if it is used with a standard handle
48 * that is redirected to a file. If an application processes
49 * multilingual output that can be redirected, determine whether
50 * the output handle is a console handle (one method is to call
51 * the GetConsoleMode function and check whether it succeeds).
52 * If the handle is a console handle, call WriteConsole. If the
53 * handle is not a console handle, the output is redirected and
54 * you should call WriteFile to perform the I/O."
56 return GetConsoleMode(hHandle
, &dwMode
);
59 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
61 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
63 /* If this is the last block or it's not free, quit */
64 if (CurrentMcb
->BlockType
== 'Z' || CurrentMcb
->OwnerPsp
!= 0) return;
68 /* Get a pointer to the next MCB */
69 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
71 /* Check if the next MCB is free */
72 if (NextMcb
->OwnerPsp
== 0)
75 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
76 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
77 NextMcb
->BlockType
= 'I';
81 /* No more adjoining free blocks */
87 static WORD
DosCopyEnvironmentBlock(WORD SourceSegment
, LPCSTR ProgramName
)
89 PCHAR Ptr
, SourceBuffer
, DestBuffer
= NULL
;
93 Ptr
= SourceBuffer
= (PCHAR
)SEG_OFF_TO_PTR(SourceSegment
, 0);
95 /* Calculate the size of the environment block */
98 TotalSize
+= strlen(Ptr
) + 1;
99 Ptr
+= strlen(Ptr
) + 1;
103 /* Add the string buffer size */
104 TotalSize
+= strlen(ProgramName
) + 1;
106 /* Allocate the memory for the environment block */
107 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
108 if (!DestSegment
) return 0;
112 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
115 /* Copy the string */
116 strcpy(DestBuffer
, Ptr
);
118 /* Advance to the next string */
119 DestBuffer
+= strlen(Ptr
);
120 Ptr
+= strlen(Ptr
) + 1;
122 /* Put a zero after the string */
126 /* Set the final zero */
129 /* Copy the program name after the environment block */
130 strcpy(DestBuffer
, ProgramName
);
135 static VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
137 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
139 /* Just set the owner */
140 Mcb
->OwnerPsp
= NewOwner
;
143 static WORD
DosOpenHandle(HANDLE Handle
)
150 /* The system PSP has no handle table */
151 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_DOS_HANDLE
;
153 /* Get a pointer to the handle table */
154 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
155 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
157 /* Find a free entry in the JFT */
158 for (DosHandle
= 0; DosHandle
< PspBlock
->HandleTableSize
; DosHandle
++)
160 if (HandleTable
[DosHandle
] == 0xFF) break;
163 /* If there are no free entries, fail */
164 if (DosHandle
== PspBlock
->HandleTableSize
) return INVALID_DOS_HANDLE
;
166 /* Check if the handle is already in the SFT */
167 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
169 /* Check if this is the same handle */
170 if (DosSystemFileTable
[i
] != Handle
) continue;
172 /* Already in the table, reference it */
175 /* Set the JFT entry to that SFT index */
176 HandleTable
[DosHandle
] = i
;
178 /* Return the new handle */
182 /* Add the handle to the SFT */
183 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
185 /* Make sure this is an empty table entry */
186 if (DosSystemFileTable
[i
] != INVALID_HANDLE_VALUE
) continue;
188 /* Initialize the empty table entry */
189 DosSystemFileTable
[i
] = Handle
;
190 DosSftRefCount
[i
] = 1;
192 /* Set the JFT entry to that SFT index */
193 HandleTable
[DosHandle
] = i
;
195 /* Return the new handle */
199 /* The SFT is full */
200 return INVALID_DOS_HANDLE
;
203 static HANDLE
DosGetRealHandle(WORD DosHandle
)
208 /* The system PSP has no handle table */
209 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_HANDLE_VALUE
;
211 /* Get a pointer to the handle table */
212 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
213 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
215 /* Make sure the handle is open */
216 if (HandleTable
[DosHandle
] == 0xFF) return INVALID_HANDLE_VALUE
;
218 /* Return the Win32 handle */
219 return DosSystemFileTable
[HandleTable
[DosHandle
]];
222 static VOID
DosCopyHandleTable(LPBYTE DestinationTable
)
228 /* Clear the table first */
229 for (i
= 0; i
< 20; i
++) DestinationTable
[i
] = 0xFF;
231 /* Check if this is the initial process */
232 if (CurrentPsp
== SYSTEM_PSP
)
234 /* Set up the standard I/O devices */
235 for (i
= 0; i
<= 2; i
++)
237 /* Set the index in the SFT */
238 DestinationTable
[i
] = (BYTE
)i
;
240 /* Increase the reference count */
248 /* Get the parent PSP block and handle table */
249 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
250 SourceTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
252 /* Copy the first 20 handles into the new table */
253 for (i
= 0; i
< 20; i
++)
255 DestinationTable
[i
] = SourceTable
[i
];
257 /* Increase the reference count */
258 DosSftRefCount
[SourceTable
[i
]]++;
262 /* PUBLIC FUNCTIONS ***********************************************************/
264 WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
266 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
, MaxSize
= 0;
267 PDOS_MCB CurrentMcb
, NextMcb
;
268 BOOLEAN SearchUmb
= FALSE
;
270 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
272 if (DosUmbLinked
&& (DosAllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
274 /* Search UMB first */
275 Segment
= UMB_START_SEGMENT
;
281 /* Get a pointer to the MCB */
282 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
284 /* Make sure it's valid */
285 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
287 DPRINT("The DOS memory arena is corrupted!\n");
288 DosLastError
= ERROR_ARENA_TRASHED
;
292 /* Only check free blocks */
293 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
295 /* Combine this free block with adjoining free blocks */
296 DosCombineFreeBlocks(Segment
);
298 /* Update the maximum block size */
299 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
301 /* Check if this block is big enough */
302 if (CurrentMcb
->Size
< Size
) goto Next
;
304 switch (DosAllocStrategy
& 0x3F)
306 case DOS_ALLOC_FIRST_FIT
:
308 /* For first fit, stop immediately */
313 case DOS_ALLOC_BEST_FIT
:
315 /* For best fit, update the smallest block found so far */
316 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
324 case DOS_ALLOC_LAST_FIT
:
326 /* For last fit, make the current block the result, but keep searching */
333 /* If this was the last MCB in the chain, quit */
334 if (CurrentMcb
->BlockType
== 'Z')
336 /* Check if nothing was found while searching through UMBs */
337 if ((Result
== 0) && SearchUmb
&& (DosAllocStrategy
& DOS_ALLOC_HIGH_LOW
))
339 /* Search low memory */
340 Segment
= FIRST_MCB_SEGMENT
;
347 /* Otherwise, update the segment and continue */
348 Segment
+= CurrentMcb
->Size
+ 1;
353 /* If we didn't find a free block, return 0 */
356 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
357 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
361 /* Get a pointer to the MCB */
362 CurrentMcb
= SEGMENT_TO_MCB(Result
);
364 /* Check if the block is larger than requested */
365 if (CurrentMcb
->Size
> Size
)
367 /* It is, split it into two blocks */
368 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
370 /* Initialize the new MCB structure */
371 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
372 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
373 NextMcb
->OwnerPsp
= 0;
375 /* Update the current block */
376 CurrentMcb
->BlockType
= 'M';
377 CurrentMcb
->Size
= Size
;
380 /* Take ownership of the block */
381 CurrentMcb
->OwnerPsp
= CurrentPsp
;
383 /* Return the segment of the data portion of the block */
387 BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
389 BOOLEAN Success
= TRUE
;
390 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
391 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
393 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
397 /* Make sure this is a valid, allocated block */
398 if ((Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') || Mcb
->OwnerPsp
== 0)
401 DosLastError
= ERROR_INVALID_HANDLE
;
405 ReturnSize
= Mcb
->Size
;
407 /* Check if we need to expand or contract the block */
408 if (NewSize
> Mcb
->Size
)
410 /* We can't expand the last block */
411 if (Mcb
->BlockType
!= 'M')
417 /* Get the pointer and segment of the next MCB */
418 NextSegment
= Segment
+ Mcb
->Size
+ 1;
419 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
421 /* Make sure the next segment is free */
422 if (NextMcb
->OwnerPsp
!= 0)
424 DPRINT("Cannot expand memory block: next segment is not free!\n");
425 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
430 /* Combine this free block with adjoining free blocks */
431 DosCombineFreeBlocks(NextSegment
);
433 /* Set the maximum possible size of the block */
434 ReturnSize
+= NextMcb
->Size
+ 1;
436 /* Maximize the current block */
437 Mcb
->Size
= ReturnSize
;
438 Mcb
->BlockType
= NextMcb
->BlockType
;
440 /* Invalidate the next block */
441 NextMcb
->BlockType
= 'I';
443 /* Check if the block is larger than requested */
444 if (Mcb
->Size
> NewSize
)
446 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
450 /* It is, split it into two blocks */
451 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
453 /* Initialize the new MCB structure */
454 NextMcb
->BlockType
= Mcb
->BlockType
;
455 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
456 NextMcb
->OwnerPsp
= 0;
458 /* Update the current block */
459 Mcb
->BlockType
= 'M';
463 else if (NewSize
< Mcb
->Size
)
465 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
469 /* Just split the block */
470 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
471 NextMcb
->BlockType
= Mcb
->BlockType
;
472 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
473 NextMcb
->OwnerPsp
= 0;
476 Mcb
->BlockType
= 'M';
481 /* Check if the operation failed */
484 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
487 /* Return the maximum possible size */
488 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
494 BOOLEAN
DosFreeMemory(WORD BlockData
)
496 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
498 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
500 /* Make sure the MCB is valid */
501 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z')
503 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
507 /* Mark the block as free */
513 BOOLEAN
DosLinkUmb(VOID
)
515 DWORD Segment
= FIRST_MCB_SEGMENT
;
516 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
518 DPRINT("Linking UMB\n");
520 /* Check if UMBs are already linked */
521 if (DosUmbLinked
) return FALSE
;
523 /* Find the last block */
524 while ((Mcb
->BlockType
== 'M') && (Segment
<= 0xFFFF))
526 Segment
+= Mcb
->Size
+ 1;
527 Mcb
= SEGMENT_TO_MCB(Segment
);
530 /* Make sure it's valid */
531 if (Mcb
->BlockType
!= 'Z') return FALSE
;
533 /* Connect the MCB with the UMB chain */
534 Mcb
->BlockType
= 'M';
540 BOOLEAN
DosUnlinkUmb(VOID
)
542 DWORD Segment
= FIRST_MCB_SEGMENT
;
543 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
545 DPRINT("Unlinking UMB\n");
547 /* Check if UMBs are already unlinked */
548 if (!DosUmbLinked
) return FALSE
;
550 /* Find the block preceding the MCB that links it with the UMB chain */
551 while (Segment
<= 0xFFFF)
553 if ((Segment
+ Mcb
->Size
) == (FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
))
555 /* This is the last non-UMB segment */
559 /* Advance to the next MCB */
560 Segment
+= Mcb
->Size
+ 1;
561 Mcb
= SEGMENT_TO_MCB(Segment
);
564 /* Mark the MCB as the last MCB */
565 Mcb
->BlockType
= 'Z';
567 DosUmbLinked
= FALSE
;
571 WORD
DosCreateFile(LPWORD Handle
, LPCSTR FilePath
, WORD Attributes
)
576 DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
580 /* Create the file */
581 FileHandle
= CreateFileA(FilePath
,
582 GENERIC_READ
| GENERIC_WRITE
,
583 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
589 if (FileHandle
== INVALID_HANDLE_VALUE
)
591 /* Return the error code */
592 return (WORD
)GetLastError();
595 /* Open the DOS handle */
596 DosHandle
= DosOpenHandle(FileHandle
);
598 if (DosHandle
== INVALID_DOS_HANDLE
)
600 /* Close the handle */
601 CloseHandle(FileHandle
);
603 /* Return the error code */
604 return ERROR_TOO_MANY_OPEN_FILES
;
607 /* It was successful */
609 return ERROR_SUCCESS
;
612 WORD
DosOpenFile(LPWORD Handle
, LPCSTR FilePath
, BYTE AccessMode
)
615 ACCESS_MASK Access
= 0;
618 DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
622 /* Parse the access mode */
623 switch (AccessMode
& 3)
628 Access
= GENERIC_READ
;
635 Access
= GENERIC_WRITE
;
642 Access
= GENERIC_READ
| GENERIC_WRITE
;
649 return ERROR_INVALID_PARAMETER
;
654 FileHandle
= CreateFileA(FilePath
,
656 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
659 FILE_ATTRIBUTE_NORMAL
,
662 if (FileHandle
== INVALID_HANDLE_VALUE
)
664 /* Return the error code */
665 return (WORD
)GetLastError();
668 /* Open the DOS handle */
669 DosHandle
= DosOpenHandle(FileHandle
);
671 if (DosHandle
== INVALID_DOS_HANDLE
)
673 /* Close the handle */
674 CloseHandle(FileHandle
);
676 /* Return the error code */
677 return ERROR_TOO_MANY_OPEN_FILES
;
680 /* It was successful */
682 return ERROR_SUCCESS
;
685 WORD
DosReadFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesRead
)
687 WORD Result
= ERROR_SUCCESS
;
688 DWORD BytesRead32
= 0;
689 HANDLE Handle
= DosGetRealHandle(FileHandle
);
691 DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle
, Count
);
693 /* Make sure the handle is valid */
694 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
697 if (!ReadFile(Handle
, Buffer
, Count
, &BytesRead32
, NULL
))
699 /* Store the error code */
700 Result
= (WORD
)GetLastError();
703 /* The number of bytes read is always 16-bit */
704 *BytesRead
= LOWORD(BytesRead32
);
706 /* Return the error code */
710 WORD
DosWriteFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesWritten
)
712 WORD Result
= ERROR_SUCCESS
;
713 DWORD BytesWritten32
= 0;
714 HANDLE Handle
= DosGetRealHandle(FileHandle
);
717 DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
721 /* Make sure the handle is valid */
722 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
724 if (IsConsoleHandle(Handle
))
726 for (i
= 0; i
< Count
; i
++)
728 /* Call the BIOS to print the character */
729 BiosPrintCharacter(((LPBYTE
)Buffer
)[i
], DOS_CHAR_ATTRIBUTE
, Bda
->VideoPage
);
736 if (!WriteFile(Handle
, Buffer
, Count
, &BytesWritten32
, NULL
))
738 /* Store the error code */
739 Result
= (WORD
)GetLastError();
743 /* The number of bytes written is always 16-bit */
744 *BytesWritten
= LOWORD(BytesWritten32
);
746 /* Return the error code */
750 WORD
DosSeekFile(WORD FileHandle
, LONG Offset
, BYTE Origin
, LPDWORD NewOffset
)
752 WORD Result
= ERROR_SUCCESS
;
754 HANDLE Handle
= DosGetRealHandle(FileHandle
);
756 DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n",
761 /* Make sure the handle is valid */
762 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
764 /* Check if the origin is valid */
765 if (Origin
!= FILE_BEGIN
&& Origin
!= FILE_CURRENT
&& Origin
!= FILE_END
)
767 return ERROR_INVALID_FUNCTION
;
770 /* Move the file pointer */
771 FilePointer
= SetFilePointer(Handle
, Offset
, NULL
, Origin
);
773 /* Check if there's a possibility the operation failed */
774 if (FilePointer
== INVALID_SET_FILE_POINTER
)
776 /* Get the real error code */
777 Result
= (WORD
)GetLastError();
780 if (Result
!= ERROR_SUCCESS
)
782 /* The operation did fail */
786 /* Return the file pointer, if requested */
787 if (NewOffset
) *NewOffset
= FilePointer
;
790 return ERROR_SUCCESS
;
793 BOOLEAN
DosFlushFileBuffers(WORD FileHandle
)
795 HANDLE Handle
= DosGetRealHandle(FileHandle
);
797 /* Make sure the handle is valid */
798 if (Handle
== INVALID_HANDLE_VALUE
) return FALSE
;
801 * No need to check whether the handle is a console handle since
802 * FlushFileBuffers() automatically does this check and calls
803 * FlushConsoleInputBuffer() for us.
805 // if (IsConsoleHandle(Handle))
806 // return (BOOLEAN)FlushConsoleInputBuffer(Handle);
808 return (BOOLEAN
)FlushFileBuffers(Handle
);
811 BOOLEAN
DosDuplicateHandle(WORD OldHandle
, WORD NewHandle
)
817 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
821 /* The system PSP has no handle table */
822 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
824 /* Get a pointer to the handle table */
825 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
826 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
828 /* Make sure the old handle is open */
829 if (HandleTable
[OldHandle
] == 0xFF) return FALSE
;
831 /* Check if the new handle is open */
832 if (HandleTable
[NewHandle
] != 0xFF)
835 DosCloseHandle(NewHandle
);
838 /* Increment the reference count of the SFT entry */
839 SftIndex
= HandleTable
[OldHandle
];
840 DosSftRefCount
[SftIndex
]++;
842 /* Make the new handle point to that SFT entry */
843 HandleTable
[NewHandle
] = SftIndex
;
849 BOOLEAN
DosCloseHandle(WORD DosHandle
)
855 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle
);
857 /* The system PSP has no handle table */
858 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
860 /* Get a pointer to the handle table */
861 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
862 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
864 /* Make sure the handle is open */
865 if (HandleTable
[DosHandle
] == 0xFF) return FALSE
;
867 /* Decrement the reference count of the SFT entry */
868 SftIndex
= HandleTable
[DosHandle
];
869 DosSftRefCount
[SftIndex
]--;
871 /* Check if the reference count fell to zero */
872 if (!DosSftRefCount
[SftIndex
])
874 /* Close the file, it's no longer needed */
875 CloseHandle(DosSystemFileTable
[SftIndex
]);
877 /* Clear the handle */
878 DosSystemFileTable
[SftIndex
] = INVALID_HANDLE_VALUE
;
881 /* Clear the entry in the JFT */
882 HandleTable
[DosHandle
] = 0xFF;
887 BOOLEAN
DosChangeDrive(BYTE Drive
)
889 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
891 /* Make sure the drive exists */
892 if (Drive
> (LastDrive
- 'A')) return FALSE
;
894 /* Find the path to the new current directory */
895 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
897 /* Change the current directory of the process */
898 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
900 /* Set the current drive */
901 CurrentDrive
= Drive
;
907 BOOLEAN
DosChangeDirectory(LPSTR Directory
)
913 /* Make sure the directory path is not too long */
914 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
916 DosLastError
= ERROR_PATH_NOT_FOUND
;
920 /* Get the drive number */
921 DriveNumber
= Directory
[0] - 'A';
923 /* Make sure the drive exists */
924 if (DriveNumber
> (LastDrive
- 'A'))
926 DosLastError
= ERROR_PATH_NOT_FOUND
;
930 /* Get the file attributes */
931 Attributes
= GetFileAttributesA(Directory
);
933 /* Make sure the path exists and is a directory */
934 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
935 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
937 DosLastError
= ERROR_PATH_NOT_FOUND
;
941 /* Check if this is the current drive */
942 if (DriveNumber
== CurrentDrive
)
944 /* Change the directory */
945 if (!SetCurrentDirectoryA(Directory
))
947 DosLastError
= LOWORD(GetLastError());
952 /* Get the directory part of the path */
953 Path
= strchr(Directory
, '\\');
956 /* Skip the backslash */
960 /* Set the directory for the drive */
963 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
967 CurrentDirectories
[DriveNumber
][0] = '\0';
974 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
976 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
977 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
979 ZeroMemory(PspBlock
, sizeof(DOS_PSP
));
981 /* Set the exit interrupt */
982 PspBlock
->Exit
[0] = 0xCD; // int 0x20
983 PspBlock
->Exit
[1] = 0x20;
985 /* Set the number of the last paragraph */
986 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
988 /* Save the interrupt vectors */
989 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
990 PspBlock
->BreakAddress
= IntVecTable
[0x23];
991 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
993 /* Set the parent PSP */
994 PspBlock
->ParentPsp
= CurrentPsp
;
996 /* Copy the parent handle table */
997 DosCopyHandleTable(PspBlock
->HandleTable
);
999 /* Set the environment block */
1000 PspBlock
->EnvBlock
= Environment
;
1002 /* Set the handle table pointers to the internal handle table */
1003 PspBlock
->HandleTableSize
= 20;
1004 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
1006 /* Set the DOS version */
1007 PspBlock
->DosVersion
= DOS_VERSION
;
1009 /* Set the far call opcodes */
1010 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
1011 PspBlock
->FarCall
[1] = 0x21;
1012 PspBlock
->FarCall
[2] = 0xCB; // retf
1014 /* Set the command line */
1015 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
1016 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
1017 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
1020 BOOLEAN
DosCreateProcess(LPCSTR CommandLine
, WORD EnvBlock
)
1022 BOOLEAN Success
= FALSE
, AllocatedEnvBlock
= FALSE
;
1023 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
1024 LPBYTE Address
= NULL
;
1025 LPSTR ProgramFilePath
, Parameters
[256];
1026 CHAR CommandLineCopy
[DOS_CMDLINE_LENGTH
];
1030 DWORD i
, FileSize
, ExeSize
;
1031 PIMAGE_DOS_HEADER Header
;
1032 PDWORD RelocationTable
;
1035 DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n",
1039 /* Save a copy of the command line */
1040 strcpy(CommandLineCopy
, CommandLine
);
1042 // FIXME: Improve parsing (especially: "some_path\with spaces\program.exe" options)
1044 /* Get the file name of the executable */
1045 ProgramFilePath
= strtok(CommandLineCopy
, " \t");
1047 /* Load the parameters in the local array */
1048 while ((ParamCount
< sizeof(Parameters
)/sizeof(Parameters
[0]))
1049 && ((Parameters
[ParamCount
] = strtok(NULL
, " \t")) != NULL
))
1054 /* Open a handle to the executable */
1055 FileHandle
= CreateFileA(ProgramFilePath
,
1060 FILE_ATTRIBUTE_NORMAL
,
1062 if (FileHandle
== INVALID_HANDLE_VALUE
) goto Cleanup
;
1064 /* Get the file size */
1065 FileSize
= GetFileSize(FileHandle
, NULL
);
1067 /* Create a mapping object for the file */
1068 FileMapping
= CreateFileMapping(FileHandle
,
1074 if (FileMapping
== NULL
) goto Cleanup
;
1076 /* Map the file into memory */
1077 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
1078 if (Address
== NULL
) goto Cleanup
;
1080 /* Did we get an environment segment? */
1083 /* Set a flag to know if the environment block was allocated here */
1084 AllocatedEnvBlock
= TRUE
;
1086 /* No, copy the one from the parent */
1087 EnvBlock
= DosCopyEnvironmentBlock((CurrentPsp
!= SYSTEM_PSP
)
1088 ? SEGMENT_TO_PSP(CurrentPsp
)->EnvBlock
1093 /* Check if this is an EXE file or a COM file */
1094 if (Address
[0] == 'M' && Address
[1] == 'Z')
1098 /* Get the MZ header */
1099 Header
= (PIMAGE_DOS_HEADER
)Address
;
1101 /* Get the base size of the file, in paragraphs (rounded up) */
1102 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
1104 /* Add the PSP size, in paragraphs */
1105 ExeSize
+= sizeof(DOS_PSP
) >> 4;
1107 /* Add the maximum size that should be allocated */
1108 ExeSize
+= Header
->e_maxalloc
;
1110 /* Make sure it does not pass 0xFFFF */
1111 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
1113 /* Reduce the size one by one until the allocation is successful */
1114 for (i
= Header
->e_maxalloc
; i
>= Header
->e_minalloc
; i
--, ExeSize
--)
1116 /* Try to allocate that much memory */
1117 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
1118 if (Segment
!= 0) break;
1121 /* Check if at least the lowest allocation was successful */
1122 if (Segment
== 0) goto Cleanup
;
1124 /* Initialize the PSP */
1125 DosInitializePsp(Segment
,
1130 /* The process owns its own memory */
1131 DosChangeMemoryOwner(Segment
, Segment
);
1132 DosChangeMemoryOwner(EnvBlock
, Segment
);
1134 /* Copy the program to Segment:0100 */
1135 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1136 Address
+ (Header
->e_cparhdr
<< 4),
1137 min(FileSize
- (Header
->e_cparhdr
<< 4),
1138 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
1140 /* Get the relocation table */
1141 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
1143 /* Perform relocations */
1144 for (i
= 0; i
< Header
->e_crlc
; i
++)
1146 /* Get a pointer to the word that needs to be patched */
1147 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
1148 0x100 + LOWORD(RelocationTable
[i
]));
1150 /* Add the number of the EXE segment to it */
1151 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
1154 /* Set the initial segment registers */
1158 /* Set the stack to the location from the header */
1159 EmulatorSetStack(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
,
1163 CurrentPsp
= Segment
;
1164 DiskTransferArea
= MAKELONG(0x80, Segment
);
1165 EmulatorExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
1174 /* Find the maximum amount of memory that can be allocated */
1175 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
1177 /* Make sure it's enough for the whole program and the PSP */
1178 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
))) goto Cleanup
;
1180 /* Allocate all of it */
1181 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
1182 if (Segment
== 0) goto Cleanup
;
1184 /* The process owns its own memory */
1185 DosChangeMemoryOwner(Segment
, Segment
);
1186 DosChangeMemoryOwner(EnvBlock
, Segment
);
1188 /* Copy the program to Segment:0100 */
1189 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1193 /* Initialize the PSP */
1194 DosInitializePsp(Segment
,
1196 (WORD
)((FileSize
+ sizeof(DOS_PSP
)) >> 4),
1199 /* Set the initial segment registers */
1203 /* Set the stack to the last word of the segment */
1204 EmulatorSetStack(Segment
, 0xFFFE);
1207 * Set the value on the stack to 0, so that a near return
1208 * jumps to PSP:0000 which has the exit code.
1210 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
1213 CurrentPsp
= Segment
;
1214 DiskTransferArea
= MAKELONG(0x80, Segment
);
1215 EmulatorExecute(Segment
, 0x100);
1223 /* It was not successful, cleanup the DOS memory */
1224 if (AllocatedEnvBlock
) DosFreeMemory(EnvBlock
);
1225 if (Segment
) DosFreeMemory(Segment
);
1229 if (Address
!= NULL
) UnmapViewOfFile(Address
);
1231 /* Close the file mapping object */
1232 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
1234 /* Close the file handle */
1235 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
1240 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
1243 WORD McbSegment
= FIRST_MCB_SEGMENT
;
1244 PDOS_MCB CurrentMcb
;
1245 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
1246 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
1248 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1252 /* Check if this PSP is it's own parent */
1253 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
1255 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
1257 /* Close the handle */
1261 /* Free the memory used by the process */
1264 /* Get a pointer to the MCB */
1265 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
1267 /* Make sure the MCB is valid */
1268 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
1270 /* If this block was allocated by the process, free it */
1271 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
+ 1);
1273 /* If this was the last block, quit */
1274 if (CurrentMcb
->BlockType
== 'Z') break;
1276 /* Update the segment and continue */
1277 McbSegment
+= CurrentMcb
->Size
+ 1;
1281 /* Restore the interrupt vectors */
1282 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
1283 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
1284 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
1286 /* Update the current PSP */
1287 if (Psp
== CurrentPsp
)
1289 CurrentPsp
= PspBlock
->ParentPsp
;
1290 if (CurrentPsp
== SYSTEM_PSP
) VdmRunning
= FALSE
;
1293 /* Save the return code - Normal termination */
1294 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
1296 /* Return control to the parent process */
1297 EmulatorExecute(HIWORD(PspBlock
->TerminateAddress
),
1298 LOWORD(PspBlock
->TerminateAddress
));
1301 CHAR
DosReadCharacter(VOID
)
1303 CHAR Character
= '\0';
1306 if (IsConsoleHandle(DosGetRealHandle(DOS_INPUT_HANDLE
)))
1309 Character
= LOBYTE(BiosGetCharacter());
1313 /* Use the file reading function */
1314 DosReadFile(DOS_INPUT_HANDLE
, &Character
, sizeof(CHAR
), &BytesRead
);
1320 BOOLEAN
DosCheckInput(VOID
)
1322 HANDLE Handle
= DosGetRealHandle(DOS_INPUT_HANDLE
);
1324 if (IsConsoleHandle(Handle
))
1327 return (BiosPeekCharacter() != 0xFFFF);
1332 DWORD FileSize
= GetFileSize(Handle
, &FileSizeHigh
);
1333 LONG LocationHigh
= 0;
1334 DWORD Location
= SetFilePointer(Handle
, 0, &LocationHigh
, FILE_CURRENT
);
1336 return ((Location
!= FileSize
) || (LocationHigh
!= FileSizeHigh
));
1340 VOID
DosPrintCharacter(CHAR Character
)
1344 /* Use the file writing function */
1345 DosWriteFile(DOS_OUTPUT_HANDLE
, &Character
, sizeof(CHAR
), &BytesWritten
);
1348 BOOLEAN
DosHandleIoctl(BYTE ControlCode
, WORD FileHandle
)
1350 HANDLE Handle
= DosGetRealHandle(FileHandle
);
1352 if (Handle
== INVALID_HANDLE_VALUE
)
1355 DosLastError
= ERROR_FILE_NOT_FOUND
;
1359 switch (ControlCode
)
1361 /* Get Device Information */
1366 if (Handle
== DosSystemFileTable
[0])
1371 else if (Handle
== DosSystemFileTable
[1])
1373 /* Console output */
1377 /* It is a character device */
1380 /* Return the device information word */
1385 /* Unsupported control code */
1388 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode
);
1390 DosLastError
= ERROR_INVALID_PARAMETER
;
1396 VOID
DosInt20h(LPWORD Stack
)
1398 /* This is the exit interrupt */
1399 DosTerminateProcess(Stack
[STACK_CS
], 0);
1402 VOID
DosInt21h(LPWORD Stack
)
1405 SYSTEMTIME SystemTime
;
1407 PDOS_INPUT_BUFFER InputBuffer
;
1409 /* Check the value in the AH register */
1412 /* Terminate Program */
1415 DosTerminateProcess(Stack
[STACK_CS
], 0);
1419 /* Read Character from STDIN with Echo */
1422 Character
= DosReadCharacter();
1423 DosPrintCharacter(Character
);
1425 /* Let the BOP repeat if needed */
1432 /* Write Character to STDOUT */
1435 Character
= getDL();
1436 DosPrintCharacter(Character
);
1439 * We return the output character (DOS 2.1+).
1440 * Also, if we're going to output a TAB, then
1441 * don't return a TAB but a SPACE instead.
1442 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1443 * for more information.
1445 setAL(Character
== '\t' ? ' ' : Character
);
1449 /* Read Character from STDAUX */
1452 // FIXME: Really read it from STDAUX!
1453 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1454 setAL(DosReadCharacter());
1458 /* Write Character to STDAUX */
1461 // FIXME: Really write it to STDAUX!
1462 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1463 DosPrintCharacter(getDL());
1467 /* Write Character to Printer */
1470 // FIXME: Really write it to printer!
1471 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1472 DPRINT1("0x%p\n", getDL());
1473 DPRINT1("\n\n-----------\n\n");
1477 /* Direct Console I/O */
1480 Character
= getDL();
1482 if (Character
!= 0xFF)
1485 DosPrintCharacter(Character
);
1488 * We return the output character (DOS 2.1+).
1489 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1490 * for more information.
1497 if (DosCheckInput())
1499 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
1500 setAL(DosReadCharacter());
1504 /* No character available */
1505 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
1513 /* Character Input without Echo */
1517 Character
= DosReadCharacter();
1519 /* Let the BOP repeat if needed */
1526 /* Write string to STDOUT */
1529 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1531 while (*String
!= '$')
1533 DosPrintCharacter(*String
);
1538 * We return the terminating character (DOS 2.1+).
1539 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1540 * for more information.
1546 /* Read Buffered Input */
1549 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1551 while (Stack
[STACK_COUNTER
] < InputBuffer
->MaxLength
)
1553 /* Try to read a character */
1554 Character
= DosReadCharacter();
1556 /* If it's not ready yet, let the BOP repeat */
1559 /* Echo the character and append it to the buffer */
1560 DosPrintCharacter(Character
);
1561 InputBuffer
->Buffer
[Stack
[STACK_COUNTER
]] = Character
;
1563 if (Character
== '\r') break;
1564 Stack
[STACK_COUNTER
]++;
1567 /* Update the length */
1568 InputBuffer
->Length
= Stack
[STACK_COUNTER
];
1572 /* Get STDIN Status */
1575 setAL(DosCheckInput() ? 0xFF : 0x00);
1579 /* Flush Buffer and Read STDIN */
1582 BYTE InputFunction
= getAL();
1584 /* Flush STDIN buffer */
1585 DosFlushFileBuffers(DOS_INPUT_HANDLE
); // Maybe just create a DosFlushInputBuffer...
1588 * If the input function number contained in AL is valid, i.e.
1589 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1590 * recursively with AL == AH.
1592 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1593 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1594 InputFunction
== 0x0A)
1596 setAH(InputFunction
);
1598 * Instead of calling ourselves really recursively as in:
1600 * prefer resetting the CF flag to let the BOP repeat.
1610 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1612 // TODO: Flush what's needed.
1613 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1615 /* Clear CF in DOS 6 only */
1616 if (PspBlock
->DosVersion
== 0x0006)
1617 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1622 /* Set Default Drive */
1625 DosChangeDrive(getDL());
1626 setAL(LastDrive
- 'A' + 1);
1630 /* NULL Function for CP/M Compatibility */
1634 * This function corresponds to the CP/M BDOS function
1635 * "get bit map of logged drives", which is meaningless
1638 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1639 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1640 * for more information.
1646 /* Get Default Drive */
1649 setAL(CurrentDrive
);
1653 /* Set Disk Transfer Area */
1656 DiskTransferArea
= MAKELONG(getDX(), getDS());
1660 /* NULL Function for CP/M Compatibility */
1665 * Function 0x1D corresponds to the CP/M BDOS function
1666 * "get bit map of read-only drives", which is meaningless
1668 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1669 * for more information.
1671 * Function 0x1E corresponds to the CP/M BDOS function
1672 * "set file attributes", which was meaningless under MS-DOS 1.x.
1673 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1674 * for more information.
1680 /* NULL Function for CP/M Compatibility */
1684 * This function corresponds to the CP/M BDOS function
1685 * "get/set default user (sublibrary) number", which is meaningless
1688 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1689 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1690 * for more information.
1696 /* Set Interrupt Vector */
1699 DWORD FarPointer
= MAKELONG(getDX(), getDS());
1700 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1702 /* Write the new far pointer to the IDT */
1703 ((PDWORD
)BaseAddress
)[getAL()] = FarPointer
;
1707 /* Create New PSP */
1710 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1714 /* Get System Date */
1717 GetLocalTime(&SystemTime
);
1718 setCX(SystemTime
.wYear
);
1719 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1720 setAL(SystemTime
.wDayOfWeek
);
1724 /* Set System Date */
1727 GetLocalTime(&SystemTime
);
1728 SystemTime
.wYear
= getCX();
1729 SystemTime
.wMonth
= getDH();
1730 SystemTime
.wDay
= getDL();
1732 /* Return success or failure */
1733 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1737 /* Get System Time */
1740 GetLocalTime(&SystemTime
);
1741 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1742 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1746 /* Set System Time */
1749 GetLocalTime(&SystemTime
);
1750 SystemTime
.wHour
= getCH();
1751 SystemTime
.wMinute
= getCL();
1752 SystemTime
.wSecond
= getDH();
1753 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1755 /* Return success or failure */
1756 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1760 /* Get Disk Transfer Area */
1763 setES(HIWORD(DiskTransferArea
));
1764 setBX(LOWORD(DiskTransferArea
));
1768 /* Get DOS Version */
1771 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1774 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1775 * for more information.
1778 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1781 * Return DOS OEM number:
1782 * 0x00 for IBM PC-DOS
1783 * 0x02 for packaged MS-DOS
1788 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1791 * Return version flag:
1792 * 1 << 3 if DOS is in ROM,
1793 * 0 (reserved) if not.
1798 /* Return DOS 24-bit user serial number in BL:CX */
1802 /* Return DOS version: Minor:Major in AH:AL */
1803 setAX(PspBlock
->DosVersion
);
1808 /* Get Interrupt Vector */
1811 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1813 /* Read the address from the IDT into ES:BX */
1814 setES(HIWORD(FarPointer
));
1815 setBX(LOWORD(FarPointer
));
1819 /* SWITCH character - AVAILDEV */
1822 if (getAL() == 0x00)
1825 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1826 * This setting is ignored by MS-DOS 4.0+.
1827 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1828 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1829 * for more information.
1834 else if (getAL() == 0x01)
1837 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1838 * This setting is ignored by MS-DOS 5+.
1839 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1840 * for more information.
1845 else if (getAL() == 0x02)
1848 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1849 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1850 * for more information.
1855 else if (getAL() == 0x03)
1858 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1859 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1860 * for more information.
1873 /* Create Directory */
1876 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1878 if (CreateDirectoryA(String
, NULL
))
1880 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1884 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1885 setAX(LOWORD(GetLastError()));
1891 /* Remove Directory */
1894 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1896 if (RemoveDirectoryA(String
))
1898 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1902 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1903 setAX(LOWORD(GetLastError()));
1909 /* Set Current Directory */
1912 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1914 if (DosChangeDirectory(String
))
1916 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1920 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1921 setAX(DosLastError
);
1931 WORD ErrorCode
= DosCreateFile(&FileHandle
,
1932 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1937 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1942 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1953 WORD ErrorCode
= DosOpenFile(&FileHandle
,
1954 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1959 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1964 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1974 if (DosCloseHandle(getBX()))
1976 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1980 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1981 setAX(ERROR_INVALID_HANDLE
);
1987 /* Read from File or Device */
1990 WORD Handle
= getBX();
1991 LPBYTE Buffer
= (LPBYTE
)SEG_OFF_TO_PTR(getDS(), getDX());
1992 WORD Count
= getCX();
1994 WORD ErrorCode
= ERROR_SUCCESS
;
1996 if (IsConsoleHandle(DosGetRealHandle(Handle
)))
1998 while (Stack
[STACK_COUNTER
] < Count
)
2000 /* Read a character from the BIOS */
2001 // FIXME: Security checks!
2002 Buffer
[Stack
[STACK_COUNTER
]] = LOBYTE(BiosGetCharacter());
2004 /* Stop if the BOP needs to be repeated */
2007 /* Increment the counter */
2008 Stack
[STACK_COUNTER
]++;
2011 if (Stack
[STACK_COUNTER
] < Count
)
2012 ErrorCode
= ERROR_NOT_READY
;
2018 /* Use the file reading function */
2019 ErrorCode
= DosReadFile(Handle
, Buffer
, Count
, &BytesRead
);
2022 if (ErrorCode
== ERROR_SUCCESS
)
2024 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2027 else if (ErrorCode
!= ERROR_NOT_READY
)
2029 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2035 /* Write to File or Device */
2038 WORD BytesWritten
= 0;
2039 WORD ErrorCode
= DosWriteFile(getBX(),
2040 SEG_OFF_TO_PTR(getDS(), getDX()),
2044 if (ErrorCode
== ERROR_SUCCESS
)
2046 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2047 setAX(BytesWritten
);
2051 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2061 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2063 /* Call the API function */
2064 if (DeleteFileA(FileName
))
2066 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2068 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2069 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2071 setAL(FileName
[0] - 'A');
2075 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2076 setAX(GetLastError());
2086 WORD ErrorCode
= DosSeekFile(getBX(),
2087 MAKELONG(getDX(), getCX()),
2091 if (ErrorCode
== ERROR_SUCCESS
)
2093 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2095 /* Return the new offset in DX:AX */
2096 setDX(HIWORD(NewLocation
));
2097 setAX(LOWORD(NewLocation
));
2101 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2108 /* Get/Set File Attributes */
2112 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2114 if (getAL() == 0x00)
2116 /* Get the attributes */
2117 Attributes
= GetFileAttributesA(FileName
);
2119 /* Check if it failed */
2120 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
2122 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2123 setAX(GetLastError());
2127 /* Return the attributes that DOS can understand */
2128 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2129 setCX(Attributes
& 0x00FF);
2132 else if (getAL() == 0x01)
2134 /* Try to set the attributes */
2135 if (SetFileAttributesA(FileName
, getCL()))
2137 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2141 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2142 setAX(GetLastError());
2147 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2148 setAX(ERROR_INVALID_FUNCTION
);
2157 if (DosHandleIoctl(getAL(), getBX()))
2159 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2163 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2164 setAX(DosLastError
);
2170 /* Duplicate Handle */
2174 HANDLE Handle
= DosGetRealHandle(getBX());
2176 if (Handle
!= INVALID_HANDLE_VALUE
)
2178 /* The handle is invalid */
2179 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2180 setAX(ERROR_INVALID_HANDLE
);
2184 /* Open a new handle to the same entry */
2185 NewHandle
= DosOpenHandle(Handle
);
2187 if (NewHandle
== INVALID_DOS_HANDLE
)
2189 /* Too many files open */
2190 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2191 setAX(ERROR_TOO_MANY_OPEN_FILES
);
2195 /* Return the result */
2196 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2201 /* Force Duplicate Handle */
2204 if (DosDuplicateHandle(getBX(), getCX()))
2206 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2210 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2211 setAX(ERROR_INVALID_HANDLE
);
2217 /* Get Current Directory */
2220 BYTE DriveNumber
= getDL();
2221 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
2223 /* Get the real drive number */
2224 if (DriveNumber
== 0)
2226 DriveNumber
= CurrentDrive
;
2230 /* Decrement DriveNumber since it was 1-based */
2234 if (DriveNumber
<= LastDrive
- 'A')
2237 * Copy the current directory into the target buffer.
2238 * It doesn't contain the drive letter and the backslash.
2240 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
2241 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2242 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2246 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2247 setAX(ERROR_INVALID_DRIVE
);
2253 /* Allocate Memory */
2256 WORD MaxAvailable
= 0;
2257 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
2261 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2266 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2267 setAX(DosLastError
);
2268 setBX(MaxAvailable
);
2277 if (DosFreeMemory(getES()))
2279 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2283 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2284 setAX(ERROR_ARENA_TRASHED
);
2290 /* Resize Memory Block */
2295 if (DosResizeMemory(getES(), getBX(), &Size
))
2297 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2301 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2302 setAX(DosLastError
);
2309 /* Terminate With Return Code */
2312 DosTerminateProcess(CurrentPsp
, getAL());
2316 /* Get Return Code (ERRORLEVEL) */
2320 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2321 * DosErrorLevel is cleared after being read by this function.
2323 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2324 setAX(DosErrorLevel
);
2325 DosErrorLevel
= 0x0000; // Clear it
2329 /* Internal - Set Current Process ID (Set PSP Address) */
2332 // FIXME: Is it really what it's done ??
2333 CurrentPsp
= getBX();
2337 /* Internal - Get Current Process ID (Get PSP Address) */
2339 /* Get Current PSP Address */
2343 * Undocumented AH=51h is identical to the documented AH=62h.
2344 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2345 * and http://www.ctyme.com/intr/rb-3140.htm
2346 * for more information.
2352 /* Get/Set Memory Management Options */
2355 if (getAL() == 0x00)
2357 /* Get allocation strategy */
2358 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2359 setAX(DosAllocStrategy
);
2361 else if (getAL() == 0x01)
2363 /* Set allocation strategy */
2365 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2366 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2368 /* Can't set both */
2369 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2370 setAX(ERROR_INVALID_PARAMETER
);
2374 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2376 /* Invalid allocation strategy */
2377 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2378 setAX(ERROR_INVALID_PARAMETER
);
2382 DosAllocStrategy
= getBL();
2383 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2385 else if (getAL() == 0x02)
2387 /* Get UMB link state */
2388 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2389 setAL(DosUmbLinked
? 0x01 : 0x00);
2391 else if (getAL() == 0x03)
2393 /* Set UMB link state */
2394 if (getBX()) DosLinkUmb();
2395 else DosUnlinkUmb();
2396 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2400 /* Invalid or unsupported function */
2401 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2402 setAX(ERROR_INVALID_FUNCTION
);
2411 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2413 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2418 VOID
DosBreakInterrupt(LPWORD Stack
)
2420 UNREFERENCED_PARAMETER(Stack
);
2425 BOOLEAN
DosInitialize(VOID
)
2428 PDOS_MCB Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
);
2431 LPWSTR SourcePtr
, Environment
;
2433 LPSTR DestPtr
= (LPSTR
)SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK
, 0);
2435 CHAR CurrentDirectory
[MAX_PATH
];
2436 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2439 /* Initialize the MCB */
2440 Mcb
->BlockType
= 'Z';
2441 Mcb
->Size
= USER_MEMORY_SIZE
;
2444 /* Initialize the link MCB to the UMB area */
2445 Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
+ 1);
2446 Mcb
->BlockType
= 'M';
2447 Mcb
->Size
= UMB_START_SEGMENT
- FIRST_MCB_SEGMENT
- USER_MEMORY_SIZE
- 2;
2448 Mcb
->OwnerPsp
= SYSTEM_PSP
;
2450 /* Initialize the UMB area */
2451 Mcb
= SEGMENT_TO_MCB(UMB_START_SEGMENT
);
2452 Mcb
->BlockType
= 'Z';
2453 Mcb
->Size
= UMB_END_SEGMENT
- UMB_START_SEGMENT
;
2456 /* Get the environment strings */
2457 SourcePtr
= Environment
= GetEnvironmentStringsW();
2458 if (Environment
== NULL
) return FALSE
;
2460 /* Fill the DOS system environment block */
2463 /* Get the size of the ASCII string */
2464 AsciiSize
= WideCharToMultiByte(CP_ACP
,
2473 /* Allocate memory for the ASCII string */
2474 AsciiString
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, AsciiSize
);
2475 if (AsciiString
== NULL
)
2477 FreeEnvironmentStringsW(Environment
);
2481 /* Convert to ASCII */
2482 WideCharToMultiByte(CP_ACP
,
2491 /* Copy the string into DOS memory */
2492 strcpy(DestPtr
, AsciiString
);
2494 /* Move to the next string */
2495 SourcePtr
+= wcslen(SourcePtr
) + 1;
2496 DestPtr
+= strlen(AsciiString
);
2499 /* Free the memory */
2500 HeapFree(GetProcessHeap(), 0, AsciiString
);
2504 /* Free the memory allocated for environment strings */
2505 FreeEnvironmentStringsW(Environment
);
2507 /* Clear the current directory buffer */
2508 ZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2510 /* Get the current directory */
2511 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2513 // TODO: Use some kind of default path?
2517 /* Convert that to a DOS path */
2518 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2520 // TODO: Use some kind of default path?
2525 CurrentDrive
= DosDirectory
[0] - 'A';
2527 /* Get the directory part of the path */
2528 Path
= strchr(DosDirectory
, '\\');
2531 /* Skip the backslash */
2535 /* Set the directory */
2538 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
2541 /* Read CONFIG.SYS */
2542 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
2545 while (fgetws(Buffer
, 256, Stream
))
2547 // TODO: Parse the line
2552 /* Initialize the SFT */
2553 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
2555 DosSystemFileTable
[i
] = INVALID_HANDLE_VALUE
;
2556 DosSftRefCount
[i
] = 0;
2559 /* Get handles to standard I/O devices */
2560 DosSystemFileTable
[0] = GetStdHandle(STD_INPUT_HANDLE
);
2561 DosSystemFileTable
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
2562 DosSystemFileTable
[2] = GetStdHandle(STD_ERROR_HANDLE
);