xref: /reactos/subsystems/mvdm/ntvdm/dos/dem.c (revision 40462c92)
1 /*
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)
11  */
12 
13 /* INCLUDES *******************************************************************/
14 
15 #include "ntvdm.h"
16 
17 #define NDEBUG
18 #include <debug.h>
19 
20 #include "emulator.h"
21 #include <isvbop.h>
22 
23 #include "utils.h"
24 
25 #include "dem.h"
26 #include "dos/dos32krnl/device.h"
27 #include "dos/dos32krnl/memory.h"
28 #include "dos/dos32krnl/process.h"
29 #include "cpu/bop.h"
30 #include "cpu/cpu.h"
31 
32 #include "bios/bios.h"
33 #include "mouse32.h"
34 
35 #include "vddsup.h"
36 
37 /*
38  * EXPERIMENTAL!
39  * Activate this line if you want to have COMMAND.COM completely external.
40  */
41 // #define COMSPEC_FULLY_EXTERNAL
42 
43 /* PRIVATE VARIABLES **********************************************************/
44 
45 /* PRIVATE FUNCTIONS **********************************************************/
46 
47 /* PUBLIC VARIABLES ***********************************************************/
48 
49 /* PUBLIC FUNCTIONS ***********************************************************/
50 
51 
52 /******************************************************************************\
53 |**                          DOS DEM Kernel helpers                          **|
54 \******************************************************************************/
55 
56 
57 VOID Dem_BiosCharPrint(CHAR Character)
58 {
59     /* Save AX and BX */
60     USHORT AX = getAX();
61     USHORT BX = getBX();
62 
63     /*
64      * Set the parameters:
65      * AL contains the character to print,
66      * BL contains the character attribute,
67      * BH contains the video page to use.
68      */
69     setAL(Character);
70     setBL(DEFAULT_ATTRIBUTE);
71     setBH(Bda->VideoPage);
72 
73     /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
74     setAH(0x0E);
75     Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
76 
77     /* Restore AX and BX */
78     setBX(BX);
79     setAX(AX);
80 }
81 
82 VOID DosCharPrint(CHAR Character)
83 {
84     DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
85 }
86 
87 
88 static VOID DemLoadNTDOSKernel(VOID)
89 {
90     BOOLEAN Success = FALSE;
91     LPCSTR  DosKernelFileName = "ntdos.sys";
92     HANDLE  hDosKernel;
93     ULONG   ulDosKernelSize = 0;
94 
95     DPRINT1("You are loading Windows NT DOS!\n");
96 
97     /* Open the DOS kernel file */
98     hDosKernel = FileOpen(DosKernelFileName, &ulDosKernelSize);
99     if (hDosKernel == NULL) goto Quit;
100 
101     /*
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 .
105      */
106     Success = FileLoadByHandle(hDosKernel,
107                                REAL_TO_PHYS(TO_LINEAR(getDI(), 0x0000)),
108                                ulDosKernelSize,
109                                &ulDosKernelSize);
110 
111     DPRINT1("Windows NT DOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
112             DosKernelFileName,
113             (Success ? "succeeded" : "failed"),
114             getDI(), 0x0000,
115             ulDosKernelSize,
116             GetLastError());
117 
118     /* Close the DOS kernel file */
119     FileClose(hDosKernel);
120 
121 Quit:
122     if (!Success)
123     {
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());
127         EmulatorTerminate();
128         return;
129     }
130 }
131 
132 static VOID WINAPI DosSystemBop(LPWORD Stack)
133 {
134     /* Get the Function Number and skip it */
135     BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
136     setIP(getIP() + 1);
137 
138     switch (FuncNum)
139     {
140         /* Load the DOS kernel */
141         case 0x11:
142         {
143             DemLoadNTDOSKernel();
144             break;
145         }
146 
147         /* Call 32-bit Driver Strategy Routine */
148         case BOP_DRV_STRATEGY:
149         {
150             DeviceStrategyBop();
151             break;
152         }
153 
154         /* Call 32-bit Driver Interrupt Routine */
155         case BOP_DRV_INTERRUPT:
156         {
157             DeviceInterruptBop();
158             break;
159         }
160 
161         default:
162         {
163             DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum);
164             // setCF(1); // Disable, otherwise we enter an infinite loop
165             break;
166         }
167     }
168 }
169 
170 
171 
172 
173 /******************************************************************************\
174 |**                      DOS Command Process management                      **|
175 \******************************************************************************/
176 
177 
178 #ifndef STANDALONE
179 static ULONG SessionId = 0;
180 
181 /*
182  * 16-bit Command Interpreter information for DOS reentry
183  */
184 typedef struct _COMSPEC_INFO
185 {
186     LIST_ENTRY Entry;
187     DWORD dwExitCode;
188     WORD ComSpecPsp;
189     BOOLEAN Terminated;
190 } COMSPEC_INFO, *PCOMSPEC_INFO;
191 
192 static COMSPEC_INFO RootCmd;
193 static DWORD ReentrancyCount = 0;
194 
195 // FIXME: Should we need list locking?
196 static LIST_ENTRY ComSpecInfoList = { &ComSpecInfoList, &ComSpecInfoList };
197 
198 static PCOMSPEC_INFO
199 FindComSpecInfoByPsp(WORD Psp)
200 {
201     PLIST_ENTRY Pointer;
202     PCOMSPEC_INFO ComSpecInfo;
203 
204     for (Pointer = ComSpecInfoList.Flink; Pointer != &ComSpecInfoList; Pointer = Pointer->Flink)
205     {
206         ComSpecInfo = CONTAINING_RECORD(Pointer, COMSPEC_INFO, Entry);
207         if (ComSpecInfo->ComSpecPsp == Psp) return ComSpecInfo;
208     }
209 
210     return NULL;
211 }
212 
213 static VOID
214 InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
215 {
216     InsertHeadList(&ComSpecInfoList, &ComSpecInfo->Entry);
217 }
218 
219 static VOID
220 RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
221 {
222     RemoveEntryList(&ComSpecInfo->Entry);
223     if (ComSpecInfo != &RootCmd)
224         RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo);
225 }
226 #endif
227 
228 static VOID DosProcessConsoleAttach(VOID)
229 {
230     /* Attach to the console */
231     ConsoleAttach();
232     VidBiosAttachToConsole();
233 }
234 
235 static VOID DosProcessConsoleDetach(VOID)
236 {
237     /* Detach from the console */
238     VidBiosDetachFromConsole();
239     ConsoleDetach();
240 }
241 
242 /*
243  * Data for the next DOS command to run
244  */
245 #ifndef STANDALONE
246 static VDM_COMMAND_INFO CommandInfo;
247 static BOOLEAN Repeat = FALSE;
248 static BOOLEAN Reentry = FALSE;
249 #endif
250 static BOOLEAN First = TRUE;
251 static CHAR CmdLine[MAX_PATH] = ""; // DOS_CMDLINE_LENGTH
252 static CHAR AppName[MAX_PATH] = "";
253 #ifndef STANDALONE
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;
260 #endif
261 
262 #pragma pack(push, 2)
263 
264 /*
265  * This structure is compatible with Windows NT DOS
266  */
267 typedef struct _NEXT_CMD
268 {
269     USHORT EnvBlockSeg;
270     USHORT EnvBlockLen;
271     USHORT CurDrive;
272     USHORT NumDrives;
273     USHORT CmdLineSeg;
274     USHORT CmdLineOff;
275     USHORT Unknown0;
276     USHORT ExitCode;
277     USHORT Unknown1;
278     ULONG  Unknown2;
279     USHORT CodePage;
280     USHORT Unknown3;
281     USHORT Unknown4;
282     USHORT AppNameSeg;
283     USHORT AppNameOff;
284     USHORT AppNameLen;
285     USHORT Flags;
286 } NEXT_CMD, *PNEXT_CMD;
287 
288 #pragma pack(pop)
289 
290 static VOID CmdStartProcess(VOID)
291 {
292 #ifndef STANDALONE
293     PCOMSPEC_INFO ComSpecInfo;
294 #endif
295     SIZE_T CmdLen;
296     PNEXT_CMD DataStruct = (PNEXT_CMD)SEG_OFF_TO_PTR(getDS(), getDX());
297 
298     DPRINT1("CmdStartProcess -- DS:DX = %04X:%04X (DataStruct = 0x%p)\n",
299             getDS(), getDX(), DataStruct);
300 
301     /* Pause the VM */
302     EmulatorPause();
303 
304 #ifndef STANDALONE
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)
308     {
309         RemoveComSpecInfo(ComSpecInfo);
310 
311         DPRINT1("Exit DOS from start-app BOP\n");
312         setCF(1);
313         goto Quit;
314     }
315 
316     /* Clear the structure */
317     RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
318 
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;
336 
337     if (First) CommandInfo.VDMState |= VDM_FLAG_FIRST_TASK;
338 
339 Command:
340 
341     if (Repeat) CommandInfo.VDMState |= VDM_FLAG_RETRY;
342     Repeat = FALSE;
343 
344     /* Get the VDM command information */
345     DPRINT1("Calling GetNextVDMCommand in CmdStartProcess: wait for new VDM task...\n");
346     if (!GetNextVDMCommand(&CommandInfo))
347     {
348         DPRINT1("CmdStartProcess - GetNextVDMCommand failed, retrying... last error = %d\n", GetLastError());
349         if (CommandInfo.EnvLen > EnvSize)
350         {
351             /* Expand the environment size */
352             EnvSize = CommandInfo.EnvLen;
353             CommandInfo.Env = Env = RtlReAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, Env, EnvSize);
354 
355             /* Repeat the request */
356             Repeat = TRUE;
357             goto Command;
358         }
359 
360         /* Shouldn't happen */
361         DisplayMessage(L"An unrecoverable failure happened from start-app BOP; exiting DOS.");
362         setCF(1);
363         goto Quit;
364     }
365 
366     // FIXME: What happens if some other 32-bit app is killed while we are waiting there??
367 
368     DPRINT1("CmdStartProcess - GetNextVDMCommand succeeded, start app...\n");
369 
370 #else
371 
372     if (!First)
373     {
374         DPRINT1("Exit DOS from start-app BOP\n");
375         setCF(1);
376         goto Quit;
377     }
378 
379 #endif
380 
381     /* Compute the command line length, not counting the terminating "\r\n" */
382     CmdLen = strlen(CmdLine);
383     if (CmdLen >= 2 && CmdLine[CmdLen - 2] == '\r')
384         CmdLen -= 2;
385 
386     DPRINT1("Starting '%s' ('%.*s')...\n", AppName, CmdLen, CmdLine);
387 
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);
394 
395 #ifndef STANDALONE
396     /* Update console title if we run in a separate console */
397     if (SessionId != 0)
398         SetConsoleTitleA(AppName);
399 #endif
400 
401     First = FALSE;
402     setCF(0);
403 
404     DPRINT1("App started!\n");
405 
406 Quit:
407     /* Resume the VM */
408     EmulatorResume();
409 }
410 
411 static VOID CmdStartExternalCommand(VOID)
412 {
413     DWORD Result;
414 
415     // TODO: improve: this code has strong similarities
416     // with the 'default' case of DosCreateProcess.
417 
418     LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
419     CHAR CmdLine[sizeof("cmd.exe /c ") + DOS_CMDLINE_LENGTH + 1] = "";
420     LPSTR CmdLinePtr;
421     SIZE_T CmdLineLen;
422 
423     /* Spawn a user-defined 32-bit command preprocessor */
424 
425     // FIXME: Use COMSPEC env var!!
426     CmdLinePtr = CmdLine;
427     strcpy(CmdLinePtr, "cmd.exe /c ");
428     CmdLinePtr += strlen(CmdLinePtr);
429 
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';
434 
435     /* Remove any trailing return carriage character and NULL-terminate the command line */
436     while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
437     *CmdLinePtr = '\0';
438 
439     DPRINT1("CMD Run Command '%s' ('%s')\n", Command, CmdLine);
440 
441     /*
442      * No need to prepare the stack for DosStartComSpec since we won't start it.
443      */
444     Result = DosStartProcess32(Command, CmdLine,
445                                SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
446                                MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
447                                FALSE);
448     if (Result != ERROR_SUCCESS)
449     {
450         DosDisplayMessage("Failed to start command '%s' ('%s'). Error: %u\n", Command, CmdLine, Result);
451         setCF(0);
452         setAL((UCHAR)Result);
453     }
454     else
455     {
456         DosDisplayMessage("Command '%s' ('%s') started successfully.\n", Command, CmdLine);
457 #ifndef STANDALONE
458         setCF(Repeat); // Set CF if we need to start a 16-bit process
459 #else
460         setCF(0);
461 #endif
462     }
463 }
464 
465 static VOID CmdStartComSpec32(VOID)
466 {
467     DWORD Result;
468 
469     // TODO: improve: this code has strong similarities with the
470     // 'default' case of DosCreateProcess and with the 'case 0x08'.
471 
472     CHAR CmdLine[sizeof("cmd.exe") + 1] = "";
473 
474     /* Spawn a user-defined 32-bit command preprocessor */
475 
476     // FIXME: Use COMSPEC env var!!
477     strcpy(CmdLine, "cmd.exe");
478 
479     DPRINT1("CMD Run 32-bit Command Interpreter '%s'\n", CmdLine);
480 
481     /*
482      * No need to prepare the stack for DosStartComSpec since we won't start it.
483      */
484     Result = DosStartProcess32(CmdLine, CmdLine,
485                                SEG_OFF_TO_PTR(getES(), 0) /*Environment*/,
486                                MAKELONG(getIP(), getCS()) /*ReturnAddress*/,
487                                FALSE);
488     if (Result != ERROR_SUCCESS)
489     {
490         DosDisplayMessage("Failed to start 32-bit Command Interpreter '%s'. Error: %u\n", CmdLine, Result);
491         setCF(0);
492         setAL((UCHAR)Result);
493     }
494     else
495     {
496         DosDisplayMessage("32-bit Command Interpreter '%s' started successfully.\n", CmdLine);
497 #ifndef STANDALONE
498         setCF(Repeat); // Set CF if we need to start a 16-bit process
499 #else
500         setCF(0);
501 #endif
502     }
503 }
504 
505 static VOID CmdSetExitCode(VOID)
506 {
507 #ifndef STANDALONE
508     BOOL Success;
509     PCOMSPEC_INFO ComSpecInfo;
510     VDM_COMMAND_INFO CommandInfo;
511 #endif
512 
513     /* Pause the VM */
514     EmulatorPause();
515 
516 #ifndef STANDALONE
517     /*
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.
520      */
521     ComSpecInfo = FindComSpecInfoByPsp(Sda->CurrentPsp);
522     if ((ComSpecInfo && ComSpecInfo->Terminated) ||
523         (ComSpecInfo == &RootCmd && SessionId != 0))
524     {
525         RemoveComSpecInfo(ComSpecInfo);
526 #endif
527         DPRINT1("Exit DOS from ExitCode (prologue)!\n");
528         setCF(0);
529         goto Quit;
530 #ifndef STANDALONE
531     }
532 
533     /* Clear the VDM structure */
534     RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
535 
536 Retry:
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());
544 
545     /*
546      * Check whether we were awaited because the 32-bit process was stopped,
547      * or because it started a new DOS application.
548      */
549     if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
550     {
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);
553 
554         /* Repeat the request */
555         Repeat = TRUE;
556         setCF(1);
557     }
558     else
559     {
560         DPRINT1("GetNextVDMCommand end-of-app, the app stopped\n");
561 
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)
565         {
566             DPRINT1("Not our 32-bit app, retrying...\n");
567             goto Retry;
568         }
569 
570         ASSERT(ComSpecInfo->Terminated == TRUE);
571 
572         /* Record found, remove it and exit now */
573         RemoveComSpecInfo(ComSpecInfo);
574 
575         DPRINT1("Exit DOS from ExitCode wait!\n");
576         setCF(0);
577     }
578 #endif
579 
580     // FIXME: Use the retrieved exit code as the value of our exit code
581     // when COMMAND.COM will shell-out ??
582 
583 Quit:
584     /* Resume the VM */
585     EmulatorResume();
586 }
587 
588 static VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
589 {
590     /* Get the Function Number and skip it */
591     BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
592     setIP(getIP() + 1);
593 
594     switch (FuncNum)
595     {
596         /* Kill the VDM */
597         case 0x00:
598         {
599             /* Stop the VDM */
600             EmulatorTerminate();
601             return;
602         }
603 
604         /*
605          * Get a new app to start
606          *
607          * Input
608          *     DS:DX : Data block.
609          *
610          * Output
611          *     CF    : 0: Success; 1: Failure.
612          */
613         case 0x01:
614         {
615             CmdStartProcess();
616             break;
617         }
618 
619         /*
620          * Check binary format
621          *
622          * Input
623          *     DS:DX : Program to check.
624          *
625          * Output
626          *     CF    : 0: Success; 1: Failure.
627          *     AX    : Error code.
628          */
629         case 0x07:
630         {
631             DWORD BinaryType;
632             LPSTR ProgramName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
633 
634             if (!GetBinaryTypeA(ProgramName, &BinaryType))
635             {
636                 /* An error happened, bail out */
637                 setCF(1);
638                 setAX(LOWORD(GetLastError()));
639                 break;
640             }
641 
642             // FIXME: We only support DOS binaries for now...
643             ASSERT(BinaryType == SCS_DOS_BINARY);
644             if (BinaryType != SCS_DOS_BINARY)
645             {
646                 /* An error happened, bail out */
647                 setCF(1);
648                 setAX(LOWORD(ERROR_BAD_EXE_FORMAT));
649                 break;
650             }
651 
652             /* Return success: DOS application */
653             setCF(0);
654             break;
655         }
656 
657         /*
658          * Start an external command
659          *
660          * Input
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.
666          *
667          * Output
668          *     CF    : 0: Shell-out; 1: Continue.
669          *     AL    : Error/Exit code.
670          */
671         case 0x08:
672         {
673             CmdStartExternalCommand();
674             break;
675         }
676 
677         /*
678          * Start the default 32-bit command interpreter (COMSPEC)
679          *
680          * Input
681          *     ES    : Environment block segment.
682          *     AL    : Current drive number.
683          *
684          * Output
685          *     CF    : 0: Shell-out; 1: Continue.
686          *     AL    : Error/Exit code.
687          */
688         case 0x0A:
689         {
690             CmdStartComSpec32();
691             break;
692         }
693 
694         /*
695          * Set exit code
696          *
697          * Input
698          *     DX    : Exit code
699          *
700          * Output
701          *     CF    : 0: Shell-out; 1: Continue.
702          */
703         case 0x0B:
704         {
705             CmdSetExitCode();
706             break;
707         }
708 
709         /*
710          * Get start information
711          *
712          * Output
713          *     AL    : 0 (resp. 1): Started from (resp. without) an existing console.
714          */
715         case 0x10:
716         {
717 #ifndef STANDALONE
718             /*
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.
723              *
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).
734              *
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.
738              *
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.
744              */
745             // setAL(SessionId != 0);
746             setAL((SessionId != 0) && !Reentry);
747             /* Reset 'Reentry' */
748             Reentry = FALSE;
749 #else
750             setAL(0);
751 #endif
752             break;
753         }
754 
755         default:
756         {
757             DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum);
758             // setCF(1); // Disable, otherwise we enter an infinite loop
759             break;
760         }
761     }
762 }
763 
764 #ifndef COMSPEC_FULLY_EXTERNAL
765 /*
766  * Internal COMMAND.COM binary data in the CommandCom array.
767  */
768 #include "command_com.h"
769 #endif
770 
771 static
772 DWORD DosStartComSpec(IN BOOLEAN Permanent,
773                       IN LPCSTR Environment OPTIONAL,
774                       IN DWORD ReturnAddress OPTIONAL,
775                       OUT PWORD ComSpecPsp OPTIONAL)
776 {
777     /*
778      * BOOLEAN Permanent
779      *   TRUE to simulate the /P switch of command.com: starts AUTOEXEC.BAT/NT
780      *   and makes the interpreter permanent (cannot exit).
781      */
782 
783     DWORD Result;
784 
785     if (ComSpecPsp) *ComSpecPsp = 0;
786 
787     Result =
788 #ifndef COMSPEC_FULLY_EXTERNAL
789     DosLoadExecutableInternal(DOS_LOAD_AND_EXECUTE,
790                               CommandCom,
791                               sizeof(CommandCom),
792                               "COMMAND.COM",
793 #else
794             DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
795 #ifndef STANDALONE  // FIXME: Those values are hardcoded paths on my local test machines!!
796                               "C:\\CMDCMD.COM",
797 #else
798                               "H:\\DOS_tests\\CMDCMD.COM",
799 #endif // STANDALONE
800 #endif // COMSPEC_FULLY_EXTERNAL
801                               NULL,
802                               Permanent ? "/P" : "",
803                               Environment ? Environment : "", // FIXME: Default environment!
804                               ReturnAddress);
805     if (Result != ERROR_SUCCESS) return Result;
806 
807     /* TODO: Read AUTOEXEC.NT/BAT */
808 
809     /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
810     if (ComSpecPsp) *ComSpecPsp = Sda->CurrentPsp;
811 
812     return Result;
813 }
814 
815 typedef struct _DOS_START_PROC32
816 {
817     LPSTR ExecutablePath;
818     LPSTR CommandLine;
819     LPSTR Environment OPTIONAL;
820 #ifndef STANDALONE
821     PCOMSPEC_INFO ComSpecInfo;
822     HANDLE hEvent;
823 #endif
824 } DOS_START_PROC32, *PDOS_START_PROC32;
825 
826 static DWORD
827 WINAPI
828 CommandThreadProc(LPVOID Parameter)
829 {
830     BOOL Success;
831     PROCESS_INFORMATION ProcessInfo;
832     STARTUPINFOA StartupInfo;
833     DWORD dwExitCode;
834     PDOS_START_PROC32 DosStartProc32 = (PDOS_START_PROC32)Parameter;
835 #ifndef STANDALONE
836     VDM_COMMAND_INFO CommandInfo;
837     PCOMSPEC_INFO ComSpecInfo = DosStartProc32->ComSpecInfo;
838 #endif
839 
840     /* Set up the VDM, startup and process info structures */
841 #ifndef STANDALONE
842     RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
843 #endif
844     RtlZeroMemory(&ProcessInfo, sizeof(ProcessInfo));
845     RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
846     StartupInfo.cb = sizeof(StartupInfo);
847 
848     // FIXME: Build suitable 32-bit environment!!
849 
850 #ifndef STANDALONE
851     /*
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.
854      */
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());
859     ++ReentrancyCount;
860 #endif
861 
862     /* Start the process */
863     Success = CreateProcessA(NULL, // ProgramName,
864                              DosStartProc32->CommandLine,
865                              NULL,
866                              NULL,
867                              TRUE, // Inherit handles
868                              CREATE_DEFAULT_ERROR_MODE | CREATE_SUSPENDED,
869                              DosStartProc32->Environment,
870                              NULL, // lpCurrentDirectory, see "START" command in cmd.exe
871                              &StartupInfo,
872                              &ProcessInfo);
873 
874 #ifndef STANDALONE
875     /* Signal our caller the process was started */
876     SetEvent(DosStartProc32->hEvent);
877     // After this point, 'DosStartProc32' is not valid anymore.
878 #endif
879 
880     if (Success)
881     {
882         /* Resume the process */
883         ResumeThread(ProcessInfo.hThread);
884 
885         /* Wait for the process to finish running and retrieve its exit code */
886         WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
887         GetExitCodeProcess(ProcessInfo.hProcess, &dwExitCode);
888 
889         /* Close the handles */
890         CloseHandle(ProcessInfo.hThread);
891         CloseHandle(ProcessInfo.hProcess);
892     }
893     else
894     {
895         dwExitCode = GetLastError();
896     }
897 
898 #ifndef STANDALONE
899     ASSERT(ComSpecInfo);
900     ComSpecInfo->Terminated = TRUE;
901     ComSpecInfo->dwExitCode = dwExitCode;
902 
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());
908     --ReentrancyCount;
909 
910     return 0;
911 #else
912     return dwExitCode;
913 #endif
914 }
915 
916 DWORD DosStartProcess32(IN LPCSTR ExecutablePath,
917                         IN LPCSTR CommandLine,
918                         IN LPCSTR Environment OPTIONAL,
919                         IN DWORD ReturnAddress OPTIONAL,
920                         IN BOOLEAN StartComSpec)
921 {
922     DWORD Result = ERROR_SUCCESS;
923     HANDLE CommandThread;
924     DOS_START_PROC32 DosStartProc32;
925 #ifndef STANDALONE
926     BOOL Success;
927     VDM_COMMAND_INFO CommandInfo;
928 #endif
929 
930     DosStartProc32.ExecutablePath = (LPSTR)ExecutablePath;
931     DosStartProc32.CommandLine    = (LPSTR)CommandLine;
932     DosStartProc32.Environment    = (LPSTR)Environment;
933 
934 #ifndef STANDALONE
935     DosStartProc32.ComSpecInfo =
936         RtlAllocateHeap(RtlGetProcessHeap(),
937                         HEAP_ZERO_MEMORY,
938                         sizeof(*DosStartProc32.ComSpecInfo));
939     ASSERT(DosStartProc32.ComSpecInfo);
940 
941     DosStartProc32.hEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
942     ASSERT(DosStartProc32.hEvent);
943 #endif
944 
945     /* Pause the VM and detach from the console */
946     EmulatorPause();
947     DosProcessConsoleDetach();
948 
949     /* Start the 32-bit process via another thread */
950     CommandThread = CreateThread(NULL, 0, &CommandThreadProc, &DosStartProc32, 0, NULL);
951     if (CommandThread == NULL)
952     {
953         DisplayMessage(L"FATAL: Failed to create the command processing thread: %d", GetLastError());
954         Result = GetLastError();
955         goto Quit;
956     }
957 
958 #ifndef STANDALONE
959     /* Close the thread handle */
960     CloseHandle(CommandThread);
961 
962     /* Wait for the process to be ready to start */
963     WaitForSingleObject(DosStartProc32.hEvent, INFINITE);
964 
965     /* Wait for any potential new DOS app started by the 32-bit process */
966     RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
967 
968 Retry:
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());
973 
974     /*
975      * Check whether we were awaited because the 32-bit process was stopped,
976      * or because it started a new DOS application.
977      */
978     if (CommandInfo.CmdLen != 0 || CommandInfo.AppLen != 0 || CommandInfo.PifLen != 0)
979     {
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);
982 
983         /* Repeat the request */
984         Repeat = TRUE;
985 
986         /*
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.
991          */
992         Reentry = StartComSpec;
993 
994         /* If needed, start a new command interpreter to handle the possible incoming DOS commands */
995         if (StartComSpec)
996         {
997             //
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).
1001             //
1002             Result = DosStartComSpec(FALSE, Environment, ReturnAddress,
1003                                      &DosStartProc32.ComSpecInfo->ComSpecPsp);
1004             if (Result != ERROR_SUCCESS)
1005             {
1006                 DosDisplayMessage("Failed to start a new Command Interpreter (Error: %u).\n", Result);
1007                 goto Quit;
1008             }
1009         }
1010         else
1011         {
1012             /* Retrieve the PSP of the COMSPEC (current PSP set by DosLoadExecutable) */
1013             DosStartProc32.ComSpecInfo->ComSpecPsp = Sda->CurrentPsp;
1014             Result = ERROR_SUCCESS;
1015         }
1016 
1017         /* Insert the new entry in the list; it will be freed when needed by COMMAND.COM */
1018         InsertComSpecInfo(DosStartProc32.ComSpecInfo);
1019     }
1020     else
1021     {
1022         DPRINT1("GetNextVDMCommand 32bit, 32-bit app stopped\n");
1023 
1024         /* Check whether this was our 32-bit app which was killed */
1025         if (!DosStartProc32.ComSpecInfo->Terminated)
1026         {
1027             DPRINT1("Not our 32-bit app, retrying...\n");
1028             goto Retry;
1029         }
1030 
1031         Result = DosStartProc32.ComSpecInfo->dwExitCode;
1032 
1033         /* Delete the entry */
1034         RtlFreeHeap(RtlGetProcessHeap(), 0, DosStartProc32.ComSpecInfo);
1035     }
1036 #else
1037     /* Wait for the thread to finish */
1038     WaitForSingleObject(CommandThread, INFINITE);
1039     GetExitCodeThread(CommandThread, &Result);
1040 
1041     /* Close the thread handle */
1042     CloseHandle(CommandThread);
1043 
1044     DPRINT1("32-bit app stopped\n");
1045 #endif
1046 
1047 Quit:
1048 #ifndef STANDALONE
1049     CloseHandle(DosStartProc32.hEvent);
1050 #endif
1051 
1052     /* Attach to the console and resume the VM */
1053     DosProcessConsoleAttach();
1054     EmulatorResume();
1055 
1056     return Result;
1057 }
1058 
1059 
1060 
1061 
1062 /******************************************************************************\
1063 |**              DOS Bootloader emulation, Startup and Shutdown              **|
1064 \******************************************************************************/
1065 
1066 
1067 //
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.
1072 //
1073 
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[] =
1077 {
1078     LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_LOAD_DOS
1079 };
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[] =
1083 {
1084     LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
1085 };
1086 
1087 static VOID WINAPI DosInitialize(LPWORD Stack);
1088 
1089 VOID DosBootsectorInitialize(VOID)
1090 {
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
1095 
1096     DPRINT("DosBootsectorInitialize\n");
1097 
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);
1105 
1106     /* Initialize the callback context */
1107     InitializeContext(&DosContext, 0x0000,
1108                       (ULONG_PTR)MEM_ALIGN_UP(0x7C00 + Address - StartAddress, sizeof(WORD)));
1109 
1110     /* Register the DOS Loading BOP */
1111     RegisterBop(BOP_LOAD_DOS, DosInitialize);
1112 }
1113 
1114 
1115 //
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.
1118 //
1119 
1120 /* 16-bit startup code for DOS32 at 0070:0000 */
1121 static BYTE Startup[] =
1122 {
1123     LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_START_DOS,
1124     LOBYTE(EMULATOR_BOP), HIBYTE(EMULATOR_BOP), BOP_UNSIMULATE
1125 };
1126 
1127 static VOID WINAPI DosStart(LPWORD Stack);
1128 
1129 static VOID WINAPI DosInitialize(LPWORD Stack)
1130 {
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
1135 
1136     DPRINT("DosInitialize('%s')\n", DosBiosFileName);
1137 
1138     /*
1139      * We succeeded, deregister the DOS Loading BOP
1140      * so that no app will be able to call us back.
1141      */
1142     RegisterBop(BOP_LOAD_DOS, NULL);
1143 
1144     /* Register the DOS BOPs */
1145     RegisterBop(BOP_DOS, DosSystemBop        );
1146     RegisterBop(BOP_CMD, DosCmdInterpreterBop);
1147 
1148     if (DosBiosFileName[0] != '\0')
1149     {
1150         BOOLEAN Success = FALSE;
1151         HANDLE  hDosBios;
1152         ULONG   ulDosBiosSize = 0;
1153 
1154         /* Open the DOS BIOS file */
1155         hDosBios = FileOpen(DosBiosFileName, &ulDosBiosSize);
1156         if (hDosBios == NULL) goto Quit;
1157 
1158         /* Attempt to load the DOS BIOS into memory */
1159         Success = FileLoadByHandle(hDosBios,
1160                                    REAL_TO_PHYS(TO_LINEAR(0x0070, 0x0000)),
1161                                    ulDosBiosSize,
1162                                    &ulDosBiosSize);
1163 
1164         DPRINT1("DOS BIOS file '%s' loading %s at %04X:%04X, size 0x%X (Error: %u).\n",
1165                 DosBiosFileName,
1166                 (Success ? "succeeded" : "failed"),
1167                 0x0070, 0x0000,
1168                 ulDosBiosSize,
1169                 GetLastError());
1170 
1171         /* Close the DOS BIOS file */
1172         FileClose(hDosBios);
1173 
1174 Quit:
1175         if (!Success)
1176         {
1177             BiosDisplayMessage("DOS BIOS file '%s' loading failed (Error: %u). The VDM will shut down.\n",
1178                                DosBiosFileName, GetLastError());
1179             return;
1180         }
1181     }
1182     else
1183     {
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));
1186 
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);
1190     }
1191 
1192     /* Position execution pointers for DOS startup and return */
1193     setCS(0x0070);
1194     setIP(0x0000);
1195 }
1196 
1197 static VOID WINAPI DosStart(LPWORD Stack)
1198 {
1199     BOOLEAN Success;
1200     DWORD Result;
1201 #ifndef STANDALONE
1202     INT i;
1203 #endif
1204 
1205     DPRINT("DosStart\n");
1206 
1207     /*
1208      * We succeeded, deregister the DOS Starting BOP
1209      * so that no app will be able to call us back.
1210      */
1211     RegisterBop(BOP_START_DOS, NULL);
1212 
1213     /* Initialize the callback context */
1214     InitializeContext(&DosContext, BIOS_CODE_SEGMENT, 0x0010);
1215 
1216     Success  = DosBIOSInitialize();
1217 //  Success &= DosKRNLInitialize();
1218     if (!Success)
1219     {
1220         BiosDisplayMessage("DOS32 loading failed (Error: %u). The VDM will shut down.\n", GetLastError());
1221         EmulatorTerminate();
1222         return;
1223     }
1224 
1225     /* Load the mouse driver */
1226     DosMouseInitialize();
1227 
1228 #ifndef STANDALONE
1229 
1230     /* Parse the command line arguments */
1231     for (i = 1; i < NtVdmArgc; i++)
1232     {
1233         if (wcsncmp(NtVdmArgv[i], L"-i", 2) == 0)
1234         {
1235             /* This is the session ID (hex format) */
1236             SessionId = wcstoul(NtVdmArgv[i] + 2, NULL, 16);
1237         }
1238     }
1239 
1240     /* Initialize Win32-VDM environment */
1241     Env = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, EnvSize);
1242     if (Env == NULL)
1243     {
1244         DosDisplayMessage("Failed to initialize the global environment (Error: %u). The VDM will shut down.\n", GetLastError());
1245         EmulatorTerminate();
1246         return;
1247     }
1248 
1249     /* Clear the structure */
1250     RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
1251 
1252     /* Get the initial information */
1253     CommandInfo.TaskId = SessionId;
1254     CommandInfo.VDMState = VDM_GET_FIRST_COMMAND | VDM_FLAG_DOS;
1255     GetNextVDMCommand(&CommandInfo);
1256 
1257 #else
1258 
1259     /* Retrieve the command to start */
1260     if (NtVdmArgc >= 2)
1261     {
1262         WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[1], -1, AppName, sizeof(AppName), NULL, NULL);
1263 
1264         if (NtVdmArgc >= 3)
1265             WideCharToMultiByte(CP_ACP, 0, NtVdmArgv[2], -1, CmdLine, sizeof(CmdLine), NULL, NULL);
1266         else
1267             strcpy(CmdLine, "");
1268     }
1269     else
1270     {
1271         DosDisplayMessage("Invalid DOS command line\n");
1272         EmulatorTerminate();
1273         return;
1274     }
1275 
1276 #endif
1277 
1278     /*
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.
1282      */
1283 
1284     /* Start the root command interpreter */
1285     // TODO: Really interpret the 'SHELL=' line of CONFIG.NT, and use it!
1286 
1287     /*
1288      * Prepare the stack for DosStartComSpec:
1289      * push Flags, CS and IP, and an extra WORD.
1290      */
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));
1298 
1299     Result = DosStartComSpec(TRUE, SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0),
1300                              MAKELONG(getIP(), getCS()),
1301 #ifndef STANDALONE
1302                              &RootCmd.ComSpecPsp
1303 #else
1304                              NULL
1305 #endif
1306                              );
1307     if (Result != ERROR_SUCCESS)
1308     {
1309         /* Unprepare the stack for DosStartComSpec */
1310         setSP(getSP() + 4*sizeof(WORD));
1311 
1312         DosDisplayMessage("Failed to start the Command Interpreter (Error: %u). The VDM will shut down.\n", Result);
1313         EmulatorTerminate();
1314         return;
1315     }
1316 
1317 #ifndef STANDALONE
1318     RootCmd.Terminated = FALSE;
1319     InsertComSpecInfo(&RootCmd);
1320 #endif
1321 
1322     /**/
1323     /* Attach to the console and resume the VM */
1324     DosProcessConsoleAttach();
1325     EmulatorResume();
1326     /**/
1327 
1328     return;
1329 }
1330 
1331 BOOLEAN DosShutdown(BOOLEAN Immediate)
1332 {
1333     /*
1334      * Immediate = TRUE:  Immediate shutdown;
1335      *             FALSE: Delayed shutdown (notification).
1336      */
1337 
1338 #ifndef STANDALONE
1339     if (Immediate)
1340     {
1341         ExitVDM(FALSE, 0);
1342         return TRUE;
1343     }
1344     else
1345     {
1346 // HACK!
1347 extern HANDLE VdmTaskEvent; // see emulator.c
1348 
1349         /*
1350          * Signal the root COMMAND.COM that it should terminate
1351          * when it checks for a new command.
1352          */
1353         RootCmd.Terminated = TRUE;
1354 
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)
1363         {
1364             /* Nothing runs, so exit immediately */
1365             ExitVDM(FALSE, 0);
1366             return TRUE;
1367         }
1368 
1369         return FALSE;
1370     }
1371 #else
1372     UNREFERENCED_PARAMETER(Immediate);
1373     return TRUE;
1374 #endif
1375 }
1376 
1377 
1378 /* PUBLIC EXPORTED APIS *******************************************************/
1379 
1380 // demLFNCleanup
1381 // demLFNGetCurrentDirectory
1382 
1383 // demGetFileTimeByHandle_WOW
1384 // demWOWLFNAllocateSearchHandle
1385 // demWOWLFNCloseSearchHandle
1386 // demWOWLFNEntry
1387 // demWOWLFNGetSearchHandle
1388 // demWOWLFNInit
1389 
1390 DWORD
1391 WINAPI
1392 demClientErrorEx(IN HANDLE FileHandle,
1393                  IN CHAR   Unknown,
1394                  IN BOOL   Flag)
1395 {
1396     UNIMPLEMENTED;
1397     return GetLastError();
1398 }
1399 
1400 DWORD
1401 WINAPI
1402 demFileDelete(IN LPCSTR FileName)
1403 {
1404     if (DeleteFileA(FileName)) SetLastError(ERROR_SUCCESS);
1405 
1406     return GetLastError();
1407 }
1408 
1409 DWORD
1410 WINAPI
1411 demFileFindFirst(OUT PVOID  lpFindFileData,
1412                  IN  LPCSTR FileName,
1413                  IN  WORD   AttribMask)
1414 {
1415     BOOLEAN Success = TRUE;
1416     WIN32_FIND_DATAA FindData;
1417     HANDLE SearchHandle;
1418     PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
1419 
1420     /* Start a search */
1421     SearchHandle = FindFirstFileA(FileName, &FindData);
1422     if (SearchHandle == INVALID_HANDLE_VALUE) return GetLastError();
1423 
1424     do
1425     {
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))
1430              & ~AttribMask))
1431         {
1432             break;
1433         }
1434     }
1435     while ((Success = FindNextFileA(SearchHandle, &FindData)));
1436 
1437     /* If we failed at some point, close the search and return an error */
1438     if (!Success)
1439     {
1440         FindClose(SearchHandle);
1441         return GetLastError();
1442     }
1443 
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));
1457     else
1458         GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
1459 
1460     return ERROR_SUCCESS;
1461 }
1462 
1463 DWORD
1464 WINAPI
1465 demFileFindNext(OUT PVOID lpFindFileData)
1466 {
1467     WIN32_FIND_DATAA FindData;
1468     PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)lpFindFileData;
1469 
1470     do
1471     {
1472         /* Continue searching as long as we haven't found a matching file */
1473 
1474         /* If we failed at some point, close the search and return an error */
1475         if (!FindNextFileA(FindFileBlock->SearchHandle, &FindData))
1476         {
1477             FindClose(FindFileBlock->SearchHandle);
1478             return GetLastError();
1479         }
1480     }
1481     while ((FindData.dwFileAttributes & (FILE_ATTRIBUTE_HIDDEN |
1482                                          FILE_ATTRIBUTE_SYSTEM |
1483                                          FILE_ATTRIBUTE_DIRECTORY))
1484            & ~FindFileBlock->AttribMask);
1485 
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));
1496     else
1497         GetShortPathNameA(FindData.cFileName, FindFileBlock->FileName, sizeof(FindFileBlock->FileName));
1498 
1499     return ERROR_SUCCESS;
1500 }
1501 
1502 UCHAR
1503 WINAPI
1504 demGetPhysicalDriveType(IN UCHAR DriveNumber)
1505 {
1506     UNIMPLEMENTED;
1507     return DOSDEVICE_DRIVE_UNKNOWN;
1508 }
1509 
1510 BOOL
1511 WINAPI
1512 demIsShortPathName(IN LPCSTR Path,
1513                    IN BOOL Unknown)
1514 {
1515     UNIMPLEMENTED;
1516     return FALSE;
1517 }
1518 
1519 DWORD
1520 WINAPI
1521 demSetCurrentDirectoryGetDrive(IN  LPCSTR CurrentDirectory,
1522                                OUT PUCHAR DriveNumber)
1523 {
1524     UNIMPLEMENTED;
1525     return ERROR_SUCCESS;
1526 }
1527 
1528 /* EOF */
1529