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