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