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 *******************************************************************/
24 #include "dos/dos32krnl/device.h"
25 #include "dos/dos32krnl/memory.h"
26 #include "dos/dos32krnl/process.h"
30 #include "bios/bios.h"
37 * Activate this line if you want to have COMMAND.COM completely external.
39 // #define COMSPEC_FULLY_EXTERNAL
41 /* PRIVATE VARIABLES **********************************************************/
43 /* PRIVATE FUNCTIONS **********************************************************/
45 /* PUBLIC VARIABLES ***********************************************************/
47 /* PUBLIC FUNCTIONS ***********************************************************/
50 /******************************************************************************\
51 |** DOS DEM Kernel helpers **|
52 \******************************************************************************/
55 VOID
BiosCharPrint(CHAR Character
)
63 * AL contains the character to print,
64 * BL contains the character attribute,
65 * BH contains the video page to use.
68 setBL(DEFAULT_ATTRIBUTE
);
69 setBH(Bda
->VideoPage
);
71 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
73 Int32Call(&DosContext
, BIOS_VIDEO_INTERRUPT
);
75 /* Restore AX and BX */
80 VOID
DosCharPrint(CHAR Character
)
82 DosPrintCharacter(DOS_OUTPUT_HANDLE
, Character
);
86 * This function, derived from ntvdm.c!DisplayMessage, is used by the BIOS and
87 * the DOS to display messages to an output device. A printer function is given
88 * for printing the characters.
91 DisplayMessageAnsiV(IN CHAR_PRINT CharPrint
,
95 static CHAR CurChar
= 0;
98 #ifndef WIN2K_COMPLIANT
99 CHAR StaticBuffer
[256];
100 LPSTR Buffer
= StaticBuffer
; // Use the static buffer by default.
102 CHAR Buffer
[2048]; // Large enough. If not, increase it by hand.
106 #ifndef WIN2K_COMPLIANT
108 * Retrieve the message length and if it is too long, allocate
109 * an auxiliary buffer; otherwise use the static buffer.
110 * The string is built to be NULL-terminated.
112 MsgLen
= _vscprintf(Format
, args
);
113 if (MsgLen
>= ARRAYSIZE(StaticBuffer
))
115 Buffer
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, (MsgLen
+ 1) * sizeof(CHAR
));
118 /* Allocation failed, use the static buffer and display a suitable error message */
119 Buffer
= StaticBuffer
;
120 Format
= "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed.";
121 MsgLen
= strlen(Format
);
125 MsgLen
= ARRAYSIZE(Buffer
) - 1;
128 RtlZeroMemory(Buffer
, (MsgLen
+ 1) * sizeof(CHAR
));
129 _vsnprintf(Buffer
, MsgLen
, Format
, args
);
131 /* Display the message */
132 DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer
);
134 MsgLen
= strlen(Buffer
);
138 if (*str
== '\n' && CurChar
!= '\r')
145 #ifndef WIN2K_COMPLIANT
146 /* Free the buffer if needed */
147 if (Buffer
!= StaticBuffer
) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer
);
152 DemDisplayMessage(IN CHAR_PRINT CharPrint
,
153 IN LPCSTR Format
, ...)
157 va_start(Parameters
, Format
);
158 DisplayMessageAnsiV(CharPrint
, Format
, Parameters
);
163 static VOID
DemLoadNTDOSKernel(VOID
)
165 BOOLEAN Success
= FALSE
;
166 LPCSTR DosKernelFileName
= "ntdos.sys";
168 ULONG ulDosKernelSize
= 0;
170 DPRINT1("You are loading Windows NT DOS!\n");
172 /* Open the DOS kernel file */
173 hDosKernel
= FileOpen(DosKernelFileName
, &ulDosKernelSize
);
174 if (hDosKernel
== NULL
) goto Quit
;
177 * Attempt to load the DOS kernel into memory.
178 * The segment where to load the DOS kernel is defined
179 * by the DOS BIOS and is found in DI:0000 .
181 Success
= FileLoadByHandle(hDosKernel
,
182 REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
186 DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
188 (Success
? "succeeded" : "failed"),
193 /* Close the DOS kernel file */
194 FileClose(hDosKernel
);
199 /* We failed everything, stop the VDM */
200 BiosDisplayMessage("Windows NT DOS kernel file '%s' loading failed (Error: %u). The VDM will shut down.\n",
201 DosKernelFileName
, GetLastError());
207 static VOID WINAPI
DosSystemBop(LPWORD Stack
)
209 /* Get the Function Number and skip it */
210 BYTE FuncNum
= *(PBYTE
)SEG_OFF_TO_PTR(getCS(), getIP());
215 /* Load the DOS kernel */
218 DemLoadNTDOSKernel();
222 /* Call 32-bit Driver Strategy Routine */
223 case BOP_DRV_STRATEGY
:
229 /* Call 32-bit Driver Interrupt Routine */
230 case BOP_DRV_INTERRUPT
:
232 DeviceInterruptBop();
238 DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum
);
239 // setCF(1); // Disable, otherwise we enter an infinite loop
248 /******************************************************************************\
249 |** DOS Command Process management **|
250 \******************************************************************************/
254 static ULONG SessionId
= 0;
257 * 16-bit Command Interpreter information for DOS reentry
259 typedef struct _COMSPEC_INFO
265 } COMSPEC_INFO
, *PCOMSPEC_INFO
;
267 static COMSPEC_INFO RootCmd
;
268 static DWORD ReentrancyCount
= 0;
270 // FIXME: Should we need list locking?
271 static LIST_ENTRY ComSpecInfoList
= { &ComSpecInfoList
, &ComSpecInfoList
};
274 FindComSpecInfoByPsp(WORD Psp
)
277 PCOMSPEC_INFO ComSpecInfo
;
279 for (Pointer
= ComSpecInfoList
.Flink
; Pointer
!= &ComSpecInfoList
; Pointer
= Pointer
->Flink
)
281 ComSpecInfo
= CONTAINING_RECORD(Pointer
, COMSPEC_INFO
, Entry
);
282 if (ComSpecInfo
->ComSpecPsp
== Psp
) return ComSpecInfo
;
289 InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo
)
291 InsertHeadList(&ComSpecInfoList
, &ComSpecInfo
->Entry
);
295 RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo
)
297 RemoveEntryList(&ComSpecInfo
->Entry
);
298 if (ComSpecInfo
!= &RootCmd
)
299 RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo
);
303 static VOID
DosProcessConsoleAttach(VOID
)
305 /* Attach to the console */
307 VidBiosAttachToConsole();
310 static VOID
DosProcessConsoleDetach(VOID
)
312 /* Detach from the console */
313 VidBiosDetachFromConsole();
318 * Data for the next DOS command to run
321 static VDM_COMMAND_INFO CommandInfo
;
322 static BOOLEAN Repeat
= FALSE
;
323 static BOOLEAN Reentry
= FALSE
;
325 static BOOLEAN First
= TRUE
;
326 static CHAR CmdLine
[MAX_PATH
] = ""; // DOS_CMDLINE_LENGTH
327 static CHAR AppName
[MAX_PATH
] = "";
329 static CHAR PifFile
[MAX_PATH
] = "";
330 static CHAR CurDirectory
[MAX_PATH
] = "";
331 static CHAR Desktop
[MAX_PATH
] = "";
332 static CHAR Title
[MAX_PATH
] = "";
333 static ULONG EnvSize
= 256;
334 static PVOID Env
= NULL
;
337 #pragma pack(push, 2)
340 * This structure is compatible with Windows NT DOS
342 typedef struct _NEXT_CMD
361 } NEXT_CMD
, *PNEXT_CMD
;
365 static VOID
CmdStartProcess(VOID
)
368 PCOMSPEC_INFO ComSpecInfo
;
371 PNEXT_CMD DataStruct
= (PNEXT_CMD
)SEG_OFF_TO_PTR(getDS(), getDX());
373 DPRINT1("CmdStartProcess -- DS:DX = %04X:%04X (DataStruct = 0x%p)\n",
374 getDS(), getDX(), DataStruct
);
380 /* Check whether we need to shell out now in case we were started by a 32-bit app */
381 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
382 if (ComSpecInfo
&& ComSpecInfo
->Terminated
)
384 RemoveComSpecInfo(ComSpecInfo
);
386 DPRINT1("Exit DOS from start-app BOP\n");
391 /* Clear the structure */
392 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
394 /* Initialize the structure members */
395 CommandInfo
.TaskId
= SessionId
;
396 CommandInfo
.VDMState
= VDM_FLAG_DOS
;
397 CommandInfo
.CmdLine
= CmdLine
;
398 CommandInfo
.CmdLen
= sizeof(CmdLine
);
399 CommandInfo
.AppName
= AppName
;
400 CommandInfo
.AppLen
= sizeof(AppName
);
401 CommandInfo
.PifFile
= PifFile
;
402 CommandInfo
.PifLen
= sizeof(PifFile
);
403 CommandInfo
.CurDirectory
= CurDirectory
;
404 CommandInfo
.CurDirectoryLen
= sizeof(CurDirectory
);
405 CommandInfo
.Desktop
= Desktop
;
406 CommandInfo
.DesktopLen
= sizeof(Desktop
);
407 CommandInfo
.Title
= Title
;
408 CommandInfo
.TitleLen
= sizeof(Title
);
409 CommandInfo
.Env
= Env
;
410 CommandInfo
.EnvLen
= EnvSize
;
412 if (First
) CommandInfo
.VDMState
|= VDM_FLAG_FIRST_TASK
;
416 if (Repeat
) CommandInfo
.VDMState
|= VDM_FLAG_RETRY
;
419 /* Get the VDM command information */
420 DPRINT1("Calling GetNextVDMCommand in CmdStartProcess: wait for new VDM task...\n");
421 if (!GetNextVDMCommand(&CommandInfo
))
423 DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
424 if (CommandInfo
.EnvLen
> EnvSize
)
426 /* Expand the environment size */
427 EnvSize
= CommandInfo
.EnvLen
;
428 CommandInfo
.Env
= Env
= RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, Env
, EnvSize
);
430 /* Repeat the request */
435 /* Shouldn't happen */
436 DisplayMessage(L
"An unrecoverable failure happened from start-app BOP; exiting DOS.");
441 // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
443 DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
449 DPRINT1("Exit DOS from start-app BOP\n");
456 CmdLen
= strlen(CmdLine
);
457 DPRINT1("Starting '%s' ('%.*s')...\n",
459 /* Display the command line without the terminating 0d 0a (and skip the terminating NULL) */
460 CmdLen
>= 2 ? (CmdLine
[CmdLen
- 2] == '\r' ? CmdLen
- 2
465 /* Start the process */
466 // FIXME: Merge 'Env' with the master environment SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0)
467 // FIXME: Environment
468 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct
->AppNameSeg
, DataStruct
->AppNameOff
), AppName
, MAX_PATH
);
469 *(PBYTE
)(SEG_OFF_TO_PTR(DataStruct
->CmdLineSeg
, DataStruct
->CmdLineOff
)) = (BYTE
)(strlen(CmdLine
) - 2);
470 RtlCopyMemory(SEG_OFF_TO_PTR(DataStruct
->CmdLineSeg
, DataStruct
->CmdLineOff
+ 1), CmdLine
, DOS_CMDLINE_LENGTH
);
473 /* Update console title if we run in a separate console */
475 SetConsoleTitleA(AppName
);
481 DPRINT1("App started!\n");
488 static VOID
CmdStartExternalCommand(VOID
)
492 // TODO: improve: this code has strong similarities
493 // with the 'default' case of DosCreateProcess.
495 LPSTR Command
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getSI());
496 CHAR CmdLine
[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH
+ 1] = "";
500 /* Spawn a user-defined 32-bit command preprocessor */
502 // FIXME: Use COMSPEC env var!!
503 CmdLinePtr
= CmdLine
;
504 strcpy(CmdLinePtr
, "cmd.exe /c ");
505 CmdLinePtr
+= strlen(CmdLinePtr
);
507 /* Build a Win32-compatible command-line */
508 CmdLineLen
= min(strlen(Command
), sizeof(CmdLine
) - strlen(CmdLinePtr
) - 1);
509 RtlCopyMemory(CmdLinePtr
, Command
, CmdLineLen
);
510 CmdLinePtr
[CmdLineLen
] = '\0';
512 /* Remove any trailing return carriage character and NULL-terminate the command line */
513 while (*CmdLinePtr
&& *CmdLinePtr
!= '\r' && *CmdLinePtr
!= '\n') CmdLinePtr
++;
516 DPRINT1("CMD Run Command '%s' ('%s')\n", Command
, CmdLine
);
519 * No need to prepare the stack for DosStartComSpec since we won't start it.
521 Result
= DosStartProcess32(Command
, CmdLine
,
522 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
523 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
525 if (Result
!= ERROR_SUCCESS
)
527 DosDisplayMessage("Failed to start command '%s' ('%s'). Error: %u\n", Command
, CmdLine
, Result
);
529 setAL((UCHAR
)Result
);
533 DosDisplayMessage("Command '%s' ('%s') started successfully.\n", Command
, CmdLine
);
535 setCF(Repeat
); // Set CF if we need to start a 16-bit process
542 static VOID
CmdStartComSpec32(VOID
)
546 // TODO: improve: this code has strong similarities with the
547 // 'default' case of DosCreateProcess and with the 'case 0x08'.
549 CHAR CmdLine
[sizeof("cmd.exe") + 1] = "";
551 /* Spawn a user-defined 32-bit command preprocessor */
553 // FIXME: Use COMSPEC env var!!
554 strcpy(CmdLine
, "cmd.exe");
556 DPRINT1("CMD Run 32-bit Command Interpreter '%s'\n", CmdLine
);
559 * No need to prepare the stack for DosStartComSpec since we won't start it.
561 Result
= DosStartProcess32(CmdLine
, CmdLine
,
562 SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
563 MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
565 if (Result
!= ERROR_SUCCESS
)
567 DosDisplayMessage("Failed to start 32-bit Command Interpreter '%s'. Error: %u\n", CmdLine
, Result
);
569 setAL((UCHAR
)Result
);
573 DosDisplayMessage("32-bit Command Interpreter '%s' started successfully.\n", CmdLine
);
575 setCF(Repeat
); // Set CF if we need to start a 16-bit process
582 static VOID
CmdSetExitCode(VOID
)
586 PCOMSPEC_INFO ComSpecInfo
;
587 VDM_COMMAND_INFO CommandInfo
;
595 * Check whether we need to shell out now in case we were started by a 32-bit app,
596 * or we were started alone along with the root 32-bit app.
598 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
599 if ((ComSpecInfo
&& ComSpecInfo
->Terminated
) ||
600 (ComSpecInfo
== &RootCmd
&& SessionId
!= 0))
602 RemoveComSpecInfo(ComSpecInfo
);
604 DPRINT1("Exit DOS from ExitCode (prologue)!\n");
610 /* Clear the VDM structure */
611 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
614 /* Update the VDM state of the task */
615 // CommandInfo.TaskId = SessionId;
616 CommandInfo
.ExitCode
= getDX();
617 CommandInfo
.VDMState
= VDM_FLAG_DONT_WAIT
;
618 DPRINT1("Calling GetNextVDMCommand 32bit end of VDM task\n");
619 Success
= GetNextVDMCommand(&CommandInfo
);
620 DPRINT1("GetNextVDMCommand 32bit end of VDM task success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
623 * Check whether we were awaited because the 32-bit process was stopped,
624 * or because it started a new DOS application.
626 if (CommandInfo
.CmdLen
!= 0 || CommandInfo
.AppLen
!= 0 || CommandInfo
.PifLen
!= 0)
628 DPRINT1("GetNextVDMCommand end-of-app, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
629 CommandInfo
.CmdLen
, CommandInfo
.AppLen
, CommandInfo
.PifLen
);
631 /* Repeat the request */
637 DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
639 /* Check whether we need to shell out now in case we were started by a 32-bit app */
640 ComSpecInfo
= FindComSpecInfoByPsp(Sda
->CurrentPsp
);
641 if (!ComSpecInfo
|| !ComSpecInfo
->Terminated
)
643 DPRINT1("Not our 32-bit app, retrying...\n");
647 ASSERT(ComSpecInfo
->Terminated
== TRUE
);
649 /* Record found, remove it and exit now */
650 RemoveComSpecInfo(ComSpecInfo
);
652 DPRINT1("Exit DOS from ExitCode wait!\n");
657 // FIXME: Use the retrieved exit code as the value of our exit code
658 // when COMMAND.COM will shell-out ??
665 static VOID WINAPI
DosCmdInterpreterBop(LPWORD Stack
)
667 /* Get the Function Number and skip it */
668 BYTE FuncNum
= *(PBYTE
)SEG_OFF_TO_PTR(getCS(), getIP());
682 * Get a new app to start
685 * DS:DX : Data block.
688 * CF : 0: Success; 1: Failure.
697 * Check binary format
700 * DS:DX : Program to check.
703 * CF : 0: Success; 1: Failure.
709 LPSTR ProgramName
= (LPSTR
)SEG_OFF_TO_PTR(getDS(), getDX());
711 if (!GetBinaryTypeA(ProgramName
, &BinaryType
))
713 /* An error happened, bail out */
715 setAX(LOWORD(GetLastError()));
719 // FIXME: We only support DOS binaries for now...
720 ASSERT(BinaryType
== SCS_DOS_BINARY
);
721 if (BinaryType
!= SCS_DOS_BINARY
)
723 /* An error happened, bail out */
725 setAX(LOWORD(ERROR_BAD_EXE_FORMAT
));
729 /* Return success: DOS application */
735 * Start an external command
738 * DS:SI : Command to start.
739 * ES : Environment block segment.
740 * AL : Current drive number.
741 * AH : 0: Directly start the command;
742 * 1: Use "cmd.exe /c" to start the command.
745 * CF : 0: Shell-out; 1: Continue.
746 * AL : Error/Exit code.
750 CmdStartExternalCommand();
755 * Start the default 32-bit command interpreter (COMSPEC)
758 * ES : Environment block segment.
759 * AL : Current drive number.
762 * CF : 0: Shell-out; 1: Continue.
763 * AL : Error/Exit code.
778 * CF : 0: Shell-out; 1: Continue.
787 * Get start information
790 * AL : 0 (resp. 1): Started from (resp. without) an existing console.
796 * When a new instance of our (internal) COMMAND.COM is started,
797 * we check whether we need to run a 32-bit COMSPEC. This goes by
798 * checking whether we were started in a new console (no parent
799 * console process) or from an existing one.
801 * However COMMAND.COM can also be started in the case where a
802 * 32-bit process (started by a 16-bit parent) wants to start a new
803 * 16-bit process: to ensure DOS reentry we need to start a new
804 * instance of COMMAND.COM. On Windows the COMMAND.COM is started
805 * just before the 32-bit process (in fact, it is this COMMAND.COM
806 * which starts the 32-bit process via an undocumented command-line
807 * switch '/z', which syntax is:
808 * COMMAND.COM /z\bAPPNAME.EXE
809 * notice the '\b' character inserted in-between. Then COMMAND.COM
810 * issues a BOP_CMD 08h with AH=00h to start the process).
812 * Instead, we do the reverse, i.e. we start the 32-bit process,
813 * and *only* if needed, i.e. if this process wants to start a
814 * new 16-bit process, we start our COMMAND.COM.
816 * The problem we then face is that our COMMAND.COM will possibly
817 * want to start a new COMSPEC, however we do not want this.
818 * The chosen solution is to flag this case -- done with the 'Reentry'
819 * boolean -- so that COMMAND.COM will not attempt to start COMSPEC
820 * but instead will directly try to start the 16-bit process.
822 // setAL(SessionId != 0);
823 setAL((SessionId
!= 0) && !Reentry
);
824 /* Reset 'Reentry' */
834 DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum
);
835 // setCF(1); // Disable, otherwise we enter an infinite loop
841 #ifndef COMSPEC_FULLY_EXTERNAL
843 * Internal COMMAND.COM binary data in the CommandCom array.
845 #include "command_com.h"
849 DWORD
DosStartComSpec(IN BOOLEAN Permanent
,
850 IN LPCSTR Environment OPTIONAL
,
851 IN DWORD ReturnAddress OPTIONAL
,
852 OUT PWORD ComSpecPsp OPTIONAL
)
856 * TRUE to simulate the /P switch of command.com: starts AUTOEXEC.BAT/NT
857 * and makes the interpreter permanent (cannot exit).
862 if (ComSpecPsp
) *ComSpecPsp
= 0;
865 #ifndef COMSPEC_FULLY_EXTERNAL
866 DosLoadExecutableInternal(DOS_LOAD_AND_EXECUTE
,
871 DosLoadExecutable(DOS_LOAD_AND_EXECUTE
,
872 #ifndef STANDALONE // FIXME: Those values are hardcoded paths on my local test machines!!
875 "H:\\DOS_tests\\CMDCMD.COM",
877 #endif // COMSPEC_FULLY_EXTERNAL
879 Permanent
? "/P" : "",
880 Environment
? Environment
: "", // FIXME: Default environment!
882 if (Result
!= ERROR_SUCCESS
) return Result
;
884 /* TODO: Read AUTOEXEC.NT/BAT */
886 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
887 if (ComSpecPsp
) *ComSpecPsp
= Sda
->CurrentPsp
;
892 typedef struct _DOS_START_PROC32
894 LPSTR ExecutablePath
;
896 LPSTR Environment OPTIONAL
;
898 PCOMSPEC_INFO ComSpecInfo
;
901 } DOS_START_PROC32
, *PDOS_START_PROC32
;
905 CommandThreadProc(LPVOID Parameter
)
908 PROCESS_INFORMATION ProcessInfo
;
909 STARTUPINFOA StartupInfo
;
911 PDOS_START_PROC32 DosStartProc32
= (PDOS_START_PROC32
)Parameter
;
913 VDM_COMMAND_INFO CommandInfo
;
914 PCOMSPEC_INFO ComSpecInfo
= DosStartProc32
->ComSpecInfo
;
917 /* Set up the VDM, startup and process info structures */
919 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
921 RtlZeroMemory(&ProcessInfo
, sizeof(ProcessInfo
));
922 RtlZeroMemory(&StartupInfo
, sizeof(StartupInfo
));
923 StartupInfo
.cb
= sizeof(StartupInfo
);
925 // FIXME: Build suitable 32-bit environment!!
929 * Wait for signaling a new VDM task and increment the VDM re-entry count so
930 * that we can handle 16-bit apps that may be possibly started by the 32-bit app.
932 CommandInfo
.VDMState
= VDM_INC_REENTER_COUNT
;
933 DPRINT1("Calling GetNextVDMCommand reenter++\n");
934 Success
= GetNextVDMCommand(&CommandInfo
);
935 DPRINT1("GetNextVDMCommand reenter++ success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
939 /* Start the process */
940 Success
= CreateProcessA(NULL
, // ProgramName,
941 DosStartProc32
->CommandLine
,
944 TRUE
, // Inherit handles
945 CREATE_DEFAULT_ERROR_MODE
| CREATE_SUSPENDED
,
946 DosStartProc32
->Environment
,
947 NULL
, // lpCurrentDirectory, see "START" command in cmd.exe
952 /* Signal our caller the process was started */
953 SetEvent(DosStartProc32
->hEvent
);
954 // After this point, 'DosStartProc32' is not valid anymore.
959 /* Resume the process */
960 ResumeThread(ProcessInfo
.hThread
);
962 /* Wait for the process to finish running and retrieve its exit code */
963 WaitForSingleObject(ProcessInfo
.hProcess
, INFINITE
);
964 GetExitCodeProcess(ProcessInfo
.hProcess
, &dwExitCode
);
966 /* Close the handles */
967 CloseHandle(ProcessInfo
.hThread
);
968 CloseHandle(ProcessInfo
.hProcess
);
972 dwExitCode
= GetLastError();
977 ComSpecInfo
->Terminated
= TRUE
;
978 ComSpecInfo
->dwExitCode
= dwExitCode
;
980 /* Decrement the VDM re-entry count */
981 CommandInfo
.VDMState
= VDM_DEC_REENTER_COUNT
;
982 DPRINT1("Calling GetNextVDMCommand reenter--\n");
983 Success
= GetNextVDMCommand(&CommandInfo
);
984 DPRINT1("GetNextVDMCommand reenter-- success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
993 DWORD
DosStartProcess32(IN LPCSTR ExecutablePath
,
994 IN LPCSTR CommandLine
,
995 IN LPCSTR Environment OPTIONAL
,
996 IN DWORD ReturnAddress OPTIONAL
,
997 IN BOOLEAN StartComSpec
)
999 DWORD Result
= ERROR_SUCCESS
;
1000 HANDLE CommandThread
;
1001 DOS_START_PROC32 DosStartProc32
;
1004 VDM_COMMAND_INFO CommandInfo
;
1007 DosStartProc32
.ExecutablePath
= (LPSTR
)ExecutablePath
;
1008 DosStartProc32
.CommandLine
= (LPSTR
)CommandLine
;
1009 DosStartProc32
.Environment
= (LPSTR
)Environment
;
1012 DosStartProc32
.ComSpecInfo
=
1013 RtlAllocateHeap(RtlGetProcessHeap(),
1015 sizeof(*DosStartProc32
.ComSpecInfo
));
1016 ASSERT(DosStartProc32
.ComSpecInfo
);
1018 DosStartProc32
.hEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
1019 ASSERT(DosStartProc32
.hEvent
);
1022 /* Pause the VM and detach from the console */
1024 DosProcessConsoleDetach();
1026 /* Start the 32-bit process via another thread */
1027 CommandThread
= CreateThread(NULL
, 0, &CommandThreadProc
, &DosStartProc32
, 0, NULL
);
1028 if (CommandThread
== NULL
)
1030 DisplayMessage(L
"FATAL: Failed to create the command processing thread: %d", GetLastError());
1031 Result
= GetLastError();
1036 /* Close the thread handle */
1037 CloseHandle(CommandThread
);
1039 /* Wait for the process to be ready to start */
1040 WaitForSingleObject(DosStartProc32
.hEvent
, INFINITE
);
1042 /* Wait for any potential new DOS app started by the 32-bit process */
1043 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1046 CommandInfo
.VDMState
= VDM_FLAG_NESTED_TASK
| VDM_FLAG_DONT_WAIT
;
1047 DPRINT1("Calling GetNextVDMCommand 32bit for possible new VDM task...\n");
1048 Success
= GetNextVDMCommand(&CommandInfo
);
1049 DPRINT1("GetNextVDMCommand 32bit awaited, success = %s, last error = %d\n", Success
? "true" : "false", GetLastError());
1052 * Check whether we were awaited because the 32-bit process was stopped,
1053 * or because it started a new DOS application.
1055 if (CommandInfo
.CmdLen
!= 0 || CommandInfo
.AppLen
!= 0 || CommandInfo
.PifLen
!= 0)
1057 DPRINT1("GetNextVDMCommand 32bit, this is for a new VDM task - CmdLen = %d, AppLen = %d, PifLen = %d\n",
1058 CommandInfo
.CmdLen
, CommandInfo
.AppLen
, CommandInfo
.PifLen
);
1060 /* Repeat the request */
1064 * Set 'Reentry' to TRUE or FALSE depending on whether we are going
1065 * to reenter with a new COMMAND.COM. See the comment for:
1066 * BOP_CMD 0x10 'Get start information'
1067 * (dem.c!DosCmdInterpreterBop) for more details.
1069 Reentry
= StartComSpec
;
1071 /* If needed, start a new command interpreter to handle the possible incoming DOS commands */
1075 // DosStartProcess32 was only called by DosCreateProcess, called from INT 21h,
1076 // so the caller stack is already prepared for running a new DOS program
1077 // (Flags, CS and IP, and the extra interrupt number, are already pushed).
1079 Result
= DosStartComSpec(FALSE
, Environment
, ReturnAddress
,
1080 &DosStartProc32
.ComSpecInfo
->ComSpecPsp
);
1081 if (Result
!= ERROR_SUCCESS
)
1083 DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result
);
1089 /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
1090 DosStartProc32
.ComSpecInfo
->ComSpecPsp
= Sda
->CurrentPsp
;
1091 Result
= ERROR_SUCCESS
;
1094 /* Insert the new entry in the list; it will be freed when needed by COMMAND.COM */
1095 InsertComSpecInfo(DosStartProc32
.ComSpecInfo
);
1099 DPRINT1("GetNextVDMCommand 32bit, 32-bit app stopped\n");
1101 /* Check whether this was our 32-bit app which was killed */
1102 if (!DosStartProc32
.ComSpecInfo
->Terminated
)
1104 DPRINT1("Not our 32-bit app, retrying...\n");
1108 Result
= DosStartProc32
.ComSpecInfo
->dwExitCode
;
1110 /* Delete the entry */
1111 RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32
.ComSpecInfo
);
1114 /* Wait for the thread to finish */
1115 WaitForSingleObject(CommandThread
, INFINITE
);
1116 GetExitCodeThread(CommandThread
, &Result
);
1118 /* Close the thread handle */
1119 CloseHandle(CommandThread
);
1121 DPRINT1("32-bit app stopped\n");
1126 CloseHandle(DosStartProc32
.hEvent
);
1129 /* Attach to the console and resume the VM */
1130 DosProcessConsoleAttach();
1139 /******************************************************************************\
1140 |** DOS Bootloader emulation, Startup and Shutdown **|
1141 \******************************************************************************/
1145 // This function (equivalent of the DOS bootsector) is called by the bootstrap
1146 // loader *BEFORE* jumping at 0000:7C00. What we should do is to write at 0000:7C00
1147 // a BOP call that calls DosInitialize back. Then the bootstrap loader jumps at
1148 // 0000:7C00, our BOP gets called and then we can initialize the 32-bit part of the DOS.
1151 /* 16-bit bootstrap code at 0000:7C00 */
1152 /* Of course, this is not in real bootsector format, because we don't care about it for now */
1153 static BYTE Bootsector1
[] =
1155 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_LOAD_DOS
1157 /* This portion of code is run if we failed to load the DOS */
1158 // NOTE: This may also be done by the BIOS32.
1159 static BYTE Bootsector2
[] =
1161 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_UNSIMULATE
1164 static VOID WINAPI
DosInitialize(LPWORD Stack
);
1166 VOID
DosBootsectorInitialize(VOID
)
1168 /* We write the bootsector at 0000:7C00 */
1169 ULONG_PTR StartAddress
= (ULONG_PTR
)SEG_OFF_TO_PTR(0x0000, 0x7C00);
1170 ULONG_PTR Address
= StartAddress
;
1171 CHAR DosKernelFileName
[] = ""; // No DOS BIOS file name, therefore we will load DOS32
1173 DPRINT("DosBootsectorInitialize\n");
1175 /* Write the "bootsector" */
1176 RtlCopyMemory((PVOID
)Address
, Bootsector1
, sizeof(Bootsector1
));
1177 Address
+= sizeof(Bootsector1
);
1178 RtlCopyMemory((PVOID
)Address
, DosKernelFileName
, sizeof(DosKernelFileName
));
1179 Address
+= sizeof(DosKernelFileName
);
1180 RtlCopyMemory((PVOID
)Address
, Bootsector2
, sizeof(Bootsector2
));
1181 Address
+= sizeof(Bootsector2
);
1183 /* Initialize the callback context */
1184 InitializeContext(&DosContext
, 0x0000,
1185 (ULONG_PTR
)MEM_ALIGN_UP(0x7C00 + Address
- StartAddress
, sizeof(WORD
)));
1187 /* Register the DOS Loading BOP */
1188 RegisterBop(BOP_LOAD_DOS
, DosInitialize
);
1193 // This function is called by the DOS bootsector in case we load DOS32.
1194 // It sets up the DOS32 start code then jumps to 0070:0000.
1197 /* 16-bit startup code for DOS32 at 0070:0000 */
1198 static BYTE Startup
[] =
1200 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_START_DOS
,
1201 LOBYTE(EMULATOR_BOP
), HIBYTE(EMULATOR_BOP
), BOP_UNSIMULATE
1204 static VOID WINAPI
DosStart(LPWORD Stack
);
1206 static VOID WINAPI
DosInitialize(LPWORD Stack
)
1208 /* Get the DOS BIOS file name (NULL-terminated) */
1209 // FIXME: Isn't it possible to use some DS:SI instead??
1210 LPCSTR DosBiosFileName
= (LPCSTR
)SEG_OFF_TO_PTR(getCS(), getIP());
1211 setIP(getIP() + strlen(DosBiosFileName
) + 1); // Skip it
1213 DPRINT("DosInitialize('%s')\n", DosBiosFileName
);
1216 * We succeeded, deregister the DOS Loading BOP
1217 * so that no app will be able to call us back.
1219 RegisterBop(BOP_LOAD_DOS
, NULL
);
1221 /* Register the DOS BOPs */
1222 RegisterBop(BOP_DOS
, DosSystemBop
);
1223 RegisterBop(BOP_CMD
, DosCmdInterpreterBop
);
1225 if (DosBiosFileName
[0] != '\0')
1227 BOOLEAN Success
= FALSE
;
1229 ULONG ulDosBiosSize
= 0;
1231 /* Open the DOS BIOS file */
1232 hDosBios
= FileOpen(DosBiosFileName
, &ulDosBiosSize
);
1233 if (hDosBios
== NULL
) goto Quit
;
1235 /* Attempt to load the DOS BIOS into memory */
1236 Success
= FileLoadByHandle(hDosBios
,
1237 REAL_TO_PHYS(TO_LINEAR(0x0070, 0x0000)),
1241 DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
1243 (Success
? "succeeded" : "failed"),
1248 /* Close the DOS BIOS file */
1249 FileClose(hDosBios
);
1254 BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
1255 DosBiosFileName
, GetLastError());
1261 /* Load the 16-bit startup code for DOS32 and register its Starting BOP */
1262 RtlCopyMemory(SEG_OFF_TO_PTR(0x0070, 0x0000), Startup
, sizeof(Startup
));
1264 // This is the equivalent of BOP_LOAD_DOS, function 0x11 "Load the DOS kernel"
1265 // for the Windows NT DOS.
1266 RegisterBop(BOP_START_DOS
, DosStart
);
1269 /* Position execution pointers for DOS startup and return */
1274 static VOID WINAPI
DosStart(LPWORD Stack
)
1282 DPRINT("DosStart\n");
1285 * We succeeded, deregister the DOS Starting BOP
1286 * so that no app will be able to call us back.
1288 RegisterBop(BOP_START_DOS
, NULL
);
1290 /* Initialize the callback context */
1291 InitializeContext(&DosContext
, BIOS_CODE_SEGMENT
, 0x0010);
1293 Success
= DosBIOSInitialize();
1294 // Success &= DosKRNLInitialize();
1297 BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
1298 EmulatorTerminate();
1302 /* Load the mouse driver */
1303 DosMouseInitialize();
1307 /* Parse the command line arguments */
1308 for (i
= 1; i
< NtVdmArgc
; i
++)
1310 if (wcsncmp(NtVdmArgv
[i
], L
"-i", 2) == 0)
1312 /* This is the session ID (hex format) */
1313 SessionId
= wcstoul(NtVdmArgv
[i
] + 2, NULL
, 16);
1317 /* Initialize Win32-VDM environment */
1318 Env
= RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY
, EnvSize
);
1321 DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
1322 EmulatorTerminate();
1326 /* Clear the structure */
1327 RtlZeroMemory(&CommandInfo
, sizeof(CommandInfo
));
1329 /* Get the initial information */
1330 CommandInfo
.TaskId
= SessionId
;
1331 CommandInfo
.VDMState
= VDM_GET_FIRST_COMMAND
| VDM_FLAG_DOS
;
1332 GetNextVDMCommand(&CommandInfo
);
1336 /* Retrieve the command to start */
1339 WideCharToMultiByte(CP_ACP
, 0, NtVdmArgv
[1], -1, AppName
, sizeof(AppName
), NULL
, NULL
);
1342 WideCharToMultiByte(CP_ACP
, 0, NtVdmArgv
[2], -1, CmdLine
, sizeof(CmdLine
), NULL
, NULL
);
1344 strcpy(CmdLine
, "");
1348 DosDisplayMessage("Invalid DOS command line\n");
1349 EmulatorTerminate();
1356 * At this point, CS:IP points to the DOS BIOS exit code. If the
1357 * root command interpreter fails to start (or if it exits), DOS
1358 * exits and the VDM terminates.
1361 /* Start the root command interpreter */
1362 // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
1365 * Prepare the stack for DosStartComSpec:
1366 * push Flags, CS and IP, and an extra WORD.
1368 setSP(getSP() - sizeof(WORD
));
1369 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = (WORD
)getEFLAGS();
1370 setSP(getSP() - sizeof(WORD
));
1371 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = getCS();
1372 setSP(getSP() - sizeof(WORD
));
1373 *((LPWORD
)SEG_OFF_TO_PTR(getSS(), getSP())) = getIP();
1374 setSP(getSP() - sizeof(WORD
));
1376 Result
= DosStartComSpec(TRUE
, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK
, 0),
1377 MAKELONG(getIP(), getCS()),
1384 if (Result
!= ERROR_SUCCESS
)
1386 /* Unprepare the stack for DosStartComSpec */
1387 setSP(getSP() + 4*sizeof(WORD
));
1389 DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result
);
1390 EmulatorTerminate();
1395 RootCmd
.Terminated
= FALSE
;
1396 InsertComSpecInfo(&RootCmd
);
1400 /* Attach to the console and resume the VM */
1401 DosProcessConsoleAttach();
1408 BOOLEAN
DosShutdown(BOOLEAN Immediate
)
1411 * Immediate = TRUE: Immediate shutdown;
1412 * FALSE: Delayed shutdown (notification).
1424 extern HANDLE VdmTaskEvent
; // see emulator.c
1427 * Signal the root COMMAND.COM that it should terminate
1428 * when it checks for a new command.
1430 RootCmd
.Terminated
= TRUE
;
1432 /* If the list is already empty, or just contains only one element, bail out */
1433 // FIXME: Question: if the list has only one element, is it ALWAYS RootCmd ??
1434 // FIXME: The following is hackish.
1435 if ((IsListEmpty(&ComSpecInfoList
) ||
1436 (ComSpecInfoList
.Flink
== &RootCmd
.Entry
&&
1437 ComSpecInfoList
.Blink
== &RootCmd
.Entry
)) &&
1438 ReentrancyCount
== 0 &&
1439 WaitForSingleObject(VdmTaskEvent
, 0) == WAIT_TIMEOUT
)
1441 /* Nothing runs, so exit immediately */
1449 UNREFERENCED_PARAMETER(Immediate
);
1455 /* PUBLIC EXPORTED APIS *******************************************************/
1458 // demLFNGetCurrentDirectory
1460 // demGetFileTimeByHandle_WOW
1461 // demWOWLFNAllocateSearchHandle
1462 // demWOWLFNCloseSearchHandle
1464 // demWOWLFNGetSearchHandle
1469 demClientErrorEx(IN HANDLE FileHandle
,
1474 return GetLastError();
1479 demFileDelete(IN LPCSTR FileName
)
1481 if (DeleteFileA(FileName
)) SetLastError(ERROR_SUCCESS
);
1483 return GetLastError();
1488 demFileFindFirst(OUT PVOID lpFindFileData
,
1492 BOOLEAN Success
= TRUE
;
1493 WIN32_FIND_DATAA FindData
;
1494 HANDLE SearchHandle
;
1495 PDOS_FIND_FILE_BLOCK FindFileBlock
= (PDOS_FIND_FILE_BLOCK
)lpFindFileData
;
1497 /* Start a search */
1498 SearchHandle
= FindFirstFileA(FileName
, &FindData
);
1499 if (SearchHandle
== INVALID_HANDLE_VALUE
) return GetLastError();
1503 /* Check the attributes and retry as long as we haven't found a matching file */
1504 if (!((FindData
.dwFileAttributes
& (FILE_ATTRIBUTE_HIDDEN
|
1505 FILE_ATTRIBUTE_SYSTEM
|
1506 FILE_ATTRIBUTE_DIRECTORY
))
1512 while ((Success
= FindNextFileA(SearchHandle
, &FindData
)));
1514 /* If we failed at some point, close the search and return an error */
1517 FindClose(SearchHandle
);
1518 return GetLastError();
1521 /* Fill the block */
1522 FindFileBlock
->DriveLetter
= DosData
->Sda
.CurrentDrive
+ 'A';
1523 FindFileBlock
->AttribMask
= AttribMask
;
1524 FindFileBlock
->SearchHandle
= SearchHandle
;
1525 FindFileBlock
->Attributes
= LOBYTE(FindData
.dwFileAttributes
);
1526 FileTimeToDosDateTime(&FindData
.ftLastWriteTime
,
1527 &FindFileBlock
->FileDate
,
1528 &FindFileBlock
->FileTime
);
1529 FindFileBlock
->FileSize
= FindData
.nFileSizeHigh
? 0xFFFFFFFF
1530 : FindData
.nFileSizeLow
;
1531 /* Build a short path name */
1532 if (*FindData
.cAlternateFileName
)
1533 strncpy(FindFileBlock
->FileName
, FindData
.cAlternateFileName
, sizeof(FindFileBlock
->FileName
));
1535 GetShortPathNameA(FindData
.cFileName
, FindFileBlock
->FileName
, sizeof(FindFileBlock
->FileName
));
1537 return ERROR_SUCCESS
;
1542 demFileFindNext(OUT PVOID lpFindFileData
)
1544 WIN32_FIND_DATAA FindData
;
1545 PDOS_FIND_FILE_BLOCK FindFileBlock
= (PDOS_FIND_FILE_BLOCK
)lpFindFileData
;
1549 /* Continue searching as long as we haven't found a matching file */
1551 /* If we failed at some point, close the search and return an error */
1552 if (!FindNextFileA(FindFileBlock
->SearchHandle
, &FindData
))
1554 FindClose(FindFileBlock
->SearchHandle
);
1555 return GetLastError();
1558 while ((FindData
.dwFileAttributes
& (FILE_ATTRIBUTE_HIDDEN
|
1559 FILE_ATTRIBUTE_SYSTEM
|
1560 FILE_ATTRIBUTE_DIRECTORY
))
1561 & ~FindFileBlock
->AttribMask
);
1563 /* Update the block */
1564 FindFileBlock
->Attributes
= LOBYTE(FindData
.dwFileAttributes
);
1565 FileTimeToDosDateTime(&FindData
.ftLastWriteTime
,
1566 &FindFileBlock
->FileDate
,
1567 &FindFileBlock
->FileTime
);
1568 FindFileBlock
->FileSize
= FindData
.nFileSizeHigh
? 0xFFFFFFFF
1569 : FindData
.nFileSizeLow
;
1570 /* Build a short path name */
1571 if (*FindData
.cAlternateFileName
)
1572 strncpy(FindFileBlock
->FileName
, FindData
.cAlternateFileName
, sizeof(FindFileBlock
->FileName
));
1574 GetShortPathNameA(FindData
.cFileName
, FindFileBlock
->FileName
, sizeof(FindFileBlock
->FileName
));
1576 return ERROR_SUCCESS
;
1581 demGetPhysicalDriveType(IN UCHAR DriveNumber
)
1584 return DOSDEVICE_DRIVE_UNKNOWN
;
1589 demIsShortPathName(IN LPCSTR Path
,
1598 demSetCurrentDirectoryGetDrive(IN LPCSTR CurrentDirectory
,
1599 OUT PUCHAR DriveNumber
)
1602 return ERROR_SUCCESS
;