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