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