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