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