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