[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 "emulator.h"
15 #include "cpu/cpu.h"
16 #include "int32.h"
17
18 #include "dos.h"
19 #include "dos/dem.h"
20 #include "device.h"
21 #include "memory.h"
22
23 #include "bios/bios.h"
24
25 #include "io.h"
26 #include "hardware/ps2.h"
27
28 /* PRIVATE VARIABLES **********************************************************/
29
30 CALLBACK16 DosContext;
31
32 static DWORD DiskTransferArea;
33 /*static*/ BYTE CurrentDrive;
34 static CHAR LastDrive = 'E';
35 static CHAR CurrentDirectories[NUM_DRIVES][DOS_DIR_LENGTH];
36 static DOS_SFT_ENTRY DosSystemFileTable[DOS_SFT_SIZE];
37 static WORD DosErrorLevel = 0x0000;
38
39 /* PUBLIC VARIABLES ***********************************************************/
40
41 /* Echo state for INT 21h, AH = 01h and AH = 3Fh */
42 BOOLEAN DoEcho = FALSE;
43 WORD CurrentPsp = SYSTEM_PSP;
44 WORD DosLastError = 0;
45
46 /* PRIVATE FUNCTIONS **********************************************************/
47
48 static WORD DosCopyEnvironmentBlock(LPCSTR Environment OPTIONAL,
49 LPCSTR ProgramName)
50 {
51 PCHAR Ptr, DestBuffer = NULL;
52 ULONG TotalSize = 0;
53 WORD DestSegment;
54
55 /* If we have an environment strings list, compute its size */
56 if (Environment)
57 {
58 /* Calculate the size of the environment block */
59 Ptr = (PCHAR)Environment;
60 while (*Ptr) Ptr += strlen(Ptr) + 1;
61 TotalSize = (ULONG_PTR)Ptr - (ULONG_PTR)Environment;
62 }
63 else
64 {
65 /* Empty environment string */
66 TotalSize = 1;
67 }
68 /* Add the final environment block NULL-terminator */
69 TotalSize++;
70
71 /* Add the two bytes for the program name tag */
72 TotalSize += 2;
73
74 /* Add the string buffer size */
75 TotalSize += strlen(ProgramName) + 1;
76
77 /* Allocate the memory for the environment block */
78 DestSegment = DosAllocateMemory((WORD)((TotalSize + 0x0F) >> 4), NULL);
79 if (!DestSegment) return 0;
80
81 DestBuffer = (PCHAR)SEG_OFF_TO_PTR(DestSegment, 0);
82
83 /* If we have an environment strings list, copy it */
84 if (Environment)
85 {
86 Ptr = (PCHAR)Environment;
87 while (*Ptr)
88 {
89 /* Copy the string and NULL-terminate it */
90 strcpy(DestBuffer, Ptr);
91 DestBuffer += strlen(Ptr);
92 *(DestBuffer++) = '\0';
93
94 /* Move to the next string */
95 Ptr += strlen(Ptr) + 1;
96 }
97 }
98 else
99 {
100 /* Empty environment string */
101 *(DestBuffer++) = '\0';
102 }
103 /* NULL-terminate the environment block */
104 *(DestBuffer++) = '\0';
105
106 /* Store the special program name tag */
107 *(DestBuffer++) = LOBYTE(DOS_PROGRAM_NAME_TAG);
108 *(DestBuffer++) = HIBYTE(DOS_PROGRAM_NAME_TAG);
109
110 /* Copy the program name after the environment block */
111 strcpy(DestBuffer, ProgramName);
112
113 return DestSegment;
114 }
115
116 /* Taken from base/shell/cmd/console.c */
117 static BOOL IsConsoleHandle(HANDLE hHandle)
118 {
119 DWORD dwMode;
120
121 /* Check whether the handle may be that of a console... */
122 if ((GetFileType(hHandle) & FILE_TYPE_CHAR) == 0) return FALSE;
123
124 /*
125 * It may be. Perform another test... The idea comes from the
126 * MSDN description of the WriteConsole API:
127 *
128 * "WriteConsole fails if it is used with a standard handle
129 * that is redirected to a file. If an application processes
130 * multilingual output that can be redirected, determine whether
131 * the output handle is a console handle (one method is to call
132 * the GetConsoleMode function and check whether it succeeds).
133 * If the handle is a console handle, call WriteConsole. If the
134 * handle is not a console handle, the output is redirected and
135 * you should call WriteFile to perform the I/O."
136 */
137 return GetConsoleMode(hHandle, &dwMode);
138 }
139
140 static inline PDOS_SFT_ENTRY DosFindFreeSftEntry(VOID)
141 {
142 UINT i;
143
144 for (i = 0; i < DOS_SFT_SIZE; i++)
145 {
146 if (DosSystemFileTable[i].Type == DOS_SFT_ENTRY_NONE)
147 {
148 return &DosSystemFileTable[i];
149 }
150 }
151
152 return NULL;
153 }
154
155 static inline PDOS_SFT_ENTRY DosFindWin32SftEntry(HANDLE Handle)
156 {
157 UINT i;
158
159 for (i = 0; i < DOS_SFT_SIZE; i++)
160 {
161 if (DosSystemFileTable[i].Type == DOS_SFT_ENTRY_WIN32
162 && DosSystemFileTable[i].Handle == Handle)
163 {
164 return &DosSystemFileTable[i];
165 }
166 }
167
168 return NULL;
169 }
170
171 static inline PDOS_SFT_ENTRY DosFindDeviceSftEntry(PDOS_DEVICE_NODE Device)
172 {
173 UINT i;
174
175 for (i = 0; i < DOS_SFT_SIZE; i++)
176 {
177 if (DosSystemFileTable[i].Type == DOS_SFT_ENTRY_DEVICE
178 && DosSystemFileTable[i].DeviceNode == Device)
179 {
180 return &DosSystemFileTable[i];
181 }
182 }
183
184 return NULL;
185 }
186
187 WORD DosOpenHandle(HANDLE Handle)
188 {
189 WORD DosHandle;
190 PDOS_PSP PspBlock;
191 LPBYTE HandleTable;
192 PDOS_SFT_ENTRY SftEntry;
193
194 /* The system PSP has no handle table */
195 if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
196
197 /* Get a pointer to the handle table */
198 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
199 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
200
201 /* Find a free entry in the JFT */
202 for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
203 {
204 if (HandleTable[DosHandle] == 0xFF) break;
205 }
206
207 /* If there are no free entries, fail */
208 if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
209
210 /* Check if the handle is already in the SFT */
211 SftEntry = DosFindWin32SftEntry(Handle);
212 if (SftEntry != NULL)
213 {
214 /* Already in the table, reference it */
215 SftEntry->RefCount++;
216 goto Finish;
217 }
218
219 /* Find a free SFT entry to use */
220 SftEntry = DosFindFreeSftEntry();
221 if (SftEntry == NULL)
222 {
223 /* The SFT is full */
224 return INVALID_DOS_HANDLE;
225 }
226
227 /* Initialize the empty table entry */
228 SftEntry->Type = DOS_SFT_ENTRY_WIN32;
229 SftEntry->Handle = Handle;
230 SftEntry->RefCount = 1;
231
232 Finish:
233
234 /* Set the JFT entry to that SFT index */
235 HandleTable[DosHandle] = ARRAY_INDEX(SftEntry, DosSystemFileTable);
236
237 /* Return the new handle */
238 return DosHandle;
239 }
240
241 WORD DosOpenDevice(PDOS_DEVICE_NODE Device)
242 {
243 WORD DosHandle;
244 PDOS_PSP PspBlock;
245 LPBYTE HandleTable;
246 PDOS_SFT_ENTRY SftEntry;
247
248 DPRINT("DosOpenDevice(\"%Z\")\n", &Device->Name);
249
250 /* The system PSP has no handle table */
251 if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
252
253 /* Get a pointer to the handle table */
254 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
255 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
256
257 /* Find a free entry in the JFT */
258 for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
259 {
260 if (HandleTable[DosHandle] == 0xFF) break;
261 }
262
263 /* If there are no free entries, fail */
264 if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
265
266 /* Check if the device is already in the SFT */
267 SftEntry = DosFindDeviceSftEntry(Device);
268 if (SftEntry != NULL)
269 {
270 /* Already in the table, reference it */
271 SftEntry->RefCount++;
272 goto Finish;
273 }
274
275 /* Find a free SFT entry to use */
276 SftEntry = DosFindFreeSftEntry();
277 if (SftEntry == NULL)
278 {
279 /* The SFT is full */
280 return INVALID_DOS_HANDLE;
281 }
282
283 /* Initialize the empty table entry */
284 SftEntry->Type = DOS_SFT_ENTRY_DEVICE;
285 SftEntry->DeviceNode = Device;
286 SftEntry->RefCount = 1;
287
288 Finish:
289
290 /* Call the open routine, if it exists */
291 if (Device->OpenRoutine) Device->OpenRoutine(Device);
292
293 /* Set the JFT entry to that SFT index */
294 HandleTable[DosHandle] = ARRAY_INDEX(SftEntry, DosSystemFileTable);
295
296 /* Return the new handle */
297 return DosHandle;
298 }
299
300 static VOID DosCopyHandleTable(LPBYTE DestinationTable)
301 {
302 UINT i;
303 PDOS_PSP PspBlock;
304 LPBYTE SourceTable;
305
306 /* Clear the table first */
307 for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF;
308
309 /* Check if this is the initial process */
310 if (CurrentPsp == SYSTEM_PSP)
311 {
312 PDOS_SFT_ENTRY SftEntry;
313 HANDLE StandardHandles[3];
314 PDOS_DEVICE_NODE Con = DosGetDevice("CON");
315 ASSERT(Con != NULL);
316
317 /* Get the native standard handles */
318 StandardHandles[0] = GetStdHandle(STD_INPUT_HANDLE);
319 StandardHandles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
320 StandardHandles[2] = GetStdHandle(STD_ERROR_HANDLE);
321
322 for (i = 0; i < 3; i++)
323 {
324 /* Find the corresponding SFT entry */
325 if (IsConsoleHandle(StandardHandles[i]))
326 {
327 SftEntry = DosFindDeviceSftEntry(Con);
328 }
329 else
330 {
331 SftEntry = DosFindWin32SftEntry(StandardHandles[i]);
332 }
333
334 if (SftEntry == NULL)
335 {
336 /* Create a new SFT entry for it */
337 SftEntry = DosFindFreeSftEntry();
338 if (SftEntry == NULL)
339 {
340 DPRINT1("Cannot create standard handle %d, the SFT is full!\n", i);
341 continue;
342 }
343
344 SftEntry->RefCount = 0;
345
346 if (IsConsoleHandle(StandardHandles[i]))
347 {
348 SftEntry->Type = DOS_SFT_ENTRY_DEVICE;
349 SftEntry->DeviceNode = Con;
350
351 /* Call the open routine */
352 if (Con->OpenRoutine) Con->OpenRoutine(Con);
353 }
354 else
355 {
356 SftEntry->Type = DOS_SFT_ENTRY_WIN32;
357 SftEntry->Handle = StandardHandles[i];
358 }
359 }
360
361 SftEntry->RefCount++;
362 DestinationTable[i] = ARRAY_INDEX(SftEntry, DosSystemFileTable);
363 }
364 }
365 else
366 {
367 /* Get the parent PSP block and handle table */
368 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
369 SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
370
371 /* Copy the first 20 handles into the new table */
372 for (i = 0; i < DEFAULT_JFT_SIZE; i++)
373 {
374 DestinationTable[i] = SourceTable[i];
375
376 /* Increase the reference count */
377 DosSystemFileTable[SourceTable[i]].RefCount++;
378 }
379 }
380 }
381
382 static BOOLEAN DosResizeHandleTable(WORD NewSize)
383 {
384 PDOS_PSP PspBlock;
385 LPBYTE HandleTable;
386 WORD Segment;
387
388 /* Get the PSP block */
389 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
390
391 if (NewSize == PspBlock->HandleTableSize)
392 {
393 /* No change */
394 return TRUE;
395 }
396
397 if (PspBlock->HandleTableSize > DEFAULT_JFT_SIZE)
398 {
399 /* Get the segment of the current table */
400 Segment = (LOWORD(PspBlock->HandleTablePtr) >> 4) + HIWORD(PspBlock->HandleTablePtr);
401
402 if (NewSize <= DEFAULT_JFT_SIZE)
403 {
404 /* Get the current handle table */
405 HandleTable = FAR_POINTER(PspBlock->HandleTablePtr);
406
407 /* Copy it to the PSP */
408 RtlCopyMemory(PspBlock->HandleTable, HandleTable, NewSize);
409
410 /* Free the memory */
411 DosFreeMemory(Segment);
412
413 /* Update the handle table pointer and size */
414 PspBlock->HandleTableSize = NewSize;
415 PspBlock->HandleTablePtr = MAKELONG(0x18, CurrentPsp);
416 }
417 else
418 {
419 /* Resize the memory */
420 if (!DosResizeMemory(Segment, NewSize, NULL))
421 {
422 /* Unable to resize, try allocating it somewhere else */
423 Segment = DosAllocateMemory(NewSize, NULL);
424 if (Segment == 0) return FALSE;
425
426 /* Get the new handle table */
427 HandleTable = SEG_OFF_TO_PTR(Segment, 0);
428
429 /* Copy the handles to the new table */
430 RtlCopyMemory(HandleTable,
431 FAR_POINTER(PspBlock->HandleTablePtr),
432 PspBlock->HandleTableSize);
433
434 /* Update the handle table pointer */
435 PspBlock->HandleTablePtr = MAKELONG(0, Segment);
436 }
437
438 /* Update the handle table size */
439 PspBlock->HandleTableSize = NewSize;
440 }
441 }
442 else if (NewSize > DEFAULT_JFT_SIZE)
443 {
444 Segment = DosAllocateMemory(NewSize, NULL);
445 if (Segment == 0) return FALSE;
446
447 /* Get the new handle table */
448 HandleTable = SEG_OFF_TO_PTR(Segment, 0);
449
450 /* Copy the handles from the PSP to the new table */
451 RtlCopyMemory(HandleTable,
452 FAR_POINTER(PspBlock->HandleTablePtr),
453 PspBlock->HandleTableSize);
454
455 /* Update the handle table pointer and size */
456 PspBlock->HandleTableSize = NewSize;
457 PspBlock->HandleTablePtr = MAKELONG(0, Segment);
458 }
459
460 return TRUE;
461 }
462
463 static BOOLEAN DosCloseHandle(WORD DosHandle)
464 {
465 PDOS_PSP PspBlock;
466 LPBYTE HandleTable;
467 PDOS_SFT_ENTRY SftEntry;
468
469 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle);
470
471 /* The system PSP has no handle table */
472 if (CurrentPsp == SYSTEM_PSP) return FALSE;
473
474 /* Get a pointer to the handle table */
475 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
476 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
477
478 /* Make sure the handle is open */
479 if (HandleTable[DosHandle] == 0xFF) return FALSE;
480
481 /* Make sure the SFT entry is valid */
482 SftEntry = &DosSystemFileTable[HandleTable[DosHandle]];
483 if (SftEntry->Type == DOS_SFT_ENTRY_NONE) return FALSE;
484
485 /* Decrement the reference count of the SFT entry */
486 SftEntry->RefCount--;
487
488 /* Check if the reference count fell to zero */
489 if (!SftEntry->RefCount)
490 {
491 switch (SftEntry->Type)
492 {
493 case DOS_SFT_ENTRY_WIN32:
494 {
495 /* Close the win32 handle and clear it */
496 CloseHandle(SftEntry->Handle);
497
498 break;
499 }
500
501 case DOS_SFT_ENTRY_DEVICE:
502 {
503 PDOS_DEVICE_NODE Node = SftEntry->DeviceNode;
504
505 /* Call the close routine, if it exists */
506 if (Node->CloseRoutine) SftEntry->DeviceNode->CloseRoutine(SftEntry->DeviceNode);
507
508 break;
509 }
510
511 default:
512 {
513 /* Shouldn't happen */
514 ASSERT(FALSE);
515 }
516 }
517
518 /* Invalidate the SFT entry */
519 SftEntry->Type = DOS_SFT_ENTRY_NONE;
520 }
521
522 /* Clear the entry in the JFT */
523 HandleTable[DosHandle] = 0xFF;
524
525 return TRUE;
526 }
527
528 static BOOLEAN DosDuplicateHandle(WORD OldHandle, WORD NewHandle)
529 {
530 BYTE SftIndex;
531 PDOS_PSP PspBlock;
532 LPBYTE HandleTable;
533
534 DPRINT("DosDuplicateHandle: OldHandle 0x%04X, NewHandle 0x%04X\n",
535 OldHandle,
536 NewHandle);
537
538 /* The system PSP has no handle table */
539 if (CurrentPsp == SYSTEM_PSP) return FALSE;
540
541 /* Get a pointer to the handle table */
542 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
543 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
544
545 /* Make sure the old handle is open */
546 if (HandleTable[OldHandle] == 0xFF) return FALSE;
547
548 /* Check if the new handle is open */
549 if (HandleTable[NewHandle] != 0xFF)
550 {
551 /* Close it */
552 DosCloseHandle(NewHandle);
553 }
554
555 /* Increment the reference count of the SFT entry */
556 SftIndex = HandleTable[OldHandle];
557 DosSystemFileTable[SftIndex].RefCount++;
558
559 /* Make the new handle point to that SFT entry */
560 HandleTable[NewHandle] = SftIndex;
561
562 /* Return success */
563 return TRUE;
564 }
565
566 static BOOLEAN DosChangeDrive(BYTE Drive)
567 {
568 WCHAR DirectoryPath[DOS_CMDLINE_LENGTH];
569
570 /* Make sure the drive exists */
571 if (Drive > (LastDrive - 'A')) return FALSE;
572
573 /* Find the path to the new current directory */
574 swprintf(DirectoryPath, L"%c\\%S", Drive + 'A', CurrentDirectories[Drive]);
575
576 /* Change the current directory of the process */
577 if (!SetCurrentDirectory(DirectoryPath)) return FALSE;
578
579 /* Set the current drive */
580 CurrentDrive = Drive;
581
582 /* Return success */
583 return TRUE;
584 }
585
586 static BOOLEAN DosChangeDirectory(LPSTR Directory)
587 {
588 BYTE DriveNumber;
589 DWORD Attributes;
590 LPSTR Path;
591
592 /* Make sure the directory path is not too long */
593 if (strlen(Directory) >= DOS_DIR_LENGTH)
594 {
595 DosLastError = ERROR_PATH_NOT_FOUND;
596 return FALSE;
597 }
598
599 /* Get the drive number */
600 DriveNumber = Directory[0] - 'A';
601
602 /* Make sure the drive exists */
603 if (DriveNumber > (LastDrive - 'A'))
604 {
605 DosLastError = ERROR_PATH_NOT_FOUND;
606 return FALSE;
607 }
608
609 /* Get the file attributes */
610 Attributes = GetFileAttributesA(Directory);
611
612 /* Make sure the path exists and is a directory */
613 if ((Attributes == INVALID_FILE_ATTRIBUTES)
614 || !(Attributes & FILE_ATTRIBUTE_DIRECTORY))
615 {
616 DosLastError = ERROR_PATH_NOT_FOUND;
617 return FALSE;
618 }
619
620 /* Check if this is the current drive */
621 if (DriveNumber == CurrentDrive)
622 {
623 /* Change the directory */
624 if (!SetCurrentDirectoryA(Directory))
625 {
626 DosLastError = LOWORD(GetLastError());
627 return FALSE;
628 }
629 }
630
631 /* Get the directory part of the path */
632 Path = strchr(Directory, '\\');
633 if (Path != NULL)
634 {
635 /* Skip the backslash */
636 Path++;
637 }
638
639 /* Set the directory for the drive */
640 if (Path != NULL)
641 {
642 strncpy(CurrentDirectories[DriveNumber], Path, DOS_DIR_LENGTH);
643 }
644 else
645 {
646 CurrentDirectories[DriveNumber][0] = '\0';
647 }
648
649 /* Return success */
650 return TRUE;
651 }
652
653 static BOOLEAN DosControlBreak(VOID)
654 {
655 setCF(0);
656
657 /* Call interrupt 0x23 */
658 Int32Call(&DosContext, 0x23);
659
660 if (getCF())
661 {
662 DosTerminateProcess(CurrentPsp, 0, 0);
663 return TRUE;
664 }
665
666 return FALSE;
667 }
668
669 /* PUBLIC FUNCTIONS ***********************************************************/
670
671 PDOS_SFT_ENTRY DosGetSftEntry(WORD DosHandle)
672 {
673 PDOS_PSP PspBlock;
674 LPBYTE HandleTable;
675
676 /* The system PSP has no handle table */
677 if (CurrentPsp == SYSTEM_PSP) return NULL;
678
679 /* Get a pointer to the handle table */
680 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
681 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
682
683 /* Make sure the handle is open */
684 if (HandleTable[DosHandle] == 0xFF) return NULL;
685
686 /* Return a pointer to the SFT entry */
687 return &DosSystemFileTable[HandleTable[DosHandle]];
688 }
689
690 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
691 {
692 PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
693 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
694
695 RtlZeroMemory(PspBlock, sizeof(*PspBlock));
696
697 /* Set the exit interrupt */
698 PspBlock->Exit[0] = 0xCD; // int 0x20
699 PspBlock->Exit[1] = 0x20;
700
701 /* Set the number of the last paragraph */
702 PspBlock->LastParagraph = PspSegment + ProgramSize - 1;
703
704 /* Save the interrupt vectors */
705 PspBlock->TerminateAddress = IntVecTable[0x22];
706 PspBlock->BreakAddress = IntVecTable[0x23];
707 PspBlock->CriticalAddress = IntVecTable[0x24];
708
709 /* Set the parent PSP */
710 PspBlock->ParentPsp = CurrentPsp;
711
712 /* Copy the parent handle table */
713 DosCopyHandleTable(PspBlock->HandleTable);
714
715 /* Set the environment block */
716 PspBlock->EnvBlock = Environment;
717
718 /* Set the handle table pointers to the internal handle table */
719 PspBlock->HandleTableSize = 20;
720 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
721
722 /* Set the DOS version */
723 PspBlock->DosVersion = DOS_VERSION;
724
725 /* Set the far call opcodes */
726 PspBlock->FarCall[0] = 0xCD; // int 0x21
727 PspBlock->FarCall[1] = 0x21;
728 PspBlock->FarCall[2] = 0xCB; // retf
729
730 /* Set the command line */
731 PspBlock->CommandLineSize = (BYTE)min(strlen(CommandLine), DOS_CMDLINE_LENGTH - 1);
732 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
733 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
734 }
735
736 DWORD DosLoadExecutable(IN DOS_EXEC_TYPE LoadType,
737 IN LPCSTR ExecutablePath,
738 IN LPCSTR CommandLine,
739 IN LPCSTR Environment OPTIONAL,
740 OUT PDWORD StackLocation OPTIONAL,
741 OUT PDWORD EntryPoint OPTIONAL)
742 {
743 DWORD Result = ERROR_SUCCESS;
744 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
745 LPBYTE Address = NULL;
746 WORD Segment = 0;
747 WORD EnvBlock = 0;
748 WORD MaxAllocSize;
749 DWORD i, FileSize, ExeSize;
750 PIMAGE_DOS_HEADER Header;
751 PDWORD RelocationTable;
752 PWORD RelocWord;
753 LPSTR CmdLinePtr = (LPSTR)CommandLine;
754
755 DPRINT1("DosLoadExecutable(%d, %s, %s, %s, 0x%08X, 0x%08X)\n",
756 LoadType,
757 ExecutablePath,
758 CommandLine,
759 Environment ? Environment : "n/a",
760 StackLocation,
761 EntryPoint);
762
763 if (LoadType == DOS_LOAD_OVERLAY)
764 {
765 DPRINT1("Overlay loading is not supported yet.\n");
766 return ERROR_NOT_SUPPORTED;
767 }
768
769 /* NULL-terminate the command line by removing the return carriage character */
770 while (*CmdLinePtr && *CmdLinePtr != '\r') CmdLinePtr++;
771 *CmdLinePtr = '\0';
772
773 /* Open a handle to the executable */
774 FileHandle = CreateFileA(ExecutablePath,
775 GENERIC_READ,
776 FILE_SHARE_READ,
777 NULL,
778 OPEN_EXISTING,
779 FILE_ATTRIBUTE_NORMAL,
780 NULL);
781 if (FileHandle == INVALID_HANDLE_VALUE)
782 {
783 Result = GetLastError();
784 goto Cleanup;
785 }
786
787 /* Get the file size */
788 FileSize = GetFileSize(FileHandle, NULL);
789
790 /* Create a mapping object for the file */
791 FileMapping = CreateFileMapping(FileHandle,
792 NULL,
793 PAGE_READONLY,
794 0,
795 0,
796 NULL);
797 if (FileMapping == NULL)
798 {
799 Result = GetLastError();
800 goto Cleanup;
801 }
802
803 /* Map the file into memory */
804 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
805 if (Address == NULL)
806 {
807 Result = GetLastError();
808 goto Cleanup;
809 }
810
811 /* Copy the environment block to DOS memory */
812 EnvBlock = DosCopyEnvironmentBlock(Environment, ExecutablePath);
813 if (EnvBlock == 0)
814 {
815 Result = ERROR_NOT_ENOUGH_MEMORY;
816 goto Cleanup;
817 }
818
819 /* Check if this is an EXE file or a COM file */
820 if (Address[0] == 'M' && Address[1] == 'Z')
821 {
822 /* EXE file */
823
824 /* Get the MZ header */
825 Header = (PIMAGE_DOS_HEADER)Address;
826
827 /* Get the base size of the file, in paragraphs (rounded up) */
828 ExeSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4;
829
830 /* Add the PSP size, in paragraphs */
831 ExeSize += sizeof(DOS_PSP) >> 4;
832
833 /* Add the maximum size that should be allocated */
834 ExeSize += Header->e_maxalloc;
835
836 /* Make sure it does not pass 0xFFFF */
837 if (ExeSize > 0xFFFF) ExeSize = 0xFFFF;
838
839 /* Try to allocate that much memory */
840 Segment = DosAllocateMemory((WORD)ExeSize, &MaxAllocSize);
841
842 if (Segment == 0)
843 {
844 /* Check if there's at least enough memory for the minimum size */
845 if (MaxAllocSize < (ExeSize - Header->e_maxalloc + Header->e_minalloc))
846 {
847 Result = DosLastError;
848 goto Cleanup;
849 }
850
851 /* Allocate that minimum amount */
852 ExeSize = MaxAllocSize;
853 Segment = DosAllocateMemory((WORD)ExeSize, NULL);
854 ASSERT(Segment != 0);
855 }
856
857 /* Initialize the PSP */
858 DosInitializePsp(Segment,
859 CommandLine,
860 (WORD)ExeSize,
861 EnvBlock);
862
863 /* The process owns its own memory */
864 DosChangeMemoryOwner(Segment, Segment);
865 DosChangeMemoryOwner(EnvBlock, Segment);
866
867 /* Copy the program to Segment:0100 */
868 RtlCopyMemory(SEG_OFF_TO_PTR(Segment, 0x100),
869 Address + (Header->e_cparhdr << 4),
870 min(FileSize - (Header->e_cparhdr << 4),
871 (ExeSize << 4) - sizeof(DOS_PSP)));
872
873 /* Get the relocation table */
874 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
875
876 /* Perform relocations */
877 for (i = 0; i < Header->e_crlc; i++)
878 {
879 /* Get a pointer to the word that needs to be patched */
880 RelocWord = (PWORD)SEG_OFF_TO_PTR(Segment + HIWORD(RelocationTable[i]),
881 0x100 + LOWORD(RelocationTable[i]));
882
883 /* Add the number of the EXE segment to it */
884 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
885 }
886
887 if (LoadType == DOS_LOAD_AND_EXECUTE)
888 {
889 /* Set the initial segment registers */
890 setDS(Segment);
891 setES(Segment);
892
893 /* Set the stack to the location from the header */
894 setSS(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss);
895 setSP(Header->e_sp);
896
897 /* Execute */
898 CurrentPsp = Segment;
899 DiskTransferArea = MAKELONG(0x80, Segment);
900 CpuExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
901 Header->e_ip);
902 }
903 }
904 else
905 {
906 /* COM file */
907
908 /* Find the maximum amount of memory that can be allocated */
909 DosAllocateMemory(0xFFFF, &MaxAllocSize);
910
911 /* Make sure it's enough for the whole program and the PSP */
912 if (((DWORD)MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP)))
913 {
914 Result = ERROR_NOT_ENOUGH_MEMORY;
915 goto Cleanup;
916 }
917
918 /* Allocate all of it */
919 Segment = DosAllocateMemory(MaxAllocSize, NULL);
920 if (Segment == 0)
921 {
922 Result = DosLastError;
923 goto Cleanup;
924 }
925
926 /* The process owns its own memory */
927 DosChangeMemoryOwner(Segment, Segment);
928 DosChangeMemoryOwner(EnvBlock, Segment);
929
930 /* Copy the program to Segment:0100 */
931 RtlCopyMemory(SEG_OFF_TO_PTR(Segment, 0x100),
932 Address,
933 FileSize);
934
935 /* Initialize the PSP */
936 DosInitializePsp(Segment,
937 CommandLine,
938 MaxAllocSize,
939 EnvBlock);
940
941 if (LoadType == DOS_LOAD_AND_EXECUTE)
942 {
943 /* Set the initial segment registers */
944 setDS(Segment);
945 setES(Segment);
946
947 /* Set the stack to the last word of the segment */
948 setSS(Segment);
949 setSP(0xFFFE);
950
951 /*
952 * Set the value on the stack to 0, so that a near return
953 * jumps to PSP:0000 which has the exit code.
954 */
955 *((LPWORD)SEG_OFF_TO_PTR(Segment, 0xFFFE)) = 0;
956
957 /* Execute */
958 CurrentPsp = Segment;
959 DiskTransferArea = MAKELONG(0x80, Segment);
960 CpuExecute(Segment, 0x100);
961 }
962 }
963
964 Cleanup:
965 if (Result != ERROR_SUCCESS)
966 {
967 /* It was not successful, cleanup the DOS memory */
968 if (EnvBlock) DosFreeMemory(EnvBlock);
969 if (Segment) DosFreeMemory(Segment);
970 }
971
972 /* Unmap the file*/
973 if (Address != NULL) UnmapViewOfFile(Address);
974
975 /* Close the file mapping object */
976 if (FileMapping != NULL) CloseHandle(FileMapping);
977
978 /* Close the file handle */
979 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
980
981 return Result;
982 }
983
984 DWORD DosStartProcess(IN LPCSTR ExecutablePath,
985 IN LPCSTR CommandLine,
986 IN LPCSTR Environment OPTIONAL)
987 {
988 DWORD Result;
989
990 Result = DosLoadExecutable(DOS_LOAD_AND_EXECUTE,
991 ExecutablePath,
992 CommandLine,
993 Environment,
994 NULL,
995 NULL);
996
997 if (Result != ERROR_SUCCESS) goto Quit;
998
999 /* Attach to the console */
1000 ConsoleAttach();
1001 VidBiosAttachToConsole();
1002
1003 // HACK: Simulate a ENTER key release scancode on the PS/2 port because
1004 // some apps expect to read a key release scancode (> 0x80) when they
1005 // are started.
1006 IOWriteB(PS2_CONTROL_PORT, 0xD2); // Next write is for the first PS/2 port
1007 IOWriteB(PS2_DATA_PORT, 0x80 | 0x1C); // ENTER key release
1008
1009 /* Start simulation */
1010 SetEvent(VdmTaskEvent);
1011 CpuSimulate();
1012
1013 /* Detach from the console */
1014 VidBiosDetachFromConsole();
1015 ConsoleDetach();
1016
1017 Quit:
1018 return Result;
1019 }
1020
1021 #ifndef STANDALONE
1022 WORD DosCreateProcess(DOS_EXEC_TYPE LoadType,
1023 LPCSTR ProgramName,
1024 PDOS_EXEC_PARAM_BLOCK Parameters)
1025 {
1026 DWORD Result;
1027 DWORD BinaryType;
1028 LPVOID Environment = NULL;
1029 VDM_COMMAND_INFO CommandInfo;
1030 CHAR CmdLine[MAX_PATH];
1031 CHAR AppName[MAX_PATH];
1032 CHAR PifFile[MAX_PATH];
1033 CHAR Desktop[MAX_PATH];
1034 CHAR Title[MAX_PATH];
1035 CHAR Env[MAX_PATH];
1036 STARTUPINFOA StartupInfo;
1037 PROCESS_INFORMATION ProcessInfo;
1038
1039 /* Get the binary type */
1040 if (!GetBinaryTypeA(ProgramName, &BinaryType)) return GetLastError();
1041
1042 /* Did the caller specify an environment segment? */
1043 if (Parameters->Environment)
1044 {
1045 /* Yes, use it instead of the parent one */
1046 Environment = SEG_OFF_TO_PTR(Parameters->Environment, 0);
1047 }
1048
1049 /* Set up the startup info structure */
1050 RtlZeroMemory(&StartupInfo, sizeof(StartupInfo));
1051 StartupInfo.cb = sizeof(StartupInfo);
1052
1053 /* Create the process */
1054 if (!CreateProcessA(ProgramName,
1055 FAR_POINTER(Parameters->CommandLine),
1056 NULL,
1057 NULL,
1058 FALSE,
1059 0,
1060 Environment,
1061 NULL,
1062 &StartupInfo,
1063 &ProcessInfo))
1064 {
1065 return GetLastError();
1066 }
1067
1068 /* Check the type of the program */
1069 switch (BinaryType)
1070 {
1071 /* These are handled by NTVDM */
1072 case SCS_DOS_BINARY:
1073 case SCS_WOW_BINARY:
1074 {
1075 /* Clear the structure */
1076 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
1077
1078 /* Initialize the structure members */
1079 CommandInfo.TaskId = SessionId;
1080 CommandInfo.VDMState = VDM_FLAG_NESTED_TASK | VDM_FLAG_DONT_WAIT;
1081 CommandInfo.CmdLine = CmdLine;
1082 CommandInfo.CmdLen = sizeof(CmdLine);
1083 CommandInfo.AppName = AppName;
1084 CommandInfo.AppLen = sizeof(AppName);
1085 CommandInfo.PifFile = PifFile;
1086 CommandInfo.PifLen = sizeof(PifFile);
1087 CommandInfo.Desktop = Desktop;
1088 CommandInfo.DesktopLen = sizeof(Desktop);
1089 CommandInfo.Title = Title;
1090 CommandInfo.TitleLen = sizeof(Title);
1091 CommandInfo.Env = Env;
1092 CommandInfo.EnvLen = sizeof(Env);
1093
1094 /* Get the VDM command information */
1095 if (!GetNextVDMCommand(&CommandInfo))
1096 {
1097 /* Shouldn't happen */
1098 ASSERT(FALSE);
1099 }
1100
1101 /* Increment the re-entry count */
1102 CommandInfo.VDMState = VDM_INC_REENTER_COUNT;
1103 GetNextVDMCommand(&CommandInfo);
1104
1105 /* Load the executable */
1106 Result = DosLoadExecutable(LoadType,
1107 AppName,
1108 CmdLine,
1109 Env,
1110 &Parameters->StackLocation,
1111 &Parameters->EntryPoint);
1112 if (Result != ERROR_SUCCESS)
1113 {
1114 DisplayMessage(L"Could not load '%S'. Error: %u", AppName, Result);
1115 // FIXME: Decrement the reenter count. Or, instead, just increment
1116 // the VDM reenter count *only* if this call succeeds...
1117 }
1118
1119 break;
1120 }
1121
1122 /* Not handled by NTVDM */
1123 default:
1124 {
1125 /* Wait for the process to finish executing */
1126 WaitForSingleObject(ProcessInfo.hProcess, INFINITE);
1127 }
1128 }
1129
1130 /* Close the handles */
1131 CloseHandle(ProcessInfo.hProcess);
1132 CloseHandle(ProcessInfo.hThread);
1133
1134 return ERROR_SUCCESS;
1135 }
1136 #endif
1137
1138 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode, WORD KeepResident)
1139 {
1140 WORD i;
1141 WORD McbSegment = FIRST_MCB_SEGMENT;
1142 PDOS_MCB CurrentMcb;
1143 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
1144 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
1145
1146 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X, KeepResident 0x%04X\n",
1147 Psp,
1148 ReturnCode,
1149 KeepResident);
1150
1151 /* Check if this PSP is it's own parent */
1152 if (PspBlock->ParentPsp == Psp) goto Done;
1153
1154 if (KeepResident == 0)
1155 {
1156 for (i = 0; i < PspBlock->HandleTableSize; i++)
1157 {
1158 /* Close the handle */
1159 DosCloseHandle(i);
1160 }
1161 }
1162
1163 /* Free the memory used by the process */
1164 while (TRUE)
1165 {
1166 /* Get a pointer to the MCB */
1167 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
1168
1169 /* Make sure the MCB is valid */
1170 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z') break;
1171
1172 /* Check if this block was allocated by the process */
1173 if (CurrentMcb->OwnerPsp == Psp)
1174 {
1175 if (KeepResident == 0)
1176 {
1177 /* Free this entire block */
1178 DosFreeMemory(McbSegment + 1);
1179 }
1180 else if (KeepResident < CurrentMcb->Size)
1181 {
1182 /* Reduce the size of the block */
1183 DosResizeMemory(McbSegment + 1, KeepResident, NULL);
1184
1185 /* No further paragraphs need to stay resident */
1186 KeepResident = 0;
1187 }
1188 else
1189 {
1190 /* Just reduce the amount of paragraphs we need to keep resident */
1191 KeepResident -= CurrentMcb->Size;
1192 }
1193 }
1194
1195 /* If this was the last block, quit */
1196 if (CurrentMcb->BlockType == 'Z') break;
1197
1198 /* Update the segment and continue */
1199 McbSegment += CurrentMcb->Size + 1;
1200 }
1201
1202 Done:
1203 /* Restore the interrupt vectors */
1204 IntVecTable[0x22] = PspBlock->TerminateAddress;
1205 IntVecTable[0x23] = PspBlock->BreakAddress;
1206 IntVecTable[0x24] = PspBlock->CriticalAddress;
1207
1208 /* Update the current PSP */
1209 if (Psp == CurrentPsp)
1210 {
1211 CurrentPsp = PspBlock->ParentPsp;
1212 if (CurrentPsp == SYSTEM_PSP)
1213 {
1214 ResetEvent(VdmTaskEvent);
1215 CpuUnsimulate();
1216 }
1217 }
1218
1219 #ifndef STANDALONE
1220 // FIXME: This is probably not the best way to do it
1221 /* Check if this was a nested DOS task */
1222 if (CurrentPsp != SYSTEM_PSP)
1223 {
1224 VDM_COMMAND_INFO CommandInfo;
1225
1226 /* Decrement the re-entry count */
1227 CommandInfo.TaskId = SessionId;
1228 CommandInfo.VDMState = VDM_DEC_REENTER_COUNT;
1229 GetNextVDMCommand(&CommandInfo);
1230
1231 /* Clear the structure */
1232 RtlZeroMemory(&CommandInfo, sizeof(CommandInfo));
1233
1234 /* Update the VDM state of the task */
1235 CommandInfo.TaskId = SessionId;
1236 CommandInfo.VDMState = VDM_FLAG_DONT_WAIT;
1237 GetNextVDMCommand(&CommandInfo);
1238 }
1239 #endif
1240
1241 /* Save the return code - Normal termination */
1242 DosErrorLevel = MAKEWORD(ReturnCode, 0x00);
1243
1244 /* Return control to the parent process */
1245 CpuExecute(HIWORD(PspBlock->TerminateAddress),
1246 LOWORD(PspBlock->TerminateAddress));
1247 }
1248
1249 BOOLEAN DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
1250 {
1251 PDOS_SFT_ENTRY SftEntry = DosGetSftEntry(FileHandle);
1252 PDOS_DEVICE_NODE Node = NULL;
1253
1254 /* Make sure it exists */
1255 if (!SftEntry)
1256 {
1257 DosLastError = ERROR_FILE_NOT_FOUND;
1258 return FALSE;
1259 }
1260
1261 if (SftEntry->Type == DOS_SFT_ENTRY_DEVICE) Node = SftEntry->DeviceNode;
1262
1263 switch (ControlCode)
1264 {
1265 /* Get Device Information */
1266 case 0x00:
1267 {
1268 WORD InfoWord = 0;
1269
1270 /*
1271 * See Ralf Brown: http://www.ctyme.com/intr/rb-2820.htm
1272 * for a list of possible flags.
1273 */
1274
1275 if (Node)
1276 {
1277 /* Return the device attributes with bit 7 set */
1278 InfoWord = Node->DeviceAttributes | (1 << 7);
1279 }
1280
1281 setDX(InfoWord);
1282 return TRUE;
1283 }
1284
1285 /* Set Device Information */
1286 case 0x01:
1287 {
1288 // TODO: NOT IMPLEMENTED
1289 UNIMPLEMENTED;
1290
1291 return FALSE;
1292 }
1293
1294 /* Read From Device I/O Control Channel */
1295 case 0x02:
1296 {
1297 WORD Length = getCX();
1298
1299 if (Node == NULL || !(Node->DeviceAttributes & DOS_DEVATTR_IOCTL))
1300 {
1301 DosLastError = ERROR_INVALID_FUNCTION;
1302 return FALSE;
1303 }
1304
1305 /* Do nothing if there is no IOCTL routine */
1306 if (!Node->IoctlReadRoutine)
1307 {
1308 setAX(0);
1309 return TRUE;
1310 }
1311
1312 Node->IoctlReadRoutine(Node, MAKELONG(getDX(), getDS()), &Length);
1313
1314 setAX(Length);
1315 return TRUE;
1316 }
1317
1318 /* Write To Device I/O Control Channel */
1319 case 0x03:
1320 {
1321 WORD Length = getCX();
1322
1323 if (Node == NULL || !(Node->DeviceAttributes & DOS_DEVATTR_IOCTL))
1324 {
1325 DosLastError = ERROR_INVALID_FUNCTION;
1326 return FALSE;
1327 }
1328
1329 /* Do nothing if there is no IOCTL routine */
1330 if (!Node->IoctlWriteRoutine)
1331 {
1332 setAX(0);
1333 return TRUE;
1334 }
1335
1336 Node->IoctlWriteRoutine(Node, MAKELONG(getDX(), getDS()), &Length);
1337
1338 setAX(Length);
1339 return TRUE;
1340 }
1341
1342 /* Unsupported control code */
1343 default:
1344 {
1345 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode);
1346
1347 DosLastError = ERROR_INVALID_PARAMETER;
1348 return FALSE;
1349 }
1350 }
1351 }
1352
1353 VOID WINAPI DosInt20h(LPWORD Stack)
1354 {
1355 /* This is the exit interrupt */
1356 DosTerminateProcess(Stack[STACK_CS], 0, 0);
1357 }
1358
1359 VOID WINAPI DosInt21h(LPWORD Stack)
1360 {
1361 BYTE Character;
1362 SYSTEMTIME SystemTime;
1363 PCHAR String;
1364 PDOS_INPUT_BUFFER InputBuffer;
1365 PDOS_COUNTRY_CODE_BUFFER CountryCodeBuffer;
1366 INT Return;
1367
1368 /* Check the value in the AH register */
1369 switch (getAH())
1370 {
1371 /* Terminate Program */
1372 case 0x00:
1373 {
1374 DosTerminateProcess(Stack[STACK_CS], 0, 0);
1375 break;
1376 }
1377
1378 /* Read Character from STDIN with Echo */
1379 case 0x01:
1380 {
1381 DPRINT("INT 21h, AH = 01h\n");
1382
1383 // FIXME: Under DOS 2+, input / output handle may be redirected!!!!
1384 DoEcho = TRUE;
1385 Character = DosReadCharacter(DOS_INPUT_HANDLE);
1386 DoEcho = FALSE;
1387
1388 // FIXME: Check whether Ctrl-C / Ctrl-Break is pressed, and call INT 23h if so.
1389 // Check also Ctrl-P and set echo-to-printer flag.
1390 // Ctrl-Z is not interpreted.
1391
1392 setAL(Character);
1393 break;
1394 }
1395
1396 /* Write Character to STDOUT */
1397 case 0x02:
1398 {
1399 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1400 Character = getDL();
1401 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1402
1403 /*
1404 * We return the output character (DOS 2.1+).
1405 * Also, if we're going to output a TAB, then
1406 * don't return a TAB but a SPACE instead.
1407 * See Ralf Brown: http://www.ctyme.com/intr/rb-2554.htm
1408 * for more information.
1409 */
1410 setAL(Character == '\t' ? ' ' : Character);
1411 break;
1412 }
1413
1414 /* Read Character from STDAUX */
1415 case 0x03:
1416 {
1417 // FIXME: Really read it from STDAUX!
1418 DPRINT1("INT 16h, 03h: Read character from STDAUX is HALFPLEMENTED\n");
1419 // setAL(DosReadCharacter());
1420 break;
1421 }
1422
1423 /* Write Character to STDAUX */
1424 case 0x04:
1425 {
1426 // FIXME: Really write it to STDAUX!
1427 DPRINT1("INT 16h, 04h: Write character to STDAUX is HALFPLEMENTED\n");
1428 // DosPrintCharacter(getDL());
1429 break;
1430 }
1431
1432 /* Write Character to Printer */
1433 case 0x05:
1434 {
1435 // FIXME: Really write it to printer!
1436 DPRINT1("INT 16h, 05h: Write character to printer is HALFPLEMENTED -\n\n");
1437 DPRINT1("0x%p\n", getDL());
1438 DPRINT1("\n\n-----------\n\n");
1439 break;
1440 }
1441
1442 /* Direct Console I/O */
1443 case 0x06:
1444 {
1445 Character = getDL();
1446
1447 // FIXME: Under DOS 2+, output handle may be redirected!!!!
1448
1449 if (Character != 0xFF)
1450 {
1451 /* Output */
1452 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1453
1454 /*
1455 * We return the output character (DOS 2.1+).
1456 * See Ralf Brown: http://www.ctyme.com/intr/rb-2558.htm
1457 * for more information.
1458 */
1459 setAL(Character);
1460 }
1461 else
1462 {
1463 /* Input */
1464 if (DosCheckInput())
1465 {
1466 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_ZF;
1467 setAL(DosReadCharacter(DOS_INPUT_HANDLE));
1468 }
1469 else
1470 {
1471 /* No character available */
1472 Stack[STACK_FLAGS] |= EMULATOR_FLAG_ZF;
1473 setAL(0x00);
1474 }
1475 }
1476
1477 break;
1478 }
1479
1480 /* Character Input without Echo */
1481 case 0x07:
1482 case 0x08:
1483 {
1484 DPRINT("Char input without echo\n");
1485
1486 Character = DosReadCharacter(DOS_INPUT_HANDLE);
1487
1488 // FIXME: For 0x07, do not check Ctrl-C/Break.
1489 // For 0x08, do check those control sequences and if needed,
1490 // call INT 0x23.
1491
1492 setAL(Character);
1493 break;
1494 }
1495
1496 /* Write string to STDOUT */
1497 case 0x09:
1498 {
1499 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
1500
1501 while (*String != '$')
1502 {
1503 DosPrintCharacter(DOS_OUTPUT_HANDLE, *String);
1504 String++;
1505 }
1506
1507 /*
1508 * We return the terminating character (DOS 2.1+).
1509 * See Ralf Brown: http://www.ctyme.com/intr/rb-2562.htm
1510 * for more information.
1511 */
1512 setAL('$'); // *String
1513 break;
1514 }
1515
1516 /* Read Buffered Input */
1517 case 0x0A:
1518 {
1519 WORD Count = 0;
1520 InputBuffer = (PDOS_INPUT_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
1521
1522 DPRINT("Read Buffered Input\n");
1523
1524 while (Count < InputBuffer->MaxLength)
1525 {
1526 /* Try to read a character (wait) */
1527 Character = DosReadCharacter(DOS_INPUT_HANDLE);
1528
1529 switch (Character)
1530 {
1531 /* Extended character */
1532 case '\0':
1533 {
1534 /* Read the scancode */
1535 DosReadCharacter(DOS_INPUT_HANDLE);
1536 break;
1537 }
1538
1539 /* Ctrl-C */
1540 case 0x03:
1541 {
1542 DosPrintCharacter(DOS_OUTPUT_HANDLE, '^');
1543 DosPrintCharacter(DOS_OUTPUT_HANDLE, 'C');
1544
1545 if (DosControlBreak()) return;
1546 break;
1547 }
1548
1549 /* Backspace */
1550 case '\b':
1551 {
1552 if (Count > 0)
1553 {
1554 Count--;
1555
1556 /* Erase the character */
1557 DosPrintCharacter(DOS_OUTPUT_HANDLE, '\b');
1558 DosPrintCharacter(DOS_OUTPUT_HANDLE, ' ');
1559 DosPrintCharacter(DOS_OUTPUT_HANDLE, '\b');
1560 }
1561
1562 break;
1563 }
1564
1565 default:
1566 {
1567 /* Append it to the buffer */
1568 InputBuffer->Buffer[Count] = Character;
1569 Count++; /* Carriage returns are also counted */
1570
1571 /* Check if this is a special character */
1572 if (Character < 0x20 && Character != 0x0A && Character != 0x0D)
1573 {
1574 DosPrintCharacter(DOS_OUTPUT_HANDLE, '^');
1575 Character += 'A' - 1;
1576 }
1577
1578 /* Echo the character */
1579 DosPrintCharacter(DOS_OUTPUT_HANDLE, Character);
1580 }
1581 }
1582
1583 if (Character == '\r') break;
1584 }
1585
1586 /* Update the length */
1587 InputBuffer->Length = Count;
1588
1589 break;
1590 }
1591
1592 /* Get STDIN Status */
1593 case 0x0B:
1594 {
1595 setAL(DosCheckInput() ? 0xFF : 0x00);
1596 break;
1597 }
1598
1599 /* Flush Buffer and Read STDIN */
1600 case 0x0C:
1601 {
1602 BYTE InputFunction = getAL();
1603
1604 /* Flush STDIN buffer */
1605 DosFlushFileBuffers(DOS_INPUT_HANDLE);
1606
1607 /*
1608 * If the input function number contained in AL is valid, i.e.
1609 * AL == 0x01 or 0x06 or 0x07 or 0x08 or 0x0A, call ourselves
1610 * recursively with AL == AH.
1611 */
1612 if (InputFunction == 0x01 || InputFunction == 0x06 ||
1613 InputFunction == 0x07 || InputFunction == 0x08 ||
1614 InputFunction == 0x0A)
1615 {
1616 /* Call ourselves recursively */
1617 setAH(InputFunction);
1618 DosInt21h(Stack);
1619 }
1620 break;
1621 }
1622
1623 /* Disk Reset */
1624 case 0x0D:
1625 {
1626 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1627
1628 // TODO: Flush what's needed.
1629 DPRINT1("INT 21h, 0Dh is UNIMPLEMENTED\n");
1630
1631 /* Clear CF in DOS 6 only */
1632 if (PspBlock->DosVersion == 0x0006)
1633 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1634
1635 break;
1636 }
1637
1638 /* Set Default Drive */
1639 case 0x0E:
1640 {
1641 DosChangeDrive(getDL());
1642 setAL(LastDrive - 'A' + 1);
1643 break;
1644 }
1645
1646 /* NULL Function for CP/M Compatibility */
1647 case 0x18:
1648 {
1649 /*
1650 * This function corresponds to the CP/M BDOS function
1651 * "get bit map of logged drives", which is meaningless
1652 * under MS-DOS.
1653 *
1654 * For: PTS-DOS 6.51 & S/DOS 1.0 - EXTENDED RENAME FILE USING FCB
1655 * See Ralf Brown: http://www.ctyme.com/intr/rb-2584.htm
1656 * for more information.
1657 */
1658 setAL(0x00);
1659 break;
1660 }
1661
1662 /* Get Default Drive */
1663 case 0x19:
1664 {
1665 setAL(CurrentDrive);
1666 break;
1667 }
1668
1669 /* Set Disk Transfer Area */
1670 case 0x1A:
1671 {
1672 DiskTransferArea = MAKELONG(getDX(), getDS());
1673 break;
1674 }
1675
1676 /* NULL Function for CP/M Compatibility */
1677 case 0x1D:
1678 case 0x1E:
1679 {
1680 /*
1681 * Function 0x1D corresponds to the CP/M BDOS function
1682 * "get bit map of read-only drives", which is meaningless
1683 * under MS-DOS.
1684 * See Ralf Brown: http://www.ctyme.com/intr/rb-2592.htm
1685 * for more information.
1686 *
1687 * Function 0x1E corresponds to the CP/M BDOS function
1688 * "set file attributes", which was meaningless under MS-DOS 1.x.
1689 * See Ralf Brown: http://www.ctyme.com/intr/rb-2593.htm
1690 * for more information.
1691 */
1692 setAL(0x00);
1693 break;
1694 }
1695
1696 /* NULL Function for CP/M Compatibility */
1697 case 0x20:
1698 {
1699 /*
1700 * This function corresponds to the CP/M BDOS function
1701 * "get/set default user (sublibrary) number", which is meaningless
1702 * under MS-DOS.
1703 *
1704 * For: S/DOS 1.0+ & PTS-DOS 6.51+ - GET OEM REVISION
1705 * See Ralf Brown: http://www.ctyme.com/intr/rb-2596.htm
1706 * for more information.
1707 */
1708 setAL(0x00);
1709 break;
1710 }
1711
1712 /* Set Interrupt Vector */
1713 case 0x25:
1714 {
1715 ULONG FarPointer = MAKELONG(getDX(), getDS());
1716 DPRINT1("Setting interrupt 0x%02X to %04X:%04X ...\n",
1717 getAL(), HIWORD(FarPointer), LOWORD(FarPointer));
1718
1719 /* Write the new far pointer to the IDT */
1720 ((PULONG)BaseAddress)[getAL()] = FarPointer;
1721 break;
1722 }
1723
1724 /* Create New PSP */
1725 case 0x26:
1726 {
1727 DPRINT1("INT 21h, AH = 26h - Create New PSP is UNIMPLEMENTED\n");
1728 break;
1729 }
1730
1731 /* Get System Date */
1732 case 0x2A:
1733 {
1734 GetLocalTime(&SystemTime);
1735 setCX(SystemTime.wYear);
1736 setDX(MAKEWORD(SystemTime.wDay, SystemTime.wMonth));
1737 setAL(SystemTime.wDayOfWeek);
1738 break;
1739 }
1740
1741 /* Set System Date */
1742 case 0x2B:
1743 {
1744 GetLocalTime(&SystemTime);
1745 SystemTime.wYear = getCX();
1746 SystemTime.wMonth = getDH();
1747 SystemTime.wDay = getDL();
1748
1749 /* Return success or failure */
1750 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1751 break;
1752 }
1753
1754 /* Get System Time */
1755 case 0x2C:
1756 {
1757 GetLocalTime(&SystemTime);
1758 setCX(MAKEWORD(SystemTime.wMinute, SystemTime.wHour));
1759 setDX(MAKEWORD(SystemTime.wMilliseconds / 10, SystemTime.wSecond));
1760 break;
1761 }
1762
1763 /* Set System Time */
1764 case 0x2D:
1765 {
1766 GetLocalTime(&SystemTime);
1767 SystemTime.wHour = getCH();
1768 SystemTime.wMinute = getCL();
1769 SystemTime.wSecond = getDH();
1770 SystemTime.wMilliseconds = getDL() * 10; // In hundredths of seconds
1771
1772 /* Return success or failure */
1773 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1774 break;
1775 }
1776
1777 /* Get Disk Transfer Area */
1778 case 0x2F:
1779 {
1780 setES(HIWORD(DiskTransferArea));
1781 setBX(LOWORD(DiskTransferArea));
1782 break;
1783 }
1784
1785 /* Get DOS Version */
1786 case 0x30:
1787 {
1788 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1789
1790 /*
1791 * DOS 2+ - GET DOS VERSION
1792 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1793 * for more information.
1794 */
1795
1796 if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
1797 {
1798 /*
1799 * Return DOS OEM number:
1800 * 0x00 for IBM PC-DOS
1801 * 0x02 for packaged MS-DOS
1802 * 0xFF for NT DOS
1803 */
1804 setBH(0xFF);
1805 }
1806
1807 if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
1808 {
1809 /*
1810 * Return version flag:
1811 * 1 << 3 if DOS is in ROM,
1812 * 0 (reserved) if not.
1813 */
1814 setBH(0x00);
1815 }
1816
1817 /* Return DOS 24-bit user serial number in BL:CX */
1818 setBL(0x00);
1819 setCX(0x0000);
1820
1821 /*
1822 * Return DOS version: Minor:Major in AH:AL
1823 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1824 */
1825 setAX(PspBlock->DosVersion);
1826
1827 break;
1828 }
1829
1830 /* Terminate and Stay Resident */
1831 case 0x31:
1832 {
1833 DPRINT1("Process going resident: %u paragraphs kept\n", getDX());
1834 DosTerminateProcess(CurrentPsp, getAL(), getDX());
1835 break;
1836 }
1837
1838 /* Extended functionalities */
1839 case 0x33:
1840 {
1841 if (getAL() == 0x06)
1842 {
1843 /*
1844 * DOS 5+ - GET TRUE VERSION NUMBER
1845 * This function always returns the true version number, unlike
1846 * AH=30h, whose return value may be changed with SETVER.
1847 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1848 * for more information.
1849 */
1850
1851 /*
1852 * Return the true DOS version: Minor:Major in BH:BL
1853 * The Windows NT DOS box returns BX=3205h (version 5.50).
1854 */
1855 setBX(NTDOS_VERSION);
1856
1857 /* DOS revision 0 */
1858 setDL(0x00);
1859
1860 /* Unpatched DOS */
1861 setDH(0x00);
1862 }
1863 // else
1864 // {
1865 // /* Invalid subfunction */
1866 // setAL(0xFF);
1867 // }
1868
1869 break;
1870 }
1871
1872 /* Get Interrupt Vector */
1873 case 0x35:
1874 {
1875 DWORD FarPointer = ((PDWORD)BaseAddress)[getAL()];
1876
1877 /* Read the address from the IDT into ES:BX */
1878 setES(HIWORD(FarPointer));
1879 setBX(LOWORD(FarPointer));
1880 break;
1881 }
1882
1883 /* SWITCH character - AVAILDEV */
1884 case 0x37:
1885 {
1886 if (getAL() == 0x00)
1887 {
1888 /*
1889 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
1890 * This setting is ignored by MS-DOS 4.0+.
1891 * MS-DOS 5+ always return AL=00h/DL=2Fh.
1892 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
1893 * for more information.
1894 */
1895 setDL('/');
1896 setAL(0x00);
1897 }
1898 else if (getAL() == 0x01)
1899 {
1900 /*
1901 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
1902 * This setting is ignored by MS-DOS 5+.
1903 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
1904 * for more information.
1905 */
1906 // getDL();
1907 setAL(0xFF);
1908 }
1909 else if (getAL() == 0x02)
1910 {
1911 /*
1912 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1913 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1914 * for more information.
1915 */
1916 // setDL();
1917 setAL(0xFF);
1918 }
1919 else if (getAL() == 0x03)
1920 {
1921 /*
1922 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
1923 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
1924 * for more information.
1925 */
1926 // getDL();
1927 setAL(0xFF);
1928 }
1929 else
1930 {
1931 /* Invalid subfunction */
1932 setAL(0xFF);
1933 }
1934
1935 break;
1936 }
1937
1938 /* Get/Set Country-dependent Information */
1939 case 0x38:
1940 {
1941 CountryCodeBuffer = (PDOS_COUNTRY_CODE_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
1942
1943 if (getAL() == 0x00)
1944 {
1945 /* Get */
1946 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDATE,
1947 &CountryCodeBuffer->TimeFormat,
1948 sizeof(CountryCodeBuffer->TimeFormat) / sizeof(TCHAR));
1949 if (Return == 0)
1950 {
1951 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1952 setAX(LOWORD(GetLastError()));
1953 break;
1954 }
1955
1956 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY,
1957 &CountryCodeBuffer->CurrencySymbol,
1958 sizeof(CountryCodeBuffer->CurrencySymbol) / sizeof(TCHAR));
1959 if (Return == 0)
1960 {
1961 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1962 setAX(LOWORD(GetLastError()));
1963 break;
1964 }
1965
1966 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,
1967 &CountryCodeBuffer->ThousandSep,
1968 sizeof(CountryCodeBuffer->ThousandSep) / sizeof(TCHAR));
1969 if (Return == 0)
1970 {
1971 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1972 setAX(LOWORD(GetLastError()));
1973 break;
1974 }
1975
1976 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,
1977 &CountryCodeBuffer->DecimalSep,
1978 sizeof(CountryCodeBuffer->DecimalSep) / sizeof(TCHAR));
1979 if (Return == 0)
1980 {
1981 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
1982 setAX(LOWORD(GetLastError()));
1983 break;
1984 }
1985
1986 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
1987 }
1988 else
1989 {
1990 // TODO: NOT IMPLEMENTED
1991 UNIMPLEMENTED;
1992 }
1993
1994 break;
1995 }
1996
1997 /* Create Directory */
1998 case 0x39:
1999 {
2000 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2001
2002 if (CreateDirectoryA(String, NULL))
2003 {
2004 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2005 }
2006 else
2007 {
2008 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2009 setAX(LOWORD(GetLastError()));
2010 }
2011
2012 break;
2013 }
2014
2015 /* Remove Directory */
2016 case 0x3A:
2017 {
2018 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2019
2020 if (RemoveDirectoryA(String))
2021 {
2022 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2023 }
2024 else
2025 {
2026 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2027 setAX(LOWORD(GetLastError()));
2028 }
2029
2030 break;
2031 }
2032
2033 /* Set Current Directory */
2034 case 0x3B:
2035 {
2036 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2037
2038 if (DosChangeDirectory(String))
2039 {
2040 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2041 }
2042 else
2043 {
2044 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2045 setAX(DosLastError);
2046 }
2047
2048 break;
2049 }
2050
2051 /* Create or Truncate File */
2052 case 0x3C:
2053 {
2054 WORD FileHandle;
2055 WORD ErrorCode = DosCreateFile(&FileHandle,
2056 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2057 CREATE_ALWAYS,
2058 getCX());
2059
2060 if (ErrorCode == ERROR_SUCCESS)
2061 {
2062 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2063 setAX(FileHandle);
2064 }
2065 else
2066 {
2067 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2068 setAX(ErrorCode);
2069 }
2070
2071 break;
2072 }
2073
2074 /* Open File or Device */
2075 case 0x3D:
2076 {
2077 WORD FileHandle;
2078 WORD ErrorCode;
2079 LPCSTR FileName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2080 PDOS_DEVICE_NODE Device = DosGetDevice(FileName);
2081
2082 if (Device)
2083 {
2084 FileHandle = DosOpenDevice(Device);
2085 ErrorCode = (FileHandle != INVALID_DOS_HANDLE)
2086 ? ERROR_SUCCESS : ERROR_TOO_MANY_OPEN_FILES;
2087 }
2088 else
2089 {
2090 ErrorCode = DosOpenFile(&FileHandle, FileName, getAL());
2091 }
2092
2093 if (ErrorCode == ERROR_SUCCESS)
2094 {
2095 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2096 setAX(FileHandle);
2097 }
2098 else
2099 {
2100 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2101 setAX(ErrorCode);
2102 }
2103
2104 break;
2105 }
2106
2107 /* Close File or Device */
2108 case 0x3E:
2109 {
2110 if (DosCloseHandle(getBX()))
2111 {
2112 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2113 }
2114 else
2115 {
2116 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2117 setAX(ERROR_INVALID_HANDLE);
2118 }
2119
2120 break;
2121 }
2122
2123 /* Read from File or Device */
2124 case 0x3F:
2125 {
2126 WORD BytesRead = 0;
2127 WORD ErrorCode;
2128
2129 DPRINT("DosReadFile(0x%04X)\n", getBX());
2130
2131 DoEcho = TRUE;
2132 ErrorCode = DosReadFile(getBX(),
2133 MAKELONG(getDX(), getDS()),
2134 getCX(),
2135 &BytesRead);
2136 DoEcho = FALSE;
2137
2138 if (ErrorCode == ERROR_SUCCESS)
2139 {
2140 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2141 setAX(BytesRead);
2142 }
2143 else if (ErrorCode != ERROR_NOT_READY)
2144 {
2145 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2146 setAX(ErrorCode);
2147 }
2148
2149 break;
2150 }
2151
2152 /* Write to File or Device */
2153 case 0x40:
2154 {
2155 WORD BytesWritten = 0;
2156 WORD ErrorCode = DosWriteFile(getBX(),
2157 MAKELONG(getDX(), getDS()),
2158 getCX(),
2159 &BytesWritten);
2160
2161 if (ErrorCode == ERROR_SUCCESS)
2162 {
2163 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2164 setAX(BytesWritten);
2165 }
2166 else
2167 {
2168 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2169 setAX(ErrorCode);
2170 }
2171
2172 break;
2173 }
2174
2175 /* Delete File */
2176 case 0x41:
2177 {
2178 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2179
2180 if (demFileDelete(FileName) == ERROR_SUCCESS)
2181 {
2182 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2183 /*
2184 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2185 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2186 */
2187 setAL(FileName[0] - 'A');
2188 }
2189 else
2190 {
2191 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2192 setAX(GetLastError());
2193 }
2194
2195 break;
2196 }
2197
2198 /* Seek File */
2199 case 0x42:
2200 {
2201 DWORD NewLocation;
2202 WORD ErrorCode = DosSeekFile(getBX(),
2203 MAKELONG(getDX(), getCX()),
2204 getAL(),
2205 &NewLocation);
2206
2207 if (ErrorCode == ERROR_SUCCESS)
2208 {
2209 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2210
2211 /* Return the new offset in DX:AX */
2212 setDX(HIWORD(NewLocation));
2213 setAX(LOWORD(NewLocation));
2214 }
2215 else
2216 {
2217 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2218 setAX(ErrorCode);
2219 }
2220
2221 break;
2222 }
2223
2224 /* Get/Set File Attributes */
2225 case 0x43:
2226 {
2227 DWORD Attributes;
2228 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2229
2230 if (getAL() == 0x00)
2231 {
2232 /* Get the attributes */
2233 Attributes = GetFileAttributesA(FileName);
2234
2235 /* Check if it failed */
2236 if (Attributes == INVALID_FILE_ATTRIBUTES)
2237 {
2238 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2239 setAX(GetLastError());
2240 }
2241 else
2242 {
2243 /* Return the attributes that DOS can understand */
2244 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2245 setCX(Attributes & 0x00FF);
2246 }
2247 }
2248 else if (getAL() == 0x01)
2249 {
2250 /* Try to set the attributes */
2251 if (SetFileAttributesA(FileName, getCL()))
2252 {
2253 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2254 }
2255 else
2256 {
2257 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2258 setAX(GetLastError());
2259 }
2260 }
2261 else
2262 {
2263 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2264 setAX(ERROR_INVALID_FUNCTION);
2265 }
2266
2267 break;
2268 }
2269
2270 /* IOCTL */
2271 case 0x44:
2272 {
2273 if (DosHandleIoctl(getAL(), getBX()))
2274 {
2275 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2276 }
2277 else
2278 {
2279 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2280 setAX(DosLastError);
2281 }
2282
2283 break;
2284 }
2285
2286 /* Duplicate Handle */
2287 case 0x45:
2288 {
2289 WORD NewHandle;
2290 PDOS_SFT_ENTRY SftEntry = DosGetSftEntry(getBX());
2291
2292 if (SftEntry == NULL || SftEntry->Type == DOS_SFT_ENTRY_NONE)
2293 {
2294 /* The handle is invalid */
2295 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2296 setAX(ERROR_INVALID_HANDLE);
2297 break;
2298 }
2299
2300 /* Open a new handle to the same entry */
2301 switch (SftEntry->Type)
2302 {
2303 case DOS_SFT_ENTRY_WIN32:
2304 {
2305 NewHandle = DosOpenHandle(SftEntry->Handle);
2306 break;
2307 }
2308
2309 case DOS_SFT_ENTRY_DEVICE:
2310 {
2311 NewHandle = DosOpenDevice(SftEntry->DeviceNode);
2312 break;
2313 }
2314
2315 default:
2316 {
2317 /* Shouldn't happen */
2318 ASSERT(FALSE);
2319 }
2320 }
2321
2322 if (NewHandle == INVALID_DOS_HANDLE)
2323 {
2324 /* Too many files open */
2325 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2326 setAX(ERROR_TOO_MANY_OPEN_FILES);
2327 break;
2328 }
2329
2330 /* Return the result */
2331 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2332 setAX(NewHandle);
2333 break;
2334 }
2335
2336 /* Force Duplicate Handle */
2337 case 0x46:
2338 {
2339 if (DosDuplicateHandle(getBX(), getCX()))
2340 {
2341 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2342 }
2343 else
2344 {
2345 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2346 setAX(ERROR_INVALID_HANDLE);
2347 }
2348
2349 break;
2350 }
2351
2352 /* Get Current Directory */
2353 case 0x47:
2354 {
2355 BYTE DriveNumber = getDL();
2356 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
2357
2358 /* Get the real drive number */
2359 if (DriveNumber == 0)
2360 {
2361 DriveNumber = CurrentDrive;
2362 }
2363 else
2364 {
2365 /* Decrement DriveNumber since it was 1-based */
2366 DriveNumber--;
2367 }
2368
2369 if (DriveNumber <= LastDrive - 'A')
2370 {
2371 /*
2372 * Copy the current directory into the target buffer.
2373 * It doesn't contain the drive letter and the backslash.
2374 */
2375 strncpy(String, CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
2376 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2377 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2378 }
2379 else
2380 {
2381 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2382 setAX(ERROR_INVALID_DRIVE);
2383 }
2384
2385 break;
2386 }
2387
2388 /* Allocate Memory */
2389 case 0x48:
2390 {
2391 WORD MaxAvailable = 0;
2392 WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
2393
2394 if (Segment != 0)
2395 {
2396 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2397 setAX(Segment);
2398 }
2399 else
2400 {
2401 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2402 setAX(DosLastError);
2403 setBX(MaxAvailable);
2404 }
2405
2406 break;
2407 }
2408
2409 /* Free Memory */
2410 case 0x49:
2411 {
2412 if (DosFreeMemory(getES()))
2413 {
2414 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2415 }
2416 else
2417 {
2418 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2419 setAX(ERROR_ARENA_TRASHED);
2420 }
2421
2422 break;
2423 }
2424
2425 /* Resize Memory Block */
2426 case 0x4A:
2427 {
2428 WORD Size;
2429
2430 if (DosResizeMemory(getES(), getBX(), &Size))
2431 {
2432 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2433 }
2434 else
2435 {
2436 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2437 setAX(DosLastError);
2438 setBX(Size);
2439 }
2440
2441 break;
2442 }
2443
2444 #ifndef STANDALONE
2445 /* Execute */
2446 case 0x4B:
2447 {
2448 DOS_EXEC_TYPE LoadType = (DOS_EXEC_TYPE)getAL();
2449 LPSTR ProgramName = SEG_OFF_TO_PTR(getDS(), getDX());
2450 PDOS_EXEC_PARAM_BLOCK ParamBlock = SEG_OFF_TO_PTR(getES(), getBX());
2451 WORD ErrorCode = DosCreateProcess(LoadType, ProgramName, ParamBlock);
2452
2453 if (ErrorCode == ERROR_SUCCESS)
2454 {
2455 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2456 }
2457 else
2458 {
2459 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2460 setAX(ErrorCode);
2461 }
2462
2463 break;
2464 }
2465 #endif
2466
2467 /* Terminate With Return Code */
2468 case 0x4C:
2469 {
2470 DosTerminateProcess(CurrentPsp, getAL(), 0);
2471 break;
2472 }
2473
2474 /* Get Return Code (ERRORLEVEL) */
2475 case 0x4D:
2476 {
2477 /*
2478 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2479 * DosErrorLevel is cleared after being read by this function.
2480 */
2481 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2482 setAX(DosErrorLevel);
2483 DosErrorLevel = 0x0000; // Clear it
2484 break;
2485 }
2486
2487 /* Find First File */
2488 case 0x4E:
2489 {
2490 WORD Result = (WORD)demFileFindFirst(FAR_POINTER(DiskTransferArea),
2491 SEG_OFF_TO_PTR(getDS(), getDX()),
2492 getCX());
2493
2494 setAX(Result);
2495
2496 if (Result == ERROR_SUCCESS)
2497 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2498 else
2499 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2500
2501 break;
2502 }
2503
2504 /* Find Next File */
2505 case 0x4F:
2506 {
2507 WORD Result = (WORD)demFileFindNext(FAR_POINTER(DiskTransferArea));
2508
2509 setAX(Result);
2510
2511 if (Result == ERROR_SUCCESS)
2512 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2513 else
2514 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2515
2516 break;
2517 }
2518
2519 /* Internal - Set Current Process ID (Set PSP Address) */
2520 case 0x50:
2521 {
2522 // FIXME: Is it really what it's done ??
2523 CurrentPsp = getBX();
2524 break;
2525 }
2526
2527 /* Internal - Get Current Process ID (Get PSP Address) */
2528 case 0x51:
2529 /* Get Current PSP Address */
2530 case 0x62:
2531 {
2532 /*
2533 * Undocumented AH=51h is identical to the documented AH=62h.
2534 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2535 * and http://www.ctyme.com/intr/rb-3140.htm
2536 * for more information.
2537 */
2538 setBX(CurrentPsp);
2539 break;
2540 }
2541
2542 /* Internal - Get "List of lists" (SYSVARS) */
2543 case 0x52:
2544 {
2545 /*
2546 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
2547 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
2548 * for more information.
2549 */
2550
2551 /* Return the DOS "list of lists" in ES:BX */
2552 setES(0x0000);
2553 setBX(0x0000);
2554
2555 DisplayMessage(L"Required for AARD code, do you remember? :P");
2556 break;
2557 }
2558
2559 /* Rename File */
2560 case 0x56:
2561 {
2562 LPSTR ExistingFileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2563 LPSTR NewFileName = (LPSTR)SEG_OFF_TO_PTR(getES(), getDI());
2564
2565 /*
2566 * See Ralf Brown: http://www.ctyme.com/intr/rb-2990.htm
2567 * for more information.
2568 */
2569
2570 if (MoveFileA(ExistingFileName, NewFileName))
2571 {
2572 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2573 }
2574 else
2575 {
2576 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2577 setAX(GetLastError());
2578 }
2579
2580 break;
2581 }
2582
2583 /* Get/Set Memory Management Options */
2584 case 0x58:
2585 {
2586 if (getAL() == 0x00)
2587 {
2588 /* Get allocation strategy */
2589 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2590 setAX(DosAllocStrategy);
2591 }
2592 else if (getAL() == 0x01)
2593 {
2594 /* Set allocation strategy */
2595
2596 if ((getBL() & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2597 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2598 {
2599 /* Can't set both */
2600 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2601 setAX(ERROR_INVALID_PARAMETER);
2602 break;
2603 }
2604
2605 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT)
2606 {
2607 /* Invalid allocation strategy */
2608 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2609 setAX(ERROR_INVALID_PARAMETER);
2610 break;
2611 }
2612
2613 DosAllocStrategy = getBL();
2614 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2615 }
2616 else if (getAL() == 0x02)
2617 {
2618 /* Get UMB link state */
2619 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2620 setAL(DosUmbLinked ? 0x01 : 0x00);
2621 }
2622 else if (getAL() == 0x03)
2623 {
2624 /* Set UMB link state */
2625 if (getBX()) DosLinkUmb();
2626 else DosUnlinkUmb();
2627 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2628 }
2629 else
2630 {
2631 /* Invalid or unsupported function */
2632 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2633 setAX(ERROR_INVALID_FUNCTION);
2634 }
2635
2636 break;
2637 }
2638
2639 /* Get Extended Error Information */
2640 case 0x59:
2641 {
2642 DPRINT1("INT 21h, AH = 59h, BX = %04Xh - Get Extended Error Information is UNIMPLEMENTED\n",
2643 getBX());
2644 break;
2645 }
2646
2647 /* Create Temporary File */
2648 case 0x5A:
2649 {
2650 LPSTR PathName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2651 LPSTR FileName = PathName; // The buffer for the path and the full file name is the same.
2652 UINT uRetVal;
2653 WORD FileHandle;
2654 WORD ErrorCode;
2655
2656 /*
2657 * See Ralf Brown: http://www.ctyme.com/intr/rb-3014.htm
2658 * for more information.
2659 */
2660
2661 // FIXME: Check for buffer validity?
2662 // It should be a ASCIZ path ending with a '\' + 13 zero bytes
2663 // to receive the generated filename.
2664
2665 /* First create the temporary file */
2666 uRetVal = GetTempFileNameA(PathName, NULL, 0, FileName);
2667 if (uRetVal == 0)
2668 {
2669 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2670 setAX(GetLastError());
2671 break;
2672 }
2673
2674 /* Now try to open it in read/write access */
2675 ErrorCode = DosOpenFile(&FileHandle, FileName, 2);
2676 if (ErrorCode == ERROR_SUCCESS)
2677 {
2678 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2679 setAX(FileHandle);
2680 }
2681 else
2682 {
2683 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2684 setAX(ErrorCode);
2685 }
2686
2687 break;
2688 }
2689
2690 /* Create New File */
2691 case 0x5B:
2692 {
2693 WORD FileHandle;
2694 WORD ErrorCode = DosCreateFile(&FileHandle,
2695 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2696 CREATE_NEW,
2697 getCX());
2698
2699 if (ErrorCode == ERROR_SUCCESS)
2700 {
2701 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2702 setAX(FileHandle);
2703 }
2704 else
2705 {
2706 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2707 setAX(ErrorCode);
2708 }
2709
2710 break;
2711 }
2712
2713 /* Lock/Unlock Region of File */
2714 case 0x5C:
2715 {
2716 PDOS_SFT_ENTRY SftEntry = DosGetSftEntry(getBX());
2717
2718 if (SftEntry == NULL || SftEntry->Type != DOS_SFT_ENTRY_WIN32)
2719 {
2720 /* The handle is invalid */
2721 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2722 setAX(ERROR_INVALID_HANDLE);
2723 break;
2724 }
2725
2726 if (getAL() == 0x00)
2727 {
2728 /* Lock region of file */
2729 if (LockFile(SftEntry->Handle,
2730 MAKELONG(getCX(), getDX()), 0,
2731 MAKELONG(getSI(), getDI()), 0))
2732 {
2733 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2734 }
2735 else
2736 {
2737 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2738 setAX(GetLastError());
2739 }
2740 }
2741 else if (getAL() == 0x01)
2742 {
2743 /* Unlock region of file */
2744 if (UnlockFile(SftEntry->Handle,
2745 MAKELONG(getCX(), getDX()), 0,
2746 MAKELONG(getSI(), getDI()), 0))
2747 {
2748 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2749 }
2750 else
2751 {
2752 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2753 setAX(GetLastError());
2754 }
2755 }
2756 else
2757 {
2758 /* Invalid subfunction */
2759 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2760 setAX(ERROR_INVALID_FUNCTION);
2761 }
2762
2763 break;
2764 }
2765
2766 /* Canonicalize File Name or Path */
2767 case 0x60:
2768 {
2769 /*
2770 * See Ralf Brown: http://www.ctyme.com/intr/rb-3137.htm
2771 * for more information.
2772 */
2773
2774 /*
2775 * We suppose that the DOS app gave to us a valid
2776 * 128-byte long buffer for the canonicalized name.
2777 */
2778 DWORD dwRetVal = GetFullPathNameA(SEG_OFF_TO_PTR(getDS(), getSI()),
2779 128,
2780 SEG_OFF_TO_PTR(getES(), getDI()),
2781 NULL);
2782 if (dwRetVal == 0)
2783 {
2784 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2785 setAX(GetLastError());
2786 }
2787 else
2788 {
2789 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2790 setAX(0x0000);
2791 }
2792
2793 // FIXME: Convert the full path name into short version.
2794 // We cannot reliably use GetShortPathName, because it fails
2795 // if the path name given doesn't exist. However this DOS
2796 // function AH=60h should be able to work even for non-existing
2797 // path and file names.
2798
2799 break;
2800 }
2801
2802 /* Set Handle Count */
2803 case 0x67:
2804 {
2805 if (!DosResizeHandleTable(getBX()))
2806 {
2807 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2808 setAX(DosLastError);
2809 }
2810 else Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2811
2812 break;
2813 }
2814
2815 /* Commit File */
2816 case 0x68:
2817 case 0x6A:
2818 {
2819 /*
2820 * Function 6Ah is identical to function 68h,
2821 * and sets AH to 68h if success.
2822 * See Ralf Brown: http://www.ctyme.com/intr/rb-3176.htm
2823 * for more information.
2824 */
2825 setAH(0x68);
2826
2827 if (DosFlushFileBuffers(getBX()))
2828 {
2829 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2830 }
2831 else
2832 {
2833 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2834 setAX(GetLastError());
2835 }
2836
2837 break;
2838 }
2839
2840 /* Extended Open/Create */
2841 case 0x6C:
2842 {
2843 WORD FileHandle;
2844 WORD CreationStatus;
2845 WORD ErrorCode;
2846
2847 /* Check for AL == 00 */
2848 if (getAL() != 0x00)
2849 {
2850 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2851 setAX(ERROR_INVALID_FUNCTION);
2852 break;
2853 }
2854
2855 /*
2856 * See Ralf Brown: http://www.ctyme.com/intr/rb-3179.htm
2857 * for the full detailed description.
2858 *
2859 * WARNING: BH contains some extended flags that are NOT SUPPORTED.
2860 */
2861
2862 ErrorCode = DosCreateFileEx(&FileHandle,
2863 &CreationStatus,
2864 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getSI()),
2865 getBL(),
2866 getDL(),
2867 getCX());
2868
2869 if (ErrorCode == ERROR_SUCCESS)
2870 {
2871 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2872 setCX(CreationStatus);
2873 setAX(FileHandle);
2874 }
2875 else
2876 {
2877 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2878 setAX(ErrorCode);
2879 }
2880
2881 break;
2882 }
2883
2884 /* Unsupported */
2885 default:
2886 {
2887 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2888 getAH(), getAL());
2889
2890 setAL(0); // Some functions expect AL to be 0 when it's not supported.
2891 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2892 }
2893 }
2894 }
2895
2896 VOID WINAPI DosBreakInterrupt(LPWORD Stack)
2897 {
2898 /* Set CF to terminate the running process */
2899 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2900 }
2901
2902 VOID WINAPI DosFastConOut(LPWORD Stack)
2903 {
2904 /*
2905 * This is the DOS 2+ Fast Console Output Interrupt.
2906 * The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
2907 *
2908 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
2909 * for more information.
2910 */
2911
2912 /* Save AX and BX */
2913 USHORT AX = getAX();
2914 USHORT BX = getBX();
2915
2916 /*
2917 * Set the parameters:
2918 * AL contains the character to print (already set),
2919 * BL contains the character attribute,
2920 * BH contains the video page to use.
2921 */
2922 setBL(DOS_CHAR_ATTRIBUTE);
2923 setBH(Bda->VideoPage);
2924
2925 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
2926 setAH(0x0E);
2927 Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
2928
2929 /* Restore AX and BX */
2930 setBX(BX);
2931 setAX(AX);
2932 }
2933
2934 VOID WINAPI DosInt2Fh(LPWORD Stack)
2935 {
2936 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
2937 getAH(), getAL());
2938 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2939 }
2940
2941 BOOLEAN DosKRNLInitialize(VOID)
2942 {
2943
2944 #if 1
2945
2946 UCHAR i;
2947 CHAR CurrentDirectory[MAX_PATH];
2948 CHAR DosDirectory[DOS_DIR_LENGTH];
2949 LPSTR Path;
2950
2951 FILE *Stream;
2952 WCHAR Buffer[256];
2953
2954 /* Clear the current directory buffer */
2955 RtlZeroMemory(CurrentDirectories, sizeof(CurrentDirectories));
2956
2957 /* Get the current directory */
2958 if (!GetCurrentDirectoryA(MAX_PATH, CurrentDirectory))
2959 {
2960 // TODO: Use some kind of default path?
2961 return FALSE;
2962 }
2963
2964 /* Convert that to a DOS path */
2965 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, DOS_DIR_LENGTH))
2966 {
2967 // TODO: Use some kind of default path?
2968 return FALSE;
2969 }
2970
2971 /* Set the drive */
2972 CurrentDrive = DosDirectory[0] - 'A';
2973
2974 /* Get the directory part of the path */
2975 Path = strchr(DosDirectory, '\\');
2976 if (Path != NULL)
2977 {
2978 /* Skip the backslash */
2979 Path++;
2980 }
2981
2982 /* Set the directory */
2983 if (Path != NULL)
2984 {
2985 strncpy(CurrentDirectories[CurrentDrive], Path, DOS_DIR_LENGTH);
2986 }
2987
2988 /* Read CONFIG.SYS */
2989 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
2990 if (Stream != NULL)
2991 {
2992 while (fgetws(Buffer, 256, Stream))
2993 {
2994 // TODO: Parse the line
2995 }
2996 fclose(Stream);
2997 }
2998
2999 /* Initialize the SFT */
3000 for (i = 0; i < DOS_SFT_SIZE; i++)
3001 {
3002 DosSystemFileTable[i].Type = DOS_SFT_ENTRY_NONE;
3003 DosSystemFileTable[i].RefCount = 0;
3004 }
3005
3006 /* Load the EMS driver */
3007 EmsDrvInitialize();
3008
3009 /* Load the CON driver */
3010 ConDrvInitialize();
3011
3012 #endif
3013
3014 /* Initialize the callback context */
3015 InitializeContext(&DosContext, 0x0070, 0x0000);
3016
3017 /* Register the DOS 32-bit Interrupts */
3018 RegisterDosInt32(0x20, DosInt20h );
3019 RegisterDosInt32(0x21, DosInt21h );
3020 // RegisterDosInt32(0x22, DosInt22h ); // Termination
3021 RegisterDosInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
3022 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
3023 RegisterDosInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
3024 RegisterDosInt32(0x2F, DosInt2Fh );
3025
3026 return TRUE;
3027 }
3028
3029 /* EOF */