[NTVDM]
[reactos.git] / reactos / subsystems / ntvdm / dos / dos32krnl / dos.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: dos/dos32krnl/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 "emulator.h"
14 #include "callback.h"
15
16 #include "dos.h"
17 #include "dos/dem.h"
18
19 #include "bios/bios.h"
20 #include "registers.h"
21
22 /* PRIVATE VARIABLES **********************************************************/
23
24 CALLBACK16 DosContext;
25
26 static WORD CurrentPsp = SYSTEM_PSP;
27 static WORD DosLastError = 0;
28 static DWORD DiskTransferArea;
29 /*static*/ BYTE CurrentDrive;
30 static CHAR LastDrive = 'E';
31 static CHAR CurrentDirectories[NUM_DRIVES][DOS_DIR_LENGTH];
32
33 static struct
34 {
35 HANDLE Handle;
36 WORD RefCount;
37 } DosSystemFileTable[DOS_SFT_SIZE];
38
39 static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
40 static BOOLEAN DosUmbLinked = FALSE;
41 static WORD DosErrorLevel = 0x0000;
42
43 /* PRIVATE FUNCTIONS **********************************************************/
44
45 /*
46 * Memory management functions
47 */
48 static VOID DosCombineFreeBlocks(WORD StartBlock)
49 {
50 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
51
52 /* If this is the last block or it's not free, quit */
53 if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
54
55 while (TRUE)
56 {
57 /* Get a pointer to the next MCB */
58 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
59
60 /* Check if the next MCB is free */
61 if (NextMcb->OwnerPsp == 0)
62 {
63 /* Combine them */
64 CurrentMcb->Size += NextMcb->Size + 1;
65 CurrentMcb->BlockType = NextMcb->BlockType;
66 NextMcb->BlockType = 'I';
67 }
68 else
69 {
70 /* No more adjoining free blocks */
71 break;
72 }
73 }
74 }
75
76 static WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
77 {
78 WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
79 PDOS_MCB CurrentMcb, NextMcb;
80 BOOLEAN SearchUmb = FALSE;
81
82 DPRINT("DosAllocateMemory: Size 0x%04X\n", Size);
83
84 if (DosUmbLinked && (DosAllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)))
85 {
86 /* Search UMB first */
87 Segment = UMB_START_SEGMENT;
88 SearchUmb = TRUE;
89 }
90
91 while (TRUE)
92 {
93 /* Get a pointer to the MCB */
94 CurrentMcb = SEGMENT_TO_MCB(Segment);
95
96 /* Make sure it's valid */
97 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
98 {
99 DPRINT("The DOS memory arena is corrupted!\n");
100 DosLastError = ERROR_ARENA_TRASHED;
101 return 0;
102 }
103
104 /* Only check free blocks */
105 if (CurrentMcb->OwnerPsp != 0) goto Next;
106
107 /* Combine this free block with adjoining free blocks */
108 DosCombineFreeBlocks(Segment);
109
110 /* Update the maximum block size */
111 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
112
113 /* Check if this block is big enough */
114 if (CurrentMcb->Size < Size) goto Next;
115
116 switch (DosAllocStrategy & 0x3F)
117 {
118 case DOS_ALLOC_FIRST_FIT:
119 {
120 /* For first fit, stop immediately */
121 Result = Segment;
122 goto Done;
123 }
124
125 case DOS_ALLOC_BEST_FIT:
126 {
127 /* For best fit, update the smallest block found so far */
128 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
129 {
130 Result = Segment;
131 }
132
133 break;
134 }
135
136 case DOS_ALLOC_LAST_FIT:
137 {
138 /* For last fit, make the current block the result, but keep searching */
139 Result = Segment;
140 break;
141 }
142 }
143
144 Next:
145 /* If this was the last MCB in the chain, quit */
146 if (CurrentMcb->BlockType == 'Z')
147 {
148 /* Check if nothing was found while searching through UMBs */
149 if ((Result == 0) && SearchUmb && (DosAllocStrategy & DOS_ALLOC_HIGH_LOW))
150 {
151 /* Search low memory */
152 Segment = FIRST_MCB_SEGMENT;
153 continue;
154 }
155
156 break;
157 }
158
159 /* Otherwise, update the segment and continue */
160 Segment += CurrentMcb->Size + 1;
161 }
162
163 Done:
164
165 /* If we didn't find a free block, return 0 */
166 if (Result == 0)
167 {
168 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
169 if (MaxAvailable) *MaxAvailable = MaxSize;
170 return 0;
171 }
172
173 /* Get a pointer to the MCB */
174 CurrentMcb = SEGMENT_TO_MCB(Result);
175
176 /* Check if the block is larger than requested */
177 if (CurrentMcb->Size > Size)
178 {
179 /* It is, split it into two blocks */
180 NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
181
182 /* Initialize the new MCB structure */
183 NextMcb->BlockType = CurrentMcb->BlockType;
184 NextMcb->Size = CurrentMcb->Size - Size - 1;
185 NextMcb->OwnerPsp = 0;
186
187 /* Update the current block */
188 CurrentMcb->BlockType = 'M';
189 CurrentMcb->Size = Size;
190 }
191
192 /* Take ownership of the block */
193 CurrentMcb->OwnerPsp = CurrentPsp;
194
195 /* Return the segment of the data portion of the block */
196 return Result + 1;
197 }
198
199 static BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
200 {
201 BOOLEAN Success = TRUE;
202 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
203 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
204
205 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
206 BlockData,
207 NewSize);
208
209 /* Make sure this is a valid, allocated block */
210 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
211 {
212 Success = FALSE;
213 DosLastError = ERROR_INVALID_HANDLE;
214 goto Done;
215 }
216
217 ReturnSize = Mcb->Size;
218
219 /* Check if we need to expand or contract the block */
220 if (NewSize > Mcb->Size)
221 {
222 /* We can't expand the last block */
223 if (Mcb->BlockType != 'M')
224 {
225 Success = FALSE;
226 goto Done;
227 }
228
229 /* Get the pointer and segment of the next MCB */
230 NextSegment = Segment + Mcb->Size + 1;
231 NextMcb = SEGMENT_TO_MCB(NextSegment);
232
233 /* Make sure the next segment is free */
234 if (NextMcb->OwnerPsp != 0)
235 {
236 DPRINT("Cannot expand memory block: next segment is not free!\n");
237 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
238 Success = FALSE;
239 goto Done;
240 }
241
242 /* Combine this free block with adjoining free blocks */
243 DosCombineFreeBlocks(NextSegment);
244
245 /* Set the maximum possible size of the block */
246 ReturnSize += NextMcb->Size + 1;
247
248 /* Maximize the current block */
249 Mcb->Size = ReturnSize;
250 Mcb->BlockType = NextMcb->BlockType;
251
252 /* Invalidate the next block */
253 NextMcb->BlockType = 'I';
254
255 /* Check if the block is larger than requested */
256 if (Mcb->Size > NewSize)
257 {
258 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
259 Mcb->Size,
260 NewSize);
261
262 /* It is, split it into two blocks */
263 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
264
265 /* Initialize the new MCB structure */
266 NextMcb->BlockType = Mcb->BlockType;
267 NextMcb->Size = Mcb->Size - NewSize - 1;
268 NextMcb->OwnerPsp = 0;
269
270 /* Update the current block */
271 Mcb->BlockType = 'M';
272 Mcb->Size = NewSize;
273 }
274 }
275 else if (NewSize < Mcb->Size)
276 {
277 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
278 Mcb->Size,
279 NewSize);
280
281 /* Just split the block */
282 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
283 NextMcb->BlockType = Mcb->BlockType;
284 NextMcb->Size = Mcb->Size - NewSize - 1;
285 NextMcb->OwnerPsp = 0;
286
287 /* Update the MCB */
288 Mcb->BlockType = 'M';
289 Mcb->Size = NewSize;
290 }
291
292 Done:
293 /* Check if the operation failed */
294 if (!Success)
295 {
296 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
297 ReturnSize);
298
299 /* Return the maximum possible size */
300 if (MaxAvailable) *MaxAvailable = ReturnSize;
301 }
302
303 return Success;
304 }
305
306 static BOOLEAN DosFreeMemory(WORD BlockData)
307 {
308 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
309
310 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
311
312 /* Make sure the MCB is valid */
313 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
314 {
315 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
316 return FALSE;
317 }
318
319 /* Mark the block as free */
320 Mcb->OwnerPsp = 0;
321
322 return TRUE;
323 }
324
325 static BOOLEAN DosLinkUmb(VOID)
326 {
327 DWORD Segment = FIRST_MCB_SEGMENT;
328 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
329
330 DPRINT("Linking UMB\n");
331
332 /* Check if UMBs are already linked */
333 if (DosUmbLinked) return FALSE;
334
335 /* Find the last block */
336 while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
337 {
338 Segment += Mcb->Size + 1;
339 Mcb = SEGMENT_TO_MCB(Segment);
340 }
341
342 /* Make sure it's valid */
343 if (Mcb->BlockType != 'Z') return FALSE;
344
345 /* Connect the MCB with the UMB chain */
346 Mcb->BlockType = 'M';
347
348 DosUmbLinked = TRUE;
349 return TRUE;
350 }
351
352 static BOOLEAN DosUnlinkUmb(VOID)
353 {
354 DWORD Segment = FIRST_MCB_SEGMENT;
355 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
356
357 DPRINT("Unlinking UMB\n");
358
359 /* Check if UMBs are already unlinked */
360 if (!DosUmbLinked) return FALSE;
361
362 /* Find the block preceding the MCB that links it with the UMB chain */
363 while (Segment <= 0xFFFF)
364 {
365 if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
366 {
367 /* This is the last non-UMB segment */
368 break;
369 }
370
371 /* Advance to the next MCB */
372 Segment += Mcb->Size + 1;
373 Mcb = SEGMENT_TO_MCB(Segment);
374 }
375
376 /* Mark the MCB as the last MCB */
377 Mcb->BlockType = 'Z';
378
379 DosUmbLinked = FALSE;
380 return TRUE;
381 }
382
383 static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
384 {
385 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
386
387 /* Just set the owner */
388 Mcb->OwnerPsp = NewOwner;
389 }
390
391 static WORD DosCopyEnvironmentBlock(LPCVOID Environment, LPCSTR ProgramName)
392 {
393 PCHAR Ptr, DestBuffer = NULL;
394 ULONG TotalSize = 0;
395 WORD DestSegment;
396
397 Ptr = (PCHAR)Environment;
398
399 /* Calculate the size of the environment block */
400 while (*Ptr)
401 {
402 TotalSize += strlen(Ptr) + 1;
403 Ptr += strlen(Ptr) + 1;
404 }
405 TotalSize++;
406
407 /* Add the string buffer size */
408 TotalSize += strlen(ProgramName) + 1;
409
410 /* Allocate the memory for the environment block */
411 DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
412 if (!DestSegment) return 0;
413
414 Ptr = (PCHAR)Environment;
415
416 DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0);
417 while (*Ptr)
418 {
419 /* Copy the string */
420 strcpy(DestBuffer, Ptr);
421
422 /* Advance to the next string */
423 DestBuffer += strlen(Ptr);
424 Ptr += strlen(Ptr) + 1;
425
426 /* Put a zero after the string */
427 *(DestBuffer++) = 0;
428 }
429
430 /* Set the final zero */
431 *(DestBuffer++) = 0;
432
433 /* Copy the program name after the environment block */
434 strcpy(DestBuffer, ProgramName);
435
436 return DestSegment;
437 }
438
439
440
441
442
443
444 /* Taken from base/shell/cmd/console.c */
445 BOOL IsConsoleHandle(HANDLE hHandle)
446 {
447 DWORD dwMode;
448
449 /* Check whether the handle may be that of a console... */
450 if ((GetFileType(hHandle) & FILE_TYPE_CHAR) == 0) return FALSE;
451
452 /*
453 * It may be. Perform another test... The idea comes from the
454 * MSDN description of the WriteConsole API:
455 *
456 * "WriteConsole fails if it is used with a standard handle
457 * that is redirected to a file. If an application processes
458 * multilingual output that can be redirected, determine whether
459 * the output handle is a console handle (one method is to call
460 * the GetConsoleMode function and check whether it succeeds).
461 * If the handle is a console handle, call WriteConsole. If the
462 * handle is not a console handle, the output is redirected and
463 * you should call WriteFile to perform the I/O."
464 */
465 return GetConsoleMode(hHandle, &dwMode);
466 }
467
468 WORD DosOpenHandle(HANDLE Handle)
469 {
470 BYTE i;
471 WORD DosHandle;
472 PDOS_PSP PspBlock;
473 LPBYTE HandleTable;
474
475 /* The system PSP has no handle table */
476 if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
477
478 /* Get a pointer to the handle table */
479 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
480 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
481
482 /* Find a free entry in the JFT */
483 for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
484 {
485 if (HandleTable[DosHandle] == 0xFF) break;
486 }
487
488 /* If there are no free entries, fail */
489 if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
490
491 /* Check if the handle is already in the SFT */
492 for (i = 0; i < DOS_SFT_SIZE; i++)
493 {
494 /* Check if this is the same handle */
495 if (DosSystemFileTable[i].Handle != Handle) continue;
496
497 /* Already in the table, reference it */
498 DosSystemFileTable[i].RefCount++;
499
500 /* Set the JFT entry to that SFT index */
501 HandleTable[DosHandle] = i;
502
503 /* Return the new handle */
504 return DosHandle;
505 }
506
507 /* Add the handle to the SFT */
508 for (i = 0; i < DOS_SFT_SIZE; i++)
509 {
510 /* Make sure this is an empty table entry */
511 if (DosSystemFileTable[i].Handle != INVALID_HANDLE_VALUE) continue;
512
513 /* Initialize the empty table entry */
514 DosSystemFileTable[i].Handle = Handle;
515 DosSystemFileTable[i].RefCount = 1;
516
517 /* Set the JFT entry to that SFT index */
518 HandleTable[DosHandle] = i;
519
520 /* Return the new handle */
521 return DosHandle;
522 }
523
524 /* The SFT is full */
525 return INVALID_DOS_HANDLE;
526 }
527
528 HANDLE DosGetRealHandle(WORD DosHandle)
529 {
530 PDOS_PSP PspBlock;
531 LPBYTE HandleTable;
532
533 /* The system PSP has no handle table */
534 if (CurrentPsp == SYSTEM_PSP) return INVALID_HANDLE_VALUE;
535
536 /* Get a pointer to the handle table */
537 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
538 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
539
540 /* Make sure the handle is open */
541 if (HandleTable[DosHandle] == 0xFF) return INVALID_HANDLE_VALUE;
542
543 /* Return the Win32 handle */
544 return DosSystemFileTable[HandleTable[DosHandle]].Handle;
545 }
546
547 static VOID DosCopyHandleTable(LPBYTE DestinationTable)
548 {
549 INT i;
550 PDOS_PSP PspBlock;
551 LPBYTE SourceTable;
552
553 /* Clear the table first */
554 for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF;
555
556 /* Check if this is the initial process */
557 if (CurrentPsp == SYSTEM_PSP)
558 {
559 /* Set up the standard I/O devices */
560 for (i = 0; i <= 2; i++)
561 {
562 /* Set the index in the SFT */
563 DestinationTable[i] = (BYTE)i;
564
565 /* Increase the reference count */
566 DosSystemFileTable[i].RefCount++;
567 }
568
569 /* Done */
570 return;
571 }
572
573 /* Get the parent PSP block and handle table */
574 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
575 SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
576
577 /* Copy the first 20 handles into the new table */
578 for (i = 0; i < 20; i++)
579 {
580 DestinationTable[i] = SourceTable[i];
581
582 /* Increase the reference count */
583 DosSystemFileTable[SourceTable[i]].RefCount++;
584 }
585 }
586
587 static BOOLEAN DosResizeHandleTable(WORD NewSize)
588 {
589 PDOS_PSP PspBlock;
590 LPBYTE HandleTable;
591 WORD Segment;
592
593 /* Get the PSP block */
594 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
595
596 if (NewSize == PspBlock->HandleTableSize)
597 {
598 /* No change */
599 return TRUE;
600 }
601
602 if (PspBlock->HandleTableSize > 20)
603 {
604 /* Get the segment of the current table */
605 Segment = (LOWORD(PspBlock->HandleTablePtr) >> 4) + HIWORD(PspBlock->HandleTablePtr);
606
607 if (NewSize <= 20)
608 {
609 /* Get the current handle table */
610 HandleTable = FAR_POINTER(PspBlock->HandleTablePtr);
611
612 /* Copy it to the PSP */
613 RtlCopyMemory(PspBlock->HandleTable, HandleTable, NewSize);
614
615 /* Free the memory */
616 DosFreeMemory(Segment);
617
618 /* Update the handle table pointer and size */
619 PspBlock->HandleTableSize = NewSize;
620 PspBlock->HandleTablePtr = MAKELONG(0x18, CurrentPsp);
621 }
622 else
623 {
624 /* Resize the memory */
625 if (!DosResizeMemory(Segment, NewSize, NULL))
626 {
627 /* Unable to resize, try allocating it somewhere else */
628 Segment = DosAllocateMemory(NewSize, NULL);
629 if (Segment == 0) return FALSE;
630
631 /* Get the new handle table */
632 HandleTable = SEG_OFF_TO_PTR(Segment, 0);
633
634 /* Copy the handles to the new table */
635 RtlCopyMemory(HandleTable,
636 FAR_POINTER(PspBlock->HandleTablePtr),
637 PspBlock->HandleTableSize);
638
639 /* Update the handle table pointer */
640 PspBlock->HandleTablePtr = MAKELONG(0, Segment);
641 }
642
643 /* Update the handle table size */
644 PspBlock->HandleTableSize = NewSize;
645 }
646 }
647 else if (NewSize > 20)
648 {
649 Segment = DosAllocateMemory(NewSize, NULL);
650 if (Segment == 0) return FALSE;
651
652 /* Get the new handle table */
653 HandleTable = SEG_OFF_TO_PTR(Segment, 0);
654
655 /* Copy the handles from the PSP to the new table */
656 RtlCopyMemory(HandleTable,
657 FAR_POINTER(PspBlock->HandleTablePtr),
658 PspBlock->HandleTableSize);
659
660 /* Update the handle table pointer and size */
661 PspBlock->HandleTableSize = NewSize;
662 PspBlock->HandleTablePtr = MAKELONG(0, Segment);
663 }
664
665 return TRUE;
666 }
667
668 static BOOLEAN DosCloseHandle(WORD DosHandle)
669 {
670 BYTE SftIndex;
671 PDOS_PSP PspBlock;
672 LPBYTE HandleTable;
673
674 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle);
675
676 /* The system PSP has no handle table */
677 if (CurrentPsp == SYSTEM_PSP) return FALSE;
678
679 /* Get a pointer to the handle table */
680 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
681 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
682
683 /* Make sure the handle is open */
684 if (HandleTable[DosHandle] == 0xFF) return FALSE;
685
686 /* Decrement the reference count of the SFT entry */
687 SftIndex = HandleTable[DosHandle];
688 DosSystemFileTable[SftIndex].RefCount--;
689
690 /* Check if the reference count fell to zero */
691 if (!DosSystemFileTable[SftIndex].RefCount)
692 {
693 /* Close the file, it's no longer needed */
694 CloseHandle(DosSystemFileTable[SftIndex].Handle);
695
696 /* Clear the handle */
697 DosSystemFileTable[SftIndex].Handle = INVALID_HANDLE_VALUE;
698 }
699
700 /* Clear the entry in the JFT */
701 HandleTable[DosHandle] = 0xFF;
702
703 return TRUE;
704 }
705
706 static BOOLEAN DosDuplicateHandle(WORD OldHandle, WORD NewHandle)
707 {
708 BYTE SftIndex;
709 PDOS_PSP PspBlock;
710 LPBYTE HandleTable;
711
712 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
713 OldHandle,
714 NewHandle);
715
716 /* The system PSP has no handle table */
717 if (CurrentPsp == SYSTEM_PSP) return FALSE;
718
719 /* Get a pointer to the handle table */
720 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
721 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
722
723 /* Make sure the old handle is open */
724 if (HandleTable[OldHandle] == 0xFF) return FALSE;
725
726 /* Check if the new handle is open */
727 if (HandleTable[NewHandle] != 0xFF)
728 {
729 /* Close it */
730 DosCloseHandle(NewHandle);
731 }
732
733 /* Increment the reference count of the SFT entry */
734 SftIndex = HandleTable[OldHandle];
735 DosSystemFileTable[SftIndex].RefCount++;
736
737 /* Make the new handle point to that SFT entry */
738 HandleTable[NewHandle] = SftIndex;
739
740 /* Return success */
741 return TRUE;
742 }
743
744
745
746
747
748
749
750 static BOOLEAN DosChangeDrive(BYTE Drive)
751 {
752 WCHAR DirectoryPath[DOS_CMDLINE_LENGTH];
753
754 /* Make sure the drive exists */
755 if (Drive > (LastDrive - 'A')) return FALSE;
756
757 /* Find the path to the new current directory */
758 swprintf(DirectoryPath, L"%c\\%S", Drive + 'A', CurrentDirectories[Drive]);
759
760 /* Change the current directory of the process */
761 if (!SetCurrentDirectory(DirectoryPath)) return FALSE;
762
763 /* Set the current drive */
764 CurrentDrive = Drive;
765
766 /* Return success */
767 return TRUE;
768 }
769
770 static BOOLEAN DosChangeDirectory(LPSTR Directory)
771 {
772 BYTE DriveNumber;
773 DWORD Attributes;
774 LPSTR Path;
775
776 /* Make sure the directory path is not too long */
777 if (strlen(Directory) >= DOS_DIR_LENGTH)
778 {
779 DosLastError = ERROR_PATH_NOT_FOUND;
780 return FALSE;
781 }
782
783 /* Get the drive number */
784 DriveNumber = Directory[0] - 'A';
785
786 /* Make sure the drive exists */
787 if (DriveNumber > (LastDrive - 'A'))
788 {
789 DosLastError = ERROR_PATH_NOT_FOUND;
790 return FALSE;
791 }
792
793 /* Get the file attributes */
794 Attributes = GetFileAttributesA(Directory);
795
796 /* Make sure the path exists and is a directory */
797 if ((Attributes == INVALID_FILE_ATTRIBUTES)
798 || !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
799 {
800 DosLastError = ERROR_PATH_NOT_FOUND;
801 return FALSE;
802 }
803
804 /* Check if this is the current drive */
805 if (DriveNumber == CurrentDrive)
806 {
807 /* Change the directory */
808 if (!SetCurrentDirectoryA(Directory))
809 {
810 DosLastError = LOWORD(GetLastError());
811 return FALSE;
812 }
813 }
814
815 /* Get the directory part of the path */
816 Path = strchr(Directory, '\\');
817 if (Path != NULL)
818 {
819 /* Skip the backslash */
820 Path++;
821 }
822
823 /* Set the directory for the drive */
824 if (Path != NULL)
825 {
826 strncpy(CurrentDirectories[DriveNumber], Path, DOS_DIR_LENGTH);
827 }
828 else
829 {
830 CurrentDirectories[DriveNumber][0] = '\0';
831 }
832
833 /* Return success */
834 return TRUE;
835 }
836
837 /* PUBLIC FUNCTIONS ***********************************************************/
838
839 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
840 {
841 PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
842 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
843
844 ZeroMemory(PspBlock, sizeof(DOS_PSP));
845
846 /* Set the exit interrupt */
847 PspBlock->Exit[0] = 0xCD; // int 0x20
848 PspBlock->Exit[1] = 0x20;
849
850 /* Set the number of the last paragraph */
851 PspBlock->LastParagraph = PspSegment + ProgramSize - 1;
852
853 /* Save the interrupt vectors */
854 PspBlock->TerminateAddress = IntVecTable[0x22];
855 PspBlock->BreakAddress = IntVecTable[0x23];
856 PspBlock->CriticalAddress = IntVecTable[0x24];
857
858 /* Set the parent PSP */
859 PspBlock->ParentPsp = CurrentPsp;
860
861 /* Copy the parent handle table */
862 DosCopyHandleTable(PspBlock->HandleTable);
863
864 /* Set the environment block */
865 PspBlock->EnvBlock = Environment;
866
867 /* Set the handle table pointers to the internal handle table */
868 PspBlock->HandleTableSize = 20;
869 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
870
871 /* Set the DOS version */
872 PspBlock->DosVersion = DOS_VERSION;
873
874 /* Set the far call opcodes */
875 PspBlock->FarCall[0] = 0xCD; // int 0x21
876 PspBlock->FarCall[1] = 0x21;
877 PspBlock->FarCall[2] = 0xCB; // retf
878
879 /* Set the command line */
880 PspBlock->CommandLineSize = (BYTE)min(strlen(CommandLine), DOS_CMDLINE_LENGTH - 1);
881 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
882 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
883 }
884
885 DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
886 IN LPCSTR ExecutablePath,
887 IN LPCSTR CommandLine,
888 IN PVOID Environment,
889 OUT PDWORD StackLocation OPTIONAL,
890 OUT PDWORD EntryPoint OPTIONAL)
891 {
892 DWORD Result = ERROR_SUCCESS;
893 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
894 LPBYTE Address = NULL;
895 WORD Segment = 0;
896 WORD EnvBlock = 0;
897 WORD MaxAllocSize;
898 DWORD i, FileSize, ExeSize;
899 PIMAGE_DOS_HEADER Header;
900 PDWORD RelocationTable;
901 PWORD RelocWord;
902
903 DPRINT1("DosLoadExecutable(%d, %s, %s, %s, 0x%08X, 0x%08X)\n",
904 LoadType,
905 ExecutablePath,
906 CommandLine,
907 Environment,
908 StackLocation,
909 EntryPoint);
910
911 if (LoadType == DOS_LOAD_OVERLAY)
912 {
913 DPRINT1("Overlay loading is not supported yet.\n");
914 return ERROR_NOT_SUPPORTED;
915 }
916
917 /* Open a handle to the executable */
918 FileHandle = CreateFileA(ExecutablePath,
919 GENERIC_READ,
920 FILE_SHARE_READ,
921 NULL,
922 OPEN_EXISTING,
923 FILE_ATTRIBUTE_NORMAL,
924 NULL);
925 if (FileHandle == INVALID_HANDLE_VALUE)
926 {
927 Result = GetLastError();
928 goto Cleanup;
929 }
930
931 /* Get the file size */
932 FileSize = GetFileSize(FileHandle, NULL);
933
934 /* Create a mapping object for the file */
935 FileMapping = CreateFileMapping(FileHandle,
936 NULL,
937 PAGE_READONLY,
938 0,
939 0,
940 NULL);
941 if (FileMapping == NULL)
942 {
943 Result = GetLastError();
944 goto Cleanup;
945 }
946
947 /* Map the file into memory */
948 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
949 if (Address == NULL)
950 {
951 Result = GetLastError();
952 goto Cleanup;
953 }
954
955 /* Copy the environment block to DOS memory */
956 EnvBlock = DosCopyEnvironmentBlock(Environment, ExecutablePath);
957 if (EnvBlock == 0)
958 {
959 Result = ERROR_NOT_ENOUGH_MEMORY;
960 goto Cleanup;
961 }
962
963 /* Check if this is an EXE file or a COM file */
964 if (Address[0] == 'M' && Address[1] == 'Z')
965 {
966 /* EXE file */
967
968 /* Get the MZ header */
969 Header = (PIMAGE_DOS_HEADER)Address;
970
971 /* Get the base size of the file, in paragraphs (rounded up) */
972 ExeSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4;
973
974 /* Add the PSP size, in paragraphs */
975 ExeSize += sizeof(DOS_PSP) >> 4;
976
977 /* Add the maximum size that should be allocated */
978 ExeSize += Header->e_maxalloc;
979
980 /* Make sure it does not pass 0xFFFF */
981 if (ExeSize > 0xFFFF) ExeSize = 0xFFFF;
982
983 /* Reduce the size one by one until the allocation is successful */
984 for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--, ExeSize--)
985 {
986 /* Try to allocate that much memory */
987 Segment = DosAllocateMemory((WORD)ExeSize, NULL);
988 if (Segment != 0) break;
989 }
990
991 /* Check if at least the lowest allocation was successful */
992 if (Segment == 0)
993 {
994 Result = ERROR_NOT_ENOUGH_MEMORY;
995 goto Cleanup;
996 }
997
998 /* Initialize the PSP */
999 DosInitializePsp(Segment,
1000 CommandLine,
1001 (WORD)ExeSize,
1002 EnvBlock);
1003
1004 /* The process owns its own memory */
1005 DosChangeMemoryOwner(Segment, Segment);
1006 DosChangeMemoryOwner(EnvBlock, Segment);
1007
1008 /* Copy the program to Segment:0100 */
1009 RtlCopyMemory(SEG_OFF_TO_PTR(Segment, 0x100),
1010 Address + (Header->e_cparhdr << 4),
1011 min(FileSize - (Header->e_cparhdr << 4),
1012 (ExeSize << 4) - sizeof(DOS_PSP)));
1013
1014 /* Get the relocation table */
1015 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
1016
1017 /* Perform relocations */
1018 for (i = 0; i < Header->e_crlc; i++)
1019 {
1020 /* Get a pointer to the word that needs to be patched */
1021 RelocWord = (PWORD)SEG_OFF_TO_PTR(Segment + HIWORD(RelocationTable[i]),
1022 0x100 + LOWORD(RelocationTable[i]));
1023
1024 /* Add the number of the EXE segment to it */
1025 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
1026 }
1027
1028 if (LoadType == DOS_LOAD_AND_EXECUTE)
1029 {
1030 /* Set the initial segment registers */
1031 setDS(Segment);
1032 setES(Segment);
1033
1034 /* Set the stack to the location from the header */
1035 EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
1036 Header->e_sp);
1037
1038 /* Execute */
1039 CurrentPsp = Segment;
1040 DiskTransferArea = MAKELONG(0x80, Segment);
1041 EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
1042 Header->e_ip);
1043 }
1044 }
1045 else
1046 {
1047 /* COM file */
1048
1049 /* Find the maximum amount of memory that can be allocated */
1050 DosAllocateMemory(0xFFFF, &MaxAllocSize);
1051
1052 /* Make sure it's enough for the whole program and the PSP */
1053 if (((DWORD)MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP)))
1054 {
1055 Result = ERROR_NOT_ENOUGH_MEMORY;
1056 goto Cleanup;
1057 }
1058
1059 /* Allocate all of it */
1060 Segment = DosAllocateMemory(MaxAllocSize, NULL);
1061 if (Segment == 0)
1062 {
1063 Result = ERROR_ARENA_TRASHED;
1064 goto Cleanup;
1065 }
1066
1067 /* The process owns its own memory */
1068 DosChangeMemoryOwner(Segment, Segment);
1069 DosChangeMemoryOwner(EnvBlock, Segment);
1070
1071 /* Copy the program to Segment:0100 */
1072 RtlCopyMemory(SEG_OFF_TO_PTR(Segment, 0x100),
1073 Address,
1074 FileSize);
1075
1076 /* Initialize the PSP */
1077 DosInitializePsp(Segment,
1078 CommandLine,
1079 MaxAllocSize,
1080 EnvBlock);
1081
1082 if (LoadType == DOS_LOAD_AND_EXECUTE)
1083 {
1084 /* Set the initial segment registers */
1085 setDS(Segment);
1086 setES(Segment);
1087
1088 /* Set the stack to the last word of the segment */
1089 EmulatorSetStack(Segment, 0xFFFE);
1090
1091 /*
1092 * Set the value on the stack to 0, so that a near return
1093 * jumps to PSP:0000 which has the exit code.
1094 */
1095 *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0;
1096
1097 /* Execute */
1098 CurrentPsp = Segment;
1099 DiskTransferArea = MAKELONG(0x80, Segment);
1100 EmulatorExecute(Segment, 0x100);
1101 }
1102 }
1103
1104 Cleanup:
1105 if (Result != ERROR_SUCCESS)
1106 {
1107 /* It was not successful, cleanup the DOS memory */
1108 if (EnvBlock) DosFreeMemory(EnvBlock);
1109 if (Segment) DosFreeMemory(Segment);
1110 }
1111
1112 /* Unmap the file*/
1113 if (Address != NULL) UnmapViewOfFile(Address);
1114
1115 /* Close the file mapping object */
1116 if (FileMapping != NULL) CloseHandle(FileMapping);
1117
1118 /* Close the file handle */
1119 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
1120
1121 return Result;
1122 }
1123
1124 DWORD DosStartProcess(IN LPCSTR ExecutablePath,
1125 IN LPCSTR CommandLine,
1126 IN PVOID Environment)
1127 {
1128 DWORD Result;
1129
1130 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
1131 ExecutablePath,
1132 CommandLine,
1133 Environment,
1134 NULL,
1135 NULL);
1136
1137 if (Result != ERROR_SUCCESS) goto Quit;
1138
1139 /* Attach to the console */
1140 VidBiosAttachToConsole(); // FIXME: And in fact, attach the full NTVDM UI to the console
1141
1142 /* Start simulation */
1143 SetEvent(VdmTaskEvent);
1144 EmulatorSimulate();
1145
1146 /* Detach from the console */
1147 VidBiosDetachFromConsole(); // FIXME: And in fact, detach the full NTVDM UI from the console
1148
1149 Quit:
1150 return Result;
1151 }
1152
1153 #ifndef STANDALONE
1154 WORD DosCreateProcess(DOS_EXEC_TYPE LoadType,
1155 LPCSTR ProgramName,
1156 PDOS_EXEC_PARAM_BLOCK Parameters)
1157 {
1158 DWORD Result;
1159 DWORD BinaryType;
1160 LPVOID Environment = NULL;
1161 VDM_COMMAND_INFO CommandInfo;
1162 CHAR CmdLine[MAX_PATH];
1163 CHAR AppName[MAX_PATH];
1164 CHAR PifFile[MAX_PATH];
1165 CHAR Desktop[MAX_PATH];
1166 CHAR Title[MAX_PATH];
1167 CHAR Env[MAX_PATH];
1168 STARTUPINFOA StartupInfo;
1169 PROCESS_INFORMATION ProcessInfo;
1170
1171 /* Get the binary type */
1172 if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
1173
1174 /* Did the caller specify an environment segment? */
1175 if (Parameters->Environment)
1176 {
1177 /* Yes, use it instead of the parent one */
1178 Environment = SEG_OFF_TO_PTR(Parameters->Environment, 0);
1179 }
1180
1181 /* Set up the startup info structure */
1182 ZeroMemory(&StartupInfo, sizeof(STARTUPINFOA));
1183 StartupInfo.cb = sizeof(STARTUPINFOA);
1184
1185 /* Create the process */
1186 if (!CreateProcessA(ProgramName,
1187 FAR_POINTER(Parameters->CommandLine),
1188 NULL,
1189 NULL,
1190 FALSE,
1191 0,
1192 Environment,
1193 NULL,
1194 &StartupInfo,
1195 &ProcessInfo))
1196 {
1197 return GetLastError();
1198 }
1199
1200 /* Check the type of the program */
1201 switch (BinaryType)
1202 {
1203 /* These are handled by NTVDM */
1204 case SCS_DOS_BINARY:
1205 case SCS_WOW_BINARY:
1206 {
1207 /* Clear the structure */
1208 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
1209
1210 /* Initialize the structure members */
1211 CommandInfo.TaskId = SessionId;
1212 CommandInfo.VDMState = VDM_FLAG_NESTED_TASK | VDM_FLAG_DONT_WAIT;
1213 CommandInfo.CmdLine = CmdLine;
1214 CommandInfo.CmdLen = sizeof(CmdLine);
1215 CommandInfo.AppName = AppName;
1216 CommandInfo.AppLen = sizeof(AppName);
1217 CommandInfo.PifFile = PifFile;
1218 CommandInfo.PifLen = sizeof(PifFile);
1219 CommandInfo.Desktop = Desktop;
1220 CommandInfo.DesktopLen = sizeof(Desktop);
1221 CommandInfo.Title = Title;
1222 CommandInfo.TitleLen = sizeof(Title);
1223 CommandInfo.Env = Env;
1224 CommandInfo.EnvLen = sizeof(Env);
1225
1226 /* Get the VDM command information */
1227 if (!GetNextVDMCommand(&CommandInfo))
1228 {
1229 /* Shouldn't happen */
1230 ASSERT(FALSE);
1231 }
1232
1233 /* Increment the re-entry count */
1234 CommandInfo.VDMState = VDM_INC_REENTER_COUNT;
1235 GetNextVDMCommand(&CommandInfo);
1236
1237 /* Load the executable */
1238 Result = DosLoadExecutable(LoadType,
1239 AppName,
1240 CmdLine,
1241 Env,
1242 &Parameters->StackLocation,
1243 &Parameters->EntryPoint);
1244 if (Result != ERROR_SUCCESS)
1245 {
1246 DisplayMessage(L"Could not load '%S'. Error: %u", AppName, Result);
1247 // FIXME: Decrement the reenter count. Or, instead, just increment
1248 // the VDM reenter count *only* if this call succeeds...
1249 }
1250
1251 break;
1252 }
1253
1254 /* Not handled by NTVDM */
1255 default:
1256 {
1257 /* Wait for the process to finish executing */
1258 WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
1259 }
1260 }
1261
1262 /* Close the handles */
1263 CloseHandle(ProcessInfo.hProcess);
1264 CloseHandle(ProcessInfo.hThread);
1265
1266 return ERROR_SUCCESS;
1267 }
1268 #endif
1269
1270 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
1271 {
1272 WORD i;
1273 WORD McbSegment = FIRST_MCB_SEGMENT;
1274 PDOS_MCB CurrentMcb;
1275 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
1276 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
1277
1278 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1279 Psp,
1280 ReturnCode);
1281
1282 /* Check if this PSP is it's own parent */
1283 if (PspBlock->ParentPsp == Psp) goto Done;
1284
1285 for (i = 0; i < PspBlock->HandleTableSize; i++)
1286 {
1287 /* Close the handle */
1288 DosCloseHandle(i);
1289 }
1290
1291 /* Free the memory used by the process */
1292 while (TRUE)
1293 {
1294 /* Get a pointer to the MCB */
1295 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
1296
1297 /* Make sure the MCB is valid */
1298 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
1299
1300 /* If this block was allocated by the process, free it */
1301 if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment + 1);
1302
1303 /* If this was the last block, quit */
1304 if (CurrentMcb->BlockType == 'Z') break;
1305
1306 /* Update the segment and continue */
1307 McbSegment += CurrentMcb->Size + 1;
1308 }
1309
1310 Done:
1311 /* Restore the interrupt vectors */
1312 IntVecTable[0x22] = PspBlock->TerminateAddress;
1313 IntVecTable[0x23] = PspBlock->BreakAddress;
1314 IntVecTable[0x24] = PspBlock->CriticalAddress;
1315
1316 /* Update the current PSP */
1317 if (Psp == CurrentPsp)
1318 {
1319 CurrentPsp = PspBlock->ParentPsp;
1320 if (CurrentPsp == SYSTEM_PSP)
1321 {
1322 ResetEvent(VdmTaskEvent);
1323 EmulatorUnsimulate();
1324 }
1325 }
1326
1327 #ifndef STANDALONE
1328 // FIXME: This is probably not the best way to do it
1329 /* Check if this was a nested DOS task */
1330 if (CurrentPsp != SYSTEM_PSP)
1331 {
1332 VDM_COMMAND_INFO CommandInfo;
1333
1334 /* Decrement the re-entry count */
1335 CommandInfo.TaskId = SessionId;
1336 CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
1337 GetNextVDMCommand(&CommandInfo);
1338
1339 /* Clear the structure */
1340 ZeroMemory(&CommandInfo, sizeof(CommandInfo));
1341
1342 /* Update the VDM state of the task */
1343 CommandInfo.TaskId = SessionId;
1344 CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
1345 GetNextVDMCommand(&CommandInfo);
1346 }
1347 #endif
1348
1349 /* Save the return code - Normal termination */
1350 DosErrorLevel = MAKEWORD(ReturnCode, 0x00);
1351
1352 /* Return control to the parent process */
1353 EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
1354 LOWORD(PspBlock->TerminateAddress));
1355 }
1356
1357 BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
1358 {
1359 HANDLE Handle = DosGetRealHandle(FileHandle);
1360
1361 if (Handle == INVALID_HANDLE_VALUE)
1362 {
1363 /* Doesn't exist */
1364 DosLastError = ERROR_FILE_NOT_FOUND;
1365 return FALSE;
1366 }
1367
1368 switch (ControlCode)
1369 {
1370 /* Get Device Information */
1371 case 0x00:
1372 {
1373 WORD InfoWord = 0;
1374
1375 /*
1376 * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
1377 * for a list of possible flags.
1378 */
1379
1380 if (Handle == DosSystemFileTable[DOS_INPUT_HANDLE].Handle)
1381 {
1382 /* Console input */
1383 InfoWord |= 1 << 0;
1384 }
1385 else if (Handle == DosSystemFileTable[DOS_OUTPUT_HANDLE].Handle)
1386 {
1387 /* Console output */
1388 InfoWord |= 1 << 1;
1389 }
1390
1391 /* It is a device */
1392 InfoWord |= 1 << 7;
1393
1394 /* Return the device information word */
1395 setDX(InfoWord);
1396 return TRUE;
1397 }
1398
1399 /* Unsupported control code */
1400 default:
1401 {
1402 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode);
1403
1404 DosLastError = ERROR_INVALID_PARAMETER;
1405 return FALSE;
1406 }
1407 }
1408 }
1409
1410 VOID WINAPI DosInt20h(LPWORD Stack)
1411 {
1412 /* This is the exit interrupt */
1413 DosTerminateProcess(Stack[STACK_CS], 0);
1414 }
1415
1416 VOID WINAPI DosInt21h(LPWORD Stack)
1417 {
1418 BYTE Character;
1419 SYSTEMTIME SystemTime;
1420 PCHAR String;
1421 PDOS_INPUT_BUFFER InputBuffer;
1422
1423 /* Check the value in the AH register */
1424 switch (getAH())
1425 {
1426 /* Terminate Program */
1427 case 0x00:
1428 {
1429 DosTerminateProcess(Stack[STACK_CS], 0);
1430 break;
1431 }
1432
1433 /* Read Character from STDIN with Echo */
1434 case 0x01:
1435 {
1436 // FIXME: Under DOS 2+, input / output handle may be redirected!!!!
1437 Character = DosReadCharacter(DOS_INPUT_HANDLE);
1438 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1439
1440 // /* Let the BOP repeat if needed */
1441 // if (getCF()) break;
1442
1443 setAL(Character);
1444 break;
1445 }
1446
1447 /* Write Character to STDOUT */
1448 case 0x02:
1449 {
1450 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1451 Character = getDL();
1452 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1453
1454 /*
1455 * We return the output character (DOS 2.1+).
1456 * Also, if we're going to output a TAB, then
1457 * don't return a TAB but a SPACE instead.
1458 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1459 * for more information.
1460 */
1461 setAL(Character == '\t' ? ' ' : Character);
1462 break;
1463 }
1464
1465 /* Read Character from STDAUX */
1466 case 0x03:
1467 {
1468 // FIXME: Really read it from STDAUX!
1469 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1470 // setAL(DosReadCharacter());
1471 break;
1472 }
1473
1474 /* Write Character to STDAUX */
1475 case 0x04:
1476 {
1477 // FIXME: Really write it to STDAUX!
1478 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1479 // DosPrintCharacter(getDL());
1480 break;
1481 }
1482
1483 /* Write Character to Printer */
1484 case 0x05:
1485 {
1486 // FIXME: Really write it to printer!
1487 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1488 DPRINT1("0x%p\n", getDL());
1489 DPRINT1("\n\n-----------\n\n");
1490 break;
1491 }
1492
1493 /* Direct Console I/O */
1494 case 0x06:
1495 {
1496 Character = getDL();
1497
1498 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1499
1500 if (Character != 0xFF)
1501 {
1502 /* Output */
1503 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1504
1505 /*
1506 * We return the output character (DOS 2.1+).
1507 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1508 * for more information.
1509 */
1510 setAL(Character);
1511 }
1512 else
1513 {
1514 /* Input */
1515 if (DosCheckInput())
1516 {
1517 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
1518 setAL(DosReadCharacter(DOS_INPUT_HANDLE));
1519 }
1520 else
1521 {
1522 /* No character available */
1523 Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
1524 setAL(0x00);
1525 }
1526 }
1527
1528 break;
1529 }
1530
1531 /* Character Input without Echo */
1532 case 0x07:
1533 case 0x08:
1534 {
1535 // FIXME: Under DOS 2+, input handle may be redirected!!!!
1536 Character = DosReadCharacter(DOS_INPUT_HANDLE);
1537
1538 // FIXME: For 0x07, do not check Ctrl-C/Break.
1539 // For 0x08, do check those control sequences and if needed,
1540 // call INT 0x23.
1541
1542 // /* Let the BOP repeat if needed */
1543 // if (getCF()) break;
1544
1545 setAL(Character);
1546 break;
1547 }
1548
1549 /* Write string to STDOUT */
1550 case 0x09:
1551 {
1552 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1553
1554 while (*String != '$')
1555 {
1556 DosPrintCharacter(DOS_OUTPUT_HANDLE, *String);
1557 String++;
1558 }
1559
1560 /*
1561 * We return the terminating character (DOS 2.1+).
1562 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1563 * for more information.
1564 */
1565 setAL('$');
1566 break;
1567 }
1568
1569 /* Read Buffered Input */
1570 case 0x0A:
1571 {
1572 WORD Count = 0;
1573 InputBuffer = (PDOS_INPUT_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
1574
1575 DPRINT1("Read Buffered Input\n");
1576
1577 while (Count < InputBuffer->MaxLength)
1578 {
1579 /* Try to read a character (wait) */
1580 Character = DosReadCharacter(DOS_INPUT_HANDLE);
1581
1582 /* Echo the character and append it to the buffer */
1583 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1584 InputBuffer->Buffer[Count] = Character;
1585
1586 if (Character == '\r') break;
1587 Count++;
1588 }
1589
1590 /* Update the length */
1591 InputBuffer->Length = Count;
1592
1593 break;
1594 }
1595
1596 /* Get STDIN Status */
1597 case 0x0B:
1598 {
1599 setAL(DosCheckInput() ? 0xFF : 0x00);
1600 break;
1601 }
1602
1603 /* Flush Buffer and Read STDIN */
1604 case 0x0C:
1605 {
1606 BYTE InputFunction = getAL();
1607
1608 /* Flush STDIN buffer */
1609 DosFlushFileBuffers(DOS_INPUT_HANDLE);
1610
1611 /*
1612 * If the input function number contained in AL is valid, i.e.
1613 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1614 * recursively with AL == AH.
1615 */
1616 if (InputFunction == 0x01 || InputFunction == 0x06 ||
1617 InputFunction == 0x07 || InputFunction == 0x08 ||
1618 InputFunction == 0x0A)
1619 {
1620 setAH(InputFunction);
1621 /*
1622 * Instead of calling ourselves really recursively as in:
1623 * DosInt21h(Stack);
1624 * prefer resetting the CF flag to let the BOP repeat.
1625 */
1626 setCF(1);
1627 }
1628 break;
1629 }
1630
1631 /* Disk Reset */
1632 case 0x0D:
1633 {
1634 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1635
1636 // TODO: Flush what's needed.
1637 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1638
1639 /* Clear CF in DOS 6 only */
1640 if (PspBlock->DosVersion == 0x0006)
1641 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1642
1643 break;
1644 }
1645
1646 /* Set Default Drive */
1647 case 0x0E:
1648 {
1649 DosChangeDrive(getDL());
1650 setAL(LastDrive - 'A' + 1);
1651 break;
1652 }
1653
1654 /* NULL Function for CP/M Compatibility */
1655 case 0x18:
1656 {
1657 /*
1658 * This function corresponds to the CP/M BDOS function
1659 * "get bit map of logged drives", which is meaningless
1660 * under MS-DOS.
1661 *
1662 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1663 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1664 * for more information.
1665 */
1666 setAL(0x00);
1667 break;
1668 }
1669
1670 /* Get Default Drive */
1671 case 0x19:
1672 {
1673 setAL(CurrentDrive);
1674 break;
1675 }
1676
1677 /* Set Disk Transfer Area */
1678 case 0x1A:
1679 {
1680 DiskTransferArea = MAKELONG(getDX(), getDS());
1681 break;
1682 }
1683
1684 /* NULL Function for CP/M Compatibility */
1685 case 0x1D:
1686 case 0x1E:
1687 {
1688 /*
1689 * Function 0x1D corresponds to the CP/M BDOS function
1690 * "get bit map of read-only drives", which is meaningless
1691 * under MS-DOS.
1692 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1693 * for more information.
1694 *
1695 * Function 0x1E corresponds to the CP/M BDOS function
1696 * "set file attributes", which was meaningless under MS-DOS 1.x.
1697 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1698 * for more information.
1699 */
1700 setAL(0x00);
1701 break;
1702 }
1703
1704 /* NULL Function for CP/M Compatibility */
1705 case 0x20:
1706 {
1707 /*
1708 * This function corresponds to the CP/M BDOS function
1709 * "get/set default user (sublibrary) number", which is meaningless
1710 * under MS-DOS.
1711 *
1712 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1713 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1714 * for more information.
1715 */
1716 setAL(0x00);
1717 break;
1718 }
1719
1720 /* Set Interrupt Vector */
1721 case 0x25:
1722 {
1723 ULONG FarPointer = MAKELONG(getDX(), getDS());
1724 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1725
1726 /* Write the new far pointer to the IDT */
1727 ((PULONG)BaseAddress)[getAL()] = FarPointer;
1728 break;
1729 }
1730
1731 /* Create New PSP */
1732 case 0x26:
1733 {
1734 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1735 break;
1736 }
1737
1738 /* Get System Date */
1739 case 0x2A:
1740 {
1741 GetLocalTime(&SystemTime);
1742 setCX(SystemTime.wYear);
1743 setDX(MAKEWORD(SystemTime.wDay, SystemTime.wMonth));
1744 setAL(SystemTime.wDayOfWeek);
1745 break;
1746 }
1747
1748 /* Set System Date */
1749 case 0x2B:
1750 {
1751 GetLocalTime(&SystemTime);
1752 SystemTime.wYear = getCX();
1753 SystemTime.wMonth = getDH();
1754 SystemTime.wDay = getDL();
1755
1756 /* Return success or failure */
1757 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1758 break;
1759 }
1760
1761 /* Get System Time */
1762 case 0x2C:
1763 {
1764 GetLocalTime(&SystemTime);
1765 setCX(MAKEWORD(SystemTime.wMinute, SystemTime.wHour));
1766 setDX(MAKEWORD(SystemTime.wMilliseconds / 10, SystemTime.wSecond));
1767 break;
1768 }
1769
1770 /* Set System Time */
1771 case 0x2D:
1772 {
1773 GetLocalTime(&SystemTime);
1774 SystemTime.wHour = getCH();
1775 SystemTime.wMinute = getCL();
1776 SystemTime.wSecond = getDH();
1777 SystemTime.wMilliseconds = getDL() * 10; // In hundredths of seconds
1778
1779 /* Return success or failure */
1780 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1781 break;
1782 }
1783
1784 /* Get Disk Transfer Area */
1785 case 0x2F:
1786 {
1787 setES(HIWORD(DiskTransferArea));
1788 setBX(LOWORD(DiskTransferArea));
1789 break;
1790 }
1791
1792 /* Get DOS Version */
1793 case 0x30:
1794 {
1795 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1796
1797 /*
1798 * DOS 2+ - GET DOS VERSION
1799 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1800 * for more information.
1801 */
1802
1803 if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
1804 {
1805 /*
1806 * Return DOS OEM number:
1807 * 0x00 for IBM PC-DOS
1808 * 0x02 for packaged MS-DOS
1809 */
1810 setBH(0x02);
1811 }
1812
1813 if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
1814 {
1815 /*
1816 * Return version flag:
1817 * 1 << 3 if DOS is in ROM,
1818 * 0 (reserved) if not.
1819 */
1820 setBH(0x00);
1821 }
1822
1823 /* Return DOS 24-bit user serial number in BL:CX */
1824 setBL(0x00);
1825 setCX(0x0000);
1826
1827 /*
1828 * Return DOS version: Minor:Major in AH:AL
1829 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1830 */
1831 setAX(PspBlock->DosVersion);
1832
1833 break;
1834 }
1835
1836 /* Extended functionalities */
1837 case 0x33:
1838 {
1839 if (getAL() == 0x06)
1840 {
1841 /*
1842 * DOS 5+ - GET TRUE VERSION NUMBER
1843 * This function always returns the true version number, unlike
1844 * AH=30h, whose return value may be changed with SETVER.
1845 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1846 * for more information.
1847 */
1848
1849 /*
1850 * Return the true DOS version: Minor:Major in BH:BL
1851 * The Windows NT DOS box returns BX=3205h (version 5.50).
1852 */
1853 setBX(NTDOS_VERSION);
1854
1855 /* DOS revision 0 */
1856 setDL(0x00);
1857
1858 /* Unpatched DOS */
1859 setDH(0x00);
1860 }
1861 // else
1862 // {
1863 // /* Invalid subfunction */
1864 // setAL(0xFF);
1865 // }
1866
1867 break;
1868 }
1869
1870 /* Get Interrupt Vector */
1871 case 0x35:
1872 {
1873 DWORD FarPointer = ((PDWORD)BaseAddress)[getAL()];
1874
1875 /* Read the address from the IDT into ES:BX */
1876 setES(HIWORD(FarPointer));
1877 setBX(LOWORD(FarPointer));
1878 break;
1879 }
1880
1881 /* SWITCH character - AVAILDEV */
1882 case 0x37:
1883 {
1884 if (getAL() == 0x00)
1885 {
1886 /*
1887 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1888 * This setting is ignored by MS-DOS 4.0+.
1889 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1890 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1891 * for more information.
1892 */
1893 setDL('/');
1894 setAL(0x00);
1895 }
1896 else if (getAL() == 0x01)
1897 {
1898 /*
1899 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1900 * This setting is ignored by MS-DOS 5+.
1901 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1902 * for more information.
1903 */
1904 // getDL();
1905 setAL(0xFF);
1906 }
1907 else if (getAL() == 0x02)
1908 {
1909 /*
1910 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1911 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1912 * for more information.
1913 */
1914 // setDL();
1915 setAL(0xFF);
1916 }
1917 else if (getAL() == 0x03)
1918 {
1919 /*
1920 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1921 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1922 * for more information.
1923 */
1924 // getDL();
1925 setAL(0xFF);
1926 }
1927 else
1928 {
1929 /* Invalid subfunction */
1930 setAL(0xFF);
1931 }
1932
1933 break;
1934 }
1935
1936 /* Create Directory */
1937 case 0x39:
1938 {
1939 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1940
1941 if (CreateDirectoryA(String, NULL))
1942 {
1943 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1944 }
1945 else
1946 {
1947 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1948 setAX(LOWORD(GetLastError()));
1949 }
1950
1951 break;
1952 }
1953
1954 /* Remove Directory */
1955 case 0x3A:
1956 {
1957 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1958
1959 if (RemoveDirectoryA(String))
1960 {
1961 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1962 }
1963 else
1964 {
1965 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1966 setAX(LOWORD(GetLastError()));
1967 }
1968
1969 break;
1970 }
1971
1972 /* Set Current Directory */
1973 case 0x3B:
1974 {
1975 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1976
1977 if (DosChangeDirectory(String))
1978 {
1979 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1980 }
1981 else
1982 {
1983 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1984 setAX(DosLastError);
1985 }
1986
1987 break;
1988 }
1989
1990 /* Create File */
1991 case 0x3C:
1992 {
1993 WORD FileHandle;
1994 WORD ErrorCode = DosCreateFile(&FileHandle,
1995 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
1996 getCX());
1997
1998 if (ErrorCode == 0)
1999 {
2000 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2001 setAX(FileHandle);
2002 }
2003 else
2004 {
2005 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2006 setAX(ErrorCode);
2007 }
2008
2009 break;
2010 }
2011
2012 /* Open File */
2013 case 0x3D:
2014 {
2015 WORD FileHandle;
2016 WORD ErrorCode = DosOpenFile(&FileHandle,
2017 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2018 getAL());
2019
2020 if (ErrorCode == 0)
2021 {
2022 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2023 setAX(FileHandle);
2024 }
2025 else
2026 {
2027 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2028 setAX(ErrorCode);
2029 }
2030
2031 break;
2032 }
2033
2034 /* Close File */
2035 case 0x3E:
2036 {
2037 if (DosCloseHandle(getBX()))
2038 {
2039 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2040 }
2041 else
2042 {
2043 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2044 setAX(ERROR_INVALID_HANDLE);
2045 }
2046
2047 break;
2048 }
2049
2050 /* Read from File or Device */
2051 case 0x3F:
2052 {
2053 WORD BytesRead = 0;
2054 WORD ErrorCode = DosReadFile(getBX(),
2055 SEG_OFF_TO_PTR(getDS(), getDX()),
2056 getCX(),
2057 &BytesRead);
2058
2059 if (ErrorCode == ERROR_SUCCESS)
2060 {
2061 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2062 setAX(BytesRead);
2063 }
2064 else if (ErrorCode != ERROR_NOT_READY)
2065 {
2066 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2067 setAX(ErrorCode);
2068 }
2069
2070 break;
2071 }
2072
2073 /* Write to File or Device */
2074 case 0x40:
2075 {
2076 WORD BytesWritten = 0;
2077 WORD ErrorCode = DosWriteFile(getBX(),
2078 SEG_OFF_TO_PTR(getDS(), getDX()),
2079 getCX(),
2080 &BytesWritten);
2081
2082 if (ErrorCode == ERROR_SUCCESS)
2083 {
2084 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2085 setAX(BytesWritten);
2086 }
2087 else
2088 {
2089 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2090 setAX(ErrorCode);
2091 }
2092
2093 break;
2094 }
2095
2096 /* Delete File */
2097 case 0x41:
2098 {
2099 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2100
2101 if (demFileDelete(FileName) == ERROR_SUCCESS)
2102 {
2103 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2104 /*
2105 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2106 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2107 */
2108 setAL(FileName[0] - 'A');
2109 }
2110 else
2111 {
2112 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2113 setAX(GetLastError());
2114 }
2115
2116 break;
2117 }
2118
2119 /* Seek File */
2120 case 0x42:
2121 {
2122 DWORD NewLocation;
2123 WORD ErrorCode = DosSeekFile(getBX(),
2124 MAKELONG(getDX(), getCX()),
2125 getAL(),
2126 &NewLocation);
2127
2128 if (ErrorCode == ERROR_SUCCESS)
2129 {
2130 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2131
2132 /* Return the new offset in DX:AX */
2133 setDX(HIWORD(NewLocation));
2134 setAX(LOWORD(NewLocation));
2135 }
2136 else
2137 {
2138 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2139 setAX(ErrorCode);
2140 }
2141
2142 break;
2143 }
2144
2145 /* Get/Set File Attributes */
2146 case 0x43:
2147 {
2148 DWORD Attributes;
2149 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2150
2151 if (getAL() == 0x00)
2152 {
2153 /* Get the attributes */
2154 Attributes = GetFileAttributesA(FileName);
2155
2156 /* Check if it failed */
2157 if (Attributes == INVALID_FILE_ATTRIBUTES)
2158 {
2159 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2160 setAX(GetLastError());
2161 }
2162 else
2163 {
2164 /* Return the attributes that DOS can understand */
2165 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2166 setCX(Attributes & 0x00FF);
2167 }
2168 }
2169 else if (getAL() == 0x01)
2170 {
2171 /* Try to set the attributes */
2172 if (SetFileAttributesA(FileName, getCL()))
2173 {
2174 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2175 }
2176 else
2177 {
2178 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2179 setAX(GetLastError());
2180 }
2181 }
2182 else
2183 {
2184 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2185 setAX(ERROR_INVALID_FUNCTION);
2186 }
2187
2188 break;
2189 }
2190
2191 /* IOCTL */
2192 case 0x44:
2193 {
2194 if (DosHandleIoctl(getAL(), getBX()))
2195 {
2196 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2197 }
2198 else
2199 {
2200 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2201 setAX(DosLastError);
2202 }
2203
2204 break;
2205 }
2206
2207 /* Duplicate Handle */
2208 case 0x45:
2209 {
2210 WORD NewHandle;
2211 HANDLE Handle = DosGetRealHandle(getBX());
2212
2213 if (Handle == INVALID_HANDLE_VALUE)
2214 {
2215 /* The handle is invalid */
2216 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2217 setAX(ERROR_INVALID_HANDLE);
2218 break;
2219 }
2220
2221 /* Open a new handle to the same entry */
2222 NewHandle = DosOpenHandle(Handle);
2223
2224 if (NewHandle == INVALID_DOS_HANDLE)
2225 {
2226 /* Too many files open */
2227 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2228 setAX(ERROR_TOO_MANY_OPEN_FILES);
2229 break;
2230 }
2231
2232 /* Return the result */
2233 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2234 setAX(NewHandle);
2235 break;
2236 }
2237
2238 /* Force Duplicate Handle */
2239 case 0x46:
2240 {
2241 if (DosDuplicateHandle(getBX(), getCX()))
2242 {
2243 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2244 }
2245 else
2246 {
2247 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2248 setAX(ERROR_INVALID_HANDLE);
2249 }
2250
2251 break;
2252 }
2253
2254 /* Get Current Directory */
2255 case 0x47:
2256 {
2257 BYTE DriveNumber = getDL();
2258 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
2259
2260 /* Get the real drive number */
2261 if (DriveNumber == 0)
2262 {
2263 DriveNumber = CurrentDrive;
2264 }
2265 else
2266 {
2267 /* Decrement DriveNumber since it was 1-based */
2268 DriveNumber--;
2269 }
2270
2271 if (DriveNumber <= LastDrive - 'A')
2272 {
2273 /*
2274 * Copy the current directory into the target buffer.
2275 * It doesn't contain the drive letter and the backslash.
2276 */
2277 strncpy(String, CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
2278 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2279 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2280 }
2281 else
2282 {
2283 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2284 setAX(ERROR_INVALID_DRIVE);
2285 }
2286
2287 break;
2288 }
2289
2290 /* Allocate Memory */
2291 case 0x48:
2292 {
2293 WORD MaxAvailable = 0;
2294 WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
2295
2296 if (Segment != 0)
2297 {
2298 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2299 setAX(Segment);
2300 }
2301 else
2302 {
2303 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2304 setAX(DosLastError);
2305 setBX(MaxAvailable);
2306 }
2307
2308 break;
2309 }
2310
2311 /* Free Memory */
2312 case 0x49:
2313 {
2314 if (DosFreeMemory(getES()))
2315 {
2316 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2317 }
2318 else
2319 {
2320 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2321 setAX(ERROR_ARENA_TRASHED);
2322 }
2323
2324 break;
2325 }
2326
2327 /* Resize Memory Block */
2328 case 0x4A:
2329 {
2330 WORD Size;
2331
2332 if (DosResizeMemory(getES(), getBX(), &Size))
2333 {
2334 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2335 }
2336 else
2337 {
2338 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2339 setAX(DosLastError);
2340 setBX(Size);
2341 }
2342
2343 break;
2344 }
2345
2346 #ifndef STANDALONE
2347 /* Execute */
2348 case 0x4B:
2349 {
2350 DOS_EXEC_TYPE LoadType = (DOS_EXEC_TYPE)getAL();
2351 LPSTR ProgramName = SEG_OFF_TO_PTR(getDS(), getDX());
2352 PDOS_EXEC_PARAM_BLOCK ParamBlock = SEG_OFF_TO_PTR(getES(), getBX());
2353 WORD ErrorCode = DosCreateProcess(LoadType, ProgramName, ParamBlock);
2354
2355 if (ErrorCode == ERROR_SUCCESS)
2356 {
2357 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2358 }
2359 else
2360 {
2361 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2362 setAX(ErrorCode);
2363 }
2364
2365 break;
2366 }
2367 #endif
2368
2369 /* Terminate With Return Code */
2370 case 0x4C:
2371 {
2372 DosTerminateProcess(CurrentPsp, getAL());
2373 break;
2374 }
2375
2376 /* Get Return Code (ERRORLEVEL) */
2377 case 0x4D:
2378 {
2379 /*
2380 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2381 * DosErrorLevel is cleared after being read by this function.
2382 */
2383 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2384 setAX(DosErrorLevel);
2385 DosErrorLevel = 0x0000; // Clear it
2386 break;
2387 }
2388
2389 /* Find First File */
2390 case 0x4E:
2391 {
2392 WORD Result = (WORD)demFileFindFirst(FAR_POINTER(DiskTransferArea),
2393 SEG_OFF_TO_PTR(getDS(), getDX()),
2394 getCX());
2395
2396 setAX(Result);
2397
2398 if (Result == ERROR_SUCCESS)
2399 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2400 else
2401 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2402
2403 break;
2404 }
2405
2406 /* Find Next File */
2407 case 0x4F:
2408 {
2409 WORD Result = (WORD)demFileFindNext(FAR_POINTER(DiskTransferArea));
2410
2411 setAX(Result);
2412
2413 if (Result == ERROR_SUCCESS)
2414 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2415 else
2416 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2417
2418 break;
2419 }
2420
2421 /* Internal - Set Current Process ID (Set PSP Address) */
2422 case 0x50:
2423 {
2424 // FIXME: Is it really what it's done ??
2425 CurrentPsp = getBX();
2426 break;
2427 }
2428
2429 /* Internal - Get Current Process ID (Get PSP Address) */
2430 case 0x51:
2431 /* Get Current PSP Address */
2432 case 0x62:
2433 {
2434 /*
2435 * Undocumented AH=51h is identical to the documented AH=62h.
2436 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2437 * and http://www.ctyme.com/intr/rb-3140.htm
2438 * for more information.
2439 */
2440 setBX(CurrentPsp);
2441 break;
2442 }
2443
2444 /* Internal - Get "List of lists" (SYSVARS) */
2445 case 0x52:
2446 {
2447 /*
2448 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
2449 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
2450 * for more information.
2451 */
2452
2453 /* Return the DOS "list of lists" in ES:BX */
2454 setES(0x0000);
2455 setBX(0x0000);
2456
2457 DisplayMessage(L"Required for AARD code, do you remember? :P");
2458 break;
2459 }
2460
2461 /* Get/Set Memory Management Options */
2462 case 0x58:
2463 {
2464 if (getAL() == 0x00)
2465 {
2466 /* Get allocation strategy */
2467 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2468 setAX(DosAllocStrategy);
2469 }
2470 else if (getAL() == 0x01)
2471 {
2472 /* Set allocation strategy */
2473
2474 if ((getBL() & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2475 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2476 {
2477 /* Can't set both */
2478 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2479 setAX(ERROR_INVALID_PARAMETER);
2480 break;
2481 }
2482
2483 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT)
2484 {
2485 /* Invalid allocation strategy */
2486 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2487 setAX(ERROR_INVALID_PARAMETER);
2488 break;
2489 }
2490
2491 DosAllocStrategy = getBL();
2492 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2493 }
2494 else if (getAL() == 0x02)
2495 {
2496 /* Get UMB link state */
2497 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2498 setAL(DosUmbLinked ? 0x01 : 0x00);
2499 }
2500 else if (getAL() == 0x03)
2501 {
2502 /* Set UMB link state */
2503 if (getBX()) DosLinkUmb();
2504 else DosUnlinkUmb();
2505 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2506 }
2507 else
2508 {
2509 /* Invalid or unsupported function */
2510 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2511 setAX(ERROR_INVALID_FUNCTION);
2512 }
2513
2514 break;
2515 }
2516
2517 /* Set Handle Count */
2518 case 0x67:
2519 {
2520 if (!DosResizeHandleTable(getBX()))
2521 {
2522 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2523 setAX(DosLastError);
2524 }
2525 else Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2526
2527 break;
2528 }
2529
2530 /* Unsupported */
2531 default:
2532 {
2533 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2534 getAH(), getAL());
2535
2536 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2537 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2538 }
2539 }
2540 }
2541
2542 VOID WINAPI DosBreakInterrupt(LPWORD Stack)
2543 {
2544 UNREFERENCED_PARAMETER(Stack);
2545
2546 /* Stop the VDM task */
2547 ResetEvent(VdmTaskEvent);
2548 EmulatorUnsimulate();
2549 }
2550
2551 VOID WINAPI DosFastConOut(LPWORD Stack)
2552 {
2553 /*
2554 * This is the DOS 2+ Fast Console Output Interrupt.
2555 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2556 * for more information.
2557 */
2558
2559 /* Save AX and BX */
2560 USHORT AX = getAX();
2561 USHORT BX = getBX();
2562
2563 /* Set the parameters (AL = character, already set) */
2564 setBL(DOS_CHAR_ATTRIBUTE);
2565 setBH(Bda->VideoPage);
2566
2567 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
2568 setAH(0x0E);
2569 Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
2570
2571 /* Restore AX and BX */
2572 setBX(BX);
2573 setAX(AX);
2574 }
2575
2576 VOID WINAPI DosInt2Fh(LPWORD Stack)
2577 {
2578 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2579 getAH(), getAL());
2580 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2581 }
2582
2583 BOOLEAN DosKRNLInitialize(VOID)
2584 {
2585
2586 #if 1
2587
2588 UCHAR i;
2589 CHAR CurrentDirectory[MAX_PATH];
2590 CHAR DosDirectory[DOS_DIR_LENGTH];
2591 LPSTR Path;
2592
2593 FILE *Stream;
2594 WCHAR Buffer[256];
2595
2596 /* Clear the current directory buffer */
2597 ZeroMemory(CurrentDirectories, sizeof(CurrentDirectories));
2598
2599 /* Get the current directory */
2600 if (!GetCurrentDirectoryA(MAX_PATH, CurrentDirectory))
2601 {
2602 // TODO: Use some kind of default path?
2603 return FALSE;
2604 }
2605
2606 /* Convert that to a DOS path */
2607 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, DOS_DIR_LENGTH))
2608 {
2609 // TODO: Use some kind of default path?
2610 return FALSE;
2611 }
2612
2613 /* Set the drive */
2614 CurrentDrive = DosDirectory[0] - 'A';
2615
2616 /* Get the directory part of the path */
2617 Path = strchr(DosDirectory, '\\');
2618 if (Path != NULL)
2619 {
2620 /* Skip the backslash */
2621 Path++;
2622 }
2623
2624 /* Set the directory */
2625 if (Path != NULL)
2626 {
2627 strncpy(CurrentDirectories[CurrentDrive], Path, DOS_DIR_LENGTH);
2628 }
2629
2630 /* Read CONFIG.SYS */
2631 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
2632 if (Stream != NULL)
2633 {
2634 while (fgetws(Buffer, 256, Stream))
2635 {
2636 // TODO: Parse the line
2637 }
2638 fclose(Stream);
2639 }
2640
2641 /* Initialize the SFT */
2642 for (i = 0; i < DOS_SFT_SIZE; i++)
2643 {
2644 DosSystemFileTable[i].Handle = INVALID_HANDLE_VALUE;
2645 DosSystemFileTable[i].RefCount = 0;
2646 }
2647
2648 /* Get handles to standard I/O devices */
2649 DosSystemFileTable[0].Handle = GetStdHandle(STD_INPUT_HANDLE);
2650 DosSystemFileTable[1].Handle = GetStdHandle(STD_OUTPUT_HANDLE);
2651 DosSystemFileTable[2].Handle = GetStdHandle(STD_ERROR_HANDLE);
2652
2653 /* Initialize the reference counts */
2654 DosSystemFileTable[0].RefCount =
2655 DosSystemFileTable[1].RefCount =
2656 DosSystemFileTable[2].RefCount = 1;
2657
2658 #endif
2659
2660 /* Initialize the callback context */
2661 InitializeContext(&DosContext, 0x0070, 0x0000);
2662
2663 /* Register the DOS 32-bit Interrupts */
2664 RegisterDosInt32(0x20, DosInt20h );
2665 RegisterDosInt32(0x21, DosInt21h );
2666 // RegisterDosInt32(0x22, DosInt22h ); // Termination
2667 RegisterDosInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
2668 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
2669 RegisterDosInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
2670 RegisterDosInt32(0x2F, DosInt2Fh );
2671
2672 return TRUE;
2673 }
2674
2675 /* EOF */