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