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