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