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 *******************************************************************/
27 #include "bios/bios.h"
30 #include "hardware/ps2.h"
34 /* PRIVATE VARIABLES **********************************************************/
36 #define INDOS_POINTER MAKELONG(0x00FE, 0x0070)
38 CALLBACK16 DosContext
;
40 static DWORD DiskTransferArea
;
41 /*static*/ BYTE CurrentDrive
;
42 static CHAR LastDrive
= 'E';
43 static CHAR CurrentDirectories
[NUM_DRIVES
][DOS_DIR_LENGTH
];
44 static WORD DosErrorLevel
= 0x0000;
47 /* PUBLIC VARIABLES ***********************************************************/
51 /* Echo state for INT 21h, AH = 01h and AH = 3Fh */
52 BOOLEAN DoEcho
= FALSE
;
53 WORD CurrentPsp
= SYSTEM_PSP
;
54 WORD DosLastError
= 0;
56 /* PRIVATE FUNCTIONS **********************************************************/
58 static WORD
DosCopyEnvironmentBlock(LPCSTR Environment OPTIONAL
,
61 PCHAR Ptr
, DestBuffer
= NULL
;
65 /* If we have an environment strings list, compute its size */
68 /* Calculate the size of the environment block */
69 Ptr
= (PCHAR
)Environment
;
70 while (*Ptr
) Ptr
+= strlen(Ptr
) + 1;
71 TotalSize
= (ULONG_PTR
)Ptr
- (ULONG_PTR
)Environment
;
75 /* Empty environment string */
78 /* Add the final environment block NULL-terminator */
81 /* Add the two bytes for the program name tag */
84 /* Add the string buffer size */
85 TotalSize
+= strlen(ProgramName
) + 1;
87 /* Allocate the memory for the environment block */
88 DestSegment
= DosAllocateMemory((WORD
)((TotalSize
+ 0x0F) >> 4), NULL
);
89 if (!DestSegment
) return 0;
91 DestBuffer
= (PCHAR
)SEG_OFF_TO_PTR(DestSegment
, 0);
93 /* If we have an environment strings list, copy it */
96 Ptr
= (PCHAR
)Environment
;
99 /* Copy the string and NULL-terminate it */
100 strcpy(DestBuffer
, Ptr
);
101 DestBuffer
+= strlen(Ptr
);
102 *(DestBuffer
++) = '\0';
104 /* Move to the next string */
105 Ptr
+= strlen(Ptr
) + 1;
110 /* Empty environment string */
111 *(DestBuffer
++) = '\0';
113 /* NULL-terminate the environment block */
114 *(DestBuffer
++) = '\0';
116 /* Store the special program name tag */
117 *(DestBuffer
++) = LOBYTE(DOS_PROGRAM_NAME_TAG
);
118 *(DestBuffer
++) = HIBYTE(DOS_PROGRAM_NAME_TAG
);
120 /* Copy the program name after the environment block */
121 strcpy(DestBuffer
, ProgramName
);
126 static BOOLEAN
DosChangeDrive(BYTE Drive
)
128 WCHAR DirectoryPath
[DOS_CMDLINE_LENGTH
];
130 /* Make sure the drive exists */
131 if (Drive
> (LastDrive
- 'A')) return FALSE
;
133 /* Find the path to the new current directory */
134 swprintf(DirectoryPath
, L
"%c\\%S", Drive
+ 'A', CurrentDirectories
[Drive
]);
136 /* Change the current directory of the process */
137 if (!SetCurrentDirectory(DirectoryPath
)) return FALSE
;
139 /* Set the current drive */
140 CurrentDrive
= Drive
;
146 static BOOLEAN
DosChangeDirectory(LPSTR Directory
)
152 /* Make sure the directory path is not too long */
153 if (strlen(Directory
) >= DOS_DIR_LENGTH
)
155 DosLastError
= ERROR_PATH_NOT_FOUND
;
159 /* Get the drive number */
160 DriveNumber
= Directory
[0] - 'A';
162 /* Make sure the drive exists */
163 if (DriveNumber
> (LastDrive
- 'A'))
165 DosLastError
= ERROR_PATH_NOT_FOUND
;
169 /* Get the file attributes */
170 Attributes
= GetFileAttributesA(Directory
);
172 /* Make sure the path exists and is a directory */
173 if ((Attributes
== INVALID_FILE_ATTRIBUTES
)
174 || !(Attributes
& FILE_ATTRIBUTE_DIRECTORY
))
176 DosLastError
= ERROR_PATH_NOT_FOUND
;
180 /* Check if this is the current drive */
181 if (DriveNumber
== CurrentDrive
)
183 /* Change the directory */
184 if (!SetCurrentDirectoryA(Directory
))
186 DosLastError
= LOWORD(GetLastError());
191 /* Get the directory part of the path */
192 Path
= strchr(Directory
, '\\');
195 /* Skip the backslash */
199 /* Set the directory for the drive */
202 strncpy(CurrentDirectories
[DriveNumber
], Path
, DOS_DIR_LENGTH
);
206 CurrentDirectories
[DriveNumber
][0] = '\0';
213 static BOOLEAN
DosControlBreak(VOID
)
217 /* Call interrupt 0x23 */
218 Int32Call(&DosContext
, 0x23);
222 DosTerminateProcess(CurrentPsp
, 0, 0);
229 /* PUBLIC FUNCTIONS ***********************************************************/
231 VOID
DosInitializePsp(WORD PspSegment
,
237 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
238 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
240 RtlZeroMemory(PspBlock
, sizeof(*PspBlock
));
242 /* Set the exit interrupt */
243 PspBlock
->Exit
[0] = 0xCD; // int 0x20
244 PspBlock
->Exit
[1] = 0x20;
246 /* Set the number of the last paragraph */
247 PspBlock
->LastParagraph
= PspSegment
+ ProgramSize
- 1;
249 /* Save the interrupt vectors */
250 PspBlock
->TerminateAddress
= ReturnAddress
;
251 PspBlock
->BreakAddress
= IntVecTable
[0x23];
252 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
254 /* Set the parent PSP */
255 PspBlock
->ParentPsp
= CurrentPsp
;
257 /* Copy the parent handle table */
258 DosCopyHandleTable(PspBlock
->HandleTable
);
260 /* Set the environment block */
261 PspBlock
->EnvBlock
= Environment
;
263 /* Set the handle table pointers to the internal handle table */
264 PspBlock
->HandleTableSize
= 20;
265 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
267 /* Set the DOS version */
268 PspBlock
->DosVersion
= DOS_VERSION
;
270 /* Set the far call opcodes */
271 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
272 PspBlock
->FarCall
[1] = 0x21;
273 PspBlock
->FarCall
[2] = 0xCB; // retf
275 /* Set the command line */
276 PspBlock
->CommandLineSize
= (BYTE
)min(strlen(CommandLine
), DOS_CMDLINE_LENGTH
- 1);
277 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
278 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
281 DWORD
DosLoadExecutable(IN DOS_EXEC_TYPE LoadType
,
282 IN LPCSTR ExecutablePath
,
283 IN LPCSTR CommandLine
,
284 IN LPCSTR Environment OPTIONAL
,
285 IN DWORD ReturnAddress OPTIONAL
,
286 OUT PDWORD StackLocation OPTIONAL
,
287 OUT PDWORD EntryPoint OPTIONAL
)
289 DWORD Result
= ERROR_SUCCESS
;
290 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
291 LPBYTE Address
= NULL
;
295 DWORD i
, FileSize
, ExeSize
;
296 PIMAGE_DOS_HEADER Header
;
297 PDWORD RelocationTable
;
299 LPSTR CmdLinePtr
= (LPSTR
)CommandLine
;
301 DPRINT1("DosLoadExecutable(%d, %s, %s, %s, 0x%08X, 0x%08X)\n",
305 Environment
? Environment
: "n/a",
309 if (LoadType
== DOS_LOAD_OVERLAY
)
311 DPRINT1("Overlay loading is not supported yet.\n");
312 return ERROR_NOT_SUPPORTED
;
315 /* NULL-terminate the command line by removing the return carriage character */
316 while (*CmdLinePtr
&& *CmdLinePtr
!= '\r') CmdLinePtr
++;
319 /* Open a handle to the executable */
320 FileHandle
= CreateFileA(ExecutablePath
,
325 FILE_ATTRIBUTE_NORMAL
,
327 if (FileHandle
== INVALID_HANDLE_VALUE
)
329 Result
= GetLastError();
333 /* Get the file size */
334 FileSize
= GetFileSize(FileHandle
, NULL
);
336 /* Create a mapping object for the file */
337 FileMapping
= CreateFileMapping(FileHandle
,
343 if (FileMapping
== NULL
)
345 Result
= GetLastError();
349 /* Map the file into memory */
350 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
353 Result
= GetLastError();
357 /* Copy the environment block to DOS memory */
358 EnvBlock
= DosCopyEnvironmentBlock(Environment
, ExecutablePath
);
361 Result
= ERROR_NOT_ENOUGH_MEMORY
;
365 /* Check if this is an EXE file or a COM file */
366 if (Address
[0] == 'M' && Address
[1] == 'Z')
370 /* Get the MZ header */
371 Header
= (PIMAGE_DOS_HEADER
)Address
;
373 /* Get the base size of the file, in paragraphs (rounded up) */
374 ExeSize
= (((Header
->e_cp
- 1) * 512) + Header
->e_cblp
+ 0x0F) >> 4;
376 /* Add the PSP size, in paragraphs */
377 ExeSize
+= sizeof(DOS_PSP
) >> 4;
379 /* Add the maximum size that should be allocated */
380 ExeSize
+= Header
->e_maxalloc
;
382 /* Make sure it does not pass 0xFFFF */
383 if (ExeSize
> 0xFFFF) ExeSize
= 0xFFFF;
385 /* Try to allocate that much memory */
386 Segment
= DosAllocateMemory((WORD
)ExeSize
, &MaxAllocSize
);
390 /* Check if there's at least enough memory for the minimum size */
391 if (MaxAllocSize
< (ExeSize
- Header
->e_maxalloc
+ Header
->e_minalloc
))
393 Result
= DosLastError
;
397 /* Allocate that minimum amount */
398 ExeSize
= MaxAllocSize
;
399 Segment
= DosAllocateMemory((WORD
)ExeSize
, NULL
);
400 ASSERT(Segment
!= 0);
403 /* Initialize the PSP */
404 DosInitializePsp(Segment
,
410 /* The process owns its own memory */
411 DosChangeMemoryOwner(Segment
, Segment
);
412 DosChangeMemoryOwner(EnvBlock
, Segment
);
414 /* Copy the program to Segment:0100 */
415 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
416 Address
+ (Header
->e_cparhdr
<< 4),
417 min(FileSize
- (Header
->e_cparhdr
<< 4),
418 (ExeSize
<< 4) - sizeof(DOS_PSP
)));
420 /* Get the relocation table */
421 RelocationTable
= (PDWORD
)(Address
+ Header
->e_lfarlc
);
423 /* Perform relocations */
424 for (i
= 0; i
< Header
->e_crlc
; i
++)
426 /* Get a pointer to the word that needs to be patched */
427 RelocWord
= (PWORD
)SEG_OFF_TO_PTR(Segment
+ HIWORD(RelocationTable
[i
]),
428 0x100 + LOWORD(RelocationTable
[i
]));
430 /* Add the number of the EXE segment to it */
431 *RelocWord
+= Segment
+ (sizeof(DOS_PSP
) >> 4);
434 if (LoadType
== DOS_LOAD_AND_EXECUTE
)
436 /* Set the initial segment registers */
440 /* Set the stack to the location from the header */
441 setSS(Segment
+ (sizeof(DOS_PSP
) >> 4) + Header
->e_ss
);
445 CurrentPsp
= Segment
;
446 DiskTransferArea
= MAKELONG(0x80, Segment
);
447 CpuExecute(Segment
+ Header
->e_cs
+ (sizeof(DOS_PSP
) >> 4),
455 /* Find the maximum amount of memory that can be allocated */
456 DosAllocateMemory(0xFFFF, &MaxAllocSize
);
458 /* Make sure it's enough for the whole program and the PSP */
459 if (((DWORD
)MaxAllocSize
<< 4) < (FileSize
+ sizeof(DOS_PSP
)))
461 Result
= ERROR_NOT_ENOUGH_MEMORY
;
465 /* Allocate all of it */
466 Segment
= DosAllocateMemory(MaxAllocSize
, NULL
);
469 Result
= DosLastError
;
473 /* The process owns its own memory */
474 DosChangeMemoryOwner(Segment
, Segment
);
475 DosChangeMemoryOwner(EnvBlock
, Segment
);
477 /* Copy the program to Segment:0100 */
478 RtlCopyMemory(SEG_OFF_TO_PTR(Segment
, 0x100),
482 /* Initialize the PSP */
483 DosInitializePsp(Segment
,
489 if (LoadType
== DOS_LOAD_AND_EXECUTE
)
491 /* Set the initial segment registers */
495 /* Set the stack to the last word of the segment */
500 * Set the value on the stack to 0, so that a near return
501 * jumps to PSP:0000 which has the exit code.
503 *((LPWORD
)SEG_OFF_TO_PTR(Segment
, 0xFFFE)) = 0;
506 CurrentPsp
= Segment
;
507 DiskTransferArea
= MAKELONG(0x80, Segment
);
508 CpuExecute(Segment
, 0x100);
513 if (Result
!= ERROR_SUCCESS
)
515 /* It was not successful, cleanup the DOS memory */
516 if (EnvBlock
) DosFreeMemory(EnvBlock
);
517 if (Segment
) DosFreeMemory(Segment
);
521 if (Address
!= NULL
) UnmapViewOfFile(Address
);
523 /* Close the file mapping object */
524 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
526 /* Close the file handle */
527 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
532 DWORD
DosStartProcess(IN LPCSTR ExecutablePath
,
533 IN LPCSTR CommandLine
,
534 IN LPCSTR Environment OPTIONAL
)
537 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
539 Result
= DosLoadExecutable(DOS_LOAD_AND_EXECUTE
,
543 IntVecTable
[0x20], // Use INT 20h
547 if (Result
!= ERROR_SUCCESS
) goto Quit
;
549 /* Attach to the console */
551 VidBiosAttachToConsole();
553 // HACK: Simulate a ENTER key release scancode on the PS/2 port because
554 // some apps expect to read a key release scancode (> 0x80) when they
556 IOWriteB(PS2_CONTROL_PORT
, 0xD2); // Next write is for the first PS/2 port
557 IOWriteB(PS2_DATA_PORT
, 0x80 | 0x1C); // ENTER key release
559 /* Start simulation */
560 SetEvent(VdmTaskEvent
);
563 /* Detach from the console */
564 VidBiosDetachFromConsole();
572 WORD
DosCreateProcess(DOS_EXEC_TYPE LoadType
,
574 PDOS_EXEC_PARAM_BLOCK Parameters
,
579 LPVOID Environment
= NULL
;
580 VDM_COMMAND_INFO CommandInfo
;
581 CHAR CmdLine
[MAX_PATH
];
582 CHAR AppName
[MAX_PATH
];
583 CHAR PifFile
[MAX_PATH
];
584 CHAR Desktop
[MAX_PATH
];
585 CHAR Title
[MAX_PATH
];
587 PVOID Env
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, EnvSize
);
588 STARTUPINFOA StartupInfo
;
589 PROCESS_INFORMATION ProcessInfo
;
591 /* Get the binary type */
592 if (!GetBinaryTypeA(ProgramName
, &BinaryType
)) return GetLastError();
594 /* Did the caller specify an environment segment? */
595 if (Parameters
->Environment
)
597 /* Yes, use it instead of the parent one */
598 Environment
= SEG_OFF_TO_PTR(Parameters
->Environment
, 0);
601 /* Set up the startup info structure */
602 RtlZeroMemory(&StartupInfo
, sizeof(StartupInfo
));
603 StartupInfo
.cb
= sizeof(StartupInfo
);
605 /* Create the process */
606 if (!CreateProcessA(ProgramName
,
607 FAR_POINTER(Parameters
->CommandLine
),
617 return GetLastError();
620 /* Check the type of the program */
623 /* These are handled by NTVDM */
627 /* Clear the structure */
628 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
630 /* Initialize the structure members */
631 CommandInfo
.TaskId
= SessionId
;
632 CommandInfo
.VDMState
= VDM_FLAG_NESTED_TASK
| VDM_FLAG_DONT_WAIT
;
633 CommandInfo
.CmdLine
= CmdLine
;
634 CommandInfo
.CmdLen
= sizeof(CmdLine
);
635 CommandInfo
.AppName
= AppName
;
636 CommandInfo
.AppLen
= sizeof(AppName
);
637 CommandInfo
.PifFile
= PifFile
;
638 CommandInfo
.PifLen
= sizeof(PifFile
);
639 CommandInfo
.Desktop
= Desktop
;
640 CommandInfo
.DesktopLen
= sizeof(Desktop
);
641 CommandInfo
.Title
= Title
;
642 CommandInfo
.TitleLen
= sizeof(Title
);
643 CommandInfo
.Env
= Env
;
644 CommandInfo
.EnvLen
= EnvSize
;
647 /* Get the VDM command information */
648 if (!GetNextVDMCommand(&CommandInfo
))
650 if (CommandInfo
.EnvLen
> EnvSize
)
652 /* Expand the environment size */
653 EnvSize
= CommandInfo
.EnvLen
;
654 CommandInfo
.Env
= Env
= RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, Env
, EnvSize
);
656 /* Repeat the request */
657 CommandInfo
.VDMState
|= VDM_FLAG_RETRY
;
661 /* Shouldn't happen */
665 /* Load the executable */
666 Result
= DosLoadExecutable(LoadType
,
671 &Parameters
->StackLocation
,
672 &Parameters
->EntryPoint
);
673 if (Result
== ERROR_SUCCESS
)
675 /* Increment the re-entry count */
676 CommandInfo
.VDMState
= VDM_INC_REENTER_COUNT
;
677 GetNextVDMCommand(&CommandInfo
);
681 DisplayMessage(L
"Could not load '%S'. Error: %u", AppName
, Result
);
687 /* Not handled by NTVDM */
690 /* Wait for the process to finish executing */
691 WaitForSingleObject(ProcessInfo
.hProcess
, INFINITE
);
695 RtlFreeHeap(RtlGetProcessHeap(), 0, Env
);
697 /* Close the handles */
698 CloseHandle(ProcessInfo
.hProcess
);
699 CloseHandle(ProcessInfo
.hThread
);
701 return ERROR_SUCCESS
;
705 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
, WORD KeepResident
)
708 WORD McbSegment
= FIRST_MCB_SEGMENT
;
710 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
711 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
713 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
718 /* Check if this PSP is it's own parent */
719 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
721 if (KeepResident
== 0)
723 for (i
= 0; i
< PspBlock
->HandleTableSize
; i
++)
725 /* Close the handle */
730 /* Free the memory used by the process */
733 /* Get a pointer to the MCB */
734 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
736 /* Make sure the MCB is valid */
737 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z') break;
739 /* Check if this block was allocated by the process */
740 if (CurrentMcb
->OwnerPsp
== Psp
)
742 if (KeepResident
== 0)
744 /* Free this entire block */
745 DosFreeMemory(McbSegment
+ 1);
747 else if (KeepResident
< CurrentMcb
->Size
)
749 /* Reduce the size of the block */
750 DosResizeMemory(McbSegment
+ 1, KeepResident
, NULL
);
752 /* No further paragraphs need to stay resident */
757 /* Just reduce the amount of paragraphs we need to keep resident */
758 KeepResident
-= CurrentMcb
->Size
;
762 /* If this was the last block, quit */
763 if (CurrentMcb
->BlockType
== 'Z') break;
765 /* Update the segment and continue */
766 McbSegment
+= CurrentMcb
->Size
+ 1;
770 /* Restore the interrupt vectors */
771 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
772 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
773 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
775 /* Update the current PSP */
776 if (Psp
== CurrentPsp
)
778 CurrentPsp
= PspBlock
->ParentPsp
;
779 if (CurrentPsp
== SYSTEM_PSP
)
781 ResetEvent(VdmTaskEvent
);
787 // FIXME: This is probably not the best way to do it
788 /* Check if this was a nested DOS task */
789 if (CurrentPsp
!= SYSTEM_PSP
)
791 VDM_COMMAND_INFO CommandInfo
;
793 /* Decrement the re-entry count */
794 CommandInfo
.TaskId
= SessionId
;
795 CommandInfo
.VDMState
= VDM_DEC_REENTER_COUNT
;
796 GetNextVDMCommand(&CommandInfo
);
798 /* Clear the structure */
799 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
801 /* Update the VDM state of the task */
802 CommandInfo
.TaskId
= SessionId
;
803 CommandInfo
.VDMState
= VDM_FLAG_DONT_WAIT
;
804 GetNextVDMCommand(&CommandInfo
);
808 /* Save the return code - Normal termination */
809 DosErrorLevel
= MAKEWORD(ReturnCode
, 0x00);
811 /* Return control to the parent process */
812 CpuExecute(HIWORD(PspBlock
->TerminateAddress
),
813 LOWORD(PspBlock
->TerminateAddress
));
816 VOID WINAPI
DosInt20h(LPWORD Stack
)
818 /* This is the exit interrupt */
819 DosTerminateProcess(Stack
[STACK_CS
], 0, 0);
822 VOID WINAPI
DosInt21h(LPWORD Stack
)
825 SYSTEMTIME SystemTime
;
827 PDOS_INPUT_BUFFER InputBuffer
;
828 PDOS_COUNTRY_CODE_BUFFER CountryCodeBuffer
;
833 /* Check the value in the AH register */
836 /* Terminate Program */
839 DosTerminateProcess(Stack
[STACK_CS
], 0, 0);
843 /* Read Character from STDIN with Echo */
846 DPRINT("INT 21h, AH = 01h\n");
848 // FIXME: Under DOS 2+, input / output handle may be redirected!!!!
850 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
853 // FIXME: Check whether Ctrl-C / Ctrl-Break is pressed, and call INT 23h if so.
854 // Check also Ctrl-P and set echo-to-printer flag.
855 // Ctrl-Z is not interpreted.
861 /* Write Character to STDOUT */
864 // FIXME: Under DOS 2+, output handle may be redirected!!!!
866 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
869 * We return the output character (DOS 2.1+).
870 * Also, if we're going to output a TAB, then
871 * don't return a TAB but a SPACE instead.
872 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
873 * for more information.
875 setAL(Character
== '\t' ? ' ' : Character
);
879 /* Read Character from STDAUX */
882 // FIXME: Really read it from STDAUX!
883 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
884 // setAL(DosReadCharacter());
888 /* Write Character to STDAUX */
891 // FIXME: Really write it to STDAUX!
892 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
893 // DosPrintCharacter(getDL());
897 /* Write Character to Printer */
900 // FIXME: Really write it to printer!
901 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
902 DPRINT1("0x%p\n", getDL());
903 DPRINT1("\n\n-----------\n\n");
907 /* Direct Console I/O */
912 // FIXME: Under DOS 2+, output handle may be redirected!!!!
914 if (Character
!= 0xFF)
917 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
920 * We return the output character (DOS 2.1+).
921 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
922 * for more information.
931 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_ZF
;
932 setAL(DosReadCharacter(DOS_INPUT_HANDLE
));
936 /* No character available */
937 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_ZF
;
945 /* Character Input without Echo */
949 DPRINT("Char input without echo\n");
951 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
953 // FIXME: For 0x07, do not check Ctrl-C/Break.
954 // For 0x08, do check those control sequences and if needed,
961 /* Write string to STDOUT */
964 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
966 while (*String
!= '$')
968 DosPrintCharacter(DOS_OUTPUT_HANDLE
, *String
);
973 * We return the terminating character (DOS 2.1+).
974 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
975 * for more information.
977 setAL('$'); // *String
981 /* Read Buffered Input */
985 InputBuffer
= (PDOS_INPUT_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
987 DPRINT("Read Buffered Input\n");
989 while (Count
< InputBuffer
->MaxLength
)
991 /* Try to read a character (wait) */
992 Character
= DosReadCharacter(DOS_INPUT_HANDLE
);
996 /* Extended character */
999 /* Read the scancode */
1000 DosReadCharacter(DOS_INPUT_HANDLE
);
1007 DosPrintCharacter(DOS_OUTPUT_HANDLE
, '^');
1008 DosPrintCharacter(DOS_OUTPUT_HANDLE
, 'C');
1010 if (DosControlBreak())
1012 /* Set the character to a newline to exit the loop */
1026 /* Erase the character */
1027 DosPrintCharacter(DOS_OUTPUT_HANDLE
, '\b');
1028 DosPrintCharacter(DOS_OUTPUT_HANDLE
, ' ');
1029 DosPrintCharacter(DOS_OUTPUT_HANDLE
, '\b');
1037 /* Append it to the buffer */
1038 InputBuffer
->Buffer
[Count
] = Character
;
1040 /* Check if this is a special character */
1041 if (Character
< 0x20 && Character
!= 0x0A && Character
!= 0x0D)
1043 DosPrintCharacter(DOS_OUTPUT_HANDLE
, '^');
1044 Character
+= 'A' - 1;
1047 /* Echo the character */
1048 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
1052 if (Character
== '\r') break;
1053 Count
++; /* Carriage returns are NOT counted */
1056 /* Update the length */
1057 InputBuffer
->Length
= Count
;
1062 /* Get STDIN Status */
1065 setAL(DosCheckInput() ? 0xFF : 0x00);
1069 /* Flush Buffer and Read STDIN */
1072 BYTE InputFunction
= getAL();
1074 /* Flush STDIN buffer */
1075 DosFlushFileBuffers(DOS_INPUT_HANDLE
);
1078 * If the input function number contained in AL is valid, i.e.
1079 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1080 * recursively with AL == AH.
1082 if (InputFunction
== 0x01 || InputFunction
== 0x06 ||
1083 InputFunction
== 0x07 || InputFunction
== 0x08 ||
1084 InputFunction
== 0x0A)
1086 /* Call ourselves recursively */
1087 setAH(InputFunction
);
1096 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1098 // TODO: Flush what's needed.
1099 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1101 /* Clear CF in DOS 6 only */
1102 if (PspBlock
->DosVersion
== 0x0006)
1103 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1108 /* Set Default Drive */
1111 DosChangeDrive(getDL());
1112 setAL(LastDrive
- 'A' + 1);
1116 /* NULL Function for CP/M Compatibility */
1120 * This function corresponds to the CP/M BDOS function
1121 * "get bit map of logged drives", which is meaningless
1124 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1125 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1126 * for more information.
1132 /* Get Default Drive */
1135 setAL(CurrentDrive
);
1139 /* Set Disk Transfer Area */
1142 DiskTransferArea
= MAKELONG(getDX(), getDS());
1146 /* NULL Function for CP/M Compatibility */
1151 * Function 0x1D corresponds to the CP/M BDOS function
1152 * "get bit map of read-only drives", which is meaningless
1154 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1155 * for more information.
1157 * Function 0x1E corresponds to the CP/M BDOS function
1158 * "set file attributes", which was meaningless under MS-DOS 1.x.
1159 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1160 * for more information.
1166 /* NULL Function for CP/M Compatibility */
1170 * This function corresponds to the CP/M BDOS function
1171 * "get/set default user (sublibrary) number", which is meaningless
1174 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1175 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1176 * for more information.
1182 /* Set Interrupt Vector */
1185 ULONG FarPointer
= MAKELONG(getDX(), getDS());
1186 DPRINT1("Setting interrupt 0x%02X to %04X:%04X ...\n",
1187 getAL(), HIWORD(FarPointer
), LOWORD(FarPointer
));
1189 /* Write the new far pointer to the IDT */
1190 ((PULONG
)BaseAddress
)[getAL()] = FarPointer
;
1194 /* Create New PSP */
1197 DPRINT1("INT 21h, AH = 26h - Create New PSP is UNIMPLEMENTED\n");
1201 /* Parse Filename into FCB */
1204 PCHAR FileName
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
1205 PDOS_FCB Fcb
= (PDOS_FCB
)SEG_OFF_TO_PTR(getES(), getDI());
1206 BYTE Options
= getAL();
1208 CHAR FillChar
= ' ';
1210 if (FileName
[1] == ':')
1212 /* Set the drive number */
1213 Fcb
->DriveNumber
= RtlUpperChar(FileName
[0]) - 'A' + 1;
1215 /* Skip to the file name part */
1220 /* No drive number specified */
1221 if (Options
& (1 << 1)) Fcb
->DriveNumber
= CurrentDrive
+ 1;
1222 else Fcb
->DriveNumber
= 0;
1225 /* Parse the file name */
1227 while ((*FileName
> 0x20) && (i
< 8))
1229 if (*FileName
== '.') break;
1230 else if (*FileName
== '*')
1236 Fcb
->FileName
[i
++] = RtlUpperChar(*FileName
++);
1239 /* Fill the whole field with blanks only if bit 2 is not set */
1240 if ((FillChar
!= ' ') || (i
!= 0) || !(Options
& (1 << 2)))
1242 for (; i
< 8; i
++) Fcb
->FileName
[i
] = FillChar
;
1245 /* Skip to the extension part */
1246 while (*FileName
> 0x20 && *FileName
!= '.') FileName
++;
1247 if (*FileName
== '.') FileName
++;
1249 /* Now parse the extension */
1253 while ((*FileName
> 0x20) && (i
< 3))
1255 if (*FileName
== '*')
1261 Fcb
->FileExt
[i
++] = RtlUpperChar(*FileName
++);
1264 /* Fill the whole field with blanks only if bit 3 is not set */
1265 if ((FillChar
!= ' ') || (i
!= 0) || !(Options
& (1 << 3)))
1267 for (; i
< 3; i
++) Fcb
->FileExt
[i
] = FillChar
;
1273 /* Get System Date */
1276 GetLocalTime(&SystemTime
);
1277 setCX(SystemTime
.wYear
);
1278 setDX(MAKEWORD(SystemTime
.wDay
, SystemTime
.wMonth
));
1279 setAL(SystemTime
.wDayOfWeek
);
1283 /* Set System Date */
1286 GetLocalTime(&SystemTime
);
1287 SystemTime
.wYear
= getCX();
1288 SystemTime
.wMonth
= getDH();
1289 SystemTime
.wDay
= getDL();
1291 /* Return success or failure */
1292 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1296 /* Get System Time */
1299 GetLocalTime(&SystemTime
);
1300 setCX(MAKEWORD(SystemTime
.wMinute
, SystemTime
.wHour
));
1301 setDX(MAKEWORD(SystemTime
.wMilliseconds
/ 10, SystemTime
.wSecond
));
1305 /* Set System Time */
1308 GetLocalTime(&SystemTime
);
1309 SystemTime
.wHour
= getCH();
1310 SystemTime
.wMinute
= getCL();
1311 SystemTime
.wSecond
= getDH();
1312 SystemTime
.wMilliseconds
= getDL() * 10; // In hundredths of seconds
1314 /* Return success or failure */
1315 setAL(SetLocalTime(&SystemTime
) ? 0x00 : 0xFF);
1319 /* Get Disk Transfer Area */
1322 setES(HIWORD(DiskTransferArea
));
1323 setBX(LOWORD(DiskTransferArea
));
1327 /* Get DOS Version */
1330 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(CurrentPsp
);
1333 * DOS 2+ - GET DOS VERSION
1334 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1335 * for more information.
1338 if (LOBYTE(PspBlock
->DosVersion
) < 5 || getAL() == 0x00)
1341 * Return DOS OEM number:
1342 * 0x00 for IBM PC-DOS
1343 * 0x02 for packaged MS-DOS
1349 if (LOBYTE(PspBlock
->DosVersion
) >= 5 && getAL() == 0x01)
1352 * Return version flag:
1353 * 1 << 3 if DOS is in ROM,
1354 * 0 (reserved) if not.
1359 /* Return DOS 24-bit user serial number in BL:CX */
1364 * Return DOS version: Minor:Major in AH:AL
1365 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1367 setAX(PspBlock
->DosVersion
);
1372 /* Terminate and Stay Resident */
1375 DPRINT1("Process going resident: %u paragraphs kept\n", getDX());
1376 DosTerminateProcess(CurrentPsp
, getAL(), getDX());
1380 /* Extended functionalities */
1383 if (getAL() == 0x06)
1386 * DOS 5+ - GET TRUE VERSION NUMBER
1387 * This function always returns the true version number, unlike
1388 * AH=30h, whose return value may be changed with SETVER.
1389 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1390 * for more information.
1394 * Return the true DOS version: Minor:Major in BH:BL
1395 * The Windows NT DOS box returns BX=3205h (version 5.50).
1397 setBX(NTDOS_VERSION
);
1399 /* DOS revision 0 */
1407 // /* Invalid subfunction */
1414 /* Get Address of InDOS flag */
1417 setES(HIWORD(INDOS_POINTER
));
1418 setBX(LOWORD(INDOS_POINTER
));
1423 /* Get Interrupt Vector */
1426 DWORD FarPointer
= ((PDWORD
)BaseAddress
)[getAL()];
1428 /* Read the address from the IDT into ES:BX */
1429 setES(HIWORD(FarPointer
));
1430 setBX(LOWORD(FarPointer
));
1434 /* SWITCH character - AVAILDEV */
1437 if (getAL() == 0x00)
1440 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1441 * This setting is ignored by MS-DOS 4.0+.
1442 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1443 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1444 * for more information.
1449 else if (getAL() == 0x01)
1452 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1453 * This setting is ignored by MS-DOS 5+.
1454 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1455 * for more information.
1460 else if (getAL() == 0x02)
1463 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1464 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1465 * for more information.
1470 else if (getAL() == 0x03)
1473 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1474 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1475 * for more information.
1482 /* Invalid subfunction */
1489 /* Get/Set Country-dependent Information */
1492 CountryCodeBuffer
= (PDOS_COUNTRY_CODE_BUFFER
)SEG_OFF_TO_PTR(getDS(), getDX());
1494 if (getAL() == 0x00)
1497 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_IDATE
,
1498 &CountryCodeBuffer
->TimeFormat
,
1499 sizeof(CountryCodeBuffer
->TimeFormat
) / sizeof(TCHAR
));
1502 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1503 setAX(LOWORD(GetLastError()));
1507 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_SCURRENCY
,
1508 &CountryCodeBuffer
->CurrencySymbol
,
1509 sizeof(CountryCodeBuffer
->CurrencySymbol
) / sizeof(TCHAR
));
1512 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1513 setAX(LOWORD(GetLastError()));
1517 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_STHOUSAND
,
1518 &CountryCodeBuffer
->ThousandSep
,
1519 sizeof(CountryCodeBuffer
->ThousandSep
) / sizeof(TCHAR
));
1522 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1523 setAX(LOWORD(GetLastError()));
1527 Return
= GetLocaleInfo(LOCALE_USER_DEFAULT
, LOCALE_SDECIMAL
,
1528 &CountryCodeBuffer
->DecimalSep
,
1529 sizeof(CountryCodeBuffer
->DecimalSep
) / sizeof(TCHAR
));
1532 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1533 setAX(LOWORD(GetLastError()));
1537 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1541 // TODO: NOT IMPLEMENTED
1548 /* Create Directory */
1551 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1553 if (CreateDirectoryA(String
, NULL
))
1555 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1559 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1560 setAX(LOWORD(GetLastError()));
1566 /* Remove Directory */
1569 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1571 if (RemoveDirectoryA(String
))
1573 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1577 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1578 setAX(LOWORD(GetLastError()));
1584 /* Set Current Directory */
1587 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getDX());
1589 if (DosChangeDirectory(String
))
1591 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1595 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1596 setAX(DosLastError
);
1602 /* Create or Truncate File */
1606 WORD ErrorCode
= DosCreateFile(&FileHandle
,
1607 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
1611 if (ErrorCode
== ERROR_SUCCESS
)
1613 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1618 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1625 /* Open File or Device */
1629 LPCSTR FileName
= (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
1630 WORD ErrorCode
= DosOpenFile(&FileHandle
, FileName
, getAL());
1632 if (ErrorCode
== ERROR_SUCCESS
)
1634 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1639 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1646 /* Close File or Device */
1649 if (DosCloseHandle(getBX()))
1651 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1655 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1656 setAX(ERROR_INVALID_HANDLE
);
1662 /* Read from File or Device */
1668 DPRINT("DosReadFile(0x%04X)\n", getBX());
1671 ErrorCode
= DosReadFile(getBX(),
1672 MAKELONG(getDX(), getDS()),
1677 if (ErrorCode
== ERROR_SUCCESS
)
1679 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1682 else if (ErrorCode
!= ERROR_NOT_READY
)
1684 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1691 /* Write to File or Device */
1694 WORD BytesWritten
= 0;
1695 WORD ErrorCode
= DosWriteFile(getBX(),
1696 MAKELONG(getDX(), getDS()),
1700 if (ErrorCode
== ERROR_SUCCESS
)
1702 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1703 setAX(BytesWritten
);
1707 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1717 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
1719 if (demFileDelete(FileName
) == ERROR_SUCCESS
)
1721 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1723 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
1724 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
1726 setAL(FileName
[0] - 'A');
1730 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1731 setAX(GetLastError());
1741 WORD ErrorCode
= DosSeekFile(getBX(),
1742 MAKELONG(getDX(), getCX()),
1746 if (ErrorCode
== ERROR_SUCCESS
)
1748 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1750 /* Return the new offset in DX:AX */
1751 setDX(HIWORD(NewLocation
));
1752 setAX(LOWORD(NewLocation
));
1756 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1763 /* Get/Set File Attributes */
1767 LPSTR FileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
1769 if (getAL() == 0x00)
1771 /* Get the attributes */
1772 Attributes
= GetFileAttributesA(FileName
);
1774 /* Check if it failed */
1775 if (Attributes
== INVALID_FILE_ATTRIBUTES
)
1777 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1778 setAX(GetLastError());
1782 /* Return the attributes that DOS can understand */
1783 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1784 setCX(Attributes
& 0x00FF);
1787 else if (getAL() == 0x01)
1789 /* Try to set the attributes */
1790 if (SetFileAttributesA(FileName
, getCL()))
1792 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1796 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1797 setAX(GetLastError());
1802 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1803 setAX(ERROR_INVALID_FUNCTION
);
1812 WORD Length
= getCX();
1814 if (DosDeviceIoControl(getBX(), getAL(), MAKELONG(getDX(), getDS()), &Length
))
1816 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1821 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1822 setAX(DosLastError
);
1828 /* Duplicate Handle */
1831 WORD NewHandle
= DosDuplicateHandle(getBX());
1833 if (NewHandle
!= INVALID_DOS_HANDLE
)
1836 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1840 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1841 setAX(DosLastError
);
1847 /* Force Duplicate Handle */
1850 if (DosForceDuplicateHandle(getBX(), getCX()))
1852 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1856 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1857 setAX(ERROR_INVALID_HANDLE
);
1863 /* Get Current Directory */
1866 BYTE DriveNumber
= getDL();
1867 String
= (PCHAR
)SEG_OFF_TO_PTR(getDS(), getSI());
1869 /* Get the real drive number */
1870 if (DriveNumber
== 0)
1872 DriveNumber
= CurrentDrive
;
1876 /* Decrement DriveNumber since it was 1-based */
1880 if (DriveNumber
<= LastDrive
- 'A')
1883 * Copy the current directory into the target buffer.
1884 * It doesn't contain the drive letter and the backslash.
1886 strncpy(String
, CurrentDirectories
[DriveNumber
], DOS_DIR_LENGTH
);
1887 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1888 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
1892 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1893 setAX(ERROR_INVALID_DRIVE
);
1899 /* Allocate Memory */
1902 WORD MaxAvailable
= 0;
1903 WORD Segment
= DosAllocateMemory(getBX(), &MaxAvailable
);
1907 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1912 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1913 setAX(DosLastError
);
1914 setBX(MaxAvailable
);
1923 if (DosFreeMemory(getES()))
1925 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1929 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1930 setAX(ERROR_ARENA_TRASHED
);
1936 /* Resize Memory Block */
1941 if (DosResizeMemory(getES(), getBX(), &Size
))
1943 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1947 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1948 setAX(DosLastError
);
1959 DOS_EXEC_TYPE LoadType
= (DOS_EXEC_TYPE
)getAL();
1960 LPSTR ProgramName
= SEG_OFF_TO_PTR(getDS(), getDX());
1961 PDOS_EXEC_PARAM_BLOCK ParamBlock
= SEG_OFF_TO_PTR(getES(), getBX());
1962 DWORD ReturnAddress
= MAKELONG(Stack
[STACK_IP
], Stack
[STACK_CS
]);
1965 if (LoadType
!= DOS_LOAD_OVERLAY
)
1967 ErrorCode
= DosCreateProcess(LoadType
, ProgramName
, ParamBlock
, ReturnAddress
);
1971 ErrorCode
= DosLoadExecutable(DOS_LOAD_OVERLAY
,
1973 FAR_POINTER(ParamBlock
->CommandLine
),
1974 SEG_OFF_TO_PTR(ParamBlock
->Environment
, 0),
1980 if (ErrorCode
== ERROR_SUCCESS
)
1982 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
1986 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
1994 /* Terminate With Return Code */
1997 DosTerminateProcess(CurrentPsp
, getAL(), 0);
2001 /* Get Return Code (ERRORLEVEL) */
2005 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2006 * DosErrorLevel is cleared after being read by this function.
2008 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2009 setAX(DosErrorLevel
);
2010 DosErrorLevel
= 0x0000; // Clear it
2014 /* Find First File */
2017 WORD Result
= (WORD
)demFileFindFirst(FAR_POINTER(DiskTransferArea
),
2018 SEG_OFF_TO_PTR(getDS(), getDX()),
2023 if (Result
== ERROR_SUCCESS
)
2024 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2026 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2031 /* Find Next File */
2034 WORD Result
= (WORD
)demFileFindNext(FAR_POINTER(DiskTransferArea
));
2038 if (Result
== ERROR_SUCCESS
)
2039 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2041 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2046 /* Internal - Set Current Process ID (Set PSP Address) */
2049 // FIXME: Is it really what it's done ??
2050 CurrentPsp
= getBX();
2054 /* Internal - Get Current Process ID (Get PSP Address) */
2056 /* Get Current PSP Address */
2060 * Undocumented AH=51h is identical to the documented AH=62h.
2061 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2062 * and http://www.ctyme.com/intr/rb-3140.htm
2063 * for more information.
2069 /* Internal - Get "List of lists" (SYSVARS) */
2073 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
2074 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
2075 * for more information.
2078 /* Return the DOS "list of lists" in ES:BX */
2079 setES(DOS_DATA_SEGMENT
);
2080 setBX(FIELD_OFFSET(DOS_SYSVARS
, FirstDpb
));
2088 LPSTR ExistingFileName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2089 LPSTR NewFileName
= (LPSTR
)SEG_OFF_TO_PTR(getES(), getDI());
2092 * See Ralf Brown: http://www.ctyme.com/intr/rb-2990.htm
2093 * for more information.
2096 if (MoveFileA(ExistingFileName
, NewFileName
))
2098 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2102 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2103 setAX(GetLastError());
2109 /* Get/Set Memory Management Options */
2112 if (getAL() == 0x00)
2114 /* Get allocation strategy */
2115 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2116 setAX(DosAllocStrategy
);
2118 else if (getAL() == 0x01)
2120 /* Set allocation strategy */
2122 if ((getBL() & (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2123 == (DOS_ALLOC_HIGH
| DOS_ALLOC_HIGH_LOW
))
2125 /* Can't set both */
2126 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2127 setAX(ERROR_INVALID_PARAMETER
);
2131 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT
)
2133 /* Invalid allocation strategy */
2134 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2135 setAX(ERROR_INVALID_PARAMETER
);
2139 DosAllocStrategy
= getBL();
2140 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2142 else if (getAL() == 0x02)
2144 /* Get UMB link state */
2145 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2146 setAL(DosUmbLinked
? 0x01 : 0x00);
2148 else if (getAL() == 0x03)
2150 /* Set UMB link state */
2151 if (getBX()) DosLinkUmb();
2152 else DosUnlinkUmb();
2153 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2157 /* Invalid or unsupported function */
2158 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2159 setAX(ERROR_INVALID_FUNCTION
);
2165 /* Get Extended Error Information */
2168 DPRINT1("INT 21h, AH = 59h, BX = %04Xh - Get Extended Error Information is UNIMPLEMENTED\n",
2173 /* Create Temporary File */
2176 LPSTR PathName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
2177 LPSTR FileName
= PathName
; // The buffer for the path and the full file name is the same.
2183 * See Ralf Brown: http://www.ctyme.com/intr/rb-3014.htm
2184 * for more information.
2187 // FIXME: Check for buffer validity?
2188 // It should be a ASCIZ path ending with a '\' + 13 zero bytes
2189 // to receive the generated filename.
2191 /* First create the temporary file */
2192 uRetVal
= GetTempFileNameA(PathName
, NULL
, 0, FileName
);
2195 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2196 setAX(GetLastError());
2200 /* Now try to open it in read/write access */
2201 ErrorCode
= DosOpenFile(&FileHandle
, FileName
, 2);
2202 if (ErrorCode
== ERROR_SUCCESS
)
2204 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2209 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2216 /* Create New File */
2220 WORD ErrorCode
= DosCreateFile(&FileHandle
,
2221 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getDX()),
2225 if (ErrorCode
== ERROR_SUCCESS
)
2227 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2232 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2239 /* Lock/Unlock Region of File */
2242 if (getAL() == 0x00)
2244 /* Lock region of file */
2245 if (DosLockFile(getBX(), MAKELONG(getCX(), getDX()), MAKELONG(getSI(), getDI())))
2247 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2251 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2252 setAX(DosLastError
);
2255 else if (getAL() == 0x01)
2257 /* Unlock region of file */
2258 if (DosUnlockFile(getBX(), MAKELONG(getCX(), getDX()), MAKELONG(getSI(), getDI())))
2260 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2264 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2265 setAX(DosLastError
);
2270 /* Invalid subfunction */
2271 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2272 setAX(ERROR_INVALID_FUNCTION
);
2278 /* Canonicalize File Name or Path */
2282 * See Ralf Brown: http://www.ctyme.com/intr/rb-3137.htm
2283 * for more information.
2287 * We suppose that the DOS app gave to us a valid
2288 * 128-byte long buffer for the canonicalized name.
2290 DWORD dwRetVal
= GetFullPathNameA(SEG_OFF_TO_PTR(getDS(), getSI()),
2292 SEG_OFF_TO_PTR(getES(), getDI()),
2296 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2297 setAX(GetLastError());
2301 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2305 // FIXME: Convert the full path name into short version.
2306 // We cannot reliably use GetShortPathName, because it fails
2307 // if the path name given doesn't exist. However this DOS
2308 // function AH=60h should be able to work even for non-existing
2309 // path and file names.
2314 /* Set Handle Count */
2317 if (!DosResizeHandleTable(getBX()))
2319 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2320 setAX(DosLastError
);
2322 else Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2332 * Function 6Ah is identical to function 68h,
2333 * and sets AH to 68h if success.
2334 * See Ralf Brown: http://www.ctyme.com/intr/rb-3176.htm
2335 * for more information.
2339 if (DosFlushFileBuffers(getBX()))
2341 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2345 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2346 setAX(GetLastError());
2352 /* Extended Open/Create */
2356 WORD CreationStatus
;
2359 /* Check for AL == 00 */
2360 if (getAL() != 0x00)
2362 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2363 setAX(ERROR_INVALID_FUNCTION
);
2368 * See Ralf Brown: http://www.ctyme.com/intr/rb-3179.htm
2369 * for the full detailed description.
2371 * WARNING: BH contains some extended flags that are NOT SUPPORTED.
2374 ErrorCode
= DosCreateFileEx(&FileHandle
,
2376 (LPCSTR
)SEG_OFF_TO_PTR(getDS(), getSI()),
2381 if (ErrorCode
== ERROR_SUCCESS
)
2383 Stack
[STACK_FLAGS
] &= ~EMULATOR_FLAG_CF
;
2384 setCX(CreationStatus
);
2389 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2399 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2402 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2403 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2410 VOID WINAPI
DosBreakInterrupt(LPWORD Stack
)
2412 /* Set CF to terminate the running process */
2413 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2416 VOID WINAPI
DosFastConOut(LPWORD Stack
)
2419 * This is the DOS 2+ Fast Console Output Interrupt.
2420 * The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
2422 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2423 * for more information.
2426 /* Save AX and BX */
2427 USHORT AX
= getAX();
2428 USHORT BX
= getBX();
2431 * Set the parameters:
2432 * AL contains the character to print (already set),
2433 * BL contains the character attribute,
2434 * BH contains the video page to use.
2436 setBL(DOS_CHAR_ATTRIBUTE
);
2437 setBH(Bda
->VideoPage
);
2439 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
2441 Int32Call(&DosContext
, BIOS_VIDEO_INTERRUPT
);
2443 /* Restore AX and BX */
2448 VOID WINAPI
DosInt2Fh(LPWORD Stack
)
2452 /* Extended Memory Specification */
2456 if (!XmsGetDriverEntry(&DriverEntry
)) break;
2458 if (getAL() == 0x00)
2460 /* The driver is loaded */
2463 else if (getAL() == 0x10)
2465 setES(HIWORD(DriverEntry
));
2466 setBX(LOWORD(DriverEntry
));
2470 DPRINT1("Unknown DOS XMS Function: INT 0x2F, AH = 43h, AL = %xh\n", getAL());
2478 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2480 Stack
[STACK_FLAGS
] |= EMULATOR_FLAG_CF
;
2485 BOOLEAN
DosKRNLInitialize(VOID
)
2490 CHAR CurrentDirectory
[MAX_PATH
];
2491 CHAR DosDirectory
[DOS_DIR_LENGTH
];
2495 const BYTE NullDriverRoutine
[] = {
2496 /* Strategy routine entry */
2497 0x26, // mov [Request.Status], DOS_DEVSTAT_DONE
2500 FIELD_OFFSET(DOS_REQUEST_HEADER
, Status
),
2501 LOBYTE(DOS_DEVSTAT_DONE
),
2502 HIBYTE(DOS_DEVSTAT_DONE
),
2504 /* Interrupt routine entry */
2511 /* Setup the InDOS flag */
2512 InDos
= (PBYTE
)FAR_POINTER(INDOS_POINTER
);
2515 /* Clear the current directory buffer */
2516 RtlZeroMemory(CurrentDirectories
, sizeof(CurrentDirectories
));
2518 /* Get the current directory */
2519 if (!GetCurrentDirectoryA(MAX_PATH
, CurrentDirectory
))
2521 // TODO: Use some kind of default path?
2525 /* Convert that to a DOS path */
2526 if (!GetShortPathNameA(CurrentDirectory
, DosDirectory
, DOS_DIR_LENGTH
))
2528 // TODO: Use some kind of default path?
2533 CurrentDrive
= DosDirectory
[0] - 'A';
2535 /* Get the directory part of the path */
2536 Path
= strchr(DosDirectory
, '\\');
2539 /* Skip the backslash */
2543 /* Set the directory */
2546 strncpy(CurrentDirectories
[CurrentDrive
], Path
, DOS_DIR_LENGTH
);
2549 /* Read CONFIG.SYS */
2550 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
2553 while (fgetws(Buffer
, 256, Stream
))
2555 // TODO: Parse the line
2560 /* Initialize the list of lists */
2561 SysVars
= (PDOS_SYSVARS
)SEG_OFF_TO_PTR(DOS_DATA_SEGMENT
, 0);
2562 RtlZeroMemory(SysVars
, sizeof(DOS_SYSVARS
));
2563 SysVars
->FirstMcb
= FIRST_MCB_SEGMENT
;
2564 SysVars
->FirstSft
= MAKELONG(MASTER_SFT_OFFSET
, DOS_DATA_SEGMENT
);
2566 /* Initialize the NUL device driver */
2567 SysVars
->NullDevice
.Link
= 0xFFFFFFFF;
2568 SysVars
->NullDevice
.DeviceAttributes
= DOS_DEVATTR_NUL
| DOS_DEVATTR_CHARACTER
;
2569 SysVars
->NullDevice
.StrategyRoutine
= FIELD_OFFSET(DOS_SYSVARS
, NullDriverRoutine
);
2570 SysVars
->NullDevice
.InterruptRoutine
= SysVars
->NullDevice
.StrategyRoutine
+ 6;
2571 RtlFillMemory(SysVars
->NullDevice
.DeviceName
,
2572 sizeof(SysVars
->NullDevice
.DeviceName
),
2574 RtlCopyMemory(SysVars
->NullDevice
.DeviceName
, "NUL", strlen("NUL"));
2575 RtlCopyMemory(SysVars
->NullDriverRoutine
,
2577 sizeof(NullDriverRoutine
));
2579 /* Initialize the SFT */
2580 Sft
= (PDOS_SFT
)FAR_POINTER(SysVars
->FirstSft
);
2581 Sft
->Link
= 0xFFFFFFFF;
2582 Sft
->NumDescriptors
= DOS_SFT_SIZE
;
2584 for (i
= 0; i
< Sft
->NumDescriptors
; i
++)
2586 /* Clear the file descriptor entry */
2587 RtlZeroMemory(&Sft
->FileDescriptors
[i
], sizeof(DOS_FILE_DESCRIPTOR
));
2592 /* Initialize the callback context */
2593 InitializeContext(&DosContext
, 0x0070, 0x0000);
2595 /* Register the DOS 32-bit Interrupts */
2596 RegisterDosInt32(0x20, DosInt20h
);
2597 RegisterDosInt32(0x21, DosInt21h
);
2598 // RegisterDosInt32(0x22, DosInt22h ); // Termination
2599 RegisterDosInt32(0x23, DosBreakInterrupt
); // Ctrl-C / Ctrl-Break
2600 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
2601 RegisterDosInt32(0x29, DosFastConOut
); // DOS 2+ Fast Console Output
2602 RegisterDosInt32(0x2F, DosInt2Fh
);
2604 /* Load the EMS driver */
2605 if (!EmsDrvInitialize(EMS_TOTAL_PAGES
))
2607 DPRINT1("Could not initialize EMS. EMS will not be available.\n"
2608 "Try reducing the number of EMS pages.\n");
2611 /* Load the XMS driver (HIMEM) */
2614 /* Load the CON driver */