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