5bc10492ef7860139cd8bc3e28e0a19fac4873c2
[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 DosAbsoluteRead(LPWORD Stack)
1872 {
1873 /*
1874 * This call should leave the flags on the stack for some reason,
1875 * so move the stack by one word.
1876 */
1877 Stack[STACK_INT_NUM] = Stack[STACK_IP];
1878 Stack[STACK_IP] = Stack[STACK_CS];
1879 Stack[STACK_CS] = Stack[STACK_FLAGS];
1880 setSP(LOWORD(getSP() - 2));
1881
1882 // TODO: NOT IMPLEMENTED;
1883 UNIMPLEMENTED;
1884
1885 /* General failure */
1886 setAX(0x800C);
1887 Stack[STACK_FLAGS - 1] |= EMULATOR_FLAG_CF;
1888 }
1889
1890 VOID WINAPI DosAbsoluteWrite(LPWORD Stack)
1891 {
1892 /*
1893 * This call should leave the flags on the stack for some reason,
1894 * so move the stack by one word.
1895 */
1896 Stack[STACK_INT_NUM] = Stack[STACK_IP];
1897 Stack[STACK_IP] = Stack[STACK_CS];
1898 Stack[STACK_CS] = Stack[STACK_FLAGS];
1899 setSP(LOWORD(getSP() - 2));
1900
1901 // TODO: NOT IMPLEMENTED;
1902 UNIMPLEMENTED;
1903
1904 /* General failure */
1905 setAX(0x800C);
1906 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1907 }
1908
1909 VOID WINAPI DosInt27h(LPWORD Stack)
1910 {
1911 DosTerminateProcess(getCS(), 0, (getDX() + 0x0F) >> 4);
1912 }
1913
1914 VOID WINAPI DosIdle(LPWORD Stack)
1915 {
1916 /*
1917 * This will set the carry flag on the first call (to repeat the BOP),
1918 * and clear it in the next, so that exactly one HLT occurs.
1919 */
1920 setCF(!getCF());
1921 }
1922
1923 VOID WINAPI DosFastConOut(LPWORD Stack)
1924 {
1925 /*
1926 * This is the DOS 2+ Fast Console Output Interrupt.
1927 * The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
1928 *
1929 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
1930 * for more information.
1931 */
1932
1933 /* Save AX and BX */
1934 USHORT AX = getAX();
1935 USHORT BX = getBX();
1936
1937 /*
1938 * Set the parameters:
1939 * AL contains the character to print (already set),
1940 * BL contains the character attribute,
1941 * BH contains the video page to use.
1942 */
1943 setBL(DOS_CHAR_ATTRIBUTE);
1944 setBH(Bda->VideoPage);
1945
1946 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
1947 setAH(0x0E);
1948 Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
1949
1950 /* Restore AX and BX */
1951 setBX(BX);
1952 setAX(AX);
1953 }
1954
1955 VOID WINAPI DosInt2Fh(LPWORD Stack)
1956 {
1957 switch (getAH())
1958 {
1959 /* Extended Memory Specification */
1960 case 0x43:
1961 {
1962 DWORD DriverEntry;
1963 if (!XmsGetDriverEntry(&DriverEntry)) break;
1964
1965 if (getAL() == 0x00)
1966 {
1967 /* The driver is loaded */
1968 setAL(0x80);
1969 }
1970 else if (getAL() == 0x10)
1971 {
1972 setES(HIWORD(DriverEntry));
1973 setBX(LOWORD(DriverEntry));
1974 }
1975 else
1976 {
1977 DPRINT1("Unknown DOS XMS Function: INT 0x2F, AH = 43h, AL = %xh\n", getAL());
1978 }
1979
1980 break;
1981 }
1982
1983 default:
1984 {
1985 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
1986 getAH(), getAL());
1987 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1988 }
1989 }
1990 }
1991
1992 BOOLEAN DosKRNLInitialize(VOID)
1993 {
1994 #if 1
1995
1996 UCHAR i;
1997 PDOS_SFT Sft;
1998 LPSTR Path;
1999 CHAR CurrentDirectory[MAX_PATH];
2000 CHAR DosDirectory[DOS_DIR_LENGTH];
2001
2002 const BYTE NullDriverRoutine[] = {
2003 /* Strategy routine entry */
2004 0x26, // mov [Request.Status], DOS_DEVSTAT_DONE
2005 0xC7,
2006 0x47,
2007 FIELD_OFFSET(DOS_REQUEST_HEADER, Status),
2008 LOBYTE(DOS_DEVSTAT_DONE),
2009 HIBYTE(DOS_DEVSTAT_DONE),
2010
2011 /* Interrupt routine entry */
2012 0xCB, // retf
2013 };
2014
2015 FILE *Stream;
2016 WCHAR Buffer[256];
2017
2018 /* Initialize the global DOS data area */
2019 DosData = (PDOS_DATA)SEG_OFF_TO_PTR(DOS_DATA_SEGMENT, 0x0000);
2020 RtlZeroMemory(DosData, sizeof(*DosData));
2021
2022 /* Initialize the list of lists */
2023 SysVars = &DosData->SysVars;
2024 RtlZeroMemory(SysVars, sizeof(*SysVars));
2025 SysVars->FirstMcb = FIRST_MCB_SEGMENT;
2026 SysVars->FirstSft = MAKELONG(DOS_DATA_OFFSET(Sft), DOS_DATA_SEGMENT);
2027 SysVars->CurrentDirs = MAKELONG(DOS_DATA_OFFSET(CurrentDirectories),
2028 DOS_DATA_SEGMENT);
2029 /* The last drive can be redefined with the LASTDRIVE command. At the moment, set the real maximum possible, 'Z'. */
2030 SysVars->NumLocalDrives = 'Z' - 'A' + 1;
2031
2032 /* Initialize the NUL device driver */
2033 SysVars->NullDevice.Link = 0xFFFFFFFF;
2034 SysVars->NullDevice.DeviceAttributes = DOS_DEVATTR_NUL | DOS_DEVATTR_CHARACTER;
2035 SysVars->NullDevice.StrategyRoutine = FIELD_OFFSET(DOS_SYSVARS, NullDriverRoutine);
2036 SysVars->NullDevice.InterruptRoutine = SysVars->NullDevice.StrategyRoutine + 6;
2037 RtlFillMemory(SysVars->NullDevice.DeviceName,
2038 sizeof(SysVars->NullDevice.DeviceName),
2039 ' ');
2040 RtlCopyMemory(SysVars->NullDevice.DeviceName, "NUL", strlen("NUL"));
2041 RtlCopyMemory(SysVars->NullDriverRoutine,
2042 NullDriverRoutine,
2043 sizeof(NullDriverRoutine));
2044
2045 /* Initialize the swappable data area */
2046 Sda = &DosData->Sda;
2047 RtlZeroMemory(Sda, sizeof(*Sda));
2048
2049 /* Get the current directory */
2050 if (!GetCurrentDirectoryA(sizeof(CurrentDirectory), CurrentDirectory))
2051 {
2052 // TODO: Use some kind of default path?
2053 return FALSE;
2054 }
2055
2056 /* Convert it to a DOS path */
2057 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, sizeof(DosDirectory)))
2058 {
2059 // TODO: Use some kind of default path?
2060 return FALSE;
2061 }
2062
2063 /* Set the drive */
2064 Sda->CurrentDrive = RtlUpperChar(DosDirectory[0]) - 'A';
2065
2066 /* Get the directory part of the path */
2067 Path = strchr(DosDirectory, '\\');
2068 if (Path != NULL)
2069 {
2070 /* Skip the backslash */
2071 Path++;
2072 }
2073
2074 /* Set the directory */
2075 if (Path != NULL)
2076 {
2077 strncpy(DosData->CurrentDirectories[Sda->CurrentDrive], Path, DOS_DIR_LENGTH);
2078 }
2079
2080 /* Set the current PSP to the system PSP */
2081 Sda->CurrentPsp = SYSTEM_PSP;
2082
2083 /* Set the initial allocation strategy to "best fit" */
2084 Sda->AllocStrategy = DOS_ALLOC_BEST_FIT;
2085
2086 /* Initialize the SFT */
2087 Sft = (PDOS_SFT)FAR_POINTER(SysVars->FirstSft);
2088 Sft->Link = 0xFFFFFFFF;
2089 Sft->NumDescriptors = DOS_SFT_SIZE;
2090
2091 for (i = 0; i < Sft->NumDescriptors; i++)
2092 {
2093 /* Clear the file descriptor entry */
2094 RtlZeroMemory(&Sft->FileDescriptors[i], sizeof(DOS_FILE_DESCRIPTOR));
2095 }
2096
2097 /* Read CONFIG.SYS */
2098 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
2099 if (Stream != NULL)
2100 {
2101 while (fgetws(Buffer, 256, Stream))
2102 {
2103 // TODO: Parse the line
2104 }
2105 fclose(Stream);
2106 }
2107
2108 #endif
2109
2110 /* Initialize the callback context */
2111 InitializeContext(&DosContext, DOS_CODE_SEGMENT, 0x0000);
2112
2113 /* Register the DOS 32-bit Interrupts */
2114 RegisterDosInt32(0x20, DosInt20h );
2115 RegisterDosInt32(0x21, DosInt21h );
2116 // RegisterDosInt32(0x22, DosInt22h ); // Termination
2117 RegisterDosInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
2118 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
2119 RegisterDosInt32(0x25, DosAbsoluteRead ); // Absolute Disk Read
2120 RegisterDosInt32(0x26, DosAbsoluteWrite ); // Absolute Disk Write
2121 RegisterDosInt32(0x27, DosInt27h ); // Terminate and Stay Resident
2122 RegisterDosInt32(0x28, DosIdle ); // DOS Idle Interrupt
2123 RegisterDosInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
2124 RegisterDosInt32(0x2F, DosInt2Fh );
2125
2126 /* Unimplemented DOS interrupts */
2127 RegisterDosInt32(0x2A, NULL); // Network - Installation Check
2128
2129 /* Load the CON driver */
2130 ConDrvInitialize();
2131
2132 /* Load the XMS driver (HIMEM) */
2133 XmsInitialize();
2134
2135 /* Load the EMS driver */
2136 if (!EmsDrvInitialize(EMS_TOTAL_PAGES))
2137 {
2138 DPRINT1("Could not initialize EMS. EMS will not be available.\n"
2139 "Try reducing the number of EMS pages.\n");
2140 }
2141
2142 return TRUE;
2143 }
2144
2145 /* EOF */