1 /*
2  * COPYRIGHT:       GPL - See COPYING in the top level directory
3  * PROJECT:         ReactOS Virtual DOS Machine
4  * FILE:            subsystems/mvdm/ntvdm/dos/dos32krnl/process.c
5  * PURPOSE:         DOS32 Processes
6  * PROGRAMMERS:     Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7  *                  Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8  */
9 
10 /* INCLUDES *******************************************************************/
11 
12 #include "ntvdm.h"
13 
14 #define NDEBUG
15 #include <debug.h>
16 
17 #include "emulator.h"
18 #include "cpu/cpu.h"
19 
20 #include "dos.h"
21 #include "dos/dem.h"
22 #include "dosfiles.h"
23 #include "handle.h"
24 #include "process.h"
25 #include "memory.h"
26 
27 #include "bios/bios.h"
28 
29 #include "io.h"
30 #include "hardware/ps2.h"
31 
32 #include "vddsup.h"
33 
34 /* PRIVATE FUNCTIONS **********************************************************/
35 
36 static VOID DosInitPsp(IN WORD Segment,
37                        IN WORD EnvBlock,
38                        IN LPCSTR CommandLine,
39                        IN LPCSTR ProgramName)
40 {
41     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Segment);
42     PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
43     LPCSTR PspName;
44     USHORT i;
45 
46     /* Link the environment block */
47     PspBlock->EnvBlock = EnvBlock;
48 
49     /*
50      * Copy the command line.
51      * Format of the CommandLine parameter: 1 byte for size; 127 bytes for contents.
52      */
53     PspBlock->CommandLineSize = min(*(PBYTE)CommandLine, DOS_CMDLINE_LENGTH);
54     CommandLine++;
55     RtlCopyMemory(PspBlock->CommandLine, CommandLine, DOS_CMDLINE_LENGTH);
56 
57     /*
58      * Initialize the owner name of the MCB of the PSP.
59      */
60 
61     /* Find the start of the file name, skipping all the path elements */
62     PspName = ProgramName;
63     while (*ProgramName)
64     {
65         switch (*ProgramName++)
66         {
67             /* Path delimiter, skip it */
68             case ':': case '\\': case '/':
69                 PspName = ProgramName;
70                 break;
71         }
72     }
73     /* Copy the file name up to the extension... */
74     for (i = 0; i < sizeof(Mcb->Name) && PspName[i] != '.' && PspName[i] != '\0'; ++i)
75     {
76         Mcb->Name[i] = RtlUpperChar(PspName[i]);
77     }
78     /* ... and NULL-terminate if needed */
79     if (i < sizeof(Mcb->Name)) Mcb->Name[i] = '\0';
80 
81     // FIXME: Initialize the FCBs
82 }
83 
84 static inline VOID DosSaveState(VOID)
85 {
86     PDOS_REGISTER_STATE State;
87     WORD StackPointer = getSP();
88 
89     DPRINT1("\n"
90             "DosSaveState(before) -- SS:SP == %04X:%04X\n"
91             "Original CPU State =\n"
92             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
93             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
94             "\n",
95             getSS(), getSP(),
96             getDS(), getES(), getAX(), getCX(),
97             getDX(), getBX(), getBP(), getSI(), getDI());
98 
99     /*
100      * Allocate stack space for the registers. Note that we
101      * already have one word allocated (the interrupt number).
102      */
103     StackPointer -= sizeof(DOS_REGISTER_STATE) - sizeof(WORD);
104     State = SEG_OFF_TO_PTR(getSS(), StackPointer);
105     setSP(StackPointer);
106 
107     /* Save */
108     State->DS = getDS();
109     State->ES = getES();
110     State->AX = getAX();
111     State->CX = getCX();
112     State->DX = getDX();
113     State->BX = getBX();
114     State->BP = getBP();
115     State->SI = getSI();
116     State->DI = getDI();
117 
118     DPRINT1("\n"
119             "DosSaveState(after) -- SS:SP == %04X:%04X\n"
120             "Saved State =\n"
121             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
122             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
123             "\n",
124             getSS(), getSP(),
125             State->DS, State->ES, State->AX, State->CX,
126             State->DX, State->BX, State->BP, State->SI, State->DI);
127 }
128 
129 static inline VOID DosRestoreState(VOID)
130 {
131     PDOS_REGISTER_STATE State;
132 
133     /*
134      * Pop the state structure from the stack. Note that we
135      * already have one word allocated (the interrupt number).
136      */
137     State = SEG_OFF_TO_PTR(getSS(), getSP());
138 
139     DPRINT1("\n"
140             "DosRestoreState(before) -- SS:SP == %04X:%04X\n"
141             "Saved State =\n"
142             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
143             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
144             "\n",
145             getSS(), getSP(),
146             State->DS, State->ES, State->AX, State->CX,
147             State->DX, State->BX, State->BP, State->SI, State->DI);
148 
149     setSP(getSP() + sizeof(DOS_REGISTER_STATE) - sizeof(WORD));
150 
151     /* Restore */
152     setDS(State->DS);
153     setES(State->ES);
154     setAX(State->AX);
155     setCX(State->CX);
156     setDX(State->DX);
157     setBX(State->BX);
158     setBP(State->BP);
159     setSI(State->SI);
160     setDI(State->DI);
161 
162     DPRINT1("\n"
163             "DosRestoreState(after) -- SS:SP == %04X:%04X\n"
164             "Restored CPU State =\n"
165             "DS = %04X; ES = %04X; AX = %04X; CX = %04X\n"
166             "DX = %04X; BX = %04X; BP = %04X; SI = %04X; DI = %04X"
167             "\n",
168             getSS(), getSP(),
169             getDS(), getES(), getAX(), getCX(),
170             getDX(), getBX(), getBP(), getSI(), getDI());
171 }
172 
173 static WORD DosCopyEnvironmentBlock(IN LPCSTR Environment OPTIONAL,
174                                     IN LPCSTR ProgramName)
175 {
176     PCHAR Ptr, DestBuffer = NULL;
177     SIZE_T TotalSize = 0;
178     WORD DestSegment;
179 
180     /* If we have an environment strings list, compute its size */
181     if (Environment)
182     {
183         /* Calculate the size of the environment block */
184         Ptr = (PCHAR)Environment;
185         while (*Ptr) Ptr += strlen(Ptr) + 1;
186         TotalSize = (ULONG_PTR)Ptr - (ULONG_PTR)Environment;
187     }
188     else
189     {
190         /* Empty environment string */
191         TotalSize = 1;
192     }
193     /* Add the final environment block NULL-terminator */
194     TotalSize++;
195 
196     /* Add the two bytes for the program name tag */
197     TotalSize += 2;
198 
199     /* Add the string buffer size */
200     TotalSize += strlen(ProgramName) + 1;
201 
202     /* Allocate the memory for the environment block */
203     DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
204     if (!DestSegment) return 0;
205 
206     DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0);
207 
208     /* If we have an environment strings list, copy it */
209     if (Environment)
210     {
211         Ptr = (PCHAR)Environment;
212         while (*Ptr)
213         {
214             /* Copy the string and NULL-terminate it */
215             strcpy(DestBuffer, Ptr);
216             DestBuffer += strlen(Ptr);
217             *(DestBuffer++) = '\0';
218 
219             /* Move to the next string */
220             Ptr += strlen(Ptr) + 1;
221         }
222     }
223     else
224     {
225         /* Empty environment string */
226         *(DestBuffer++) = '\0';
227     }
228     /* NULL-terminate the environment block */
229     *(DestBuffer++) = '\0';
230 
231     /* Store the special program name tag */
232     *(DestBuffer++) = LOBYTE(DOS_PROGRAM_NAME_TAG);
233     *(DestBuffer++) = HIBYTE(DOS_PROGRAM_NAME_TAG);
234 
235     /* Copy the program name after the environment block */
236     strcpy(DestBuffer, ProgramName);
237 
238     return DestSegment;
239 }
240 
241 /* PUBLIC FUNCTIONS ***********************************************************/
242 
243 VOID DosClonePsp(WORD DestSegment, WORD SourceSegment)
244 {
245     PDOS_PSP DestPsp    = SEGMENT_TO_PSP(DestSegment);
246     PDOS_PSP SourcePsp  = SEGMENT_TO_PSP(SourceSegment);
247     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
248 
249     /* Literally copy the PSP first */
250     RtlCopyMemory(DestPsp, SourcePsp, sizeof(*DestPsp));
251 
252     /* Save the interrupt vectors */
253     DestPsp->TerminateAddress = IntVecTable[0x22];
254     DestPsp->BreakAddress     = IntVecTable[0x23];
255     DestPsp->CriticalAddress  = IntVecTable[0x24];
256 
257     /* No parent PSP */
258     DestPsp->ParentPsp = 0;
259 
260     /* Set the handle table pointers to the internal handle table */
261     DestPsp->HandleTableSize = DEFAULT_JFT_SIZE;
262     DestPsp->HandleTablePtr  = MAKELONG(0x18, DestSegment);
263 
264     /* Copy the parent handle table without referencing the SFT */
265     RtlCopyMemory(FAR_POINTER(DestPsp->HandleTablePtr),
266                   FAR_POINTER(SourcePsp->HandleTablePtr),
267                   DEFAULT_JFT_SIZE);
268 }
269 
270 VOID DosCreatePsp(WORD Segment, WORD ProgramSize)
271 {
272     PDOS_PSP PspBlock   = SEGMENT_TO_PSP(Segment);
273     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
274 
275     RtlZeroMemory(PspBlock, sizeof(*PspBlock));
276 
277     /* Set the exit interrupt */
278     PspBlock->Exit[0] = 0xCD; // int 0x20
279     PspBlock->Exit[1] = 0x20;
280 
281     /* Set the number of the last paragraph */
282     PspBlock->LastParagraph = Segment + ProgramSize;
283 
284     /* Save the interrupt vectors */
285     PspBlock->TerminateAddress = IntVecTable[0x22];
286     PspBlock->BreakAddress     = IntVecTable[0x23];
287     PspBlock->CriticalAddress  = IntVecTable[0x24];
288 
289     /* Set the parent PSP */
290     PspBlock->ParentPsp = Sda->CurrentPsp;
291 
292     if (Sda->CurrentPsp != SYSTEM_PSP)
293     {
294         /* Link to the parent's environment block */
295         PspBlock->EnvBlock = SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock;
296     }
297 /*
298     else
299     {
300         PspBlock->EnvBlock = SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0);
301     }
302 */
303 
304     /* Copy the parent handle table */
305     DosCopyHandleTable(PspBlock->HandleTable);
306 
307     /* Set the handle table pointers to the internal handle table */
308     PspBlock->HandleTableSize = DEFAULT_JFT_SIZE;
309     PspBlock->HandleTablePtr  = MAKELONG(0x18, Segment);
310 
311     /* Set the DOS version */
312     // FIXME: This is here that SETVER stuff enters into action!
313     PspBlock->DosVersion = DosData->DosVersion;
314 
315     /* Set the far call opcodes */
316     PspBlock->FarCall[0] = 0xCD; // int 0x21
317     PspBlock->FarCall[1] = 0x21;
318     PspBlock->FarCall[2] = 0xCB; // retf
319 }
320 
321 VOID DosSetProcessContext(WORD Segment)
322 {
323     Sda->CurrentPsp = Segment;
324     Sda->DiskTransferArea = MAKELONG(0x80, Segment);
325 }
326 
327 DWORD DosLoadExecutableInternal(IN DOS_EXEC_TYPE LoadType,
328                                 IN LPBYTE ExeBuffer,
329                                 IN DWORD ExeBufferSize,
330                                 IN LPCSTR ExePath,
331                                 IN PDOS_EXEC_PARAM_BLOCK Parameters,
332                                 IN LPCSTR CommandLine OPTIONAL,
333                                 IN LPCSTR Environment OPTIONAL,
334                                 IN DWORD ReturnAddress OPTIONAL)
335 {
336     DWORD Result = ERROR_SUCCESS;
337     WORD Segment = 0;
338     WORD EnvBlock = 0;
339     WORD ExeSignature;
340     WORD LoadSegment;
341     WORD MaxAllocSize;
342 
343     WORD FinalSS, FinalSP;
344     WORD FinalCS, FinalIP;
345 
346     /* Buffer for command line conversion: 1 byte for size; 127 bytes for contents */
347     CHAR CmdLineBuffer[1 + DOS_CMDLINE_LENGTH];
348 
349     DPRINT1("DosLoadExecutableInternal(%d, 0x%p, '%s', 0x%p, 0x%p, 0x%p)\n",
350             LoadType, ExeBuffer, ExePath, Parameters, CommandLine, Environment);
351 
352     if (LoadType != DOS_LOAD_OVERLAY)
353     {
354         /* If an optional Win32 command line is given... */
355         if (CommandLine)
356         {
357             /* ... convert it into DOS format */
358             BYTE CmdLineLen;
359 
360             PBYTE CmdLineSize  = (PBYTE)CmdLineBuffer;
361             LPSTR CmdLineStart = CmdLineBuffer + 1;
362             LPSTR CmdLinePtr   = CmdLineStart;
363 
364             // For debugging purposes
365             RtlFillMemory(CmdLineBuffer, sizeof(CmdLineBuffer), 0xFF);
366 
367             /*
368              * Set the command line: it is either an empty command line or has
369              * the format: " foo bar ..." (with at least one leading whitespace),
370              * and is then always followed by '\r' (and optionally by '\n').
371              */
372             CmdLineLen = (BYTE)strlen(CommandLine);
373             *CmdLineSize = 0;
374 
375             /*
376              * Add the leading space if the command line is not empty
377              * and doesn't already start with some whitespace...
378              */
379             if (*CommandLine && *CommandLine != '\r' && *CommandLine != '\n' &&
380                 *CommandLine != ' ' && *CommandLine != '\t')
381             {
382                 (*CmdLineSize)++;
383                 *CmdLinePtr++ = ' ';
384             }
385 
386             /* Compute the number of characters we need to copy from the original command line */
387             CmdLineLen = min(CmdLineLen, DOS_CMDLINE_LENGTH - *CmdLineSize);
388 
389             /* The trailing '\r' or '\n' do not count in the PSP command line size parameter */
390             while (CmdLineLen && (CommandLine[CmdLineLen - 1] == '\r' || CommandLine[CmdLineLen - 1] == '\n'))
391             {
392                 CmdLineLen--;
393             }
394 
395             /* Finally, set everything up */
396             *CmdLineSize += CmdLineLen;
397             RtlCopyMemory(CmdLinePtr, CommandLine, CmdLineLen);
398             CmdLineStart[*CmdLineSize] = '\r';
399 
400             /* Finally make the pointer point to the static buffer */
401             CommandLine = CmdLineBuffer;
402         }
403         else
404         {
405             /*
406              * ... otherwise, get the one from the parameter block.
407              * Format of the command line: 1 byte for size; 127 bytes for contents.
408              */
409             ASSERT(Parameters);
410             CommandLine = (LPCSTR)FAR_POINTER(Parameters->CommandLine);
411         }
412 
413         /* If no optional environment is given... */
414         if (Environment == NULL)
415         {
416             ASSERT(Parameters);
417             /* ... get the one from the parameter block (if not NULL)... */
418             if (Parameters->Environment)
419                 Environment = (LPCSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
420             /* ... or the one from the parent (otherwise) */
421             else
422                 Environment = (LPCSTR)SEG_OFF_TO_PTR(SEGMENT_TO_PSP(Sda->CurrentPsp)->EnvBlock, 0);
423         }
424 
425         /* Copy the environment block to DOS memory */
426         EnvBlock = DosCopyEnvironmentBlock(Environment, ExePath);
427         if (EnvBlock == 0)
428         {
429             Result = Sda->LastErrorCode;
430             goto Cleanup;
431         }
432     }
433 
434     /*
435      * Check if this is an EXE file or a COM file by looking
436      * at the MZ signature:
437      * 0x4D5A 'MZ': old signature (stored as 0x5A, 0x4D)
438      * 0x5A4D 'ZM': new signature (stored as 0x4D, 0x5A)
439      */
440     ExeSignature = *(PWORD)ExeBuffer;
441     if (ExeSignature == 'MZ' || ExeSignature == 'ZM')
442     {
443         /* EXE file */
444         PIMAGE_DOS_HEADER Header;
445         DWORD BaseSize;
446         PDWORD RelocationTable;
447         PWORD RelocWord;
448         WORD RelocFactor;
449         WORD i;
450 
451         /* Get the MZ header */
452         Header = (PIMAGE_DOS_HEADER)ExeBuffer;
453 
454         /* Get the base size of the file, in paragraphs (rounded up) */
455 #if 0   // Normally this is not needed to check for the number of bytes in the last pages.
456         BaseSize = ((((Header->e_cp - (Header->e_cblp != 0)) * 512) + Header->e_cblp) >> 4)
457                     - Header->e_cparhdr;
458 #else
459         // e_cp is the number of 512-byte blocks. 512 == (1 << 9)
460         // so this corresponds to (1 << 5) number of paragraphs.
461         //
462         // For DOS compatibility we need to truncate BaseSize to a WORD value.
463         // This fact is exploited by some EXEs which are bigger than 1 Mb while
464         // being able to load on DOS, the main EXE code loads the remaining data.
465 
466         BaseSize = ((Header->e_cp << 5) - Header->e_cparhdr) & 0xFFFF;
467 #endif
468 
469         if (LoadType != DOS_LOAD_OVERLAY)
470         {
471             BOOLEAN LoadHigh = FALSE;
472             DWORD TotalSize;
473 
474             /* Find the maximum amount of memory that can be allocated */
475             DosAllocateMemory(0xFFFF, &MaxAllocSize);
476 
477             /* Compute the total needed size, in paragraphs */
478             TotalSize = BaseSize + (sizeof(DOS_PSP) >> 4);
479 
480             /* We must have the required minimum amount of memory. If not, bail out. */
481             if (MaxAllocSize < TotalSize + Header->e_minalloc)
482             {
483                 Result = ERROR_NOT_ENOUGH_MEMORY;
484                 goto Cleanup;
485             }
486 
487             /* Check if the program should be loaded high */
488             if (Header->e_minalloc == 0 && Header->e_maxalloc == 0)
489             {
490                 /* Yes it should. Use all the available memory. */
491                 LoadHigh  = TRUE;
492                 TotalSize = MaxAllocSize;
493             }
494             else
495             {
496                 /* Compute the maximum memory size that can be allocated */
497                 if (Header->e_maxalloc != 0)
498                     TotalSize = min(TotalSize + Header->e_maxalloc, MaxAllocSize);
499                 else
500                     TotalSize = MaxAllocSize; // Use all the available memory
501             }
502 
503             /* Try to allocate that much memory */
504             Segment = DosAllocateMemory((WORD)TotalSize, NULL);
505             if (Segment == 0)
506             {
507                 Result = Sda->LastErrorCode;
508                 goto Cleanup;
509             }
510 
511             /* The process owns its memory */
512             DosChangeMemoryOwner(Segment , Segment);
513             DosChangeMemoryOwner(EnvBlock, Segment);
514 
515             /* Set INT 22h to the return address */
516             ((PULONG)BaseAddress)[0x22] = ReturnAddress;
517 
518             /* Create the PSP and initialize it */
519             DosCreatePsp(Segment, (WORD)TotalSize);
520             DosInitPsp(Segment, EnvBlock, CommandLine, ExePath);
521 
522             /* Calculate the segment where the program should be loaded */
523             if (!LoadHigh)
524                 LoadSegment = Segment + (sizeof(DOS_PSP) >> 4);
525             else
526                 LoadSegment = Segment + TotalSize - BaseSize;
527 
528             RelocFactor = LoadSegment;
529         }
530         else
531         {
532             ASSERT(Parameters);
533             LoadSegment = Parameters->Overlay.Segment;
534             RelocFactor = Parameters->Overlay.RelocationFactor;
535         }
536 
537         /* Copy the program to the code segment */
538         RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
539                       ExeBuffer + (Header->e_cparhdr << 4),
540                       min(ExeBufferSize - (Header->e_cparhdr << 4), BaseSize << 4));
541 
542         /* Get the relocation table */
543         RelocationTable = (PDWORD)(ExeBuffer + Header->e_lfarlc);
544 
545         /* Perform relocations */
546         for (i = 0; i < Header->e_crlc; i++)
547         {
548             /* Get a pointer to the word that needs to be patched */
549             RelocWord = (PWORD)SEG_OFF_TO_PTR(LoadSegment + HIWORD(RelocationTable[i]),
550                                               LOWORD(RelocationTable[i]));
551 
552             /* Add the relocation factor to it */
553             *RelocWord += RelocFactor;
554         }
555 
556         /* Set the stack to the location from the header */
557         FinalSS = LoadSegment + Header->e_ss;
558         FinalSP = Header->e_sp;
559 
560         /* Set the code segment/pointer */
561         FinalCS = LoadSegment + Header->e_cs;
562         FinalIP = Header->e_ip;
563     }
564     else
565     {
566         /* COM file */
567 
568         if (LoadType != DOS_LOAD_OVERLAY)
569         {
570             /* Find the maximum amount of memory that can be allocated */
571             DosAllocateMemory(0xFFFF, &MaxAllocSize);
572 
573             /* Make sure it's enough for the whole program and the PSP */
574             if (((DWORD)MaxAllocSize << 4) < (ExeBufferSize + sizeof(DOS_PSP)))
575             {
576                 Result = ERROR_NOT_ENOUGH_MEMORY;
577                 goto Cleanup;
578             }
579 
580             /* Allocate all of it */
581             Segment = DosAllocateMemory(MaxAllocSize, NULL);
582             if (Segment == 0)
583             {
584                 Result = Sda->LastErrorCode;
585                 goto Cleanup;
586             }
587 
588             /* The process owns its memory */
589             DosChangeMemoryOwner(Segment , Segment);
590             DosChangeMemoryOwner(EnvBlock, Segment);
591 
592             /* Set INT 22h to the return address */
593             ((PULONG)BaseAddress)[0x22] = ReturnAddress;
594 
595             /* Create the PSP and initialize it */
596             DosCreatePsp(Segment, MaxAllocSize);
597             DosInitPsp(Segment, EnvBlock, CommandLine, ExePath);
598 
599             /* Calculate the segment where the program should be loaded */
600             LoadSegment = Segment + (sizeof(DOS_PSP) >> 4);
601         }
602         else
603         {
604             ASSERT(Parameters);
605             LoadSegment = Parameters->Overlay.Segment;
606         }
607 
608         /* Copy the program to the code segment */
609         RtlCopyMemory(SEG_OFF_TO_PTR(LoadSegment, 0),
610                       ExeBuffer, ExeBufferSize);
611 
612         /* Set the stack to the last word of the segment */
613         FinalSS = Segment;
614         FinalSP = 0xFFFE;
615 
616         /*
617          * Set the value on the stack to 0x0000, so that a near return
618          * jumps to PSP:0000 which has the exit code.
619          */
620         *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0x0000;
621 
622         /* Set the code segment/pointer */
623         FinalCS = Segment;
624         FinalIP = 0x0100;
625     }
626 
627     if (LoadType == DOS_LOAD_AND_EXECUTE)
628     {
629         /* Save the program state */
630         if (Sda->CurrentPsp != SYSTEM_PSP)
631         {
632             /* Push the task state */
633             DosSaveState();
634 
635             DPRINT1("Sda->CurrentPsp = 0x%04x; Old LastStack = 0x%08x, New LastStack = 0x%08x\n",
636                    Sda->CurrentPsp, SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack, MAKELONG(getSP(), getSS()));
637 
638             /* Update the last stack in the PSP */
639             SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack = MAKELONG(getSP(), getSS());
640         }
641 
642         /* Set the initial segment registers */
643         setDS(Segment);
644         setES(Segment);
645 
646         /* Set the stack */
647         setSS(FinalSS);
648         setSP(FinalSP);
649 
650         /*
651          * Set the other registers as in real DOS: some demos expect them so!
652          * See http://www.fysnet.net/yourhelp.htm
653          * and http://www.beroset.com/asm/showregs.asm
654          */
655         setDX(Segment);
656         setDI(FinalSP);
657         setBP(0x091E); // DOS base stack pointer relic value. In MS-DOS 5.0 and Windows' NTVDM it's 0x091C. This is in fact the old SP value inside DosData disk stack.
658         setSI(FinalIP);
659 
660         setAX(0/*0xFFFF*/); // FIXME: fcbcode
661         setBX(0/*0xFFFF*/); // FIXME: fcbcode
662         setCX(0x00FF);
663 
664         /*
665          * Keep critical flags, clear test flags (OF, SF, ZF, AF, PF, CF)
666          * and explicitely set the interrupt flag.
667          */
668         setEFLAGS((getEFLAGS() & ~0x08D5) | 0x0200);
669 
670         /* Notify VDDs of process execution */
671         VDDCreateUserHook(Segment);
672 
673         /* Execute */
674         DosSetProcessContext(Segment);
675         CpuExecute(FinalCS, FinalIP);
676     }
677     else if (LoadType == DOS_LOAD_ONLY)
678     {
679         ASSERT(Parameters);
680         Parameters->StackLocation = MAKELONG(FinalSP, FinalSS);
681         Parameters->EntryPoint    = MAKELONG(FinalIP, FinalCS);
682     }
683 
684 Cleanup:
685     if (Result != ERROR_SUCCESS)
686     {
687         /* It was not successful, cleanup the DOS memory */
688         if (EnvBlock) DosFreeMemory(EnvBlock);
689         if (Segment)  DosFreeMemory(Segment);
690     }
691 
692     return Result;
693 }
694 
695 DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
696                         IN LPCSTR ExecutablePath,
697                         IN PDOS_EXEC_PARAM_BLOCK Parameters,
698                         IN LPCSTR CommandLine OPTIONAL,
699                         IN LPCSTR Environment OPTIONAL,
700                         IN DWORD ReturnAddress OPTIONAL)
701 {
702     DWORD Result = ERROR_SUCCESS;
703     HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
704     DWORD FileSize;
705     LPBYTE Address = NULL;
706     CHAR FullPath[MAX_PATH];
707     CHAR ShortFullPath[MAX_PATH];
708 
709     DPRINT1("DosLoadExecutable(%d, '%s', 0x%p, 0x%p, 0x%p)\n",
710             LoadType, ExecutablePath, Parameters, CommandLine, Environment);
711 
712     /* Try to get the full path to the executable */
713     if (GetFullPathNameA(ExecutablePath, sizeof(FullPath), FullPath, NULL))
714     {
715         /* Get the corresponding short path */
716         if (GetShortPathNameA(FullPath, ShortFullPath, sizeof(ShortFullPath)))
717         {
718             /* Use the shortened full path from now on */
719             ExecutablePath = ShortFullPath;
720         }
721     }
722 
723     /* Open a handle to the executable */
724     FileHandle = CreateFileA(ExecutablePath,
725                              GENERIC_READ,
726                              FILE_SHARE_READ,
727                              NULL,
728                              OPEN_EXISTING,
729                              FILE_ATTRIBUTE_NORMAL,
730                              NULL);
731     if (FileHandle == INVALID_HANDLE_VALUE)
732     {
733         Result = GetLastError();
734         goto Cleanup;
735     }
736 
737     /* Get the file size */
738     FileSize = GetFileSize(FileHandle, NULL);
739 
740     /* Create a mapping object for the file */
741     FileMapping = CreateFileMapping(FileHandle,
742                                     NULL,
743                                     PAGE_READONLY,
744                                     0,
745                                     0,
746                                     NULL);
747     if (FileMapping == NULL)
748     {
749         Result = GetLastError();
750         goto Cleanup;
751     }
752 
753     /* Map the file into memory */
754     Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
755     if (Address == NULL)
756     {
757         Result = GetLastError();
758         goto Cleanup;
759     }
760 
761     Result = DosLoadExecutableInternal(LoadType,
762                                        Address,
763                                        FileSize,
764                                        ExecutablePath,
765                                        Parameters,
766                                        CommandLine,
767                                        Environment,
768                                        ReturnAddress);
769 
770 Cleanup:
771     /* Unmap the file*/
772     if (Address != NULL) UnmapViewOfFile(Address);
773 
774     /* Close the file mapping object */
775     if (FileMapping != NULL) CloseHandle(FileMapping);
776 
777     /* Close the file handle */
778     if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
779 
780     return Result;
781 }
782 
783 WORD DosCreateProcess(IN LPCSTR ProgramName,
784                       IN PDOS_EXEC_PARAM_BLOCK Parameters,
785                       IN DWORD ReturnAddress OPTIONAL)
786 {
787     DWORD Result = ERROR_SUCCESS;
788     DWORD BinaryType;
789 
790     /* Get the binary type */
791     if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
792 
793     /* Check the type of the program */
794     switch (BinaryType)
795     {
796         /* Those are handled by NTVDM */
797         case SCS_WOW_BINARY:
798         {
799             DisplayMessage(L"Trying to load '%S'.\n"
800                            L"WOW16 applications are not supported internally by NTVDM at the moment.\n"
801                            L"Press 'OK' to continue.",
802                            ProgramName);
803             // Fall through
804         }
805         case SCS_DOS_BINARY:
806         {
807             /* Load the executable */
808             Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
809                                        ProgramName,
810                                        Parameters,
811                                        NULL,
812                                        NULL,
813                                        ReturnAddress);
814             if (Result != ERROR_SUCCESS)
815             {
816                 DisplayMessage(L"Could not load '%S'. Error: %u", ProgramName, Result);
817             }
818 
819             break;
820         }
821 
822         /* Not handled by NTVDM */
823         default:
824         {
825             LPSTR Environment = NULL;
826             CHAR CmdLine[MAX_PATH + DOS_CMDLINE_LENGTH + 1];
827             LPSTR CmdLinePtr;
828             ULONG CmdLineSize;
829 
830             /* Did the caller specify an environment segment? */
831             if (Parameters->Environment)
832             {
833                 /* Yes, use it instead of the parent one */
834                 Environment = (LPSTR)SEG_OFF_TO_PTR(Parameters->Environment, 0);
835             }
836 
837             /*
838              * Convert the DOS command line to Win32-compatible format, by concatenating
839              * the program name with the converted command line.
840              * Format of the DOS command line: 1 byte for size; 127 bytes for contents.
841              */
842             CmdLinePtr = CmdLine;
843             strncpy(CmdLinePtr, ProgramName, MAX_PATH); // Concatenate the program name
844             CmdLinePtr += strlen(CmdLinePtr);
845             *CmdLinePtr++ = ' ';                        // Add separating space
846 
847             CmdLineSize = min(*(PBYTE)FAR_POINTER(Parameters->CommandLine), DOS_CMDLINE_LENGTH);
848             RtlCopyMemory(CmdLinePtr,
849                           (LPSTR)FAR_POINTER(Parameters->CommandLine) + 1,
850                           CmdLineSize);
851             /* NULL-terminate it */
852             CmdLinePtr[CmdLineSize] = '\0';
853 
854             /* Remove any trailing return carriage character and NULL-terminate the command line */
855             while (*CmdLinePtr && *CmdLinePtr != '\r' && *CmdLinePtr != '\n') CmdLinePtr++;
856             *CmdLinePtr = '\0';
857 
858             Result = DosStartProcess32(ProgramName, CmdLine,
859                                        Environment, ReturnAddress,
860                                        TRUE);
861             if (Result != ERROR_SUCCESS)
862             {
863                 DisplayMessage(L"Could not load 32-bit '%S'. Error: %u", ProgramName, Result);
864             }
865 
866             break;
867         }
868     }
869 
870     return Result;
871 }
872 
873 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
874 {
875     WORD McbSegment = SysVars->FirstMcb;
876     PDOS_MCB CurrentMcb;
877     LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
878     PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
879     LPWORD Stack;
880     BYTE TerminationType;
881 
882     DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
883            Psp, ReturnCode, KeepResident);
884 
885     /* Notify VDDs of process termination */
886     VDDTerminateUserHook(Psp);
887 
888     /* Check if this PSP is its own parent */
889     if (PspBlock->ParentPsp == Psp) goto Done;
890 
891     if (KeepResident == 0)
892     {
893         WORD i;
894         for (i = 0; i < PspBlock->HandleTableSize; i++)
895         {
896             /* Close the handle */
897             DosCloseHandle(i);
898         }
899     }
900 
901     /* Free the memory used by the process */
902     while (TRUE)
903     {
904         /* Get a pointer to the MCB */
905         CurrentMcb = SEGMENT_TO_MCB(McbSegment);
906 
907         /* Make sure the MCB is valid */
908         if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z') break;
909 
910         /* Check if this block was allocated by the process */
911         if (CurrentMcb->OwnerPsp == Psp)
912         {
913             if (KeepResident)
914             {
915                 /* Check if this is the PSP block and we should reduce its size */
916                 if ((McbSegment + 1) == Psp && KeepResident < CurrentMcb->Size)
917                 {
918                     /* Reduce the size of the block */
919                     DosResizeMemory(McbSegment + 1, KeepResident, NULL);
920                     break;
921                 }
922             }
923             else
924             {
925                 /* Free this entire block */
926                 DosFreeMemory(McbSegment + 1);
927             }
928         }
929 
930         /* If this was the last block, quit */
931         if (CurrentMcb->BlockType == 'Z') break;
932 
933         /* Update the segment and continue */
934         McbSegment += CurrentMcb->Size + 1;
935     }
936 
937 Done:
938     /* Restore the interrupt vectors */
939     IntVecTable[0x22] = PspBlock->TerminateAddress;
940     IntVecTable[0x23] = PspBlock->BreakAddress;
941     IntVecTable[0x24] = PspBlock->CriticalAddress;
942 
943     /* Update the current PSP with the parent's one */
944     if (Psp == Sda->CurrentPsp)
945     {
946         DosSetProcessContext(PspBlock->ParentPsp);
947         if (Sda->CurrentPsp == SYSTEM_PSP)
948         {
949             // NOTE: we can also use the DOS BIOS exit code.
950             CpuUnsimulate();
951             return;
952         }
953     }
954 
955     /* Save the return code - Normal termination or TSR */
956     TerminationType = (KeepResident != 0 ? 0x03 : 0x00);
957     Sda->ErrorLevel = MAKEWORD(ReturnCode, TerminationType);
958 
959     DPRINT1("PspBlock->ParentPsp = 0x%04x; Sda->CurrentPsp = 0x%04x\n",
960            PspBlock->ParentPsp, Sda->CurrentPsp);
961 
962     if (Sda->CurrentPsp != SYSTEM_PSP)
963     {
964         DPRINT1("Sda->CurrentPsp = 0x%04x; Old SS:SP = %04X:%04X going to be LastStack = 0x%08x\n",
965                Sda->CurrentPsp, getSS(), getSP(), SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack);
966 
967         /* Restore the parent's stack */
968         setSS(HIWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
969         setSP(LOWORD(SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack));
970 
971         /* Pop the task state */
972         DosRestoreState();
973     }
974 
975     /* Return control to the parent process */
976     Stack = (LPWORD)SEG_OFF_TO_PTR(getSS(), getSP());
977     Stack[STACK_CS] = HIWORD(PspBlock->TerminateAddress);
978     Stack[STACK_IP] = LOWORD(PspBlock->TerminateAddress);
979 }
980 
981 /* EOF */
982