[NTVDM]
[reactos.git] / reactos / subsystems / mvdm / ntvdm / dos / dos32krnl / dos.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: dos/dos32krnl/dos.c
5 * PURPOSE: DOS32 Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 * Hermes Belusca-Maito (hermes.belusca@sfr.fr)
8 */
9
10 /* INCLUDES *******************************************************************/
11
12 #define NDEBUG
13
14 #include "ntvdm.h"
15 #include "emulator.h"
16 #include "cpu/cpu.h"
17 #include "int32.h"
18
19 #include "dos.h"
20 #include "dos/dem.h"
21 #include "country.h"
22 #include "device.h"
23 #include "handle.h"
24 #include "dosfiles.h"
25 #include "memory.h"
26 #include "process.h"
27 #include "himem.h"
28
29 #include "bios/bios.h"
30
31 #include "io.h"
32 #include "hardware/ps2.h"
33
34 #include "emsdrv.h"
35
36 /* PRIVATE VARIABLES **********************************************************/
37
38 CALLBACK16 DosContext;
39
40 /* PUBLIC VARIABLES ***********************************************************/
41
42 /* Global DOS data area contained in guest memory */
43 PDOS_DATA DosData;
44 /* Easy accessors to useful DOS data area parts */
45 PDOS_SYSVARS SysVars;
46 PDOS_SDA Sda;
47
48 /* Echo state for INT 21h, AH = 01h and AH = 3Fh */
49 BOOLEAN DoEcho = FALSE;
50
51 /* PRIVATE FUNCTIONS **********************************************************/
52
53 static BOOLEAN DosChangeDrive(BYTE Drive)
54 {
55 CHAR DirectoryPath[DOS_CMDLINE_LENGTH + 1];
56
57 /* Make sure the drive exists */
58 if (Drive >= SysVars->NumLocalDrives) return FALSE;
59
60 RtlZeroMemory(DirectoryPath, sizeof(DirectoryPath));
61
62 /* Find the path to the new current directory */
63 snprintf(DirectoryPath,
64 DOS_CMDLINE_LENGTH,
65 "%c:\\%s",
66 'A' + Drive,
67 DosData->CurrentDirectories[Drive]);
68
69 /* Change the current directory of the process */
70 if (!SetCurrentDirectoryA(DirectoryPath)) return FALSE;
71
72 /* Set the current drive */
73 Sda->CurrentDrive = Drive;
74
75 /* Return success */
76 return TRUE;
77 }
78
79 static BOOLEAN DosChangeDirectory(LPSTR Directory)
80 {
81 BYTE DriveNumber;
82 DWORD Attributes;
83 LPSTR Path;
84 CHAR CurrentDirectory[MAX_PATH];
85 CHAR DosDirectory[DOS_DIR_LENGTH];
86
87 /* Make sure the directory path is not too long */
88 if (strlen(Directory) >= DOS_DIR_LENGTH)
89 {
90 Sda->LastErrorCode = ERROR_PATH_NOT_FOUND;
91 return FALSE;
92 }
93
94 /* Check whether the directory string is of format "?:..." */
95 if (strlen(Directory) >= 2 && Directory[1] == ':')
96 {
97 /* Get the drive number */
98 DriveNumber = RtlUpperChar(Directory[0]) - 'A';
99
100 /* Make sure the drive exists */
101 if (DriveNumber >= SysVars->NumLocalDrives)
102 {
103 Sda->LastErrorCode = ERROR_PATH_NOT_FOUND;
104 return FALSE;
105 }
106 }
107 else
108 {
109 /* Keep the current drive number */
110 DriveNumber = Sda->CurrentDrive;
111 }
112
113 /* Get the file attributes */
114 Attributes = GetFileAttributesA(Directory);
115
116 /* Make sure the path exists and is a directory */
117 if ((Attributes == INVALID_FILE_ATTRIBUTES)
118 || !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
119 {
120 Sda->LastErrorCode = ERROR_PATH_NOT_FOUND;
121 return FALSE;
122 }
123
124 /* Check if this is the current drive */
125 if (DriveNumber == Sda->CurrentDrive)
126 {
127 /* Change the directory */
128 if (!SetCurrentDirectoryA(Directory))
129 {
130 Sda->LastErrorCode = LOWORD(GetLastError());
131 return FALSE;
132 }
133 }
134
135 /* Get the (possibly new) current directory (needed if we specified a relative directory) */
136 if (!GetCurrentDirectoryA(sizeof(CurrentDirectory), CurrentDirectory))
137 {
138 // TODO: Use some kind of default path?
139 return FALSE;
140 }
141
142 /* Convert it to a DOS path */
143 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, sizeof(DosDirectory)))
144 {
145 // TODO: Use some kind of default path?
146 return FALSE;
147 }
148
149 /* Get the directory part of the path */
150 Path = strchr(DosDirectory, '\\');
151 if (Path != NULL)
152 {
153 /* Skip the backslash */
154 Path++;
155 }
156
157 /* Set the directory for the drive */
158 if (Path != NULL)
159 {
160 strncpy(DosData->CurrentDirectories[DriveNumber], Path, DOS_DIR_LENGTH);
161 }
162 else
163 {
164 DosData->CurrentDirectories[DriveNumber][0] = '\0';
165 }
166
167 /* Return success */
168 return TRUE;
169 }
170
171 static BOOLEAN DosControlBreak(VOID)
172 {
173 setCF(0);
174
175 /* Call interrupt 0x23 */
176 Int32Call(&DosContext, 0x23);
177
178 if (getCF())
179 {
180 DosTerminateProcess(Sda->CurrentPsp, 0, 0);
181 return TRUE;
182 }
183
184 return FALSE;
185 }
186
187 /* PUBLIC FUNCTIONS ***********************************************************/
188
189 VOID WINAPI DosInt20h(LPWORD Stack)
190 {
191 /* This is the exit interrupt */
192 DosTerminateProcess(Stack[STACK_CS], 0, 0);
193 }
194
195 VOID WINAPI DosInt21h(LPWORD Stack)
196 {
197 BYTE Character;
198 SYSTEMTIME SystemTime;
199 PCHAR String;
200 PDOS_INPUT_BUFFER InputBuffer;
201
202 Sda->InDos++;
203
204 /* Save the value of SS:SP on entry in the PSP */
205 SEGMENT_TO_PSP(Sda->CurrentPsp)->LastStack =
206 MAKELONG(getSP() + (STACK_FLAGS + 1) * 2, getSS());
207
208 /* Check the value in the AH register */
209 switch (getAH())
210 {
211 /* Terminate Program */
212 case 0x00:
213 {
214 DosTerminateProcess(Stack[STACK_CS], 0, 0);
215 break;
216 }
217
218 /* Read Character from STDIN with Echo */
219 case 0x01:
220 {
221 DPRINT("INT 21h, AH = 01h\n");
222
223 // FIXME: Under DOS 2+, input / output handle may be redirected!!!!
224 DoEcho = TRUE;
225 Character = DosReadCharacter(DOS_INPUT_HANDLE);
226 DoEcho = FALSE;
227
228 // FIXME: Check whether Ctrl-C / Ctrl-Break is pressed, and call INT 23h if so.
229 // Check also Ctrl-P and set echo-to-printer flag.
230 // Ctrl-Z is not interpreted.
231
232 setAL(Character);
233 break;
234 }
235
236 /* Write Character to STDOUT */
237 case 0x02:
238 {
239 // FIXME: Under DOS 2+, output handle may be redirected!!!!
240 Character = getDL();
241 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
242
243 /*
244 * We return the output character (DOS 2.1+).
245 * Also, if we're going to output a TAB, then
246 * don't return a TAB but a SPACE instead.
247 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
248 * for more information.
249 */
250 setAL(Character == '\t' ? ' ' : Character);
251 break;
252 }
253
254 /* Read Character from STDAUX */
255 case 0x03:
256 {
257 // FIXME: Really read it from STDAUX!
258 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
259 // setAL(DosReadCharacter());
260 break;
261 }
262
263 /* Write Character to STDAUX */
264 case 0x04:
265 {
266 // FIXME: Really write it to STDAUX!
267 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
268 // DosPrintCharacter(getDL());
269 break;
270 }
271
272 /* Write Character to Printer */
273 case 0x05:
274 {
275 // FIXME: Really write it to printer!
276 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
277 DPRINT1("0x%p\n", getDL());
278 DPRINT1("\n\n-----------\n\n");
279 break;
280 }
281
282 /* Direct Console I/O */
283 case 0x06:
284 {
285 Character = getDL();
286
287 // FIXME: Under DOS 2+, output handle may be redirected!!!!
288
289 if (Character != 0xFF)
290 {
291 /* Output */
292 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
293
294 /*
295 * We return the output character (DOS 2.1+).
296 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
297 * for more information.
298 */
299 setAL(Character);
300 }
301 else
302 {
303 /* Input */
304 if (DosCheckInput())
305 {
306 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
307 setAL(DosReadCharacter(DOS_INPUT_HANDLE));
308 }
309 else
310 {
311 /* No character available */
312 Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
313 setAL(0x00);
314 }
315 }
316
317 break;
318 }
319
320 /* Character Input without Echo */
321 case 0x07:
322 case 0x08:
323 {
324 DPRINT("Char input without echo\n");
325
326 Character = DosReadCharacter(DOS_INPUT_HANDLE);
327
328 // FIXME: For 0x07, do not check Ctrl-C/Break.
329 // For 0x08, do check those control sequences and if needed,
330 // call INT 0x23.
331
332 setAL(Character);
333 break;
334 }
335
336 /* Write String to STDOUT */
337 case 0x09:
338 {
339 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
340
341 while (*String != '$')
342 {
343 DosPrintCharacter(DOS_OUTPUT_HANDLE, *String);
344 String++;
345 }
346
347 /*
348 * We return the terminating character (DOS 2.1+).
349 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
350 * for more information.
351 */
352 setAL('$'); // *String
353 break;
354 }
355
356 /* Read Buffered Input */
357 case 0x0A:
358 {
359 WORD Count = 0;
360 InputBuffer = (PDOS_INPUT_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
361
362 DPRINT("Read Buffered Input\n");
363
364 while (Count < InputBuffer->MaxLength)
365 {
366 /* Try to read a character (wait) */
367 Character = DosReadCharacter(DOS_INPUT_HANDLE);
368
369 switch (Character)
370 {
371 /* Extended character */
372 case '\0':
373 {
374 /* Read the scancode */
375 DosReadCharacter(DOS_INPUT_HANDLE);
376 break;
377 }
378
379 /* Ctrl-C */
380 case 0x03:
381 {
382 DosPrintCharacter(DOS_OUTPUT_HANDLE, '^');
383 DosPrintCharacter(DOS_OUTPUT_HANDLE, 'C');
384
385 if (DosControlBreak())
386 {
387 /* Set the character to a newline to exit the loop */
388 Character = '\r';
389 }
390
391 break;
392 }
393
394 /* Backspace */
395 case '\b':
396 {
397 if (Count > 0)
398 {
399 Count--;
400
401 /* Erase the character */
402 DosPrintCharacter(DOS_OUTPUT_HANDLE, '\b');
403 DosPrintCharacter(DOS_OUTPUT_HANDLE, ' ');
404 DosPrintCharacter(DOS_OUTPUT_HANDLE, '\b');
405 }
406
407 break;
408 }
409
410 default:
411 {
412 /* Append it to the buffer */
413 InputBuffer->Buffer[Count] = Character;
414
415 /* Check if this is a special character */
416 if (Character < 0x20 && Character != 0x0A && Character != 0x0D)
417 {
418 DosPrintCharacter(DOS_OUTPUT_HANDLE, '^');
419 Character += 'A' - 1;
420 }
421
422 /* Echo the character */
423 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
424 }
425 }
426
427 if (Character == '\r') break;
428 if (Character == '\b') continue;
429 Count++; /* Carriage returns are NOT counted */
430 }
431
432 /* Update the length */
433 InputBuffer->Length = Count;
434
435 break;
436 }
437
438 /* Get STDIN Status */
439 case 0x0B:
440 {
441 setAL(DosCheckInput() ? 0xFF : 0x00);
442 break;
443 }
444
445 /* Flush Buffer and Read STDIN */
446 case 0x0C:
447 {
448 BYTE InputFunction = getAL();
449
450 /* Flush STDIN buffer */
451 DosFlushFileBuffers(DOS_INPUT_HANDLE);
452
453 /*
454 * If the input function number contained in AL is valid, i.e.
455 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
456 * recursively with AL == AH.
457 */
458 if (InputFunction == 0x01 || InputFunction == 0x06 ||
459 InputFunction == 0x07 || InputFunction == 0x08 ||
460 InputFunction == 0x0A)
461 {
462 /* Call ourselves recursively */
463 setAH(InputFunction);
464 DosInt21h(Stack);
465 }
466 break;
467 }
468
469 /* Disk Reset */
470 case 0x0D:
471 {
472 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Sda->CurrentPsp);
473
474 // TODO: Flush what's needed.
475 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
476
477 /* Clear CF in DOS 6 only */
478 if (PspBlock->DosVersion == 0x0006)
479 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
480
481 break;
482 }
483
484 /* Set Default Drive */
485 case 0x0E:
486 {
487 DosChangeDrive(getDL());
488 setAL(SysVars->NumLocalDrives);
489 break;
490 }
491
492 /* NULL Function for CP/M Compatibility */
493 case 0x18:
494 {
495 /*
496 * This function corresponds to the CP/M BDOS function
497 * "get bit map of logged drives", which is meaningless
498 * under MS-DOS.
499 *
500 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
501 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
502 * for more information.
503 */
504 setAL(0x00);
505 break;
506 }
507
508 /* Get Default Drive */
509 case 0x19:
510 {
511 setAL(Sda->CurrentDrive);
512 break;
513 }
514
515 /* Set Disk Transfer Area */
516 case 0x1A:
517 {
518 Sda->DiskTransferArea = MAKELONG(getDX(), getDS());
519 break;
520 }
521
522 /* NULL Function for CP/M Compatibility */
523 case 0x1D:
524 case 0x1E:
525 {
526 /*
527 * Function 0x1D corresponds to the CP/M BDOS function
528 * "get bit map of read-only drives", which is meaningless
529 * under MS-DOS.
530 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
531 * for more information.
532 *
533 * Function 0x1E corresponds to the CP/M BDOS function
534 * "set file attributes", which was meaningless under MS-DOS 1.x.
535 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
536 * for more information.
537 */
538 setAL(0x00);
539 break;
540 }
541
542 /* NULL Function for CP/M Compatibility */
543 case 0x20:
544 {
545 /*
546 * This function corresponds to the CP/M BDOS function
547 * "get/set default user (sublibrary) number", which is meaningless
548 * under MS-DOS.
549 *
550 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
551 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
552 * for more information.
553 */
554 setAL(0x00);
555 break;
556 }
557
558 /* Set Interrupt Vector */
559 case 0x25:
560 {
561 ULONG FarPointer = MAKELONG(getDX(), getDS());
562 DPRINT1("Setting interrupt 0x%02X to %04X:%04X ...\n",
563 getAL(), HIWORD(FarPointer), LOWORD(FarPointer));
564
565 /* Write the new far pointer to the IDT */
566 ((PULONG)BaseAddress)[getAL()] = FarPointer;
567 break;
568 }
569
570 /* Create New PSP */
571 case 0x26:
572 {
573 DosClonePsp(getDX(), getCS());
574 break;
575 }
576
577 /* Parse Filename into FCB */
578 case 0x29:
579 {
580 PCHAR FileName = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
581 PDOS_FCB Fcb = (PDOS_FCB)SEG_OFF_TO_PTR(getES(), getDI());
582 BYTE Options = getAL();
583 CHAR FillChar = ' ';
584 UINT i;
585
586 if (FileName[1] == ':')
587 {
588 /* Set the drive number */
589 Fcb->DriveNumber = RtlUpperChar(FileName[0]) - 'A' + 1;
590
591 /* Skip to the file name part */
592 FileName += 2;
593 }
594 else
595 {
596 /* No drive number specified */
597 if (Options & (1 << 1)) Fcb->DriveNumber = Sda->CurrentDrive + 1;
598 else Fcb->DriveNumber = 0;
599 }
600
601 /* Parse the file name */
602 i = 0;
603 while ((*FileName > 0x20) && (i < 8))
604 {
605 if (*FileName == '.') break;
606 else if (*FileName == '*')
607 {
608 FillChar = '?';
609 break;
610 }
611
612 Fcb->FileName[i++] = RtlUpperChar(*FileName++);
613 }
614
615 /* Fill the whole field with blanks only if bit 2 is not set */
616 if ((FillChar != ' ') || (i != 0) || !(Options & (1 << 2)))
617 {
618 for (; i < 8; i++) Fcb->FileName[i] = FillChar;
619 }
620
621 /* Skip to the extension part */
622 while (*FileName > 0x20 && *FileName != '.') FileName++;
623 if (*FileName == '.') FileName++;
624
625 /* Now parse the extension */
626 i = 0;
627 FillChar = ' ';
628
629 while ((*FileName > 0x20) && (i < 3))
630 {
631 if (*FileName == '*')
632 {
633 FillChar = '?';
634 break;
635 }
636
637 Fcb->FileExt[i++] = RtlUpperChar(*FileName++);
638 }
639
640 /* Fill the whole field with blanks only if bit 3 is not set */
641 if ((FillChar != ' ') || (i != 0) || !(Options & (1 << 3)))
642 {
643 for (; i < 3; i++) Fcb->FileExt[i] = FillChar;
644 }
645
646 break;
647 }
648
649 /* Get System Date */
650 case 0x2A:
651 {
652 GetLocalTime(&SystemTime);
653 setCX(SystemTime.wYear);
654 setDX(MAKEWORD(SystemTime.wDay, SystemTime.wMonth));
655 setAL(SystemTime.wDayOfWeek);
656 break;
657 }
658
659 /* Set System Date */
660 case 0x2B:
661 {
662 GetLocalTime(&SystemTime);
663 SystemTime.wYear = getCX();
664 SystemTime.wMonth = getDH();
665 SystemTime.wDay = getDL();
666
667 /* Return success or failure */
668 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
669 break;
670 }
671
672 /* Get System Time */
673 case 0x2C:
674 {
675 GetLocalTime(&SystemTime);
676 setCX(MAKEWORD(SystemTime.wMinute, SystemTime.wHour));
677 setDX(MAKEWORD(SystemTime.wMilliseconds / 10, SystemTime.wSecond));
678 break;
679 }
680
681 /* Set System Time */
682 case 0x2D:
683 {
684 GetLocalTime(&SystemTime);
685 SystemTime.wHour = getCH();
686 SystemTime.wMinute = getCL();
687 SystemTime.wSecond = getDH();
688 SystemTime.wMilliseconds = getDL() * 10; // In hundredths of seconds
689
690 /* Return success or failure */
691 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
692 break;
693 }
694
695 /* Get Disk Transfer Area */
696 case 0x2F:
697 {
698 setES(HIWORD(Sda->DiskTransferArea));
699 setBX(LOWORD(Sda->DiskTransferArea));
700 break;
701 }
702
703 /* Get DOS Version */
704 case 0x30:
705 {
706 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Sda->CurrentPsp);
707
708 /*
709 * DOS 2+ - GET DOS VERSION
710 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
711 * for more information.
712 */
713
714 if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
715 {
716 /*
717 * Return DOS OEM number:
718 * 0x00 for IBM PC-DOS
719 * 0x02 for packaged MS-DOS
720 * 0xFF for NT DOS
721 */
722 setBH(0xFF);
723 }
724
725 if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
726 {
727 /*
728 * Return version flag:
729 * 1 << 3 if DOS is in ROM,
730 * 0 (reserved) if not.
731 */
732 setBH(0x00);
733 }
734
735 /* Return DOS 24-bit user serial number in BL:CX */
736 setBL(0x00);
737 setCX(0x0000);
738
739 /*
740 * Return DOS version: Minor:Major in AH:AL
741 * The Windows NT DOS box returns version 5.00, subject to SETVER.
742 */
743 setAX(PspBlock->DosVersion);
744
745 break;
746 }
747
748 /* Terminate and Stay Resident */
749 case 0x31:
750 {
751 DPRINT1("Process going resident: %u paragraphs kept\n", getDX());
752 DosTerminateProcess(Sda->CurrentPsp, getAL(), getDX());
753 break;
754 }
755
756 /* Extended functionalities */
757 case 0x33:
758 {
759 switch (getAL())
760 {
761 /*
762 * DOS 4+ - GET BOOT DRIVE
763 */
764 case 0x05:
765 {
766 setDL(SysVars->BootDrive);
767 break;
768 }
769
770 /*
771 * DOS 5+ - GET TRUE VERSION NUMBER
772 * This function always returns the true version number, unlike
773 * AH=30h, whose return value may be changed with SETVER.
774 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
775 * for more information.
776 */
777 case 0x06:
778 {
779 /*
780 * Return the true DOS version: Minor:Major in BH:BL
781 * The Windows NT DOS box returns BX=3205h (version 5.50).
782 */
783 setBX(NTDOS_VERSION);
784
785 /* DOS revision 0 */
786 setDL(0x00);
787
788 /* Unpatched DOS */
789 setDH(0x00);
790
791 break;
792 }
793
794 default:
795 {
796 DPRINT1("INT 21h, AH = 33h, subfunction AL = %Xh NOT IMPLEMENTED\n",
797 getAL());
798 }
799 }
800
801 break;
802 }
803
804 /* Get Address of InDOS flag */
805 case 0x34:
806 {
807 setES(DOS_DATA_SEGMENT);
808 setBX(DOS_DATA_OFFSET(Sda.InDos));
809 break;
810 }
811
812 /* Get Interrupt Vector */
813 case 0x35:
814 {
815 ULONG FarPointer = ((PULONG)BaseAddress)[getAL()];
816
817 /* Read the address from the IDT into ES:BX */
818 setES(HIWORD(FarPointer));
819 setBX(LOWORD(FarPointer));
820 break;
821 }
822
823 /* Get Free Disk Space */
824 case 0x36:
825 {
826 CHAR RootPath[] = "?:\\";
827 DWORD SectorsPerCluster;
828 DWORD BytesPerSector;
829 DWORD NumberOfFreeClusters;
830 DWORD TotalNumberOfClusters;
831
832 if (getDL() == 0x00) RootPath[0] = 'A' + Sda->CurrentDrive;
833 else RootPath[0] = 'A' + getDL() - 1;
834
835 if (GetDiskFreeSpaceA(RootPath,
836 &SectorsPerCluster,
837 &BytesPerSector,
838 &NumberOfFreeClusters,
839 &TotalNumberOfClusters))
840 {
841 setAX(LOWORD(SectorsPerCluster));
842 setCX(LOWORD(BytesPerSector));
843 setBX(min(NumberOfFreeClusters, 0xFFFF));
844 setDX(min(TotalNumberOfClusters, 0xFFFF));
845 }
846 else
847 {
848 /* Error */
849 setAX(0xFFFF);
850 }
851
852 break;
853 }
854
855 /* SWITCH character - AVAILDEV */
856 case 0x37:
857 {
858 switch (getAL())
859 {
860 /*
861 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
862 * This setting is ignored by MS-DOS 4.0+.
863 * MS-DOS 5+ always return AL=00h/DL=2Fh.
864 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
865 * for more information.
866 */
867 case 0x00:
868 setDL('/');
869 setAL(0x00);
870 break;
871
872 /*
873 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
874 * This setting is ignored by MS-DOS 5+.
875 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
876 * for more information.
877 */
878 case 0x01:
879 // getDL();
880 setAL(0xFF);
881 break;
882
883 /*
884 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
885 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
886 * for more information.
887 */
888 case 0x02:
889 // setDL();
890 setAL(0xFF);
891 break;
892
893 /*
894 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
895 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
896 * for more information.
897 */
898 case 0x03:
899 // getDL();
900 setAL(0xFF);
901 break;
902
903 /* Invalid subfunction */
904 default:
905 setAL(0xFF);
906 break;
907 }
908
909 break;
910 }
911
912 /* Get/Set Country-dependent Information */
913 case 0x38:
914 {
915 WORD CountryId = getAL() < 0xFF ? getAL() : getBX();
916 WORD ErrorCode;
917
918 ErrorCode = DosGetCountryInfo(&CountryId,
919 (PDOS_COUNTRY_INFO)SEG_OFF_TO_PTR(getDS(), getDX()));
920
921 if (ErrorCode == ERROR_SUCCESS)
922 {
923 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
924 setBX(CountryId);
925 }
926 else
927 {
928 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
929 setAX(ErrorCode);
930 }
931
932 break;
933 }
934
935 /* Create Directory */
936 case 0x39:
937 {
938 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
939
940 if (CreateDirectoryA(String, NULL))
941 {
942 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
943 }
944 else
945 {
946 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
947 setAX(LOWORD(GetLastError()));
948 }
949
950 break;
951 }
952
953 /* Remove Directory */
954 case 0x3A:
955 {
956 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
957
958 if (RemoveDirectoryA(String))
959 {
960 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
961 }
962 else
963 {
964 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
965 setAX(LOWORD(GetLastError()));
966 }
967
968 break;
969 }
970
971 /* Set Current Directory */
972 case 0x3B:
973 {
974 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
975
976 if (DosChangeDirectory(String))
977 {
978 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
979 }
980 else
981 {
982 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
983 setAX(Sda->LastErrorCode);
984 }
985
986 break;
987 }
988
989 /* Create or Truncate File */
990 case 0x3C:
991 {
992 WORD FileHandle;
993 WORD ErrorCode = DosCreateFile(&FileHandle,
994 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
995 CREATE_ALWAYS,
996 getCX());
997
998 if (ErrorCode == ERROR_SUCCESS)
999 {
1000 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1001 setAX(FileHandle);
1002 }
1003 else
1004 {
1005 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1006 setAX(ErrorCode);
1007 }
1008
1009 break;
1010 }
1011
1012 /* Open File or Device */
1013 case 0x3D:
1014 {
1015 WORD FileHandle;
1016 LPCSTR FileName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX());
1017 WORD ErrorCode = DosOpenFile(&FileHandle, FileName, getAL());
1018
1019 if (ErrorCode == ERROR_SUCCESS)
1020 {
1021 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1022 setAX(FileHandle);
1023 }
1024 else
1025 {
1026 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1027 setAX(ErrorCode);
1028 }
1029
1030 break;
1031 }
1032
1033 /* Close File or Device */
1034 case 0x3E:
1035 {
1036 if (DosCloseHandle(getBX()))
1037 {
1038 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1039 }
1040 else
1041 {
1042 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1043 setAX(ERROR_INVALID_HANDLE);
1044 }
1045
1046 break;
1047 }
1048
1049 /* Read from File or Device */
1050 case 0x3F:
1051 {
1052 WORD BytesRead = 0;
1053 WORD ErrorCode;
1054
1055 DPRINT("DosReadFile(0x%04X)\n", getBX());
1056
1057 DoEcho = TRUE;
1058 ErrorCode = DosReadFile(getBX(),
1059 MAKELONG(getDX(), getDS()),
1060 getCX(),
1061 &BytesRead);
1062 DoEcho = FALSE;
1063
1064 if (ErrorCode == ERROR_SUCCESS)
1065 {
1066 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1067 setAX(BytesRead);
1068 }
1069 else if (ErrorCode != ERROR_NOT_READY)
1070 {
1071 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1072 setAX(ErrorCode);
1073 }
1074
1075 break;
1076 }
1077
1078 /* Write to File or Device */
1079 case 0x40:
1080 {
1081 WORD BytesWritten = 0;
1082 WORD ErrorCode = DosWriteFile(getBX(),
1083 MAKELONG(getDX(), getDS()),
1084 getCX(),
1085 &BytesWritten);
1086
1087 if (ErrorCode == ERROR_SUCCESS)
1088 {
1089 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1090 setAX(BytesWritten);
1091 }
1092 else
1093 {
1094 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1095 setAX(ErrorCode);
1096 }
1097
1098 break;
1099 }
1100
1101 /* Delete File */
1102 case 0x41:
1103 {
1104 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
1105
1106 if (demFileDelete(FileName) == ERROR_SUCCESS)
1107 {
1108 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1109 /*
1110 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
1111 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
1112 */
1113 setAL(FileName[0] - 'A');
1114 }
1115 else
1116 {
1117 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1118 setAX(GetLastError());
1119 }
1120
1121 break;
1122 }
1123
1124 /* Seek File */
1125 case 0x42:
1126 {
1127 DWORD NewLocation;
1128 WORD ErrorCode = DosSeekFile(getBX(),
1129 MAKELONG(getDX(), getCX()),
1130 getAL(),
1131 &NewLocation);
1132
1133 if (ErrorCode == ERROR_SUCCESS)
1134 {
1135 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1136
1137 /* Return the new offset in DX:AX */
1138 setDX(HIWORD(NewLocation));
1139 setAX(LOWORD(NewLocation));
1140 }
1141 else
1142 {
1143 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1144 setAX(ErrorCode);
1145 }
1146
1147 break;
1148 }
1149
1150 /* Get/Set File Attributes */
1151 case 0x43:
1152 {
1153 DWORD Attributes;
1154 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
1155
1156 if (getAL() == 0x00)
1157 {
1158 /* Get the attributes */
1159 Attributes = GetFileAttributesA(FileName);
1160
1161 /* Check if it failed */
1162 if (Attributes == INVALID_FILE_ATTRIBUTES)
1163 {
1164 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1165 setAX(GetLastError());
1166 }
1167 else
1168 {
1169 /* Return the attributes that DOS can understand */
1170 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1171 setCX(Attributes & 0x00FF);
1172 }
1173 }
1174 else if (getAL() == 0x01)
1175 {
1176 /* Try to set the attributes */
1177 if (SetFileAttributesA(FileName, getCL()))
1178 {
1179 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1180 }
1181 else
1182 {
1183 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1184 setAX(GetLastError());
1185 }
1186 }
1187 else
1188 {
1189 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1190 setAX(ERROR_INVALID_FUNCTION);
1191 }
1192
1193 break;
1194 }
1195
1196 /* IOCTL */
1197 case 0x44:
1198 {
1199 WORD Length = getCX();
1200
1201 if (DosDeviceIoControl(getBX(), getAL(), MAKELONG(getDX(), getDS()), &Length))
1202 {
1203 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1204 setAX(Length);
1205 }
1206 else
1207 {
1208 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1209 setAX(Sda->LastErrorCode);
1210 }
1211
1212 break;
1213 }
1214
1215 /* Duplicate Handle */
1216 case 0x45:
1217 {
1218 WORD NewHandle = DosDuplicateHandle(getBX());
1219
1220 if (NewHandle != INVALID_DOS_HANDLE)
1221 {
1222 setAX(NewHandle);
1223 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1224 }
1225 else
1226 {
1227 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1228 setAX(Sda->LastErrorCode);
1229 }
1230
1231 break;
1232 }
1233
1234 /* Force Duplicate Handle */
1235 case 0x46:
1236 {
1237 if (DosForceDuplicateHandle(getBX(), getCX()))
1238 {
1239 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1240 }
1241 else
1242 {
1243 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1244 setAX(ERROR_INVALID_HANDLE);
1245 }
1246
1247 break;
1248 }
1249
1250 /* Get Current Directory */
1251 case 0x47:
1252 {
1253 BYTE DriveNumber = getDL();
1254 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
1255
1256 /* Get the real drive number */
1257 if (DriveNumber == 0)
1258 {
1259 DriveNumber = Sda->CurrentDrive;
1260 }
1261 else
1262 {
1263 /* Decrement DriveNumber since it was 1-based */
1264 DriveNumber--;
1265 }
1266
1267 if (DriveNumber < SysVars->NumLocalDrives)
1268 {
1269 /*
1270 * Copy the current directory into the target buffer.
1271 * It doesn't contain the drive letter and the backslash.
1272 */
1273 strncpy(String, DosData->CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
1274 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1275 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
1276 }
1277 else
1278 {
1279 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1280 setAX(ERROR_INVALID_DRIVE);
1281 }
1282
1283 break;
1284 }
1285
1286 /* Allocate Memory */
1287 case 0x48:
1288 {
1289 WORD MaxAvailable = 0;
1290 WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
1291
1292 if (Segment != 0)
1293 {
1294 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1295 setAX(Segment);
1296 }
1297 else
1298 {
1299 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1300 setAX(Sda->LastErrorCode);
1301 setBX(MaxAvailable);
1302 }
1303
1304 break;
1305 }
1306
1307 /* Free Memory */
1308 case 0x49:
1309 {
1310 if (DosFreeMemory(getES()))
1311 {
1312 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1313 }
1314 else
1315 {
1316 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1317 setAX(ERROR_ARENA_TRASHED);
1318 }
1319
1320 break;
1321 }
1322
1323 /* Resize Memory Block */
1324 case 0x4A:
1325 {
1326 WORD Size;
1327
1328 if (DosResizeMemory(getES(), getBX(), &Size))
1329 {
1330 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1331 }
1332 else
1333 {
1334 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1335 setAX(Sda->LastErrorCode);
1336 setBX(Size);
1337 }
1338
1339 break;
1340 }
1341
1342 /* Execute */
1343 case 0x4B:
1344 {
1345 BYTE OrgAL = getAL();
1346 LPSTR ProgramName = SEG_OFF_TO_PTR(getDS(), getDX());
1347 PDOS_EXEC_PARAM_BLOCK ParamBlock = SEG_OFF_TO_PTR(getES(), getBX());
1348 WORD ErrorCode;
1349
1350 if (OrgAL <= DOS_LOAD_OVERLAY)
1351 {
1352 DOS_EXEC_TYPE LoadType = (DOS_EXEC_TYPE)OrgAL;
1353
1354 #ifndef STANDALONE
1355 if (LoadType == DOS_LOAD_AND_EXECUTE)
1356 {
1357 /* Create a new process */
1358 ErrorCode = DosCreateProcess(ProgramName,
1359 ParamBlock,
1360 MAKELONG(Stack[STACK_IP], Stack[STACK_CS]));
1361 }
1362 else
1363 #endif
1364 {
1365 /* Just load an executable */
1366 ErrorCode = DosLoadExecutable(LoadType,
1367 ProgramName,
1368 ParamBlock,
1369 NULL,
1370 NULL,
1371 MAKELONG(Stack[STACK_IP], Stack[STACK_CS]));
1372 }
1373 }
1374 else if (OrgAL == 0x05)
1375 {
1376 // http://www.ctyme.com/intr/rb-2942.htm
1377 DPRINT1("Set execution state is UNIMPLEMENTED\n");
1378 ErrorCode = ERROR_CALL_NOT_IMPLEMENTED;
1379 }
1380 else
1381 {
1382 ErrorCode = ERROR_INVALID_FUNCTION;
1383 }
1384
1385 if (ErrorCode == ERROR_SUCCESS)
1386 {
1387 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1388 }
1389 else
1390 {
1391 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1392 setAX(ErrorCode);
1393 }
1394
1395 break;
1396 }
1397
1398 /* Terminate with Return Code */
1399 case 0x4C:
1400 {
1401 DosTerminateProcess(Sda->CurrentPsp, getAL(), 0);
1402 break;
1403 }
1404
1405 /* Get Return Code (ERRORLEVEL) */
1406 case 0x4D:
1407 {
1408 /*
1409 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
1410 * DosErrorLevel is cleared after being read by this function.
1411 */
1412 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1413 setAX(Sda->ErrorLevel);
1414 Sda->ErrorLevel = 0x0000; // Clear it
1415 break;
1416 }
1417
1418 /* Find First File */
1419 case 0x4E:
1420 {
1421 WORD Result = (WORD)demFileFindFirst(FAR_POINTER(Sda->DiskTransferArea),
1422 SEG_OFF_TO_PTR(getDS(), getDX()),
1423 getCX());
1424
1425 setAX(Result);
1426
1427 if (Result == ERROR_SUCCESS)
1428 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1429 else
1430 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1431
1432 break;
1433 }
1434
1435 /* Find Next File */
1436 case 0x4F:
1437 {
1438 WORD Result = (WORD)demFileFindNext(FAR_POINTER(Sda->DiskTransferArea));
1439
1440 setAX(Result);
1441
1442 if (Result == ERROR_SUCCESS)
1443 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1444 else
1445 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1446
1447 break;
1448 }
1449
1450 /* Internal - Set Current Process ID (Set PSP Address) */
1451 case 0x50:
1452 {
1453 DosSetProcessContext(getBX());
1454 break;
1455 }
1456
1457 /* Internal - Get Current Process ID (Get PSP Address) */
1458 case 0x51:
1459 /* Get Current PSP Address */
1460 case 0x62:
1461 {
1462 /*
1463 * Undocumented AH=51h is identical to the documented AH=62h.
1464 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
1465 * and http://www.ctyme.com/intr/rb-3140.htm
1466 * for more information.
1467 */
1468 setBX(Sda->CurrentPsp);
1469 break;
1470 }
1471
1472 /* Internal - Get "List of lists" (SYSVARS) */
1473 case 0x52:
1474 {
1475 /*
1476 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
1477 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
1478 * for more information.
1479 */
1480
1481 /* Return the DOS "list of lists" in ES:BX */
1482 setES(DOS_DATA_SEGMENT);
1483 setBX(DOS_DATA_OFFSET(SysVars.FirstDpb));
1484 break;
1485 }
1486
1487 /* Create Child PSP */
1488 case 0x55:
1489 {
1490 DosCreatePsp(getDX(), getSI());
1491 DosSetProcessContext(getDX());
1492 break;
1493 }
1494
1495 /* Rename File */
1496 case 0x56:
1497 {
1498 LPSTR ExistingFileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
1499 LPSTR NewFileName = (LPSTR)SEG_OFF_TO_PTR(getES(), getDI());
1500
1501 /*
1502 * See Ralf Brown: http://www.ctyme.com/intr/rb-2990.htm
1503 * for more information.
1504 */
1505
1506 if (MoveFileA(ExistingFileName, NewFileName))
1507 {
1508 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1509 }
1510 else
1511 {
1512 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1513 setAX(GetLastError());
1514 }
1515
1516 break;
1517 }
1518
1519 /* Get/Set Memory Management Options */
1520 case 0x58:
1521 {
1522 if (getAL() == 0x00)
1523 {
1524 /* Get allocation strategy */
1525 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1526 setAX(Sda->AllocStrategy);
1527 }
1528 else if (getAL() == 0x01)
1529 {
1530 /* Set allocation strategy */
1531
1532 if ((getBL() & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
1533 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
1534 {
1535 /* Can't set both */
1536 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1537 setAX(ERROR_INVALID_PARAMETER);
1538 break;
1539 }
1540
1541 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT)
1542 {
1543 /* Invalid allocation strategy */
1544 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1545 setAX(ERROR_INVALID_PARAMETER);
1546 break;
1547 }
1548
1549 Sda->AllocStrategy = getBL();
1550 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1551 }
1552 else if (getAL() == 0x02)
1553 {
1554 /* Get UMB link state */
1555 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1556 setAL(DosUmbLinked ? 0x01 : 0x00);
1557 }
1558 else if (getAL() == 0x03)
1559 {
1560 /* Set UMB link state */
1561 if (getBX()) DosLinkUmb();
1562 else DosUnlinkUmb();
1563 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1564 }
1565 else
1566 {
1567 /* Invalid or unsupported function */
1568 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1569 setAX(ERROR_INVALID_FUNCTION);
1570 }
1571
1572 break;
1573 }
1574
1575 /* Get Extended Error Information */
1576 case 0x59:
1577 {
1578 DPRINT1("INT 21h, AH = 59h, BX = %04Xh - Get Extended Error Information is UNIMPLEMENTED\n",
1579 getBX());
1580 break;
1581 }
1582
1583 /* Create Temporary File */
1584 case 0x5A:
1585 {
1586 LPSTR PathName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
1587 LPSTR FileName = PathName; // The buffer for the path and the full file name is the same.
1588 UINT uRetVal;
1589 WORD FileHandle;
1590 WORD ErrorCode;
1591
1592 /*
1593 * See Ralf Brown: http://www.ctyme.com/intr/rb-3014.htm
1594 * for more information.
1595 */
1596
1597 // FIXME: Check for buffer validity?
1598 // It should be a ASCIIZ path ending with a '\' + 13 zero bytes
1599 // to receive the generated filename.
1600
1601 /* First create the temporary file */
1602 uRetVal = GetTempFileNameA(PathName, NULL, 0, FileName);
1603 if (uRetVal == 0)
1604 {
1605 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1606 setAX(GetLastError());
1607 break;
1608 }
1609
1610 /* Now try to open it in read/write access */
1611 ErrorCode = DosOpenFile(&FileHandle, FileName, 2);
1612 if (ErrorCode == ERROR_SUCCESS)
1613 {
1614 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1615 setAX(FileHandle);
1616 }
1617 else
1618 {
1619 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1620 setAX(ErrorCode);
1621 }
1622
1623 break;
1624 }
1625
1626 /* Create New File */
1627 case 0x5B:
1628 {
1629 WORD FileHandle;
1630 WORD ErrorCode = DosCreateFile(&FileHandle,
1631 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
1632 CREATE_NEW,
1633 getCX());
1634
1635 if (ErrorCode == ERROR_SUCCESS)
1636 {
1637 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1638 setAX(FileHandle);
1639 }
1640 else
1641 {
1642 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1643 setAX(ErrorCode);
1644 }
1645
1646 break;
1647 }
1648
1649 /* Lock/Unlock Region of File */
1650 case 0x5C:
1651 {
1652 if (getAL() == 0x00)
1653 {
1654 /* Lock region of file */
1655 if (DosLockFile(getBX(), MAKELONG(getDX(), getCX()), MAKELONG(getDI(), getSI())))
1656 {
1657 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1658 }
1659 else
1660 {
1661 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1662 setAX(Sda->LastErrorCode);
1663 }
1664 }
1665 else if (getAL() == 0x01)
1666 {
1667 /* Unlock region of file */
1668 if (DosUnlockFile(getBX(), MAKELONG(getDX(), getCX()), MAKELONG(getDI(), getSI())))
1669 {
1670 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1671 }
1672 else
1673 {
1674 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1675 setAX(Sda->LastErrorCode);
1676 }
1677 }
1678 else
1679 {
1680 /* Invalid subfunction */
1681 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1682 setAX(ERROR_INVALID_FUNCTION);
1683 }
1684
1685 break;
1686 }
1687
1688 /* Canonicalize File Name or Path */
1689 case 0x60:
1690 {
1691 /*
1692 * See Ralf Brown: http://www.ctyme.com/intr/rb-3137.htm
1693 * for more information.
1694 */
1695
1696 /*
1697 * We suppose that the DOS app gave to us a valid
1698 * 128-byte long buffer for the canonicalized name.
1699 */
1700 DWORD dwRetVal = GetFullPathNameA(SEG_OFF_TO_PTR(getDS(), getSI()),
1701 128,
1702 SEG_OFF_TO_PTR(getES(), getDI()),
1703 NULL);
1704 if (dwRetVal == 0)
1705 {
1706 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1707 setAX(GetLastError());
1708 }
1709 else
1710 {
1711 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1712 setAX(0x0000);
1713 }
1714
1715 // FIXME: Convert the full path name into short version.
1716 // We cannot reliably use GetShortPathName, because it fails
1717 // if the path name given doesn't exist. However this DOS
1718 // function AH=60h should be able to work even for non-existing
1719 // path and file names.
1720
1721 break;
1722 }
1723
1724 /* Miscellaneous Internal Functions */
1725 case 0x5D:
1726 {
1727 switch (getAL())
1728 {
1729 /* Get Swappable Data Area */
1730 case 0x06:
1731 {
1732 setDS(DOS_DATA_SEGMENT);
1733 setSI(DOS_DATA_OFFSET(Sda.ErrorMode));
1734 setCX(sizeof(DOS_SDA));
1735 setDX(FIELD_OFFSET(DOS_SDA, LastAX));
1736
1737 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1738 break;
1739 }
1740
1741 default:
1742 {
1743 DPRINT1("INT 21h, AH = 5Dh, subfunction AL = %Xh NOT IMPLEMENTED\n",
1744 getAL());
1745 }
1746 }
1747
1748 break;
1749 }
1750
1751 /* Extended Country Information */
1752 case 0x65:
1753 {
1754 switch (getAL())
1755 {
1756 case 0x01: case 0x02: case 0x03:
1757 case 0x04: case 0x05: case 0x06:
1758 case 0x07:
1759 {
1760 WORD BufferSize = getCX();
1761 WORD ErrorCode;
1762 ErrorCode = DosGetCountryInfoEx(getAL(),
1763 getBX(),
1764 getDX(),
1765 (PDOS_COUNTRY_INFO_2)SEG_OFF_TO_PTR(getES(), getDI()),
1766 &BufferSize);
1767 if (ErrorCode == ERROR_SUCCESS)
1768 {
1769 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1770 setCX(BufferSize);
1771 }
1772 else
1773 {
1774 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1775 setAX(ErrorCode);
1776 }
1777
1778 break;
1779 }
1780
1781 /* Country-dependent Character Capitalization -- Character */
1782 case 0x20:
1783 /* Country-dependent Filename Capitalization -- Character */
1784 case 0xA0:
1785 {
1786 setDL(DosToUpper(getDL()));
1787 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1788 // setAX(ERROR_SUCCESS);
1789 break;
1790 }
1791
1792 /* Country-dependent Character Capitalization -- Counted ASCII String */
1793 case 0x21:
1794 /* Country-dependent Filename Capitalization -- Counted ASCII String */
1795 case 0xA1:
1796 {
1797 PCHAR Str = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1798 // FIXME: Check for NULL ptr!!
1799 DosToUpperStrN(Str, Str, getCX());
1800 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1801 // setAX(ERROR_SUCCESS);
1802 break;
1803 }
1804
1805 /* Country-dependent Character Capitalization -- ASCIIZ String */
1806 case 0x22:
1807 /* Country-dependent Filename Capitalization -- ASCIIZ String */
1808 case 0xA2:
1809 {
1810 PSTR Str = (PSTR)SEG_OFF_TO_PTR(getDS(), getDX());
1811 // FIXME: Check for NULL ptr!!
1812 DosToUpperStrZ(Str, Str);
1813 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1814 // setAX(ERROR_SUCCESS);
1815 break;
1816 }
1817
1818 /* Determine if Character represents YES/NO Response */
1819 case 0x23:
1820 {
1821 setAX(DosIfCharYesNo(MAKEWORD(getDL(), getDH())));
1822 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1823 break;
1824 }
1825
1826 default:
1827 {
1828 DPRINT1("INT 21h, AH = 65h, subfunction AL = %Xh NOT IMPLEMENTED\n",
1829 getAL());
1830 }
1831 }
1832
1833 break;
1834 }
1835
1836 /* Set Handle Count */
1837 case 0x67:
1838 {
1839 if (!DosResizeHandleTable(getBX()))
1840 {
1841 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1842 setAX(Sda->LastErrorCode);
1843 }
1844 else Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1845
1846 break;
1847 }
1848
1849 /* Commit File */
1850 case 0x68:
1851 case 0x6A:
1852 {
1853 /*
1854 * Function 6Ah is identical to function 68h,
1855 * and sets AH to 68h if success.
1856 * See Ralf Brown: http://www.ctyme.com/intr/rb-3176.htm
1857 * for more information.
1858 */
1859 setAH(0x68);
1860
1861 if (DosFlushFileBuffers(getBX()))
1862 {
1863 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1864 }
1865 else
1866 {
1867 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1868 setAX(GetLastError());
1869 }
1870
1871 break;
1872 }
1873
1874 /* Extended Open/Create */
1875 case 0x6C:
1876 {
1877 WORD FileHandle;
1878 WORD CreationStatus;
1879 WORD ErrorCode;
1880
1881 /* Check for AL == 00 */
1882 if (getAL() != 0x00)
1883 {
1884 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1885 setAX(ERROR_INVALID_FUNCTION);
1886 break;
1887 }
1888
1889 /*
1890 * See Ralf Brown: http://www.ctyme.com/intr/rb-3179.htm
1891 * for the full detailed description.
1892 *
1893 * WARNING: BH contains some extended flags that are NOT SUPPORTED.
1894 */
1895
1896 ErrorCode = DosCreateFileEx(&FileHandle,
1897 &CreationStatus,
1898 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getSI()),
1899 getBL(),
1900 getDL(),
1901 getCX());
1902
1903 if (ErrorCode == ERROR_SUCCESS)
1904 {
1905 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1906 setCX(CreationStatus);
1907 setAX(FileHandle);
1908 }
1909 else
1910 {
1911 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1912 setAX(ErrorCode);
1913 }
1914
1915 break;
1916 }
1917
1918 /* Unsupported */
1919 default:
1920 {
1921 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
1922 getAH(), getAL());
1923
1924 setAL(0); // Some functions expect AL to be 0 when it's not supported.
1925 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1926 }
1927 }
1928
1929 Sda->InDos--;
1930 }
1931
1932 VOID WINAPI DosBreakInterrupt(LPWORD Stack)
1933 {
1934 /* Set CF to terminate the running process */
1935 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1936 }
1937
1938 VOID WINAPI DosAbsoluteRead(LPWORD Stack)
1939 {
1940 /*
1941 * This call should leave the flags on the stack for some reason,
1942 * so move the stack by one word.
1943 * See: http://www.techhelpmanual.com/565-int_25h_26h__absolute_disk_read_write.html
1944 */
1945 Stack[STACK_INT_NUM] = Stack[STACK_IP];
1946 Stack[STACK_IP] = Stack[STACK_CS];
1947 Stack[STACK_CS] = Stack[STACK_FLAGS];
1948 setSP(LOWORD(getSP() - 2));
1949
1950 // TODO: NOT IMPLEMENTED;
1951 UNIMPLEMENTED;
1952
1953 /* General failure */
1954 setAX(0x800C);
1955 Stack[STACK_FLAGS - 1] |= EMULATOR_FLAG_CF;
1956 }
1957
1958 VOID WINAPI DosAbsoluteWrite(LPWORD Stack)
1959 {
1960 /*
1961 * This call should leave the flags on the stack for some reason,
1962 * so move the stack by one word.
1963 * See: http://www.techhelpmanual.com/565-int_25h_26h__absolute_disk_read_write.html
1964 */
1965 Stack[STACK_INT_NUM] = Stack[STACK_IP];
1966 Stack[STACK_IP] = Stack[STACK_CS];
1967 Stack[STACK_CS] = Stack[STACK_FLAGS];
1968 setSP(LOWORD(getSP() - 2));
1969
1970 // TODO: NOT IMPLEMENTED;
1971 UNIMPLEMENTED;
1972
1973 /* General failure */
1974 setAX(0x800C);
1975 Stack[STACK_FLAGS - 1] |= EMULATOR_FLAG_CF;
1976 }
1977
1978 VOID WINAPI DosInt27h(LPWORD Stack)
1979 {
1980 DosTerminateProcess(getCS(), 0, (getDX() + 0x0F) >> 4);
1981 }
1982
1983 VOID WINAPI DosIdle(LPWORD Stack)
1984 {
1985 /*
1986 * This will set the carry flag on the first call (to repeat the BOP),
1987 * and clear it in the next, so that exactly one HLT occurs.
1988 */
1989 setCF(!getCF());
1990 }
1991
1992 VOID WINAPI DosFastConOut(LPWORD Stack)
1993 {
1994 /*
1995 * This is the DOS 2+ Fast Console Output Interrupt.
1996 * The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
1997 *
1998 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
1999 * for more information.
2000 */
2001
2002 /* Save AX and BX */
2003 USHORT AX = getAX();
2004 USHORT BX = getBX();
2005
2006 /*
2007 * Set the parameters:
2008 * AL contains the character to print (already set),
2009 * BL contains the character attribute,
2010 * BH contains the video page to use.
2011 */
2012 setBL(DOS_CHAR_ATTRIBUTE);
2013 setBH(Bda->VideoPage);
2014
2015 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
2016 setAH(0x0E);
2017 Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
2018
2019 /* Restore AX and BX */
2020 setBX(BX);
2021 setAX(AX);
2022 }
2023
2024 VOID WINAPI DosInt2Fh(LPWORD Stack)
2025 {
2026 switch (getAH())
2027 {
2028 /* Extended Memory Specification */
2029 case 0x43:
2030 {
2031 DWORD DriverEntry;
2032 if (!XmsGetDriverEntry(&DriverEntry)) break;
2033
2034 switch (getAL())
2035 {
2036 /* Installation Check */
2037 case 0x00:
2038 {
2039 /* The driver is loaded */
2040 setAL(0x80);
2041 break;
2042 }
2043
2044 /* Get Driver Address */
2045 case 0x10:
2046 {
2047 setES(HIWORD(DriverEntry));
2048 setBX(LOWORD(DriverEntry));
2049 break;
2050 }
2051
2052 default:
2053 DPRINT1("Unknown DOS XMS Function: INT 0x2F, AH = 43h, AL = %xh\n", getAL());
2054 break;
2055 }
2056
2057 break;
2058 }
2059
2060 default:
2061 {
2062 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2063 getAH(), getAL());
2064 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2065 }
2066 }
2067 }
2068
2069 BOOLEAN DosKRNLInitialize(VOID)
2070 {
2071 #if 1
2072
2073 UCHAR i;
2074 PDOS_SFT Sft;
2075 LPSTR Path;
2076 CHAR CurrentDirectory[MAX_PATH];
2077 CHAR DosDirectory[DOS_DIR_LENGTH];
2078
2079 const BYTE NullDriverRoutine[] = {
2080 /* Strategy routine entry */
2081 0x26, // mov [Request.Status], DOS_DEVSTAT_DONE
2082 0xC7,
2083 0x47,
2084 FIELD_OFFSET(DOS_REQUEST_HEADER, Status),
2085 LOBYTE(DOS_DEVSTAT_DONE),
2086 HIBYTE(DOS_DEVSTAT_DONE),
2087
2088 /* Interrupt routine entry */
2089 0xCB, // retf
2090 };
2091
2092 FILE *Stream;
2093 WCHAR Buffer[256];
2094
2095 /* Initialize the global DOS data area */
2096 DosData = (PDOS_DATA)SEG_OFF_TO_PTR(DOS_DATA_SEGMENT, 0x0000);
2097 RtlZeroMemory(DosData, sizeof(*DosData));
2098
2099 /* Initialize the list of lists */
2100 SysVars = &DosData->SysVars;
2101 RtlZeroMemory(SysVars, sizeof(*SysVars));
2102 SysVars->FirstMcb = FIRST_MCB_SEGMENT;
2103 SysVars->FirstSft = MAKELONG(DOS_DATA_OFFSET(Sft), DOS_DATA_SEGMENT);
2104 SysVars->CurrentDirs = MAKELONG(DOS_DATA_OFFSET(CurrentDirectories),
2105 DOS_DATA_SEGMENT);
2106 /* The last drive can be redefined with the LASTDRIVE command. At the moment, set the real maximum possible, 'Z'. */
2107 SysVars->NumLocalDrives = 'Z' - 'A' + 1;
2108
2109 /* The boot drive is initialized to the %SYSTEMDRIVE% value */
2110 // NOTE: Using the NtSystemRoot system variable might be OS-specific...
2111 SysVars->BootDrive = SharedUserData->NtSystemRoot[0] - 'A' + 1;
2112
2113 /* Initialize the NUL device driver */
2114 SysVars->NullDevice.Link = 0xFFFFFFFF;
2115 SysVars->NullDevice.DeviceAttributes = DOS_DEVATTR_NUL | DOS_DEVATTR_CHARACTER;
2116 SysVars->NullDevice.StrategyRoutine = FIELD_OFFSET(DOS_SYSVARS, NullDriverRoutine);
2117 SysVars->NullDevice.InterruptRoutine = SysVars->NullDevice.StrategyRoutine + 6;
2118 RtlFillMemory(SysVars->NullDevice.DeviceName,
2119 sizeof(SysVars->NullDevice.DeviceName),
2120 ' ');
2121 RtlCopyMemory(SysVars->NullDevice.DeviceName, "NUL", strlen("NUL"));
2122 RtlCopyMemory(SysVars->NullDriverRoutine,
2123 NullDriverRoutine,
2124 sizeof(NullDriverRoutine));
2125
2126 /* Initialize the swappable data area */
2127 Sda = &DosData->Sda;
2128 RtlZeroMemory(Sda, sizeof(*Sda));
2129
2130 /* Get the current directory */
2131 if (!GetCurrentDirectoryA(sizeof(CurrentDirectory), CurrentDirectory))
2132 {
2133 // TODO: Use some kind of default path?
2134 return FALSE;
2135 }
2136
2137 /* Convert it to a DOS path */
2138 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, sizeof(DosDirectory)))
2139 {
2140 // TODO: Use some kind of default path?
2141 return FALSE;
2142 }
2143
2144 /* Set the drive */
2145 Sda->CurrentDrive = RtlUpperChar(DosDirectory[0]) - 'A';
2146
2147 /* Get the directory part of the path */
2148 Path = strchr(DosDirectory, '\\');
2149 if (Path != NULL)
2150 {
2151 /* Skip the backslash */
2152 Path++;
2153 }
2154
2155 /* Set the directory */
2156 if (Path != NULL)
2157 {
2158 strncpy(DosData->CurrentDirectories[Sda->CurrentDrive], Path, DOS_DIR_LENGTH);
2159 }
2160
2161 /* Set the current PSP to the system PSP */
2162 Sda->CurrentPsp = SYSTEM_PSP;
2163
2164 /* Set the initial allocation strategy to "best fit" */
2165 Sda->AllocStrategy = DOS_ALLOC_BEST_FIT;
2166
2167 /* Initialize the SFT */
2168 Sft = (PDOS_SFT)FAR_POINTER(SysVars->FirstSft);
2169 Sft->Link = 0xFFFFFFFF;
2170 Sft->NumDescriptors = DOS_SFT_SIZE;
2171
2172 for (i = 0; i < Sft->NumDescriptors; i++)
2173 {
2174 /* Clear the file descriptor entry */
2175 RtlZeroMemory(&Sft->FileDescriptors[i], sizeof(DOS_FILE_DESCRIPTOR));
2176 }
2177
2178 /* Read CONFIG.SYS */
2179 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
2180 if (Stream != NULL)
2181 {
2182 while (fgetws(Buffer, 256, Stream))
2183 {
2184 // TODO: Parse the line
2185 }
2186 fclose(Stream);
2187 }
2188
2189 #endif
2190
2191 /* Initialize the callback context */
2192 InitializeContext(&DosContext, DOS_CODE_SEGMENT, 0x0000);
2193
2194 /* Register the DOS 32-bit Interrupts */
2195 RegisterDosInt32(0x20, DosInt20h );
2196 RegisterDosInt32(0x21, DosInt21h );
2197 // RegisterDosInt32(0x22, DosInt22h ); // Termination
2198 RegisterDosInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
2199 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
2200 RegisterDosInt32(0x25, DosAbsoluteRead ); // Absolute Disk Read
2201 RegisterDosInt32(0x26, DosAbsoluteWrite ); // Absolute Disk Write
2202 RegisterDosInt32(0x27, DosInt27h ); // Terminate and Stay Resident
2203 RegisterDosInt32(0x28, DosIdle ); // DOS Idle Interrupt
2204 RegisterDosInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
2205 RegisterDosInt32(0x2F, DosInt2Fh ); // Multiplex Interrupt
2206
2207 /* Unimplemented DOS interrupts */
2208 RegisterDosInt32(0x2A, NULL); // Network - Installation Check
2209
2210 /* Initialize country data */
2211 DosCountryInitialize();
2212
2213 /* Load the CON driver */
2214 ConDrvInitialize();
2215
2216 /* Load the XMS driver (HIMEM) */
2217 XmsInitialize();
2218
2219 /* Load the EMS driver */
2220 if (!EmsDrvInitialize(EMS_TOTAL_PAGES))
2221 {
2222 DPRINT1("Could not initialize EMS. EMS will not be available.\n"
2223 "Try reducing the number of EMS pages.\n");
2224 }
2225
2226 return TRUE;
2227 }
2228
2229 /* EOF */