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)
20 * Activate this line if you want to have COMMAND.COM completely external.
22 // #define COMSPEC_FULLY_EXTERNAL
24 /* PRIVATE VARIABLES **********************************************************/
26 /* PRIVATE FUNCTIONS **********************************************************/
28 /* PUBLIC VARIABLES ***********************************************************/
30 /* PUBLIC FUNCTIONS ***********************************************************/
33 /******************************************************************************\
34 |** DOS DEM Kernel helpers **|
35 \******************************************************************************/
38 VOID
BiosCharPrint(CHAR Character
)
46 * AL contains the character to print,
47 * BL contains the character attribute,
48 * BH contains the video page to use.
51 setBL(DEFAULT_ATTRIBUTE
);
52 setBH(Bda
->VideoPage
);
54 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
56 Int32Call(&DosContext
, BIOS_VIDEO_INTERRUPT
);
58 /* Restore AX and BX */
63 VOID
DosCharPrint(CHAR Character
)
65 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
69 static VOID
DemLoadNTDOSKernel(VOID
)
71 BOOLEAN Success
= FALSE
;
72 LPCSTR DosKernelFileName
= "ntdos.sys";
74 ULONG ulDosKernelSize
= 0;
76 DPRINT1("You are loading Windows NT DOS!\n");
78 /* Open the DOS kernel file */
79 hDosKernel
= FileOpen(DosKernelFileName
, &ulDosKernelSize
);
80 if (hDosKernel
== NULL
) goto Quit
;
83 * Attempt to load the DOS kernel into memory.
84 * The segment where to load the DOS kernel is defined
85 * by the DOS BIOS and is found in DI:0000 .
87 Success
= FileLoadByHandle(hDosKernel
,
88 REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
92 DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
94 (Success
? "succeeded" : "failed"),
99 /* Close the DOS kernel file */
100 FileClose(hDosKernel
);
105 /* We failed everything, stop the VDM */
106 BiosDisplayMessage("Windows NT DOS kernel file '%s' loading failed (Error: %u). The VDM will shut down.\n",
107 DosKernelFileName
, GetLastError());
113 static VOID WINAPI
DosSystemBop(LPWORD Stack
)
115 /* Get the Function Number and skip it */
116 BYTE FuncNum
= *(PBYTE
)SEG_OFF_TO_PTR(getCS(), getIP());
121 /* Load the DOS kernel */
124 DemLoadNTDOSKernel();
128 /* Call 32-bit Driver Strategy Routine */
129 case BOP_DRV_STRATEGY
:
135 /* Call 32-bit Driver Interrupt Routine */
136 case BOP_DRV_INTERRUPT
:
138 DeviceInterruptBop();
144 DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum
);
145 // setCF(1); // Disable, otherwise we enter an infinite loop
154 /******************************************************************************\
155 |** DOS Command Process management **|
156 \******************************************************************************/
160 static ULONG SessionId
= 0;
163 * 16-bit Command Interpreter information for DOS reentry
165 typedef struct _COMSPEC_INFO
171 } COMSPEC_INFO
, *PCOMSPEC_INFO
;
173 static COMSPEC_INFO RootCmd
;
174 static DWORD ReentrancyCount
= 0;
176 // FIXME: Should we need list locking?
177 static LIST_ENTRY ComSpecInfoList
= { &ComSpecInfoList
, &ComSpecInfoList
};
180 FindComSpecInfoByPsp(WORD Psp
)
183 PCOMSPEC_INFO ComSpecInfo
;
185 for (Pointer
= ComSpecInfoList
.Flink
; Pointer
!= &ComSpecInfoList
; Pointer
= Pointer
->Flink
)
187 ComSpecInfo
= CONTAINING_RECORD(Pointer
, COMSPEC_INFO
, Entry
);
188 if (ComSpecInfo
->ComSpecPsp
== Psp
) return ComSpecInfo
;
195 InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo
)
197 InsertHeadList(&ComSpecInfoList
, &ComSpecInfo
->Entry
);
201 RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo
)
203 RemoveEntryList(&ComSpecInfo
->Entry
);
204 if (ComSpecInfo
!= &RootCmd
)
205 RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo
);
209 static VOID
DosProcessConsoleAttach(VOID
)
211 /* Attach to the console */
213 VidBiosAttachToConsole();
216 static VOID
DosProcessConsoleDetach(VOID
)
218 /* Detach from the console */
219 VidBiosDetachFromConsole();
224 * Data for the next DOS command to run
227 static VDM_COMMAND_INFO CommandInfo
;
228 static BOOLEAN Repeat
= FALSE
;
229 static BOOLEAN Reentry
= FALSE
;
231 static BOOLEAN First
= TRUE
;
232 static CHAR CmdLine
[MAX_PATH
] = ""; // DOS_CMDLINE_LENGTH
233 static CHAR AppName
[MAX_PATH
] = "";
235 static CHAR PifFile
[MAX_PATH
] = "";
236 static CHAR CurDirectory
[MAX_PATH
] = "";
237 static CHAR Desktop
[MAX_PATH
] = "";
238 static CHAR Title
[MAX_PATH
] = "";
239 static ULONG EnvSize
= 256;
240 static PVOID Env
= NULL
;
243 #pragma pack(push, 2)
246 * This structure is compatible with Windows NT DOS
248 typedef struct _NEXT_CMD
267 } NEXT_CMD
, *PNEXT_CMD
;
271 static VOID
CmdStartProcess(VOID
)
274 PCOMSPEC_INFO ComSpecInfo
;
277 PNEXT_CMD DataStruct
= (PNEXT_CMD
)SEG_OFF_TO_PTR(getDS(), getDX());
279 DPRINT1("CmdStartProcess -- DS:DX = %04X:%04X (DataStruct = 0x%p)\n",
280 getDS(), getDX(), DataStruct
);
286 /* Check whether we need to shell out now in case we were started by a 32-bit app */
287 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
288 if (ComSpecInfo
&& ComSpecInfo
->Terminated
)
290 RemoveComSpecInfo(ComSpecInfo
);
292 DPRINT1("Exit DOS from start-app BOP\n");
297 /* Clear the structure */
298 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
300 /* Initialize the structure members */
301 CommandInfo
.TaskId
= SessionId
;
302 CommandInfo
.VDMState
= VDM_FLAG_DOS
;
303 CommandInfo
.CmdLine
= CmdLine
;
304 CommandInfo
.CmdLen
= sizeof(CmdLine
);
305 CommandInfo
.AppName
= AppName
;
306 CommandInfo
.AppLen
= sizeof(AppName
);
307 CommandInfo
.PifFile
= PifFile
;
308 CommandInfo
.PifLen
= sizeof(PifFile
);
309 CommandInfo
.CurDirectory
= CurDirectory
;
310 CommandInfo
.CurDirectoryLen
= sizeof(CurDirectory
);
311 CommandInfo
.Desktop
= Desktop
;
312 CommandInfo
.DesktopLen
= sizeof(Desktop
);
313 CommandInfo
.Title
= Title
;
314 CommandInfo
.TitleLen
= sizeof(Title
);
315 CommandInfo
.Env
= Env
;
316 CommandInfo
.EnvLen
= EnvSize
;
318 if (First
) CommandInfo
.VDMState
|= VDM_FLAG_FIRST_TASK
;
322 if (Repeat
) CommandInfo
.VDMState
|= VDM_FLAG_RETRY
;
325 /* Get the VDM command information */
326 DPRINT1("Calling GetNextVDMCommand in CmdStartProcess: wait for new VDM task...\n");
327 if (!GetNextVDMCommand(&CommandInfo
))
329 DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
330 if (CommandInfo
.EnvLen
> EnvSize
)
332 /* Expand the environment size */
333 EnvSize
= CommandInfo
.EnvLen
;
334 CommandInfo
.Env
= Env
= RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, Env
, EnvSize
);
336 /* Repeat the request */
341 /* Shouldn't happen */
342 DisplayMessage(L
"An unrecoverable failure happened from start-app BOP; exiting DOS.");
347 // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
349 DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
355 DPRINT1("Exit DOS from start-app BOP\n");
362 CmdLen
= strlen(CmdLine
);
363 DPRINT1("Starting '%s' ('%.*s')...\n",
365 /* Display the command line without the terminating 0d 0a (and skip the terminating NULL) */
366 CmdLen
>= 2 ? (CmdLine
[CmdLen
- 2] == '\r' ? CmdLen
- 2
371 /* Start the process */
372 // FIXME: Merge 'Env' with the master environment SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0)
373 // FIXME: Environment
374 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct
->AppNameSeg
, DataStruct
->AppNameOff
), AppName
, MAX_PATH
);
375 *(PBYTE
)(SEG_OFF_TO_PTR(DataStruct
->CmdLineSeg
, DataStruct
->CmdLineOff
)) = (BYTE
)(strlen(CmdLine
) - 2);
376 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct
->CmdLineSeg
, DataStruct
->CmdLineOff
+ 1), CmdLine
, DOS_CMDLINE_LENGTH
);
379 /* Update console title if we run in a separate console */
381 SetConsoleTitleA(AppName
);
387 DPRINT1("App started!\n");
394 static VOID
CmdStartExternalCommand(VOID
)
398 // TODO: improve: this code has strong similarities
399 // with the 'default' case of DosCreateProcess.
401 LPSTR Command
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getSI());
402 CHAR CmdLine
[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH
+ 1] = "";
406 /* Spawn a user-defined 32-bit command preprocessor */
408 // FIXME: Use COMSPEC env var!!
409 CmdLinePtr
= CmdLine
;
410 strcpy(CmdLinePtr
, "cmd.exe /c ");
411 CmdLinePtr
+= strlen(CmdLinePtr
);
413 /* Build a Win32-compatible command-line */
414 CmdLineLen
= min(strlen(Command
), sizeof(CmdLine
) - strlen(CmdLinePtr
) - 1);
415 RtlCopyMemory(CmdLinePtr
, Command
, CmdLineLen
);
416 CmdLinePtr
[CmdLineLen
] = '\0';
418 /* Remove any trailing return carriage character and NULL-terminate the command line */
419 while (*CmdLinePtr
&& *CmdLinePtr
!= '\r' && *CmdLinePtr
!= '\n') CmdLinePtr
++;
422 DPRINT1("CMD Run Command '%s' ('%s')\n", Command
, CmdLine
);
425 * No need to prepare the stack for DosStartComSpec since we won't start it.
427 Result
= DosStartProcess32(Command
, CmdLine
,
428 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
429 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
431 if (Result
!= ERROR_SUCCESS
)
433 DosDisplayMessage("Failed to start command '%s' ('%s'). Error: %u\n", Command
, CmdLine
, Result
);
435 setAL((UCHAR
)Result
);
439 DosDisplayMessage("Command '%s' ('%s') started successfully.\n", Command
, CmdLine
);
441 setCF(Repeat
); // Set CF if we need to start a 16-bit process
448 static VOID
CmdStartComSpec32(VOID
)
452 // TODO: improve: this code has strong similarities with the
453 // 'default' case of DosCreateProcess and with the 'case 0x08'.
455 CHAR CmdLine
[sizeof("cmd.exe") + 1] = "";
457 /* Spawn a user-defined 32-bit command preprocessor */
459 // FIXME: Use COMSPEC env var!!
460 strcpy(CmdLine
, "cmd.exe");
462 DPRINT1("CMD Run 32-bit Command Interpreter '%s'\n", CmdLine
);
465 * No need to prepare the stack for DosStartComSpec since we won't start it.
467 Result
= DosStartProcess32(CmdLine
, CmdLine
,
468 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
469 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
471 if (Result
!= ERROR_SUCCESS
)
473 DosDisplayMessage("Failed to start 32-bit Command Interpreter '%s'. Error: %u\n", CmdLine
, Result
);
475 setAL((UCHAR
)Result
);
479 DosDisplayMessage("32-bit Command Interpreter '%s' started successfully.\n", CmdLine
);
481 setCF(Repeat
); // Set CF if we need to start a 16-bit process
488 static VOID
CmdSetExitCode(VOID
)
492 PCOMSPEC_INFO ComSpecInfo
;
493 VDM_COMMAND_INFO CommandInfo
;
501 * Check whether we need to shell out now in case we were started by a 32-bit app,
502 * or we were started alone along with the root 32-bit app.
504 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
505 if ((ComSpecInfo
&& ComSpecInfo
->Terminated
) ||
506 (ComSpecInfo
== &RootCmd
&& SessionId
!= 0))
508 RemoveComSpecInfo(ComSpecInfo
);
510 DPRINT1("Exit DOS from ExitCode (prologue)!\n");
516 /* Clear the VDM structure */
517 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
520 /* Update the VDM state of the task */
521 // CommandInfo.TaskId = SessionId;
522 CommandInfo
.ExitCode
= getDX();
523 CommandInfo
.VDMState
= VDM_FLAG_DONT_WAIT
;
524 DPRINT1("Calling GetNextVDMCommand 32bit end of VDM task\n");
525 Success
= GetNextVDMCommand(&CommandInfo
);
526 DPRINT1("GetNextVDMCommand 32bit end of VDM task success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
529 * Check whether we were awaited because the 32-bit process was stopped,
530 * or because it started a new DOS application.
532 if (CommandInfo
.CmdLen
!= 0 || CommandInfo
.AppLen
!= 0 || CommandInfo
.PifLen
!= 0)
534 DPRINT1("GetNextVDMCommand end-of-app, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
535 CommandInfo
.CmdLen
, CommandInfo
.AppLen
, CommandInfo
.PifLen
);
537 /* Repeat the request */
543 DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
545 /* Check whether we need to shell out now in case we were started by a 32-bit app */
546 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
547 if (!ComSpecInfo
|| !ComSpecInfo
->Terminated
)
549 DPRINT1("Not our 32-bit app, retrying...\n");
553 ASSERT(ComSpecInfo
->Terminated
== TRUE
);
555 /* Record found, remove it and exit now */
556 RemoveComSpecInfo(ComSpecInfo
);
558 DPRINT1("Exit DOS from ExitCode wait!\n");
563 // FIXME: Use the retrieved exit code as the value of our exit code
564 // when COMMAND.COM will shell-out ??
571 static VOID WINAPI
DosCmdInterpreterBop(LPWORD Stack
)
573 /* Get the Function Number and skip it */
574 BYTE FuncNum
= *(PBYTE
)SEG_OFF_TO_PTR(getCS(), getIP());
588 * Get a new app to start
591 * DS:DX : Data block.
594 * CF : 0: Success; 1: Failure.
603 * Check binary format
606 * DS:DX : Program to check.
609 * CF : 0: Success; 1: Failure.
615 LPSTR ProgramName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
617 if (!GetBinaryTypeA(ProgramName
, &BinaryType
))
619 /* An error happened, bail out */
621 setAX(LOWORD(GetLastError()));
625 // FIXME: We only support DOS binaries for now...
626 ASSERT(BinaryType
== SCS_DOS_BINARY
);
627 if (BinaryType
!= SCS_DOS_BINARY
)
629 /* An error happened, bail out */
631 setAX(LOWORD(ERROR_BAD_EXE_FORMAT
));
635 /* Return success: DOS application */
641 * Start an external command
644 * DS:SI : Command to start.
645 * ES : Environment block segment.
646 * AL : Current drive number.
647 * AH : 0: Directly start the command;
648 * 1: Use "cmd.exe /c" to start the command.
651 * CF : 0: Shell-out; 1: Continue.
652 * AL : Error/Exit code.
656 CmdStartExternalCommand();
661 * Start the default 32-bit command interpreter (COMSPEC)
664 * ES : Environment block segment.
665 * AL : Current drive number.
668 * CF : 0: Shell-out; 1: Continue.
669 * AL : Error/Exit code.
684 * CF : 0: Shell-out; 1: Continue.
693 * Get start information
696 * AL : 0 (resp. 1): Started from (resp. without) an existing console.
702 * When a new instance of our (internal) COMMAND.COM is started,
703 * we check whether we need to run a 32-bit COMSPEC. This goes by
704 * checking whether we were started in a new console (no parent
705 * console process) or from an existing one.
707 * However COMMAND.COM can also be started in the case where a
708 * 32-bit process (started by a 16-bit parent) wants to start a new
709 * 16-bit process: to ensure DOS reentry we need to start a new
710 * instance of COMMAND.COM. On Windows the COMMAND.COM is started
711 * just before the 32-bit process (in fact, it is this COMMAND.COM
712 * which starts the 32-bit process via an undocumented command-line
713 * switch '/z', which syntax is:
714 * COMMAND.COM /z\bAPPNAME.EXE
715 * notice the '\b' character inserted in-between. Then COMMAND.COM
716 * issues a BOP_CMD 08h with AH=00h to start the process).
718 * Instead, we do the reverse, i.e. we start the 32-bit process,
719 * and *only* if needed, i.e. if this process wants to start a
720 * new 16-bit process, we start our COMMAND.COM.
722 * The problem we then face is that our COMMAND.COM will possibly
723 * want to start a new COMSPEC, however we do not want this.
724 * The chosen solution is to flag this case -- done with the 'Reentry'
725 * boolean -- so that COMMAND.COM will not attempt to start COMSPEC
726 * but instead will directly try to start the 16-bit process.
728 // setAL(SessionId != 0);
729 setAL((SessionId
!= 0) && !Reentry
);
730 /* Reset 'Reentry' */
740 DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum
);
741 // setCF(1); // Disable, otherwise we enter an infinite loop
747 #ifndef COMSPEC_FULLY_EXTERNAL
749 * Internal COMMAND.COM binary data in the CommandCom array.
751 #include "command_com.h"
755 DWORD
DosStartComSpec(IN BOOLEAN Permanent
,
756 IN LPCSTR Environment OPTIONAL
,
757 IN DWORD ReturnAddress OPTIONAL
,
758 OUT PWORD ComSpecPsp OPTIONAL
)
762 * TRUE to simulate the /P switch of command.com: starts AUTOEXEC.BAT/NT
763 * and makes the interpreter permanent (cannot exit).
768 if (ComSpecPsp
) *ComSpecPsp
= 0;
771 #ifndef COMSPEC_FULLY_EXTERNAL
772 DosLoadExecutableInternal(DOS_LOAD_AND_EXECUTE
,
777 DosLoadExecutable(DOS_LOAD_AND_EXECUTE
,
778 #ifndef STANDALONE // FIXME: Those values are hardcoded paths on my local test machines!!
781 "H:\\DOS_tests\\CMDCMD.COM",
783 #endif // COMSPEC_FULLY_EXTERNAL
785 Permanent
? "/P" : "",
786 Environment
? Environment
: "", // FIXME: Default environment!
788 if (Result
!= ERROR_SUCCESS
) return Result
;
790 /* TODO: Read AUTOEXEC.NT/BAT */
792 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
793 if (ComSpecPsp
) *ComSpecPsp
= Sda
->CurrentPsp
;
798 typedef struct _DOS_START_PROC32
800 LPSTR ExecutablePath
;
802 LPSTR Environment OPTIONAL
;
804 PCOMSPEC_INFO ComSpecInfo
;
807 } DOS_START_PROC32
, *PDOS_START_PROC32
;
811 CommandThreadProc(LPVOID Parameter
)
814 PROCESS_INFORMATION ProcessInfo
;
815 STARTUPINFOA StartupInfo
;
817 PDOS_START_PROC32 DosStartProc32
= (PDOS_START_PROC32
)Parameter
;
819 VDM_COMMAND_INFO CommandInfo
;
820 PCOMSPEC_INFO ComSpecInfo
= DosStartProc32
->ComSpecInfo
;
823 /* Set up the VDM, startup and process info structures */
825 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
827 RtlZeroMemory(&ProcessInfo
, sizeof(ProcessInfo
));
828 RtlZeroMemory(&StartupInfo
, sizeof(StartupInfo
));
829 StartupInfo
.cb
= sizeof(StartupInfo
);
831 // FIXME: Build suitable 32-bit environment!!
835 * Wait for signaling a new VDM task and increment the VDM re-entry count so
836 * that we can handle 16-bit apps that may be possibly started by the 32-bit app.
838 CommandInfo
.VDMState
= VDM_INC_REENTER_COUNT
;
839 DPRINT1("Calling GetNextVDMCommand reenter++\n");
840 Success
= GetNextVDMCommand(&CommandInfo
);
841 DPRINT1("GetNextVDMCommand reenter++ success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
845 /* Start the process */
846 Success
= CreateProcessA(NULL
, // ProgramName,
847 DosStartProc32
->CommandLine
,
850 TRUE
, // Inherit handles
851 CREATE_DEFAULT_ERROR_MODE
| CREATE_SUSPENDED
,
852 DosStartProc32
->Environment
,
853 NULL
, // lpCurrentDirectory, see "START" command in cmd.exe
858 /* Signal our caller the process was started */
859 SetEvent(DosStartProc32
->hEvent
);
860 // After this point, 'DosStartProc32' is not valid anymore.
865 /* Resume the process */
866 ResumeThread(ProcessInfo
.hThread
);
868 /* Wait for the process to finish running and retrieve its exit code */
869 WaitForSingleObject(ProcessInfo
.hProcess
, INFINITE
);
870 GetExitCodeProcess(ProcessInfo
.hProcess
, &dwExitCode
);
872 /* Close the handles */
873 CloseHandle(ProcessInfo
.hThread
);
874 CloseHandle(ProcessInfo
.hProcess
);
878 dwExitCode
= GetLastError();
883 ComSpecInfo
->Terminated
= TRUE
;
884 ComSpecInfo
->dwExitCode
= dwExitCode
;
886 /* Decrement the VDM re-entry count */
887 CommandInfo
.VDMState
= VDM_DEC_REENTER_COUNT
;
888 DPRINT1("Calling GetNextVDMCommand reenter--\n");
889 Success
= GetNextVDMCommand(&CommandInfo
);
890 DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
899 DWORD
DosStartProcess32(IN LPCSTR ExecutablePath
,
900 IN LPCSTR CommandLine
,
901 IN LPCSTR Environment OPTIONAL
,
902 IN DWORD ReturnAddress OPTIONAL
,
903 IN BOOLEAN StartComSpec
)
905 DWORD Result
= ERROR_SUCCESS
;
906 HANDLE CommandThread
;
907 DOS_START_PROC32 DosStartProc32
;
910 VDM_COMMAND_INFO CommandInfo
;
913 DosStartProc32
.ExecutablePath
= (LPSTR
)ExecutablePath
;
914 DosStartProc32
.CommandLine
= (LPSTR
)CommandLine
;
915 DosStartProc32
.Environment
= (LPSTR
)Environment
;
918 DosStartProc32
.ComSpecInfo
=
919 RtlAllocateHeap(RtlGetProcessHeap(),
921 sizeof(*DosStartProc32
.ComSpecInfo
));
922 ASSERT(DosStartProc32
.ComSpecInfo
);
924 DosStartProc32
.hEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
925 ASSERT(DosStartProc32
.hEvent
);
928 /* Pause the VM and detach from the console */
930 DosProcessConsoleDetach();
932 /* Start the 32-bit process via another thread */
933 CommandThread
= CreateThread(NULL
, 0, &CommandThreadProc
, &DosStartProc32
, 0, NULL
);
934 if (CommandThread
== NULL
)
936 DisplayMessage(L
"FATAL: Failed to create the command processing thread: %d", GetLastError());
937 Result
= GetLastError();
942 /* Close the thread handle */
943 CloseHandle(CommandThread
);
945 /* Wait for the process to be ready to start */
946 WaitForSingleObject(DosStartProc32
.hEvent
, INFINITE
);
948 /* Wait for any potential new DOS app started by the 32-bit process */
949 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
952 CommandInfo
.VDMState
= VDM_FLAG_NESTED_TASK
| VDM_FLAG_DONT_WAIT
;
953 DPRINT1("Calling GetNextVDMCommand 32bit for possible new VDM task...\n");
954 Success
= GetNextVDMCommand(&CommandInfo
);
955 DPRINT1("GetNextVDMCommand 32bit awaited, success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
958 * Check whether we were awaited because the 32-bit process was stopped,
959 * or because it started a new DOS application.
961 if (CommandInfo
.CmdLen
!= 0 || CommandInfo
.AppLen
!= 0 || CommandInfo
.PifLen
!= 0)
963 DPRINT1("GetNextVDMCommand 32bit, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
964 CommandInfo
.CmdLen
, CommandInfo
.AppLen
, CommandInfo
.PifLen
);
966 /* Repeat the request */
970 * Set 'Reentry' to TRUE or FALSE depending on whether we are going
971 * to reenter with a new COMMAND.COM. See the comment for:
972 * BOP_CMD 0x10 'Get start information'
973 * (dem.c!DosCmdInterpreterBop) for more details.
975 Reentry
= StartComSpec
;
977 /* If needed, start a new command interpreter to handle the possible incoming DOS commands */
981 // DosStartProcess32 was only called by DosCreateProcess, called from INT 21h,
982 // so the caller stack is already prepared for running a new DOS program
983 // (Flags, CS and IP, and the extra interrupt number, are already pushed).
985 Result
= DosStartComSpec(FALSE
, Environment
, ReturnAddress
,
986 &DosStartProc32
.ComSpecInfo
->ComSpecPsp
);
987 if (Result
!= ERROR_SUCCESS
)
989 DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result
);
995 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
996 DosStartProc32
.ComSpecInfo
->ComSpecPsp
= Sda
->CurrentPsp
;
997 Result
= ERROR_SUCCESS
;
1000 /* Insert the new entry in the list; it will be freed when needed by COMMAND.COM */
1001 InsertComSpecInfo(DosStartProc32
.ComSpecInfo
);
1005 DPRINT1("GetNextVDMCommand 32bit, 32-bit app stopped\n");
1007 /* Check whether this was our 32-bit app which was killed */
1008 if (!DosStartProc32
.ComSpecInfo
->Terminated
)
1010 DPRINT1("Not our 32-bit app, retrying...\n");
1014 Result
= DosStartProc32
.ComSpecInfo
->dwExitCode
;
1016 /* Delete the entry */
1017 RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32
.ComSpecInfo
);
1020 /* Wait for the thread to finish */
1021 WaitForSingleObject(CommandThread
, INFINITE
);
1022 GetExitCodeThread(CommandThread
, &Result
);
1024 /* Close the thread handle */
1025 CloseHandle(CommandThread
);
1027 DPRINT1("32-bit app stopped\n");
1032 CloseHandle(DosStartProc32
.hEvent
);
1035 /* Attach to the console and resume the VM */
1036 DosProcessConsoleAttach();
1045 /******************************************************************************\
1046 |** DOS Bootloader emulation, Startup and Shutdown **|
1047 \******************************************************************************/
1051 // This function (equivalent of the DOS bootsector) is called by the bootstrap
1052 // loader *BEFORE* jumping at 0000:7C00. What we should do is to write at 0000:7C00
1053 // a BOP call that calls DosInitialize back. Then the bootstrap loader jumps at
1054 // 0000:7C00, our BOP gets called and then we can initialize the 32-bit part of the DOS.
1057 /* 16-bit bootstrap code at 0000:7C00 */
1058 /* Of course, this is not in real bootsector format, because we don't care about it for now */
1059 static BYTE Bootsector1
[] =
1061 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_LOAD_DOS
1063 /* This portion of code is run if we failed to load the DOS */
1064 // NOTE: This may also be done by the BIOS32.
1065 static BYTE Bootsector2
[] =
1067 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_UNSIMULATE
1070 static VOID WINAPI
DosInitialize(LPWORD Stack
);
1072 VOID
DosBootsectorInitialize(VOID
)
1074 /* We write the bootsector at 0000:7C00 */
1075 ULONG_PTR StartAddress
= (ULONG_PTR
)SEG_OFF_TO_PTR(0x0000, 0x7C00);
1076 ULONG_PTR Address
= StartAddress
;
1077 CHAR DosKernelFileName
[] = ""; // No DOS BIOS file name, therefore we will load DOS32
1079 DPRINT("DosBootsectorInitialize\n");
1081 /* Write the "bootsector" */
1082 RtlCopyMemory((PVOID
)Address
, Bootsector1
, sizeof(Bootsector1
));
1083 Address
+= sizeof(Bootsector1
);
1084 RtlCopyMemory((PVOID
)Address
, DosKernelFileName
, sizeof(DosKernelFileName
));
1085 Address
+= sizeof(DosKernelFileName
);
1086 RtlCopyMemory((PVOID
)Address
, Bootsector2
, sizeof(Bootsector2
));
1087 Address
+= sizeof(Bootsector2
);
1089 /* Initialize the callback context */
1090 InitializeContext(&DosContext
, 0x0000,
1091 (ULONG_PTR
)MEM_ALIGN_UP(0x7C00 + Address
- StartAddress
, sizeof(WORD
)));
1093 /* Register the DOS Loading BOP */
1094 RegisterBop(BOP_LOAD_DOS
, DosInitialize
);
1099 // This function is called by the DOS bootsector in case we load DOS32.
1100 // It sets up the DOS32 start code then jumps to 0070:0000.
1103 /* 16-bit startup code for DOS32 at 0070:0000 */
1104 static BYTE Startup
[] =
1106 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_START_DOS
,
1107 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_UNSIMULATE
1110 static VOID WINAPI
DosStart(LPWORD Stack
);
1112 static VOID WINAPI
DosInitialize(LPWORD Stack
)
1114 /* Get the DOS BIOS file name (NULL-terminated) */
1115 // FIXME: Isn't it possible to use some DS:SI instead??
1116 LPCSTR DosBiosFileName
= (LPCSTR
)SEG_OFF_TO_PTR(getCS(), getIP());
1117 setIP(getIP() + strlen(DosBiosFileName
) + 1); // Skip it
1119 DPRINT("DosInitialize('%s')\n", DosBiosFileName
);
1122 * We succeeded, deregister the DOS Loading BOP
1123 * so that no app will be able to call us back.
1125 RegisterBop(BOP_LOAD_DOS
, NULL
);
1127 /* Register the DOS BOPs */
1128 RegisterBop(BOP_DOS
, DosSystemBop
);
1129 RegisterBop(BOP_CMD
, DosCmdInterpreterBop
);
1131 if (DosBiosFileName
[0] != '\0')
1133 BOOLEAN Success
= FALSE
;
1135 ULONG ulDosBiosSize
= 0;
1137 /* Open the DOS BIOS file */
1138 hDosBios
= FileOpen(DosBiosFileName
, &ulDosBiosSize
);
1139 if (hDosBios
== NULL
) goto Quit
;
1141 /* Attempt to load the DOS BIOS into memory */
1142 Success
= FileLoadByHandle(hDosBios
,
1143 REAL_TO_PHYS(TO_LINEAR(0x0070, 0x0000)),
1147 DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
1149 (Success
? "succeeded" : "failed"),
1154 /* Close the DOS BIOS file */
1155 FileClose(hDosBios
);
1160 BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
1161 DosBiosFileName
, GetLastError());
1167 /* Load the 16-bit startup code for DOS32 and register its Starting BOP */
1168 RtlCopyMemory(SEG_OFF_TO_PTR(0x0070, 0x0000), Startup
, sizeof(Startup
));
1170 // This is the equivalent of BOP_LOAD_DOS, function 0x11 "Load the DOS kernel"
1171 // for the Windows NT DOS.
1172 RegisterBop(BOP_START_DOS
, DosStart
);
1175 /* Position execution pointers for DOS startup and return */
1180 static VOID WINAPI
DosStart(LPWORD Stack
)
1188 DPRINT("DosStart\n");
1191 * We succeeded, deregister the DOS Starting BOP
1192 * so that no app will be able to call us back.
1194 RegisterBop(BOP_START_DOS
, NULL
);
1196 /* Initialize the callback context */
1197 InitializeContext(&DosContext
, BIOS_CODE_SEGMENT
, 0x0010);
1199 Success
= DosBIOSInitialize();
1200 // Success &= DosKRNLInitialize();
1203 BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
1204 EmulatorTerminate();
1208 /* Load the mouse driver */
1209 DosMouseInitialize();
1213 /* Parse the command line arguments */
1214 for (i
= 1; i
< NtVdmArgc
; i
++)
1216 if (wcsncmp(NtVdmArgv
[i
], L
"-i", 2) == 0)
1218 /* This is the session ID (hex format) */
1219 SessionId
= wcstoul(NtVdmArgv
[i
] + 2, NULL
, 16);
1223 /* Initialize Win32-VDM environment */
1224 Env
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, EnvSize
);
1227 DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
1228 EmulatorTerminate();
1232 /* Clear the structure */
1233 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1235 /* Get the initial information */
1236 CommandInfo
.TaskId
= SessionId
;
1237 CommandInfo
.VDMState
= VDM_GET_FIRST_COMMAND
| VDM_FLAG_DOS
;
1238 GetNextVDMCommand(&CommandInfo
);
1242 /* Retrieve the command to start */
1245 WideCharToMultiByte(CP_ACP
, 0, NtVdmArgv
[1], -1, AppName
, sizeof(AppName
), NULL
, NULL
);
1248 WideCharToMultiByte(CP_ACP
, 0, NtVdmArgv
[2], -1, CmdLine
, sizeof(CmdLine
), NULL
, NULL
);
1250 strcpy(CmdLine
, "");
1254 DosDisplayMessage("Invalid DOS command line\n");
1255 EmulatorTerminate();
1262 * At this point, CS:IP points to the DOS BIOS exit code. If the
1263 * root command interpreter fails to start (or if it exits), DOS
1264 * exits and the VDM terminates.
1267 /* Start the root command interpreter */
1268 // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
1271 * Prepare the stack for DosStartComSpec:
1272 * push Flags, CS and IP, and an extra WORD.
1274 setSP(getSP() - sizeof(WORD
));
1275 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD
)getEFLAGS();
1276 setSP(getSP() - sizeof(WORD
));
1277 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS();
1278 setSP(getSP() - sizeof(WORD
));
1279 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP();
1280 setSP(getSP() - sizeof(WORD
));
1282 Result
= DosStartComSpec(TRUE
, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK
, 0),
1283 MAKELONG(getIP(), getCS()),
1290 if (Result
!= ERROR_SUCCESS
)
1292 /* Unprepare the stack for DosStartComSpec */
1293 setSP(getSP() + 4*sizeof(WORD
));
1295 DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result
);
1296 EmulatorTerminate();
1301 RootCmd
.Terminated
= FALSE
;
1302 InsertComSpecInfo(&RootCmd
);
1306 /* Attach to the console and resume the VM */
1307 DosProcessConsoleAttach();
1314 BOOLEAN
DosShutdown(BOOLEAN Immediate
)
1317 * Immediate = TRUE: Immediate shutdown;
1318 * FALSE: Delayed shutdown (notification).
1330 extern HANDLE VdmTaskEvent
; // see emulator.c
1333 * Signal the root COMMAND.COM that it should terminate
1334 * when it checks for a new command.
1336 RootCmd
.Terminated
= TRUE
;
1338 /* If the list is already empty, or just contains only one element, bail out */
1339 // FIXME: Question: if the list has only one element, is it ALWAYS RootCmd ??
1340 // FIXME: The following is hackish.
1341 if ((IsListEmpty(&ComSpecInfoList
) ||
1342 (ComSpecInfoList
.Flink
== &RootCmd
.Entry
&&
1343 ComSpecInfoList
.Blink
== &RootCmd
.Entry
)) &&
1344 ReentrancyCount
== 0 &&
1345 WaitForSingleObject(VdmTaskEvent
, 0) == WAIT_TIMEOUT
)
1347 /* Nothing runs, so exit immediately */
1355 UNREFERENCED_PARAMETER(Immediate
);
1361 /* PUBLIC EXPORTED APIS *******************************************************/
1364 // demLFNGetCurrentDirectory
1366 // demGetFileTimeByHandle_WOW
1367 // demWOWLFNAllocateSearchHandle
1368 // demWOWLFNCloseSearchHandle
1370 // demWOWLFNGetSearchHandle
1375 demClientErrorEx(IN HANDLE FileHandle
,
1380 return GetLastError();
1385 demFileDelete(IN LPCSTR FileName
)
1387 if (DeleteFileA(FileName
)) SetLastError(ERROR_SUCCESS
);
1389 return GetLastError();
1394 demFileFindFirst(OUT PVOID lpFindFileData
,
1398 BOOLEAN Success
= TRUE
;
1399 WIN32_FIND_DATAA FindData
;
1400 HANDLE SearchHandle
;
1401 PDOS_FIND_FILE_BLOCK FindFileBlock
= (PDOS_FIND_FILE_BLOCK
)lpFindFileData
;
1403 /* Start a search */
1404 SearchHandle
= FindFirstFileA(FileName
, &FindData
);
1405 if (SearchHandle
== INVALID_HANDLE_VALUE
) return GetLastError();
1409 /* Check the attributes and retry as long as we haven't found a matching file */
1410 if (!((FindData
.dwFileAttributes
& (FILE_ATTRIBUTE_HIDDEN
|
1411 FILE_ATTRIBUTE_SYSTEM
|
1412 FILE_ATTRIBUTE_DIRECTORY
))
1418 while ((Success
= FindNextFileA(SearchHandle
, &FindData
)));
1420 /* If we failed at some point, close the search and return an error */
1423 FindClose(SearchHandle
);
1424 return GetLastError();
1427 /* Fill the block */
1428 FindFileBlock
->DriveLetter
= DosData
->Sda
.CurrentDrive
+ 'A';
1429 FindFileBlock
->AttribMask
= AttribMask
;
1430 FindFileBlock
->SearchHandle
= SearchHandle
;
1431 FindFileBlock
->Attributes
= LOBYTE(FindData
.dwFileAttributes
);
1432 FileTimeToDosDateTime(&FindData
.ftLastWriteTime
,
1433 &FindFileBlock
->FileDate
,
1434 &FindFileBlock
->FileTime
);
1435 FindFileBlock
->FileSize
= FindData
.nFileSizeHigh
? 0xFFFFFFFF
1436 : FindData
.nFileSizeLow
;
1437 /* Build a short path name */
1438 if (*FindData
.cAlternateFileName
)
1439 strncpy(FindFileBlock
->FileName
, FindData
.cAlternateFileName
, sizeof(FindFileBlock
->FileName
));
1441 GetShortPathNameA(FindData
.cFileName
, FindFileBlock
->FileName
, sizeof(FindFileBlock
->FileName
));
1443 return ERROR_SUCCESS
;
1448 demFileFindNext(OUT PVOID lpFindFileData
)
1450 WIN32_FIND_DATAA FindData
;
1451 PDOS_FIND_FILE_BLOCK FindFileBlock
= (PDOS_FIND_FILE_BLOCK
)lpFindFileData
;
1455 /* Continue searching as long as we haven't found a matching file */
1457 /* If we failed at some point, close the search and return an error */
1458 if (!FindNextFileA(FindFileBlock
->SearchHandle
, &FindData
))
1460 FindClose(FindFileBlock
->SearchHandle
);
1461 return GetLastError();
1464 while ((FindData
.dwFileAttributes
& (FILE_ATTRIBUTE_HIDDEN
|
1465 FILE_ATTRIBUTE_SYSTEM
|
1466 FILE_ATTRIBUTE_DIRECTORY
))
1467 & ~FindFileBlock
->AttribMask
);
1469 /* Update the block */
1470 FindFileBlock
->Attributes
= LOBYTE(FindData
.dwFileAttributes
);
1471 FileTimeToDosDateTime(&FindData
.ftLastWriteTime
,
1472 &FindFileBlock
->FileDate
,
1473 &FindFileBlock
->FileTime
);
1474 FindFileBlock
->FileSize
= FindData
.nFileSizeHigh
? 0xFFFFFFFF
1475 : FindData
.nFileSizeLow
;
1476 /* Build a short path name */
1477 if (*FindData
.cAlternateFileName
)
1478 strncpy(FindFileBlock
->FileName
, FindData
.cAlternateFileName
, sizeof(FindFileBlock
->FileName
));
1480 GetShortPathNameA(FindData
.cFileName
, FindFileBlock
->FileName
, sizeof(FindFileBlock
->FileName
));
1482 return ERROR_SUCCESS
;
1487 demGetPhysicalDriveType(IN UCHAR DriveNumber
)
1490 return DOSDEVICE_DRIVE_UNKNOWN
;
1495 demIsShortPathName(IN LPCSTR Path
,
1504 demSetCurrentDirectoryGetDrive(IN LPCSTR CurrentDirectory
,
1505 OUT PUCHAR DriveNumber
)
1508 return ERROR_SUCCESS
;