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