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