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 *******************************************************************/
18 #include "registers.h"
20 /* PRIVATE VARIABLES **********************************************************/
22 static WORD CurrentPsp
= SYSTEM_PSP
;
23 static WORD DosLastError
= 0;
24 static DWORD DiskTransferArea
;
25 static BYTE CurrentDrive
;
26 static CHAR LastDrive
= 'E';
27 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
28 static HANDLE DosSystemFileTable
[DOS_SFT_SIZE
];
29 static WORD DosSftRefCount
[DOS_SFT_SIZE
];
30 static BYTE DosAllocStrategy
= DOS_ALLOC_BEST_FIT
;
31 static BOOLEAN DosUmbLinked
= FALSE
;
32 static WORD DosErrorLevel
= 0x0000;
34 /* PRIVATE FUNCTIONS **********************************************************/
36 /* Taken from base/shell/cmd/console.c */
37 static BOOL
IsConsoleHandle(HANDLE hHandle
)
41 /* Check whether the handle may be that of a console... */
42 if ((GetFileType(hHandle
) & FILE_TYPE_CHAR
) == 0) return FALSE
;
45 * It may be. Perform another test... The idea comes from the
46 * MSDN description of the WriteConsole API:
48 * "WriteConsole fails if it is used with a standard handle
49 * that is redirected to a file. If an application processes
50 * multilingual output that can be redirected, determine whether
51 * the output handle is a console handle (one method is to call
52 * the GetConsoleMode function and check whether it succeeds).
53 * If the handle is a console handle, call WriteConsole. If the
54 * handle is not a console handle, the output is redirected and
55 * you should call WriteFile to perform the I/O."
57 return GetConsoleMode(hHandle
, &dwMode
);
60 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
62 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
64 /* If this is the last block or it's not free, quit */
65 if (CurrentMcb
->BlockType
== 'Z' || CurrentMcb
->OwnerPsp
!= 0) return;
69 /* Get a pointer to the next MCB */
70 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
72 /* Check if the next MCB is free */
73 if (NextMcb
->OwnerPsp
== 0)
76 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
77 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
78 NextMcb
->BlockType
= 'I';
82 /* No more adjoining free blocks */
88 static WORD
DosCopyEnvironmentBlock(WORD SourceSegment
, LPCSTR ProgramName
)
90 PCHAR Ptr
, SourceBuffer
, DestBuffer
= NULL
;
94 Ptr
= SourceBuffer
= (PCHAR
)SEG_OFF_TO_PTR(SourceSegment
, 0);
96 /* Calculate the size of the environment block */
99 TotalSize
+= strlen(Ptr
) + 1;
100 Ptr
+= strlen(Ptr
) + 1;
104 /* Add the string buffer size */
105 TotalSize
+= strlen(ProgramName
) + 1;
107 /* Allocate the memory for the environment block */
108 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
109 if (!DestSegment
) return 0;
113 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
116 /* Copy the string */
117 strcpy(DestBuffer
, Ptr
);
119 /* Advance to the next string */
120 DestBuffer
+= strlen(Ptr
);
121 Ptr
+= strlen(Ptr
) + 1;
123 /* Put a zero after the string */
127 /* Set the final zero */
130 /* Copy the program name after the environment block */
131 strcpy(DestBuffer
, ProgramName
);
136 static VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
138 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
140 /* Just set the owner */
141 Mcb
->OwnerPsp
= NewOwner
;
144 static WORD
DosOpenHandle(HANDLE Handle
)
151 /* The system PSP has no handle table */
152 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_DOS_HANDLE
;
154 /* Get a pointer to the handle table */
155 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
156 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
158 /* Find a free entry in the JFT */
159 for (DosHandle
= 0; DosHandle
< PspBlock
->HandleTableSize
; DosHandle
++)
161 if (HandleTable
[DosHandle
] == 0xFF) break;
164 /* If there are no free entries, fail */
165 if (DosHandle
== PspBlock
->HandleTableSize
) return INVALID_DOS_HANDLE
;
167 /* Check if the handle is already in the SFT */
168 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
170 /* Check if this is the same handle */
171 if (DosSystemFileTable
[i
] != Handle
) continue;
173 /* Already in the table, reference it */
176 /* Set the JFT entry to that SFT index */
177 HandleTable
[DosHandle
] = i
;
179 /* Return the new handle */
183 /* Add the handle to the SFT */
184 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
186 /* Make sure this is an empty table entry */
187 if (DosSystemFileTable
[i
] != INVALID_HANDLE_VALUE
) continue;
189 /* Initialize the empty table entry */
190 DosSystemFileTable
[i
] = Handle
;
191 DosSftRefCount
[i
] = 1;
193 /* Set the JFT entry to that SFT index */
194 HandleTable
[DosHandle
] = i
;
196 /* Return the new handle */
200 /* The SFT is full */
201 return INVALID_DOS_HANDLE
;
204 static HANDLE
DosGetRealHandle(WORD DosHandle
)
209 /* The system PSP has no handle table */
210 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_HANDLE_VALUE
;
212 /* Get a pointer to the handle table */
213 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
214 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
216 /* Make sure the handle is open */
217 if (HandleTable
[DosHandle
] == 0xFF) return INVALID_HANDLE_VALUE
;
219 /* Return the Win32 handle */
220 return DosSystemFileTable
[HandleTable
[DosHandle
]];
223 static VOID
DosCopyHandleTable(LPBYTE DestinationTable
)
229 /* Clear the table first */
230 for (i
= 0; i
< 20; i
++) DestinationTable
[i
] = 0xFF;
232 /* Check if this is the initial process */
233 if (CurrentPsp
== SYSTEM_PSP
)
235 /* Set up the standard I/O devices */
236 for (i
= 0; i
<= 2; i
++)
238 /* Set the index in the SFT */
239 DestinationTable
[i
] = (BYTE
)i
;
241 /* Increase the reference count */
249 /* Get the parent PSP block and handle table */
250 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
251 SourceTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
253 /* Copy the first 20 handles into the new table */
254 for (i
= 0; i
< 20; i
++)
256 DestinationTable
[i
] = SourceTable
[i
];
258 /* Increase the reference count */
259 DosSftRefCount
[SourceTable
[i
]]++;
263 /* PUBLIC FUNCTIONS ***********************************************************/
265 WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
267 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
, MaxSize
= 0;
268 PDOS_MCB CurrentMcb
, NextMcb
;
269 BOOLEAN SearchUmb
= FALSE
;
271 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
273 if (DosUmbLinked
&& (DosAllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
275 /* Search UMB first */
276 Segment
= UMB_START_SEGMENT
;
282 /* Get a pointer to the MCB */
283 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
285 /* Make sure it's valid */
286 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
288 DPRINT("The DOS memory arena is corrupted!\n");
289 DosLastError
= ERROR_ARENA_TRASHED
;
293 /* Only check free blocks */
294 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
296 /* Combine this free block with adjoining free blocks */
297 DosCombineFreeBlocks(Segment
);
299 /* Update the maximum block size */
300 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
302 /* Check if this block is big enough */
303 if (CurrentMcb
->Size
< Size
) goto Next
;
305 switch (DosAllocStrategy
& 0x3F)
307 case DOS_ALLOC_FIRST_FIT
:
309 /* For first fit, stop immediately */
314 case DOS_ALLOC_BEST_FIT
:
316 /* For best fit, update the smallest block found so far */
317 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
325 case DOS_ALLOC_LAST_FIT
:
327 /* For last fit, make the current block the result, but keep searching */
334 /* If this was the last MCB in the chain, quit */
335 if (CurrentMcb
->BlockType
== 'Z')
337 /* Check if nothing was found while searching through UMBs */
338 if ((Result
== 0) && SearchUmb
&& (DosAllocStrategy
& DOS_ALLOC_HIGH_LOW
))
340 /* Search low memory */
341 Segment
= FIRST_MCB_SEGMENT
;
348 /* Otherwise, update the segment and continue */
349 Segment
+= CurrentMcb
->Size
+ 1;
354 /* If we didn't find a free block, return 0 */
357 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
358 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
362 /* Get a pointer to the MCB */
363 CurrentMcb
= SEGMENT_TO_MCB(Result
);
365 /* Check if the block is larger than requested */
366 if (CurrentMcb
->Size
> Size
)
368 /* It is, split it into two blocks */
369 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
371 /* Initialize the new MCB structure */
372 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
373 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
374 NextMcb
->OwnerPsp
= 0;
376 /* Update the current block */
377 CurrentMcb
->BlockType
= 'M';
378 CurrentMcb
->Size
= Size
;
381 /* Take ownership of the block */
382 CurrentMcb
->OwnerPsp
= CurrentPsp
;
384 /* Return the segment of the data portion of the block */
388 BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
390 BOOLEAN Success
= TRUE
;
391 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
392 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
394 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
398 /* Make sure this is a valid, allocated block */
399 if ((Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') || Mcb
->OwnerPsp
== 0)
402 DosLastError
= ERROR_INVALID_HANDLE
;
406 ReturnSize
= Mcb
->Size
;
408 /* Check if we need to expand or contract the block */
409 if (NewSize
> Mcb
->Size
)
411 /* We can't expand the last block */
412 if (Mcb
->BlockType
!= 'M')
418 /* Get the pointer and segment of the next MCB */
419 NextSegment
= Segment
+ Mcb
->Size
+ 1;
420 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
422 /* Make sure the next segment is free */
423 if (NextMcb
->OwnerPsp
!= 0)
425 DPRINT("Cannot expand memory block: next segment is not free!\n");
426 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
431 /* Combine this free block with adjoining free blocks */
432 DosCombineFreeBlocks(NextSegment
);
434 /* Set the maximum possible size of the block */
435 ReturnSize
+= NextMcb
->Size
+ 1;
437 /* Maximize the current block */
438 Mcb
->Size
= ReturnSize
;
439 Mcb
->BlockType
= NextMcb
->BlockType
;
441 /* Invalidate the next block */
442 NextMcb
->BlockType
= 'I';
444 /* Check if the block is larger than requested */
445 if (Mcb
->Size
> NewSize
)
447 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
451 /* It is, split it into two blocks */
452 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
454 /* Initialize the new MCB structure */
455 NextMcb
->BlockType
= Mcb
->BlockType
;
456 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
457 NextMcb
->OwnerPsp
= 0;
459 /* Update the current block */
460 Mcb
->BlockType
= 'M';
464 else if (NewSize
< Mcb
->Size
)
466 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
470 /* Just split the block */
471 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
472 NextMcb
->BlockType
= Mcb
->BlockType
;
473 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
474 NextMcb
->OwnerPsp
= 0;
477 Mcb
->BlockType
= 'M';
482 /* Check if the operation failed */
485 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
488 /* Return the maximum possible size */
489 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
495 BOOLEAN
DosFreeMemory(WORD BlockData
)
497 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
499 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
501 /* Make sure the MCB is valid */
502 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z')
504 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
508 /* Mark the block as free */
514 BOOLEAN
DosLinkUmb(VOID
)
516 DWORD Segment
= FIRST_MCB_SEGMENT
;
517 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
519 DPRINT("Linking UMB\n");
521 /* Check if UMBs are already linked */
522 if (DosUmbLinked
) return FALSE
;
524 /* Find the last block */
525 while ((Mcb
->BlockType
== 'M') && (Segment
<= 0xFFFF))
527 Segment
+= Mcb
->Size
+ 1;
528 Mcb
= SEGMENT_TO_MCB(Segment
);
531 /* Make sure it's valid */
532 if (Mcb
->BlockType
!= 'Z') return FALSE
;
534 /* Connect the MCB with the UMB chain */
535 Mcb
->BlockType
= 'M';
541 BOOLEAN
DosUnlinkUmb(VOID
)
543 DWORD Segment
= FIRST_MCB_SEGMENT
;
544 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
546 DPRINT("Unlinking UMB\n");
548 /* Check if UMBs are already unlinked */
549 if (!DosUmbLinked
) return FALSE
;
551 /* Find the block preceding the MCB that links it with the UMB chain */
552 while (Segment
<= 0xFFFF)
554 if ((Segment
+ Mcb
->Size
) == (FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
))
556 /* This is the last non-UMB segment */
560 /* Advance to the next MCB */
561 Segment
+= Mcb
->Size
+ 1;
562 Mcb
= SEGMENT_TO_MCB(Segment
);
565 /* Mark the MCB as the last MCB */
566 Mcb
->BlockType
= 'Z';
568 DosUmbLinked
= FALSE
;
572 WORD
DosCreateFile(LPWORD Handle
, LPCSTR FilePath
, WORD Attributes
)
577 DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
581 /* Create the file */
582 FileHandle
= CreateFileA(FilePath
,
583 GENERIC_READ
| GENERIC_WRITE
,
584 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
590 if (FileHandle
== INVALID_HANDLE_VALUE
)
592 /* Return the error code */
593 return (WORD
)GetLastError();
596 /* Open the DOS handle */
597 DosHandle
= DosOpenHandle(FileHandle
);
599 if (DosHandle
== INVALID_DOS_HANDLE
)
601 /* Close the handle */
602 CloseHandle(FileHandle
);
604 /* Return the error code */
605 return ERROR_TOO_MANY_OPEN_FILES
;
608 /* It was successful */
610 return ERROR_SUCCESS
;
613 WORD
DosOpenFile(LPWORD Handle
, LPCSTR FilePath
, BYTE AccessMode
)
616 ACCESS_MASK Access
= 0;
619 DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
623 /* Parse the access mode */
624 switch (AccessMode
& 3)
629 Access
= GENERIC_READ
;
636 Access
= GENERIC_WRITE
;
643 Access
= GENERIC_READ
| GENERIC_WRITE
;
650 return ERROR_INVALID_PARAMETER
;
655 FileHandle
= CreateFileA(FilePath
,
657 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
660 FILE_ATTRIBUTE_NORMAL
,
663 if (FileHandle
== INVALID_HANDLE_VALUE
)
665 /* Return the error code */
666 return (WORD
)GetLastError();
669 /* Open the DOS handle */
670 DosHandle
= DosOpenHandle(FileHandle
);
672 if (DosHandle
== INVALID_DOS_HANDLE
)
674 /* Close the handle */
675 CloseHandle(FileHandle
);
677 /* Return the error code */
678 return ERROR_TOO_MANY_OPEN_FILES
;
681 /* It was successful */
683 return ERROR_SUCCESS
;
686 WORD
DosReadFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesRead
)
688 WORD Result
= ERROR_SUCCESS
;
689 DWORD BytesRead32
= 0;
690 HANDLE Handle
= DosGetRealHandle(FileHandle
);
692 DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle
, Count
);
694 /* Make sure the handle is valid */
695 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
698 if (!ReadFile(Handle
, Buffer
, Count
, &BytesRead32
, NULL
))
700 /* Store the error code */
701 Result
= (WORD
)GetLastError();
704 /* The number of bytes read is always 16-bit */
705 *BytesRead
= LOWORD(BytesRead32
);
707 /* Return the error code */
711 WORD
DosWriteFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesWritten
)
713 WORD Result
= ERROR_SUCCESS
;
714 DWORD BytesWritten32
= 0;
715 HANDLE Handle
= DosGetRealHandle(FileHandle
);
718 DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
722 /* Make sure the handle is valid */
723 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
725 if (IsConsoleHandle(Handle
))
727 for (i
= 0; i
< Count
; i
++)
729 /* Call the BIOS to print the character */
730 BiosPrintCharacter(((LPBYTE
)Buffer
)[i
], DOS_CHAR_ATTRIBUTE
, Bda
->VideoPage
);
737 if (!WriteFile(Handle
, Buffer
, Count
, &BytesWritten32
, NULL
))
739 /* Store the error code */
740 Result
= (WORD
)GetLastError();
744 /* The number of bytes written is always 16-bit */
745 *BytesWritten
= LOWORD(BytesWritten32
);
747 /* Return the error code */
751 WORD
DosSeekFile(WORD FileHandle
, LONG Offset
, BYTE Origin
, LPDWORD NewOffset
)
753 WORD Result
= ERROR_SUCCESS
;
755 HANDLE Handle
= DosGetRealHandle(FileHandle
);
757 DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n",
762 /* Make sure the handle is valid */
763 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
765 /* Check if the origin is valid */
766 if (Origin
!= FILE_BEGIN
&& Origin
!= FILE_CURRENT
&& Origin
!= FILE_END
)
768 return ERROR_INVALID_FUNCTION
;
771 /* Move the file pointer */
772 FilePointer
= SetFilePointer(Handle
, Offset
, NULL
, Origin
);
774 /* Check if there's a possibility the operation failed */
775 if (FilePointer
== INVALID_SET_FILE_POINTER
)
777 /* Get the real error code */
778 Result
= (WORD
)GetLastError();
781 if (Result
!= ERROR_SUCCESS
)
783 /* The operation did fail */
787 /* Return the file pointer, if requested */
788 if (NewOffset
) *NewOffset
= FilePointer
;
791 return ERROR_SUCCESS
;
794 BOOLEAN
DosFlushFileBuffers(WORD FileHandle
)
796 HANDLE Handle
= DosGetRealHandle(FileHandle
);
798 /* Make sure the handle is valid */
799 if (Handle
== INVALID_HANDLE_VALUE
) return FALSE
;
802 * No need to check whether the handle is a console handle since
803 * FlushFileBuffers() automatically does this check and calls
804 * FlushConsoleInputBuffer() for us.
806 // if (IsConsoleHandle(Handle))
807 // return (BOOLEAN)FlushConsoleInputBuffer(Handle);
809 return (BOOLEAN
)FlushFileBuffers(Handle
);
812 BOOLEAN
DosDuplicateHandle(WORD OldHandle
, WORD NewHandle
)
818 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
822 /* The system PSP has no handle table */
823 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
825 /* Get a pointer to the handle table */
826 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
827 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
829 /* Make sure the old handle is open */
830 if (HandleTable
[OldHandle
] == 0xFF) return FALSE
;
832 /* Check if the new handle is open */
833 if (HandleTable
[NewHandle
] != 0xFF)
836 DosCloseHandle(NewHandle
);
839 /* Increment the reference count of the SFT entry */
840 SftIndex
= HandleTable
[OldHandle
];
841 DosSftRefCount
[SftIndex
]++;
843 /* Make the new handle point to that SFT entry */
844 HandleTable
[NewHandle
] = SftIndex
;
850 BOOLEAN
DosCloseHandle(WORD DosHandle
)
856 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle
);
858 /* The system PSP has no handle table */
859 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
861 /* Get a pointer to the handle table */
862 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
863 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
865 /* Make sure the handle is open */
866 if (HandleTable
[DosHandle
] == 0xFF) return FALSE
;
868 /* Decrement the reference count of the SFT entry */
869 SftIndex
= HandleTable
[DosHandle
];
870 DosSftRefCount
[SftIndex
]--;
872 /* Check if the reference count fell to zero */
873 if (!DosSftRefCount
[SftIndex
])
875 /* Close the file, it's no longer needed */
876 CloseHandle(DosSystemFileTable
[SftIndex
]);
878 /* Clear the handle */
879 DosSystemFileTable
[SftIndex
] = INVALID_HANDLE_VALUE
;
882 /* Clear the entry in the JFT */
883 HandleTable
[DosHandle
] = 0xFF;
888 BOOLEAN
DosChangeDrive(BYTE Drive
)
890 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
892 /* Make sure the drive exists */
893 if (Drive
> (LastDrive
- 'A')) return FALSE
;
895 /* Find the path to the new current directory */
896 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
898 /* Change the current directory of the process */
899 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
901 /* Set the current drive */
902 CurrentDrive
= Drive
;
908 BOOLEAN
DosChangeDirectory(LPSTR Directory
)
914 /* Make sure the directory path is not too long */
915 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
917 DosLastError
= ERROR_PATH_NOT_FOUND
;
921 /* Get the drive number */
922 DriveNumber
= Directory
[0] - 'A';
924 /* Make sure the drive exists */
925 if (DriveNumber
> (LastDrive
- 'A'))
927 DosLastError
= ERROR_PATH_NOT_FOUND
;
931 /* Get the file attributes */
932 Attributes
= GetFileAttributesA(Directory
);
934 /* Make sure the path exists and is a directory */
935 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
936 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
938 DosLastError
= ERROR_PATH_NOT_FOUND
;
942 /* Check if this is the current drive */
943 if (DriveNumber
== CurrentDrive
)
945 /* Change the directory */
946 if (!SetCurrentDirectoryA(Directory
))
948 DosLastError
= LOWORD(GetLastError());
953 /* Get the directory part of the path */
954 Path
= strchr(Directory
, '\\');
957 /* Skip the backslash */
961 /* Set the directory for the drive */
964 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
968 CurrentDirectories
[DriveNumber
][0] = '\0';
975 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
977 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
978 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
980 ZeroMemory(PspBlock
, sizeof(DOS_PSP
));
982 /* Set the exit interrupt */
983 PspBlock
->Exit
[0] = 0xCD; // int 0x20
984 PspBlock
->Exit
[1] = 0x20;
986 /* Set the number of the last paragraph */
987 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
989 /* Save the interrupt vectors */
990 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
991 PspBlock
->BreakAddress
= IntVecTable
[0x23];
992 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
994 /* Set the parent PSP */
995 PspBlock
->ParentPsp
= CurrentPsp
;
997 /* Copy the parent handle table */
998 DosCopyHandleTable(PspBlock
->HandleTable
);
1000 /* Set the environment block */
1001 PspBlock
->EnvBlock
= Environment
;
1003 /* Set the handle table pointers to the internal handle table */
1004 PspBlock
->HandleTableSize
= 20;
1005 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
1007 /* Set the DOS version */
1008 PspBlock
->DosVersion
= DOS_VERSION
;
1010 /* Set the far call opcodes */
1011 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
1012 PspBlock
->FarCall
[1] = 0x21;
1013 PspBlock
->FarCall
[2] = 0xCB; // retf
1015 /* Set the command line */
1016 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
1017 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
1018 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
1021 BOOLEAN
DosCreateProcess(LPCSTR CommandLine
, WORD EnvBlock
)
1023 BOOLEAN Success
= FALSE
, AllocatedEnvBlock
= FALSE
;
1024 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
1025 LPBYTE Address
= NULL
;
1026 LPSTR ProgramFilePath
, Parameters
[256];
1027 CHAR CommandLineCopy
[DOS_CMDLINE_LENGTH
];
1028 CHAR ParamString
[DOS_CMDLINE_LENGTH
];
1032 DWORD i
, FileSize
, ExeSize
;
1033 PIMAGE_DOS_HEADER Header
;
1034 PDWORD RelocationTable
;
1037 DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n",
1041 /* Save a copy of the command line */
1042 strcpy(CommandLineCopy
, CommandLine
);
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 ZeroMemory(ParamString
, sizeof(ParamString
));
1056 /* Store the parameters in a string */
1057 for (i
= 0; i
< ParamCount
; i
++)
1059 strncat(ParamString
, Parameters
[i
], DOS_CMDLINE_LENGTH
- strlen(ParamString
) - 1);
1060 strncat(ParamString
, " ", DOS_CMDLINE_LENGTH
- strlen(ParamString
) - 1);
1063 /* Open a handle to the executable */
1064 FileHandle
= CreateFileA(ProgramFilePath
,
1069 FILE_ATTRIBUTE_NORMAL
,
1071 if (FileHandle
== INVALID_HANDLE_VALUE
) goto Cleanup
;
1073 /* Get the file size */
1074 FileSize
= GetFileSize(FileHandle
, NULL
);
1076 /* Create a mapping object for the file */
1077 FileMapping
= CreateFileMapping(FileHandle
,
1083 if (FileMapping
== NULL
) goto Cleanup
;
1085 /* Map the file into memory */
1086 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
1087 if (Address
== NULL
) goto Cleanup
;
1089 /* Did we get an environment segment? */
1092 /* Set a flag to know if the environment block was allocated here */
1093 AllocatedEnvBlock
= TRUE
;
1095 /* No, copy the one from the parent */
1096 EnvBlock
= DosCopyEnvironmentBlock((CurrentPsp
!= SYSTEM_PSP
)
1097 ? SEGMENT_TO_PSP(CurrentPsp
)->EnvBlock
1102 /* Check if this is an EXE file or a COM file */
1103 if (Address
[0] == 'M' && Address
[1] == 'Z')
1107 /* Get the MZ header */
1108 Header
= (PIMAGE_DOS_HEADER
)Address
;
1110 /* Get the base size of the file, in paragraphs (rounded up) */
1111 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
1113 /* Add the PSP size, in paragraphs */
1114 ExeSize
+= sizeof(DOS_PSP
) >> 4;
1116 /* Add the maximum size that should be allocated */
1117 ExeSize
+= Header
->e_maxalloc
;
1119 /* Make sure it does not pass 0xFFFF */
1120 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
1122 /* Reduce the size one by one until the allocation is successful */
1123 for (i
= Header
->e_maxalloc
; i
>= Header
->e_minalloc
; i
--, ExeSize
--)
1125 /* Try to allocate that much memory */
1126 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
1127 if (Segment
!= 0) break;
1130 /* Check if at least the lowest allocation was successful */
1131 if (Segment
== 0) goto Cleanup
;
1133 /* Initialize the PSP */
1134 DosInitializePsp(Segment
,
1139 /* The process owns its own memory */
1140 DosChangeMemoryOwner(Segment
, Segment
);
1141 DosChangeMemoryOwner(EnvBlock
, Segment
);
1143 /* Copy the program to Segment:0100 */
1144 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1145 Address
+ (Header
->e_cparhdr
<< 4),
1146 min(FileSize
- (Header
->e_cparhdr
<< 4),
1147 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
1149 /* Get the relocation table */
1150 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
1152 /* Perform relocations */
1153 for (i
= 0; i
< Header
->e_crlc
; i
++)
1155 /* Get a pointer to the word that needs to be patched */
1156 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
1157 0x100 + LOWORD(RelocationTable
[i
]));
1159 /* Add the number of the EXE segment to it */
1160 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
1163 /* Set the initial segment registers */
1167 /* Set the stack to the location from the header */
1168 EmulatorSetStack(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
,
1172 CurrentPsp
= Segment
;
1173 DiskTransferArea
= MAKELONG(0x80, Segment
);
1174 EmulatorExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
1183 /* Find the maximum amount of memory that can be allocated */
1184 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
1186 /* Make sure it's enough for the whole program and the PSP */
1187 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
))) goto Cleanup
;
1189 /* Allocate all of it */
1190 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
1191 if (Segment
== 0) goto Cleanup
;
1193 /* The process owns its own memory */
1194 DosChangeMemoryOwner(Segment
, Segment
);
1195 DosChangeMemoryOwner(EnvBlock
, Segment
);
1197 /* Copy the program to Segment:0100 */
1198 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1202 /* Initialize the PSP */
1203 DosInitializePsp(Segment
,
1208 /* Set the initial segment registers */
1212 /* Set the stack to the last word of the segment */
1213 EmulatorSetStack(Segment
, 0xFFFE);
1216 * Set the value on the stack to 0, so that a near return
1217 * jumps to PSP:0000 which has the exit code.
1219 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
1222 CurrentPsp
= Segment
;
1223 DiskTransferArea
= MAKELONG(0x80, Segment
);
1224 EmulatorExecute(Segment
, 0x100);
1232 /* It was not successful, cleanup the DOS memory */
1233 if (AllocatedEnvBlock
) DosFreeMemory(EnvBlock
);
1234 if (Segment
) DosFreeMemory(Segment
);
1238 if (Address
!= NULL
) UnmapViewOfFile(Address
);
1240 /* Close the file mapping object */
1241 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
1243 /* Close the file handle */
1244 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
1249 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
1252 WORD McbSegment
= FIRST_MCB_SEGMENT
;
1253 PDOS_MCB CurrentMcb
;
1254 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
1255 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
1257 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1261 /* Check if this PSP is it's own parent */
1262 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
1264 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
1266 /* Close the handle */
1270 /* Free the memory used by the process */
1273 /* Get a pointer to the MCB */
1274 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
1276 /* Make sure the MCB is valid */
1277 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
1279 /* If this block was allocated by the process, free it */
1280 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
+ 1);
1282 /* If this was the last block, quit */
1283 if (CurrentMcb
->BlockType
== 'Z') break;
1285 /* Update the segment and continue */
1286 McbSegment
+= CurrentMcb
->Size
+ 1;
1290 /* Restore the interrupt vectors */
1291 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
1292 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
1293 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
1295 /* Update the current PSP */
1296 if (Psp
== CurrentPsp
)
1298 CurrentPsp
= PspBlock
->ParentPsp
;
1299 if (CurrentPsp
== SYSTEM_PSP
) VdmRunning
= FALSE
;
1302 /* Save the return code - Normal termination */
1303 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
1305 /* Return control to the parent process */
1306 EmulatorExecute(HIWORD(PspBlock
->TerminateAddress
),
1307 LOWORD(PspBlock
->TerminateAddress
));
1310 CHAR
DosReadCharacter(VOID
)
1312 CHAR Character
= '\0';
1315 if (IsConsoleHandle(DosGetRealHandle(DOS_INPUT_HANDLE
)))
1318 Character
= LOBYTE(BiosGetCharacter());
1322 /* Use the file reading function */
1323 DosReadFile(DOS_INPUT_HANDLE
, &Character
, sizeof(CHAR
), &BytesRead
);
1329 BOOLEAN
DosCheckInput(VOID
)
1331 HANDLE Handle
= DosGetRealHandle(DOS_INPUT_HANDLE
);
1333 if (IsConsoleHandle(Handle
))
1336 return (BiosPeekCharacter() != 0xFFFF);
1341 DWORD FileSize
= GetFileSize(Handle
, &FileSizeHigh
);
1342 LONG LocationHigh
= 0;
1343 DWORD Location
= SetFilePointer(Handle
, 0, &LocationHigh
, FILE_CURRENT
);
1345 return ((Location
!= FileSize
) || (LocationHigh
!= FileSizeHigh
));
1349 VOID
DosPrintCharacter(CHAR Character
)
1353 /* Use the file writing function */
1354 DosWriteFile(DOS_OUTPUT_HANDLE
, &Character
, sizeof(CHAR
), &BytesWritten
);
1357 BOOLEAN
DosHandleIoctl(BYTE ControlCode
, WORD FileHandle
)
1359 HANDLE Handle
= DosGetRealHandle(FileHandle
);
1361 if (Handle
== INVALID_HANDLE_VALUE
)
1364 DosLastError
= ERROR_FILE_NOT_FOUND
;
1368 switch (ControlCode
)
1370 /* Get Device Information */
1375 if (Handle
== DosSystemFileTable
[0])
1380 else if (Handle
== DosSystemFileTable
[1])
1382 /* Console output */
1386 /* It is a character device */
1389 /* Return the device information word */
1394 /* Unsupported control code */
1397 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode
);
1399 DosLastError
= ERROR_INVALID_PARAMETER
;
1405 VOID WINAPI
DosInt20h(LPWORD Stack
)
1407 /* This is the exit interrupt */
1408 DosTerminateProcess(Stack
[STACK_CS
], 0);
1411 VOID WINAPI
DosInt21h(LPWORD Stack
)
1414 SYSTEMTIME SystemTime
;
1416 PDOS_INPUT_BUFFER InputBuffer
;
1418 /* Check the value in the AH register */
1421 /* Terminate Program */
1424 DosTerminateProcess(Stack
[STACK_CS
], 0);
1428 /* Read Character from STDIN with Echo */
1431 Character
= DosReadCharacter();
1432 DosPrintCharacter(Character
);
1434 /* Let the BOP repeat if needed */
1441 /* Write Character to STDOUT */
1444 Character
= getDL();
1445 DosPrintCharacter(Character
);
1448 * We return the output character (DOS 2.1+).
1449 * Also, if we're going to output a TAB, then
1450 * don't return a TAB but a SPACE instead.
1451 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1452 * for more information.
1454 setAL(Character
== '\t' ? ' ' : Character
);
1458 /* Read Character from STDAUX */
1461 // FIXME: Really read it from STDAUX!
1462 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1463 setAL(DosReadCharacter());
1467 /* Write Character to STDAUX */
1470 // FIXME: Really write it to STDAUX!
1471 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1472 DosPrintCharacter(getDL());
1476 /* Write Character to Printer */
1479 // FIXME: Really write it to printer!
1480 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1481 DPRINT1("0x%p\n", getDL());
1482 DPRINT1("\n\n-----------\n\n");
1486 /* Direct Console I/O */
1489 Character
= getDL();
1491 if (Character
!= 0xFF)
1494 DosPrintCharacter(Character
);
1497 * We return the output character (DOS 2.1+).
1498 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1499 * for more information.
1506 if (DosCheckInput())
1508 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
1509 setAL(DosReadCharacter());
1513 /* No character available */
1514 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
1522 /* Character Input without Echo */
1526 Character
= DosReadCharacter();
1528 /* Let the BOP repeat if needed */
1535 /* Write string to STDOUT */
1538 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1540 while (*String
!= '$')
1542 DosPrintCharacter(*String
);
1547 * We return the terminating character (DOS 2.1+).
1548 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1549 * for more information.
1555 /* Read Buffered Input */
1558 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1560 while (Stack
[STACK_COUNTER
] < InputBuffer
->MaxLength
)
1562 /* Try to read a character */
1563 Character
= DosReadCharacter();
1565 /* If it's not ready yet, let the BOP repeat */
1568 /* Echo the character and append it to the buffer */
1569 DosPrintCharacter(Character
);
1570 InputBuffer
->Buffer
[Stack
[STACK_COUNTER
]] = Character
;
1572 if (Character
== '\r') break;
1573 Stack
[STACK_COUNTER
]++;
1576 /* Update the length */
1577 InputBuffer
->Length
= Stack
[STACK_COUNTER
];
1581 /* Get STDIN Status */
1584 setAL(DosCheckInput() ? 0xFF : 0x00);
1588 /* Flush Buffer and Read STDIN */
1591 BYTE InputFunction
= getAL();
1593 /* Flush STDIN buffer */
1594 DosFlushFileBuffers(DOS_INPUT_HANDLE
); // Maybe just create a DosFlushInputBuffer...
1597 * If the input function number contained in AL is valid, i.e.
1598 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1599 * recursively with AL == AH.
1601 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1602 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1603 InputFunction
== 0x0A)
1605 setAH(InputFunction
);
1607 * Instead of calling ourselves really recursively as in:
1609 * prefer resetting the CF flag to let the BOP repeat.
1619 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1621 // TODO: Flush what's needed.
1622 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1624 /* Clear CF in DOS 6 only */
1625 if (PspBlock
->DosVersion
== 0x0006)
1626 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1631 /* Set Default Drive */
1634 DosChangeDrive(getDL());
1635 setAL(LastDrive
- 'A' + 1);
1639 /* NULL Function for CP/M Compatibility */
1643 * This function corresponds to the CP/M BDOS function
1644 * "get bit map of logged drives", which is meaningless
1647 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1648 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1649 * for more information.
1655 /* Get Default Drive */
1658 setAL(CurrentDrive
);
1662 /* Set Disk Transfer Area */
1665 DiskTransferArea
= MAKELONG(getDX(), getDS());
1669 /* NULL Function for CP/M Compatibility */
1674 * Function 0x1D corresponds to the CP/M BDOS function
1675 * "get bit map of read-only drives", which is meaningless
1677 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1678 * for more information.
1680 * Function 0x1E corresponds to the CP/M BDOS function
1681 * "set file attributes", which was meaningless under MS-DOS 1.x.
1682 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1683 * for more information.
1689 /* NULL Function for CP/M Compatibility */
1693 * This function corresponds to the CP/M BDOS function
1694 * "get/set default user (sublibrary) number", which is meaningless
1697 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1698 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1699 * for more information.
1705 /* Set Interrupt Vector */
1708 DWORD FarPointer
= MAKELONG(getDX(), getDS());
1709 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1711 /* Write the new far pointer to the IDT */
1712 ((PDWORD
)BaseAddress
)[getAL()] = FarPointer
;
1716 /* Create New PSP */
1719 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1723 /* Get System Date */
1726 GetLocalTime(&SystemTime
);
1727 setCX(SystemTime
.wYear
);
1728 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1729 setAL(SystemTime
.wDayOfWeek
);
1733 /* Set System Date */
1736 GetLocalTime(&SystemTime
);
1737 SystemTime
.wYear
= getCX();
1738 SystemTime
.wMonth
= getDH();
1739 SystemTime
.wDay
= getDL();
1741 /* Return success or failure */
1742 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1746 /* Get System Time */
1749 GetLocalTime(&SystemTime
);
1750 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1751 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1755 /* Set System Time */
1758 GetLocalTime(&SystemTime
);
1759 SystemTime
.wHour
= getCH();
1760 SystemTime
.wMinute
= getCL();
1761 SystemTime
.wSecond
= getDH();
1762 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1764 /* Return success or failure */
1765 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1769 /* Get Disk Transfer Area */
1772 setES(HIWORD(DiskTransferArea
));
1773 setBX(LOWORD(DiskTransferArea
));
1777 /* Get DOS Version */
1780 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1783 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1784 * for more information.
1787 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1790 * Return DOS OEM number:
1791 * 0x00 for IBM PC-DOS
1792 * 0x02 for packaged MS-DOS
1797 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1800 * Return version flag:
1801 * 1 << 3 if DOS is in ROM,
1802 * 0 (reserved) if not.
1807 /* Return DOS 24-bit user serial number in BL:CX */
1811 /* Return DOS version: Minor:Major in AH:AL */
1812 setAX(PspBlock
->DosVersion
);
1817 /* Get Interrupt Vector */
1820 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1822 /* Read the address from the IDT into ES:BX */
1823 setES(HIWORD(FarPointer
));
1824 setBX(LOWORD(FarPointer
));
1828 /* SWITCH character - AVAILDEV */
1831 if (getAL() == 0x00)
1834 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1835 * This setting is ignored by MS-DOS 4.0+.
1836 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1837 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1838 * for more information.
1843 else if (getAL() == 0x01)
1846 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1847 * This setting is ignored by MS-DOS 5+.
1848 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1849 * for more information.
1854 else if (getAL() == 0x02)
1857 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1858 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1859 * for more information.
1864 else if (getAL() == 0x03)
1867 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1868 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1869 * for more information.
1882 /* Create Directory */
1885 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1887 if (CreateDirectoryA(String
, NULL
))
1889 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1893 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1894 setAX(LOWORD(GetLastError()));
1900 /* Remove Directory */
1903 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1905 if (RemoveDirectoryA(String
))
1907 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1911 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1912 setAX(LOWORD(GetLastError()));
1918 /* Set Current Directory */
1921 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1923 if (DosChangeDirectory(String
))
1925 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1929 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1930 setAX(DosLastError
);
1940 WORD ErrorCode
= DosCreateFile(&FileHandle
,
1941 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1946 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1951 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1962 WORD ErrorCode
= DosOpenFile(&FileHandle
,
1963 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1968 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1973 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1983 if (DosCloseHandle(getBX()))
1985 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1989 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1990 setAX(ERROR_INVALID_HANDLE
);
1996 /* Read from File or Device */
1999 WORD Handle
= getBX();
2000 LPBYTE Buffer
= (LPBYTE
)SEG_OFF_TO_PTR(getDS(), getDX());
2001 WORD Count
= getCX();
2003 WORD ErrorCode
= ERROR_SUCCESS
;
2005 if (IsConsoleHandle(DosGetRealHandle(Handle
)))
2007 while (Stack
[STACK_COUNTER
] < Count
)
2009 /* Read a character from the BIOS */
2010 // FIXME: Security checks!
2011 Buffer
[Stack
[STACK_COUNTER
]] = LOBYTE(BiosGetCharacter());
2013 /* Stop if the BOP needs to be repeated */
2016 /* Increment the counter */
2017 Stack
[STACK_COUNTER
]++;
2020 if (Stack
[STACK_COUNTER
] < Count
)
2021 ErrorCode
= ERROR_NOT_READY
;
2027 /* Use the file reading function */
2028 ErrorCode
= DosReadFile(Handle
, Buffer
, Count
, &BytesRead
);
2031 if (ErrorCode
== ERROR_SUCCESS
)
2033 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2036 else if (ErrorCode
!= ERROR_NOT_READY
)
2038 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2044 /* Write to File or Device */
2047 WORD BytesWritten
= 0;
2048 WORD ErrorCode
= DosWriteFile(getBX(),
2049 SEG_OFF_TO_PTR(getDS(), getDX()),
2053 if (ErrorCode
== ERROR_SUCCESS
)
2055 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2056 setAX(BytesWritten
);
2060 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2070 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2072 /* Call the API function */
2073 if (DeleteFileA(FileName
))
2075 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2077 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2078 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2080 setAL(FileName
[0] - 'A');
2084 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2085 setAX(GetLastError());
2095 WORD ErrorCode
= DosSeekFile(getBX(),
2096 MAKELONG(getDX(), getCX()),
2100 if (ErrorCode
== ERROR_SUCCESS
)
2102 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2104 /* Return the new offset in DX:AX */
2105 setDX(HIWORD(NewLocation
));
2106 setAX(LOWORD(NewLocation
));
2110 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2117 /* Get/Set File Attributes */
2121 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2123 if (getAL() == 0x00)
2125 /* Get the attributes */
2126 Attributes
= GetFileAttributesA(FileName
);
2128 /* Check if it failed */
2129 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
2131 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2132 setAX(GetLastError());
2136 /* Return the attributes that DOS can understand */
2137 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2138 setCX(Attributes
& 0x00FF);
2141 else if (getAL() == 0x01)
2143 /* Try to set the attributes */
2144 if (SetFileAttributesA(FileName
, getCL()))
2146 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2150 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2151 setAX(GetLastError());
2156 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2157 setAX(ERROR_INVALID_FUNCTION
);
2166 if (DosHandleIoctl(getAL(), getBX()))
2168 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2172 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2173 setAX(DosLastError
);
2179 /* Duplicate Handle */
2183 HANDLE Handle
= DosGetRealHandle(getBX());
2185 if (Handle
!= INVALID_HANDLE_VALUE
)
2187 /* The handle is invalid */
2188 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2189 setAX(ERROR_INVALID_HANDLE
);
2193 /* Open a new handle to the same entry */
2194 NewHandle
= DosOpenHandle(Handle
);
2196 if (NewHandle
== INVALID_DOS_HANDLE
)
2198 /* Too many files open */
2199 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2200 setAX(ERROR_TOO_MANY_OPEN_FILES
);
2204 /* Return the result */
2205 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2210 /* Force Duplicate Handle */
2213 if (DosDuplicateHandle(getBX(), getCX()))
2215 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2219 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2220 setAX(ERROR_INVALID_HANDLE
);
2226 /* Get Current Directory */
2229 BYTE DriveNumber
= getDL();
2230 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
2232 /* Get the real drive number */
2233 if (DriveNumber
== 0)
2235 DriveNumber
= CurrentDrive
;
2239 /* Decrement DriveNumber since it was 1-based */
2243 if (DriveNumber
<= LastDrive
- 'A')
2246 * Copy the current directory into the target buffer.
2247 * It doesn't contain the drive letter and the backslash.
2249 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
2250 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2251 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2255 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2256 setAX(ERROR_INVALID_DRIVE
);
2262 /* Allocate Memory */
2265 WORD MaxAvailable
= 0;
2266 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
2270 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2275 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2276 setAX(DosLastError
);
2277 setBX(MaxAvailable
);
2286 if (DosFreeMemory(getES()))
2288 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2292 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2293 setAX(ERROR_ARENA_TRASHED
);
2299 /* Resize Memory Block */
2304 if (DosResizeMemory(getES(), getBX(), &Size
))
2306 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2310 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2311 setAX(DosLastError
);
2318 /* Terminate With Return Code */
2321 DosTerminateProcess(CurrentPsp
, getAL());
2325 /* Get Return Code (ERRORLEVEL) */
2329 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2330 * DosErrorLevel is cleared after being read by this function.
2332 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2333 setAX(DosErrorLevel
);
2334 DosErrorLevel
= 0x0000; // Clear it
2338 /* Internal - Set Current Process ID (Set PSP Address) */
2341 // FIXME: Is it really what it's done ??
2342 CurrentPsp
= getBX();
2346 /* Internal - Get Current Process ID (Get PSP Address) */
2348 /* Get Current PSP Address */
2352 * Undocumented AH=51h is identical to the documented AH=62h.
2353 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2354 * and http://www.ctyme.com/intr/rb-3140.htm
2355 * for more information.
2361 /* Get/Set Memory Management Options */
2364 if (getAL() == 0x00)
2366 /* Get allocation strategy */
2367 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2368 setAX(DosAllocStrategy
);
2370 else if (getAL() == 0x01)
2372 /* Set allocation strategy */
2374 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2375 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2377 /* Can't set both */
2378 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2379 setAX(ERROR_INVALID_PARAMETER
);
2383 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2385 /* Invalid allocation strategy */
2386 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2387 setAX(ERROR_INVALID_PARAMETER
);
2391 DosAllocStrategy
= getBL();
2392 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2394 else if (getAL() == 0x02)
2396 /* Get UMB link state */
2397 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2398 setAL(DosUmbLinked
? 0x01 : 0x00);
2400 else if (getAL() == 0x03)
2402 /* Set UMB link state */
2403 if (getBX()) DosLinkUmb();
2404 else DosUnlinkUmb();
2405 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2409 /* Invalid or unsupported function */
2410 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2411 setAX(ERROR_INVALID_FUNCTION
);
2420 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2423 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2424 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2429 VOID WINAPI
DosBreakInterrupt(LPWORD Stack
)
2431 UNREFERENCED_PARAMETER(Stack
);
2436 VOID WINAPI
DosInt2Fh(LPWORD Stack
)
2438 DPRINT1("DOS System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2440 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2443 BOOLEAN
DosInitialize(VOID
)
2446 PDOS_MCB Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
);
2449 LPWSTR SourcePtr
, Environment
;
2451 LPSTR DestPtr
= (LPSTR
)SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK
, 0);
2453 CHAR CurrentDirectory
[MAX_PATH
];
2454 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2457 /* Initialize the MCB */
2458 Mcb
->BlockType
= 'Z';
2459 Mcb
->Size
= USER_MEMORY_SIZE
;
2462 /* Initialize the link MCB to the UMB area */
2463 Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
+ 1);
2464 Mcb
->BlockType
= 'M';
2465 Mcb
->Size
= UMB_START_SEGMENT
- FIRST_MCB_SEGMENT
- USER_MEMORY_SIZE
- 2;
2466 Mcb
->OwnerPsp
= SYSTEM_PSP
;
2468 /* Initialize the UMB area */
2469 Mcb
= SEGMENT_TO_MCB(UMB_START_SEGMENT
);
2470 Mcb
->BlockType
= 'Z';
2471 Mcb
->Size
= UMB_END_SEGMENT
- UMB_START_SEGMENT
;
2474 /* Get the environment strings */
2475 SourcePtr
= Environment
= GetEnvironmentStringsW();
2476 if (Environment
== NULL
) return FALSE
;
2478 /* Fill the DOS system environment block */
2481 /* Get the size of the ASCII string */
2482 AsciiSize
= WideCharToMultiByte(CP_ACP
,
2491 /* Allocate memory for the ASCII string */
2492 AsciiString
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, AsciiSize
);
2493 if (AsciiString
== NULL
)
2495 FreeEnvironmentStringsW(Environment
);
2499 /* Convert to ASCII */
2500 WideCharToMultiByte(CP_ACP
,
2509 /* Copy the string into DOS memory */
2510 strcpy(DestPtr
, AsciiString
);
2512 /* Move to the next string */
2513 SourcePtr
+= wcslen(SourcePtr
) + 1;
2514 DestPtr
+= strlen(AsciiString
);
2517 /* Free the memory */
2518 HeapFree(GetProcessHeap(), 0, AsciiString
);
2522 /* Free the memory allocated for environment strings */
2523 FreeEnvironmentStringsW(Environment
);
2525 /* Clear the current directory buffer */
2526 ZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2528 /* Get the current directory */
2529 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2531 // TODO: Use some kind of default path?
2535 /* Convert that to a DOS path */
2536 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2538 // TODO: Use some kind of default path?
2543 CurrentDrive
= DosDirectory
[0] - 'A';
2545 /* Get the directory part of the path */
2546 Path
= strchr(DosDirectory
, '\\');
2549 /* Skip the backslash */
2553 /* Set the directory */
2556 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
2559 /* Read CONFIG.SYS */
2560 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
2563 while (fgetws(Buffer
, 256, Stream
))
2565 // TODO: Parse the line
2570 /* Initialize the SFT */
2571 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
2573 DosSystemFileTable
[i
] = INVALID_HANDLE_VALUE
;
2574 DosSftRefCount
[i
] = 0;
2577 /* Get handles to standard I/O devices */
2578 DosSystemFileTable
[0] = GetStdHandle(STD_INPUT_HANDLE
);
2579 DosSystemFileTable
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
2580 DosSystemFileTable
[2] = GetStdHandle(STD_ERROR_HANDLE
);
2582 /* Register the DOS 32-bit Interrupts */
2583 RegisterInt32(0x20, DosInt20h
);
2584 RegisterInt32(0x21, DosInt21h
);
2585 // RegisterInt32(0x22, DosInt22h ); // Termination
2586 RegisterInt32(0x23, DosBreakInterrupt
); // Ctrl-C / Ctrl-Break
2587 // RegisterInt32(0x24, DosInt24h ); // Critical Error
2588 RegisterInt32(0x2F, DosInt2Fh
);