2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: dos/dos32krnl/dos.c
5 * PURPOSE: VDM DOS Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
9 /* INCLUDES *******************************************************************/
19 #include "bios/bios.h"
20 #include "registers.h"
22 /* PRIVATE VARIABLES **********************************************************/
24 CALLBACK16 DosContext
;
26 static WORD CurrentPsp
= SYSTEM_PSP
;
27 static WORD DosLastError
= 0;
28 static DWORD DiskTransferArea
;
29 /*static*/ BYTE CurrentDrive
;
30 static CHAR LastDrive
= 'E';
31 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
37 } DosSystemFileTable
[DOS_SFT_SIZE
];
39 static BYTE DosAllocStrategy
= DOS_ALLOC_BEST_FIT
;
40 static BOOLEAN DosUmbLinked
= FALSE
;
41 static WORD DosErrorLevel
= 0x0000;
43 /* PRIVATE FUNCTIONS **********************************************************/
46 * Memory management functions
48 static VOID
DosCombineFreeBlocks(WORD StartBlock
)
50 PDOS_MCB CurrentMcb
= SEGMENT_TO_MCB(StartBlock
), NextMcb
;
52 /* If this is the last block or it's not free, quit */
53 if (CurrentMcb
->BlockType
== 'Z' || CurrentMcb
->OwnerPsp
!= 0) return;
57 /* Get a pointer to the next MCB */
58 NextMcb
= SEGMENT_TO_MCB(StartBlock
+ CurrentMcb
->Size
+ 1);
60 /* Check if the next MCB is free */
61 if (NextMcb
->OwnerPsp
== 0)
64 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
65 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
66 NextMcb
->BlockType
= 'I';
70 /* No more adjoining free blocks */
76 static WORD
DosAllocateMemory(WORD Size
, WORD
*MaxAvailable
)
78 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
, MaxSize
= 0;
79 PDOS_MCB CurrentMcb
, NextMcb
;
80 BOOLEAN SearchUmb
= FALSE
;
82 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size
);
84 if (DosUmbLinked
&& (DosAllocStrategy
& (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
)))
86 /* Search UMB first */
87 Segment
= UMB_START_SEGMENT
;
93 /* Get a pointer to the MCB */
94 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
96 /* Make sure it's valid */
97 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
99 DPRINT("The DOS memory arena is corrupted!\n");
100 DosLastError
= ERROR_ARENA_TRASHED
;
104 /* Only check free blocks */
105 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
107 /* Combine this free block with adjoining free blocks */
108 DosCombineFreeBlocks(Segment
);
110 /* Update the maximum block size */
111 if (CurrentMcb
->Size
> MaxSize
) MaxSize
= CurrentMcb
->Size
;
113 /* Check if this block is big enough */
114 if (CurrentMcb
->Size
< Size
) goto Next
;
116 switch (DosAllocStrategy
& 0x3F)
118 case DOS_ALLOC_FIRST_FIT
:
120 /* For first fit, stop immediately */
125 case DOS_ALLOC_BEST_FIT
:
127 /* For best fit, update the smallest block found so far */
128 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
136 case DOS_ALLOC_LAST_FIT
:
138 /* For last fit, make the current block the result, but keep searching */
145 /* If this was the last MCB in the chain, quit */
146 if (CurrentMcb
->BlockType
== 'Z')
148 /* Check if nothing was found while searching through UMBs */
149 if ((Result
== 0) && SearchUmb
&& (DosAllocStrategy
& DOS_ALLOC_HIGH_LOW
))
151 /* Search low memory */
152 Segment
= FIRST_MCB_SEGMENT
;
159 /* Otherwise, update the segment and continue */
160 Segment
+= CurrentMcb
->Size
+ 1;
165 /* If we didn't find a free block, return 0 */
168 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
169 if (MaxAvailable
) *MaxAvailable
= MaxSize
;
173 /* Get a pointer to the MCB */
174 CurrentMcb
= SEGMENT_TO_MCB(Result
);
176 /* Check if the block is larger than requested */
177 if (CurrentMcb
->Size
> Size
)
179 /* It is, split it into two blocks */
180 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
182 /* Initialize the new MCB structure */
183 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
184 NextMcb
->Size
= CurrentMcb
->Size
- Size
- 1;
185 NextMcb
->OwnerPsp
= 0;
187 /* Update the current block */
188 CurrentMcb
->BlockType
= 'M';
189 CurrentMcb
->Size
= Size
;
192 /* Take ownership of the block */
193 CurrentMcb
->OwnerPsp
= CurrentPsp
;
195 /* Return the segment of the data portion of the block */
199 static BOOLEAN
DosResizeMemory(WORD BlockData
, WORD NewSize
, WORD
*MaxAvailable
)
201 BOOLEAN Success
= TRUE
;
202 WORD Segment
= BlockData
- 1, ReturnSize
= 0, NextSegment
;
203 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), NextMcb
;
205 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
209 /* Make sure this is a valid, allocated block */
210 if ((Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') || Mcb
->OwnerPsp
== 0)
213 DosLastError
= ERROR_INVALID_HANDLE
;
217 ReturnSize
= Mcb
->Size
;
219 /* Check if we need to expand or contract the block */
220 if (NewSize
> Mcb
->Size
)
222 /* We can't expand the last block */
223 if (Mcb
->BlockType
!= 'M')
229 /* Get the pointer and segment of the next MCB */
230 NextSegment
= Segment
+ Mcb
->Size
+ 1;
231 NextMcb
= SEGMENT_TO_MCB(NextSegment
);
233 /* Make sure the next segment is free */
234 if (NextMcb
->OwnerPsp
!= 0)
236 DPRINT("Cannot expand memory block: next segment is not free!\n");
237 DosLastError
= ERROR_NOT_ENOUGH_MEMORY
;
242 /* Combine this free block with adjoining free blocks */
243 DosCombineFreeBlocks(NextSegment
);
245 /* Set the maximum possible size of the block */
246 ReturnSize
+= NextMcb
->Size
+ 1;
248 /* Maximize the current block */
249 Mcb
->Size
= ReturnSize
;
250 Mcb
->BlockType
= NextMcb
->BlockType
;
252 /* Invalidate the next block */
253 NextMcb
->BlockType
= 'I';
255 /* Check if the block is larger than requested */
256 if (Mcb
->Size
> NewSize
)
258 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
262 /* It is, split it into two blocks */
263 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
265 /* Initialize the new MCB structure */
266 NextMcb
->BlockType
= Mcb
->BlockType
;
267 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
268 NextMcb
->OwnerPsp
= 0;
270 /* Update the current block */
271 Mcb
->BlockType
= 'M';
275 else if (NewSize
< Mcb
->Size
)
277 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
281 /* Just split the block */
282 NextMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
283 NextMcb
->BlockType
= Mcb
->BlockType
;
284 NextMcb
->Size
= Mcb
->Size
- NewSize
- 1;
285 NextMcb
->OwnerPsp
= 0;
288 Mcb
->BlockType
= 'M';
293 /* Check if the operation failed */
296 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
299 /* Return the maximum possible size */
300 if (MaxAvailable
) *MaxAvailable
= ReturnSize
;
306 static BOOLEAN
DosFreeMemory(WORD BlockData
)
308 PDOS_MCB Mcb
= SEGMENT_TO_MCB(BlockData
- 1);
310 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData
);
312 /* Make sure the MCB is valid */
313 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z')
315 DPRINT("MCB block type '%c' not valid!\n", Mcb
->BlockType
);
319 /* Mark the block as free */
325 static BOOLEAN
DosLinkUmb(VOID
)
327 DWORD Segment
= FIRST_MCB_SEGMENT
;
328 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
330 DPRINT("Linking UMB\n");
332 /* Check if UMBs are already linked */
333 if (DosUmbLinked
) return FALSE
;
335 /* Find the last block */
336 while ((Mcb
->BlockType
== 'M') && (Segment
<= 0xFFFF))
338 Segment
+= Mcb
->Size
+ 1;
339 Mcb
= SEGMENT_TO_MCB(Segment
);
342 /* Make sure it's valid */
343 if (Mcb
->BlockType
!= 'Z') return FALSE
;
345 /* Connect the MCB with the UMB chain */
346 Mcb
->BlockType
= 'M';
352 static BOOLEAN
DosUnlinkUmb(VOID
)
354 DWORD Segment
= FIRST_MCB_SEGMENT
;
355 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
357 DPRINT("Unlinking UMB\n");
359 /* Check if UMBs are already unlinked */
360 if (!DosUmbLinked
) return FALSE
;
362 /* Find the block preceding the MCB that links it with the UMB chain */
363 while (Segment
<= 0xFFFF)
365 if ((Segment
+ Mcb
->Size
) == (FIRST_MCB_SEGMENT
+ USER_MEMORY_SIZE
))
367 /* This is the last non-UMB segment */
371 /* Advance to the next MCB */
372 Segment
+= Mcb
->Size
+ 1;
373 Mcb
= SEGMENT_TO_MCB(Segment
);
376 /* Mark the MCB as the last MCB */
377 Mcb
->BlockType
= 'Z';
379 DosUmbLinked
= FALSE
;
383 static VOID
DosChangeMemoryOwner(WORD Segment
, WORD NewOwner
)
385 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
- 1);
387 /* Just set the owner */
388 Mcb
->OwnerPsp
= NewOwner
;
391 static WORD
DosCopyEnvironmentBlock(LPCVOID Environment
, LPCSTR ProgramName
)
393 PCHAR Ptr
, DestBuffer
= NULL
;
397 Ptr
= (PCHAR
)Environment
;
399 /* Calculate the size of the environment block */
402 TotalSize
+= strlen(Ptr
) + 1;
403 Ptr
+= strlen(Ptr
) + 1;
407 /* Add the string buffer size */
408 TotalSize
+= strlen(ProgramName
) + 1;
410 /* Allocate the memory for the environment block */
411 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
412 if (!DestSegment
) return 0;
414 Ptr
= (PCHAR
)Environment
;
416 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
419 /* Copy the string */
420 strcpy(DestBuffer
, Ptr
);
422 /* Advance to the next string */
423 DestBuffer
+= strlen(Ptr
);
424 Ptr
+= strlen(Ptr
) + 1;
426 /* Put a zero after the string */
430 /* Set the final zero */
433 /* Copy the program name after the environment block */
434 strcpy(DestBuffer
, ProgramName
);
444 /* Taken from base/shell/cmd/console.c */
445 BOOL
IsConsoleHandle(HANDLE hHandle
)
449 /* Check whether the handle may be that of a console... */
450 if ((GetFileType(hHandle
) & FILE_TYPE_CHAR
) == 0) return FALSE
;
453 * It may be. Perform another test... The idea comes from the
454 * MSDN description of the WriteConsole API:
456 * "WriteConsole fails if it is used with a standard handle
457 * that is redirected to a file. If an application processes
458 * multilingual output that can be redirected, determine whether
459 * the output handle is a console handle (one method is to call
460 * the GetConsoleMode function and check whether it succeeds).
461 * If the handle is a console handle, call WriteConsole. If the
462 * handle is not a console handle, the output is redirected and
463 * you should call WriteFile to perform the I/O."
465 return GetConsoleMode(hHandle
, &dwMode
);
468 WORD
DosOpenHandle(HANDLE Handle
)
475 /* The system PSP has no handle table */
476 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_DOS_HANDLE
;
478 /* Get a pointer to the handle table */
479 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
480 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
482 /* Find a free entry in the JFT */
483 for (DosHandle
= 0; DosHandle
< PspBlock
->HandleTableSize
; DosHandle
++)
485 if (HandleTable
[DosHandle
] == 0xFF) break;
488 /* If there are no free entries, fail */
489 if (DosHandle
== PspBlock
->HandleTableSize
) return INVALID_DOS_HANDLE
;
491 /* Check if the handle is already in the SFT */
492 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
494 /* Check if this is the same handle */
495 if (DosSystemFileTable
[i
].Handle
!= Handle
) continue;
497 /* Already in the table, reference it */
498 DosSystemFileTable
[i
].RefCount
++;
500 /* Set the JFT entry to that SFT index */
501 HandleTable
[DosHandle
] = i
;
503 /* Return the new handle */
507 /* Add the handle to the SFT */
508 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
510 /* Make sure this is an empty table entry */
511 if (DosSystemFileTable
[i
].Handle
!= INVALID_HANDLE_VALUE
) continue;
513 /* Initialize the empty table entry */
514 DosSystemFileTable
[i
].Handle
= Handle
;
515 DosSystemFileTable
[i
].RefCount
= 1;
517 /* Set the JFT entry to that SFT index */
518 HandleTable
[DosHandle
] = i
;
520 /* Return the new handle */
524 /* The SFT is full */
525 return INVALID_DOS_HANDLE
;
528 HANDLE
DosGetRealHandle(WORD DosHandle
)
533 /* The system PSP has no handle table */
534 if (CurrentPsp
== SYSTEM_PSP
) return INVALID_HANDLE_VALUE
;
536 /* Get a pointer to the handle table */
537 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
538 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
540 /* Make sure the handle is open */
541 if (HandleTable
[DosHandle
] == 0xFF) return INVALID_HANDLE_VALUE
;
543 /* Return the Win32 handle */
544 return DosSystemFileTable
[HandleTable
[DosHandle
]].Handle
;
547 static VOID
DosCopyHandleTable(LPBYTE DestinationTable
)
553 /* Clear the table first */
554 for (i
= 0; i
< 20; i
++) DestinationTable
[i
] = 0xFF;
556 /* Check if this is the initial process */
557 if (CurrentPsp
== SYSTEM_PSP
)
559 /* Set up the standard I/O devices */
560 for (i
= 0; i
<= 2; i
++)
562 /* Set the index in the SFT */
563 DestinationTable
[i
] = (BYTE
)i
;
565 /* Increase the reference count */
566 DosSystemFileTable
[i
].RefCount
++;
573 /* Get the parent PSP block and handle table */
574 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
575 SourceTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
577 /* Copy the first 20 handles into the new table */
578 for (i
= 0; i
< 20; i
++)
580 DestinationTable
[i
] = SourceTable
[i
];
582 /* Increase the reference count */
583 DosSystemFileTable
[SourceTable
[i
]].RefCount
++;
587 static BOOLEAN
DosCloseHandle(WORD DosHandle
)
593 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle
);
595 /* The system PSP has no handle table */
596 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
598 /* Get a pointer to the handle table */
599 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
600 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
602 /* Make sure the handle is open */
603 if (HandleTable
[DosHandle
] == 0xFF) return FALSE
;
605 /* Decrement the reference count of the SFT entry */
606 SftIndex
= HandleTable
[DosHandle
];
607 DosSystemFileTable
[SftIndex
].RefCount
--;
609 /* Check if the reference count fell to zero */
610 if (!DosSystemFileTable
[SftIndex
].RefCount
)
612 /* Close the file, it's no longer needed */
613 CloseHandle(DosSystemFileTable
[SftIndex
].Handle
);
615 /* Clear the handle */
616 DosSystemFileTable
[SftIndex
].Handle
= INVALID_HANDLE_VALUE
;
619 /* Clear the entry in the JFT */
620 HandleTable
[DosHandle
] = 0xFF;
625 static BOOLEAN
DosDuplicateHandle(WORD OldHandle
, WORD NewHandle
)
631 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
635 /* The system PSP has no handle table */
636 if (CurrentPsp
== SYSTEM_PSP
) return FALSE
;
638 /* Get a pointer to the handle table */
639 PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
640 HandleTable
= (LPBYTE
)FAR_POINTER(PspBlock
->HandleTablePtr
);
642 /* Make sure the old handle is open */
643 if (HandleTable
[OldHandle
] == 0xFF) return FALSE
;
645 /* Check if the new handle is open */
646 if (HandleTable
[NewHandle
] != 0xFF)
649 DosCloseHandle(NewHandle
);
652 /* Increment the reference count of the SFT entry */
653 SftIndex
= HandleTable
[OldHandle
];
654 DosSystemFileTable
[SftIndex
].RefCount
++;
656 /* Make the new handle point to that SFT entry */
657 HandleTable
[NewHandle
] = SftIndex
;
669 static BOOLEAN
DosChangeDrive(BYTE Drive
)
671 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
673 /* Make sure the drive exists */
674 if (Drive
> (LastDrive
- 'A')) return FALSE
;
676 /* Find the path to the new current directory */
677 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
679 /* Change the current directory of the process */
680 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
682 /* Set the current drive */
683 CurrentDrive
= Drive
;
689 static BOOLEAN
DosChangeDirectory(LPSTR Directory
)
695 /* Make sure the directory path is not too long */
696 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
698 DosLastError
= ERROR_PATH_NOT_FOUND
;
702 /* Get the drive number */
703 DriveNumber
= Directory
[0] - 'A';
705 /* Make sure the drive exists */
706 if (DriveNumber
> (LastDrive
- 'A'))
708 DosLastError
= ERROR_PATH_NOT_FOUND
;
712 /* Get the file attributes */
713 Attributes
= GetFileAttributesA(Directory
);
715 /* Make sure the path exists and is a directory */
716 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
717 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
719 DosLastError
= ERROR_PATH_NOT_FOUND
;
723 /* Check if this is the current drive */
724 if (DriveNumber
== CurrentDrive
)
726 /* Change the directory */
727 if (!SetCurrentDirectoryA(Directory
))
729 DosLastError
= LOWORD(GetLastError());
734 /* Get the directory part of the path */
735 Path
= strchr(Directory
, '\\');
738 /* Skip the backslash */
742 /* Set the directory for the drive */
745 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
749 CurrentDirectories
[DriveNumber
][0] = '\0';
756 /* PUBLIC FUNCTIONS ***********************************************************/
758 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
760 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
761 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
763 ZeroMemory(PspBlock
, sizeof(DOS_PSP
));
765 /* Set the exit interrupt */
766 PspBlock
->Exit
[0] = 0xCD; // int 0x20
767 PspBlock
->Exit
[1] = 0x20;
769 /* Set the number of the last paragraph */
770 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
772 /* Save the interrupt vectors */
773 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
774 PspBlock
->BreakAddress
= IntVecTable
[0x23];
775 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
777 /* Set the parent PSP */
778 PspBlock
->ParentPsp
= CurrentPsp
;
780 /* Copy the parent handle table */
781 DosCopyHandleTable(PspBlock
->HandleTable
);
783 /* Set the environment block */
784 PspBlock
->EnvBlock
= Environment
;
786 /* Set the handle table pointers to the internal handle table */
787 PspBlock
->HandleTableSize
= 20;
788 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
790 /* Set the DOS version */
791 PspBlock
->DosVersion
= DOS_VERSION
;
793 /* Set the far call opcodes */
794 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
795 PspBlock
->FarCall
[1] = 0x21;
796 PspBlock
->FarCall
[2] = 0xCB; // retf
798 /* Set the command line */
799 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
800 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
801 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
804 DWORD
DosLoadExecutable(IN DOS_EXEC_TYPE LoadType
,
805 IN LPCSTR ExecutablePath
,
806 IN LPCSTR CommandLine
,
807 IN PVOID Environment
,
808 OUT PDWORD StackLocation OPTIONAL
,
809 OUT PDWORD EntryPoint OPTIONAL
)
811 DWORD Result
= ERROR_SUCCESS
;
812 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
813 LPBYTE Address
= NULL
;
817 DWORD i
, FileSize
, ExeSize
;
818 PIMAGE_DOS_HEADER Header
;
819 PDWORD RelocationTable
;
822 DPRINT1("DosLoadExecutable(%d, %s, %s, %s, 0x%08X, 0x%08X)\n",
830 if (LoadType
== DOS_LOAD_OVERLAY
)
832 DPRINT1("Overlay loading is not supported yet.\n");
833 return ERROR_NOT_SUPPORTED
;
836 /* Open a handle to the executable */
837 FileHandle
= CreateFileA(ExecutablePath
,
842 FILE_ATTRIBUTE_NORMAL
,
844 if (FileHandle
== INVALID_HANDLE_VALUE
)
846 Result
= GetLastError();
850 /* Get the file size */
851 FileSize
= GetFileSize(FileHandle
, NULL
);
853 /* Create a mapping object for the file */
854 FileMapping
= CreateFileMapping(FileHandle
,
860 if (FileMapping
== NULL
)
862 Result
= GetLastError();
866 /* Map the file into memory */
867 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
870 Result
= GetLastError();
874 /* Copy the environment block to DOS memory */
875 EnvBlock
= DosCopyEnvironmentBlock(Environment
, ExecutablePath
);
878 Result
= ERROR_NOT_ENOUGH_MEMORY
;
882 /* Check if this is an EXE file or a COM file */
883 if (Address
[0] == 'M' && Address
[1] == 'Z')
887 /* Get the MZ header */
888 Header
= (PIMAGE_DOS_HEADER
)Address
;
890 /* Get the base size of the file, in paragraphs (rounded up) */
891 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
893 /* Add the PSP size, in paragraphs */
894 ExeSize
+= sizeof(DOS_PSP
) >> 4;
896 /* Add the maximum size that should be allocated */
897 ExeSize
+= Header
->e_maxalloc
;
899 /* Make sure it does not pass 0xFFFF */
900 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
902 /* Reduce the size one by one until the allocation is successful */
903 for (i
= Header
->e_maxalloc
; i
>= Header
->e_minalloc
; i
--, ExeSize
--)
905 /* Try to allocate that much memory */
906 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
907 if (Segment
!= 0) break;
910 /* Check if at least the lowest allocation was successful */
913 Result
= ERROR_NOT_ENOUGH_MEMORY
;
917 /* Initialize the PSP */
918 DosInitializePsp(Segment
,
923 /* The process owns its own memory */
924 DosChangeMemoryOwner(Segment
, Segment
);
925 DosChangeMemoryOwner(EnvBlock
, Segment
);
927 /* Copy the program to Segment:0100 */
928 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
929 Address
+ (Header
->e_cparhdr
<< 4),
930 min(FileSize
- (Header
->e_cparhdr
<< 4),
931 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
933 /* Get the relocation table */
934 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
936 /* Perform relocations */
937 for (i
= 0; i
< Header
->e_crlc
; i
++)
939 /* Get a pointer to the word that needs to be patched */
940 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
941 0x100 + LOWORD(RelocationTable
[i
]));
943 /* Add the number of the EXE segment to it */
944 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
947 if (LoadType
== DOS_LOAD_AND_EXECUTE
)
949 /* Set the initial segment registers */
953 /* Set the stack to the location from the header */
954 EmulatorSetStack(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
,
958 CurrentPsp
= Segment
;
959 DiskTransferArea
= MAKELONG(0x80, Segment
);
960 EmulatorExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
968 /* Find the maximum amount of memory that can be allocated */
969 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
971 /* Make sure it's enough for the whole program and the PSP */
972 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
)))
974 Result
= ERROR_NOT_ENOUGH_MEMORY
;
978 /* Allocate all of it */
979 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
982 Result
= ERROR_ARENA_TRASHED
;
986 /* The process owns its own memory */
987 DosChangeMemoryOwner(Segment
, Segment
);
988 DosChangeMemoryOwner(EnvBlock
, Segment
);
990 /* Copy the program to Segment:0100 */
991 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
995 /* Initialize the PSP */
996 DosInitializePsp(Segment
,
1001 if (LoadType
== DOS_LOAD_AND_EXECUTE
)
1003 /* Set the initial segment registers */
1007 /* Set the stack to the last word of the segment */
1008 EmulatorSetStack(Segment
, 0xFFFE);
1011 * Set the value on the stack to 0, so that a near return
1012 * jumps to PSP:0000 which has the exit code.
1014 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
1017 CurrentPsp
= Segment
;
1018 DiskTransferArea
= MAKELONG(0x80, Segment
);
1019 EmulatorExecute(Segment
, 0x100);
1024 if (Result
!= ERROR_SUCCESS
)
1026 /* It was not successful, cleanup the DOS memory */
1027 if (EnvBlock
) DosFreeMemory(EnvBlock
);
1028 if (Segment
) DosFreeMemory(Segment
);
1032 if (Address
!= NULL
) UnmapViewOfFile(Address
);
1034 /* Close the file mapping object */
1035 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
1037 /* Close the file handle */
1038 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
1043 DWORD
DosStartProcess(IN LPCSTR ExecutablePath
,
1044 IN LPCSTR CommandLine
,
1045 IN PVOID Environment
)
1049 Result
= DosLoadExecutable(DOS_LOAD_AND_EXECUTE
,
1056 if (Result
!= ERROR_SUCCESS
) goto Quit
;
1058 /* Attach to the console */
1059 VidBiosAttachToConsole(); // FIXME: And in fact, attach the full NTVDM UI to the console
1061 /* Start simulation */
1062 SetEvent(VdmTaskEvent
);
1065 /* Detach from the console */
1066 VidBiosDetachFromConsole(); // FIXME: And in fact, detach the full NTVDM UI from the console
1073 WORD
DosCreateProcess(DOS_EXEC_TYPE LoadType
,
1075 PDOS_EXEC_PARAM_BLOCK Parameters
)
1079 LPVOID Environment
= NULL
;
1080 VDM_COMMAND_INFO CommandInfo
;
1081 CHAR CmdLine
[MAX_PATH
];
1082 CHAR AppName
[MAX_PATH
];
1083 CHAR PifFile
[MAX_PATH
];
1084 CHAR Desktop
[MAX_PATH
];
1085 CHAR Title
[MAX_PATH
];
1087 STARTUPINFOA StartupInfo
;
1088 PROCESS_INFORMATION ProcessInfo
;
1090 /* Get the binary type */
1091 if (!GetBinaryTypeA(ProgramName
, &BinaryType
)) return GetLastError();
1093 /* Did the caller specify an environment segment? */
1094 if (Parameters
->Environment
)
1096 /* Yes, use it instead of the parent one */
1097 Environment
= SEG_OFF_TO_PTR(Parameters
->Environment
, 0);
1100 /* Set up the startup info structure */
1101 ZeroMemory(&StartupInfo
, sizeof(STARTUPINFOA
));
1102 StartupInfo
.cb
= sizeof(STARTUPINFOA
);
1104 /* Create the process */
1105 if (!CreateProcessA(ProgramName
,
1106 FAR_POINTER(Parameters
->CommandLine
),
1116 return GetLastError();
1119 /* Check the type of the program */
1122 /* These are handled by NTVDM */
1123 case SCS_DOS_BINARY
:
1124 case SCS_WOW_BINARY
:
1126 /* Clear the structure */
1127 ZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1129 /* Initialize the structure members */
1130 CommandInfo
.TaskId
= SessionId
;
1131 CommandInfo
.VDMState
= VDM_FLAG_NESTED_TASK
| VDM_FLAG_DONT_WAIT
;
1132 CommandInfo
.CmdLine
= CmdLine
;
1133 CommandInfo
.CmdLen
= sizeof(CmdLine
);
1134 CommandInfo
.AppName
= AppName
;
1135 CommandInfo
.AppLen
= sizeof(AppName
);
1136 CommandInfo
.PifFile
= PifFile
;
1137 CommandInfo
.PifLen
= sizeof(PifFile
);
1138 CommandInfo
.Desktop
= Desktop
;
1139 CommandInfo
.DesktopLen
= sizeof(Desktop
);
1140 CommandInfo
.Title
= Title
;
1141 CommandInfo
.TitleLen
= sizeof(Title
);
1142 CommandInfo
.Env
= Env
;
1143 CommandInfo
.EnvLen
= sizeof(Env
);
1145 /* Get the VDM command information */
1146 if (!GetNextVDMCommand(&CommandInfo
))
1148 /* Shouldn't happen */
1152 /* Increment the re-entry count */
1153 CommandInfo
.VDMState
= VDM_INC_REENTER_COUNT
;
1154 GetNextVDMCommand(&CommandInfo
);
1156 /* Load the executable */
1157 Result
= DosLoadExecutable(LoadType
,
1161 &Parameters
->StackLocation
,
1162 &Parameters
->EntryPoint
);
1163 if (Result
!= ERROR_SUCCESS
)
1165 DisplayMessage(L
"Could not load '%S'. Error: %u", AppName
, Result
);
1166 // FIXME: Decrement the reenter count. Or, instead, just increment
1167 // the VDM reenter count *only* if this call succeeds...
1173 /* Not handled by NTVDM */
1176 /* Wait for the process to finish executing */
1177 WaitForSingleObject(ProcessInfo
.hProcess
, INFINITE
);
1181 /* Close the handles */
1182 CloseHandle(ProcessInfo
.hProcess
);
1183 CloseHandle(ProcessInfo
.hThread
);
1185 return ERROR_SUCCESS
;
1189 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
1192 WORD McbSegment
= FIRST_MCB_SEGMENT
;
1193 PDOS_MCB CurrentMcb
;
1194 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
1195 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
1197 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1201 /* Check if this PSP is it's own parent */
1202 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
1204 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
1206 /* Close the handle */
1210 /* Free the memory used by the process */
1213 /* Get a pointer to the MCB */
1214 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
1216 /* Make sure the MCB is valid */
1217 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
1219 /* If this block was allocated by the process, free it */
1220 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
+ 1);
1222 /* If this was the last block, quit */
1223 if (CurrentMcb
->BlockType
== 'Z') break;
1225 /* Update the segment and continue */
1226 McbSegment
+= CurrentMcb
->Size
+ 1;
1230 /* Restore the interrupt vectors */
1231 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
1232 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
1233 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
1235 /* Update the current PSP */
1236 if (Psp
== CurrentPsp
)
1238 CurrentPsp
= PspBlock
->ParentPsp
;
1239 if (CurrentPsp
== SYSTEM_PSP
)
1241 ResetEvent(VdmTaskEvent
);
1242 EmulatorUnsimulate();
1247 // FIXME: This is probably not the best way to do it
1248 /* Check if this was a nested DOS task */
1249 if (CurrentPsp
!= SYSTEM_PSP
)
1251 VDM_COMMAND_INFO CommandInfo
;
1253 /* Decrement the re-entry count */
1254 CommandInfo
.TaskId
= SessionId
;
1255 CommandInfo
.VDMState
= VDM_DEC_REENTER_COUNT
;
1256 GetNextVDMCommand(&CommandInfo
);
1258 /* Clear the structure */
1259 ZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1261 /* Update the VDM state of the task */
1262 CommandInfo
.TaskId
= SessionId
;
1263 CommandInfo
.VDMState
= VDM_FLAG_DONT_WAIT
;
1264 GetNextVDMCommand(&CommandInfo
);
1268 /* Save the return code - Normal termination */
1269 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
1271 /* Return control to the parent process */
1272 EmulatorExecute(HIWORD(PspBlock
->TerminateAddress
),
1273 LOWORD(PspBlock
->TerminateAddress
));
1276 BOOLEAN
DosHandleIoctl(BYTE ControlCode
, WORD FileHandle
)
1278 HANDLE Handle
= DosGetRealHandle(FileHandle
);
1280 if (Handle
== INVALID_HANDLE_VALUE
)
1283 DosLastError
= ERROR_FILE_NOT_FOUND
;
1287 switch (ControlCode
)
1289 /* Get Device Information */
1295 * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
1296 * for a list of possible flags.
1299 if (Handle
== DosSystemFileTable
[DOS_INPUT_HANDLE
].Handle
)
1304 else if (Handle
== DosSystemFileTable
[DOS_OUTPUT_HANDLE
].Handle
)
1306 /* Console output */
1310 /* It is a device */
1313 /* Return the device information word */
1318 /* Unsupported control code */
1321 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode
);
1323 DosLastError
= ERROR_INVALID_PARAMETER
;
1329 VOID WINAPI
DosInt20h(LPWORD Stack
)
1331 /* This is the exit interrupt */
1332 DosTerminateProcess(Stack
[STACK_CS
], 0);
1335 VOID WINAPI
DosInt21h(LPWORD Stack
)
1338 SYSTEMTIME SystemTime
;
1340 PDOS_INPUT_BUFFER InputBuffer
;
1342 /* Check the value in the AH register */
1345 /* Terminate Program */
1348 DosTerminateProcess(Stack
[STACK_CS
], 0);
1352 /* Read Character from STDIN with Echo */
1355 // FIXME: Under DOS 2+, input / output handle may be redirected!!!!
1356 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
1357 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1359 // /* Let the BOP repeat if needed */
1360 // if (getCF()) break;
1366 /* Write Character to STDOUT */
1369 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1370 Character
= getDL();
1371 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1374 * We return the output character (DOS 2.1+).
1375 * Also, if we're going to output a TAB, then
1376 * don't return a TAB but a SPACE instead.
1377 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1378 * for more information.
1380 setAL(Character
== '\t' ? ' ' : Character
);
1384 /* Read Character from STDAUX */
1387 // FIXME: Really read it from STDAUX!
1388 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1389 // setAL(DosReadCharacter());
1393 /* Write Character to STDAUX */
1396 // FIXME: Really write it to STDAUX!
1397 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1398 // DosPrintCharacter(getDL());
1402 /* Write Character to Printer */
1405 // FIXME: Really write it to printer!
1406 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1407 DPRINT1("0x%p\n", getDL());
1408 DPRINT1("\n\n-----------\n\n");
1412 /* Direct Console I/O */
1415 Character
= getDL();
1417 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1419 if (Character
!= 0xFF)
1422 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1425 * We return the output character (DOS 2.1+).
1426 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1427 * for more information.
1434 if (DosCheckInput())
1436 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
1437 setAL(DosReadCharacter(DOS_INPUT_HANDLE
));
1441 /* No character available */
1442 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
1450 /* Character Input without Echo */
1454 // FIXME: Under DOS 2+, input handle may be redirected!!!!
1455 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
1457 // FIXME: For 0x07, do not check Ctrl-C/Break.
1458 // For 0x08, do check those control sequences and if needed,
1461 // /* Let the BOP repeat if needed */
1462 // if (getCF()) break;
1468 /* Write string to STDOUT */
1471 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1473 while (*String
!= '$')
1475 DosPrintCharacter(DOS_OUTPUT_HANDLE
, *String
);
1480 * We return the terminating character (DOS 2.1+).
1481 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1482 * for more information.
1488 /* Read Buffered Input */
1492 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1494 DPRINT1("Read Buffered Input\n");
1496 while (Count
< InputBuffer
->MaxLength
)
1498 /* Try to read a character (wait) */
1499 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
1501 /* Echo the character and append it to the buffer */
1502 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1503 InputBuffer
->Buffer
[Count
] = Character
;
1505 if (Character
== '\r') break;
1509 /* Update the length */
1510 InputBuffer
->Length
= Count
;
1515 /* Get STDIN Status */
1518 setAL(DosCheckInput() ? 0xFF : 0x00);
1522 /* Flush Buffer and Read STDIN */
1525 BYTE InputFunction
= getAL();
1527 /* Flush STDIN buffer */
1528 DosFlushFileBuffers(DOS_INPUT_HANDLE
);
1531 * If the input function number contained in AL is valid, i.e.
1532 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1533 * recursively with AL == AH.
1535 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1536 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1537 InputFunction
== 0x0A)
1539 setAH(InputFunction
);
1541 * Instead of calling ourselves really recursively as in:
1543 * prefer resetting the CF flag to let the BOP repeat.
1553 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1555 // TODO: Flush what's needed.
1556 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1558 /* Clear CF in DOS 6 only */
1559 if (PspBlock
->DosVersion
== 0x0006)
1560 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1565 /* Set Default Drive */
1568 DosChangeDrive(getDL());
1569 setAL(LastDrive
- 'A' + 1);
1573 /* NULL Function for CP/M Compatibility */
1577 * This function corresponds to the CP/M BDOS function
1578 * "get bit map of logged drives", which is meaningless
1581 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1582 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1583 * for more information.
1589 /* Get Default Drive */
1592 setAL(CurrentDrive
);
1596 /* Set Disk Transfer Area */
1599 DiskTransferArea
= MAKELONG(getDX(), getDS());
1603 /* NULL Function for CP/M Compatibility */
1608 * Function 0x1D corresponds to the CP/M BDOS function
1609 * "get bit map of read-only drives", which is meaningless
1611 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1612 * for more information.
1614 * Function 0x1E corresponds to the CP/M BDOS function
1615 * "set file attributes", which was meaningless under MS-DOS 1.x.
1616 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1617 * for more information.
1623 /* NULL Function for CP/M Compatibility */
1627 * This function corresponds to the CP/M BDOS function
1628 * "get/set default user (sublibrary) number", which is meaningless
1631 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1632 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1633 * for more information.
1639 /* Set Interrupt Vector */
1642 ULONG FarPointer
= MAKELONG(getDX(), getDS());
1643 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1645 /* Write the new far pointer to the IDT */
1646 ((PULONG
)BaseAddress
)[getAL()] = FarPointer
;
1650 /* Create New PSP */
1653 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1657 /* Get System Date */
1660 GetLocalTime(&SystemTime
);
1661 setCX(SystemTime
.wYear
);
1662 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1663 setAL(SystemTime
.wDayOfWeek
);
1667 /* Set System Date */
1670 GetLocalTime(&SystemTime
);
1671 SystemTime
.wYear
= getCX();
1672 SystemTime
.wMonth
= getDH();
1673 SystemTime
.wDay
= getDL();
1675 /* Return success or failure */
1676 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1680 /* Get System Time */
1683 GetLocalTime(&SystemTime
);
1684 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1685 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1689 /* Set System Time */
1692 GetLocalTime(&SystemTime
);
1693 SystemTime
.wHour
= getCH();
1694 SystemTime
.wMinute
= getCL();
1695 SystemTime
.wSecond
= getDH();
1696 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1698 /* Return success or failure */
1699 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1703 /* Get Disk Transfer Area */
1706 setES(HIWORD(DiskTransferArea
));
1707 setBX(LOWORD(DiskTransferArea
));
1711 /* Get DOS Version */
1714 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1717 * DOS 2+ - GET DOS VERSION
1718 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1719 * for more information.
1722 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1725 * Return DOS OEM number:
1726 * 0x00 for IBM PC-DOS
1727 * 0x02 for packaged MS-DOS
1732 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1735 * Return version flag:
1736 * 1 << 3 if DOS is in ROM,
1737 * 0 (reserved) if not.
1742 /* Return DOS 24-bit user serial number in BL:CX */
1747 * Return DOS version: Minor:Major in AH:AL
1748 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1750 setAX(PspBlock
->DosVersion
);
1755 /* Extended functionalities */
1758 if (getAL() == 0x06)
1761 * DOS 5+ - GET TRUE VERSION NUMBER
1762 * This function always returns the true version number, unlike
1763 * AH=30h, whose return value may be changed with SETVER.
1764 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1765 * for more information.
1769 * Return the true DOS version: Minor:Major in BH:BL
1770 * The Windows NT DOS box returns BX=3205h (version 5.50).
1772 setBX(NTDOS_VERSION
);
1774 /* DOS revision 0 */
1782 // /* Invalid subfunction */
1789 /* Get Interrupt Vector */
1792 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1794 /* Read the address from the IDT into ES:BX */
1795 setES(HIWORD(FarPointer
));
1796 setBX(LOWORD(FarPointer
));
1800 /* SWITCH character - AVAILDEV */
1803 if (getAL() == 0x00)
1806 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1807 * This setting is ignored by MS-DOS 4.0+.
1808 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1809 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1810 * for more information.
1815 else if (getAL() == 0x01)
1818 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1819 * This setting is ignored by MS-DOS 5+.
1820 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1821 * for more information.
1826 else if (getAL() == 0x02)
1829 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1830 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1831 * for more information.
1836 else if (getAL() == 0x03)
1839 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1840 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1841 * for more information.
1848 /* Invalid subfunction */
1855 /* Create Directory */
1858 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1860 if (CreateDirectoryA(String
, NULL
))
1862 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1866 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1867 setAX(LOWORD(GetLastError()));
1873 /* Remove Directory */
1876 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1878 if (RemoveDirectoryA(String
))
1880 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1884 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1885 setAX(LOWORD(GetLastError()));
1891 /* Set Current Directory */
1894 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1896 if (DosChangeDirectory(String
))
1898 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1902 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1903 setAX(DosLastError
);
1913 WORD ErrorCode
= DosCreateFile(&FileHandle
,
1914 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1919 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1924 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1935 WORD ErrorCode
= DosOpenFile(&FileHandle
,
1936 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1941 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1946 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1956 if (DosCloseHandle(getBX()))
1958 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1962 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1963 setAX(ERROR_INVALID_HANDLE
);
1969 /* Read from File or Device */
1973 WORD ErrorCode
= DosReadFile(getBX(),
1974 SEG_OFF_TO_PTR(getDS(), getDX()),
1978 if (ErrorCode
== ERROR_SUCCESS
)
1980 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1983 else if (ErrorCode
!= ERROR_NOT_READY
)
1985 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1992 /* Write to File or Device */
1995 WORD BytesWritten
= 0;
1996 WORD ErrorCode
= DosWriteFile(getBX(),
1997 SEG_OFF_TO_PTR(getDS(), getDX()),
2001 if (ErrorCode
== ERROR_SUCCESS
)
2003 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2004 setAX(BytesWritten
);
2008 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2018 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2020 if (demFileDelete(FileName
) == ERROR_SUCCESS
)
2022 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2024 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2025 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2027 setAL(FileName
[0] - 'A');
2031 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2032 setAX(GetLastError());
2042 WORD ErrorCode
= DosSeekFile(getBX(),
2043 MAKELONG(getDX(), getCX()),
2047 if (ErrorCode
== ERROR_SUCCESS
)
2049 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2051 /* Return the new offset in DX:AX */
2052 setDX(HIWORD(NewLocation
));
2053 setAX(LOWORD(NewLocation
));
2057 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2064 /* Get/Set File Attributes */
2068 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2070 if (getAL() == 0x00)
2072 /* Get the attributes */
2073 Attributes
= GetFileAttributesA(FileName
);
2075 /* Check if it failed */
2076 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
2078 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2079 setAX(GetLastError());
2083 /* Return the attributes that DOS can understand */
2084 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2085 setCX(Attributes
& 0x00FF);
2088 else if (getAL() == 0x01)
2090 /* Try to set the attributes */
2091 if (SetFileAttributesA(FileName
, getCL()))
2093 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2097 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2098 setAX(GetLastError());
2103 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2104 setAX(ERROR_INVALID_FUNCTION
);
2113 if (DosHandleIoctl(getAL(), getBX()))
2115 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2119 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2120 setAX(DosLastError
);
2126 /* Duplicate Handle */
2130 HANDLE Handle
= DosGetRealHandle(getBX());
2132 if (Handle
== INVALID_HANDLE_VALUE
)
2134 /* The handle is invalid */
2135 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2136 setAX(ERROR_INVALID_HANDLE
);
2140 /* Open a new handle to the same entry */
2141 NewHandle
= DosOpenHandle(Handle
);
2143 if (NewHandle
== INVALID_DOS_HANDLE
)
2145 /* Too many files open */
2146 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2147 setAX(ERROR_TOO_MANY_OPEN_FILES
);
2151 /* Return the result */
2152 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2157 /* Force Duplicate Handle */
2160 if (DosDuplicateHandle(getBX(), getCX()))
2162 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2166 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2167 setAX(ERROR_INVALID_HANDLE
);
2173 /* Get Current Directory */
2176 BYTE DriveNumber
= getDL();
2177 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
2179 /* Get the real drive number */
2180 if (DriveNumber
== 0)
2182 DriveNumber
= CurrentDrive
;
2186 /* Decrement DriveNumber since it was 1-based */
2190 if (DriveNumber
<= LastDrive
- 'A')
2193 * Copy the current directory into the target buffer.
2194 * It doesn't contain the drive letter and the backslash.
2196 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
2197 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2198 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2202 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2203 setAX(ERROR_INVALID_DRIVE
);
2209 /* Allocate Memory */
2212 WORD MaxAvailable
= 0;
2213 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
2217 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2222 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2223 setAX(DosLastError
);
2224 setBX(MaxAvailable
);
2233 if (DosFreeMemory(getES()))
2235 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2239 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2240 setAX(ERROR_ARENA_TRASHED
);
2246 /* Resize Memory Block */
2251 if (DosResizeMemory(getES(), getBX(), &Size
))
2253 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2257 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2258 setAX(DosLastError
);
2269 DOS_EXEC_TYPE LoadType
= (DOS_EXEC_TYPE
)getAL();
2270 LPSTR ProgramName
= SEG_OFF_TO_PTR(getDS(), getDX());
2271 PDOS_EXEC_PARAM_BLOCK ParamBlock
= SEG_OFF_TO_PTR(getES(), getBX());
2272 WORD ErrorCode
= DosCreateProcess(LoadType
, ProgramName
, ParamBlock
);
2274 if (ErrorCode
== ERROR_SUCCESS
)
2276 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2280 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2288 /* Terminate With Return Code */
2291 DosTerminateProcess(CurrentPsp
, getAL());
2295 /* Get Return Code (ERRORLEVEL) */
2299 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2300 * DosErrorLevel is cleared after being read by this function.
2302 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2303 setAX(DosErrorLevel
);
2304 DosErrorLevel
= 0x0000; // Clear it
2308 /* Find First File */
2311 WORD Result
= (WORD
)demFileFindFirst(FAR_POINTER(DiskTransferArea
),
2312 SEG_OFF_TO_PTR(getDS(), getDX()),
2317 if (Result
== ERROR_SUCCESS
)
2318 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2320 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2325 /* Find Next File */
2328 WORD Result
= (WORD
)demFileFindNext(FAR_POINTER(DiskTransferArea
));
2332 if (Result
== ERROR_SUCCESS
)
2333 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2335 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2340 /* Internal - Set Current Process ID (Set PSP Address) */
2343 // FIXME: Is it really what it's done ??
2344 CurrentPsp
= getBX();
2348 /* Internal - Get Current Process ID (Get PSP Address) */
2350 /* Get Current PSP Address */
2354 * Undocumented AH=51h is identical to the documented AH=62h.
2355 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2356 * and http://www.ctyme.com/intr/rb-3140.htm
2357 * for more information.
2363 /* Internal - Get "List of lists" (SYSVARS) */
2367 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
2368 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
2369 * for more information.
2372 /* Return the DOS "list of lists" in ES:BX */
2376 DisplayMessage(L
"Required for AARD code, do you remember? :P");
2380 /* Get/Set Memory Management Options */
2383 if (getAL() == 0x00)
2385 /* Get allocation strategy */
2386 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2387 setAX(DosAllocStrategy
);
2389 else if (getAL() == 0x01)
2391 /* Set allocation strategy */
2393 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2394 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2396 /* Can't set both */
2397 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2398 setAX(ERROR_INVALID_PARAMETER
);
2402 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2404 /* Invalid allocation strategy */
2405 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2406 setAX(ERROR_INVALID_PARAMETER
);
2410 DosAllocStrategy
= getBL();
2411 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2413 else if (getAL() == 0x02)
2415 /* Get UMB link state */
2416 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2417 setAL(DosUmbLinked
? 0x01 : 0x00);
2419 else if (getAL() == 0x03)
2421 /* Set UMB link state */
2422 if (getBX()) DosLinkUmb();
2423 else DosUnlinkUmb();
2424 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2428 /* Invalid or unsupported function */
2429 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2430 setAX(ERROR_INVALID_FUNCTION
);
2439 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2442 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2443 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2448 VOID WINAPI
DosBreakInterrupt(LPWORD Stack
)
2450 UNREFERENCED_PARAMETER(Stack
);
2452 /* Stop the VDM task */
2453 ResetEvent(VdmTaskEvent
);
2454 EmulatorUnsimulate();
2457 VOID WINAPI
DosFastConOut(LPWORD Stack
)
2460 * This is the DOS 2+ Fast Console Output Interrupt.
2461 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2462 * for more information.
2465 /* Save AX and BX */
2466 USHORT AX
= getAX();
2467 USHORT BX
= getBX();
2469 /* Set the parameters (AL = character, already set) */
2470 setBL(DOS_CHAR_ATTRIBUTE
);
2471 setBH(Bda
->VideoPage
);
2473 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
2475 Int32Call(&DosContext
, BIOS_VIDEO_INTERRUPT
);
2477 /* Restore AX and BX */
2482 VOID WINAPI
DosInt2Fh(LPWORD Stack
)
2484 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2486 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2489 BOOLEAN
DosKRNLInitialize(VOID
)
2495 CHAR CurrentDirectory
[MAX_PATH
];
2496 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2502 /* Clear the current directory buffer */
2503 ZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2505 /* Get the current directory */
2506 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2508 // TODO: Use some kind of default path?
2512 /* Convert that to a DOS path */
2513 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2515 // TODO: Use some kind of default path?
2520 CurrentDrive
= DosDirectory
[0] - 'A';
2522 /* Get the directory part of the path */
2523 Path
= strchr(DosDirectory
, '\\');
2526 /* Skip the backslash */
2530 /* Set the directory */
2533 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
2536 /* Read CONFIG.SYS */
2537 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
2540 while (fgetws(Buffer
, 256, Stream
))
2542 // TODO: Parse the line
2547 /* Initialize the SFT */
2548 for (i
= 0; i
< DOS_SFT_SIZE
; i
++)
2550 DosSystemFileTable
[i
].Handle
= INVALID_HANDLE_VALUE
;
2551 DosSystemFileTable
[i
].RefCount
= 0;
2554 /* Get handles to standard I/O devices */
2555 DosSystemFileTable
[0].Handle
= GetStdHandle(STD_INPUT_HANDLE
);
2556 DosSystemFileTable
[1].Handle
= GetStdHandle(STD_OUTPUT_HANDLE
);
2557 DosSystemFileTable
[2].Handle
= GetStdHandle(STD_ERROR_HANDLE
);
2559 /* Initialize the reference counts */
2560 DosSystemFileTable
[0].RefCount
=
2561 DosSystemFileTable
[1].RefCount
=
2562 DosSystemFileTable
[2].RefCount
= 1;
2566 /* Initialize the callback context */
2567 InitializeContext(&DosContext
, 0x0070, 0x0000);
2569 /* Register the DOS 32-bit Interrupts */
2570 RegisterDosInt32(0x20, DosInt20h
);
2571 RegisterDosInt32(0x21, DosInt21h
);
2572 // RegisterDosInt32(0x22, DosInt22h ); // Termination
2573 RegisterDosInt32(0x23, DosBreakInterrupt
); // Ctrl-C / Ctrl-Break
2574 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
2575 RegisterDosInt32(0x29, DosFastConOut
); // DOS 2+ Fast Console Output
2576 RegisterDosInt32(0x2F, DosInt2Fh
);