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