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