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