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 *******************************************************************/
19 #include "registers.h"
21 /* PRIVATE VARIABLES **********************************************************/
23 static WORD CurrentPsp
= SYSTEM_PSP
;
24 static WORD DosLastError
= 0;
25 static DWORD DiskTransferArea
;
26 static BYTE CurrentDrive
;
27 static CHAR LastDrive
= 'E';
28 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
29 static HANDLE DosSystemFileTable
[DOS_SFT_SIZE
];
30 static WORD DosSftRefCount
[DOS_SFT_SIZE
];
31 static BYTE DosAllocStrategy
= DOS_ALLOC_BEST_FIT
;
32 static BOOLEAN DosUmbLinked
= FALSE
;
33 static WORD DosErrorLevel
= 0x0000;
35 /* PRIVATE FUNCTIONS **********************************************************/
37 /* Taken from base/shell/cmd/console.c */
38 static BOOL
IsConsoleHandle(HANDLE hHandle
)
42 /* Check whether the handle may be that of a console... */
43 if ((GetFileType(hHandle
) & FILE_TYPE_CHAR
) == 0) return FALSE
;
46 * It may be. Perform another test... The idea comes from the
47 * MSDN description of the WriteConsole API:
49 * "WriteConsole fails if it is used with a standard handle
50 * that is redirected to a file. If an application processes
51 * multilingual output that can be redirected, determine whether
52 * the output handle is a console handle (one method is to call
53 * the GetConsoleMode function and check whether it succeeds).
54 * If the handle is a console handle, call WriteConsole. If the
55 * handle is not a console handle, the output is redirected and
56 * you should call WriteFile to perform the I/O."
58 return GetConsoleMode(hHandle
, &dwMode
);
61 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
63 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
65 /* If this is the last block or it's not free, quit */
66 if (CurrentMcb
->BlockType
== 'Z' || CurrentMcb
->OwnerPsp
!= 0) return;
70 /* Get a pointer to the next MCB */
71 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
73 /* Check if the next MCB is free */
74 if (NextMcb
->OwnerPsp
== 0)
77 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
78 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
79 NextMcb
->BlockType
= 'I';
83 /* No more adjoining free blocks */
89 static WORD
DosCopyEnvironmentBlock(WORD SourceSegment
, LPCSTR ProgramName
)
91 PCHAR Ptr
, SourceBuffer
, DestBuffer
= NULL
;
95 Ptr
= SourceBuffer
= (PCHAR
)SEG_OFF_TO_PTR(SourceSegment
, 0);
97 /* Calculate the size of the environment block */
100 TotalSize
+= strlen(Ptr
) + 1;
101 Ptr
+= strlen(Ptr
) + 1;
105 /* Add the string buffer size */
106 TotalSize
+= strlen(ProgramName
) + 1;
108 /* Allocate the memory for the environment block */
109 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
110 if (!DestSegment
) return 0;
114 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
117 /* Copy the string */
118 strcpy(DestBuffer
, Ptr
);
120 /* Advance to the next string */
121 DestBuffer
+= strlen(Ptr
);
122 Ptr
+= strlen(Ptr
) + 1;
124 /* Put a zero after the string */
128 /* Set the final zero */
131 /* Copy the program name after the environment block */
132 strcpy(DestBuffer
, ProgramName
);
137 static VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
139 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
141 /* Just set the owner */
142 Mcb
->OwnerPsp
= NewOwner
;
145 static WORD
DosOpenHandle(HANDLE Handle
)
152 /* The system PSP has no handle table */
153 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_DOS_HANDLE
;
155 /* Get a pointer to the handle table */
156 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
157 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
159 /* Find a free entry in the JFT */
160 for (DosHandle
= 0; DosHandle
< PspBlock
->HandleTableSize
; DosHandle
++)
162 if (HandleTable
[DosHandle
] == 0xFF) break;
165 /* If there are no free entries, fail */
166 if (DosHandle
== PspBlock
->HandleTableSize
) return INVALID_DOS_HANDLE
;
168 /* Check if the handle is already in the SFT */
169 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
171 /* Check if this is the same handle */
172 if (DosSystemFileTable
[i
] != Handle
) continue;
174 /* Already in the table, reference it */
177 /* Set the JFT entry to that SFT index */
178 HandleTable
[DosHandle
] = i
;
180 /* Return the new handle */
184 /* Add the handle to the SFT */
185 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
187 /* Make sure this is an empty table entry */
188 if (DosSystemFileTable
[i
] != INVALID_HANDLE_VALUE
) continue;
190 /* Initialize the empty table entry */
191 DosSystemFileTable
[i
] = Handle
;
192 DosSftRefCount
[i
] = 1;
194 /* Set the JFT entry to that SFT index */
195 HandleTable
[DosHandle
] = i
;
197 /* Return the new handle */
201 /* The SFT is full */
202 return INVALID_DOS_HANDLE
;
205 static HANDLE
DosGetRealHandle(WORD DosHandle
)
210 /* The system PSP has no handle table */
211 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_HANDLE_VALUE
;
213 /* Get a pointer to the handle table */
214 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
215 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
217 /* Make sure the handle is open */
218 if (HandleTable
[DosHandle
] == 0xFF) return INVALID_HANDLE_VALUE
;
220 /* Return the Win32 handle */
221 return DosSystemFileTable
[HandleTable
[DosHandle
]];
224 static VOID
DosCopyHandleTable(LPBYTE DestinationTable
)
230 /* Clear the table first */
231 for (i
= 0; i
< 20; i
++) DestinationTable
[i
] = 0xFF;
233 /* Check if this is the initial process */
234 if (CurrentPsp
== SYSTEM_PSP
)
236 /* Set up the standard I/O devices */
237 for (i
= 0; i
<= 2; i
++)
239 /* Set the index in the SFT */
240 DestinationTable
[i
] = (BYTE
)i
;
242 /* Increase the reference count */
250 /* Get the parent PSP block and handle table */
251 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
252 SourceTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
254 /* Copy the first 20 handles into the new table */
255 for (i
= 0; i
< 20; i
++)
257 DestinationTable
[i
] = SourceTable
[i
];
259 /* Increase the reference count */
260 DosSftRefCount
[SourceTable
[i
]]++;
264 /* PUBLIC FUNCTIONS ***********************************************************/
266 WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
268 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
, MaxSize
= 0;
269 PDOS_MCB CurrentMcb
, NextMcb
;
270 BOOLEAN SearchUmb
= FALSE
;
272 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
274 if (DosUmbLinked
&& (DosAllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
276 /* Search UMB first */
277 Segment
= UMB_START_SEGMENT
;
283 /* Get a pointer to the MCB */
284 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
286 /* Make sure it's valid */
287 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
289 DPRINT("The DOS memory arena is corrupted!\n");
290 DosLastError
= ERROR_ARENA_TRASHED
;
294 /* Only check free blocks */
295 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
297 /* Combine this free block with adjoining free blocks */
298 DosCombineFreeBlocks(Segment
);
300 /* Update the maximum block size */
301 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
303 /* Check if this block is big enough */
304 if (CurrentMcb
->Size
< Size
) goto Next
;
306 switch (DosAllocStrategy
& 0x3F)
308 case DOS_ALLOC_FIRST_FIT
:
310 /* For first fit, stop immediately */
315 case DOS_ALLOC_BEST_FIT
:
317 /* For best fit, update the smallest block found so far */
318 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
326 case DOS_ALLOC_LAST_FIT
:
328 /* For last fit, make the current block the result, but keep searching */
335 /* If this was the last MCB in the chain, quit */
336 if (CurrentMcb
->BlockType
== 'Z')
338 /* Check if nothing was found while searching through UMBs */
339 if ((Result
== 0) && SearchUmb
&& (DosAllocStrategy
& DOS_ALLOC_HIGH_LOW
))
341 /* Search low memory */
342 Segment
= FIRST_MCB_SEGMENT
;
349 /* Otherwise, update the segment and continue */
350 Segment
+= CurrentMcb
->Size
+ 1;
355 /* If we didn't find a free block, return 0 */
358 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
359 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
363 /* Get a pointer to the MCB */
364 CurrentMcb
= SEGMENT_TO_MCB(Result
);
366 /* Check if the block is larger than requested */
367 if (CurrentMcb
->Size
> Size
)
369 /* It is, split it into two blocks */
370 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
372 /* Initialize the new MCB structure */
373 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
374 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
375 NextMcb
->OwnerPsp
= 0;
377 /* Update the current block */
378 CurrentMcb
->BlockType
= 'M';
379 CurrentMcb
->Size
= Size
;
382 /* Take ownership of the block */
383 CurrentMcb
->OwnerPsp
= CurrentPsp
;
385 /* Return the segment of the data portion of the block */
389 BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
391 BOOLEAN Success
= TRUE
;
392 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
393 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
395 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
399 /* Make sure this is a valid, allocated block */
400 if ((Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') || Mcb
->OwnerPsp
== 0)
403 DosLastError
= ERROR_INVALID_HANDLE
;
407 ReturnSize
= Mcb
->Size
;
409 /* Check if we need to expand or contract the block */
410 if (NewSize
> Mcb
->Size
)
412 /* We can't expand the last block */
413 if (Mcb
->BlockType
!= 'M')
419 /* Get the pointer and segment of the next MCB */
420 NextSegment
= Segment
+ Mcb
->Size
+ 1;
421 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
423 /* Make sure the next segment is free */
424 if (NextMcb
->OwnerPsp
!= 0)
426 DPRINT("Cannot expand memory block: next segment is not free!\n");
427 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
432 /* Combine this free block with adjoining free blocks */
433 DosCombineFreeBlocks(NextSegment
);
435 /* Set the maximum possible size of the block */
436 ReturnSize
+= NextMcb
->Size
+ 1;
438 /* Maximize the current block */
439 Mcb
->Size
= ReturnSize
;
440 Mcb
->BlockType
= NextMcb
->BlockType
;
442 /* Invalidate the next block */
443 NextMcb
->BlockType
= 'I';
445 /* Check if the block is larger than requested */
446 if (Mcb
->Size
> NewSize
)
448 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
452 /* It is, split it into two blocks */
453 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
455 /* Initialize the new MCB structure */
456 NextMcb
->BlockType
= Mcb
->BlockType
;
457 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
458 NextMcb
->OwnerPsp
= 0;
460 /* Update the current block */
461 Mcb
->BlockType
= 'M';
465 else if (NewSize
< Mcb
->Size
)
467 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
471 /* Just split the block */
472 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
473 NextMcb
->BlockType
= Mcb
->BlockType
;
474 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
475 NextMcb
->OwnerPsp
= 0;
478 Mcb
->BlockType
= 'M';
483 /* Check if the operation failed */
486 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
489 /* Return the maximum possible size */
490 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
496 BOOLEAN
DosFreeMemory(WORD BlockData
)
498 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
500 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
502 /* Make sure the MCB is valid */
503 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z')
505 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
509 /* Mark the block as free */
515 BOOLEAN
DosLinkUmb(VOID
)
517 DWORD Segment
= FIRST_MCB_SEGMENT
;
518 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
520 DPRINT("Linking UMB\n");
522 /* Check if UMBs are already linked */
523 if (DosUmbLinked
) return FALSE
;
525 /* Find the last block */
526 while ((Mcb
->BlockType
== 'M') && (Segment
<= 0xFFFF))
528 Segment
+= Mcb
->Size
+ 1;
529 Mcb
= SEGMENT_TO_MCB(Segment
);
532 /* Make sure it's valid */
533 if (Mcb
->BlockType
!= 'Z') return FALSE
;
535 /* Connect the MCB with the UMB chain */
536 Mcb
->BlockType
= 'M';
542 BOOLEAN
DosUnlinkUmb(VOID
)
544 DWORD Segment
= FIRST_MCB_SEGMENT
;
545 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
547 DPRINT("Unlinking UMB\n");
549 /* Check if UMBs are already unlinked */
550 if (!DosUmbLinked
) return FALSE
;
552 /* Find the block preceding the MCB that links it with the UMB chain */
553 while (Segment
<= 0xFFFF)
555 if ((Segment
+ Mcb
->Size
) == (FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
))
557 /* This is the last non-UMB segment */
561 /* Advance to the next MCB */
562 Segment
+= Mcb
->Size
+ 1;
563 Mcb
= SEGMENT_TO_MCB(Segment
);
566 /* Mark the MCB as the last MCB */
567 Mcb
->BlockType
= 'Z';
569 DosUmbLinked
= FALSE
;
573 WORD
DosCreateFile(LPWORD Handle
, LPCSTR FilePath
, WORD Attributes
)
578 DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
582 /* Create the file */
583 FileHandle
= CreateFileA(FilePath
,
584 GENERIC_READ
| GENERIC_WRITE
,
585 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
591 if (FileHandle
== INVALID_HANDLE_VALUE
)
593 /* Return the error code */
594 return (WORD
)GetLastError();
597 /* Open the DOS handle */
598 DosHandle
= DosOpenHandle(FileHandle
);
600 if (DosHandle
== INVALID_DOS_HANDLE
)
602 /* Close the handle */
603 CloseHandle(FileHandle
);
605 /* Return the error code */
606 return ERROR_TOO_MANY_OPEN_FILES
;
609 /* It was successful */
611 return ERROR_SUCCESS
;
614 WORD
DosOpenFile(LPWORD Handle
, LPCSTR FilePath
, BYTE AccessMode
)
617 ACCESS_MASK Access
= 0;
620 DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
624 /* Parse the access mode */
625 switch (AccessMode
& 3)
630 Access
= GENERIC_READ
;
637 Access
= GENERIC_WRITE
;
644 Access
= GENERIC_READ
| GENERIC_WRITE
;
651 return ERROR_INVALID_PARAMETER
;
656 FileHandle
= CreateFileA(FilePath
,
658 FILE_SHARE_READ
| FILE_SHARE_WRITE
| FILE_SHARE_DELETE
,
661 FILE_ATTRIBUTE_NORMAL
,
664 if (FileHandle
== INVALID_HANDLE_VALUE
)
666 /* Return the error code */
667 return (WORD
)GetLastError();
670 /* Open the DOS handle */
671 DosHandle
= DosOpenHandle(FileHandle
);
673 if (DosHandle
== INVALID_DOS_HANDLE
)
675 /* Close the handle */
676 CloseHandle(FileHandle
);
678 /* Return the error code */
679 return ERROR_TOO_MANY_OPEN_FILES
;
682 /* It was successful */
684 return ERROR_SUCCESS
;
687 WORD
DosReadFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesRead
)
689 WORD Result
= ERROR_SUCCESS
;
690 DWORD BytesRead32
= 0;
691 HANDLE Handle
= DosGetRealHandle(FileHandle
);
693 DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle
, Count
);
695 /* Make sure the handle is valid */
696 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
699 if (!ReadFile(Handle
, Buffer
, Count
, &BytesRead32
, NULL
))
701 /* Store the error code */
702 Result
= (WORD
)GetLastError();
705 /* The number of bytes read is always 16-bit */
706 *BytesRead
= LOWORD(BytesRead32
);
708 /* Return the error code */
712 WORD
DosWriteFile(WORD FileHandle
, LPVOID Buffer
, WORD Count
, LPWORD BytesWritten
)
714 WORD Result
= ERROR_SUCCESS
;
715 DWORD BytesWritten32
= 0;
716 HANDLE Handle
= DosGetRealHandle(FileHandle
);
719 DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
723 /* Make sure the handle is valid */
724 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
726 if (IsConsoleHandle(Handle
))
728 for (i
= 0; i
< Count
; i
++)
730 /* Call the BIOS to print the character */
731 BiosPrintCharacter(((LPBYTE
)Buffer
)[i
], DOS_CHAR_ATTRIBUTE
, Bda
->VideoPage
);
738 if (!WriteFile(Handle
, Buffer
, Count
, &BytesWritten32
, NULL
))
740 /* Store the error code */
741 Result
= (WORD
)GetLastError();
745 /* The number of bytes written is always 16-bit */
746 *BytesWritten
= LOWORD(BytesWritten32
);
748 /* Return the error code */
752 WORD
DosSeekFile(WORD FileHandle
, LONG Offset
, BYTE Origin
, LPDWORD NewOffset
)
754 WORD Result
= ERROR_SUCCESS
;
756 HANDLE Handle
= DosGetRealHandle(FileHandle
);
758 DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n",
763 /* Make sure the handle is valid */
764 if (Handle
== INVALID_HANDLE_VALUE
) return ERROR_INVALID_HANDLE
;
766 /* Check if the origin is valid */
767 if (Origin
!= FILE_BEGIN
&& Origin
!= FILE_CURRENT
&& Origin
!= FILE_END
)
769 return ERROR_INVALID_FUNCTION
;
772 /* Move the file pointer */
773 FilePointer
= SetFilePointer(Handle
, Offset
, NULL
, Origin
);
775 /* Check if there's a possibility the operation failed */
776 if (FilePointer
== INVALID_SET_FILE_POINTER
)
778 /* Get the real error code */
779 Result
= (WORD
)GetLastError();
782 if (Result
!= ERROR_SUCCESS
)
784 /* The operation did fail */
788 /* Return the file pointer, if requested */
789 if (NewOffset
) *NewOffset
= FilePointer
;
792 return ERROR_SUCCESS
;
795 BOOLEAN
DosFlushFileBuffers(WORD FileHandle
)
797 HANDLE Handle
= DosGetRealHandle(FileHandle
);
799 /* Make sure the handle is valid */
800 if (Handle
== INVALID_HANDLE_VALUE
) return FALSE
;
803 * No need to check whether the handle is a console handle since
804 * FlushFileBuffers() automatically does this check and calls
805 * FlushConsoleInputBuffer() for us.
807 // if (IsConsoleHandle(Handle))
808 // return (BOOLEAN)FlushConsoleInputBuffer(Handle);
810 return (BOOLEAN
)FlushFileBuffers(Handle
);
813 BOOLEAN
DosDuplicateHandle(WORD OldHandle
, WORD NewHandle
)
819 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
823 /* The system PSP has no handle table */
824 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
826 /* Get a pointer to the handle table */
827 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
828 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
830 /* Make sure the old handle is open */
831 if (HandleTable
[OldHandle
] == 0xFF) return FALSE
;
833 /* Check if the new handle is open */
834 if (HandleTable
[NewHandle
] != 0xFF)
837 DosCloseHandle(NewHandle
);
840 /* Increment the reference count of the SFT entry */
841 SftIndex
= HandleTable
[OldHandle
];
842 DosSftRefCount
[SftIndex
]++;
844 /* Make the new handle point to that SFT entry */
845 HandleTable
[NewHandle
] = SftIndex
;
851 BOOLEAN
DosCloseHandle(WORD DosHandle
)
857 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle
);
859 /* The system PSP has no handle table */
860 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
862 /* Get a pointer to the handle table */
863 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
864 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
866 /* Make sure the handle is open */
867 if (HandleTable
[DosHandle
] == 0xFF) return FALSE
;
869 /* Decrement the reference count of the SFT entry */
870 SftIndex
= HandleTable
[DosHandle
];
871 DosSftRefCount
[SftIndex
]--;
873 /* Check if the reference count fell to zero */
874 if (!DosSftRefCount
[SftIndex
])
876 /* Close the file, it's no longer needed */
877 CloseHandle(DosSystemFileTable
[SftIndex
]);
879 /* Clear the handle */
880 DosSystemFileTable
[SftIndex
] = INVALID_HANDLE_VALUE
;
883 /* Clear the entry in the JFT */
884 HandleTable
[DosHandle
] = 0xFF;
889 BOOLEAN
DosChangeDrive(BYTE Drive
)
891 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
893 /* Make sure the drive exists */
894 if (Drive
> (LastDrive
- 'A')) return FALSE
;
896 /* Find the path to the new current directory */
897 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
899 /* Change the current directory of the process */
900 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
902 /* Set the current drive */
903 CurrentDrive
= Drive
;
909 BOOLEAN
DosChangeDirectory(LPSTR Directory
)
915 /* Make sure the directory path is not too long */
916 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
918 DosLastError
= ERROR_PATH_NOT_FOUND
;
922 /* Get the drive number */
923 DriveNumber
= Directory
[0] - 'A';
925 /* Make sure the drive exists */
926 if (DriveNumber
> (LastDrive
- 'A'))
928 DosLastError
= ERROR_PATH_NOT_FOUND
;
932 /* Get the file attributes */
933 Attributes
= GetFileAttributesA(Directory
);
935 /* Make sure the path exists and is a directory */
936 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
937 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
939 DosLastError
= ERROR_PATH_NOT_FOUND
;
943 /* Check if this is the current drive */
944 if (DriveNumber
== CurrentDrive
)
946 /* Change the directory */
947 if (!SetCurrentDirectoryA(Directory
))
949 DosLastError
= LOWORD(GetLastError());
954 /* Get the directory part of the path */
955 Path
= strchr(Directory
, '\\');
958 /* Skip the backslash */
962 /* Set the directory for the drive */
965 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
969 CurrentDirectories
[DriveNumber
][0] = '\0';
976 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
978 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
979 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
981 ZeroMemory(PspBlock
, sizeof(DOS_PSP
));
983 /* Set the exit interrupt */
984 PspBlock
->Exit
[0] = 0xCD; // int 0x20
985 PspBlock
->Exit
[1] = 0x20;
987 /* Set the number of the last paragraph */
988 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
990 /* Save the interrupt vectors */
991 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
992 PspBlock
->BreakAddress
= IntVecTable
[0x23];
993 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
995 /* Set the parent PSP */
996 PspBlock
->ParentPsp
= CurrentPsp
;
998 /* Copy the parent handle table */
999 DosCopyHandleTable(PspBlock
->HandleTable
);
1001 /* Set the environment block */
1002 PspBlock
->EnvBlock
= Environment
;
1004 /* Set the handle table pointers to the internal handle table */
1005 PspBlock
->HandleTableSize
= 20;
1006 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
1008 /* Set the DOS version */
1009 PspBlock
->DosVersion
= DOS_VERSION
;
1011 /* Set the far call opcodes */
1012 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
1013 PspBlock
->FarCall
[1] = 0x21;
1014 PspBlock
->FarCall
[2] = 0xCB; // retf
1016 /* Set the command line */
1017 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
1018 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
1019 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
1022 BOOLEAN
DosCreateProcess(LPCSTR CommandLine
, WORD EnvBlock
)
1024 BOOLEAN Success
= FALSE
, AllocatedEnvBlock
= FALSE
;
1025 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
1026 LPBYTE Address
= NULL
;
1027 LPSTR ProgramFilePath
, Parameters
[256];
1028 CHAR CommandLineCopy
[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 // FIXME: Improve parsing (especially: "some_path\with spaces\program.exe" options)
1046 /* Get the file name of the executable */
1047 ProgramFilePath
= strtok(CommandLineCopy
, " \t");
1049 /* Load the parameters in the local array */
1050 while ((ParamCount
< sizeof(Parameters
)/sizeof(Parameters
[0]))
1051 && ((Parameters
[ParamCount
] = strtok(NULL
, " \t")) != NULL
))
1056 /* Open a handle to the executable */
1057 FileHandle
= CreateFileA(ProgramFilePath
,
1062 FILE_ATTRIBUTE_NORMAL
,
1064 if (FileHandle
== INVALID_HANDLE_VALUE
) goto Cleanup
;
1066 /* Get the file size */
1067 FileSize
= GetFileSize(FileHandle
, NULL
);
1069 /* Create a mapping object for the file */
1070 FileMapping
= CreateFileMapping(FileHandle
,
1076 if (FileMapping
== NULL
) goto Cleanup
;
1078 /* Map the file into memory */
1079 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
1080 if (Address
== NULL
) goto Cleanup
;
1082 /* Did we get an environment segment? */
1085 /* Set a flag to know if the environment block was allocated here */
1086 AllocatedEnvBlock
= TRUE
;
1088 /* No, copy the one from the parent */
1089 EnvBlock
= DosCopyEnvironmentBlock((CurrentPsp
!= SYSTEM_PSP
)
1090 ? SEGMENT_TO_PSP(CurrentPsp
)->EnvBlock
1095 /* Check if this is an EXE file or a COM file */
1096 if (Address
[0] == 'M' && Address
[1] == 'Z')
1100 /* Get the MZ header */
1101 Header
= (PIMAGE_DOS_HEADER
)Address
;
1103 /* Get the base size of the file, in paragraphs (rounded up) */
1104 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
1106 /* Add the PSP size, in paragraphs */
1107 ExeSize
+= sizeof(DOS_PSP
) >> 4;
1109 /* Add the maximum size that should be allocated */
1110 ExeSize
+= Header
->e_maxalloc
;
1112 /* Make sure it does not pass 0xFFFF */
1113 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
1115 /* Reduce the size one by one until the allocation is successful */
1116 for (i
= Header
->e_maxalloc
; i
>= Header
->e_minalloc
; i
--, ExeSize
--)
1118 /* Try to allocate that much memory */
1119 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
1120 if (Segment
!= 0) break;
1123 /* Check if at least the lowest allocation was successful */
1124 if (Segment
== 0) goto Cleanup
;
1126 /* Initialize the PSP */
1127 DosInitializePsp(Segment
,
1132 /* The process owns its own memory */
1133 DosChangeMemoryOwner(Segment
, Segment
);
1134 DosChangeMemoryOwner(EnvBlock
, Segment
);
1136 /* Copy the program to Segment:0100 */
1137 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1138 Address
+ (Header
->e_cparhdr
<< 4),
1139 min(FileSize
- (Header
->e_cparhdr
<< 4),
1140 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
1142 /* Get the relocation table */
1143 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
1145 /* Perform relocations */
1146 for (i
= 0; i
< Header
->e_crlc
; i
++)
1148 /* Get a pointer to the word that needs to be patched */
1149 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
1150 0x100 + LOWORD(RelocationTable
[i
]));
1152 /* Add the number of the EXE segment to it */
1153 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
1156 /* Set the initial segment registers */
1160 /* Set the stack to the location from the header */
1161 EmulatorSetStack(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
,
1165 CurrentPsp
= Segment
;
1166 DiskTransferArea
= MAKELONG(0x80, Segment
);
1167 EmulatorExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
1176 /* Find the maximum amount of memory that can be allocated */
1177 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
1179 /* Make sure it's enough for the whole program and the PSP */
1180 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
))) goto Cleanup
;
1182 /* Allocate all of it */
1183 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
1184 if (Segment
== 0) goto Cleanup
;
1186 /* The process owns its own memory */
1187 DosChangeMemoryOwner(Segment
, Segment
);
1188 DosChangeMemoryOwner(EnvBlock
, Segment
);
1190 /* Copy the program to Segment:0100 */
1191 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1195 /* Initialize the PSP */
1196 DosInitializePsp(Segment
,
1198 (WORD
)((FileSize
+ sizeof(DOS_PSP
)) >> 4),
1201 /* Set the initial segment registers */
1205 /* Set the stack to the last word of the segment */
1206 EmulatorSetStack(Segment
, 0xFFFE);
1209 * Set the value on the stack to 0, so that a near return
1210 * jumps to PSP:0000 which has the exit code.
1212 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
1215 CurrentPsp
= Segment
;
1216 DiskTransferArea
= MAKELONG(0x80, Segment
);
1217 EmulatorExecute(Segment
, 0x100);
1225 /* It was not successful, cleanup the DOS memory */
1226 if (AllocatedEnvBlock
) DosFreeMemory(EnvBlock
);
1227 if (Segment
) DosFreeMemory(Segment
);
1231 if (Address
!= NULL
) UnmapViewOfFile(Address
);
1233 /* Close the file mapping object */
1234 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
1236 /* Close the file handle */
1237 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
1242 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
1245 WORD McbSegment
= FIRST_MCB_SEGMENT
;
1246 PDOS_MCB CurrentMcb
;
1247 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
1248 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
1250 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1254 /* Check if this PSP is it's own parent */
1255 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
1257 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
1259 /* Close the handle */
1263 /* Free the memory used by the process */
1266 /* Get a pointer to the MCB */
1267 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
1269 /* Make sure the MCB is valid */
1270 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
1272 /* If this block was allocated by the process, free it */
1273 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
+ 1);
1275 /* If this was the last block, quit */
1276 if (CurrentMcb
->BlockType
== 'Z') break;
1278 /* Update the segment and continue */
1279 McbSegment
+= CurrentMcb
->Size
+ 1;
1283 /* Restore the interrupt vectors */
1284 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
1285 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
1286 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
1288 /* Update the current PSP */
1289 if (Psp
== CurrentPsp
)
1291 CurrentPsp
= PspBlock
->ParentPsp
;
1292 if (CurrentPsp
== SYSTEM_PSP
) VdmRunning
= FALSE
;
1295 /* Save the return code - Normal termination */
1296 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
1298 /* Return control to the parent process */
1299 EmulatorExecute(HIWORD(PspBlock
->TerminateAddress
),
1300 LOWORD(PspBlock
->TerminateAddress
));
1303 CHAR
DosReadCharacter(VOID
)
1305 CHAR Character
= '\0';
1308 if (IsConsoleHandle(DosGetRealHandle(DOS_INPUT_HANDLE
)))
1311 Character
= LOBYTE(BiosGetCharacter());
1315 /* Use the file reading function */
1316 DosReadFile(DOS_INPUT_HANDLE
, &Character
, sizeof(CHAR
), &BytesRead
);
1322 BOOLEAN
DosCheckInput(VOID
)
1324 HANDLE Handle
= DosGetRealHandle(DOS_INPUT_HANDLE
);
1326 if (IsConsoleHandle(Handle
))
1329 return (BiosPeekCharacter() != 0xFFFF);
1334 DWORD FileSize
= GetFileSize(Handle
, &FileSizeHigh
);
1335 LONG LocationHigh
= 0;
1336 DWORD Location
= SetFilePointer(Handle
, 0, &LocationHigh
, FILE_CURRENT
);
1338 return ((Location
!= FileSize
) || (LocationHigh
!= FileSizeHigh
));
1342 VOID
DosPrintCharacter(CHAR Character
)
1346 /* Use the file writing function */
1347 DosWriteFile(DOS_OUTPUT_HANDLE
, &Character
, sizeof(CHAR
), &BytesWritten
);
1350 BOOLEAN
DosHandleIoctl(BYTE ControlCode
, WORD FileHandle
)
1352 HANDLE Handle
= DosGetRealHandle(FileHandle
);
1354 if (Handle
== INVALID_HANDLE_VALUE
)
1357 DosLastError
= ERROR_FILE_NOT_FOUND
;
1361 switch (ControlCode
)
1363 /* Get Device Information */
1368 if (Handle
== DosSystemFileTable
[0])
1373 else if (Handle
== DosSystemFileTable
[1])
1375 /* Console output */
1379 /* It is a character device */
1382 /* Return the device information word */
1387 /* Unsupported control code */
1390 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode
);
1392 DosLastError
= ERROR_INVALID_PARAMETER
;
1398 VOID WINAPI
DosInt20h(LPWORD Stack
)
1400 /* This is the exit interrupt */
1401 DosTerminateProcess(Stack
[STACK_CS
], 0);
1404 VOID WINAPI
DosInt21h(LPWORD Stack
)
1407 SYSTEMTIME SystemTime
;
1409 PDOS_INPUT_BUFFER InputBuffer
;
1411 /* Check the value in the AH register */
1414 /* Terminate Program */
1417 DosTerminateProcess(Stack
[STACK_CS
], 0);
1421 /* Read Character from STDIN with Echo */
1424 Character
= DosReadCharacter();
1425 DosPrintCharacter(Character
);
1427 /* Let the BOP repeat if needed */
1434 /* Write Character to STDOUT */
1437 Character
= getDL();
1438 DosPrintCharacter(Character
);
1441 * We return the output character (DOS 2.1+).
1442 * Also, if we're going to output a TAB, then
1443 * don't return a TAB but a SPACE instead.
1444 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1445 * for more information.
1447 setAL(Character
== '\t' ? ' ' : Character
);
1451 /* Read Character from STDAUX */
1454 // FIXME: Really read it from STDAUX!
1455 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1456 setAL(DosReadCharacter());
1460 /* Write Character to STDAUX */
1463 // FIXME: Really write it to STDAUX!
1464 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1465 DosPrintCharacter(getDL());
1469 /* Write Character to Printer */
1472 // FIXME: Really write it to printer!
1473 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1474 DPRINT1("0x%p\n", getDL());
1475 DPRINT1("\n\n-----------\n\n");
1479 /* Direct Console I/O */
1482 Character
= getDL();
1484 if (Character
!= 0xFF)
1487 DosPrintCharacter(Character
);
1490 * We return the output character (DOS 2.1+).
1491 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1492 * for more information.
1499 if (DosCheckInput())
1501 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
1502 setAL(DosReadCharacter());
1506 /* No character available */
1507 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
1515 /* Character Input without Echo */
1519 Character
= DosReadCharacter();
1521 /* Let the BOP repeat if needed */
1528 /* Write string to STDOUT */
1531 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1533 while (*String
!= '$')
1535 DosPrintCharacter(*String
);
1540 * We return the terminating character (DOS 2.1+).
1541 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1542 * for more information.
1548 /* Read Buffered Input */
1551 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1553 while (Stack
[STACK_COUNTER
] < InputBuffer
->MaxLength
)
1555 /* Try to read a character */
1556 Character
= DosReadCharacter();
1558 /* If it's not ready yet, let the BOP repeat */
1561 /* Echo the character and append it to the buffer */
1562 DosPrintCharacter(Character
);
1563 InputBuffer
->Buffer
[Stack
[STACK_COUNTER
]] = Character
;
1565 if (Character
== '\r') break;
1566 Stack
[STACK_COUNTER
]++;
1569 /* Update the length */
1570 InputBuffer
->Length
= Stack
[STACK_COUNTER
];
1574 /* Get STDIN Status */
1577 setAL(DosCheckInput() ? 0xFF : 0x00);
1581 /* Flush Buffer and Read STDIN */
1584 BYTE InputFunction
= getAL();
1586 /* Flush STDIN buffer */
1587 DosFlushFileBuffers(DOS_INPUT_HANDLE
); // Maybe just create a DosFlushInputBuffer...
1590 * If the input function number contained in AL is valid, i.e.
1591 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1592 * recursively with AL == AH.
1594 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1595 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1596 InputFunction
== 0x0A)
1598 setAH(InputFunction
);
1600 * Instead of calling ourselves really recursively as in:
1602 * prefer resetting the CF flag to let the BOP repeat.
1612 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1614 // TODO: Flush what's needed.
1615 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1617 /* Clear CF in DOS 6 only */
1618 if (PspBlock
->DosVersion
== 0x0006)
1619 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1624 /* Set Default Drive */
1627 DosChangeDrive(getDL());
1628 setAL(LastDrive
- 'A' + 1);
1632 /* NULL Function for CP/M Compatibility */
1636 * This function corresponds to the CP/M BDOS function
1637 * "get bit map of logged drives", which is meaningless
1640 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1641 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1642 * for more information.
1648 /* Get Default Drive */
1651 setAL(CurrentDrive
);
1655 /* Set Disk Transfer Area */
1658 DiskTransferArea
= MAKELONG(getDX(), getDS());
1662 /* NULL Function for CP/M Compatibility */
1667 * Function 0x1D corresponds to the CP/M BDOS function
1668 * "get bit map of read-only drives", which is meaningless
1670 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1671 * for more information.
1673 * Function 0x1E corresponds to the CP/M BDOS function
1674 * "set file attributes", which was meaningless under MS-DOS 1.x.
1675 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1676 * for more information.
1682 /* NULL Function for CP/M Compatibility */
1686 * This function corresponds to the CP/M BDOS function
1687 * "get/set default user (sublibrary) number", which is meaningless
1690 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1691 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1692 * for more information.
1698 /* Set Interrupt Vector */
1701 DWORD FarPointer
= MAKELONG(getDX(), getDS());
1702 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1704 /* Write the new far pointer to the IDT */
1705 ((PDWORD
)BaseAddress
)[getAL()] = FarPointer
;
1709 /* Create New PSP */
1712 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1716 /* Get System Date */
1719 GetLocalTime(&SystemTime
);
1720 setCX(SystemTime
.wYear
);
1721 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1722 setAL(SystemTime
.wDayOfWeek
);
1726 /* Set System Date */
1729 GetLocalTime(&SystemTime
);
1730 SystemTime
.wYear
= getCX();
1731 SystemTime
.wMonth
= getDH();
1732 SystemTime
.wDay
= getDL();
1734 /* Return success or failure */
1735 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1739 /* Get System Time */
1742 GetLocalTime(&SystemTime
);
1743 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1744 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1748 /* Set System Time */
1751 GetLocalTime(&SystemTime
);
1752 SystemTime
.wHour
= getCH();
1753 SystemTime
.wMinute
= getCL();
1754 SystemTime
.wSecond
= getDH();
1755 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1757 /* Return success or failure */
1758 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1762 /* Get Disk Transfer Area */
1765 setES(HIWORD(DiskTransferArea
));
1766 setBX(LOWORD(DiskTransferArea
));
1770 /* Get DOS Version */
1773 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1776 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1777 * for more information.
1780 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1783 * Return DOS OEM number:
1784 * 0x00 for IBM PC-DOS
1785 * 0x02 for packaged MS-DOS
1790 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1793 * Return version flag:
1794 * 1 << 3 if DOS is in ROM,
1795 * 0 (reserved) if not.
1800 /* Return DOS 24-bit user serial number in BL:CX */
1804 /* Return DOS version: Minor:Major in AH:AL */
1805 setAX(PspBlock
->DosVersion
);
1810 /* Get Interrupt Vector */
1813 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1815 /* Read the address from the IDT into ES:BX */
1816 setES(HIWORD(FarPointer
));
1817 setBX(LOWORD(FarPointer
));
1821 /* SWITCH character - AVAILDEV */
1824 if (getAL() == 0x00)
1827 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1828 * This setting is ignored by MS-DOS 4.0+.
1829 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1830 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1831 * for more information.
1836 else if (getAL() == 0x01)
1839 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1840 * This setting is ignored by MS-DOS 5+.
1841 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1842 * for more information.
1847 else if (getAL() == 0x02)
1850 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1851 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1852 * for more information.
1857 else if (getAL() == 0x03)
1860 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1861 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1862 * for more information.
1875 /* Create Directory */
1878 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1880 if (CreateDirectoryA(String
, NULL
))
1882 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1886 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1887 setAX(LOWORD(GetLastError()));
1893 /* Remove Directory */
1896 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1898 if (RemoveDirectoryA(String
))
1900 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1904 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1905 setAX(LOWORD(GetLastError()));
1911 /* Set Current Directory */
1914 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1916 if (DosChangeDirectory(String
))
1918 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1922 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1923 setAX(DosLastError
);
1933 WORD ErrorCode
= DosCreateFile(&FileHandle
,
1934 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1939 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1944 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1955 WORD ErrorCode
= DosOpenFile(&FileHandle
,
1956 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1961 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1966 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1976 if (DosCloseHandle(getBX()))
1978 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1982 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1983 setAX(ERROR_INVALID_HANDLE
);
1989 /* Read from File or Device */
1992 WORD Handle
= getBX();
1993 LPBYTE Buffer
= (LPBYTE
)SEG_OFF_TO_PTR(getDS(), getDX());
1994 WORD Count
= getCX();
1996 WORD ErrorCode
= ERROR_SUCCESS
;
1998 if (IsConsoleHandle(DosGetRealHandle(Handle
)))
2000 while (Stack
[STACK_COUNTER
] < Count
)
2002 /* Read a character from the BIOS */
2003 // FIXME: Security checks!
2004 Buffer
[Stack
[STACK_COUNTER
]] = LOBYTE(BiosGetCharacter());
2006 /* Stop if the BOP needs to be repeated */
2009 /* Increment the counter */
2010 Stack
[STACK_COUNTER
]++;
2013 if (Stack
[STACK_COUNTER
] < Count
)
2014 ErrorCode
= ERROR_NOT_READY
;
2020 /* Use the file reading function */
2021 ErrorCode
= DosReadFile(Handle
, Buffer
, Count
, &BytesRead
);
2024 if (ErrorCode
== ERROR_SUCCESS
)
2026 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2029 else if (ErrorCode
!= ERROR_NOT_READY
)
2031 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2037 /* Write to File or Device */
2040 WORD BytesWritten
= 0;
2041 WORD ErrorCode
= DosWriteFile(getBX(),
2042 SEG_OFF_TO_PTR(getDS(), getDX()),
2046 if (ErrorCode
== ERROR_SUCCESS
)
2048 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2049 setAX(BytesWritten
);
2053 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2063 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2065 /* Call the API function */
2066 if (DeleteFileA(FileName
))
2068 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2070 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2071 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2073 setAL(FileName
[0] - 'A');
2077 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2078 setAX(GetLastError());
2088 WORD ErrorCode
= DosSeekFile(getBX(),
2089 MAKELONG(getDX(), getCX()),
2093 if (ErrorCode
== ERROR_SUCCESS
)
2095 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2097 /* Return the new offset in DX:AX */
2098 setDX(HIWORD(NewLocation
));
2099 setAX(LOWORD(NewLocation
));
2103 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2110 /* Get/Set File Attributes */
2114 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2116 if (getAL() == 0x00)
2118 /* Get the attributes */
2119 Attributes
= GetFileAttributesA(FileName
);
2121 /* Check if it failed */
2122 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
2124 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2125 setAX(GetLastError());
2129 /* Return the attributes that DOS can understand */
2130 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2131 setCX(Attributes
& 0x00FF);
2134 else if (getAL() == 0x01)
2136 /* Try to set the attributes */
2137 if (SetFileAttributesA(FileName
, getCL()))
2139 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2143 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2144 setAX(GetLastError());
2149 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2150 setAX(ERROR_INVALID_FUNCTION
);
2159 if (DosHandleIoctl(getAL(), getBX()))
2161 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2165 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2166 setAX(DosLastError
);
2172 /* Duplicate Handle */
2176 HANDLE Handle
= DosGetRealHandle(getBX());
2178 if (Handle
!= INVALID_HANDLE_VALUE
)
2180 /* The handle is invalid */
2181 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2182 setAX(ERROR_INVALID_HANDLE
);
2186 /* Open a new handle to the same entry */
2187 NewHandle
= DosOpenHandle(Handle
);
2189 if (NewHandle
== INVALID_DOS_HANDLE
)
2191 /* Too many files open */
2192 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2193 setAX(ERROR_TOO_MANY_OPEN_FILES
);
2197 /* Return the result */
2198 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2203 /* Force Duplicate Handle */
2206 if (DosDuplicateHandle(getBX(), getCX()))
2208 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2212 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2213 setAX(ERROR_INVALID_HANDLE
);
2219 /* Get Current Directory */
2222 BYTE DriveNumber
= getDL();
2223 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
2225 /* Get the real drive number */
2226 if (DriveNumber
== 0)
2228 DriveNumber
= CurrentDrive
;
2232 /* Decrement DriveNumber since it was 1-based */
2236 if (DriveNumber
<= LastDrive
- 'A')
2239 * Copy the current directory into the target buffer.
2240 * It doesn't contain the drive letter and the backslash.
2242 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
2243 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2244 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2248 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2249 setAX(ERROR_INVALID_DRIVE
);
2255 /* Allocate Memory */
2258 WORD MaxAvailable
= 0;
2259 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
2263 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2268 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2269 setAX(DosLastError
);
2270 setBX(MaxAvailable
);
2279 if (DosFreeMemory(getES()))
2281 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2285 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2286 setAX(ERROR_ARENA_TRASHED
);
2292 /* Resize Memory Block */
2297 if (DosResizeMemory(getES(), getBX(), &Size
))
2299 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2303 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2304 setAX(DosLastError
);
2311 /* Terminate With Return Code */
2314 DosTerminateProcess(CurrentPsp
, getAL());
2318 /* Get Return Code (ERRORLEVEL) */
2322 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2323 * DosErrorLevel is cleared after being read by this function.
2325 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2326 setAX(DosErrorLevel
);
2327 DosErrorLevel
= 0x0000; // Clear it
2331 /* Internal - Set Current Process ID (Set PSP Address) */
2334 // FIXME: Is it really what it's done ??
2335 CurrentPsp
= getBX();
2339 /* Internal - Get Current Process ID (Get PSP Address) */
2341 /* Get Current PSP Address */
2345 * Undocumented AH=51h is identical to the documented AH=62h.
2346 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2347 * and http://www.ctyme.com/intr/rb-3140.htm
2348 * for more information.
2354 /* Get/Set Memory Management Options */
2357 if (getAL() == 0x00)
2359 /* Get allocation strategy */
2360 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2361 setAX(DosAllocStrategy
);
2363 else if (getAL() == 0x01)
2365 /* Set allocation strategy */
2367 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2368 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2370 /* Can't set both */
2371 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2372 setAX(ERROR_INVALID_PARAMETER
);
2376 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2378 /* Invalid allocation strategy */
2379 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2380 setAX(ERROR_INVALID_PARAMETER
);
2384 DosAllocStrategy
= getBL();
2385 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2387 else if (getAL() == 0x02)
2389 /* Get UMB link state */
2390 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2391 setAL(DosUmbLinked
? 0x01 : 0x00);
2393 else if (getAL() == 0x03)
2395 /* Set UMB link state */
2396 if (getBX()) DosLinkUmb();
2397 else DosUnlinkUmb();
2398 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2402 /* Invalid or unsupported function */
2403 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2404 setAX(ERROR_INVALID_FUNCTION
);
2413 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2415 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2420 VOID WINAPI
DosBreakInterrupt(LPWORD Stack
)
2422 UNREFERENCED_PARAMETER(Stack
);
2427 VOID WINAPI
DosInt2Fh(LPWORD Stack
)
2429 DPRINT1("DOS System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2431 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2434 BOOLEAN
DosInitialize(VOID
)
2437 PDOS_MCB Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
);
2440 LPWSTR SourcePtr
, Environment
;
2442 LPSTR DestPtr
= (LPSTR
)SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK
, 0);
2444 CHAR CurrentDirectory
[MAX_PATH
];
2445 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2448 /* Initialize the MCB */
2449 Mcb
->BlockType
= 'Z';
2450 Mcb
->Size
= USER_MEMORY_SIZE
;
2453 /* Initialize the link MCB to the UMB area */
2454 Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
+ 1);
2455 Mcb
->BlockType
= 'M';
2456 Mcb
->Size
= UMB_START_SEGMENT
- FIRST_MCB_SEGMENT
- USER_MEMORY_SIZE
- 2;
2457 Mcb
->OwnerPsp
= SYSTEM_PSP
;
2459 /* Initialize the UMB area */
2460 Mcb
= SEGMENT_TO_MCB(UMB_START_SEGMENT
);
2461 Mcb
->BlockType
= 'Z';
2462 Mcb
->Size
= UMB_END_SEGMENT
- UMB_START_SEGMENT
;
2465 /* Get the environment strings */
2466 SourcePtr
= Environment
= GetEnvironmentStringsW();
2467 if (Environment
== NULL
) return FALSE
;
2469 /* Fill the DOS system environment block */
2472 /* Get the size of the ASCII string */
2473 AsciiSize
= WideCharToMultiByte(CP_ACP
,
2482 /* Allocate memory for the ASCII string */
2483 AsciiString
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, AsciiSize
);
2484 if (AsciiString
== NULL
)
2486 FreeEnvironmentStringsW(Environment
);
2490 /* Convert to ASCII */
2491 WideCharToMultiByte(CP_ACP
,
2500 /* Copy the string into DOS memory */
2501 strcpy(DestPtr
, AsciiString
);
2503 /* Move to the next string */
2504 SourcePtr
+= wcslen(SourcePtr
) + 1;
2505 DestPtr
+= strlen(AsciiString
);
2508 /* Free the memory */
2509 HeapFree(GetProcessHeap(), 0, AsciiString
);
2513 /* Free the memory allocated for environment strings */
2514 FreeEnvironmentStringsW(Environment
);
2516 /* Clear the current directory buffer */
2517 ZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2519 /* Get the current directory */
2520 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2522 // TODO: Use some kind of default path?
2526 /* Convert that to a DOS path */
2527 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2529 // TODO: Use some kind of default path?
2534 CurrentDrive
= DosDirectory
[0] - 'A';
2536 /* Get the directory part of the path */
2537 Path
= strchr(DosDirectory
, '\\');
2540 /* Skip the backslash */
2544 /* Set the directory */
2547 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
2550 /* Read CONFIG.SYS */
2551 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
2554 while (fgetws(Buffer
, 256, Stream
))
2556 // TODO: Parse the line
2561 /* Initialize the SFT */
2562 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
2564 DosSystemFileTable
[i
] = INVALID_HANDLE_VALUE
;
2565 DosSftRefCount
[i
] = 0;
2568 /* Get handles to standard I/O devices */
2569 DosSystemFileTable
[0] = GetStdHandle(STD_INPUT_HANDLE
);
2570 DosSystemFileTable
[1] = GetStdHandle(STD_OUTPUT_HANDLE
);
2571 DosSystemFileTable
[2] = GetStdHandle(STD_ERROR_HANDLE
);
2573 /* Register the DOS-32 Interrupts */
2574 RegisterInt32(0x20, DosInt20h
);
2575 RegisterInt32(0x21, DosInt21h
);
2576 RegisterInt32(0x23, DosBreakInterrupt
);
2577 RegisterInt32(0x2F, DosInt2Fh
);