2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: subsystems/mvdm/ntvdm/dos/dem.c
5 * PURPOSE: DOS 32-bit Emulation Support Library -
6 * This library is used by the built-in NTVDM DOS32 and by
7 * the NT 16-bit DOS in Windows (via BOPs). It also exposes
8 * exported functions that can be used by VDDs.
9 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
10 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
13 /* INCLUDES *******************************************************************/
26 #include "dos/dos32krnl/device.h"
27 #include "dos/dos32krnl/memory.h"
28 #include "dos/dos32krnl/process.h"
32 #include "bios/bios.h"
39 * Activate this line if you want to have COMMAND.COM completely external.
41 // #define COMSPEC_FULLY_EXTERNAL
43 /* PRIVATE VARIABLES **********************************************************/
45 /* PRIVATE FUNCTIONS **********************************************************/
47 /* PUBLIC VARIABLES ***********************************************************/
49 /* PUBLIC FUNCTIONS ***********************************************************/
52 /******************************************************************************\
53 |** DOS DEM Kernel helpers **|
54 \******************************************************************************/
57 VOID
Dem_BiosCharPrint(CHAR Character
)
65 * AL contains the character to print,
66 * BL contains the character attribute,
67 * BH contains the video page to use.
70 setBL(DEFAULT_ATTRIBUTE
);
71 setBH(Bda
->VideoPage
);
73 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
75 Int32Call(&DosContext
, BIOS_VIDEO_INTERRUPT
);
77 /* Restore AX and BX */
82 VOID
DosCharPrint(CHAR Character
)
84 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
88 static VOID
DemLoadNTDOSKernel(VOID
)
90 BOOLEAN Success
= FALSE
;
91 LPCSTR DosKernelFileName
= "ntdos.sys";
93 ULONG ulDosKernelSize
= 0;
95 DPRINT1("You are loading Windows NT DOS!\n");
97 /* Open the DOS kernel file */
98 hDosKernel
= FileOpen(DosKernelFileName
, &ulDosKernelSize
);
99 if (hDosKernel
== NULL
) goto Quit
;
102 * Attempt to load the DOS kernel into memory.
103 * The segment where to load the DOS kernel is defined
104 * by the DOS BIOS and is found in DI:0000 .
106 Success
= FileLoadByHandle(hDosKernel
,
107 REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
111 DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
113 (Success
? "succeeded" : "failed"),
118 /* Close the DOS kernel file */
119 FileClose(hDosKernel
);
124 /* We failed everything, stop the VDM */
125 BiosDisplayMessage("Windows NT DOS kernel file '%s' loading failed (Error: %u). The VDM will shut down.\n",
126 DosKernelFileName
, GetLastError());
132 static VOID WINAPI
DosSystemBop(LPWORD Stack
)
134 /* Get the Function Number and skip it */
135 BYTE FuncNum
= *(PBYTE
)SEG_OFF_TO_PTR(getCS(), getIP());
140 /* Load the DOS kernel */
143 DemLoadNTDOSKernel();
147 /* Call 32-bit Driver Strategy Routine */
148 case BOP_DRV_STRATEGY
:
154 /* Call 32-bit Driver Interrupt Routine */
155 case BOP_DRV_INTERRUPT
:
157 DeviceInterruptBop();
163 DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum
);
164 // setCF(1); // Disable, otherwise we enter an infinite loop
173 /******************************************************************************\
174 |** DOS Command Process management **|
175 \******************************************************************************/
179 static ULONG SessionId
= 0;
182 * 16-bit Command Interpreter information for DOS reentry
184 typedef struct _COMSPEC_INFO
190 } COMSPEC_INFO
, *PCOMSPEC_INFO
;
192 static COMSPEC_INFO RootCmd
;
193 static DWORD ReentrancyCount
= 0;
195 // FIXME: Should we need list locking?
196 static LIST_ENTRY ComSpecInfoList
= { &ComSpecInfoList
, &ComSpecInfoList
};
199 FindComSpecInfoByPsp(WORD Psp
)
202 PCOMSPEC_INFO ComSpecInfo
;
204 for (Pointer
= ComSpecInfoList
.Flink
; Pointer
!= &ComSpecInfoList
; Pointer
= Pointer
->Flink
)
206 ComSpecInfo
= CONTAINING_RECORD(Pointer
, COMSPEC_INFO
, Entry
);
207 if (ComSpecInfo
->ComSpecPsp
== Psp
) return ComSpecInfo
;
214 InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo
)
216 InsertHeadList(&ComSpecInfoList
, &ComSpecInfo
->Entry
);
220 RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo
)
222 RemoveEntryList(&ComSpecInfo
->Entry
);
223 if (ComSpecInfo
!= &RootCmd
)
224 RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo
);
228 static VOID
DosProcessConsoleAttach(VOID
)
230 /* Attach to the console */
232 VidBiosAttachToConsole();
235 static VOID
DosProcessConsoleDetach(VOID
)
237 /* Detach from the console */
238 VidBiosDetachFromConsole();
243 * Data for the next DOS command to run
246 static VDM_COMMAND_INFO CommandInfo
;
247 static BOOLEAN Repeat
= FALSE
;
248 static BOOLEAN Reentry
= FALSE
;
250 static BOOLEAN First
= TRUE
;
251 static CHAR CmdLine
[MAX_PATH
] = ""; // DOS_CMDLINE_LENGTH
252 static CHAR AppName
[MAX_PATH
] = "";
254 static CHAR PifFile
[MAX_PATH
] = "";
255 static CHAR CurDirectory
[MAX_PATH
] = "";
256 static CHAR Desktop
[MAX_PATH
] = "";
257 static CHAR Title
[MAX_PATH
] = "";
258 static ULONG EnvSize
= 256;
259 static PVOID Env
= NULL
;
262 #pragma pack(push, 2)
265 * This structure is compatible with Windows NT DOS
267 typedef struct _NEXT_CMD
286 } NEXT_CMD
, *PNEXT_CMD
;
290 static VOID
CmdStartProcess(VOID
)
293 PCOMSPEC_INFO ComSpecInfo
;
296 PNEXT_CMD DataStruct
= (PNEXT_CMD
)SEG_OFF_TO_PTR(getDS(), getDX());
298 DPRINT1("CmdStartProcess -- DS:DX = %04X:%04X (DataStruct = 0x%p)\n",
299 getDS(), getDX(), DataStruct
);
305 /* Check whether we need to shell out now in case we were started by a 32-bit app */
306 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
307 if (ComSpecInfo
&& ComSpecInfo
->Terminated
)
309 RemoveComSpecInfo(ComSpecInfo
);
311 DPRINT1("Exit DOS from start-app BOP\n");
316 /* Clear the structure */
317 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
319 /* Initialize the structure members */
320 CommandInfo
.TaskId
= SessionId
;
321 CommandInfo
.VDMState
= VDM_FLAG_DOS
;
322 CommandInfo
.CmdLine
= CmdLine
;
323 CommandInfo
.CmdLen
= sizeof(CmdLine
);
324 CommandInfo
.AppName
= AppName
;
325 CommandInfo
.AppLen
= sizeof(AppName
);
326 CommandInfo
.PifFile
= PifFile
;
327 CommandInfo
.PifLen
= sizeof(PifFile
);
328 CommandInfo
.CurDirectory
= CurDirectory
;
329 CommandInfo
.CurDirectoryLen
= sizeof(CurDirectory
);
330 CommandInfo
.Desktop
= Desktop
;
331 CommandInfo
.DesktopLen
= sizeof(Desktop
);
332 CommandInfo
.Title
= Title
;
333 CommandInfo
.TitleLen
= sizeof(Title
);
334 CommandInfo
.Env
= Env
;
335 CommandInfo
.EnvLen
= EnvSize
;
337 if (First
) CommandInfo
.VDMState
|= VDM_FLAG_FIRST_TASK
;
341 if (Repeat
) CommandInfo
.VDMState
|= VDM_FLAG_RETRY
;
344 /* Get the VDM command information */
345 DPRINT1("Calling GetNextVDMCommand in CmdStartProcess: wait for new VDM task...\n");
346 if (!GetNextVDMCommand(&CommandInfo
))
348 DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
349 if (CommandInfo
.EnvLen
> EnvSize
)
351 /* Expand the environment size */
352 EnvSize
= CommandInfo
.EnvLen
;
353 CommandInfo
.Env
= Env
= RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, Env
, EnvSize
);
355 /* Repeat the request */
360 /* Shouldn't happen */
361 DisplayMessage(L
"An unrecoverable failure happened from start-app BOP; exiting DOS.");
366 // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
368 DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
374 DPRINT1("Exit DOS from start-app BOP\n");
381 /* Compute the command line length, not counting the terminating "\r\n" */
382 CmdLen
= strlen(CmdLine
);
383 if (CmdLen
>= 2 && CmdLine
[CmdLen
- 2] == '\r')
386 DPRINT1("Starting '%s' ('%.*s')...\n", AppName
, CmdLen
, CmdLine
);
388 /* Start the process */
389 // FIXME: Merge 'Env' with the master environment SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0)
390 // FIXME: Environment
391 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct
->AppNameSeg
, DataStruct
->AppNameOff
), AppName
, MAX_PATH
);
392 *(PBYTE
)(SEG_OFF_TO_PTR(DataStruct
->CmdLineSeg
, DataStruct
->CmdLineOff
)) = (BYTE
)CmdLen
;
393 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct
->CmdLineSeg
, DataStruct
->CmdLineOff
+ 1), CmdLine
, DOS_CMDLINE_LENGTH
);
396 /* Update console title if we run in a separate console */
398 SetConsoleTitleA(AppName
);
404 DPRINT1("App started!\n");
411 static VOID
CmdStartExternalCommand(VOID
)
415 // TODO: improve: this code has strong similarities
416 // with the 'default' case of DosCreateProcess.
418 LPSTR Command
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getSI());
419 CHAR CmdLine
[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH
+ 1] = "";
423 /* Spawn a user-defined 32-bit command preprocessor */
425 // FIXME: Use COMSPEC env var!!
426 CmdLinePtr
= CmdLine
;
427 strcpy(CmdLinePtr
, "cmd.exe /c ");
428 CmdLinePtr
+= strlen(CmdLinePtr
);
430 /* Build a Win32-compatible command-line */
431 CmdLineLen
= min(strlen(Command
), sizeof(CmdLine
) - strlen(CmdLinePtr
) - 1);
432 RtlCopyMemory(CmdLinePtr
, Command
, CmdLineLen
);
433 CmdLinePtr
[CmdLineLen
] = '\0';
435 /* Remove any trailing return carriage character and NULL-terminate the command line */
436 while (*CmdLinePtr
&& *CmdLinePtr
!= '\r' && *CmdLinePtr
!= '\n') CmdLinePtr
++;
439 DPRINT1("CMD Run Command '%s' ('%s')\n", Command
, CmdLine
);
442 * No need to prepare the stack for DosStartComSpec since we won't start it.
444 Result
= DosStartProcess32(Command
, CmdLine
,
445 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
446 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
448 if (Result
!= ERROR_SUCCESS
)
450 DosDisplayMessage("Failed to start command '%s' ('%s'). Error: %u\n", Command
, CmdLine
, Result
);
452 setAL((UCHAR
)Result
);
456 DosDisplayMessage("Command '%s' ('%s') started successfully.\n", Command
, CmdLine
);
458 setCF(Repeat
); // Set CF if we need to start a 16-bit process
465 static VOID
CmdStartComSpec32(VOID
)
469 // TODO: improve: this code has strong similarities with the
470 // 'default' case of DosCreateProcess and with the 'case 0x08'.
472 CHAR CmdLine
[sizeof("cmd.exe") + 1] = "";
474 /* Spawn a user-defined 32-bit command preprocessor */
476 // FIXME: Use COMSPEC env var!!
477 strcpy(CmdLine
, "cmd.exe");
479 DPRINT1("CMD Run 32-bit Command Interpreter '%s'\n", CmdLine
);
482 * No need to prepare the stack for DosStartComSpec since we won't start it.
484 Result
= DosStartProcess32(CmdLine
, CmdLine
,
485 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
486 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
488 if (Result
!= ERROR_SUCCESS
)
490 DosDisplayMessage("Failed to start 32-bit Command Interpreter '%s'. Error: %u\n", CmdLine
, Result
);
492 setAL((UCHAR
)Result
);
496 DosDisplayMessage("32-bit Command Interpreter '%s' started successfully.\n", CmdLine
);
498 setCF(Repeat
); // Set CF if we need to start a 16-bit process
505 static VOID
CmdSetExitCode(VOID
)
509 PCOMSPEC_INFO ComSpecInfo
;
510 VDM_COMMAND_INFO CommandInfo
;
518 * Check whether we need to shell out now in case we were started by a 32-bit app,
519 * or we were started alone along with the root 32-bit app.
521 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
522 if ((ComSpecInfo
&& ComSpecInfo
->Terminated
) ||
523 (ComSpecInfo
== &RootCmd
&& SessionId
!= 0))
525 RemoveComSpecInfo(ComSpecInfo
);
527 DPRINT1("Exit DOS from ExitCode (prologue)!\n");
533 /* Clear the VDM structure */
534 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
537 /* Update the VDM state of the task */
538 // CommandInfo.TaskId = SessionId;
539 CommandInfo
.ExitCode
= getDX();
540 CommandInfo
.VDMState
= VDM_FLAG_DONT_WAIT
;
541 DPRINT1("Calling GetNextVDMCommand 32bit end of VDM task\n");
542 Success
= GetNextVDMCommand(&CommandInfo
);
543 DPRINT1("GetNextVDMCommand 32bit end of VDM task success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
546 * Check whether we were awaited because the 32-bit process was stopped,
547 * or because it started a new DOS application.
549 if (CommandInfo
.CmdLen
!= 0 || CommandInfo
.AppLen
!= 0 || CommandInfo
.PifLen
!= 0)
551 DPRINT1("GetNextVDMCommand end-of-app, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
552 CommandInfo
.CmdLen
, CommandInfo
.AppLen
, CommandInfo
.PifLen
);
554 /* Repeat the request */
560 DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
562 /* Check whether we need to shell out now in case we were started by a 32-bit app */
563 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
564 if (!ComSpecInfo
|| !ComSpecInfo
->Terminated
)
566 DPRINT1("Not our 32-bit app, retrying...\n");
570 ASSERT(ComSpecInfo
->Terminated
== TRUE
);
572 /* Record found, remove it and exit now */
573 RemoveComSpecInfo(ComSpecInfo
);
575 DPRINT1("Exit DOS from ExitCode wait!\n");
580 // FIXME: Use the retrieved exit code as the value of our exit code
581 // when COMMAND.COM will shell-out ??
588 static VOID WINAPI
DosCmdInterpreterBop(LPWORD Stack
)
590 /* Get the Function Number and skip it */
591 BYTE FuncNum
= *(PBYTE
)SEG_OFF_TO_PTR(getCS(), getIP());
605 * Get a new app to start
608 * DS:DX : Data block.
611 * CF : 0: Success; 1: Failure.
620 * Check binary format
623 * DS:DX : Program to check.
626 * CF : 0: Success; 1: Failure.
632 LPSTR ProgramName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
634 if (!GetBinaryTypeA(ProgramName
, &BinaryType
))
636 /* An error happened, bail out */
638 setAX(LOWORD(GetLastError()));
642 // FIXME: We only support DOS binaries for now...
643 ASSERT(BinaryType
== SCS_DOS_BINARY
);
644 if (BinaryType
!= SCS_DOS_BINARY
)
646 /* An error happened, bail out */
648 setAX(LOWORD(ERROR_BAD_EXE_FORMAT
));
652 /* Return success: DOS application */
658 * Start an external command
661 * DS:SI : Command to start.
662 * ES : Environment block segment.
663 * AL : Current drive number.
664 * AH : 0: Directly start the command;
665 * 1: Use "cmd.exe /c" to start the command.
668 * CF : 0: Shell-out; 1: Continue.
669 * AL : Error/Exit code.
673 CmdStartExternalCommand();
678 * Start the default 32-bit command interpreter (COMSPEC)
681 * ES : Environment block segment.
682 * AL : Current drive number.
685 * CF : 0: Shell-out; 1: Continue.
686 * AL : Error/Exit code.
701 * CF : 0: Shell-out; 1: Continue.
710 * Get start information
713 * AL : 0 (resp. 1): Started from (resp. without) an existing console.
719 * When a new instance of our (internal) COMMAND.COM is started,
720 * we check whether we need to run a 32-bit COMSPEC. This goes by
721 * checking whether we were started in a new console (no parent
722 * console process) or from an existing one.
724 * However COMMAND.COM can also be started in the case where a
725 * 32-bit process (started by a 16-bit parent) wants to start a new
726 * 16-bit process: to ensure DOS reentry we need to start a new
727 * instance of COMMAND.COM. On Windows the COMMAND.COM is started
728 * just before the 32-bit process (in fact, it is this COMMAND.COM
729 * which starts the 32-bit process via an undocumented command-line
730 * switch '/z', which syntax is:
731 * COMMAND.COM /z\bAPPNAME.EXE
732 * notice the '\b' character inserted in-between. Then COMMAND.COM
733 * issues a BOP_CMD 08h with AH=00h to start the process).
735 * Instead, we do the reverse, i.e. we start the 32-bit process,
736 * and *only* if needed, i.e. if this process wants to start a
737 * new 16-bit process, we start our COMMAND.COM.
739 * The problem we then face is that our COMMAND.COM will possibly
740 * want to start a new COMSPEC, however we do not want this.
741 * The chosen solution is to flag this case -- done with the 'Reentry'
742 * boolean -- so that COMMAND.COM will not attempt to start COMSPEC
743 * but instead will directly try to start the 16-bit process.
745 // setAL(SessionId != 0);
746 setAL((SessionId
!= 0) && !Reentry
);
747 /* Reset 'Reentry' */
757 DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum
);
758 // setCF(1); // Disable, otherwise we enter an infinite loop
764 #ifndef COMSPEC_FULLY_EXTERNAL
766 * Internal COMMAND.COM binary data in the CommandCom array.
768 #include "command_com.h"
772 DWORD
DosStartComSpec(IN BOOLEAN Permanent
,
773 IN LPCSTR Environment OPTIONAL
,
774 IN DWORD ReturnAddress OPTIONAL
,
775 OUT PWORD ComSpecPsp OPTIONAL
)
779 * TRUE to simulate the /P switch of command.com: starts AUTOEXEC.BAT/NT
780 * and makes the interpreter permanent (cannot exit).
785 if (ComSpecPsp
) *ComSpecPsp
= 0;
788 #ifndef COMSPEC_FULLY_EXTERNAL
789 DosLoadExecutableInternal(DOS_LOAD_AND_EXECUTE
,
794 DosLoadExecutable(DOS_LOAD_AND_EXECUTE
,
795 #ifndef STANDALONE // FIXME: Those values are hardcoded paths on my local test machines!!
798 "H:\\DOS_tests\\CMDCMD.COM",
800 #endif // COMSPEC_FULLY_EXTERNAL
802 Permanent
? "/P" : "",
803 Environment
? Environment
: "", // FIXME: Default environment!
805 if (Result
!= ERROR_SUCCESS
) return Result
;
807 /* TODO: Read AUTOEXEC.NT/BAT */
809 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
810 if (ComSpecPsp
) *ComSpecPsp
= Sda
->CurrentPsp
;
815 typedef struct _DOS_START_PROC32
817 LPSTR ExecutablePath
;
819 LPSTR Environment OPTIONAL
;
821 PCOMSPEC_INFO ComSpecInfo
;
824 } DOS_START_PROC32
, *PDOS_START_PROC32
;
828 CommandThreadProc(LPVOID Parameter
)
831 PROCESS_INFORMATION ProcessInfo
;
832 STARTUPINFOA StartupInfo
;
834 PDOS_START_PROC32 DosStartProc32
= (PDOS_START_PROC32
)Parameter
;
836 VDM_COMMAND_INFO CommandInfo
;
837 PCOMSPEC_INFO ComSpecInfo
= DosStartProc32
->ComSpecInfo
;
840 /* Set up the VDM, startup and process info structures */
842 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
844 RtlZeroMemory(&ProcessInfo
, sizeof(ProcessInfo
));
845 RtlZeroMemory(&StartupInfo
, sizeof(StartupInfo
));
846 StartupInfo
.cb
= sizeof(StartupInfo
);
848 // FIXME: Build suitable 32-bit environment!!
852 * Wait for signaling a new VDM task and increment the VDM re-entry count so
853 * that we can handle 16-bit apps that may be possibly started by the 32-bit app.
855 CommandInfo
.VDMState
= VDM_INC_REENTER_COUNT
;
856 DPRINT1("Calling GetNextVDMCommand reenter++\n");
857 Success
= GetNextVDMCommand(&CommandInfo
);
858 DPRINT1("GetNextVDMCommand reenter++ success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
862 /* Start the process */
863 Success
= CreateProcessA(NULL
, // ProgramName,
864 DosStartProc32
->CommandLine
,
867 TRUE
, // Inherit handles
868 CREATE_DEFAULT_ERROR_MODE
| CREATE_SUSPENDED
,
869 DosStartProc32
->Environment
,
870 NULL
, // lpCurrentDirectory, see "START" command in cmd.exe
875 /* Signal our caller the process was started */
876 SetEvent(DosStartProc32
->hEvent
);
877 // After this point, 'DosStartProc32' is not valid anymore.
882 /* Resume the process */
883 ResumeThread(ProcessInfo
.hThread
);
885 /* Wait for the process to finish running and retrieve its exit code */
886 WaitForSingleObject(ProcessInfo
.hProcess
, INFINITE
);
887 GetExitCodeProcess(ProcessInfo
.hProcess
, &dwExitCode
);
889 /* Close the handles */
890 CloseHandle(ProcessInfo
.hThread
);
891 CloseHandle(ProcessInfo
.hProcess
);
895 dwExitCode
= GetLastError();
900 ComSpecInfo
->Terminated
= TRUE
;
901 ComSpecInfo
->dwExitCode
= dwExitCode
;
903 /* Decrement the VDM re-entry count */
904 CommandInfo
.VDMState
= VDM_DEC_REENTER_COUNT
;
905 DPRINT1("Calling GetNextVDMCommand reenter--\n");
906 Success
= GetNextVDMCommand(&CommandInfo
);
907 DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
916 DWORD
DosStartProcess32(IN LPCSTR ExecutablePath
,
917 IN LPCSTR CommandLine
,
918 IN LPCSTR Environment OPTIONAL
,
919 IN DWORD ReturnAddress OPTIONAL
,
920 IN BOOLEAN StartComSpec
)
922 DWORD Result
= ERROR_SUCCESS
;
923 HANDLE CommandThread
;
924 DOS_START_PROC32 DosStartProc32
;
927 VDM_COMMAND_INFO CommandInfo
;
930 DosStartProc32
.ExecutablePath
= (LPSTR
)ExecutablePath
;
931 DosStartProc32
.CommandLine
= (LPSTR
)CommandLine
;
932 DosStartProc32
.Environment
= (LPSTR
)Environment
;
935 DosStartProc32
.ComSpecInfo
=
936 RtlAllocateHeap(RtlGetProcessHeap(),
938 sizeof(*DosStartProc32
.ComSpecInfo
));
939 ASSERT(DosStartProc32
.ComSpecInfo
);
941 DosStartProc32
.hEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
942 ASSERT(DosStartProc32
.hEvent
);
945 /* Pause the VM and detach from the console */
947 DosProcessConsoleDetach();
949 /* Start the 32-bit process via another thread */
950 CommandThread
= CreateThread(NULL
, 0, &CommandThreadProc
, &DosStartProc32
, 0, NULL
);
951 if (CommandThread
== NULL
)
953 DisplayMessage(L
"FATAL: Failed to create the command processing thread: %d", GetLastError());
954 Result
= GetLastError();
959 /* Close the thread handle */
960 CloseHandle(CommandThread
);
962 /* Wait for the process to be ready to start */
963 WaitForSingleObject(DosStartProc32
.hEvent
, INFINITE
);
965 /* Wait for any potential new DOS app started by the 32-bit process */
966 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
969 CommandInfo
.VDMState
= VDM_FLAG_NESTED_TASK
| VDM_FLAG_DONT_WAIT
;
970 DPRINT1("Calling GetNextVDMCommand 32bit for possible new VDM task...\n");
971 Success
= GetNextVDMCommand(&CommandInfo
);
972 DPRINT1("GetNextVDMCommand 32bit awaited, success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
975 * Check whether we were awaited because the 32-bit process was stopped,
976 * or because it started a new DOS application.
978 if (CommandInfo
.CmdLen
!= 0 || CommandInfo
.AppLen
!= 0 || CommandInfo
.PifLen
!= 0)
980 DPRINT1("GetNextVDMCommand 32bit, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
981 CommandInfo
.CmdLen
, CommandInfo
.AppLen
, CommandInfo
.PifLen
);
983 /* Repeat the request */
987 * Set 'Reentry' to TRUE or FALSE depending on whether we are going
988 * to reenter with a new COMMAND.COM. See the comment for:
989 * BOP_CMD 0x10 'Get start information'
990 * (dem.c!DosCmdInterpreterBop) for more details.
992 Reentry
= StartComSpec
;
994 /* If needed, start a new command interpreter to handle the possible incoming DOS commands */
998 // DosStartProcess32 was only called by DosCreateProcess, called from INT 21h,
999 // so the caller stack is already prepared for running a new DOS program
1000 // (Flags, CS and IP, and the extra interrupt number, are already pushed).
1002 Result
= DosStartComSpec(FALSE
, Environment
, ReturnAddress
,
1003 &DosStartProc32
.ComSpecInfo
->ComSpecPsp
);
1004 if (Result
!= ERROR_SUCCESS
)
1006 DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result
);
1012 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
1013 DosStartProc32
.ComSpecInfo
->ComSpecPsp
= Sda
->CurrentPsp
;
1014 Result
= ERROR_SUCCESS
;
1017 /* Insert the new entry in the list; it will be freed when needed by COMMAND.COM */
1018 InsertComSpecInfo(DosStartProc32
.ComSpecInfo
);
1022 DPRINT1("GetNextVDMCommand 32bit, 32-bit app stopped\n");
1024 /* Check whether this was our 32-bit app which was killed */
1025 if (!DosStartProc32
.ComSpecInfo
->Terminated
)
1027 DPRINT1("Not our 32-bit app, retrying...\n");
1031 Result
= DosStartProc32
.ComSpecInfo
->dwExitCode
;
1033 /* Delete the entry */
1034 RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32
.ComSpecInfo
);
1037 /* Wait for the thread to finish */
1038 WaitForSingleObject(CommandThread
, INFINITE
);
1039 GetExitCodeThread(CommandThread
, &Result
);
1041 /* Close the thread handle */
1042 CloseHandle(CommandThread
);
1044 DPRINT1("32-bit app stopped\n");
1049 CloseHandle(DosStartProc32
.hEvent
);
1052 /* Attach to the console and resume the VM */
1053 DosProcessConsoleAttach();
1062 /******************************************************************************\
1063 |** DOS Bootloader emulation, Startup and Shutdown **|
1064 \******************************************************************************/
1068 // This function (equivalent of the DOS bootsector) is called by the bootstrap
1069 // loader *BEFORE* jumping at 0000:7C00. What we should do is to write at 0000:7C00
1070 // a BOP call that calls DosInitialize back. Then the bootstrap loader jumps at
1071 // 0000:7C00, our BOP gets called and then we can initialize the 32-bit part of the DOS.
1074 /* 16-bit bootstrap code at 0000:7C00 */
1075 /* Of course, this is not in real bootsector format, because we don't care about it for now */
1076 static BYTE Bootsector1
[] =
1078 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_LOAD_DOS
1080 /* This portion of code is run if we failed to load the DOS */
1081 // NOTE: This may also be done by the BIOS32.
1082 static BYTE Bootsector2
[] =
1084 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_UNSIMULATE
1087 static VOID WINAPI
DosInitialize(LPWORD Stack
);
1089 VOID
DosBootsectorInitialize(VOID
)
1091 /* We write the bootsector at 0000:7C00 */
1092 ULONG_PTR StartAddress
= (ULONG_PTR
)SEG_OFF_TO_PTR(0x0000, 0x7C00);
1093 ULONG_PTR Address
= StartAddress
;
1094 CHAR DosKernelFileName
[] = ""; // No DOS BIOS file name, therefore we will load DOS32
1096 DPRINT("DosBootsectorInitialize\n");
1098 /* Write the "bootsector" */
1099 RtlCopyMemory((PVOID
)Address
, Bootsector1
, sizeof(Bootsector1
));
1100 Address
+= sizeof(Bootsector1
);
1101 RtlCopyMemory((PVOID
)Address
, DosKernelFileName
, sizeof(DosKernelFileName
));
1102 Address
+= sizeof(DosKernelFileName
);
1103 RtlCopyMemory((PVOID
)Address
, Bootsector2
, sizeof(Bootsector2
));
1104 Address
+= sizeof(Bootsector2
);
1106 /* Initialize the callback context */
1107 InitializeContext(&DosContext
, 0x0000,
1108 (ULONG_PTR
)MEM_ALIGN_UP(0x7C00 + Address
- StartAddress
, sizeof(WORD
)));
1110 /* Register the DOS Loading BOP */
1111 RegisterBop(BOP_LOAD_DOS
, DosInitialize
);
1116 // This function is called by the DOS bootsector in case we load DOS32.
1117 // It sets up the DOS32 start code then jumps to 0070:0000.
1120 /* 16-bit startup code for DOS32 at 0070:0000 */
1121 static BYTE Startup
[] =
1123 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_START_DOS
,
1124 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_UNSIMULATE
1127 static VOID WINAPI
DosStart(LPWORD Stack
);
1129 static VOID WINAPI
DosInitialize(LPWORD Stack
)
1131 /* Get the DOS BIOS file name (NULL-terminated) */
1132 // FIXME: Isn't it possible to use some DS:SI instead??
1133 LPCSTR DosBiosFileName
= (LPCSTR
)SEG_OFF_TO_PTR(getCS(), getIP());
1134 setIP(getIP() + strlen(DosBiosFileName
) + 1); // Skip it
1136 DPRINT("DosInitialize('%s')\n", DosBiosFileName
);
1139 * We succeeded, deregister the DOS Loading BOP
1140 * so that no app will be able to call us back.
1142 RegisterBop(BOP_LOAD_DOS
, NULL
);
1144 /* Register the DOS BOPs */
1145 RegisterBop(BOP_DOS
, DosSystemBop
);
1146 RegisterBop(BOP_CMD
, DosCmdInterpreterBop
);
1148 if (DosBiosFileName
[0] != '\0')
1150 BOOLEAN Success
= FALSE
;
1152 ULONG ulDosBiosSize
= 0;
1154 /* Open the DOS BIOS file */
1155 hDosBios
= FileOpen(DosBiosFileName
, &ulDosBiosSize
);
1156 if (hDosBios
== NULL
) goto Quit
;
1158 /* Attempt to load the DOS BIOS into memory */
1159 Success
= FileLoadByHandle(hDosBios
,
1160 REAL_TO_PHYS(TO_LINEAR(0x0070, 0x0000)),
1164 DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
1166 (Success
? "succeeded" : "failed"),
1171 /* Close the DOS BIOS file */
1172 FileClose(hDosBios
);
1177 BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
1178 DosBiosFileName
, GetLastError());
1184 /* Load the 16-bit startup code for DOS32 and register its Starting BOP */
1185 RtlCopyMemory(SEG_OFF_TO_PTR(0x0070, 0x0000), Startup
, sizeof(Startup
));
1187 // This is the equivalent of BOP_LOAD_DOS, function 0x11 "Load the DOS kernel"
1188 // for the Windows NT DOS.
1189 RegisterBop(BOP_START_DOS
, DosStart
);
1192 /* Position execution pointers for DOS startup and return */
1197 static VOID WINAPI
DosStart(LPWORD Stack
)
1205 DPRINT("DosStart\n");
1208 * We succeeded, deregister the DOS Starting BOP
1209 * so that no app will be able to call us back.
1211 RegisterBop(BOP_START_DOS
, NULL
);
1213 /* Initialize the callback context */
1214 InitializeContext(&DosContext
, BIOS_CODE_SEGMENT
, 0x0010);
1216 Success
= DosBIOSInitialize();
1217 // Success &= DosKRNLInitialize();
1220 BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
1221 EmulatorTerminate();
1225 /* Load the mouse driver */
1226 DosMouseInitialize();
1230 /* Parse the command line arguments */
1231 for (i
= 1; i
< NtVdmArgc
; i
++)
1233 if (wcsncmp(NtVdmArgv
[i
], L
"-i", 2) == 0)
1235 /* This is the session ID (hex format) */
1236 SessionId
= wcstoul(NtVdmArgv
[i
] + 2, NULL
, 16);
1240 /* Initialize Win32-VDM environment */
1241 Env
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, EnvSize
);
1244 DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
1245 EmulatorTerminate();
1249 /* Clear the structure */
1250 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1252 /* Get the initial information */
1253 CommandInfo
.TaskId
= SessionId
;
1254 CommandInfo
.VDMState
= VDM_GET_FIRST_COMMAND
| VDM_FLAG_DOS
;
1255 GetNextVDMCommand(&CommandInfo
);
1259 /* Retrieve the command to start */
1262 WideCharToMultiByte(CP_ACP
, 0, NtVdmArgv
[1], -1, AppName
, sizeof(AppName
), NULL
, NULL
);
1265 WideCharToMultiByte(CP_ACP
, 0, NtVdmArgv
[2], -1, CmdLine
, sizeof(CmdLine
), NULL
, NULL
);
1267 strcpy(CmdLine
, "");
1271 DosDisplayMessage("Invalid DOS command line\n");
1272 EmulatorTerminate();
1279 * At this point, CS:IP points to the DOS BIOS exit code. If the
1280 * root command interpreter fails to start (or if it exits), DOS
1281 * exits and the VDM terminates.
1284 /* Start the root command interpreter */
1285 // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
1288 * Prepare the stack for DosStartComSpec:
1289 * push Flags, CS and IP, and an extra WORD.
1291 setSP(getSP() - sizeof(WORD
));
1292 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD
)getEFLAGS();
1293 setSP(getSP() - sizeof(WORD
));
1294 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS();
1295 setSP(getSP() - sizeof(WORD
));
1296 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP();
1297 setSP(getSP() - sizeof(WORD
));
1299 Result
= DosStartComSpec(TRUE
, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK
, 0),
1300 MAKELONG(getIP(), getCS()),
1307 if (Result
!= ERROR_SUCCESS
)
1309 /* Unprepare the stack for DosStartComSpec */
1310 setSP(getSP() + 4*sizeof(WORD
));
1312 DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result
);
1313 EmulatorTerminate();
1318 RootCmd
.Terminated
= FALSE
;
1319 InsertComSpecInfo(&RootCmd
);
1323 /* Attach to the console and resume the VM */
1324 DosProcessConsoleAttach();
1331 BOOLEAN
DosShutdown(BOOLEAN Immediate
)
1334 * Immediate = TRUE: Immediate shutdown;
1335 * FALSE: Delayed shutdown (notification).
1347 extern HANDLE VdmTaskEvent
; // see emulator.c
1350 * Signal the root COMMAND.COM that it should terminate
1351 * when it checks for a new command.
1353 RootCmd
.Terminated
= TRUE
;
1355 /* If the list is already empty, or just contains only one element, bail out */
1356 // FIXME: Question: if the list has only one element, is it ALWAYS RootCmd ??
1357 // FIXME: The following is hackish.
1358 if ((IsListEmpty(&ComSpecInfoList
) ||
1359 (ComSpecInfoList
.Flink
== &RootCmd
.Entry
&&
1360 ComSpecInfoList
.Blink
== &RootCmd
.Entry
)) &&
1361 ReentrancyCount
== 0 &&
1362 WaitForSingleObject(VdmTaskEvent
, 0) == WAIT_TIMEOUT
)
1364 /* Nothing runs, so exit immediately */
1372 UNREFERENCED_PARAMETER(Immediate
);
1378 /* PUBLIC EXPORTED APIS *******************************************************/
1381 // demLFNGetCurrentDirectory
1383 // demGetFileTimeByHandle_WOW
1384 // demWOWLFNAllocateSearchHandle
1385 // demWOWLFNCloseSearchHandle
1387 // demWOWLFNGetSearchHandle
1392 demClientErrorEx(IN HANDLE FileHandle
,
1397 return GetLastError();
1402 demFileDelete(IN LPCSTR FileName
)
1404 if (DeleteFileA(FileName
)) SetLastError(ERROR_SUCCESS
);
1406 return GetLastError();
1411 demFileFindFirst(OUT PVOID lpFindFileData
,
1415 BOOLEAN Success
= TRUE
;
1416 WIN32_FIND_DATAA FindData
;
1417 HANDLE SearchHandle
;
1418 PDOS_FIND_FILE_BLOCK FindFileBlock
= (PDOS_FIND_FILE_BLOCK
)lpFindFileData
;
1420 /* Start a search */
1421 SearchHandle
= FindFirstFileA(FileName
, &FindData
);
1422 if (SearchHandle
== INVALID_HANDLE_VALUE
) return GetLastError();
1426 /* Check the attributes and retry as long as we haven't found a matching file */
1427 if (!((FindData
.dwFileAttributes
& (FILE_ATTRIBUTE_HIDDEN
|
1428 FILE_ATTRIBUTE_SYSTEM
|
1429 FILE_ATTRIBUTE_DIRECTORY
))
1435 while ((Success
= FindNextFileA(SearchHandle
, &FindData
)));
1437 /* If we failed at some point, close the search and return an error */
1440 FindClose(SearchHandle
);
1441 return GetLastError();
1444 /* Fill the block */
1445 FindFileBlock
->DriveLetter
= DosData
->Sda
.CurrentDrive
+ 'A';
1446 FindFileBlock
->AttribMask
= AttribMask
;
1447 FindFileBlock
->SearchHandle
= SearchHandle
;
1448 FindFileBlock
->Attributes
= LOBYTE(FindData
.dwFileAttributes
);
1449 FileTimeToDosDateTime(&FindData
.ftLastWriteTime
,
1450 &FindFileBlock
->FileDate
,
1451 &FindFileBlock
->FileTime
);
1452 FindFileBlock
->FileSize
= FindData
.nFileSizeHigh
? 0xFFFFFFFF
1453 : FindData
.nFileSizeLow
;
1454 /* Build a short path name */
1455 if (*FindData
.cAlternateFileName
)
1456 strncpy(FindFileBlock
->FileName
, FindData
.cAlternateFileName
, sizeof(FindFileBlock
->FileName
));
1458 GetShortPathNameA(FindData
.cFileName
, FindFileBlock
->FileName
, sizeof(FindFileBlock
->FileName
));
1460 return ERROR_SUCCESS
;
1465 demFileFindNext(OUT PVOID lpFindFileData
)
1467 WIN32_FIND_DATAA FindData
;
1468 PDOS_FIND_FILE_BLOCK FindFileBlock
= (PDOS_FIND_FILE_BLOCK
)lpFindFileData
;
1472 /* Continue searching as long as we haven't found a matching file */
1474 /* If we failed at some point, close the search and return an error */
1475 if (!FindNextFileA(FindFileBlock
->SearchHandle
, &FindData
))
1477 FindClose(FindFileBlock
->SearchHandle
);
1478 return GetLastError();
1481 while ((FindData
.dwFileAttributes
& (FILE_ATTRIBUTE_HIDDEN
|
1482 FILE_ATTRIBUTE_SYSTEM
|
1483 FILE_ATTRIBUTE_DIRECTORY
))
1484 & ~FindFileBlock
->AttribMask
);
1486 /* Update the block */
1487 FindFileBlock
->Attributes
= LOBYTE(FindData
.dwFileAttributes
);
1488 FileTimeToDosDateTime(&FindData
.ftLastWriteTime
,
1489 &FindFileBlock
->FileDate
,
1490 &FindFileBlock
->FileTime
);
1491 FindFileBlock
->FileSize
= FindData
.nFileSizeHigh
? 0xFFFFFFFF
1492 : FindData
.nFileSizeLow
;
1493 /* Build a short path name */
1494 if (*FindData
.cAlternateFileName
)
1495 strncpy(FindFileBlock
->FileName
, FindData
.cAlternateFileName
, sizeof(FindFileBlock
->FileName
));
1497 GetShortPathNameA(FindData
.cFileName
, FindFileBlock
->FileName
, sizeof(FindFileBlock
->FileName
));
1499 return ERROR_SUCCESS
;
1504 demGetPhysicalDriveType(IN UCHAR DriveNumber
)
1507 return DOSDEVICE_DRIVE_UNKNOWN
;
1512 demIsShortPathName(IN LPCSTR Path
,
1521 demSetCurrentDirectoryGetDrive(IN LPCSTR CurrentDirectory
,
1522 OUT PUCHAR DriveNumber
)
1525 return ERROR_SUCCESS
;