xref: /reactos/subsystems/mvdm/ntvdm/dos/dem.c (revision 81d5e650)
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 
Dem_BiosCharPrint(CHAR Character)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 
DosCharPrint(CHAR Character)82 VOID DosCharPrint(CHAR Character)
83 {
84     DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
85 }
86 
87 
DemLoadNTDOSKernel(VOID)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 
DosSystemBop(LPWORD Stack)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
FindComSpecInfoByPsp(WORD Psp)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
InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo)214 InsertComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
215 {
216     InsertHeadList(&ComSpecInfoList, &ComSpecInfo->Entry);
217 }
218 
219 static VOID
RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo)220 RemoveComSpecInfo(PCOMSPEC_INFO ComSpecInfo)
221 {
222     RemoveEntryList(&ComSpecInfo->Entry);
223     if (ComSpecInfo != &RootCmd)
224         RtlFreeHeap(RtlGetProcessHeap(), 0, ComSpecInfo);
225 }
226 #endif
227 
DosProcessConsoleAttach(VOID)228 static VOID DosProcessConsoleAttach(VOID)
229 {
230     /* Attach to the console */
231     ConsoleAttach();
232     VidBiosAttachToConsole();
233 }
234 
DosProcessConsoleDetach(VOID)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 
CmdStartProcess(VOID)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 
CmdStartExternalCommand(VOID)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 
CmdStartComSpec32(VOID)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 
CmdSetExitCode(VOID)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 
DosCmdInterpreterBop(LPWORD Stack)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
DosStartComSpec(IN BOOLEAN Permanent,IN LPCSTR Environment OPTIONAL,IN DWORD ReturnAddress OPTIONAL,OUT PWORD ComSpecPsp OPTIONAL)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
CommandThreadProc(LPVOID Parameter)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 
DosStartProcess32(IN LPCSTR ExecutablePath,IN LPCSTR CommandLine,IN LPCSTR Environment OPTIONAL,IN DWORD ReturnAddress OPTIONAL,IN BOOLEAN StartComSpec)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 
DosBootsectorInitialize(VOID)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 
DosInitialize(LPWORD Stack)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() + (USHORT)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 
DosStart(LPWORD Stack)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 
DosShutdown(BOOLEAN Immediate)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
demClientErrorEx(IN HANDLE FileHandle,IN CHAR Unknown,IN BOOL Flag)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
demFileDelete(IN LPCSTR FileName)1402 demFileDelete(IN LPCSTR FileName)
1403 {
1404     if (DeleteFileA(FileName)) SetLastError(ERROR_SUCCESS);
1405 
1406     return GetLastError();
1407 }
1408 
1409 /**
1410  * @brief   Helper for demFileFindFirst() and demFileFindNext().
1411  * Returns TRUE if a file matches the DOS attributes and has a 8.3 file name.
1412  **/
1413 static BOOLEAN
dempIsFileMatch(_Inout_ PWIN32_FIND_DATAA FindData,_In_ WORD AttribMask,_Out_ PCSTR * ShortName)1414 dempIsFileMatch(
1415     _Inout_ PWIN32_FIND_DATAA FindData,
1416     _In_  WORD   AttribMask,
1417     _Out_ PCSTR* ShortName)
1418 {
1419     /* Convert in place the attributes to DOS format */
1420     FindData->dwFileAttributes = NT_TO_DOS_FA(FindData->dwFileAttributes);
1421 
1422     /* Check the attributes */
1423     if ((FindData->dwFileAttributes & (FA_HIDDEN | FA_SYSTEM | FA_DIRECTORY))
1424         & ~AttribMask)
1425     {
1426         return FALSE;
1427     }
1428 
1429     /* Check whether the file has a 8.3 file name */
1430     if (*FindData->cAlternateFileName)
1431     {
1432         /* Use the available one */
1433         *ShortName = FindData->cAlternateFileName;
1434         return TRUE;
1435     }
1436     else
1437     {
1438         /*
1439          * Verify whether the original long name is actually a valid
1440          * 8.3 file name. Note that we cannot use GetShortPathName()
1441          * since the latter works on full paths, that we do not always have.
1442          */
1443         BOOLEAN IsNameLegal, SpacesInName;
1444         WCHAR FileNameBufferU[_countof(FindData->cFileName) + 1];
1445         UNICODE_STRING FileNameU;
1446         ANSI_STRING FileNameA;
1447 
1448         RtlInitAnsiString(&FileNameA, FindData->cFileName);
1449         RtlInitEmptyUnicodeString(&FileNameU, FileNameBufferU, sizeof(FileNameBufferU));
1450         RtlAnsiStringToUnicodeString(&FileNameU, &FileNameA, FALSE);
1451 
1452         IsNameLegal = RtlIsNameLegalDOS8Dot3(&FileNameU,
1453                                              NULL, // (lpOemName ? &AnsiName : NULL),
1454                                              &SpacesInName);
1455 
1456         if (!IsNameLegal || SpacesInName)
1457         {
1458             /* This is an error situation */
1459             DPRINT1("'%.*s' is %s 8.3 filename %s spaces\n",
1460                     _countof(FindData->cFileName), FindData->cFileName,
1461                     (IsNameLegal ? "a valid" : "an invalid"), (SpacesInName ? "with" : "without"));
1462         }
1463 
1464         if (IsNameLegal && !SpacesInName)
1465         {
1466             /* We can use the original name */
1467             *ShortName = FindData->cFileName;
1468             return TRUE;
1469         }
1470     }
1471 
1472     DPRINT1("No short 8.3 filename available for '%.*s'\n",
1473             _countof(FindData->cFileName), FindData->cFileName);
1474 
1475     return FALSE;
1476 }
1477 
1478 /**
1479  * @name demFileFindFirst
1480  * Implementation of the DOS INT 21h, AH=4Eh "Find First File" function.
1481  *
1482  * Starts enumerating files that match the given file search specification
1483  * and whose attributes are _at most_ those specified by the mask. This means
1484  * in particular that "normal files", i.e. files with no attributes set, are
1485  * always enumerated along those matching the requested attributes.
1486  *
1487  * @param[out] pFindFileData
1488  * Pointer to the DTA (Disk Transfer Area) filled with FindFirst data block.
1489  *
1490  * @param[in]  FileName
1491  * File search specification (may include path and wildcards).
1492  *
1493  * @param[in]  AttribMask
1494  * Mask of file attributes. Includes files with a given attribute bit set
1495  * if the corresponding bit is set to 1 in the mask. Excludes files with a
1496  * given attribute bit set if the corresponding bit is set to 0 in the mask.
1497  * Supported file attributes:
1498  *     FA_NORMAL       0x0000
1499  *     FA_READONLY     0x0001 (ignored)
1500  *     FA_HIDDEN       0x0002
1501  *     FA_SYSTEM       0x0004
1502  *     FA_VOLID        0x0008 (not currently supported)
1503  *     FA_LABEL
1504  *     FA_DIRECTORY    0x0010
1505  *     FA_ARCHIVE      0x0020 (ignored)
1506  *     FA_DEVICE       0x0040 (ignored)
1507  *
1508  * @return
1509  * ERROR_SUCCESS on success (found match), or a last error (match not found).
1510  *
1511  * @see demFileFindNext()
1512  **/
1513 DWORD
1514 WINAPI
demFileFindFirst(_Out_ PVOID pFindFileData,_In_ PCSTR FileName,_In_ WORD AttribMask)1515 demFileFindFirst(
1516     _Out_ PVOID pFindFileData,
1517     _In_  PCSTR FileName,
1518     _In_  WORD  AttribMask)
1519 {
1520     PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)pFindFileData;
1521     HANDLE SearchHandle;
1522     WIN32_FIND_DATAA FindData;
1523     PCSTR ShortName = NULL;
1524 
1525     /* Reset the private block fields */
1526     RtlZeroMemory(FindFileBlock, RTL_SIZEOF_THROUGH_FIELD(DOS_FIND_FILE_BLOCK, SearchHandle));
1527 
1528     // TODO: Handle FA_VOLID for volume label.
1529     if (AttribMask & FA_VOLID)
1530     {
1531         DPRINT1("demFileFindFirst: Volume label attribute is UNIMPLEMENTED!\n");
1532         AttribMask &= ~FA_VOLID; // Remove it for the time being...
1533     }
1534 
1535     /* Filter out the ignored attributes */
1536     AttribMask &= ~(FA_DEVICE | FA_ARCHIVE | FA_READONLY);
1537 
1538     /* Start a search */
1539     SearchHandle = FindFirstFileA(FileName, &FindData);
1540     if (SearchHandle == INVALID_HANDLE_VALUE)
1541         return GetLastError();
1542 
1543     /* Check the attributes and retry as long as we haven't found a matching file */
1544     while (!dempIsFileMatch(&FindData, AttribMask, &ShortName))
1545     {
1546         /* Continue searching. If we fail at some point,
1547          * stop the search and return an error. */
1548         if (!FindNextFileA(SearchHandle, &FindData))
1549         {
1550             FindClose(SearchHandle);
1551             return GetLastError();
1552         }
1553     }
1554 
1555     /* Fill the block */
1556     FindFileBlock->DriveLetter  = DosData->Sda.CurrentDrive + 'A';
1557     strncpy(FindFileBlock->Pattern, FileName, _countof(FindFileBlock->Pattern));
1558     FindFileBlock->AttribMask   = AttribMask;
1559     FindFileBlock->SearchHandle = SearchHandle;
1560     FindFileBlock->Attributes   = LOBYTE(FindData.dwFileAttributes);
1561     FileTimeToDosDateTime(&FindData.ftLastWriteTime,
1562                           &FindFileBlock->FileDate,
1563                           &FindFileBlock->FileTime);
1564     FindFileBlock->FileSize = FindData.nFileSizeHigh ? MAXDWORD
1565                                                      : FindData.nFileSizeLow;
1566 
1567     /* Copy the NULL-terminated short file name */
1568     RtlStringCchCopyA(FindFileBlock->FileName,
1569                       _countof(FindFileBlock->FileName),
1570                       ShortName);
1571 
1572     return ERROR_SUCCESS;
1573 }
1574 
1575 /**
1576  * @name demFileFindNext
1577  * Implementation of the DOS INT 21h, AH=4Fh "Find Next File" function.
1578  *
1579  * Continues enumerating files, with the same file search specification
1580  * and attributes as those given to the first demFileFindFirst() call.
1581  *
1582  * @param[in,out] pFindFileData
1583  * Pointer to the DTA (Disk Transfer Area) filled with FindFirst data block.
1584  *
1585  * @return
1586  * ERROR_SUCCESS on success (found match), or a last error (match not found).
1587  *
1588  * @see demFileFindFirst()
1589  **/
1590 DWORD
1591 WINAPI
demFileFindNext(_Inout_ PVOID pFindFileData)1592 demFileFindNext(
1593     _Inout_ PVOID pFindFileData)
1594 {
1595     PDOS_FIND_FILE_BLOCK FindFileBlock = (PDOS_FIND_FILE_BLOCK)pFindFileData;
1596     HANDLE SearchHandle = FindFileBlock->SearchHandle;
1597     WIN32_FIND_DATAA FindData;
1598     PCSTR ShortName = NULL;
1599 
1600     do
1601     {
1602         /* Continue searching. If we fail at some point,
1603          * stop the search and return an error. */
1604         if (!FindNextFileA(SearchHandle, &FindData))
1605         {
1606             FindClose(SearchHandle);
1607 
1608             /* Reset the private block fields */
1609             RtlZeroMemory(FindFileBlock, RTL_SIZEOF_THROUGH_FIELD(DOS_FIND_FILE_BLOCK, SearchHandle));
1610             return GetLastError();
1611         }
1612     }
1613     /* Check the attributes and retry as long as we haven't found a matching file */
1614     while (!dempIsFileMatch(&FindData, FindFileBlock->AttribMask, &ShortName));
1615 
1616     /* Update the block */
1617     FindFileBlock->Attributes = LOBYTE(FindData.dwFileAttributes);
1618     FileTimeToDosDateTime(&FindData.ftLastWriteTime,
1619                           &FindFileBlock->FileDate,
1620                           &FindFileBlock->FileTime);
1621     FindFileBlock->FileSize = FindData.nFileSizeHigh ? MAXDWORD
1622                                                      : FindData.nFileSizeLow;
1623 
1624     /* Copy the NULL-terminated short file name */
1625     RtlStringCchCopyA(FindFileBlock->FileName,
1626                       _countof(FindFileBlock->FileName),
1627                       ShortName);
1628 
1629     return ERROR_SUCCESS;
1630 }
1631 
1632 UCHAR
1633 WINAPI
demGetPhysicalDriveType(IN UCHAR DriveNumber)1634 demGetPhysicalDriveType(IN UCHAR DriveNumber)
1635 {
1636     UNIMPLEMENTED;
1637     return DOSDEVICE_DRIVE_UNKNOWN;
1638 }
1639 
1640 BOOL
1641 WINAPI
demIsShortPathName(IN LPCSTR Path,IN BOOL Unknown)1642 demIsShortPathName(IN LPCSTR Path,
1643                    IN BOOL Unknown)
1644 {
1645     UNIMPLEMENTED;
1646     return FALSE;
1647 }
1648 
1649 DWORD
1650 WINAPI
demSetCurrentDirectoryGetDrive(IN LPCSTR CurrentDirectory,OUT PUCHAR DriveNumber)1651 demSetCurrentDirectoryGetDrive(IN  LPCSTR CurrentDirectory,
1652                                OUT PUCHAR DriveNumber)
1653 {
1654     UNIMPLEMENTED;
1655     return ERROR_SUCCESS;
1656 }
1657 
1658 /* EOF */
1659