2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: dos/dos32krnl/dos.c
5 * PURPOSE: DOS32 Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
10 /* INCLUDES *******************************************************************/
21 #include "bios/bios.h"
24 #include "hardware/ps2.h"
26 /* PRIVATE VARIABLES **********************************************************/
28 CALLBACK16 DosContext
;
30 static WORD CurrentPsp
= SYSTEM_PSP
;
31 static WORD DosLastError
= 0;
32 static DWORD DiskTransferArea
;
33 /*static*/ BYTE CurrentDrive
;
34 static CHAR LastDrive
= 'E';
35 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
41 } DosSystemFileTable
[DOS_SFT_SIZE
];
43 static BYTE DosAllocStrategy
= DOS_ALLOC_BEST_FIT
;
44 static BOOLEAN DosUmbLinked
= FALSE
;
45 static WORD DosErrorLevel
= 0x0000;
47 /* Echo state for INT 21h, AH = 01h and AH = 3Fh */
48 BOOLEAN DoEcho
= FALSE
;
50 /* PRIVATE FUNCTIONS **********************************************************/
53 * Memory management functions
55 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
57 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
59 /* If this is the last block or it's not free, quit */
60 if (CurrentMcb
->BlockType
== 'Z' || CurrentMcb
->OwnerPsp
!= 0) return;
64 /* Get a pointer to the next MCB */
65 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
67 /* Check if the next MCB is free */
68 if (NextMcb
->OwnerPsp
== 0)
71 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
72 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
73 NextMcb
->BlockType
= 'I';
77 /* No more adjoining free blocks */
83 static WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
85 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
, MaxSize
= 0;
86 PDOS_MCB CurrentMcb
, NextMcb
;
87 BOOLEAN SearchUmb
= FALSE
;
89 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
91 if (DosUmbLinked
&& (DosAllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
93 /* Search UMB first */
94 Segment
= UMB_START_SEGMENT
;
100 /* Get a pointer to the MCB */
101 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
103 /* Make sure it's valid */
104 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
106 DPRINT("The DOS memory arena is corrupted!\n");
107 DosLastError
= ERROR_ARENA_TRASHED
;
111 /* Only check free blocks */
112 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
114 /* Combine this free block with adjoining free blocks */
115 DosCombineFreeBlocks(Segment
);
117 /* Update the maximum block size */
118 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
120 /* Check if this block is big enough */
121 if (CurrentMcb
->Size
< Size
) goto Next
;
123 switch (DosAllocStrategy
& 0x3F)
125 case DOS_ALLOC_FIRST_FIT
:
127 /* For first fit, stop immediately */
132 case DOS_ALLOC_BEST_FIT
:
134 /* For best fit, update the smallest block found so far */
135 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
143 case DOS_ALLOC_LAST_FIT
:
145 /* For last fit, make the current block the result, but keep searching */
152 /* If this was the last MCB in the chain, quit */
153 if (CurrentMcb
->BlockType
== 'Z')
155 /* Check if nothing was found while searching through UMBs */
156 if ((Result
== 0) && SearchUmb
&& (DosAllocStrategy
& DOS_ALLOC_HIGH_LOW
))
158 /* Search low memory */
159 Segment
= FIRST_MCB_SEGMENT
;
166 /* Otherwise, update the segment and continue */
167 Segment
+= CurrentMcb
->Size
+ 1;
172 /* If we didn't find a free block, return 0 */
175 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
176 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
180 /* Get a pointer to the MCB */
181 CurrentMcb
= SEGMENT_TO_MCB(Result
);
183 /* Check if the block is larger than requested */
184 if (CurrentMcb
->Size
> Size
)
186 /* It is, split it into two blocks */
187 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
189 /* Initialize the new MCB structure */
190 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
191 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
192 NextMcb
->OwnerPsp
= 0;
194 /* Update the current block */
195 CurrentMcb
->BlockType
= 'M';
196 CurrentMcb
->Size
= Size
;
199 /* Take ownership of the block */
200 CurrentMcb
->OwnerPsp
= CurrentPsp
;
202 /* Return the segment of the data portion of the block */
206 static BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
208 BOOLEAN Success
= TRUE
;
209 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
210 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
212 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
216 /* Make sure this is a valid, allocated block */
217 if ((Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') || Mcb
->OwnerPsp
== 0)
220 DosLastError
= ERROR_INVALID_HANDLE
;
224 ReturnSize
= Mcb
->Size
;
226 /* Check if we need to expand or contract the block */
227 if (NewSize
> Mcb
->Size
)
229 /* We can't expand the last block */
230 if (Mcb
->BlockType
!= 'M')
236 /* Get the pointer and segment of the next MCB */
237 NextSegment
= Segment
+ Mcb
->Size
+ 1;
238 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
240 /* Make sure the next segment is free */
241 if (NextMcb
->OwnerPsp
!= 0)
243 DPRINT("Cannot expand memory block: next segment is not free!\n");
244 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
249 /* Combine this free block with adjoining free blocks */
250 DosCombineFreeBlocks(NextSegment
);
252 /* Set the maximum possible size of the block */
253 ReturnSize
+= NextMcb
->Size
+ 1;
255 if (ReturnSize
< NewSize
)
257 DPRINT("Cannot expand memory block: insufficient free segments available!\n");
258 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
263 /* Maximize the current block */
264 Mcb
->Size
= ReturnSize
;
265 Mcb
->BlockType
= NextMcb
->BlockType
;
267 /* Invalidate the next block */
268 NextMcb
->BlockType
= 'I';
270 /* Check if the block is larger than requested */
271 if (Mcb
->Size
> NewSize
)
273 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
277 /* It is, split it into two blocks */
278 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
280 /* Initialize the new MCB structure */
281 NextMcb
->BlockType
= Mcb
->BlockType
;
282 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
283 NextMcb
->OwnerPsp
= 0;
285 /* Update the current block */
286 Mcb
->BlockType
= 'M';
290 else if (NewSize
< Mcb
->Size
)
292 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
296 /* Just split the block */
297 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
298 NextMcb
->BlockType
= Mcb
->BlockType
;
299 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
300 NextMcb
->OwnerPsp
= 0;
303 Mcb
->BlockType
= 'M';
308 /* Check if the operation failed */
311 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
314 /* Return the maximum possible size */
315 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
321 static BOOLEAN
DosFreeMemory(WORD BlockData
)
323 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
325 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
327 /* Make sure the MCB is valid */
328 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z')
330 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
334 /* Mark the block as free */
340 static BOOLEAN
DosLinkUmb(VOID
)
342 DWORD Segment
= FIRST_MCB_SEGMENT
;
343 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
345 DPRINT("Linking UMB\n");
347 /* Check if UMBs are already linked */
348 if (DosUmbLinked
) return FALSE
;
350 /* Find the last block */
351 while ((Mcb
->BlockType
== 'M') && (Segment
<= 0xFFFF))
353 Segment
+= Mcb
->Size
+ 1;
354 Mcb
= SEGMENT_TO_MCB(Segment
);
357 /* Make sure it's valid */
358 if (Mcb
->BlockType
!= 'Z') return FALSE
;
360 /* Connect the MCB with the UMB chain */
361 Mcb
->BlockType
= 'M';
367 static BOOLEAN
DosUnlinkUmb(VOID
)
369 DWORD Segment
= FIRST_MCB_SEGMENT
;
370 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
372 DPRINT("Unlinking UMB\n");
374 /* Check if UMBs are already unlinked */
375 if (!DosUmbLinked
) return FALSE
;
377 /* Find the block preceding the MCB that links it with the UMB chain */
378 while (Segment
<= 0xFFFF)
380 if ((Segment
+ Mcb
->Size
) == (FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
))
382 /* This is the last non-UMB segment */
386 /* Advance to the next MCB */
387 Segment
+= Mcb
->Size
+ 1;
388 Mcb
= SEGMENT_TO_MCB(Segment
);
391 /* Mark the MCB as the last MCB */
392 Mcb
->BlockType
= 'Z';
394 DosUmbLinked
= FALSE
;
398 static VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
400 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
402 /* Just set the owner */
403 Mcb
->OwnerPsp
= NewOwner
;
406 static WORD
DosCopyEnvironmentBlock(LPCSTR Environment OPTIONAL
,
409 PCHAR Ptr
, DestBuffer
= NULL
;
413 /* If we have an environment strings list, compute its size */
416 /* Calculate the size of the environment block */
417 Ptr
= (PCHAR
)Environment
;
418 while (*Ptr
) Ptr
+= strlen(Ptr
) + 1;
419 TotalSize
= (ULONG_PTR
)Ptr
- (ULONG_PTR
)Environment
;
423 /* Empty environment string */
426 /* Add the final environment block NULL-terminator */
429 /* Add the two bytes for the program name tag */
432 /* Add the string buffer size */
433 TotalSize
+= strlen(ProgramName
) + 1;
435 /* Allocate the memory for the environment block */
436 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
437 if (!DestSegment
) return 0;
439 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
441 /* If we have an environment strings list, copy it */
444 Ptr
= (PCHAR
)Environment
;
447 /* Copy the string and NULL-terminate it */
448 strcpy(DestBuffer
, Ptr
);
449 DestBuffer
+= strlen(Ptr
);
450 *(DestBuffer
++) = '\0';
452 /* Move to the next string */
453 Ptr
+= strlen(Ptr
) + 1;
458 /* Empty environment string */
459 *(DestBuffer
++) = '\0';
461 /* NULL-terminate the environment block */
462 *(DestBuffer
++) = '\0';
464 /* Store the special program name tag */
465 *(DestBuffer
++) = LOBYTE(DOS_PROGRAM_NAME_TAG
);
466 *(DestBuffer
++) = HIBYTE(DOS_PROGRAM_NAME_TAG
);
468 /* Copy the program name after the environment block */
469 strcpy(DestBuffer
, ProgramName
);
479 /* Taken from base/shell/cmd/console.c */
480 BOOL
IsConsoleHandle(HANDLE hHandle
)
484 /* Check whether the handle may be that of a console... */
485 if ((GetFileType(hHandle
) & FILE_TYPE_CHAR
) == 0) return FALSE
;
488 * It may be. Perform another test... The idea comes from the
489 * MSDN description of the WriteConsole API:
491 * "WriteConsole fails if it is used with a standard handle
492 * that is redirected to a file. If an application processes
493 * multilingual output that can be redirected, determine whether
494 * the output handle is a console handle (one method is to call
495 * the GetConsoleMode function and check whether it succeeds).
496 * If the handle is a console handle, call WriteConsole. If the
497 * handle is not a console handle, the output is redirected and
498 * you should call WriteFile to perform the I/O."
500 return GetConsoleMode(hHandle
, &dwMode
);
503 WORD
DosOpenHandle(HANDLE Handle
)
510 /* The system PSP has no handle table */
511 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_DOS_HANDLE
;
513 /* Get a pointer to the handle table */
514 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
515 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
517 /* Find a free entry in the JFT */
518 for (DosHandle
= 0; DosHandle
< PspBlock
->HandleTableSize
; DosHandle
++)
520 if (HandleTable
[DosHandle
] == 0xFF) break;
523 /* If there are no free entries, fail */
524 if (DosHandle
== PspBlock
->HandleTableSize
) return INVALID_DOS_HANDLE
;
526 /* Check if the handle is already in the SFT */
527 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
529 /* Check if this is the same handle */
530 if (DosSystemFileTable
[i
].Handle
!= Handle
) continue;
532 /* Already in the table, reference it */
533 DosSystemFileTable
[i
].RefCount
++;
535 /* Set the JFT entry to that SFT index */
536 HandleTable
[DosHandle
] = i
;
538 /* Return the new handle */
542 /* Add the handle to the SFT */
543 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
545 /* Make sure this is an empty table entry */
546 if (DosSystemFileTable
[i
].Handle
!= INVALID_HANDLE_VALUE
) continue;
548 /* Initialize the empty table entry */
549 DosSystemFileTable
[i
].Handle
= Handle
;
550 DosSystemFileTable
[i
].RefCount
= 1;
552 /* Set the JFT entry to that SFT index */
553 HandleTable
[DosHandle
] = i
;
555 /* Return the new handle */
559 /* The SFT is full */
560 return INVALID_DOS_HANDLE
;
563 HANDLE
DosGetRealHandle(WORD DosHandle
)
568 /* The system PSP has no handle table */
569 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_HANDLE_VALUE
;
571 /* Get a pointer to the handle table */
572 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
573 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
575 /* Make sure the handle is open */
576 if (HandleTable
[DosHandle
] == 0xFF) return INVALID_HANDLE_VALUE
;
578 /* Return the Win32 handle */
579 return DosSystemFileTable
[HandleTable
[DosHandle
]].Handle
;
582 static VOID
DosCopyHandleTable(LPBYTE DestinationTable
)
588 /* Clear the table first */
589 for (i
= 0; i
< 20; i
++) DestinationTable
[i
] = 0xFF;
591 /* Check if this is the initial process */
592 if (CurrentPsp
== SYSTEM_PSP
)
594 /* Set up the standard I/O devices */
595 for (i
= 0; i
<= 2; i
++)
597 /* Set the index in the SFT */
598 DestinationTable
[i
] = (BYTE
)i
;
600 /* Increase the reference count */
601 DosSystemFileTable
[i
].RefCount
++;
608 /* Get the parent PSP block and handle table */
609 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
610 SourceTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
612 /* Copy the first 20 handles into the new table */
613 for (i
= 0; i
< 20; i
++)
615 DestinationTable
[i
] = SourceTable
[i
];
617 /* Increase the reference count */
618 DosSystemFileTable
[SourceTable
[i
]].RefCount
++;
622 static BOOLEAN
DosResizeHandleTable(WORD NewSize
)
628 /* Get the PSP block */
629 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
631 if (NewSize
== PspBlock
->HandleTableSize
)
637 if (PspBlock
->HandleTableSize
> 20)
639 /* Get the segment of the current table */
640 Segment
= (LOWORD(PspBlock
->HandleTablePtr
) >> 4) + HIWORD(PspBlock
->HandleTablePtr
);
644 /* Get the current handle table */
645 HandleTable
= FAR_POINTER(PspBlock
->HandleTablePtr
);
647 /* Copy it to the PSP */
648 RtlCopyMemory(PspBlock
->HandleTable
, HandleTable
, NewSize
);
650 /* Free the memory */
651 DosFreeMemory(Segment
);
653 /* Update the handle table pointer and size */
654 PspBlock
->HandleTableSize
= NewSize
;
655 PspBlock
->HandleTablePtr
= MAKELONG(0x18, CurrentPsp
);
659 /* Resize the memory */
660 if (!DosResizeMemory(Segment
, NewSize
, NULL
))
662 /* Unable to resize, try allocating it somewhere else */
663 Segment
= DosAllocateMemory(NewSize
, NULL
);
664 if (Segment
== 0) return FALSE
;
666 /* Get the new handle table */
667 HandleTable
= SEG_OFF_TO_PTR(Segment
, 0);
669 /* Copy the handles to the new table */
670 RtlCopyMemory(HandleTable
,
671 FAR_POINTER(PspBlock
->HandleTablePtr
),
672 PspBlock
->HandleTableSize
);
674 /* Update the handle table pointer */
675 PspBlock
->HandleTablePtr
= MAKELONG(0, Segment
);
678 /* Update the handle table size */
679 PspBlock
->HandleTableSize
= NewSize
;
682 else if (NewSize
> 20)
684 Segment
= DosAllocateMemory(NewSize
, NULL
);
685 if (Segment
== 0) return FALSE
;
687 /* Get the new handle table */
688 HandleTable
= SEG_OFF_TO_PTR(Segment
, 0);
690 /* Copy the handles from the PSP to the new table */
691 RtlCopyMemory(HandleTable
,
692 FAR_POINTER(PspBlock
->HandleTablePtr
),
693 PspBlock
->HandleTableSize
);
695 /* Update the handle table pointer and size */
696 PspBlock
->HandleTableSize
= NewSize
;
697 PspBlock
->HandleTablePtr
= MAKELONG(0, Segment
);
703 static BOOLEAN
DosCloseHandle(WORD DosHandle
)
709 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle
);
711 /* The system PSP has no handle table */
712 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
714 /* Get a pointer to the handle table */
715 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
716 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
718 /* Make sure the handle is open */
719 if (HandleTable
[DosHandle
] == 0xFF) return FALSE
;
721 /* Decrement the reference count of the SFT entry */
722 SftIndex
= HandleTable
[DosHandle
];
723 DosSystemFileTable
[SftIndex
].RefCount
--;
725 /* Check if the reference count fell to zero */
726 if (!DosSystemFileTable
[SftIndex
].RefCount
)
728 /* Close the file, it's no longer needed */
729 CloseHandle(DosSystemFileTable
[SftIndex
].Handle
);
731 /* Clear the handle */
732 DosSystemFileTable
[SftIndex
].Handle
= INVALID_HANDLE_VALUE
;
735 /* Clear the entry in the JFT */
736 HandleTable
[DosHandle
] = 0xFF;
741 static BOOLEAN
DosDuplicateHandle(WORD OldHandle
, WORD NewHandle
)
747 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
751 /* The system PSP has no handle table */
752 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
754 /* Get a pointer to the handle table */
755 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
756 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
758 /* Make sure the old handle is open */
759 if (HandleTable
[OldHandle
] == 0xFF) return FALSE
;
761 /* Check if the new handle is open */
762 if (HandleTable
[NewHandle
] != 0xFF)
765 DosCloseHandle(NewHandle
);
768 /* Increment the reference count of the SFT entry */
769 SftIndex
= HandleTable
[OldHandle
];
770 DosSystemFileTable
[SftIndex
].RefCount
++;
772 /* Make the new handle point to that SFT entry */
773 HandleTable
[NewHandle
] = SftIndex
;
785 static BOOLEAN
DosChangeDrive(BYTE Drive
)
787 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
789 /* Make sure the drive exists */
790 if (Drive
> (LastDrive
- 'A')) return FALSE
;
792 /* Find the path to the new current directory */
793 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
795 /* Change the current directory of the process */
796 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
798 /* Set the current drive */
799 CurrentDrive
= Drive
;
805 static BOOLEAN
DosChangeDirectory(LPSTR Directory
)
811 /* Make sure the directory path is not too long */
812 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
814 DosLastError
= ERROR_PATH_NOT_FOUND
;
818 /* Get the drive number */
819 DriveNumber
= Directory
[0] - 'A';
821 /* Make sure the drive exists */
822 if (DriveNumber
> (LastDrive
- 'A'))
824 DosLastError
= ERROR_PATH_NOT_FOUND
;
828 /* Get the file attributes */
829 Attributes
= GetFileAttributesA(Directory
);
831 /* Make sure the path exists and is a directory */
832 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
833 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
835 DosLastError
= ERROR_PATH_NOT_FOUND
;
839 /* Check if this is the current drive */
840 if (DriveNumber
== CurrentDrive
)
842 /* Change the directory */
843 if (!SetCurrentDirectoryA(Directory
))
845 DosLastError
= LOWORD(GetLastError());
850 /* Get the directory part of the path */
851 Path
= strchr(Directory
, '\\');
854 /* Skip the backslash */
858 /* Set the directory for the drive */
861 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
865 CurrentDirectories
[DriveNumber
][0] = '\0';
872 /* PUBLIC FUNCTIONS ***********************************************************/
874 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
876 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
877 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
879 RtlZeroMemory(PspBlock
, sizeof(*PspBlock
));
881 /* Set the exit interrupt */
882 PspBlock
->Exit
[0] = 0xCD; // int 0x20
883 PspBlock
->Exit
[1] = 0x20;
885 /* Set the number of the last paragraph */
886 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
888 /* Save the interrupt vectors */
889 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
890 PspBlock
->BreakAddress
= IntVecTable
[0x23];
891 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
893 /* Set the parent PSP */
894 PspBlock
->ParentPsp
= CurrentPsp
;
896 /* Copy the parent handle table */
897 DosCopyHandleTable(PspBlock
->HandleTable
);
899 /* Set the environment block */
900 PspBlock
->EnvBlock
= Environment
;
902 /* Set the handle table pointers to the internal handle table */
903 PspBlock
->HandleTableSize
= 20;
904 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
906 /* Set the DOS version */
907 PspBlock
->DosVersion
= DOS_VERSION
;
909 /* Set the far call opcodes */
910 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
911 PspBlock
->FarCall
[1] = 0x21;
912 PspBlock
->FarCall
[2] = 0xCB; // retf
914 /* Set the command line */
915 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
916 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
917 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
920 DWORD
DosLoadExecutable(IN DOS_EXEC_TYPE LoadType
,
921 IN LPCSTR ExecutablePath
,
922 IN LPCSTR CommandLine
,
923 IN LPCSTR Environment OPTIONAL
,
924 OUT PDWORD StackLocation OPTIONAL
,
925 OUT PDWORD EntryPoint OPTIONAL
)
927 DWORD Result
= ERROR_SUCCESS
;
928 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
929 LPBYTE Address
= NULL
;
933 DWORD i
, FileSize
, ExeSize
;
934 PIMAGE_DOS_HEADER Header
;
935 PDWORD RelocationTable
;
937 LPSTR CmdLinePtr
= (LPSTR
)CommandLine
;
939 DPRINT1("DosLoadExecutable(%d, %s, %s, %s, 0x%08X, 0x%08X)\n",
943 Environment
? Environment
: "n/a",
947 if (LoadType
== DOS_LOAD_OVERLAY
)
949 DPRINT1("Overlay loading is not supported yet.\n");
950 return ERROR_NOT_SUPPORTED
;
953 /* NULL-terminate the command line by removing the return carriage character */
954 while (*CmdLinePtr
&& *CmdLinePtr
!= '\r') CmdLinePtr
++;
957 /* Open a handle to the executable */
958 FileHandle
= CreateFileA(ExecutablePath
,
963 FILE_ATTRIBUTE_NORMAL
,
965 if (FileHandle
== INVALID_HANDLE_VALUE
)
967 Result
= GetLastError();
971 /* Get the file size */
972 FileSize
= GetFileSize(FileHandle
, NULL
);
974 /* Create a mapping object for the file */
975 FileMapping
= CreateFileMapping(FileHandle
,
981 if (FileMapping
== NULL
)
983 Result
= GetLastError();
987 /* Map the file into memory */
988 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
991 Result
= GetLastError();
995 /* Copy the environment block to DOS memory */
996 EnvBlock
= DosCopyEnvironmentBlock(Environment
, ExecutablePath
);
999 Result
= ERROR_NOT_ENOUGH_MEMORY
;
1003 /* Check if this is an EXE file or a COM file */
1004 if (Address
[0] == 'M' && Address
[1] == 'Z')
1008 /* Get the MZ header */
1009 Header
= (PIMAGE_DOS_HEADER
)Address
;
1011 /* Get the base size of the file, in paragraphs (rounded up) */
1012 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
1014 /* Add the PSP size, in paragraphs */
1015 ExeSize
+= sizeof(DOS_PSP
) >> 4;
1017 /* Add the maximum size that should be allocated */
1018 ExeSize
+= Header
->e_maxalloc
;
1020 /* Make sure it does not pass 0xFFFF */
1021 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
1023 /* Reduce the size one by one until the allocation is successful */
1024 for (i
= Header
->e_maxalloc
; i
>= Header
->e_minalloc
; i
--, ExeSize
--)
1026 /* Try to allocate that much memory */
1027 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
1028 if (Segment
!= 0) break;
1031 /* Check if at least the lowest allocation was successful */
1034 Result
= DosLastError
;
1038 /* Initialize the PSP */
1039 DosInitializePsp(Segment
,
1044 /* The process owns its own memory */
1045 DosChangeMemoryOwner(Segment
, Segment
);
1046 DosChangeMemoryOwner(EnvBlock
, Segment
);
1048 /* Copy the program to Segment:0100 */
1049 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1050 Address
+ (Header
->e_cparhdr
<< 4),
1051 min(FileSize
- (Header
->e_cparhdr
<< 4),
1052 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
1054 /* Get the relocation table */
1055 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
1057 /* Perform relocations */
1058 for (i
= 0; i
< Header
->e_crlc
; i
++)
1060 /* Get a pointer to the word that needs to be patched */
1061 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
1062 0x100 + LOWORD(RelocationTable
[i
]));
1064 /* Add the number of the EXE segment to it */
1065 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
1068 if (LoadType
== DOS_LOAD_AND_EXECUTE
)
1070 /* Set the initial segment registers */
1074 /* Set the stack to the location from the header */
1075 setSS(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
);
1076 setSP(Header
->e_sp
);
1079 CurrentPsp
= Segment
;
1080 DiskTransferArea
= MAKELONG(0x80, Segment
);
1081 CpuExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
1089 /* Find the maximum amount of memory that can be allocated */
1090 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
1092 /* Make sure it's enough for the whole program and the PSP */
1093 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
)))
1095 Result
= ERROR_NOT_ENOUGH_MEMORY
;
1099 /* Allocate all of it */
1100 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
1103 Result
= DosLastError
;
1107 /* The process owns its own memory */
1108 DosChangeMemoryOwner(Segment
, Segment
);
1109 DosChangeMemoryOwner(EnvBlock
, Segment
);
1111 /* Copy the program to Segment:0100 */
1112 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
1116 /* Initialize the PSP */
1117 DosInitializePsp(Segment
,
1122 if (LoadType
== DOS_LOAD_AND_EXECUTE
)
1124 /* Set the initial segment registers */
1128 /* Set the stack to the last word of the segment */
1133 * Set the value on the stack to 0, so that a near return
1134 * jumps to PSP:0000 which has the exit code.
1136 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
1139 CurrentPsp
= Segment
;
1140 DiskTransferArea
= MAKELONG(0x80, Segment
);
1141 CpuExecute(Segment
, 0x100);
1146 if (Result
!= ERROR_SUCCESS
)
1148 /* It was not successful, cleanup the DOS memory */
1149 if (EnvBlock
) DosFreeMemory(EnvBlock
);
1150 if (Segment
) DosFreeMemory(Segment
);
1154 if (Address
!= NULL
) UnmapViewOfFile(Address
);
1156 /* Close the file mapping object */
1157 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
1159 /* Close the file handle */
1160 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
1165 DWORD
DosStartProcess(IN LPCSTR ExecutablePath
,
1166 IN LPCSTR CommandLine
,
1167 IN LPCSTR Environment OPTIONAL
)
1171 Result
= DosLoadExecutable(DOS_LOAD_AND_EXECUTE
,
1178 if (Result
!= ERROR_SUCCESS
) goto Quit
;
1180 /* Attach to the console */
1181 VidBiosAttachToConsole(); // FIXME: And in fact, attach the full NTVDM UI to the console
1183 // HACK: Simulate a ENTER key release scancode on the PS/2 port because
1184 // some apps expect to read a key release scancode (> 0x80) when they
1186 IOWriteB(PS2_CONTROL_PORT
, 0xD2); // Next write is for the first PS/2 port
1187 IOWriteB(PS2_DATA_PORT
, 0x80 | 0x1C); // ENTER key release
1189 /* Start simulation */
1190 SetEvent(VdmTaskEvent
);
1193 /* Detach from the console */
1194 VidBiosDetachFromConsole(); // FIXME: And in fact, detach the full NTVDM UI from the console
1201 WORD
DosCreateProcess(DOS_EXEC_TYPE LoadType
,
1203 PDOS_EXEC_PARAM_BLOCK Parameters
)
1207 LPVOID Environment
= NULL
;
1208 VDM_COMMAND_INFO CommandInfo
;
1209 CHAR CmdLine
[MAX_PATH
];
1210 CHAR AppName
[MAX_PATH
];
1211 CHAR PifFile
[MAX_PATH
];
1212 CHAR Desktop
[MAX_PATH
];
1213 CHAR Title
[MAX_PATH
];
1215 STARTUPINFOA StartupInfo
;
1216 PROCESS_INFORMATION ProcessInfo
;
1218 /* Get the binary type */
1219 if (!GetBinaryTypeA(ProgramName
, &BinaryType
)) return GetLastError();
1221 /* Did the caller specify an environment segment? */
1222 if (Parameters
->Environment
)
1224 /* Yes, use it instead of the parent one */
1225 Environment
= SEG_OFF_TO_PTR(Parameters
->Environment
, 0);
1228 /* Set up the startup info structure */
1229 RtlZeroMemory(&StartupInfo
, sizeof(StartupInfo
));
1230 StartupInfo
.cb
= sizeof(StartupInfo
);
1232 /* Create the process */
1233 if (!CreateProcessA(ProgramName
,
1234 FAR_POINTER(Parameters
->CommandLine
),
1244 return GetLastError();
1247 /* Check the type of the program */
1250 /* These are handled by NTVDM */
1251 case SCS_DOS_BINARY
:
1252 case SCS_WOW_BINARY
:
1254 /* Clear the structure */
1255 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1257 /* Initialize the structure members */
1258 CommandInfo
.TaskId
= SessionId
;
1259 CommandInfo
.VDMState
= VDM_FLAG_NESTED_TASK
| VDM_FLAG_DONT_WAIT
;
1260 CommandInfo
.CmdLine
= CmdLine
;
1261 CommandInfo
.CmdLen
= sizeof(CmdLine
);
1262 CommandInfo
.AppName
= AppName
;
1263 CommandInfo
.AppLen
= sizeof(AppName
);
1264 CommandInfo
.PifFile
= PifFile
;
1265 CommandInfo
.PifLen
= sizeof(PifFile
);
1266 CommandInfo
.Desktop
= Desktop
;
1267 CommandInfo
.DesktopLen
= sizeof(Desktop
);
1268 CommandInfo
.Title
= Title
;
1269 CommandInfo
.TitleLen
= sizeof(Title
);
1270 CommandInfo
.Env
= Env
;
1271 CommandInfo
.EnvLen
= sizeof(Env
);
1273 /* Get the VDM command information */
1274 if (!GetNextVDMCommand(&CommandInfo
))
1276 /* Shouldn't happen */
1280 /* Increment the re-entry count */
1281 CommandInfo
.VDMState
= VDM_INC_REENTER_COUNT
;
1282 GetNextVDMCommand(&CommandInfo
);
1284 /* Load the executable */
1285 Result
= DosLoadExecutable(LoadType
,
1289 &Parameters
->StackLocation
,
1290 &Parameters
->EntryPoint
);
1291 if (Result
!= ERROR_SUCCESS
)
1293 DisplayMessage(L
"Could not load '%S'. Error: %u", AppName
, Result
);
1294 // FIXME: Decrement the reenter count. Or, instead, just increment
1295 // the VDM reenter count *only* if this call succeeds...
1301 /* Not handled by NTVDM */
1304 /* Wait for the process to finish executing */
1305 WaitForSingleObject(ProcessInfo
.hProcess
, INFINITE
);
1309 /* Close the handles */
1310 CloseHandle(ProcessInfo
.hProcess
);
1311 CloseHandle(ProcessInfo
.hThread
);
1313 return ERROR_SUCCESS
;
1317 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
1320 WORD McbSegment
= FIRST_MCB_SEGMENT
;
1321 PDOS_MCB CurrentMcb
;
1322 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
1323 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
1325 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1329 /* Check if this PSP is it's own parent */
1330 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
1332 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
1334 /* Close the handle */
1338 /* Free the memory used by the process */
1341 /* Get a pointer to the MCB */
1342 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
1344 /* Make sure the MCB is valid */
1345 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
1347 /* If this block was allocated by the process, free it */
1348 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
+ 1);
1350 /* If this was the last block, quit */
1351 if (CurrentMcb
->BlockType
== 'Z') break;
1353 /* Update the segment and continue */
1354 McbSegment
+= CurrentMcb
->Size
+ 1;
1358 /* Restore the interrupt vectors */
1359 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
1360 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
1361 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
1363 /* Update the current PSP */
1364 if (Psp
== CurrentPsp
)
1366 CurrentPsp
= PspBlock
->ParentPsp
;
1367 if (CurrentPsp
== SYSTEM_PSP
)
1369 ResetEvent(VdmTaskEvent
);
1375 // FIXME: This is probably not the best way to do it
1376 /* Check if this was a nested DOS task */
1377 if (CurrentPsp
!= SYSTEM_PSP
)
1379 VDM_COMMAND_INFO CommandInfo
;
1381 /* Decrement the re-entry count */
1382 CommandInfo
.TaskId
= SessionId
;
1383 CommandInfo
.VDMState
= VDM_DEC_REENTER_COUNT
;
1384 GetNextVDMCommand(&CommandInfo
);
1386 /* Clear the structure */
1387 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1389 /* Update the VDM state of the task */
1390 CommandInfo
.TaskId
= SessionId
;
1391 CommandInfo
.VDMState
= VDM_FLAG_DONT_WAIT
;
1392 GetNextVDMCommand(&CommandInfo
);
1396 /* Save the return code - Normal termination */
1397 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
1399 /* Return control to the parent process */
1400 CpuExecute(HIWORD(PspBlock
->TerminateAddress
),
1401 LOWORD(PspBlock
->TerminateAddress
));
1404 BOOLEAN
DosHandleIoctl(BYTE ControlCode
, WORD FileHandle
)
1406 HANDLE Handle
= DosGetRealHandle(FileHandle
);
1408 if (Handle
== INVALID_HANDLE_VALUE
)
1411 DosLastError
= ERROR_FILE_NOT_FOUND
;
1415 switch (ControlCode
)
1417 /* Get Device Information */
1423 * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
1424 * for a list of possible flags.
1427 if (Handle
== DosSystemFileTable
[DOS_INPUT_HANDLE
].Handle
)
1432 /* It is a device */
1435 else if (Handle
== DosSystemFileTable
[DOS_OUTPUT_HANDLE
].Handle
)
1437 /* Console output */
1440 /* It is a device */
1444 /* Return the device information word */
1449 /* Unsupported control code */
1452 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode
);
1454 DosLastError
= ERROR_INVALID_PARAMETER
;
1460 VOID WINAPI
DosInt20h(LPWORD Stack
)
1462 /* This is the exit interrupt */
1463 DosTerminateProcess(Stack
[STACK_CS
], 0);
1466 VOID WINAPI
DosInt21h(LPWORD Stack
)
1469 SYSTEMTIME SystemTime
;
1471 PDOS_INPUT_BUFFER InputBuffer
;
1472 PDOS_COUNTRY_CODE_BUFFER CountryCodeBuffer
;
1475 /* Check the value in the AH register */
1478 /* Terminate Program */
1481 DosTerminateProcess(Stack
[STACK_CS
], 0);
1485 /* Read Character from STDIN with Echo */
1488 DPRINT("INT 21h, AH = 01h\n");
1490 // FIXME: Under DOS 2+, input / output handle may be redirected!!!!
1492 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
1495 // FIXME: Check whether Ctrl-C / Ctrl-Break is pressed, and call INT 23h if so.
1496 // Check also Ctrl-P and set echo-to-printer flag.
1497 // Ctrl-Z is not interpreted.
1503 /* Write Character to STDOUT */
1506 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1507 Character
= getDL();
1508 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1511 * We return the output character (DOS 2.1+).
1512 * Also, if we're going to output a TAB, then
1513 * don't return a TAB but a SPACE instead.
1514 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1515 * for more information.
1517 setAL(Character
== '\t' ? ' ' : Character
);
1521 /* Read Character from STDAUX */
1524 // FIXME: Really read it from STDAUX!
1525 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1526 // setAL(DosReadCharacter());
1530 /* Write Character to STDAUX */
1533 // FIXME: Really write it to STDAUX!
1534 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1535 // DosPrintCharacter(getDL());
1539 /* Write Character to Printer */
1542 // FIXME: Really write it to printer!
1543 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1544 DPRINT1("0x%p\n", getDL());
1545 DPRINT1("\n\n-----------\n\n");
1549 /* Direct Console I/O */
1552 Character
= getDL();
1554 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1556 if (Character
!= 0xFF)
1559 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1562 * We return the output character (DOS 2.1+).
1563 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1564 * for more information.
1571 if (DosCheckInput())
1573 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
1574 setAL(DosReadCharacter(DOS_INPUT_HANDLE
));
1578 /* No character available */
1579 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
1587 /* Character Input without Echo */
1591 DPRINT("Char input without echo\n");
1593 // FIXME: Under DOS 2+, input handle may be redirected!!!!
1594 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
1596 // FIXME: For 0x07, do not check Ctrl-C/Break.
1597 // For 0x08, do check those control sequences and if needed,
1600 // /* Let the BOP repeat if needed */
1601 // if (getCF()) break;
1607 /* Write string to STDOUT */
1610 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1612 while (*String
!= '$')
1614 DosPrintCharacter(DOS_OUTPUT_HANDLE
, *String
);
1619 * We return the terminating character (DOS 2.1+).
1620 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1621 * for more information.
1623 setAL('$'); // *String
1627 /* Read Buffered Input */
1631 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1633 DPRINT("Read Buffered Input\n");
1635 while (Count
< InputBuffer
->MaxLength
)
1637 // FIXME!! This function should interpret backspaces etc...
1639 /* Try to read a character (wait) */
1640 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
1642 // FIXME: Check whether Ctrl-C / Ctrl-Break is pressed, and call INT 23h if so.
1644 /* Echo the character and append it to the buffer */
1645 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1646 InputBuffer
->Buffer
[Count
] = Character
;
1648 Count
++; /* Carriage returns are also counted */
1650 if (Character
== '\r') break;
1653 /* Update the length */
1654 InputBuffer
->Length
= Count
;
1659 /* Get STDIN Status */
1662 setAL(DosCheckInput() ? 0xFF : 0x00);
1666 /* Flush Buffer and Read STDIN */
1669 BYTE InputFunction
= getAL();
1671 /* Flush STDIN buffer */
1672 DosFlushFileBuffers(DOS_INPUT_HANDLE
);
1675 * If the input function number contained in AL is valid, i.e.
1676 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1677 * recursively with AL == AH.
1679 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1680 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1681 InputFunction
== 0x0A)
1683 /* Call ourselves recursively */
1684 setAH(InputFunction
);
1693 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1695 // TODO: Flush what's needed.
1696 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1698 /* Clear CF in DOS 6 only */
1699 if (PspBlock
->DosVersion
== 0x0006)
1700 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1705 /* Set Default Drive */
1708 DosChangeDrive(getDL());
1709 setAL(LastDrive
- 'A' + 1);
1713 /* NULL Function for CP/M Compatibility */
1717 * This function corresponds to the CP/M BDOS function
1718 * "get bit map of logged drives", which is meaningless
1721 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1722 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1723 * for more information.
1729 /* Get Default Drive */
1732 setAL(CurrentDrive
);
1736 /* Set Disk Transfer Area */
1739 DiskTransferArea
= MAKELONG(getDX(), getDS());
1743 /* NULL Function for CP/M Compatibility */
1748 * Function 0x1D corresponds to the CP/M BDOS function
1749 * "get bit map of read-only drives", which is meaningless
1751 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1752 * for more information.
1754 * Function 0x1E corresponds to the CP/M BDOS function
1755 * "set file attributes", which was meaningless under MS-DOS 1.x.
1756 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1757 * for more information.
1763 /* NULL Function for CP/M Compatibility */
1767 * This function corresponds to the CP/M BDOS function
1768 * "get/set default user (sublibrary) number", which is meaningless
1771 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1772 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1773 * for more information.
1779 /* Set Interrupt Vector */
1782 ULONG FarPointer
= MAKELONG(getDX(), getDS());
1783 DPRINT1("Setting interrupt 0x%02X to %04X:%04X ...\n",
1784 getAL(), HIWORD(FarPointer
), LOWORD(FarPointer
));
1786 /* Write the new far pointer to the IDT */
1787 ((PULONG
)BaseAddress
)[getAL()] = FarPointer
;
1791 /* Create New PSP */
1794 DPRINT1("INT 21h, AH = 26h - Create New PSP is UNIMPLEMENTED\n");
1798 /* Get System Date */
1801 GetLocalTime(&SystemTime
);
1802 setCX(SystemTime
.wYear
);
1803 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1804 setAL(SystemTime
.wDayOfWeek
);
1808 /* Set System Date */
1811 GetLocalTime(&SystemTime
);
1812 SystemTime
.wYear
= getCX();
1813 SystemTime
.wMonth
= getDH();
1814 SystemTime
.wDay
= getDL();
1816 /* Return success or failure */
1817 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1821 /* Get System Time */
1824 GetLocalTime(&SystemTime
);
1825 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1826 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1830 /* Set System Time */
1833 GetLocalTime(&SystemTime
);
1834 SystemTime
.wHour
= getCH();
1835 SystemTime
.wMinute
= getCL();
1836 SystemTime
.wSecond
= getDH();
1837 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1839 /* Return success or failure */
1840 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1844 /* Get Disk Transfer Area */
1847 setES(HIWORD(DiskTransferArea
));
1848 setBX(LOWORD(DiskTransferArea
));
1852 /* Get DOS Version */
1855 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1858 * DOS 2+ - GET DOS VERSION
1859 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1860 * for more information.
1863 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1866 * Return DOS OEM number:
1867 * 0x00 for IBM PC-DOS
1868 * 0x02 for packaged MS-DOS
1874 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1877 * Return version flag:
1878 * 1 << 3 if DOS is in ROM,
1879 * 0 (reserved) if not.
1884 /* Return DOS 24-bit user serial number in BL:CX */
1889 * Return DOS version: Minor:Major in AH:AL
1890 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1892 setAX(PspBlock
->DosVersion
);
1897 /* Extended functionalities */
1900 if (getAL() == 0x06)
1903 * DOS 5+ - GET TRUE VERSION NUMBER
1904 * This function always returns the true version number, unlike
1905 * AH=30h, whose return value may be changed with SETVER.
1906 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1907 * for more information.
1911 * Return the true DOS version: Minor:Major in BH:BL
1912 * The Windows NT DOS box returns BX=3205h (version 5.50).
1914 setBX(NTDOS_VERSION
);
1916 /* DOS revision 0 */
1924 // /* Invalid subfunction */
1931 /* Get Interrupt Vector */
1934 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1936 /* Read the address from the IDT into ES:BX */
1937 setES(HIWORD(FarPointer
));
1938 setBX(LOWORD(FarPointer
));
1942 /* SWITCH character - AVAILDEV */
1945 if (getAL() == 0x00)
1948 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1949 * This setting is ignored by MS-DOS 4.0+.
1950 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1951 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1952 * for more information.
1957 else if (getAL() == 0x01)
1960 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1961 * This setting is ignored by MS-DOS 5+.
1962 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1963 * for more information.
1968 else if (getAL() == 0x02)
1971 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1972 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1973 * for more information.
1978 else if (getAL() == 0x03)
1981 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1982 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1983 * for more information.
1990 /* Invalid subfunction */
1997 /* Get/Set Country-dependent Information */
2000 CountryCodeBuffer
= (PDOS_COUNTRY_CODE_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
2002 if (getAL() == 0x00)
2005 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_IDATE
,
2006 &CountryCodeBuffer
->TimeFormat
,
2007 sizeof(CountryCodeBuffer
->TimeFormat
) / sizeof(TCHAR
));
2010 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2011 setAX(LOWORD(GetLastError()));
2015 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_SCURRENCY
,
2016 &CountryCodeBuffer
->CurrencySymbol
,
2017 sizeof(CountryCodeBuffer
->CurrencySymbol
) / sizeof(TCHAR
));
2020 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2021 setAX(LOWORD(GetLastError()));
2025 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_STHOUSAND
,
2026 &CountryCodeBuffer
->ThousandSep
,
2027 sizeof(CountryCodeBuffer
->ThousandSep
) / sizeof(TCHAR
));
2030 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2031 setAX(LOWORD(GetLastError()));
2035 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_SDECIMAL
,
2036 &CountryCodeBuffer
->DecimalSep
,
2037 sizeof(CountryCodeBuffer
->DecimalSep
) / sizeof(TCHAR
));
2040 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2041 setAX(LOWORD(GetLastError()));
2045 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2049 // TODO: NOT IMPLEMENTED
2056 /* Create Directory */
2059 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
2061 if (CreateDirectoryA(String
, NULL
))
2063 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2067 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2068 setAX(LOWORD(GetLastError()));
2074 /* Remove Directory */
2077 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
2079 if (RemoveDirectoryA(String
))
2081 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2085 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2086 setAX(LOWORD(GetLastError()));
2092 /* Set Current Directory */
2095 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
2097 if (DosChangeDirectory(String
))
2099 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2103 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2104 setAX(DosLastError
);
2110 /* Create or Truncate File */
2114 WORD ErrorCode
= DosCreateFile(&FileHandle
,
2115 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
2119 if (ErrorCode
== ERROR_SUCCESS
)
2121 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2126 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2137 WORD ErrorCode
= DosOpenFile(&FileHandle
,
2138 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
2141 if (ErrorCode
== ERROR_SUCCESS
)
2143 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2148 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2158 if (DosCloseHandle(getBX()))
2160 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2164 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2165 setAX(ERROR_INVALID_HANDLE
);
2171 /* Read from File or Device */
2177 DPRINT("INT 21h, AH = 3Fh\n");
2180 ErrorCode
= DosReadFile(getBX(),
2181 SEG_OFF_TO_PTR(getDS(), getDX()),
2186 if (ErrorCode
== ERROR_SUCCESS
)
2188 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2191 else if (ErrorCode
!= ERROR_NOT_READY
)
2193 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2200 /* Write to File or Device */
2203 WORD BytesWritten
= 0;
2204 WORD ErrorCode
= DosWriteFile(getBX(),
2205 SEG_OFF_TO_PTR(getDS(), getDX()),
2209 if (ErrorCode
== ERROR_SUCCESS
)
2211 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2212 setAX(BytesWritten
);
2216 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2226 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2228 if (demFileDelete(FileName
) == ERROR_SUCCESS
)
2230 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2232 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2233 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2235 setAL(FileName
[0] - 'A');
2239 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2240 setAX(GetLastError());
2250 WORD ErrorCode
= DosSeekFile(getBX(),
2251 MAKELONG(getDX(), getCX()),
2255 if (ErrorCode
== ERROR_SUCCESS
)
2257 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2259 /* Return the new offset in DX:AX */
2260 setDX(HIWORD(NewLocation
));
2261 setAX(LOWORD(NewLocation
));
2265 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2272 /* Get/Set File Attributes */
2276 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2278 if (getAL() == 0x00)
2280 /* Get the attributes */
2281 Attributes
= GetFileAttributesA(FileName
);
2283 /* Check if it failed */
2284 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
2286 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2287 setAX(GetLastError());
2291 /* Return the attributes that DOS can understand */
2292 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2293 setCX(Attributes
& 0x00FF);
2296 else if (getAL() == 0x01)
2298 /* Try to set the attributes */
2299 if (SetFileAttributesA(FileName
, getCL()))
2301 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2305 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2306 setAX(GetLastError());
2311 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2312 setAX(ERROR_INVALID_FUNCTION
);
2321 if (DosHandleIoctl(getAL(), getBX()))
2323 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2327 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2328 setAX(DosLastError
);
2334 /* Duplicate Handle */
2338 HANDLE Handle
= DosGetRealHandle(getBX());
2340 if (Handle
== INVALID_HANDLE_VALUE
)
2342 /* The handle is invalid */
2343 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2344 setAX(ERROR_INVALID_HANDLE
);
2348 /* Open a new handle to the same entry */
2349 NewHandle
= DosOpenHandle(Handle
);
2351 if (NewHandle
== INVALID_DOS_HANDLE
)
2353 /* Too many files open */
2354 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2355 setAX(ERROR_TOO_MANY_OPEN_FILES
);
2359 /* Return the result */
2360 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2365 /* Force Duplicate Handle */
2368 if (DosDuplicateHandle(getBX(), getCX()))
2370 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2374 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2375 setAX(ERROR_INVALID_HANDLE
);
2381 /* Get Current Directory */
2384 BYTE DriveNumber
= getDL();
2385 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
2387 /* Get the real drive number */
2388 if (DriveNumber
== 0)
2390 DriveNumber
= CurrentDrive
;
2394 /* Decrement DriveNumber since it was 1-based */
2398 if (DriveNumber
<= LastDrive
- 'A')
2401 * Copy the current directory into the target buffer.
2402 * It doesn't contain the drive letter and the backslash.
2404 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
2405 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2406 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2410 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2411 setAX(ERROR_INVALID_DRIVE
);
2417 /* Allocate Memory */
2420 WORD MaxAvailable
= 0;
2421 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
2425 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2430 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2431 setAX(DosLastError
);
2432 setBX(MaxAvailable
);
2441 if (DosFreeMemory(getES()))
2443 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2447 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2448 setAX(ERROR_ARENA_TRASHED
);
2454 /* Resize Memory Block */
2459 if (DosResizeMemory(getES(), getBX(), &Size
))
2461 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2465 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2466 setAX(DosLastError
);
2477 DOS_EXEC_TYPE LoadType
= (DOS_EXEC_TYPE
)getAL();
2478 LPSTR ProgramName
= SEG_OFF_TO_PTR(getDS(), getDX());
2479 PDOS_EXEC_PARAM_BLOCK ParamBlock
= SEG_OFF_TO_PTR(getES(), getBX());
2480 WORD ErrorCode
= DosCreateProcess(LoadType
, ProgramName
, ParamBlock
);
2482 if (ErrorCode
== ERROR_SUCCESS
)
2484 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2488 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2496 /* Terminate With Return Code */
2499 DosTerminateProcess(CurrentPsp
, getAL());
2503 /* Get Return Code (ERRORLEVEL) */
2507 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2508 * DosErrorLevel is cleared after being read by this function.
2510 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2511 setAX(DosErrorLevel
);
2512 DosErrorLevel
= 0x0000; // Clear it
2516 /* Find First File */
2519 WORD Result
= (WORD
)demFileFindFirst(FAR_POINTER(DiskTransferArea
),
2520 SEG_OFF_TO_PTR(getDS(), getDX()),
2525 if (Result
== ERROR_SUCCESS
)
2526 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2528 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2533 /* Find Next File */
2536 WORD Result
= (WORD
)demFileFindNext(FAR_POINTER(DiskTransferArea
));
2540 if (Result
== ERROR_SUCCESS
)
2541 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2543 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2548 /* Internal - Set Current Process ID (Set PSP Address) */
2551 // FIXME: Is it really what it's done ??
2552 CurrentPsp
= getBX();
2556 /* Internal - Get Current Process ID (Get PSP Address) */
2558 /* Get Current PSP Address */
2562 * Undocumented AH=51h is identical to the documented AH=62h.
2563 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2564 * and http://www.ctyme.com/intr/rb-3140.htm
2565 * for more information.
2571 /* Internal - Get "List of lists" (SYSVARS) */
2575 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
2576 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
2577 * for more information.
2580 /* Return the DOS "list of lists" in ES:BX */
2584 DisplayMessage(L
"Required for AARD code, do you remember? :P");
2591 LPSTR ExistingFileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2592 LPSTR NewFileName
= (LPSTR
)SEG_OFF_TO_PTR(getES(), getDI());
2595 * See Ralf Brown: http://www.ctyme.com/intr/rb-2990.htm
2596 * for more information.
2599 if (MoveFileA(ExistingFileName
, NewFileName
))
2601 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2605 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2606 setAX(GetLastError());
2612 /* Get/Set Memory Management Options */
2615 if (getAL() == 0x00)
2617 /* Get allocation strategy */
2618 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2619 setAX(DosAllocStrategy
);
2621 else if (getAL() == 0x01)
2623 /* Set allocation strategy */
2625 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2626 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2628 /* Can't set both */
2629 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2630 setAX(ERROR_INVALID_PARAMETER
);
2634 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2636 /* Invalid allocation strategy */
2637 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2638 setAX(ERROR_INVALID_PARAMETER
);
2642 DosAllocStrategy
= getBL();
2643 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2645 else if (getAL() == 0x02)
2647 /* Get UMB link state */
2648 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2649 setAL(DosUmbLinked
? 0x01 : 0x00);
2651 else if (getAL() == 0x03)
2653 /* Set UMB link state */
2654 if (getBX()) DosLinkUmb();
2655 else DosUnlinkUmb();
2656 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2660 /* Invalid or unsupported function */
2661 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2662 setAX(ERROR_INVALID_FUNCTION
);
2668 /* Get Extended Error Information */
2671 DPRINT1("INT 21h, AH = 59h, BX = %04Xh - Get Extended Error Information is UNIMPLEMENTED\n",
2676 /* Create Temporary File */
2679 LPSTR PathName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2680 LPSTR FileName
= PathName
; // The buffer for the path and the full file name is the same.
2686 * See Ralf Brown: http://www.ctyme.com/intr/rb-3014.htm
2687 * for more information.
2690 // FIXME: Check for buffer validity?
2691 // It should be a ASCIZ path ending with a '\' + 13 zero bytes
2692 // to receive the generated filename.
2694 /* First create the temporary file */
2695 uRetVal
= GetTempFileNameA(PathName
, NULL
, 0, FileName
);
2698 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2699 setAX(GetLastError());
2703 /* Now try to open it in read/write access */
2704 ErrorCode
= DosOpenFile(&FileHandle
, FileName
, 2);
2705 if (ErrorCode
== ERROR_SUCCESS
)
2707 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2712 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2719 /* Create New File */
2723 WORD ErrorCode
= DosCreateFile(&FileHandle
,
2724 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
2728 if (ErrorCode
== ERROR_SUCCESS
)
2730 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2735 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2742 /* Lock/Unlock Region of File */
2745 HANDLE Handle
= DosGetRealHandle(getBX());
2747 if (Handle
== INVALID_HANDLE_VALUE
)
2749 /* The handle is invalid */
2750 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2751 setAX(ERROR_INVALID_HANDLE
);
2755 if (getAL() == 0x00)
2757 /* Lock region of file */
2758 if (LockFile(Handle
,
2759 MAKELONG(getCX(), getDX()), 0,
2760 MAKELONG(getSI(), getDI()), 0))
2762 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2766 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2767 setAX(GetLastError());
2770 else if (getAL() == 0x01)
2772 /* Unlock region of file */
2773 if (UnlockFile(Handle
,
2774 MAKELONG(getCX(), getDX()), 0,
2775 MAKELONG(getSI(), getDI()), 0))
2777 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2781 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2782 setAX(GetLastError());
2787 /* Invalid subfunction */
2788 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2789 setAX(ERROR_INVALID_FUNCTION
);
2795 /* Canonicalize File Name or Path */
2799 * See Ralf Brown: http://www.ctyme.com/intr/rb-3137.htm
2800 * for more information.
2804 * We suppose that the DOS app gave to us a valid
2805 * 128-byte long buffer for the canonicalized name.
2807 DWORD dwRetVal
= GetFullPathNameA(SEG_OFF_TO_PTR(getDS(), getSI()),
2809 SEG_OFF_TO_PTR(getES(), getDI()),
2813 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2814 setAX(GetLastError());
2818 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2822 // FIXME: Convert the full path name into short version.
2823 // We cannot reliably use GetShortPathName, because it fails
2824 // if the path name given doesn't exist. However this DOS
2825 // function AH=60h should be able to work even for non-existing
2826 // path and file names.
2831 /* Set Handle Count */
2834 if (!DosResizeHandleTable(getBX()))
2836 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2837 setAX(DosLastError
);
2839 else Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2849 * Function 6Ah is identical to function 68h,
2850 * and sets AH to 68h if success.
2851 * See Ralf Brown: http://www.ctyme.com/intr/rb-3176.htm
2852 * for more information.
2856 if (DosFlushFileBuffers(getBX()))
2858 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2862 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2863 setAX(GetLastError());
2869 /* Extended Open/Create */
2873 WORD CreationStatus
;
2876 /* Check for AL == 00 */
2877 if (getAL() != 0x00)
2879 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2880 setAX(ERROR_INVALID_FUNCTION
);
2885 * See Ralf Brown: http://www.ctyme.com/intr/rb-3179.htm
2886 * for the full detailed description.
2888 * WARNING: BH contains some extended flags that are NOT SUPPORTED.
2891 ErrorCode
= DosCreateFileEx(&FileHandle
,
2893 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getSI()),
2898 if (ErrorCode
== ERROR_SUCCESS
)
2900 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2901 setCX(CreationStatus
);
2906 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2916 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2919 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2920 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2925 VOID WINAPI
DosBreakInterrupt(LPWORD Stack
)
2927 UNREFERENCED_PARAMETER(Stack
);
2929 /* Stop the VDM task */
2930 ResetEvent(VdmTaskEvent
);
2934 VOID WINAPI
DosFastConOut(LPWORD Stack
)
2937 * This is the DOS 2+ Fast Console Output Interrupt.
2938 * The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
2940 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2941 * for more information.
2944 /* Save AX and BX */
2945 USHORT AX
= getAX();
2946 USHORT BX
= getBX();
2949 * Set the parameters:
2950 * AL contains the character to print (already set),
2951 * BL contains the character attribute,
2952 * BH contains the video page to use.
2954 setBL(DOS_CHAR_ATTRIBUTE
);
2955 setBH(Bda
->VideoPage
);
2957 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
2959 Int32Call(&DosContext
, BIOS_VIDEO_INTERRUPT
);
2961 /* Restore AX and BX */
2966 VOID WINAPI
DosInt2Fh(LPWORD Stack
)
2968 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2970 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2973 BOOLEAN
DosKRNLInitialize(VOID
)
2979 CHAR CurrentDirectory
[MAX_PATH
];
2980 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2986 /* Clear the current directory buffer */
2987 RtlZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2989 /* Get the current directory */
2990 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2992 // TODO: Use some kind of default path?
2996 /* Convert that to a DOS path */
2997 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2999 // TODO: Use some kind of default path?
3004 CurrentDrive
= DosDirectory
[0] - 'A';
3006 /* Get the directory part of the path */
3007 Path
= strchr(DosDirectory
, '\\');
3010 /* Skip the backslash */
3014 /* Set the directory */
3017 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
3020 /* Read CONFIG.SYS */
3021 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
3024 while (fgetws(Buffer
, 256, Stream
))
3026 // TODO: Parse the line
3031 /* Initialize the SFT */
3032 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
3034 DosSystemFileTable
[i
].Handle
= INVALID_HANDLE_VALUE
;
3035 DosSystemFileTable
[i
].RefCount
= 0;
3038 /* Get handles to standard I/O devices */
3039 DosSystemFileTable
[0].Handle
= GetStdHandle(STD_INPUT_HANDLE
);
3040 DosSystemFileTable
[1].Handle
= GetStdHandle(STD_OUTPUT_HANDLE
);
3041 DosSystemFileTable
[2].Handle
= GetStdHandle(STD_ERROR_HANDLE
);
3043 /* Initialize the reference counts */
3044 DosSystemFileTable
[0].RefCount
=
3045 DosSystemFileTable
[1].RefCount
=
3046 DosSystemFileTable
[2].RefCount
= 1;
3050 /* Initialize the callback context */
3051 InitializeContext(&DosContext
, 0x0070, 0x0000);
3053 /* Register the DOS 32-bit Interrupts */
3054 RegisterDosInt32(0x20, DosInt20h
);
3055 RegisterDosInt32(0x21, DosInt21h
);
3056 // RegisterDosInt32(0x22, DosInt22h ); // Termination
3057 RegisterDosInt32(0x23, DosBreakInterrupt
); // Ctrl-C / Ctrl-Break
3058 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
3059 RegisterDosInt32(0x29, DosFastConOut
); // DOS 2+ Fast Console Output
3060 RegisterDosInt32(0x2F, DosInt2Fh
);