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