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