[NTVDM] Allow opening files with write access on CDROM
[reactos.git] / 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: 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 */