[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 #define NDEBUG
12
13 #include "dos.h"
14 #include "bios.h"
15 #include "emulator.h"
16
17 /* PRIVATE VARIABLES **********************************************************/
18
19 static WORD CurrentPsp = SYSTEM_PSP;
20 static WORD DosLastError = 0;
21 static DWORD DiskTransferArea;
22 static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
23 static WORD DosSftRefCount[DOS_SFT_SIZE];
24 static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
25 static BOOLEAN DosUmbLinked = FALSE;
26
27 /* PRIVATE FUNCTIONS **********************************************************/
28
29 static VOID DosCombineFreeBlocks(WORD StartBlock)
30 {
31 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
32
33 /* If this is the last block or it's not free, quit */
34 if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
35
36 while (TRUE)
37 {
38 /* Get a pointer to the next MCB */
39 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
40
41 /* Check if the next MCB is free */
42 if (NextMcb->OwnerPsp == 0)
43 {
44 /* Combine them */
45 CurrentMcb->Size += NextMcb->Size + 1;
46 CurrentMcb->BlockType = NextMcb->BlockType;
47 NextMcb->BlockType = 'I';
48 }
49 else
50 {
51 /* No more adjoining free blocks */
52 break;
53 }
54 }
55 }
56
57 static WORD DosCopyEnvironmentBlock(WORD SourceSegment, LPCSTR ProgramName)
58 {
59 PCHAR Ptr, SourceBuffer, DestBuffer = NULL;
60 ULONG TotalSize = 0;
61 WORD DestSegment;
62
63 Ptr = SourceBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(SourceSegment, 0));
64
65 /* Calculate the size of the environment block */
66 while (*Ptr)
67 {
68 TotalSize += strlen(Ptr) + 1;
69 Ptr += strlen(Ptr) + 1;
70 }
71 TotalSize++;
72
73 /* Add the string buffer size */
74 TotalSize += strlen(ProgramName) + 1;
75
76 /* Allocate the memory for the environment block */
77 DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
78 if (!DestSegment) return 0;
79
80 Ptr = SourceBuffer;
81
82 DestBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DestSegment, 0));
83 while (*Ptr)
84 {
85 /* Copy the string */
86 strcpy(DestBuffer, Ptr);
87
88 /* Advance to the next string */
89 DestBuffer += strlen(Ptr);
90 Ptr += strlen(Ptr) + 1;
91
92 /* Put a zero after the string */
93 *(DestBuffer++) = 0;
94 }
95
96 /* Set the final zero */
97 *(DestBuffer++) = 0;
98
99 /* Copy the program name after the environment block */
100 strcpy(DestBuffer, ProgramName);
101
102 return DestSegment;
103 }
104
105 static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
106 {
107 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
108
109 /* Just set the owner */
110 Mcb->OwnerPsp = NewOwner;
111 }
112
113 static WORD DosOpenHandle(HANDLE Handle)
114 {
115 BYTE i;
116 WORD DosHandle;
117 PDOS_PSP PspBlock;
118 LPBYTE HandleTable;
119
120 /* The system PSP has no handle table */
121 if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
122
123 /* Get a pointer to the handle table */
124 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
125 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
126
127 /* Find a free entry in the JFT */
128 for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
129 {
130 if (HandleTable[DosHandle] == 0xFF) break;
131 }
132
133 /* If there are no free entries, fail */
134 if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
135
136 /* Check if the handle is already in the SFT */
137 for (i = 0; i < DOS_SFT_SIZE; i++)
138 {
139 /* Check if this is the same handle */
140 if (DosSystemFileTable[i] != Handle) continue;
141
142 /* Already in the table, reference it */
143 DosSftRefCount[i]++;
144
145 /* Set the JFT entry to that SFT index */
146 HandleTable[DosHandle] = i;
147
148 /* Return the new handle */
149 return DosHandle;
150 }
151
152 /* Add the handle to the SFT */
153 for (i = 0; i < DOS_SFT_SIZE; i++)
154 {
155 /* Make sure this is an empty table entry */
156 if (DosSystemFileTable[i] != INVALID_HANDLE_VALUE) continue;
157
158 /* Initialize the empty table entry */
159 DosSystemFileTable[i] = Handle;
160 DosSftRefCount[i] = 1;
161
162 /* Set the JFT entry to that SFT index */
163 HandleTable[DosHandle] = i;
164
165 /* Return the new handle */
166 return DosHandle;
167 }
168
169 /* The SFT is full */
170 return INVALID_DOS_HANDLE;
171 }
172
173 static HANDLE DosGetRealHandle(WORD DosHandle)
174 {
175 PDOS_PSP PspBlock;
176 LPBYTE HandleTable;
177
178 /* The system PSP has no handle table */
179 if (CurrentPsp == SYSTEM_PSP) return INVALID_HANDLE_VALUE;
180
181 /* Get a pointer to the handle table */
182 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
183 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
184
185 /* Make sure the handle is open */
186 if (HandleTable[DosHandle] == 0xFF) return INVALID_HANDLE_VALUE;
187
188 /* Return the Win32 handle */
189 return DosSystemFileTable[HandleTable[DosHandle]];
190 }
191
192 static VOID DosCopyHandleTable(LPBYTE DestinationTable)
193 {
194 INT i;
195 PDOS_PSP PspBlock;
196 LPBYTE SourceTable;
197
198 /* Clear the table first */
199 for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF;
200
201 /* Check if this is the initial process */
202 if (CurrentPsp == SYSTEM_PSP)
203 {
204 /* Set up the standard I/O devices */
205 for (i = 0; i <= 2; i++)
206 {
207 /* Set the index in the SFT */
208 DestinationTable[i] = i;
209
210 /* Increase the reference count */
211 DosSftRefCount[i]++;
212 }
213
214 /* Done */
215 return;
216 }
217
218 /* Get the parent PSP block and handle table */
219 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
220 SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
221
222 /* Copy the first 20 handles into the new table */
223 for (i = 0; i < 20; i++)
224 {
225 DestinationTable[i] = SourceTable[i];
226
227 /* Increase the reference count */
228 DosSftRefCount[SourceTable[i]]++;
229 }
230 }
231
232 /* PUBLIC FUNCTIONS ***********************************************************/
233
234 WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
235 {
236 WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
237 PDOS_MCB CurrentMcb, NextMcb;
238 BOOLEAN SearchUmb = FALSE;
239
240 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size);
241
242 if (DosUmbLinked && (DosAllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)))
243 {
244 /* Search UMB first */
245 Segment = UMB_START_SEGMENT;
246 SearchUmb = TRUE;
247 }
248
249 while (TRUE)
250 {
251 /* Get a pointer to the MCB */
252 CurrentMcb = SEGMENT_TO_MCB(Segment);
253
254 /* Make sure it's valid */
255 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
256 {
257 DPRINT("The DOS memory arena is corrupted!\n");
258 DosLastError = ERROR_ARENA_TRASHED;
259 return 0;
260 }
261
262 /* Only check free blocks */
263 if (CurrentMcb->OwnerPsp != 0) goto Next;
264
265 /* Combine this free block with adjoining free blocks */
266 DosCombineFreeBlocks(Segment);
267
268 /* Update the maximum block size */
269 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
270
271 /* Check if this block is big enough */
272 if (CurrentMcb->Size < Size) goto Next;
273
274 switch (DosAllocStrategy & 0x3F)
275 {
276 case DOS_ALLOC_FIRST_FIT:
277 {
278 /* For first fit, stop immediately */
279 Result = Segment;
280 goto Done;
281 }
282
283 case DOS_ALLOC_BEST_FIT:
284 {
285 /* For best fit, update the smallest block found so far */
286 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
287 {
288 Result = Segment;
289 }
290
291 break;
292 }
293
294 case DOS_ALLOC_LAST_FIT:
295 {
296 /* For last fit, make the current block the result, but keep searching */
297 Result = Segment;
298 break;
299 }
300 }
301
302 Next:
303 /* If this was the last MCB in the chain, quit */
304 if (CurrentMcb->BlockType == 'Z')
305 {
306 /* Check if nothing was found while searching through UMBs */
307 if ((Result == 0) && SearchUmb && (DosAllocStrategy & DOS_ALLOC_HIGH_LOW))
308 {
309 /* Search low memory */
310 Segment = FIRST_MCB_SEGMENT;
311 continue;
312 }
313
314 break;
315 }
316
317 /* Otherwise, update the segment and continue */
318 Segment += CurrentMcb->Size + 1;
319 }
320
321 Done:
322
323 /* If we didn't find a free block, return 0 */
324 if (Result == 0)
325 {
326 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
327 if (MaxAvailable) *MaxAvailable = MaxSize;
328 return 0;
329 }
330
331 /* Get a pointer to the MCB */
332 CurrentMcb = SEGMENT_TO_MCB(Result);
333
334 /* Check if the block is larger than requested */
335 if (CurrentMcb->Size > Size)
336 {
337 /* It is, split it into two blocks */
338 NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
339
340 /* Initialize the new MCB structure */
341 NextMcb->BlockType = CurrentMcb->BlockType;
342 NextMcb->Size = CurrentMcb->Size - Size - 1;
343 NextMcb->OwnerPsp = 0;
344
345 /* Update the current block */
346 CurrentMcb->BlockType = 'M';
347 CurrentMcb->Size = Size;
348 }
349
350 /* Take ownership of the block */
351 CurrentMcb->OwnerPsp = CurrentPsp;
352
353 /* Return the segment of the data portion of the block */
354 return Result + 1;
355 }
356
357 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
358 {
359 BOOLEAN Success = TRUE;
360 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
361 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
362
363 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
364 BlockData,
365 NewSize);
366
367 /* Make sure this is a valid, allocated block */
368 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
369 {
370 Success = FALSE;
371 DosLastError = ERROR_INVALID_HANDLE;
372 goto Done;
373 }
374
375 ReturnSize = Mcb->Size;
376
377 /* Check if we need to expand or contract the block */
378 if (NewSize > Mcb->Size)
379 {
380 /* We can't expand the last block */
381 if (Mcb->BlockType != 'M')
382 {
383 Success = FALSE;
384 goto Done;
385 }
386
387 /* Get the pointer and segment of the next MCB */
388 NextSegment = Segment + Mcb->Size + 1;
389 NextMcb = SEGMENT_TO_MCB(NextSegment);
390
391 /* Make sure the next segment is free */
392 if (NextMcb->OwnerPsp != 0)
393 {
394 DPRINT("Cannot expand memory block: next segment is not free!\n");
395 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
396 Success = FALSE;
397 goto Done;
398 }
399
400 /* Combine this free block with adjoining free blocks */
401 DosCombineFreeBlocks(NextSegment);
402
403 /* Set the maximum possible size of the block */
404 ReturnSize += NextMcb->Size + 1;
405
406 /* Maximize the current block */
407 Mcb->Size = ReturnSize;
408 Mcb->BlockType = NextMcb->BlockType;
409
410 /* Invalidate the next block */
411 NextMcb->BlockType = 'I';
412
413 /* Check if the block is larger than requested */
414 if (Mcb->Size > NewSize)
415 {
416 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
417 Mcb->Size,
418 NewSize);
419
420 /* It is, split it into two blocks */
421 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
422
423 /* Initialize the new MCB structure */
424 NextMcb->BlockType = Mcb->BlockType;
425 NextMcb->Size = Mcb->Size - NewSize - 1;
426 NextMcb->OwnerPsp = 0;
427
428 /* Update the current block */
429 Mcb->BlockType = 'M';
430 Mcb->Size = NewSize;
431 }
432 }
433 else if (NewSize < Mcb->Size)
434 {
435 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
436 Mcb->Size,
437 NewSize);
438
439 /* Just split the block */
440 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
441 NextMcb->BlockType = Mcb->BlockType;
442 NextMcb->Size = Mcb->Size - NewSize - 1;
443 NextMcb->OwnerPsp = 0;
444
445 /* Update the MCB */
446 Mcb->BlockType = 'M';
447 Mcb->Size = NewSize;
448 }
449
450 Done:
451 /* Check if the operation failed */
452 if (!Success)
453 {
454 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
455 ReturnSize);
456
457 /* Return the maximum possible size */
458 if (MaxAvailable) *MaxAvailable = ReturnSize;
459 }
460
461 return Success;
462 }
463
464 BOOLEAN DosFreeMemory(WORD BlockData)
465 {
466 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
467
468 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
469
470 /* Make sure the MCB is valid */
471 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
472 {
473 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
474 return FALSE;
475 }
476
477 /* Mark the block as free */
478 Mcb->OwnerPsp = 0;
479
480 return TRUE;
481 }
482
483 BOOLEAN DosLinkUmb(VOID)
484 {
485 DWORD Segment = FIRST_MCB_SEGMENT;
486 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
487
488 DPRINT("Linking UMB\n");
489
490 /* Check if UMBs are already linked */
491 if (DosUmbLinked) return FALSE;
492
493 /* Find the last block */
494 while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
495 {
496 Segment += Mcb->Size + 1;
497 Mcb = SEGMENT_TO_MCB(Segment);
498 }
499
500 /* Make sure it's valid */
501 if (Mcb->BlockType != 'Z') return FALSE;
502
503 /* Connect the MCB with the UMB chain */
504 Mcb->BlockType = 'M';
505
506 DosUmbLinked = TRUE;
507 return TRUE;
508 }
509
510 BOOLEAN DosUnlinkUmb(VOID)
511 {
512 DWORD Segment = FIRST_MCB_SEGMENT;
513 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
514
515 DPRINT("Unlinking UMB\n");
516
517 /* Check if UMBs are already unlinked */
518 if (!DosUmbLinked) return FALSE;
519
520 /* Find the block preceding the MCB that links it with the UMB chain */
521 while (Segment <= 0xFFFF)
522 {
523 if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
524 {
525 /* This is the last non-UMB segment */
526 break;
527 }
528
529 /* Advance to the next MCB */
530 Segment += Mcb->Size + 1;
531 Mcb = SEGMENT_TO_MCB(Segment);
532 }
533
534 /* Mark the MCB as the last MCB */
535 Mcb->BlockType = 'Z';
536
537 DosUmbLinked = FALSE;
538 return TRUE;
539 }
540
541 WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes)
542 {
543 HANDLE FileHandle;
544 WORD DosHandle;
545
546 DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
547 FilePath,
548 Attributes);
549
550 /* Create the file */
551 FileHandle = CreateFileA(FilePath,
552 GENERIC_READ | GENERIC_WRITE,
553 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
554 NULL,
555 CREATE_ALWAYS,
556 Attributes,
557 NULL);
558
559 if (FileHandle == INVALID_HANDLE_VALUE)
560 {
561 /* Return the error code */
562 return GetLastError();
563 }
564
565 /* Open the DOS handle */
566 DosHandle = DosOpenHandle(FileHandle);
567
568 if (DosHandle == INVALID_DOS_HANDLE)
569 {
570 /* Close the handle */
571 CloseHandle(FileHandle);
572
573 /* Return the error code */
574 return ERROR_TOO_MANY_OPEN_FILES;
575 }
576
577 /* It was successful */
578 *Handle = DosHandle;
579 return ERROR_SUCCESS;
580 }
581
582 WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode)
583 {
584 HANDLE FileHandle;
585 ACCESS_MASK Access = 0;
586 WORD DosHandle;
587
588 DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
589 FilePath,
590 AccessMode);
591
592 /* Parse the access mode */
593 switch (AccessMode & 3)
594 {
595 case 0:
596 {
597 /* Read-only */
598 Access = GENERIC_READ;
599 break;
600 }
601
602 case 1:
603 {
604 /* Write only */
605 Access = GENERIC_WRITE;
606 break;
607 }
608
609 case 2:
610 {
611 /* Read and write */
612 Access = GENERIC_READ | GENERIC_WRITE;
613 break;
614 }
615
616 default:
617 {
618 /* Invalid */
619 return ERROR_INVALID_PARAMETER;
620 }
621 }
622
623 /* Open the file */
624 FileHandle = CreateFileA(FilePath,
625 Access,
626 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
627 NULL,
628 OPEN_EXISTING,
629 FILE_ATTRIBUTE_NORMAL,
630 NULL);
631
632 if (FileHandle == INVALID_HANDLE_VALUE)
633 {
634 /* Return the error code */
635 return GetLastError();
636 }
637
638 /* Open the DOS handle */
639 DosHandle = DosOpenHandle(FileHandle);
640
641 if (DosHandle == INVALID_DOS_HANDLE)
642 {
643 /* Close the handle */
644 CloseHandle(FileHandle);
645
646 /* Return the error code */
647 return ERROR_TOO_MANY_OPEN_FILES;
648 }
649
650 /* It was successful */
651 *Handle = DosHandle;
652 return ERROR_SUCCESS;
653 }
654
655 WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead)
656 {
657 WORD Result = ERROR_SUCCESS;
658 DWORD BytesRead32 = 0;
659 HANDLE Handle = DosGetRealHandle(FileHandle);
660
661 DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle, Count);
662
663 /* Make sure the handle is valid */
664 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
665
666 /* Read the file */
667 if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
668 {
669 /* Store the error code */
670 Result = GetLastError();
671 }
672
673 /* The number of bytes read is always 16-bit */
674 *BytesRead = LOWORD(BytesRead32);
675
676 /* Return the error code */
677 return Result;
678 }
679
680 WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritten)
681 {
682 WORD Result = ERROR_SUCCESS;
683 DWORD BytesWritten32 = 0;
684 HANDLE Handle = DosGetRealHandle(FileHandle);
685
686 DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
687 FileHandle,
688 Count);
689
690 /* Make sure the handle is valid */
691 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
692
693 /* Write the file */
694 if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
695 {
696 /* Store the error code */
697 Result = GetLastError();
698 }
699
700 /* The number of bytes written is always 16-bit */
701 *BytesWritten = LOWORD(BytesWritten32);
702
703 /* Return the error code */
704 return Result;
705 }
706
707 WORD DosSeekFile(WORD FileHandle, LONG Offset, BYTE Origin, LPDWORD NewOffset)
708 {
709 WORD Result = ERROR_SUCCESS;
710 DWORD FilePointer;
711 HANDLE Handle = DosGetRealHandle(FileHandle);
712
713 DPRINT("DosSeekFile: FileHandle 0x%04X, Offset 0x%08X, Origin 0x%02X\n",
714 FileHandle,
715 Offset,
716 Origin);
717
718 /* Make sure the handle is valid */
719 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
720
721 /* Check if the origin is valid */
722 if (Origin != FILE_BEGIN && Origin != FILE_CURRENT && Origin != FILE_END)
723 {
724 return ERROR_INVALID_FUNCTION;
725 }
726
727 /* Move the file pointer */
728 FilePointer = SetFilePointer(Handle, Offset, NULL, Origin);
729
730 /* Check if there's a possibility the operation failed */
731 if (FilePointer == INVALID_SET_FILE_POINTER)
732 {
733 /* Get the real error code */
734 Result = GetLastError();
735 }
736
737 if (Result != ERROR_SUCCESS)
738 {
739 /* The operation did fail */
740 return Result;
741 }
742
743 /* Return the file pointer, if requested */
744 if (NewOffset) *NewOffset = FilePointer;
745
746 /* Return success */
747 return ERROR_SUCCESS;
748 }
749
750 BOOLEAN DosDuplicateHandle(WORD OldHandle, WORD NewHandle)
751 {
752 BYTE SftIndex;
753 PDOS_PSP PspBlock;
754 LPBYTE HandleTable;
755
756 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
757 OldHandle,
758 NewHandle);
759
760 /* The system PSP has no handle table */
761 if (CurrentPsp == SYSTEM_PSP) return FALSE;
762
763 /* Get a pointer to the handle table */
764 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
765 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
766
767 /* Make sure the old handle is open */
768 if (HandleTable[OldHandle] == 0xFF) return FALSE;
769
770 /* Check if the new handle is open */
771 if (HandleTable[NewHandle] != 0xFF)
772 {
773 /* Close it */
774 DosCloseHandle(NewHandle);
775 }
776
777 /* Increment the reference count of the SFT entry */
778 SftIndex = HandleTable[OldHandle];
779 DosSftRefCount[SftIndex]++;
780
781 /* Make the new handle point to that SFT entry */
782 HandleTable[NewHandle] = SftIndex;
783
784 /* Return success */
785 return TRUE;
786 }
787
788 BOOLEAN DosCloseHandle(WORD DosHandle)
789 {
790 BYTE SftIndex;
791 PDOS_PSP PspBlock;
792 LPBYTE HandleTable;
793
794 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle);
795
796 /* The system PSP has no handle table */
797 if (CurrentPsp == SYSTEM_PSP) return FALSE;
798
799 /* Get a pointer to the handle table */
800 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
801 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
802
803 /* Make sure the handle is open */
804 if (HandleTable[DosHandle] == 0xFF) return FALSE;
805
806 /* Decrement the reference count of the SFT entry */
807 SftIndex = HandleTable[DosHandle];
808 DosSftRefCount[SftIndex]--;
809
810 /* Check if the reference count fell to zero */
811 if (!DosSftRefCount[SftIndex])
812 {
813 /* Close the file, it's no longer needed */
814 CloseHandle(DosSystemFileTable[SftIndex]);
815
816 /* Clear the handle */
817 DosSystemFileTable[SftIndex] = INVALID_HANDLE_VALUE;
818 }
819
820 /* Clear the entry in the JFT */
821 HandleTable[DosHandle] = 0xFF;
822
823 return TRUE;
824 }
825
826 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
827 {
828 PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
829 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
830
831 ZeroMemory(PspBlock, sizeof(DOS_PSP));
832
833 /* Set the exit interrupt */
834 PspBlock->Exit[0] = 0xCD; // int 0x20
835 PspBlock->Exit[1] = 0x20;
836
837 /* Set the number of the last paragraph */
838 PspBlock->LastParagraph = PspSegment + ProgramSize - 1;
839
840 /* Save the interrupt vectors */
841 PspBlock->TerminateAddress = IntVecTable[0x22];
842 PspBlock->BreakAddress = IntVecTable[0x23];
843 PspBlock->CriticalAddress = IntVecTable[0x24];
844
845 /* Set the parent PSP */
846 PspBlock->ParentPsp = CurrentPsp;
847
848 /* Copy the parent handle table */
849 DosCopyHandleTable(PspBlock->HandleTable);
850
851 /* Set the environment block */
852 PspBlock->EnvBlock = Environment;
853
854 /* Set the handle table pointers to the internal handle table */
855 PspBlock->HandleTableSize = 20;
856 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
857
858 /* Set the DOS version */
859 PspBlock->DosVersion = DOS_VERSION;
860
861 /* Set the far call opcodes */
862 PspBlock->FarCall[0] = 0xCD; // int 0x21
863 PspBlock->FarCall[1] = 0x21;
864 PspBlock->FarCall[2] = 0xCB; // retf
865
866 /* Set the command line */
867 PspBlock->CommandLineSize = strlen(CommandLine);
868 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
869 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
870 }
871
872 BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
873 {
874 BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
875 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
876 LPBYTE Address = NULL;
877 LPSTR ProgramFilePath, Parameters[128];
878 CHAR CommandLineCopy[128];
879 INT ParamCount = 0;
880 DWORD Segment = 0;
881 WORD MaxAllocSize;
882 DWORD i, FileSize, ExeSize;
883 PIMAGE_DOS_HEADER Header;
884 PDWORD RelocationTable;
885 PWORD RelocWord;
886
887 DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n",
888 CommandLine,
889 EnvBlock);
890
891 /* Save a copy of the command line */
892 strcpy(CommandLineCopy, CommandLine);
893
894 /* Get the file name of the executable */
895 ProgramFilePath = strtok(CommandLineCopy, " \t");
896
897 /* Load the parameters in the local array */
898 while ((ParamCount < 256)
899 && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
900 {
901 ParamCount++;
902 }
903
904 /* Open a handle to the executable */
905 FileHandle = CreateFileA(ProgramFilePath,
906 GENERIC_READ,
907 0,
908 NULL,
909 OPEN_EXISTING,
910 FILE_ATTRIBUTE_NORMAL,
911 NULL);
912 if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
913
914 /* Get the file size */
915 FileSize = GetFileSize(FileHandle, NULL);
916
917 /* Create a mapping object for the file */
918 FileMapping = CreateFileMapping(FileHandle,
919 NULL,
920 PAGE_READONLY,
921 0,
922 0,
923 NULL);
924 if (FileMapping == NULL) goto Cleanup;
925
926 /* Map the file into memory */
927 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
928 if (Address == NULL) goto Cleanup;
929
930 /* Did we get an environment segment? */
931 if (!EnvBlock)
932 {
933 /* Set a flag to know if the environment block was allocated here */
934 AllocatedEnvBlock = TRUE;
935
936 /* No, copy the one from the parent */
937 EnvBlock = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
938 ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
939 : SYSTEM_ENV_BLOCK,
940 ProgramFilePath);
941 }
942
943 /* Check if this is an EXE file or a COM file */
944 if (Address[0] == 'M' && Address[1] == 'Z')
945 {
946 /* EXE file */
947
948 /* Get the MZ header */
949 Header = (PIMAGE_DOS_HEADER)Address;
950
951 /* Get the base size of the file, in paragraphs (rounded up) */
952 ExeSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4;
953
954 /* Add the PSP size, in paragraphs */
955 ExeSize += sizeof(DOS_PSP) >> 4;
956
957 /* Add the maximum size that should be allocated */
958 ExeSize += Header->e_maxalloc;
959
960 /* Make sure it does not pass 0xFFFF */
961 if (ExeSize > 0xFFFF) ExeSize = 0xFFFF;
962
963 /* Reduce the size one by one until the allocation is successful */
964 for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--, ExeSize--)
965 {
966 /* Try to allocate that much memory */
967 Segment = DosAllocateMemory(ExeSize, NULL);
968 if (Segment != 0) break;
969 }
970
971 /* Check if at least the lowest allocation was successful */
972 if (Segment == 0) goto Cleanup;
973
974 /* Initialize the PSP */
975 DosInitializePsp(Segment,
976 CommandLine,
977 ExeSize,
978 EnvBlock);
979
980 /* The process owns its own memory */
981 DosChangeMemoryOwner(Segment, Segment);
982 DosChangeMemoryOwner(EnvBlock, Segment);
983
984 /* Copy the program to Segment:0100 */
985 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
986 + TO_LINEAR(Segment, 0x100)),
987 Address + (Header->e_cparhdr << 4),
988 min(FileSize - (Header->e_cparhdr << 4),
989 (ExeSize << 4) - sizeof(DOS_PSP)));
990
991 /* Get the relocation table */
992 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
993
994 /* Perform relocations */
995 for (i = 0; i < Header->e_crlc; i++)
996 {
997 /* Get a pointer to the word that needs to be patched */
998 RelocWord = (PWORD)((ULONG_PTR)BaseAddress
999 + TO_LINEAR(Segment + HIWORD(RelocationTable[i]),
1000 0x100 + LOWORD(RelocationTable[i])));
1001
1002 /* Add the number of the EXE segment to it */
1003 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
1004 }
1005
1006 /* Set the initial segment registers */
1007 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
1008 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
1009
1010 /* Set the stack to the location from the header */
1011 EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
1012 Header->e_sp);
1013
1014 /* Execute */
1015 CurrentPsp = Segment;
1016 DiskTransferArea = MAKELONG(0x80, Segment);
1017 EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
1018 Header->e_ip);
1019
1020 Success = TRUE;
1021 }
1022 else
1023 {
1024 /* COM file */
1025
1026 /* Find the maximum amount of memory that can be allocated */
1027 DosAllocateMemory(0xFFFF, &MaxAllocSize);
1028
1029 /* Make sure it's enough for the whole program and the PSP */
1030 if ((MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP))) goto Cleanup;
1031
1032 /* Allocate all of it */
1033 Segment = DosAllocateMemory(MaxAllocSize, NULL);
1034 if (Segment == 0) goto Cleanup;
1035
1036 /* The process owns its own memory */
1037 DosChangeMemoryOwner(Segment, Segment);
1038 DosChangeMemoryOwner(EnvBlock, Segment);
1039
1040 /* Copy the program to Segment:0100 */
1041 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
1042 + TO_LINEAR(Segment, 0x100)),
1043 Address,
1044 FileSize);
1045
1046 /* Initialize the PSP */
1047 DosInitializePsp(Segment,
1048 CommandLine,
1049 (FileSize + sizeof(DOS_PSP)) >> 4,
1050 EnvBlock);
1051
1052 /* Set the initial segment registers */
1053 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
1054 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
1055
1056 /* Set the stack to the last word of the segment */
1057 EmulatorSetStack(Segment, 0xFFFE);
1058
1059 /* Execute */
1060 CurrentPsp = Segment;
1061 DiskTransferArea = MAKELONG(0x80, Segment);
1062 EmulatorExecute(Segment, 0x100);
1063
1064 Success = TRUE;
1065 }
1066
1067 Cleanup:
1068 if (!Success)
1069 {
1070 /* It was not successful, cleanup the DOS memory */
1071 if (AllocatedEnvBlock) DosFreeMemory(EnvBlock);
1072 if (Segment) DosFreeMemory(Segment);
1073 }
1074
1075 /* Unmap the file*/
1076 if (Address != NULL) UnmapViewOfFile(Address);
1077
1078 /* Close the file mapping object */
1079 if (FileMapping != NULL) CloseHandle(FileMapping);
1080
1081 /* Close the file handle */
1082 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
1083
1084 return Success;
1085 }
1086
1087 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
1088 {
1089 WORD i;
1090 WORD McbSegment = FIRST_MCB_SEGMENT;
1091 PDOS_MCB CurrentMcb;
1092 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
1093 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
1094
1095 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1096 Psp,
1097 ReturnCode);
1098
1099 /* Check if this PSP is it's own parent */
1100 if (PspBlock->ParentPsp == Psp) goto Done;
1101
1102 for (i = 0; i < PspBlock->HandleTableSize; i++)
1103 {
1104 /* Close the handle */
1105 DosCloseHandle(i);
1106 }
1107
1108 /* Free the memory used by the process */
1109 while (TRUE)
1110 {
1111 /* Get a pointer to the MCB */
1112 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
1113
1114 /* Make sure the MCB is valid */
1115 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
1116
1117 /* If this block was allocated by the process, free it */
1118 if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment);
1119
1120 /* If this was the last block, quit */
1121 if (CurrentMcb->BlockType == 'Z') break;
1122
1123 /* Update the segment and continue */
1124 McbSegment += CurrentMcb->Size + 1;
1125 }
1126
1127 Done:
1128 /* Restore the interrupt vectors */
1129 IntVecTable[0x22] = PspBlock->TerminateAddress;
1130 IntVecTable[0x23] = PspBlock->BreakAddress;
1131 IntVecTable[0x24] = PspBlock->CriticalAddress;
1132
1133 /* Update the current PSP */
1134 if (Psp == CurrentPsp)
1135 {
1136 CurrentPsp = PspBlock->ParentPsp;
1137 if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
1138 }
1139
1140 /* Return control to the parent process */
1141 EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
1142 LOWORD(PspBlock->TerminateAddress));
1143 }
1144
1145 CHAR DosReadCharacter(VOID)
1146 {
1147 CHAR Character = '\0';
1148 WORD BytesRead;
1149
1150 /* Use the file reading function */
1151 DosReadFile(DOS_INPUT_HANDLE, &Character, sizeof(CHAR), &BytesRead);
1152
1153 return Character;
1154 }
1155
1156 VOID DosPrintCharacter(CHAR Character)
1157 {
1158 WORD BytesWritten;
1159
1160 /* Use the file writing function */
1161 DosWriteFile(DOS_OUTPUT_HANDLE, &Character, sizeof(CHAR), &BytesWritten);
1162 }
1163
1164 BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
1165 {
1166 HANDLE Handle = DosGetRealHandle(FileHandle);
1167
1168 if (Handle == INVALID_HANDLE_VALUE)
1169 {
1170 /* Doesn't exist */
1171 DosLastError = ERROR_FILE_NOT_FOUND;
1172 return FALSE;
1173 }
1174
1175 switch (ControlCode)
1176 {
1177 /* Get Device Information */
1178 case 0x00:
1179 {
1180 WORD InfoWord = 0;
1181
1182 if (Handle == DosSystemFileTable[0])
1183 {
1184 /* Console input */
1185 InfoWord |= 1 << 0;
1186 }
1187 else if (Handle == DosSystemFileTable[1])
1188 {
1189 /* Console output */
1190 InfoWord |= 1 << 1;
1191 }
1192
1193 /* It is a character device */
1194 InfoWord |= 1 << 7;
1195
1196 /* Return the device information word */
1197 EmulatorSetRegister(EMULATOR_REG_DX, InfoWord);
1198
1199 return TRUE;
1200 }
1201
1202 /* Unsupported control code */
1203 default:
1204 {
1205 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode);
1206
1207 DosLastError = ERROR_INVALID_PARAMETER;
1208 return FALSE;
1209 }
1210 }
1211 }
1212
1213 VOID DosInt20h(LPWORD Stack)
1214 {
1215 /* This is the exit interrupt */
1216 DosTerminateProcess(Stack[STACK_CS], 0);
1217 }
1218
1219 VOID DosInt21h(LPWORD Stack)
1220 {
1221 INT i;
1222 CHAR Character;
1223 SYSTEMTIME SystemTime;
1224 PCHAR String;
1225 PDOS_INPUT_BUFFER InputBuffer;
1226 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
1227 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
1228 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
1229 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
1230 WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
1231 WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
1232
1233 /* Check the value in the AH register */
1234 switch (HIBYTE(Eax))
1235 {
1236 /* Terminate Program */
1237 case 0x00:
1238 {
1239 DosTerminateProcess(Stack[STACK_CS], 0);
1240 break;
1241 }
1242
1243 /* Read Character And Echo */
1244 case 0x01:
1245 {
1246 Character = DosReadCharacter();
1247 DosPrintCharacter(Character);
1248 EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
1249 break;
1250 }
1251
1252 /* Print Character */
1253 case 0x02:
1254 {
1255 DosPrintCharacter(LOBYTE(Edx));
1256 break;
1257 }
1258
1259 /* Read Character Without Echo */
1260 case 0x07:
1261 case 0x08:
1262 {
1263 EmulatorSetRegister(EMULATOR_REG_AX,
1264 (Eax & 0xFFFFFF00) | DosReadCharacter());
1265 break;
1266 }
1267
1268 /* Print String */
1269 case 0x09:
1270 {
1271 String = (PCHAR)((ULONG_PTR)BaseAddress
1272 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1273
1274 while ((*String) != '$')
1275 {
1276 DosPrintCharacter(*String);
1277 String++;
1278 }
1279
1280 break;
1281 }
1282
1283 /* Read Buffered Input */
1284 case 0x0A:
1285 {
1286 InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
1287 + TO_LINEAR(DataSegment,
1288 LOWORD(Edx)));
1289
1290 InputBuffer->Length = 0;
1291 for (i = 0; i < InputBuffer->MaxLength; i ++)
1292 {
1293 Character = DosReadCharacter();
1294 DosPrintCharacter(Character);
1295 InputBuffer->Buffer[InputBuffer->Length] = Character;
1296 if (Character == '\r') break;
1297 InputBuffer->Length++;
1298 }
1299
1300 break;
1301 }
1302
1303 /* Set Disk Transfer Area */
1304 case 0x1A:
1305 {
1306 DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment);
1307 break;
1308 }
1309
1310 /* Set Interrupt Vector */
1311 case 0x25:
1312 {
1313 DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment);
1314
1315 /* Write the new far pointer to the IDT */
1316 ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer;
1317
1318 break;
1319 }
1320
1321 /* Get system date */
1322 case 0x2A:
1323 {
1324 GetLocalTime(&SystemTime);
1325 EmulatorSetRegister(EMULATOR_REG_CX,
1326 (Ecx & 0xFFFF0000) | SystemTime.wYear);
1327 EmulatorSetRegister(EMULATOR_REG_DX,
1328 (Edx & 0xFFFF0000)
1329 | (SystemTime.wMonth << 8)
1330 | SystemTime.wDay);
1331 EmulatorSetRegister(EMULATOR_REG_AX,
1332 (Eax & 0xFFFFFF00) | SystemTime.wDayOfWeek);
1333 break;
1334 }
1335
1336 /* Set system date */
1337 case 0x2B:
1338 {
1339 GetLocalTime(&SystemTime);
1340 SystemTime.wYear = LOWORD(Ecx);
1341 SystemTime.wMonth = HIBYTE(Edx);
1342 SystemTime.wDay = LOBYTE(Edx);
1343
1344 if (SetLocalTime(&SystemTime))
1345 {
1346 /* Return success */
1347 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
1348 }
1349 else
1350 {
1351 /* Return failure */
1352 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
1353 }
1354
1355 break;
1356 }
1357
1358 /* Get system time */
1359 case 0x2C:
1360 {
1361 GetLocalTime(&SystemTime);
1362 EmulatorSetRegister(EMULATOR_REG_CX,
1363 (Ecx & 0xFFFF0000)
1364 | (SystemTime.wHour << 8)
1365 | SystemTime.wMinute);
1366 EmulatorSetRegister(EMULATOR_REG_DX,
1367 (Edx & 0xFFFF0000)
1368 | (SystemTime.wSecond << 8)
1369 | (SystemTime.wMilliseconds / 10));
1370 break;
1371 }
1372
1373 /* Set system time */
1374 case 0x2D:
1375 {
1376 GetLocalTime(&SystemTime);
1377 SystemTime.wHour = HIBYTE(Ecx);
1378 SystemTime.wMinute = LOBYTE(Ecx);
1379 SystemTime.wSecond = HIBYTE(Edx);
1380 SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
1381
1382 if (SetLocalTime(&SystemTime))
1383 {
1384 /* Return success */
1385 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
1386 }
1387 else
1388 {
1389 /* Return failure */
1390 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
1391 }
1392
1393 break;
1394 }
1395
1396 /* Get Disk Transfer Area */
1397 case 0x2F:
1398 {
1399 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea));
1400 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea));
1401
1402 break;
1403 }
1404
1405 /* Get DOS Version */
1406 case 0x30:
1407 {
1408 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1409
1410 EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion);
1411 break;
1412 }
1413
1414 /* Get Interrupt Vector */
1415 case 0x35:
1416 {
1417 DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)];
1418
1419 /* Read the address from the IDT into ES:BX */
1420 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer));
1421 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer));
1422
1423 break;
1424 }
1425
1426 /* Create Directory */
1427 case 0x39:
1428 {
1429 String = (PCHAR)((ULONG_PTR)BaseAddress
1430 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1431
1432 if (CreateDirectoryA(String, NULL))
1433 {
1434 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1435 }
1436 else
1437 {
1438 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1439 EmulatorSetRegister(EMULATOR_REG_AX,
1440 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1441 }
1442
1443 break;
1444 }
1445
1446 /* Remove Directory */
1447 case 0x3A:
1448 {
1449 String = (PCHAR)((ULONG_PTR)BaseAddress
1450 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1451
1452 if (RemoveDirectoryA(String))
1453 {
1454 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1455 }
1456 else
1457 {
1458 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1459 EmulatorSetRegister(EMULATOR_REG_AX,
1460 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1461 }
1462
1463
1464 break;
1465 }
1466
1467 /* Set Current Directory */
1468 case 0x3B:
1469 {
1470 String = (PCHAR)((ULONG_PTR)BaseAddress
1471 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1472
1473 if (SetCurrentDirectoryA(String))
1474 {
1475 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1476 }
1477 else
1478 {
1479 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1480 EmulatorSetRegister(EMULATOR_REG_AX,
1481 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1482 }
1483
1484 break;
1485 }
1486
1487 /* Create File */
1488 case 0x3C:
1489 {
1490 WORD FileHandle;
1491 WORD ErrorCode = DosCreateFile(&FileHandle,
1492 (LPCSTR)(ULONG_PTR)BaseAddress
1493 + TO_LINEAR(DataSegment, LOWORD(Edx)),
1494 LOWORD(Ecx));
1495
1496 if (ErrorCode == 0)
1497 {
1498 /* Clear CF */
1499 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1500
1501 /* Return the handle in AX */
1502 EmulatorSetRegister(EMULATOR_REG_AX,
1503 (Eax & 0xFFFF0000) | FileHandle);
1504 }
1505 else
1506 {
1507 /* Set CF */
1508 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1509
1510 /* Return the error code in AX */
1511 EmulatorSetRegister(EMULATOR_REG_AX,
1512 (Eax & 0xFFFF0000) | ErrorCode);
1513 }
1514
1515 break;
1516 }
1517
1518 /* Open File */
1519 case 0x3D:
1520 {
1521 WORD FileHandle;
1522 WORD ErrorCode = DosCreateFile(&FileHandle,
1523 (LPCSTR)(ULONG_PTR)BaseAddress
1524 + TO_LINEAR(DataSegment, LOWORD(Edx)),
1525 LOBYTE(Eax));
1526
1527 if (ErrorCode == 0)
1528 {
1529 /* Clear CF */
1530 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1531
1532 /* Return the handle in AX */
1533 EmulatorSetRegister(EMULATOR_REG_AX,
1534 (Eax & 0xFFFF0000) | FileHandle);
1535 }
1536 else
1537 {
1538 /* Set CF */
1539 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1540
1541 /* Return the error code in AX */
1542 EmulatorSetRegister(EMULATOR_REG_AX,
1543 (Eax & 0xFFFF0000) | ErrorCode);
1544 }
1545
1546 break;
1547 }
1548
1549 /* Close File */
1550 case 0x3E:
1551 {
1552 if (DosCloseHandle(LOWORD(Ebx)))
1553 {
1554 /* Clear CF */
1555 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1556 }
1557 else
1558 {
1559 /* Set CF */
1560 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1561
1562 /* Return the error code in AX */
1563 EmulatorSetRegister(EMULATOR_REG_AX,
1564 (Eax & 0xFFFF0000) | ERROR_INVALID_HANDLE);
1565 }
1566
1567 break;
1568 }
1569
1570 /* Read File */
1571 case 0x3F:
1572 {
1573 WORD BytesRead = 0;
1574 WORD ErrorCode = DosReadFile(LOWORD(Ebx),
1575 (LPVOID)((ULONG_PTR)BaseAddress
1576 + TO_LINEAR(DataSegment, LOWORD(Edx))),
1577 LOWORD(Ecx),
1578 &BytesRead);
1579
1580 if (ErrorCode == 0)
1581 {
1582 /* Clear CF */
1583 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1584
1585 /* Return the number of bytes read in AX */
1586 EmulatorSetRegister(EMULATOR_REG_AX,
1587 (Eax & 0xFFFF0000) | BytesRead);
1588 }
1589 else
1590 {
1591 /* Set CF */
1592 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1593
1594 /* Return the error code in AX */
1595 EmulatorSetRegister(EMULATOR_REG_AX,
1596 (Eax & 0xFFFF0000) | ErrorCode);
1597 }
1598 break;
1599 }
1600
1601 /* Write File */
1602 case 0x40:
1603 {
1604 WORD BytesWritten = 0;
1605 WORD ErrorCode = DosWriteFile(LOWORD(Ebx),
1606 (LPVOID)((ULONG_PTR)BaseAddress
1607 + TO_LINEAR(DataSegment, LOWORD(Edx))),
1608 LOWORD(Ecx),
1609 &BytesWritten);
1610
1611 if (ErrorCode == 0)
1612 {
1613 /* Clear CF */
1614 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1615
1616 /* Return the number of bytes written in AX */
1617 EmulatorSetRegister(EMULATOR_REG_AX,
1618 (Eax & 0xFFFF0000) | BytesWritten);
1619 }
1620 else
1621 {
1622 /* Set CF */
1623 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1624
1625 /* Return the error code in AX */
1626 EmulatorSetRegister(EMULATOR_REG_AX,
1627 (Eax & 0xFFFF0000) | ErrorCode);
1628 }
1629
1630 break;
1631 }
1632
1633 /* Delete File */
1634 case 0x41:
1635 {
1636 LPSTR FileName = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(DataSegment, Edx));
1637
1638 /* Call the API function */
1639 if (DeleteFileA(FileName)) Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1640 else
1641 {
1642 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1643 EmulatorSetRegister(EMULATOR_REG_AX, GetLastError());
1644 }
1645
1646 break;
1647 }
1648
1649 /* Seek File */
1650 case 0x42:
1651 {
1652 DWORD NewLocation;
1653 WORD ErrorCode = DosSeekFile(LOWORD(Ebx),
1654 MAKELONG(LOWORD(Edx), LOWORD(Ecx)),
1655 LOBYTE(Eax),
1656 &NewLocation);
1657
1658 if (ErrorCode == 0)
1659 {
1660 /* Clear CF */
1661 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1662
1663 /* Return the new offset in DX:AX */
1664 EmulatorSetRegister(EMULATOR_REG_DX,
1665 (Edx & 0xFFFF0000) | HIWORD(NewLocation));
1666 EmulatorSetRegister(EMULATOR_REG_AX,
1667 (Eax & 0xFFFF0000) | LOWORD(NewLocation));
1668 }
1669 else
1670 {
1671 /* Set CF */
1672 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1673
1674 /* Return the error code in AX */
1675 EmulatorSetRegister(EMULATOR_REG_AX,
1676 (Eax & 0xFFFF0000) | ErrorCode);
1677 }
1678
1679 break;
1680 }
1681
1682 /* Get/Set File Attributes */
1683 case 0x43:
1684 {
1685 DWORD Attributes;
1686 LPSTR FileName = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(DataSegment, Edx));
1687
1688 if (LOBYTE(Eax) == 0x00)
1689 {
1690 /* Get the attributes */
1691 Attributes = GetFileAttributesA(FileName);
1692
1693 /* Check if it failed */
1694 if (Attributes == INVALID_FILE_ATTRIBUTES)
1695 {
1696 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1697 EmulatorSetRegister(EMULATOR_REG_AX, GetLastError());
1698
1699 break;
1700 }
1701
1702 /* Return the attributes that DOS can understand */
1703 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1704 EmulatorSetRegister(EMULATOR_REG_CX,
1705 (Ecx & 0xFFFFFF00) | LOBYTE(Attributes));
1706 }
1707 else if (LOBYTE(Eax) == 0x01)
1708 {
1709 /* Try to set the attributes */
1710 if (SetFileAttributesA(FileName, LOBYTE(Ecx)))
1711 {
1712 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1713 }
1714 else
1715 {
1716 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1717 EmulatorSetRegister(EMULATOR_REG_AX, GetLastError());
1718 }
1719 }
1720 else
1721 {
1722 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1723 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_FUNCTION);
1724 }
1725
1726 break;
1727 }
1728
1729 /* IOCTL */
1730 case 0x44:
1731 {
1732 if (DosHandleIoctl(LOBYTE(Eax), LOWORD(Ebx)))
1733 {
1734 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1735 }
1736 else
1737 {
1738 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1739 EmulatorSetRegister(EMULATOR_REG_AX, DosLastError);
1740 }
1741
1742 break;
1743 }
1744
1745 /* Duplicate Handle */
1746 case 0x45:
1747 {
1748 WORD NewHandle;
1749 HANDLE Handle = DosGetRealHandle(LOWORD(Ebx));
1750
1751 if (Handle != INVALID_HANDLE_VALUE)
1752 {
1753 /* The handle is invalid */
1754 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1755 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_HANDLE);
1756
1757 break;
1758 }
1759
1760 /* Open a new handle to the same entry */
1761 NewHandle = DosOpenHandle(Handle);
1762
1763 if (NewHandle == INVALID_DOS_HANDLE)
1764 {
1765 /* Too many files open */
1766 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1767 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_TOO_MANY_OPEN_FILES);
1768
1769 break;
1770 }
1771
1772 /* Return the result */
1773 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1774 EmulatorSetRegister(EMULATOR_REG_AX, NewHandle);
1775
1776 break;
1777 }
1778
1779 /* Force Duplicate Handle */
1780 case 0x46:
1781 {
1782 if (DosDuplicateHandle(LOWORD(Ebx), LOWORD(Ecx)))
1783 {
1784 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1785 }
1786 else
1787 {
1788 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1789 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_HANDLE);
1790 }
1791
1792 break;
1793 }
1794
1795 /* Allocate Memory */
1796 case 0x48:
1797 {
1798 WORD MaxAvailable = 0;
1799 WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
1800
1801 if (Segment != 0)
1802 {
1803 EmulatorSetRegister(EMULATOR_REG_AX, Segment);
1804 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1805 }
1806 else
1807 {
1808 EmulatorSetRegister(EMULATOR_REG_AX, DosLastError);
1809 EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
1810 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1811 }
1812
1813 break;
1814 }
1815
1816 /* Free Memory */
1817 case 0x49:
1818 {
1819 if (DosFreeMemory(ExtSegment))
1820 {
1821 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1822 }
1823 else
1824 {
1825 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_ARENA_TRASHED);
1826 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1827 }
1828
1829 break;
1830 }
1831
1832 /* Resize Memory Block */
1833 case 0x4A:
1834 {
1835 WORD Size;
1836
1837 if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size))
1838 {
1839 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1840 }
1841 else
1842 {
1843 EmulatorSetRegister(EMULATOR_REG_AX, DosLastError);
1844 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1845 EmulatorSetRegister(EMULATOR_REG_BX, Size);
1846 }
1847
1848 break;
1849 }
1850
1851 /* Terminate With Return Code */
1852 case 0x4C:
1853 {
1854 DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
1855 break;
1856 }
1857
1858 /* Get Current Process */
1859 case 0x51:
1860 {
1861 EmulatorSetRegister(EMULATOR_REG_BX, CurrentPsp);
1862
1863 break;
1864 }
1865
1866 /* Get/Set Memory Management Options */
1867 case 0x58:
1868 {
1869 if (LOBYTE(Eax) == 0x00)
1870 {
1871 /* Get allocation strategy */
1872
1873 EmulatorSetRegister(EMULATOR_REG_AX, DosAllocStrategy);
1874 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1875 }
1876 else if (LOBYTE(Eax) == 0x01)
1877 {
1878 /* Set allocation strategy */
1879
1880 if ((LOBYTE(Ebx) & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
1881 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
1882 {
1883 /* Can't set both */
1884 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER);
1885 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1886 break;
1887 }
1888
1889 if ((LOBYTE(Ebx) & 0x3F) > DOS_ALLOC_LAST_FIT)
1890 {
1891 /* Invalid allocation strategy */
1892 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER);
1893 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1894 break;
1895 }
1896
1897 DosAllocStrategy = LOBYTE(Ebx);
1898 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1899 }
1900 else if (LOBYTE(Eax) == 0x02)
1901 {
1902 /* Get UMB link state */
1903
1904 Eax &= 0xFFFFFF00;
1905 if (DosUmbLinked) Eax |= 1;
1906 EmulatorSetRegister(EMULATOR_REG_AX, Eax);
1907 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1908 }
1909 else if (LOBYTE(Eax) == 0x03)
1910 {
1911 /* Set UMB link state */
1912
1913 if (Ebx) DosLinkUmb();
1914 else DosUnlinkUmb();
1915 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1916 }
1917 else
1918 {
1919 /* Invalid or unsupported function */
1920
1921 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1922 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_FUNCTION);
1923 }
1924
1925 break;
1926 }
1927
1928 /* Unsupported */
1929 default:
1930 {
1931 DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
1932 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1933 }
1934 }
1935 }
1936
1937 VOID DosBreakInterrupt(LPWORD Stack)
1938 {
1939 VdmRunning = FALSE;
1940 }
1941
1942 BOOLEAN DosInitialize(VOID)
1943 {
1944 BYTE i;
1945 PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
1946 FILE *Stream;
1947 WCHAR Buffer[256];
1948 LPWSTR SourcePtr, Environment;
1949 LPSTR AsciiString;
1950 LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
1951 DWORD AsciiSize;
1952
1953 /* Initialize the MCB */
1954 Mcb->BlockType = 'Z';
1955 Mcb->Size = USER_MEMORY_SIZE;
1956 Mcb->OwnerPsp = 0;
1957
1958 /* Initialize the link MCB to the UMB area */
1959 Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT + USER_MEMORY_SIZE + 1);
1960 Mcb->BlockType = 'M';
1961 Mcb->Size = UMB_START_SEGMENT - FIRST_MCB_SEGMENT - USER_MEMORY_SIZE - 2;
1962 Mcb->OwnerPsp = SYSTEM_PSP;
1963
1964 /* Initialize the UMB area */
1965 Mcb = SEGMENT_TO_MCB(UMB_START_SEGMENT);
1966 Mcb->BlockType = 'Z';
1967 Mcb->Size = UMB_END_SEGMENT - UMB_START_SEGMENT;
1968 Mcb->OwnerPsp = 0;
1969
1970 /* Get the environment strings */
1971 SourcePtr = Environment = GetEnvironmentStringsW();
1972 if (Environment == NULL) return FALSE;
1973
1974 /* Fill the DOS system environment block */
1975 while (*SourcePtr)
1976 {
1977 /* Get the size of the ASCII string */
1978 AsciiSize = WideCharToMultiByte(CP_ACP,
1979 0,
1980 SourcePtr,
1981 -1,
1982 NULL,
1983 0,
1984 NULL,
1985 NULL);
1986
1987 /* Allocate memory for the ASCII string */
1988 AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
1989 if (AsciiString == NULL)
1990 {
1991 FreeEnvironmentStringsW(Environment);
1992 return FALSE;
1993 }
1994
1995 /* Convert to ASCII */
1996 WideCharToMultiByte(CP_ACP,
1997 0,
1998 SourcePtr,
1999 -1,
2000 AsciiString,
2001 AsciiSize,
2002 NULL,
2003 NULL);
2004
2005 /* Copy the string into DOS memory */
2006 strcpy(DestPtr, AsciiString);
2007
2008 /* Move to the next string */
2009 SourcePtr += wcslen(SourcePtr) + 1;
2010 DestPtr += strlen(AsciiString);
2011 *(DestPtr++) = 0;
2012
2013 /* Free the memory */
2014 HeapFree(GetProcessHeap(), 0, AsciiString);
2015 }
2016 *DestPtr = 0;
2017
2018 /* Free the memory allocated for environment strings */
2019 FreeEnvironmentStringsW(Environment);
2020
2021 /* Read CONFIG.SYS */
2022 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
2023 if (Stream != NULL)
2024 {
2025 while (fgetws(Buffer, 256, Stream))
2026 {
2027 // TODO: Parse the line
2028 }
2029 fclose(Stream);
2030 }
2031
2032 /* Initialize the SFT */
2033 for (i = 0; i < DOS_SFT_SIZE; i++)
2034 {
2035 DosSystemFileTable[i] = INVALID_HANDLE_VALUE;
2036 DosSftRefCount[i] = 0;
2037 }
2038
2039 /* Get handles to standard I/O devices */
2040 DosSystemFileTable[0] = GetStdHandle(STD_INPUT_HANDLE);
2041 DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE);
2042 DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE);
2043
2044 return TRUE;
2045 }
2046
2047 /* EOF */