[NTVDM]
[reactos.git] / subsystems / ntvdm / dos.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: dos.c
5 * PURPOSE: VDM DOS Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "dos.h"
12 #include "bios.h"
13 #include "emulator.h"
14
15 /* PRIVATE VARIABLES **********************************************************/
16
17 static WORD CurrentPsp = SYSTEM_PSP;
18 static DWORD DiskTransferArea;
19 static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
20 static WORD DosSftRefCount[DOS_SFT_SIZE];
21
22 /* PRIVATE FUNCTIONS **********************************************************/
23
24 static VOID DosCombineFreeBlocks(WORD StartBlock)
25 {
26 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
27
28 /* If this is the last block or it's not free, quit */
29 if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
30
31 while (TRUE)
32 {
33 /* Get a pointer to the next MCB */
34 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
35
36 /* Check if the next MCB is free */
37 if (NextMcb->OwnerPsp == 0)
38 {
39 /* Combine them */
40 CurrentMcb->Size += NextMcb->Size + 1;
41 CurrentMcb->BlockType = NextMcb->BlockType;
42 NextMcb->BlockType = 'I';
43 }
44 else
45 {
46 /* No more adjoining free blocks */
47 break;
48 }
49 }
50 }
51
52 static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
53 {
54 PCHAR Ptr, SourceBuffer, DestBuffer = NULL;
55 ULONG TotalSize = 0;
56 WORD DestSegment;
57
58 Ptr = SourceBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(SourceSegment, 0));
59
60 /* Calculate the size of the environment block */
61 while (*Ptr)
62 {
63 TotalSize += strlen(Ptr) + 1;
64 Ptr += strlen(Ptr) + 1;
65 }
66 TotalSize++;
67
68 /* Allocate the memory for the environment block */
69 DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
70 if (!DestSegment) return 0;
71
72 Ptr = SourceBuffer;
73
74 DestBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DestSegment, 0));
75 while (*Ptr)
76 {
77 /* Copy the string */
78 strcpy(DestBuffer, Ptr);
79
80 /* Advance to the next string */
81 Ptr += strlen(Ptr) + 1;
82 DestBuffer += strlen(Ptr) + 1;
83 }
84
85 /* Set the final zero */
86 *DestBuffer = 0;
87
88 return DestSegment;
89 }
90
91 static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
92 {
93 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
94
95 /* Just set the owner */
96 Mcb->OwnerPsp = NewOwner;
97 }
98
99 static WORD DosOpenHandle(HANDLE Handle)
100 {
101 BYTE i;
102 WORD DosHandle;
103 PDOS_PSP PspBlock;
104 LPBYTE HandleTable;
105
106 /* The system PSP has no handle table */
107 if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
108
109 /* Get a pointer to the handle table */
110 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
111 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
112
113 /* Find a free entry in the JFT */
114 for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
115 {
116 if (HandleTable[DosHandle] == 0xFF) break;
117 }
118
119 /* If there are no free entries, fail */
120 if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
121
122 /* Check if the handle is already in the SFT */
123 for (i = 0; i < DOS_SFT_SIZE; i++)
124 {
125 /* Check if this is the same handle */
126 if (DosSystemFileTable[i] != Handle) continue;
127
128 /* Already in the table, reference it */
129 DosSftRefCount[i]++;
130
131 /* Set the JFT entry to that SFT index */
132 HandleTable[DosHandle] = i;
133
134 /* Return the new handle */
135 return DosHandle;
136 }
137
138 /* Add the handle to the SFT */
139 for (i = 0; i < DOS_SFT_SIZE; i++)
140 {
141 /* Make sure this is an empty table entry */
142 if (DosSystemFileTable[i] != INVALID_HANDLE_VALUE) continue;
143
144 /* Initialize the empty table entry */
145 DosSystemFileTable[i] = Handle;
146 DosSftRefCount[i] = 1;
147
148 /* Set the JFT entry to that SFT index */
149 HandleTable[DosHandle] = i;
150
151 /* Return the new handle */
152 return DosHandle;
153 }
154
155 /* The SFT is full */
156 return INVALID_DOS_HANDLE;
157 }
158
159 static HANDLE DosGetRealHandle(WORD DosHandle)
160 {
161 PDOS_PSP PspBlock;
162 LPBYTE HandleTable;
163
164 /* The system PSP has no handle table */
165 if (CurrentPsp == SYSTEM_PSP) return INVALID_HANDLE_VALUE;
166
167 /* Get a pointer to the handle table */
168 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
169 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
170
171 /* Make sure the handle is open */
172 if (HandleTable[DosHandle] == 0xFF) return INVALID_HANDLE_VALUE;
173
174 /* Return the Win32 handle */
175 return DosSystemFileTable[HandleTable[DosHandle]];
176 }
177
178 static VOID DosCopyHandleTable(LPBYTE DestinationTable)
179 {
180 INT i;
181 PDOS_PSP PspBlock;
182 LPBYTE SourceTable;
183
184 /* Clear the table first */
185 for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF;
186
187 /* Check if this is the initial process */
188 if (CurrentPsp == SYSTEM_PSP)
189 {
190 /* Set up the standard I/O devices */
191 for (i = 0; i <= 2; i++)
192 {
193 /* Set the index in the SFT */
194 DestinationTable[i] = i;
195
196 /* Increase the reference count */
197 DosSftRefCount[i]++;
198 }
199
200 /* Done */
201 return;
202 }
203
204 /* Get the parent PSP block and handle table */
205 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
206 SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
207
208 /* Copy the first 20 handles into the new table */
209 for (i = 0; i < 20; i++)
210 {
211 DestinationTable[i] = SourceTable[i];
212
213 /* Increase the reference count */
214 DosSftRefCount[SourceTable[i]]++;
215 }
216 }
217
218 /* PUBLIC FUNCTIONS ***********************************************************/
219
220 WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
221 {
222 WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
223 PDOS_MCB CurrentMcb, NextMcb;
224
225 while (TRUE)
226 {
227 /* Get a pointer to the MCB */
228 CurrentMcb = SEGMENT_TO_MCB(Segment);
229
230 /* Make sure it's valid */
231 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
232 {
233 return 0;
234 }
235
236 /* Only check free blocks */
237 if (CurrentMcb->OwnerPsp != 0) goto Next;
238
239 /* Combine this free block with adjoining free blocks */
240 DosCombineFreeBlocks(Segment);
241
242 /* Update the maximum block size */
243 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
244
245 /* Check if this block is big enough */
246 if (CurrentMcb->Size < Size) goto Next;
247
248 /* It is, update the smallest found so far */
249 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
250 {
251 Result = Segment;
252 }
253
254 Next:
255 /* If this was the last MCB in the chain, quit */
256 if (CurrentMcb->BlockType == 'Z') break;
257
258 /* Otherwise, update the segment and continue */
259 Segment += CurrentMcb->Size + 1;
260 }
261
262 /* If we didn't find a free block, return 0 */
263 if (Result == 0)
264 {
265 if (MaxAvailable) *MaxAvailable = MaxSize;
266 return 0;
267 }
268
269 /* Get a pointer to the MCB */
270 CurrentMcb = SEGMENT_TO_MCB(Result);
271
272 /* Check if the block is larger than requested */
273 if (CurrentMcb->Size > Size)
274 {
275 /* It is, split it into two blocks */
276 NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
277
278 /* Initialize the new MCB structure */
279 NextMcb->BlockType = CurrentMcb->BlockType;
280 NextMcb->Size = CurrentMcb->Size - Size - 1;
281 NextMcb->OwnerPsp = 0;
282
283 /* Update the current block */
284 CurrentMcb->BlockType = 'M';
285 CurrentMcb->Size = Size;
286 }
287
288 /* Take ownership of the block */
289 CurrentMcb->OwnerPsp = CurrentPsp;
290
291 /* Return the segment of the data portion of the block */
292 return Result + 1;
293 }
294
295 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
296 {
297 BOOLEAN Success = TRUE;
298 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
299 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
300
301 /* Make sure this is a valid, allocated block */
302 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
303 {
304 Success = FALSE;
305 goto Done;
306 }
307
308 ReturnSize = Mcb->Size;
309
310 /* Check if we need to expand or contract the block */
311 if (NewSize > Mcb->Size)
312 {
313 /* We can't expand the last block */
314 if (Mcb->BlockType != 'M')
315 {
316 Success = FALSE;
317 goto Done;
318 }
319
320 /* Get the pointer and segment of the next MCB */
321 NextSegment = Segment + Mcb->Size + 1;
322 NextMcb = SEGMENT_TO_MCB(NextSegment);
323
324 /* Make sure the next segment is free */
325 if (NextMcb->OwnerPsp != 0)
326 {
327 Success = FALSE;
328 goto Done;
329 }
330
331 /* Combine this free block with adjoining free blocks */
332 DosCombineFreeBlocks(NextSegment);
333
334 /* Set the maximum possible size of the block */
335 ReturnSize += NextMcb->Size + 1;
336
337 /* Maximize the current block */
338 Mcb->Size = ReturnSize;
339 Mcb->BlockType = NextMcb->BlockType;
340
341 /* Invalidate the next block */
342 NextMcb->BlockType = 'I';
343
344 /* Check if the block is larger than requested */
345 if (Mcb->Size > NewSize)
346 {
347 /* It is, split it into two blocks */
348 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
349
350 /* Initialize the new MCB structure */
351 NextMcb->BlockType = Mcb->BlockType;
352 NextMcb->Size = Mcb->Size - NewSize - 1;
353 NextMcb->OwnerPsp = 0;
354
355 /* Update the current block */
356 Mcb->BlockType = 'M';
357 Mcb->Size = NewSize;
358 }
359 }
360 else if (NewSize < Mcb->Size)
361 {
362 /* Just split the block */
363 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
364 NextMcb->BlockType = Mcb->BlockType;
365 NextMcb->Size = Mcb->Size - NewSize - 1;
366 NextMcb->OwnerPsp = 0;
367
368 /* Update the MCB */
369 Mcb->BlockType = 'M';
370 Mcb->Size = NewSize;
371 }
372
373 Done:
374 /* Check if the operation failed */
375 if (!Success)
376 {
377 /* Return the maximum possible size */
378 if (MaxAvailable) *MaxAvailable = ReturnSize;
379 }
380
381 return Success;
382 }
383
384 BOOLEAN DosFreeMemory(WORD BlockData)
385 {
386 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
387
388 /* Make sure the MCB is valid */
389 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') return FALSE;
390
391 /* Mark the block as free */
392 Mcb->OwnerPsp = 0;
393
394 return TRUE;
395 }
396
397 WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes)
398 {
399 HANDLE FileHandle;
400 WORD DosHandle;
401
402 /* Create the file */
403 FileHandle = CreateFileA(FilePath,
404 GENERIC_READ | GENERIC_WRITE,
405 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
406 NULL,
407 CREATE_ALWAYS,
408 Attributes,
409 NULL);
410
411 if (FileHandle == INVALID_HANDLE_VALUE)
412 {
413 /* Return the error code */
414 return GetLastError();
415 }
416
417 /* Open the DOS handle */
418 DosHandle = DosOpenHandle(FileHandle);
419
420 if (DosHandle == INVALID_DOS_HANDLE)
421 {
422 /* Close the handle */
423 CloseHandle(FileHandle);
424
425 /* Return the error code */
426 return ERROR_TOO_MANY_OPEN_FILES;
427 }
428
429 /* It was successful */
430 *Handle = DosHandle;
431 return ERROR_SUCCESS;
432 }
433
434 WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode)
435 {
436 HANDLE FileHandle;
437 ACCESS_MASK Access = 0;
438 WORD DosHandle;
439
440 /* Parse the access mode */
441 switch (AccessMode & 3)
442 {
443 case 0:
444 {
445 /* Read-only */
446 Access = GENERIC_READ;
447 break;
448 }
449
450 case 1:
451 {
452 /* Write only */
453 Access = GENERIC_WRITE;
454 break;
455 }
456
457 case 2:
458 {
459 /* Read and write */
460 Access = GENERIC_READ | GENERIC_WRITE;
461 break;
462 }
463
464 default:
465 {
466 /* Invalid */
467 return ERROR_INVALID_PARAMETER;
468 }
469 }
470
471 /* Open the file */
472 FileHandle = CreateFileA(FilePath,
473 Access,
474 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
475 NULL,
476 OPEN_EXISTING,
477 FILE_ATTRIBUTE_NORMAL,
478 NULL);
479
480 if (FileHandle == INVALID_HANDLE_VALUE)
481 {
482 /* Return the error code */
483 return GetLastError();
484 }
485
486 /* Open the DOS handle */
487 DosHandle = DosOpenHandle(FileHandle);
488
489 if (DosHandle == INVALID_DOS_HANDLE)
490 {
491 /* Close the handle */
492 CloseHandle(FileHandle);
493
494 /* Return the error code */
495 return ERROR_TOO_MANY_OPEN_FILES;
496 }
497
498 /* It was successful */
499 *Handle = DosHandle;
500 return ERROR_SUCCESS;
501 }
502
503 WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead)
504 {
505 WORD Result = ERROR_SUCCESS;
506 DWORD BytesRead32 = 0;
507 HANDLE Handle = DosGetRealHandle(FileHandle);
508
509 /* Make sure the handle is valid */
510 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_PARAMETER;
511
512 /* Read the file */
513 if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
514 {
515 /* Store the error code */
516 Result = GetLastError();
517 }
518
519 /* The number of bytes read is always 16-bit */
520 *BytesRead = LOWORD(BytesRead32);
521
522 /* Return the error code */
523 return Result;
524 }
525
526 WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritten)
527 {
528 WORD Result = ERROR_SUCCESS;
529 DWORD BytesWritten32 = 0;
530 HANDLE Handle = DosGetRealHandle(FileHandle);
531
532 /* Make sure the handle is valid */
533 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_PARAMETER;
534
535 /* Write the file */
536 if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
537 {
538 /* Store the error code */
539 Result = GetLastError();
540 }
541
542 /* The number of bytes written is always 16-bit */
543 *BytesWritten = LOWORD(BytesWritten32);
544
545 /* Return the error code */
546 return Result;
547 }
548
549 BOOLEAN DosCloseHandle(WORD DosHandle)
550 {
551 BYTE SftIndex;
552 PDOS_PSP PspBlock;
553 LPBYTE HandleTable;
554
555 /* The system PSP has no handle table */
556 if (CurrentPsp == SYSTEM_PSP) return FALSE;
557
558 /* Get a pointer to the handle table */
559 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
560 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
561
562 /* Make sure the handle is open */
563 if (HandleTable[DosHandle] == 0xFF) return FALSE;
564
565 /* Decrement the reference count of the SFT entry */
566 SftIndex = HandleTable[DosHandle];
567 DosSftRefCount[SftIndex]--;
568
569 /* Check if the reference count fell to zero */
570 if (!DosSftRefCount[SftIndex])
571 {
572 /* Close the file, it's no longer needed */
573 CloseHandle(DosSystemFileTable[SftIndex]);
574
575 /* Clear the handle */
576 DosSystemFileTable[SftIndex] = INVALID_HANDLE_VALUE;
577 }
578
579 return TRUE;
580 }
581
582 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
583 {
584 PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
585 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
586
587 ZeroMemory(PspBlock, sizeof(DOS_PSP));
588
589 /* Set the exit interrupt */
590 PspBlock->Exit[0] = 0xCD; // int 0x20
591 PspBlock->Exit[1] = 0x20;
592
593 /* Set the program size */
594 PspBlock->MemSize = ProgramSize;
595
596 /* Save the interrupt vectors */
597 PspBlock->TerminateAddress = IntVecTable[0x22];
598 PspBlock->BreakAddress = IntVecTable[0x23];
599 PspBlock->CriticalAddress = IntVecTable[0x24];
600
601 /* Set the parent PSP */
602 PspBlock->ParentPsp = CurrentPsp;
603
604 /* Copy the parent handle table */
605 DosCopyHandleTable(PspBlock->HandleTable);
606
607 /* Set the environment block */
608 PspBlock->EnvBlock = Environment;
609
610 /* Set the handle table pointers to the internal handle table */
611 PspBlock->HandleTableSize = 20;
612 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
613
614 /* Set the DOS version */
615 PspBlock->DosVersion = DOS_VERSION;
616
617 /* Set the far call opcodes */
618 PspBlock->FarCall[0] = 0xCD; // int 0x21
619 PspBlock->FarCall[1] = 0x21;
620 PspBlock->FarCall[2] = 0xCB; // retf
621
622 /* Set the command line */
623 PspBlock->CommandLineSize = strlen(CommandLine);
624 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
625 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
626 }
627
628 BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
629 {
630 BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
631 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
632 LPBYTE Address = NULL;
633 LPSTR ProgramFilePath, Parameters[128];
634 CHAR CommandLineCopy[128];
635 INT ParamCount = 0;
636 WORD i, Segment = 0, FileSize, ExeSize;
637 PIMAGE_DOS_HEADER Header;
638 PDWORD RelocationTable;
639 PWORD RelocWord;
640
641 /* Save a copy of the command line */
642 strcpy(CommandLineCopy, CommandLine);
643
644 /* Get the file name of the executable */
645 ProgramFilePath = strtok(CommandLineCopy, " \t");
646
647 /* Load the parameters in the local array */
648 while ((ParamCount < 256)
649 && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
650 {
651 ParamCount++;
652 }
653
654 /* Open a handle to the executable */
655 FileHandle = CreateFileA(ProgramFilePath,
656 GENERIC_READ,
657 0,
658 NULL,
659 OPEN_EXISTING,
660 FILE_ATTRIBUTE_NORMAL,
661 NULL);
662 if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
663
664 /* Get the file size */
665 FileSize = GetFileSize(FileHandle, NULL);
666
667 /* Create a mapping object for the file */
668 FileMapping = CreateFileMapping(FileHandle,
669 NULL,
670 PAGE_READONLY,
671 0,
672 0,
673 NULL);
674 if (FileMapping == NULL) goto Cleanup;
675
676 /* Map the file into memory */
677 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
678 if (Address == NULL) goto Cleanup;
679
680 /* Did we get an environment segment? */
681 if (!EnvBlock)
682 {
683 /* Set a flag to know if the environment block was allocated here */
684 AllocatedEnvBlock = TRUE;
685
686 /* No, copy the one from the parent */
687 EnvBlock = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
688 ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
689 : SYSTEM_ENV_BLOCK);
690 }
691
692 /* Check if this is an EXE file or a COM file */
693 if (Address[0] == 'M' && Address[1] == 'Z')
694 {
695 /* EXE file */
696
697 /* Get the MZ header */
698 Header = (PIMAGE_DOS_HEADER)Address;
699
700 // TODO: Verify checksum and executable!
701
702 /* Get the base size of the file, in paragraphs (rounded up) */
703 ExeSize = (((Header->e_cp - 1) << 8) + Header->e_cblp + 0x0F) >> 4;
704
705 /* Loop from the maximum to the minimum number of extra paragraphs */
706 for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--)
707 {
708 /* Try to allocate that much memory */
709 Segment = DosAllocateMemory(ExeSize + (sizeof(DOS_PSP) >> 4) + i, NULL);
710 if (Segment != 0) break;
711 }
712
713 /* Check if at least the lowest allocation was successful */
714 if (Segment == 0) goto Cleanup;
715
716 /* Initialize the PSP */
717 DosInitializePsp(Segment,
718 CommandLine, ExeSize + (sizeof(DOS_PSP) >> 4) + i,
719 EnvBlock);
720
721 /* The process owns its own memory */
722 DosChangeMemoryOwner(Segment, Segment);
723 DosChangeMemoryOwner(EnvBlock, Segment);
724
725 /* Copy the program to Segment:0100 */
726 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
727 + TO_LINEAR(Segment, 0x100)),
728 Address + (Header->e_cparhdr << 4),
729 FileSize - (Header->e_cparhdr << 4));
730
731 /* Get the relocation table */
732 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
733
734 /* Perform relocations */
735 for (i = 0; i < Header->e_crlc; i++)
736 {
737 /* Get a pointer to the word that needs to be patched */
738 RelocWord = (PWORD)((ULONG_PTR)BaseAddress
739 + TO_LINEAR(Segment + HIWORD(RelocationTable[i]),
740 0x100 + LOWORD(RelocationTable[i])));
741
742 /* Add the number of the EXE segment to it */
743 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
744 }
745
746 /* Set the initial segment registers */
747 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
748 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
749
750 /* Set the stack to the location from the header */
751 EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
752 Header->e_sp);
753
754 /* Execute */
755 CurrentPsp = Segment;
756 DiskTransferArea = MAKELONG(0x80, Segment);
757 EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
758 Header->e_ip);
759
760 Success = TRUE;
761 }
762 else
763 {
764 /* COM file */
765
766 /* Allocate memory for the whole program and the PSP */
767 Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4, NULL);
768 if (Segment == 0) goto Cleanup;
769
770 /* Copy the program to Segment:0100 */
771 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
772 + TO_LINEAR(Segment, 0x100)),
773 Address,
774 FileSize);
775
776 /* Initialize the PSP */
777 DosInitializePsp(Segment,
778 CommandLine,
779 (FileSize + sizeof(DOS_PSP)) >> 4,
780 EnvBlock);
781
782 /* Set the initial segment registers */
783 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
784 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
785
786 /* Set the stack to the last word of the segment */
787 EmulatorSetStack(Segment, 0xFFFE);
788
789 /* Execute */
790 CurrentPsp = Segment;
791 DiskTransferArea = MAKELONG(0x80, Segment);
792 EmulatorExecute(Segment, 0x100);
793
794 Success = TRUE;
795 }
796
797 Cleanup:
798 if (!Success)
799 {
800 /* It was not successful, cleanup the DOS memory */
801 if (AllocatedEnvBlock) DosFreeMemory(EnvBlock);
802 if (Segment) DosFreeMemory(Segment);
803 }
804
805 /* Unmap the file*/
806 if (Address != NULL) UnmapViewOfFile(Address);
807
808 /* Close the file mapping object */
809 if (FileMapping != NULL) CloseHandle(FileMapping);
810
811 /* Close the file handle */
812 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
813
814 return Success;
815 }
816
817 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
818 {
819 WORD McbSegment = FIRST_MCB_SEGMENT;
820 PDOS_MCB CurrentMcb;
821 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
822 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
823
824 /* Check if this PSP is it's own parent */
825 if (PspBlock->ParentPsp == Psp) goto Done;
826
827 // TODO: Close all handles opened by the process
828
829 /* Free the memory used by the process */
830 while (TRUE)
831 {
832 /* Get a pointer to the MCB */
833 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
834
835 /* Make sure the MCB is valid */
836 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
837
838 /* If this block was allocated by the process, free it */
839 if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment);
840
841 /* If this was the last block, quit */
842 if (CurrentMcb->BlockType == 'Z') break;
843
844 /* Update the segment and continue */
845 McbSegment += CurrentMcb->Size + 1;
846 }
847
848 Done:
849 /* Restore the interrupt vectors */
850 IntVecTable[0x22] = PspBlock->TerminateAddress;
851 IntVecTable[0x23] = PspBlock->BreakAddress;
852 IntVecTable[0x24] = PspBlock->CriticalAddress;
853
854 /* Update the current PSP */
855 if (Psp == CurrentPsp)
856 {
857 CurrentPsp = PspBlock->ParentPsp;
858 if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
859 }
860
861 /* Return control to the parent process */
862 EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
863 LOWORD(PspBlock->TerminateAddress));
864 }
865
866 CHAR DosReadCharacter()
867 {
868 // TODO: STDIN can be redirected under DOS 2.0+
869 CHAR Character = 0;
870
871 /* A zero value for the character indicates a special key */
872 do Character = BiosGetCharacter();
873 while (!Character);
874
875 return Character;
876 }
877
878 VOID DosPrintCharacter(CHAR Character)
879 {
880 // TODO: STDOUT can be redirected under DOS 2.0+
881 if (Character == '\r') Character = '\n';
882 putchar(Character);
883 }
884
885 VOID DosInt20h(WORD CodeSegment)
886 {
887 /* This is the exit interrupt */
888 DosTerminateProcess(CodeSegment, 0);
889 }
890
891 VOID DosInt21h(WORD CodeSegment)
892 {
893 INT i;
894 CHAR Character;
895 SYSTEMTIME SystemTime;
896 PCHAR String;
897 PDOS_INPUT_BUFFER InputBuffer;
898 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
899 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
900 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
901 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
902 WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
903 WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
904
905 /* Check the value in the AH register */
906 switch (HIBYTE(Eax))
907 {
908 /* Terminate Program */
909 case 0x00:
910 {
911 DosTerminateProcess(CodeSegment, 0);
912 break;
913 }
914
915 /* Read Character And Echo */
916 case 0x01:
917 {
918 Character = DosReadCharacter();
919 DosPrintCharacter(Character);
920 EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
921 break;
922 }
923
924 /* Print Character */
925 case 0x02:
926 {
927 DosPrintCharacter(LOBYTE(Edx));
928 break;
929 }
930
931 /* Read Character Without Echo */
932 case 0x08:
933 {
934 EmulatorSetRegister(EMULATOR_REG_AX,
935 (Eax & 0xFFFFFF00) | DosReadCharacter());
936 break;
937 }
938
939 /* Print String */
940 case 0x09:
941 {
942 String = (PCHAR)((ULONG_PTR)BaseAddress
943 + TO_LINEAR(DataSegment, LOWORD(Edx)));
944
945 while ((*String) != '$')
946 {
947 DosPrintCharacter(*String);
948 String++;
949 }
950
951 break;
952 }
953
954 /* Read Buffered Input */
955 case 0x0A:
956 {
957 InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
958 + TO_LINEAR(DataSegment,
959 LOWORD(Edx)));
960
961 InputBuffer->Length = 0;
962 for (i = 0; i < InputBuffer->MaxLength; i ++)
963 {
964 Character = DosReadCharacter();
965 DosPrintCharacter(Character);
966 InputBuffer->Buffer[InputBuffer->Length] = Character;
967 if (Character == '\r') break;
968 InputBuffer->Length++;
969 }
970
971 break;
972 }
973
974 /* Set Disk Transfer Area */
975 case 0x1A:
976 {
977 DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment);
978 break;
979 }
980
981 /* Set Interrupt Vector */
982 case 0x25:
983 {
984 DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment);
985
986 /* Write the new far pointer to the IDT */
987 ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer;
988
989 break;
990 }
991
992 /* Get system date */
993 case 0x2A:
994 {
995 GetLocalTime(&SystemTime);
996 EmulatorSetRegister(EMULATOR_REG_CX,
997 (Ecx & 0xFFFF0000) | SystemTime.wYear);
998 EmulatorSetRegister(EMULATOR_REG_DX,
999 (Edx & 0xFFFF0000)
1000 | (SystemTime.wMonth << 8)
1001 | SystemTime.wDay);
1002 EmulatorSetRegister(EMULATOR_REG_AX,
1003 (Eax & 0xFFFFFF00) | SystemTime.wDayOfWeek);
1004 break;
1005 }
1006
1007 /* Set system date */
1008 case 0x2B:
1009 {
1010 GetLocalTime(&SystemTime);
1011 SystemTime.wYear = LOWORD(Ecx);
1012 SystemTime.wMonth = HIBYTE(Edx);
1013 SystemTime.wDay = LOBYTE(Edx);
1014
1015 if (SetLocalTime(&SystemTime))
1016 {
1017 /* Return success */
1018 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
1019 }
1020 else
1021 {
1022 /* Return failure */
1023 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
1024 }
1025
1026 break;
1027 }
1028
1029 /* Get system time */
1030 case 0x2C:
1031 {
1032 GetLocalTime(&SystemTime);
1033 EmulatorSetRegister(EMULATOR_REG_CX,
1034 (Ecx & 0xFFFF0000)
1035 | (SystemTime.wHour << 8)
1036 | SystemTime.wMinute);
1037 EmulatorSetRegister(EMULATOR_REG_DX,
1038 (Edx & 0xFFFF0000)
1039 | (SystemTime.wSecond << 8)
1040 | (SystemTime.wMilliseconds / 10));
1041 break;
1042 }
1043
1044 /* Set system time */
1045 case 0x2D:
1046 {
1047 GetLocalTime(&SystemTime);
1048 SystemTime.wHour = HIBYTE(Ecx);
1049 SystemTime.wMinute = LOBYTE(Ecx);
1050 SystemTime.wSecond = HIBYTE(Edx);
1051 SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
1052
1053 if (SetLocalTime(&SystemTime))
1054 {
1055 /* Return success */
1056 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
1057 }
1058 else
1059 {
1060 /* Return failure */
1061 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
1062 }
1063
1064 break;
1065 }
1066
1067 /* Get Disk Transfer Area */
1068 case 0x2F:
1069 {
1070 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea));
1071 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea));
1072
1073 break;
1074 }
1075
1076 /* Get DOS Version */
1077 case 0x30:
1078 {
1079 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1080
1081 EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion);
1082 break;
1083 }
1084
1085 /* Get Interrupt Vector */
1086 case 0x35:
1087 {
1088 DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)];
1089
1090 /* Read the address from the IDT into ES:BX */
1091 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer));
1092 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer));
1093
1094 break;
1095 }
1096
1097 /* Create Directory */
1098 case 0x39:
1099 {
1100 String = (PCHAR)((ULONG_PTR)BaseAddress
1101 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1102
1103 if (CreateDirectoryA(String, NULL))
1104 {
1105 EmulatorClearFlag(EMULATOR_FLAG_CF);
1106 }
1107 else
1108 {
1109 EmulatorSetFlag(EMULATOR_FLAG_CF);
1110 EmulatorSetRegister(EMULATOR_REG_AX,
1111 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1112 }
1113
1114 break;
1115 }
1116
1117 /* Remove Directory */
1118 case 0x3A:
1119 {
1120 String = (PCHAR)((ULONG_PTR)BaseAddress
1121 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1122
1123 if (RemoveDirectoryA(String))
1124 {
1125 EmulatorClearFlag(EMULATOR_FLAG_CF);
1126 }
1127 else
1128 {
1129 EmulatorSetFlag(EMULATOR_FLAG_CF);
1130 EmulatorSetRegister(EMULATOR_REG_AX,
1131 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1132 }
1133
1134
1135 break;
1136 }
1137
1138 /* Set Current Directory */
1139 case 0x3B:
1140 {
1141 String = (PCHAR)((ULONG_PTR)BaseAddress
1142 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1143
1144 if (SetCurrentDirectoryA(String))
1145 {
1146 EmulatorClearFlag(EMULATOR_FLAG_CF);
1147 }
1148 else
1149 {
1150 EmulatorSetFlag(EMULATOR_FLAG_CF);
1151 EmulatorSetRegister(EMULATOR_REG_AX,
1152 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1153 }
1154
1155 break;
1156 }
1157
1158 /* Create File */
1159 case 0x3C:
1160 {
1161 WORD FileHandle;
1162 WORD ErrorCode = DosCreateFile(&FileHandle,
1163 (LPCSTR)(ULONG_PTR)BaseAddress
1164 + TO_LINEAR(DataSegment, LOWORD(Edx)),
1165 LOWORD(Ecx));
1166
1167 if (ErrorCode == 0)
1168 {
1169 /* Clear CF */
1170 EmulatorClearFlag(EMULATOR_FLAG_CF);
1171
1172 /* Return the handle in AX */
1173 EmulatorSetRegister(EMULATOR_REG_AX,
1174 (Eax & 0xFFFF0000) | FileHandle);
1175 }
1176 else
1177 {
1178 /* Set CF */
1179 EmulatorSetFlag(EMULATOR_FLAG_CF);
1180
1181 /* Return the error code in AX */
1182 EmulatorSetRegister(EMULATOR_REG_AX,
1183 (Eax & 0xFFFF0000) | ErrorCode);
1184 }
1185
1186 break;
1187 }
1188
1189 /* Open File */
1190 case 0x3D:
1191 {
1192 WORD FileHandle;
1193 WORD ErrorCode = DosCreateFile(&FileHandle,
1194 (LPCSTR)(ULONG_PTR)BaseAddress
1195 + TO_LINEAR(DataSegment, LOWORD(Edx)),
1196 LOBYTE(Eax));
1197
1198 if (ErrorCode == 0)
1199 {
1200 /* Clear CF */
1201 EmulatorClearFlag(EMULATOR_FLAG_CF);
1202
1203 /* Return the handle in AX */
1204 EmulatorSetRegister(EMULATOR_REG_AX,
1205 (Eax & 0xFFFF0000) | FileHandle);
1206 }
1207 else
1208 {
1209 /* Set CF */
1210 EmulatorSetFlag(EMULATOR_FLAG_CF);
1211
1212 /* Return the error code in AX */
1213 EmulatorSetRegister(EMULATOR_REG_AX,
1214 (Eax & 0xFFFF0000) | ErrorCode);
1215 }
1216
1217 break;
1218 }
1219
1220 /* Close File */
1221 case 0x3E:
1222 {
1223 if (DosCloseHandle(LOWORD(Ebx)))
1224 {
1225 /* Clear CF */
1226 EmulatorClearFlag(EMULATOR_FLAG_CF);
1227 }
1228 else
1229 {
1230 /* Set CF */
1231 EmulatorSetFlag(EMULATOR_FLAG_CF);
1232
1233 /* Return the error code in AX */
1234 EmulatorSetRegister(EMULATOR_REG_AX,
1235 (Eax & 0xFFFF0000) | ERROR_INVALID_PARAMETER);
1236 }
1237
1238 break;
1239 }
1240
1241 /* Read File */
1242 case 0x3F:
1243 {
1244 WORD BytesRead = 0;
1245 WORD ErrorCode = DosReadFile(LOWORD(Ebx),
1246 (LPVOID)((ULONG_PTR)BaseAddress
1247 + TO_LINEAR(DataSegment, LOWORD(Edx))),
1248 LOWORD(Ecx),
1249 &BytesRead);
1250
1251 if (ErrorCode == 0)
1252 {
1253 /* Clear CF */
1254 EmulatorClearFlag(EMULATOR_FLAG_CF);
1255
1256 /* Return the number of bytes read in AX */
1257 EmulatorSetRegister(EMULATOR_REG_AX,
1258 (Eax & 0xFFFF0000) | BytesRead);
1259 }
1260 else
1261 {
1262 /* Set CF */
1263 EmulatorSetFlag(EMULATOR_FLAG_CF);
1264
1265 /* Return the error code in AX */
1266 EmulatorSetRegister(EMULATOR_REG_AX,
1267 (Eax & 0xFFFF0000) | ErrorCode);
1268 }
1269 break;
1270 }
1271
1272 /* Write File */
1273 case 0x40:
1274 {
1275 WORD BytesWritten = 0;
1276 WORD ErrorCode = DosWriteFile(LOWORD(Ebx),
1277 (LPVOID)((ULONG_PTR)BaseAddress
1278 + TO_LINEAR(DataSegment, LOWORD(Edx))),
1279 LOWORD(Ecx),
1280 &BytesWritten);
1281
1282 if (ErrorCode == 0)
1283 {
1284 /* Clear CF */
1285 EmulatorClearFlag(EMULATOR_FLAG_CF);
1286
1287 /* Return the number of bytes written in AX */
1288 EmulatorSetRegister(EMULATOR_REG_AX,
1289 (Eax & 0xFFFF0000) | BytesWritten);
1290 }
1291 else
1292 {
1293 /* Set CF */
1294 EmulatorSetFlag(EMULATOR_FLAG_CF);
1295
1296 /* Return the error code in AX */
1297 EmulatorSetRegister(EMULATOR_REG_AX,
1298 (Eax & 0xFFFF0000) | ErrorCode);
1299 }
1300
1301 break;
1302 }
1303
1304 /* Allocate Memory */
1305 case 0x48:
1306 {
1307 WORD MaxAvailable = 0;
1308 WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
1309
1310 if (Segment != 0)
1311 {
1312 EmulatorSetRegister(EMULATOR_REG_AX, Segment);
1313 EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
1314 EmulatorClearFlag(EMULATOR_FLAG_CF);
1315 }
1316 else EmulatorSetFlag(EMULATOR_FLAG_CF);
1317
1318 break;
1319 }
1320
1321 /* Free Memory */
1322 case 0x49:
1323 {
1324 if (DosFreeMemory(ExtSegment))
1325 {
1326 EmulatorClearFlag(EMULATOR_FLAG_CF);
1327 }
1328 else EmulatorSetFlag(EMULATOR_FLAG_CF);
1329
1330 break;
1331 }
1332
1333 /* Resize Memory Block */
1334 case 0x4A:
1335 {
1336 WORD Size;
1337
1338 if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size))
1339 {
1340 EmulatorClearFlag(EMULATOR_FLAG_CF);
1341 }
1342 else
1343 {
1344 EmulatorSetFlag(EMULATOR_FLAG_CF);
1345 EmulatorSetRegister(EMULATOR_REG_BX, Size);
1346 }
1347
1348 break;
1349 }
1350
1351 /* Terminate With Return Code */
1352 case 0x4C:
1353 {
1354 DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
1355 break;
1356 }
1357
1358 /* Unsupported */
1359 default:
1360 {
1361 DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
1362 EmulatorSetFlag(EMULATOR_FLAG_CF);
1363 }
1364 }
1365 }
1366
1367 VOID DosBreakInterrupt()
1368 {
1369 VdmRunning = FALSE;
1370 }
1371
1372 BOOLEAN DosInitialize()
1373 {
1374 BYTE i;
1375 PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
1376 FILE *Stream;
1377 WCHAR Buffer[256];
1378 LPWSTR SourcePtr, Environment;
1379 LPSTR AsciiString;
1380 LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
1381 DWORD AsciiSize;
1382
1383 /* Initialize the MCB */
1384 Mcb->BlockType = 'Z';
1385 Mcb->Size = (WORD)USER_MEMORY_SIZE;
1386 Mcb->OwnerPsp = 0;
1387
1388 /* Get the environment strings */
1389 SourcePtr = Environment = GetEnvironmentStringsW();
1390 if (Environment == NULL) return FALSE;
1391
1392 /* Fill the DOS system environment block */
1393 while (*SourcePtr)
1394 {
1395 /* Get the size of the ASCII string */
1396 AsciiSize = WideCharToMultiByte(CP_ACP,
1397 0,
1398 SourcePtr,
1399 -1,
1400 NULL,
1401 0,
1402 NULL,
1403 NULL);
1404
1405 /* Allocate memory for the ASCII string */
1406 AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
1407 if (AsciiString == NULL)
1408 {
1409 FreeEnvironmentStringsW(Environment);
1410 return FALSE;
1411 }
1412
1413 /* Convert to ASCII */
1414 WideCharToMultiByte(CP_ACP,
1415 0,
1416 SourcePtr,
1417 -1,
1418 AsciiString,
1419 AsciiSize,
1420 NULL,
1421 NULL);
1422
1423 /* Copy the string into DOS memory */
1424 strcpy(DestPtr, AsciiString);
1425
1426 /* Free the memory */
1427 HeapFree(GetProcessHeap(), 0, AsciiString);
1428
1429 /* Move to the next string */
1430 SourcePtr += wcslen(SourcePtr) + 1;
1431 DestPtr += strlen(AsciiString) + 1;
1432 }
1433
1434 /* Free the memory allocated for environment strings */
1435 FreeEnvironmentStringsW(Environment);
1436
1437 /* Read CONFIG.SYS */
1438 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
1439 if (Stream != NULL)
1440 {
1441 while (fgetws(Buffer, 256, Stream))
1442 {
1443 // TODO: Parse the line
1444 }
1445 fclose(Stream);
1446 }
1447
1448 /* Initialize the SFT */
1449 for (i = 0; i < DOS_SFT_SIZE; i++)
1450 {
1451 DosSystemFileTable[i] = INVALID_HANDLE_VALUE;
1452 DosSftRefCount[i] = 0;
1453 }
1454
1455 /* Get handles to standard I/O devices */
1456 DosSystemFileTable[0] = GetStdHandle(STD_INPUT_HANDLE);
1457 DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE);
1458 DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE);
1459
1460 return TRUE;
1461 }
1462
1463 /* EOF */