[NTVDM]: DOS: Add some description / fix a comment.
[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 /*
1381 * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
1382 * for a list of possible flags.
1383 */
1384
1385 if (Handle == DosSystemFileTable[0])
1386 {
1387 /* Console input */
1388 InfoWord |= 1 << 0;
1389 }
1390 else if (Handle == DosSystemFileTable[1])
1391 {
1392 /* Console output */
1393 InfoWord |= 1 << 1;
1394 }
1395
1396 /* It is a device */
1397 InfoWord |= 1 << 7;
1398
1399 /* Return the device information word */
1400 setDX(InfoWord);
1401 return TRUE;
1402 }
1403
1404 /* Unsupported control code */
1405 default:
1406 {
1407 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode);
1408
1409 DosLastError = ERROR_INVALID_PARAMETER;
1410 return FALSE;
1411 }
1412 }
1413 }
1414
1415 VOID WINAPI DosSystemBop(LPWORD Stack)
1416 {
1417 /* Get the Function Number and skip it */
1418 BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
1419 setIP(getIP() + 1);
1420
1421 DPRINT1("Unknown DOS System BOP Function: 0x%02X\n", FuncNum);
1422 }
1423
1424 VOID WINAPI DosCmdInterpreterBop(LPWORD Stack)
1425 {
1426 /* Get the Function Number and skip it */
1427 BYTE FuncNum = *(PBYTE)SEG_OFF_TO_PTR(getCS(), getIP());
1428 setIP(getIP() + 1);
1429
1430 switch (FuncNum)
1431 {
1432 case 0x08: // Launch external command
1433 {
1434 #define CMDLINE_LENGTH 1024
1435
1436 BOOL Result;
1437 DWORD dwExitCode;
1438
1439 LPSTR Command = (LPSTR)SEG_OFF_TO_PTR(getDS(), getSI());
1440 CHAR CommandLine[CMDLINE_LENGTH] = "";
1441 STARTUPINFOA StartupInfo;
1442 PROCESS_INFORMATION ProcessInformation;
1443 DPRINT1("CMD Run Command '%s'\n", Command);
1444
1445 Command[strlen(Command)-1] = 0;
1446
1447 strcpy(CommandLine, "cmd.exe /c ");
1448 strcat(CommandLine, Command);
1449
1450 ZeroMemory(&StartupInfo, sizeof(StartupInfo));
1451 ZeroMemory(&ProcessInformation, sizeof(ProcessInformation));
1452
1453 StartupInfo.cb = sizeof(StartupInfo);
1454
1455 DosPrintCharacter('\n');
1456
1457 Result = CreateProcessA(NULL,
1458 CommandLine,
1459 NULL,
1460 NULL,
1461 TRUE,
1462 0,
1463 NULL,
1464 NULL,
1465 &StartupInfo,
1466 &ProcessInformation);
1467 if (Result)
1468 {
1469 DPRINT1("Command '%s' launched successfully\n");
1470
1471 /* Wait for process termination */
1472 WaitForSingleObject(ProcessInformation.hProcess, INFINITE);
1473
1474 /* Get the exit code */
1475 GetExitCodeProcess(ProcessInformation.hProcess, &dwExitCode);
1476
1477 /* Close handles */
1478 CloseHandle(ProcessInformation.hThread);
1479 CloseHandle(ProcessInformation.hProcess);
1480 }
1481 else
1482 {
1483 DPRINT1("Failed when launched command '%s'\n");
1484 dwExitCode = GetLastError();
1485 }
1486
1487 DosPrintCharacter('\n');
1488
1489 setAL((UCHAR)dwExitCode);
1490
1491 break;
1492 }
1493
1494 default:
1495 {
1496 DPRINT1("Unknown DOS CMD Interpreter BOP Function: 0x%02X\n", FuncNum);
1497 // setCF(1); // Disable, otherwise we enter an infinite loop
1498 break;
1499 }
1500 }
1501 }
1502
1503 VOID WINAPI DosInt20h(LPWORD Stack)
1504 {
1505 /* This is the exit interrupt */
1506 DosTerminateProcess(Stack[STACK_CS], 0);
1507 }
1508
1509 VOID WINAPI DosInt21h(LPWORD Stack)
1510 {
1511 BYTE Character;
1512 SYSTEMTIME SystemTime;
1513 PCHAR String;
1514 PDOS_INPUT_BUFFER InputBuffer;
1515
1516 /* Check the value in the AH register */
1517 switch (getAH())
1518 {
1519 /* Terminate Program */
1520 case 0x00:
1521 {
1522 DosTerminateProcess(Stack[STACK_CS], 0);
1523 break;
1524 }
1525
1526 /* Read Character from STDIN with Echo */
1527 case 0x01:
1528 {
1529 Character = DosReadCharacter();
1530 DosPrintCharacter(Character);
1531
1532 /* Let the BOP repeat if needed */
1533 if (getCF()) break;
1534
1535 setAL(Character);
1536 break;
1537 }
1538
1539 /* Write Character to STDOUT */
1540 case 0x02:
1541 {
1542 Character = getDL();
1543 DosPrintCharacter(Character);
1544
1545 /*
1546 * We return the output character (DOS 2.1+).
1547 * Also, if we're going to output a TAB, then
1548 * don't return a TAB but a SPACE instead.
1549 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1550 * for more information.
1551 */
1552 setAL(Character == '\t' ? ' ' : Character);
1553 break;
1554 }
1555
1556 /* Read Character from STDAUX */
1557 case 0x03:
1558 {
1559 // FIXME: Really read it from STDAUX!
1560 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1561 setAL(DosReadCharacter());
1562 break;
1563 }
1564
1565 /* Write Character to STDAUX */
1566 case 0x04:
1567 {
1568 // FIXME: Really write it to STDAUX!
1569 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1570 DosPrintCharacter(getDL());
1571 break;
1572 }
1573
1574 /* Write Character to Printer */
1575 case 0x05:
1576 {
1577 // FIXME: Really write it to printer!
1578 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1579 DPRINT1("0x%p\n", getDL());
1580 DPRINT1("\n\n-----------\n\n");
1581 break;
1582 }
1583
1584 /* Direct Console I/O */
1585 case 0x06:
1586 {
1587 Character = getDL();
1588
1589 if (Character != 0xFF)
1590 {
1591 /* Output */
1592 DosPrintCharacter(Character);
1593
1594 /*
1595 * We return the output character (DOS 2.1+).
1596 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1597 * for more information.
1598 */
1599 setAL(Character);
1600 }
1601 else
1602 {
1603 /* Input */
1604 if (DosCheckInput())
1605 {
1606 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
1607 setAL(DosReadCharacter());
1608 }
1609 else
1610 {
1611 /* No character available */
1612 Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
1613 setAL(0x00);
1614 }
1615 }
1616
1617 break;
1618 }
1619
1620 /* Character Input without Echo */
1621 case 0x07:
1622 case 0x08:
1623 {
1624 Character = DosReadCharacter();
1625
1626 /* Let the BOP repeat if needed */
1627 if (getCF()) break;
1628
1629 setAL(Character);
1630 break;
1631 }
1632
1633 /* Write string to STDOUT */
1634 case 0x09:
1635 {
1636 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1637
1638 while (*String != '$')
1639 {
1640 DosPrintCharacter(*String);
1641 String++;
1642 }
1643
1644 /*
1645 * We return the terminating character (DOS 2.1+).
1646 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1647 * for more information.
1648 */
1649 setAL('$');
1650 break;
1651 }
1652
1653 /* Read Buffered Input */
1654 case 0x0A:
1655 {
1656 InputBuffer = (PDOS_INPUT_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
1657
1658 while (Stack[STACK_COUNTER] < InputBuffer->MaxLength)
1659 {
1660 /* Try to read a character */
1661 Character = DosReadCharacter();
1662
1663 /* If it's not ready yet, let the BOP repeat */
1664 if (getCF()) break;
1665
1666 /* Echo the character and append it to the buffer */
1667 DosPrintCharacter(Character);
1668 InputBuffer->Buffer[Stack[STACK_COUNTER]] = Character;
1669
1670 if (Character == '\r') break;
1671 Stack[STACK_COUNTER]++;
1672 }
1673
1674 /* Update the length */
1675 InputBuffer->Length = Stack[STACK_COUNTER];
1676 break;
1677 }
1678
1679 /* Get STDIN Status */
1680 case 0x0B:
1681 {
1682 setAL(DosCheckInput() ? 0xFF : 0x00);
1683 break;
1684 }
1685
1686 /* Flush Buffer and Read STDIN */
1687 case 0x0C:
1688 {
1689 BYTE InputFunction = getAL();
1690
1691 /* Flush STDIN buffer */
1692 DosFlushFileBuffers(DOS_INPUT_HANDLE); // Maybe just create a DosFlushInputBuffer...
1693
1694 /*
1695 * If the input function number contained in AL is valid, i.e.
1696 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1697 * recursively with AL == AH.
1698 */
1699 if (InputFunction == 0x01 || InputFunction == 0x06 ||
1700 InputFunction == 0x07 || InputFunction == 0x08 ||
1701 InputFunction == 0x0A)
1702 {
1703 setAH(InputFunction);
1704 /*
1705 * Instead of calling ourselves really recursively as in:
1706 * DosInt21h(Stack);
1707 * prefer resetting the CF flag to let the BOP repeat.
1708 */
1709 setCF(1);
1710 }
1711 break;
1712 }
1713
1714 /* Disk Reset */
1715 case 0x0D:
1716 {
1717 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1718
1719 // TODO: Flush what's needed.
1720 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1721
1722 /* Clear CF in DOS 6 only */
1723 if (PspBlock->DosVersion == 0x0006)
1724 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1725
1726 break;
1727 }
1728
1729 /* Set Default Drive */
1730 case 0x0E:
1731 {
1732 DosChangeDrive(getDL());
1733 setAL(LastDrive - 'A' + 1);
1734 break;
1735 }
1736
1737 /* NULL Function for CP/M Compatibility */
1738 case 0x18:
1739 {
1740 /*
1741 * This function corresponds to the CP/M BDOS function
1742 * "get bit map of logged drives", which is meaningless
1743 * under MS-DOS.
1744 *
1745 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1746 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1747 * for more information.
1748 */
1749 setAL(0x00);
1750 break;
1751 }
1752
1753 /* Get Default Drive */
1754 case 0x19:
1755 {
1756 setAL(CurrentDrive);
1757 break;
1758 }
1759
1760 /* Set Disk Transfer Area */
1761 case 0x1A:
1762 {
1763 DiskTransferArea = MAKELONG(getDX(), getDS());
1764 break;
1765 }
1766
1767 /* NULL Function for CP/M Compatibility */
1768 case 0x1D:
1769 case 0x1E:
1770 {
1771 /*
1772 * Function 0x1D corresponds to the CP/M BDOS function
1773 * "get bit map of read-only drives", which is meaningless
1774 * under MS-DOS.
1775 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1776 * for more information.
1777 *
1778 * Function 0x1E corresponds to the CP/M BDOS function
1779 * "set file attributes", which was meaningless under MS-DOS 1.x.
1780 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1781 * for more information.
1782 */
1783 setAL(0x00);
1784 break;
1785 }
1786
1787 /* NULL Function for CP/M Compatibility */
1788 case 0x20:
1789 {
1790 /*
1791 * This function corresponds to the CP/M BDOS function
1792 * "get/set default user (sublibrary) number", which is meaningless
1793 * under MS-DOS.
1794 *
1795 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1796 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1797 * for more information.
1798 */
1799 setAL(0x00);
1800 break;
1801 }
1802
1803 /* Set Interrupt Vector */
1804 case 0x25:
1805 {
1806 DWORD FarPointer = MAKELONG(getDX(), getDS());
1807 DPRINT1("Setting interrupt 0x%x ...\n", getAL());
1808
1809 /* Write the new far pointer to the IDT */
1810 ((PDWORD)BaseAddress)[getAL()] = FarPointer;
1811 break;
1812 }
1813
1814 /* Create New PSP */
1815 case 0x26:
1816 {
1817 DPRINT1("INT 21h, 26h - Create New PSP is UNIMPLEMENTED\n");
1818 break;
1819 }
1820
1821 /* Get System Date */
1822 case 0x2A:
1823 {
1824 GetLocalTime(&SystemTime);
1825 setCX(SystemTime.wYear);
1826 setDX(MAKEWORD(SystemTime.wDay, SystemTime.wMonth));
1827 setAL(SystemTime.wDayOfWeek);
1828 break;
1829 }
1830
1831 /* Set System Date */
1832 case 0x2B:
1833 {
1834 GetLocalTime(&SystemTime);
1835 SystemTime.wYear = getCX();
1836 SystemTime.wMonth = getDH();
1837 SystemTime.wDay = getDL();
1838
1839 /* Return success or failure */
1840 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1841 break;
1842 }
1843
1844 /* Get System Time */
1845 case 0x2C:
1846 {
1847 GetLocalTime(&SystemTime);
1848 setCX(MAKEWORD(SystemTime.wMinute, SystemTime.wHour));
1849 setDX(MAKEWORD(SystemTime.wMilliseconds / 10, SystemTime.wSecond));
1850 break;
1851 }
1852
1853 /* Set System Time */
1854 case 0x2D:
1855 {
1856 GetLocalTime(&SystemTime);
1857 SystemTime.wHour = getCH();
1858 SystemTime.wMinute = getCL();
1859 SystemTime.wSecond = getDH();
1860 SystemTime.wMilliseconds = getDL() * 10; // In hundredths of seconds
1861
1862 /* Return success or failure */
1863 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1864 break;
1865 }
1866
1867 /* Get Disk Transfer Area */
1868 case 0x2F:
1869 {
1870 setES(HIWORD(DiskTransferArea));
1871 setBX(LOWORD(DiskTransferArea));
1872 break;
1873 }
1874
1875 /* Get DOS Version */
1876 case 0x30:
1877 {
1878 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1879
1880 /*
1881 * DOS 2+ - GET DOS VERSION
1882 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1883 * for more information.
1884 */
1885
1886 if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
1887 {
1888 /*
1889 * Return DOS OEM number:
1890 * 0x00 for IBM PC-DOS
1891 * 0x02 for packaged MS-DOS
1892 */
1893 setBH(0x02);
1894 }
1895
1896 if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
1897 {
1898 /*
1899 * Return version flag:
1900 * 1 << 3 if DOS is in ROM,
1901 * 0 (reserved) if not.
1902 */
1903 setBH(0x00);
1904 }
1905
1906 /* Return DOS 24-bit user serial number in BL:CX */
1907 setBL(0x00);
1908 setCX(0x0000);
1909
1910 /*
1911 * Return DOS version: Minor:Major in AH:AL
1912 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1913 */
1914 setAX(PspBlock->DosVersion);
1915
1916 break;
1917 }
1918
1919 /* Extended functionalities */
1920 case 0x33:
1921 {
1922 if (getAL() == 0x06)
1923 {
1924 /*
1925 * DOS 5+ - GET TRUE VERSION NUMBER
1926 * This function always returns the true version number, unlike
1927 * AH=30h, whose return value may be changed with SETVER.
1928 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1929 * for more information.
1930 */
1931
1932 /*
1933 * Return the true DOS version: Minor:Major in BH:BL
1934 * The Windows NT DOS box returns BX=3205h (version 5.50).
1935 */
1936 setBX(NTDOS_VERSION);
1937
1938 /* DOS revision 0 */
1939 setDL(0x00);
1940
1941 /* Unpatched DOS */
1942 setDH(0x00);
1943 }
1944 // else
1945 // {
1946 // /* Invalid subfunction */
1947 // setAL(0xFF);
1948 // }
1949
1950 break;
1951 }
1952
1953 /* Get Interrupt Vector */
1954 case 0x35:
1955 {
1956 DWORD FarPointer = ((PDWORD)BaseAddress)[getAL()];
1957
1958 /* Read the address from the IDT into ES:BX */
1959 setES(HIWORD(FarPointer));
1960 setBX(LOWORD(FarPointer));
1961 break;
1962 }
1963
1964 /* SWITCH character - AVAILDEV */
1965 case 0x37:
1966 {
1967 if (getAL() == 0x00)
1968 {
1969 /*
1970 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1971 * This setting is ignored by MS-DOS 4.0+.
1972 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1973 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1974 * for more information.
1975 */
1976 setDL('/');
1977 setAL(0x00);
1978 }
1979 else if (getAL() == 0x01)
1980 {
1981 /*
1982 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1983 * This setting is ignored by MS-DOS 5+.
1984 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1985 * for more information.
1986 */
1987 // getDL();
1988 setAL(0xFF);
1989 }
1990 else if (getAL() == 0x02)
1991 {
1992 /*
1993 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1994 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1995 * for more information.
1996 */
1997 // setDL();
1998 setAL(0xFF);
1999 }
2000 else if (getAL() == 0x03)
2001 {
2002 /*
2003 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
2004 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
2005 * for more information.
2006 */
2007 // getDL();
2008 setAL(0xFF);
2009 }
2010 else
2011 {
2012 /* Invalid subfunction */
2013 setAL(0xFF);
2014 }
2015
2016 break;
2017 }
2018
2019 /* Create Directory */
2020 case 0x39:
2021 {
2022 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2023
2024 if (CreateDirectoryA(String, NULL))
2025 {
2026 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2027 }
2028 else
2029 {
2030 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2031 setAX(LOWORD(GetLastError()));
2032 }
2033
2034 break;
2035 }
2036
2037 /* Remove Directory */
2038 case 0x3A:
2039 {
2040 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2041
2042 if (RemoveDirectoryA(String))
2043 {
2044 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2045 }
2046 else
2047 {
2048 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2049 setAX(LOWORD(GetLastError()));
2050 }
2051
2052 break;
2053 }
2054
2055 /* Set Current Directory */
2056 case 0x3B:
2057 {
2058 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2059
2060 if (DosChangeDirectory(String))
2061 {
2062 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2063 }
2064 else
2065 {
2066 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2067 setAX(DosLastError);
2068 }
2069
2070 break;
2071 }
2072
2073 /* Create File */
2074 case 0x3C:
2075 {
2076 WORD FileHandle;
2077 WORD ErrorCode = DosCreateFile(&FileHandle,
2078 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2079 getCX());
2080
2081 if (ErrorCode == 0)
2082 {
2083 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2084 setAX(FileHandle);
2085 }
2086 else
2087 {
2088 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2089 setAX(ErrorCode);
2090 }
2091
2092 break;
2093 }
2094
2095 /* Open File */
2096 case 0x3D:
2097 {
2098 WORD FileHandle;
2099 WORD ErrorCode = DosOpenFile(&FileHandle,
2100 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2101 getAL());
2102
2103 if (ErrorCode == 0)
2104 {
2105 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2106 setAX(FileHandle);
2107 }
2108 else
2109 {
2110 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2111 setAX(ErrorCode);
2112 }
2113
2114 break;
2115 }
2116
2117 /* Close File */
2118 case 0x3E:
2119 {
2120 if (DosCloseHandle(getBX()))
2121 {
2122 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2123 }
2124 else
2125 {
2126 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2127 setAX(ERROR_INVALID_HANDLE);
2128 }
2129
2130 break;
2131 }
2132
2133 /* Read from File or Device */
2134 case 0x3F:
2135 {
2136 WORD Handle = getBX();
2137 LPBYTE Buffer = (LPBYTE)SEG_OFF_TO_PTR(getDS(), getDX());
2138 WORD Count = getCX();
2139 WORD BytesRead = 0;
2140 WORD ErrorCode = ERROR_SUCCESS;
2141 CHAR Character;
2142
2143 if (IsConsoleHandle(DosGetRealHandle(Handle)))
2144 {
2145 while (Stack[STACK_COUNTER] < Count)
2146 {
2147 /* Read a character from the BIOS */
2148 Character = LOBYTE(BiosGetCharacter());
2149
2150 /* Stop if the BOP needs to be repeated */
2151 if (getCF()) break;
2152
2153 // FIXME: Security checks!
2154 DosPrintCharacter(Character);
2155 Buffer[Stack[STACK_COUNTER]++] = Character;
2156
2157 if (Character == '\r')
2158 {
2159 /* Stop on first carriage return */
2160 DosPrintCharacter('\n');
2161 break;
2162 }
2163 }
2164
2165 if (Character != '\r')
2166 {
2167 if (Stack[STACK_COUNTER] < Count) ErrorCode = ERROR_NOT_READY;
2168 else BytesRead = Count;
2169 }
2170 else BytesRead = Stack[STACK_COUNTER];
2171 }
2172 else
2173 {
2174 /* Use the file reading function */
2175 ErrorCode = DosReadFile(Handle, Buffer, Count, &BytesRead);
2176 }
2177
2178 if (ErrorCode == ERROR_SUCCESS)
2179 {
2180 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2181 setAX(BytesRead);
2182 }
2183 else if (ErrorCode != ERROR_NOT_READY)
2184 {
2185 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2186 setAX(ErrorCode);
2187 }
2188 break;
2189 }
2190
2191 /* Write to File or Device */
2192 case 0x40:
2193 {
2194 WORD BytesWritten = 0;
2195 WORD ErrorCode = DosWriteFile(getBX(),
2196 SEG_OFF_TO_PTR(getDS(), getDX()),
2197 getCX(),
2198 &BytesWritten);
2199
2200 if (ErrorCode == ERROR_SUCCESS)
2201 {
2202 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2203 setAX(BytesWritten);
2204 }
2205 else
2206 {
2207 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2208 setAX(ErrorCode);
2209 }
2210
2211 break;
2212 }
2213
2214 /* Delete File */
2215 case 0x41:
2216 {
2217 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2218
2219 /* Call the API function */
2220 if (DeleteFileA(FileName))
2221 {
2222 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2223 /*
2224 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2225 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2226 */
2227 setAL(FileName[0] - 'A');
2228 }
2229 else
2230 {
2231 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2232 setAX(GetLastError());
2233 }
2234
2235 break;
2236 }
2237
2238 /* Seek File */
2239 case 0x42:
2240 {
2241 DWORD NewLocation;
2242 WORD ErrorCode = DosSeekFile(getBX(),
2243 MAKELONG(getDX(), getCX()),
2244 getAL(),
2245 &NewLocation);
2246
2247 if (ErrorCode == ERROR_SUCCESS)
2248 {
2249 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2250
2251 /* Return the new offset in DX:AX */
2252 setDX(HIWORD(NewLocation));
2253 setAX(LOWORD(NewLocation));
2254 }
2255 else
2256 {
2257 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2258 setAX(ErrorCode);
2259 }
2260
2261 break;
2262 }
2263
2264 /* Get/Set File Attributes */
2265 case 0x43:
2266 {
2267 DWORD Attributes;
2268 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2269
2270 if (getAL() == 0x00)
2271 {
2272 /* Get the attributes */
2273 Attributes = GetFileAttributesA(FileName);
2274
2275 /* Check if it failed */
2276 if (Attributes == INVALID_FILE_ATTRIBUTES)
2277 {
2278 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2279 setAX(GetLastError());
2280 }
2281 else
2282 {
2283 /* Return the attributes that DOS can understand */
2284 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2285 setCX(Attributes & 0x00FF);
2286 }
2287 }
2288 else if (getAL() == 0x01)
2289 {
2290 /* Try to set the attributes */
2291 if (SetFileAttributesA(FileName, getCL()))
2292 {
2293 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2294 }
2295 else
2296 {
2297 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2298 setAX(GetLastError());
2299 }
2300 }
2301 else
2302 {
2303 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2304 setAX(ERROR_INVALID_FUNCTION);
2305 }
2306
2307 break;
2308 }
2309
2310 /* IOCTL */
2311 case 0x44:
2312 {
2313 if (DosHandleIoctl(getAL(), getBX()))
2314 {
2315 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2316 }
2317 else
2318 {
2319 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2320 setAX(DosLastError);
2321 }
2322
2323 break;
2324 }
2325
2326 /* Duplicate Handle */
2327 case 0x45:
2328 {
2329 WORD NewHandle;
2330 HANDLE Handle = DosGetRealHandle(getBX());
2331
2332 if (Handle != INVALID_HANDLE_VALUE)
2333 {
2334 /* The handle is invalid */
2335 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2336 setAX(ERROR_INVALID_HANDLE);
2337 break;
2338 }
2339
2340 /* Open a new handle to the same entry */
2341 NewHandle = DosOpenHandle(Handle);
2342
2343 if (NewHandle == INVALID_DOS_HANDLE)
2344 {
2345 /* Too many files open */
2346 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2347 setAX(ERROR_TOO_MANY_OPEN_FILES);
2348 break;
2349 }
2350
2351 /* Return the result */
2352 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2353 setAX(NewHandle);
2354 break;
2355 }
2356
2357 /* Force Duplicate Handle */
2358 case 0x46:
2359 {
2360 if (DosDuplicateHandle(getBX(), getCX()))
2361 {
2362 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2363 }
2364 else
2365 {
2366 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2367 setAX(ERROR_INVALID_HANDLE);
2368 }
2369
2370 break;
2371 }
2372
2373 /* Get Current Directory */
2374 case 0x47:
2375 {
2376 BYTE DriveNumber = getDL();
2377 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
2378
2379 /* Get the real drive number */
2380 if (DriveNumber == 0)
2381 {
2382 DriveNumber = CurrentDrive;
2383 }
2384 else
2385 {
2386 /* Decrement DriveNumber since it was 1-based */
2387 DriveNumber--;
2388 }
2389
2390 if (DriveNumber <= LastDrive - 'A')
2391 {
2392 /*
2393 * Copy the current directory into the target buffer.
2394 * It doesn't contain the drive letter and the backslash.
2395 */
2396 strncpy(String, CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
2397 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2398 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2399 }
2400 else
2401 {
2402 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2403 setAX(ERROR_INVALID_DRIVE);
2404 }
2405
2406 break;
2407 }
2408
2409 /* Allocate Memory */
2410 case 0x48:
2411 {
2412 WORD MaxAvailable = 0;
2413 WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
2414
2415 if (Segment != 0)
2416 {
2417 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2418 setAX(Segment);
2419 }
2420 else
2421 {
2422 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2423 setAX(DosLastError);
2424 setBX(MaxAvailable);
2425 }
2426
2427 break;
2428 }
2429
2430 /* Free Memory */
2431 case 0x49:
2432 {
2433 if (DosFreeMemory(getES()))
2434 {
2435 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2436 }
2437 else
2438 {
2439 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2440 setAX(ERROR_ARENA_TRASHED);
2441 }
2442
2443 break;
2444 }
2445
2446 /* Resize Memory Block */
2447 case 0x4A:
2448 {
2449 WORD Size;
2450
2451 if (DosResizeMemory(getES(), getBX(), &Size))
2452 {
2453 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2454 }
2455 else
2456 {
2457 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2458 setAX(DosLastError);
2459 setBX(Size);
2460 }
2461
2462 break;
2463 }
2464
2465 /* Terminate With Return Code */
2466 case 0x4C:
2467 {
2468 DosTerminateProcess(CurrentPsp, getAL());
2469 break;
2470 }
2471
2472 /* Get Return Code (ERRORLEVEL) */
2473 case 0x4D:
2474 {
2475 /*
2476 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2477 * DosErrorLevel is cleared after being read by this function.
2478 */
2479 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2480 setAX(DosErrorLevel);
2481 DosErrorLevel = 0x0000; // Clear it
2482 break;
2483 }
2484
2485 /* Internal - Set Current Process ID (Set PSP Address) */
2486 case 0x50:
2487 {
2488 // FIXME: Is it really what it's done ??
2489 CurrentPsp = getBX();
2490 break;
2491 }
2492
2493 /* Internal - Get Current Process ID (Get PSP Address) */
2494 case 0x51:
2495 /* Get Current PSP Address */
2496 case 0x62:
2497 {
2498 /*
2499 * Undocumented AH=51h is identical to the documented AH=62h.
2500 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2501 * and http://www.ctyme.com/intr/rb-3140.htm
2502 * for more information.
2503 */
2504 setBX(CurrentPsp);
2505 break;
2506 }
2507
2508 /* Get/Set Memory Management Options */
2509 case 0x58:
2510 {
2511 if (getAL() == 0x00)
2512 {
2513 /* Get allocation strategy */
2514 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2515 setAX(DosAllocStrategy);
2516 }
2517 else if (getAL() == 0x01)
2518 {
2519 /* Set allocation strategy */
2520
2521 if ((getBL() & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2522 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2523 {
2524 /* Can't set both */
2525 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2526 setAX(ERROR_INVALID_PARAMETER);
2527 break;
2528 }
2529
2530 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT)
2531 {
2532 /* Invalid allocation strategy */
2533 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2534 setAX(ERROR_INVALID_PARAMETER);
2535 break;
2536 }
2537
2538 DosAllocStrategy = getBL();
2539 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2540 }
2541 else if (getAL() == 0x02)
2542 {
2543 /* Get UMB link state */
2544 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2545 setAL(DosUmbLinked ? 0x01 : 0x00);
2546 }
2547 else if (getAL() == 0x03)
2548 {
2549 /* Set UMB link state */
2550 if (getBX()) DosLinkUmb();
2551 else DosUnlinkUmb();
2552 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2553 }
2554 else
2555 {
2556 /* Invalid or unsupported function */
2557 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2558 setAX(ERROR_INVALID_FUNCTION);
2559 }
2560
2561 break;
2562 }
2563
2564 /* Unsupported */
2565 default:
2566 {
2567 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2568 getAH(), getAL());
2569
2570 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2571 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2572 }
2573 }
2574 }
2575
2576 VOID WINAPI DosBreakInterrupt(LPWORD Stack)
2577 {
2578 UNREFERENCED_PARAMETER(Stack);
2579
2580 /* Stop the VDM */
2581 VdmRunning = FALSE;
2582 }
2583
2584 VOID WINAPI DosFastConOut(LPWORD Stack)
2585 {
2586 /*
2587 * This is the DOS 2+ Fast Console Output Interrupt.
2588 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2589 * for more information.
2590 */
2591 UNREFERENCED_PARAMETER(Stack);
2592
2593 /*
2594 * The default handler under DOS 2.x and 3.x simply calls INT 10/AH=0Eh.
2595 * Do better and call directly BiosPrintCharacter: it's what INT 10/AH=0Eh
2596 * does. Otherwise we would have to set BL to DOS_CHAR_ATTRIBUTE and
2597 * BH to Bda->VideoPage.
2598 */
2599 BiosPrintCharacter(getAL(), DOS_CHAR_ATTRIBUTE, Bda->VideoPage);
2600 }
2601
2602 VOID WINAPI DosInt2Fh(LPWORD Stack)
2603 {
2604 DPRINT1("DOS System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2605 getAH(), getAL());
2606 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2607 }
2608
2609 BOOLEAN DosInitialize(VOID)
2610 {
2611 BYTE i;
2612 PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
2613 FILE *Stream;
2614 WCHAR Buffer[256];
2615 LPWSTR SourcePtr, Environment;
2616 LPSTR AsciiString;
2617 LPSTR DestPtr = (LPSTR)SEG_OFF_TO_PTR(SYSTEM_ENV_BLOCK, 0);
2618 DWORD AsciiSize;
2619 CHAR CurrentDirectory[MAX_PATH];
2620 CHAR DosDirectory[DOS_DIR_LENGTH];
2621 LPSTR Path;
2622
2623 /* Initialize the MCB */
2624 Mcb->BlockType = 'Z';
2625 Mcb->Size = USER_MEMORY_SIZE;
2626 Mcb->OwnerPsp = 0;
2627
2628 /* Initialize the link MCB to the UMB area */
2629 Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT + USER_MEMORY_SIZE + 1);
2630 Mcb->BlockType = 'M';
2631 Mcb->Size = UMB_START_SEGMENT - FIRST_MCB_SEGMENT - USER_MEMORY_SIZE - 2;
2632 Mcb->OwnerPsp = SYSTEM_PSP;
2633
2634 /* Initialize the UMB area */
2635 Mcb = SEGMENT_TO_MCB(UMB_START_SEGMENT);
2636 Mcb->BlockType = 'Z';
2637 Mcb->Size = UMB_END_SEGMENT - UMB_START_SEGMENT;
2638 Mcb->OwnerPsp = 0;
2639
2640 /* Get the environment strings */
2641 SourcePtr = Environment = GetEnvironmentStringsW();
2642 if (Environment == NULL) return FALSE;
2643
2644 /* Fill the DOS system environment block */
2645 while (*SourcePtr)
2646 {
2647 /* Get the size of the ASCII string */
2648 AsciiSize = WideCharToMultiByte(CP_ACP,
2649 0,
2650 SourcePtr,
2651 -1,
2652 NULL,
2653 0,
2654 NULL,
2655 NULL);
2656
2657 /* Allocate memory for the ASCII string */
2658 AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
2659 if (AsciiString == NULL)
2660 {
2661 FreeEnvironmentStringsW(Environment);
2662 return FALSE;
2663 }
2664
2665 /* Convert to ASCII */
2666 WideCharToMultiByte(CP_ACP,
2667 0,
2668 SourcePtr,
2669 -1,
2670 AsciiString,
2671 AsciiSize,
2672 NULL,
2673 NULL);
2674
2675 /* Copy the string into DOS memory */
2676 strcpy(DestPtr, AsciiString);
2677
2678 /* Move to the next string */
2679 SourcePtr += wcslen(SourcePtr) + 1;
2680 DestPtr += strlen(AsciiString);
2681 *(DestPtr++) = 0;
2682
2683 /* Free the memory */
2684 HeapFree(GetProcessHeap(), 0, AsciiString);
2685 }
2686 *DestPtr = 0;
2687
2688 /* Free the memory allocated for environment strings */
2689 FreeEnvironmentStringsW(Environment);
2690
2691 /* Clear the current directory buffer */
2692 ZeroMemory(CurrentDirectories, sizeof(CurrentDirectories));
2693
2694 /* Get the current directory */
2695 if (!GetCurrentDirectoryA(MAX_PATH, CurrentDirectory))
2696 {
2697 // TODO: Use some kind of default path?
2698 return FALSE;
2699 }
2700
2701 /* Convert that to a DOS path */
2702 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, DOS_DIR_LENGTH))
2703 {
2704 // TODO: Use some kind of default path?
2705 return FALSE;
2706 }
2707
2708 /* Set the drive */
2709 CurrentDrive = DosDirectory[0] - 'A';
2710
2711 /* Get the directory part of the path */
2712 Path = strchr(DosDirectory, '\\');
2713 if (Path != NULL)
2714 {
2715 /* Skip the backslash */
2716 Path++;
2717 }
2718
2719 /* Set the directory */
2720 if (Path != NULL)
2721 {
2722 strncpy(CurrentDirectories[CurrentDrive], Path, DOS_DIR_LENGTH);
2723 }
2724
2725 /* Read CONFIG.SYS */
2726 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
2727 if (Stream != NULL)
2728 {
2729 while (fgetws(Buffer, 256, Stream))
2730 {
2731 // TODO: Parse the line
2732 }
2733 fclose(Stream);
2734 }
2735
2736 /* Initialize the SFT */
2737 for (i = 0; i < DOS_SFT_SIZE; i++)
2738 {
2739 DosSystemFileTable[i] = INVALID_HANDLE_VALUE;
2740 DosSftRefCount[i] = 0;
2741 }
2742
2743 /* Get handles to standard I/O devices */
2744 DosSystemFileTable[0] = GetStdHandle(STD_INPUT_HANDLE);
2745 DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE);
2746 DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE);
2747
2748 /* Register the DOS BOPs */
2749 RegisterBop(BOP_DOS, DosSystemBop );
2750 RegisterBop(BOP_CMD, DosCmdInterpreterBop);
2751
2752 /* Register the DOS 32-bit Interrupts */
2753 RegisterInt32(0x20, DosInt20h );
2754 RegisterInt32(0x21, DosInt21h );
2755 // RegisterInt32(0x22, DosInt22h ); // Termination
2756 RegisterInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
2757 // RegisterInt32(0x24, DosInt24h ); // Critical Error
2758 RegisterInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
2759 RegisterInt32(0x2F, DosInt2Fh );
2760
2761 return TRUE;
2762 }
2763
2764 /* EOF */