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