b8cc55b940ff1279d95d4d1ebe72e53913f49042
[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 /* Parse Filename into FCB */
1772 case 0x29:
1773 {
1774 PCHAR FileName = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
1775 PDOS_FCB Fcb = (PDOS_FCB)SEG_OFF_TO_PTR(getES(), getDI());
1776 BYTE Options = getAL();
1777 INT i;
1778 CHAR FillChar = ' ';
1779
1780 if (FileName[1] == ':')
1781 {
1782 /* Set the drive number */
1783 Fcb->DriveNumber = RtlUpperChar(FileName[0]) - 'A' + 1;
1784
1785 /* Skip to the file name part */
1786 FileName += 2;
1787 }
1788 else
1789 {
1790 /* No drive number specified */
1791 if (Options & (1 << 1)) Fcb->DriveNumber = CurrentDrive + 1;
1792 else Fcb->DriveNumber = 0;
1793 }
1794
1795 /* Parse the file name */
1796 i = 0;
1797 while ((*FileName > 0x20) && (i < 8))
1798 {
1799 if (*FileName == '.') break;
1800 else if (*FileName == '*')
1801 {
1802 FillChar = '?';
1803 break;
1804 }
1805
1806 Fcb->FileName[i++] = RtlUpperChar(*FileName++);
1807 }
1808
1809 /* Fill the whole field with blanks only if bit 2 is not set */
1810 if ((FillChar != ' ') || (i != 0) || !(Options & (1 << 2)))
1811 {
1812 for (; i < 8; i++) Fcb->FileName[i] = FillChar;
1813 }
1814
1815 /* Skip to the extension part */
1816 while (*FileName > 0x20 && *FileName != '.') FileName++;
1817 if (*FileName == '.') FileName++;
1818
1819 /* Now parse the extension */
1820 i = 0;
1821 FillChar = ' ';
1822
1823 while ((*FileName > 0x20) && (i < 3))
1824 {
1825 if (*FileName == '*')
1826 {
1827 FillChar = '?';
1828 break;
1829 }
1830
1831 Fcb->FileExt[i++] = RtlUpperChar(*FileName++);
1832 }
1833
1834 /* Fill the whole field with blanks only if bit 3 is not set */
1835 if ((FillChar != ' ') || (i != 0) || !(Options & (1 << 3)))
1836 {
1837 for (; i < 3; i++) Fcb->FileExt[i] = FillChar;
1838 }
1839
1840 break;
1841 }
1842
1843 /* Get System Date */
1844 case 0x2A:
1845 {
1846 GetLocalTime(&SystemTime);
1847 setCX(SystemTime.wYear);
1848 setDX(MAKEWORD(SystemTime.wDay, SystemTime.wMonth));
1849 setAL(SystemTime.wDayOfWeek);
1850 break;
1851 }
1852
1853 /* Set System Date */
1854 case 0x2B:
1855 {
1856 GetLocalTime(&SystemTime);
1857 SystemTime.wYear = getCX();
1858 SystemTime.wMonth = getDH();
1859 SystemTime.wDay = getDL();
1860
1861 /* Return success or failure */
1862 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1863 break;
1864 }
1865
1866 /* Get System Time */
1867 case 0x2C:
1868 {
1869 GetLocalTime(&SystemTime);
1870 setCX(MAKEWORD(SystemTime.wMinute, SystemTime.wHour));
1871 setDX(MAKEWORD(SystemTime.wMilliseconds / 10, SystemTime.wSecond));
1872 break;
1873 }
1874
1875 /* Set System Time */
1876 case 0x2D:
1877 {
1878 GetLocalTime(&SystemTime);
1879 SystemTime.wHour = getCH();
1880 SystemTime.wMinute = getCL();
1881 SystemTime.wSecond = getDH();
1882 SystemTime.wMilliseconds = getDL() * 10; // In hundredths of seconds
1883
1884 /* Return success or failure */
1885 setAL(SetLocalTime(&SystemTime) ? 0x00 : 0xFF);
1886 break;
1887 }
1888
1889 /* Get Disk Transfer Area */
1890 case 0x2F:
1891 {
1892 setES(HIWORD(DiskTransferArea));
1893 setBX(LOWORD(DiskTransferArea));
1894 break;
1895 }
1896
1897 /* Get DOS Version */
1898 case 0x30:
1899 {
1900 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1901
1902 /*
1903 * DOS 2+ - GET DOS VERSION
1904 * See Ralf Brown: http://www.ctyme.com/intr/rb-2711.htm
1905 * for more information.
1906 */
1907
1908 if (LOBYTE(PspBlock->DosVersion) < 5 || getAL() == 0x00)
1909 {
1910 /*
1911 * Return DOS OEM number:
1912 * 0x00 for IBM PC-DOS
1913 * 0x02 for packaged MS-DOS
1914 * 0xFF for NT DOS
1915 */
1916 setBH(0xFF);
1917 }
1918
1919 if (LOBYTE(PspBlock->DosVersion) >= 5 && getAL() == 0x01)
1920 {
1921 /*
1922 * Return version flag:
1923 * 1 << 3 if DOS is in ROM,
1924 * 0 (reserved) if not.
1925 */
1926 setBH(0x00);
1927 }
1928
1929 /* Return DOS 24-bit user serial number in BL:CX */
1930 setBL(0x00);
1931 setCX(0x0000);
1932
1933 /*
1934 * Return DOS version: Minor:Major in AH:AL
1935 * The Windows NT DOS box returns version 5.00, subject to SETVER.
1936 */
1937 setAX(PspBlock->DosVersion);
1938
1939 break;
1940 }
1941
1942 /* Terminate and Stay Resident */
1943 case 0x31:
1944 {
1945 DPRINT1("Process going resident: %u paragraphs kept\n", getDX());
1946 DosTerminateProcess(CurrentPsp, getAL(), getDX());
1947 break;
1948 }
1949
1950 /* Extended functionalities */
1951 case 0x33:
1952 {
1953 if (getAL() == 0x06)
1954 {
1955 /*
1956 * DOS 5+ - GET TRUE VERSION NUMBER
1957 * This function always returns the true version number, unlike
1958 * AH=30h, whose return value may be changed with SETVER.
1959 * See Ralf Brown: http://www.ctyme.com/intr/rb-2730.htm
1960 * for more information.
1961 */
1962
1963 /*
1964 * Return the true DOS version: Minor:Major in BH:BL
1965 * The Windows NT DOS box returns BX=3205h (version 5.50).
1966 */
1967 setBX(NTDOS_VERSION);
1968
1969 /* DOS revision 0 */
1970 setDL(0x00);
1971
1972 /* Unpatched DOS */
1973 setDH(0x00);
1974 }
1975 // else
1976 // {
1977 // /* Invalid subfunction */
1978 // setAL(0xFF);
1979 // }
1980
1981 break;
1982 }
1983
1984 /* Get Address of InDOS flag */
1985 case 0x34:
1986 {
1987 setES(HIWORD(INDOS_POINTER));
1988 setBX(LOWORD(INDOS_POINTER));
1989
1990 break;
1991 }
1992
1993 /* Get Interrupt Vector */
1994 case 0x35:
1995 {
1996 DWORD FarPointer = ((PDWORD)BaseAddress)[getAL()];
1997
1998 /* Read the address from the IDT into ES:BX */
1999 setES(HIWORD(FarPointer));
2000 setBX(LOWORD(FarPointer));
2001 break;
2002 }
2003
2004 /* SWITCH character - AVAILDEV */
2005 case 0x37:
2006 {
2007 if (getAL() == 0x00)
2008 {
2009 /*
2010 * DOS 2+ - "SWITCHAR" - GET SWITCH CHARACTER
2011 * This setting is ignored by MS-DOS 4.0+.
2012 * MS-DOS 5+ always return AL=00h/DL=2Fh.
2013 * See Ralf Brown: http://www.ctyme.com/intr/rb-2752.htm
2014 * for more information.
2015 */
2016 setDL('/');
2017 setAL(0x00);
2018 }
2019 else if (getAL() == 0x01)
2020 {
2021 /*
2022 * DOS 2+ - "SWITCHAR" - SET SWITCH CHARACTER
2023 * This setting is ignored by MS-DOS 5+.
2024 * See Ralf Brown: http://www.ctyme.com/intr/rb-2753.htm
2025 * for more information.
2026 */
2027 // getDL();
2028 setAL(0xFF);
2029 }
2030 else if (getAL() == 0x02)
2031 {
2032 /*
2033 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
2034 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
2035 * for more information.
2036 */
2037 // setDL();
2038 setAL(0xFF);
2039 }
2040 else if (getAL() == 0x03)
2041 {
2042 /*
2043 * DOS 2.x and 3.3+ only - "AVAILDEV" - SPECIFY \DEV\ PREFIX USE
2044 * See Ralf Brown: http://www.ctyme.com/intr/rb-2754.htm
2045 * for more information.
2046 */
2047 // getDL();
2048 setAL(0xFF);
2049 }
2050 else
2051 {
2052 /* Invalid subfunction */
2053 setAL(0xFF);
2054 }
2055
2056 break;
2057 }
2058
2059 /* Get/Set Country-dependent Information */
2060 case 0x38:
2061 {
2062 CountryCodeBuffer = (PDOS_COUNTRY_CODE_BUFFER)SEG_OFF_TO_PTR(getDS(), getDX());
2063
2064 if (getAL() == 0x00)
2065 {
2066 /* Get */
2067 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_IDATE,
2068 &CountryCodeBuffer->TimeFormat,
2069 sizeof(CountryCodeBuffer->TimeFormat) / sizeof(TCHAR));
2070 if (Return == 0)
2071 {
2072 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2073 setAX(LOWORD(GetLastError()));
2074 break;
2075 }
2076
2077 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SCURRENCY,
2078 &CountryCodeBuffer->CurrencySymbol,
2079 sizeof(CountryCodeBuffer->CurrencySymbol) / sizeof(TCHAR));
2080 if (Return == 0)
2081 {
2082 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2083 setAX(LOWORD(GetLastError()));
2084 break;
2085 }
2086
2087 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND,
2088 &CountryCodeBuffer->ThousandSep,
2089 sizeof(CountryCodeBuffer->ThousandSep) / sizeof(TCHAR));
2090 if (Return == 0)
2091 {
2092 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2093 setAX(LOWORD(GetLastError()));
2094 break;
2095 }
2096
2097 Return = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL,
2098 &CountryCodeBuffer->DecimalSep,
2099 sizeof(CountryCodeBuffer->DecimalSep) / sizeof(TCHAR));
2100 if (Return == 0)
2101 {
2102 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2103 setAX(LOWORD(GetLastError()));
2104 break;
2105 }
2106
2107 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2108 }
2109 else
2110 {
2111 // TODO: NOT IMPLEMENTED
2112 UNIMPLEMENTED;
2113 }
2114
2115 break;
2116 }
2117
2118 /* Create Directory */
2119 case 0x39:
2120 {
2121 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2122
2123 if (CreateDirectoryA(String, NULL))
2124 {
2125 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2126 }
2127 else
2128 {
2129 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2130 setAX(LOWORD(GetLastError()));
2131 }
2132
2133 break;
2134 }
2135
2136 /* Remove Directory */
2137 case 0x3A:
2138 {
2139 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2140
2141 if (RemoveDirectoryA(String))
2142 {
2143 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2144 }
2145 else
2146 {
2147 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2148 setAX(LOWORD(GetLastError()));
2149 }
2150
2151 break;
2152 }
2153
2154 /* Set Current Directory */
2155 case 0x3B:
2156 {
2157 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getDX());
2158
2159 if (DosChangeDirectory(String))
2160 {
2161 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2162 }
2163 else
2164 {
2165 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2166 setAX(DosLastError);
2167 }
2168
2169 break;
2170 }
2171
2172 /* Create or Truncate File */
2173 case 0x3C:
2174 {
2175 WORD FileHandle;
2176 WORD ErrorCode = DosCreateFile(&FileHandle,
2177 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2178 CREATE_ALWAYS,
2179 getCX());
2180
2181 if (ErrorCode == ERROR_SUCCESS)
2182 {
2183 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2184 setAX(FileHandle);
2185 }
2186 else
2187 {
2188 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2189 setAX(ErrorCode);
2190 }
2191
2192 break;
2193 }
2194
2195 /* Open File or Device */
2196 case 0x3D:
2197 {
2198 WORD FileHandle;
2199 WORD ErrorCode;
2200 LPCSTR FileName = (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2201 PDOS_DEVICE_NODE Device = DosGetDevice(FileName);
2202
2203 if (Device)
2204 {
2205 FileHandle = DosOpenDevice(Device);
2206 ErrorCode = (FileHandle != INVALID_DOS_HANDLE)
2207 ? ERROR_SUCCESS : ERROR_TOO_MANY_OPEN_FILES;
2208 }
2209 else
2210 {
2211 ErrorCode = DosOpenFile(&FileHandle, FileName, getAL());
2212 }
2213
2214 if (ErrorCode == ERROR_SUCCESS)
2215 {
2216 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2217 setAX(FileHandle);
2218 }
2219 else
2220 {
2221 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2222 setAX(ErrorCode);
2223 }
2224
2225 break;
2226 }
2227
2228 /* Close File or Device */
2229 case 0x3E:
2230 {
2231 if (DosCloseHandle(getBX()))
2232 {
2233 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2234 }
2235 else
2236 {
2237 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2238 setAX(ERROR_INVALID_HANDLE);
2239 }
2240
2241 break;
2242 }
2243
2244 /* Read from File or Device */
2245 case 0x3F:
2246 {
2247 WORD BytesRead = 0;
2248 WORD ErrorCode;
2249
2250 DPRINT("DosReadFile(0x%04X)\n", getBX());
2251
2252 DoEcho = TRUE;
2253 ErrorCode = DosReadFile(getBX(),
2254 MAKELONG(getDX(), getDS()),
2255 getCX(),
2256 &BytesRead);
2257 DoEcho = FALSE;
2258
2259 if (ErrorCode == ERROR_SUCCESS)
2260 {
2261 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2262 setAX(BytesRead);
2263 }
2264 else if (ErrorCode != ERROR_NOT_READY)
2265 {
2266 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2267 setAX(ErrorCode);
2268 }
2269
2270 break;
2271 }
2272
2273 /* Write to File or Device */
2274 case 0x40:
2275 {
2276 WORD BytesWritten = 0;
2277 WORD ErrorCode = DosWriteFile(getBX(),
2278 MAKELONG(getDX(), getDS()),
2279 getCX(),
2280 &BytesWritten);
2281
2282 if (ErrorCode == ERROR_SUCCESS)
2283 {
2284 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2285 setAX(BytesWritten);
2286 }
2287 else
2288 {
2289 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2290 setAX(ErrorCode);
2291 }
2292
2293 break;
2294 }
2295
2296 /* Delete File */
2297 case 0x41:
2298 {
2299 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2300
2301 if (demFileDelete(FileName) == ERROR_SUCCESS)
2302 {
2303 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2304 /*
2305 * See Ralf Brown: http://www.ctyme.com/intr/rb-2797.htm
2306 * "AX destroyed (DOS 3.3) AL seems to be drive of deleted file."
2307 */
2308 setAL(FileName[0] - 'A');
2309 }
2310 else
2311 {
2312 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2313 setAX(GetLastError());
2314 }
2315
2316 break;
2317 }
2318
2319 /* Seek File */
2320 case 0x42:
2321 {
2322 DWORD NewLocation;
2323 WORD ErrorCode = DosSeekFile(getBX(),
2324 MAKELONG(getDX(), getCX()),
2325 getAL(),
2326 &NewLocation);
2327
2328 if (ErrorCode == ERROR_SUCCESS)
2329 {
2330 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2331
2332 /* Return the new offset in DX:AX */
2333 setDX(HIWORD(NewLocation));
2334 setAX(LOWORD(NewLocation));
2335 }
2336 else
2337 {
2338 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2339 setAX(ErrorCode);
2340 }
2341
2342 break;
2343 }
2344
2345 /* Get/Set File Attributes */
2346 case 0x43:
2347 {
2348 DWORD Attributes;
2349 LPSTR FileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2350
2351 if (getAL() == 0x00)
2352 {
2353 /* Get the attributes */
2354 Attributes = GetFileAttributesA(FileName);
2355
2356 /* Check if it failed */
2357 if (Attributes == INVALID_FILE_ATTRIBUTES)
2358 {
2359 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2360 setAX(GetLastError());
2361 }
2362 else
2363 {
2364 /* Return the attributes that DOS can understand */
2365 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2366 setCX(Attributes & 0x00FF);
2367 }
2368 }
2369 else if (getAL() == 0x01)
2370 {
2371 /* Try to set the attributes */
2372 if (SetFileAttributesA(FileName, getCL()))
2373 {
2374 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2375 }
2376 else
2377 {
2378 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2379 setAX(GetLastError());
2380 }
2381 }
2382 else
2383 {
2384 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2385 setAX(ERROR_INVALID_FUNCTION);
2386 }
2387
2388 break;
2389 }
2390
2391 /* IOCTL */
2392 case 0x44:
2393 {
2394 if (DosHandleIoctl(getAL(), getBX()))
2395 {
2396 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2397 }
2398 else
2399 {
2400 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2401 setAX(DosLastError);
2402 }
2403
2404 break;
2405 }
2406
2407 /* Duplicate Handle */
2408 case 0x45:
2409 {
2410 WORD NewHandle;
2411 PDOS_SFT_ENTRY SftEntry = DosGetSftEntry(getBX());
2412
2413 if (SftEntry == NULL || SftEntry->Type == DOS_SFT_ENTRY_NONE)
2414 {
2415 /* The handle is invalid */
2416 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2417 setAX(ERROR_INVALID_HANDLE);
2418 break;
2419 }
2420
2421 /* Open a new handle to the same entry */
2422 switch (SftEntry->Type)
2423 {
2424 case DOS_SFT_ENTRY_WIN32:
2425 {
2426 NewHandle = DosOpenHandle(SftEntry->Handle);
2427 break;
2428 }
2429
2430 case DOS_SFT_ENTRY_DEVICE:
2431 {
2432 NewHandle = DosOpenDevice(SftEntry->DeviceNode);
2433 break;
2434 }
2435
2436 default:
2437 {
2438 /* Shouldn't happen */
2439 ASSERT(FALSE);
2440 }
2441 }
2442
2443 if (NewHandle == INVALID_DOS_HANDLE)
2444 {
2445 /* Too many files open */
2446 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2447 setAX(ERROR_TOO_MANY_OPEN_FILES);
2448 break;
2449 }
2450
2451 /* Return the result */
2452 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2453 setAX(NewHandle);
2454 break;
2455 }
2456
2457 /* Force Duplicate Handle */
2458 case 0x46:
2459 {
2460 if (DosDuplicateHandle(getBX(), getCX()))
2461 {
2462 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2463 }
2464 else
2465 {
2466 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2467 setAX(ERROR_INVALID_HANDLE);
2468 }
2469
2470 break;
2471 }
2472
2473 /* Get Current Directory */
2474 case 0x47:
2475 {
2476 BYTE DriveNumber = getDL();
2477 String = (PCHAR)SEG_OFF_TO_PTR(getDS(), getSI());
2478
2479 /* Get the real drive number */
2480 if (DriveNumber == 0)
2481 {
2482 DriveNumber = CurrentDrive;
2483 }
2484 else
2485 {
2486 /* Decrement DriveNumber since it was 1-based */
2487 DriveNumber--;
2488 }
2489
2490 if (DriveNumber <= LastDrive - 'A')
2491 {
2492 /*
2493 * Copy the current directory into the target buffer.
2494 * It doesn't contain the drive letter and the backslash.
2495 */
2496 strncpy(String, CurrentDirectories[DriveNumber], DOS_DIR_LENGTH);
2497 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2498 setAX(0x0100); // Undocumented, see Ralf Brown: http://www.ctyme.com/intr/rb-2933.htm
2499 }
2500 else
2501 {
2502 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2503 setAX(ERROR_INVALID_DRIVE);
2504 }
2505
2506 break;
2507 }
2508
2509 /* Allocate Memory */
2510 case 0x48:
2511 {
2512 WORD MaxAvailable = 0;
2513 WORD Segment = DosAllocateMemory(getBX(), &MaxAvailable);
2514
2515 if (Segment != 0)
2516 {
2517 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2518 setAX(Segment);
2519 }
2520 else
2521 {
2522 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2523 setAX(DosLastError);
2524 setBX(MaxAvailable);
2525 }
2526
2527 break;
2528 }
2529
2530 /* Free Memory */
2531 case 0x49:
2532 {
2533 if (DosFreeMemory(getES()))
2534 {
2535 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2536 }
2537 else
2538 {
2539 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2540 setAX(ERROR_ARENA_TRASHED);
2541 }
2542
2543 break;
2544 }
2545
2546 /* Resize Memory Block */
2547 case 0x4A:
2548 {
2549 WORD Size;
2550
2551 if (DosResizeMemory(getES(), getBX(), &Size))
2552 {
2553 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2554 }
2555 else
2556 {
2557 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2558 setAX(DosLastError);
2559 setBX(Size);
2560 }
2561
2562 break;
2563 }
2564
2565 #ifndef STANDALONE
2566 /* Execute */
2567 case 0x4B:
2568 {
2569 DOS_EXEC_TYPE LoadType = (DOS_EXEC_TYPE)getAL();
2570 LPSTR ProgramName = SEG_OFF_TO_PTR(getDS(), getDX());
2571 PDOS_EXEC_PARAM_BLOCK ParamBlock = SEG_OFF_TO_PTR(getES(), getBX());
2572 DWORD ReturnAddress = MAKELONG(Stack[STACK_IP], Stack[STACK_CS]);
2573 WORD ErrorCode;
2574
2575 if (LoadType != DOS_LOAD_OVERLAY)
2576 {
2577 ErrorCode = DosCreateProcess(LoadType, ProgramName, ParamBlock, ReturnAddress);
2578 }
2579 else
2580 {
2581 ErrorCode = DosLoadExecutable(DOS_LOAD_OVERLAY,
2582 ProgramName,
2583 FAR_POINTER(ParamBlock->CommandLine),
2584 SEG_OFF_TO_PTR(ParamBlock->Environment, 0),
2585 ReturnAddress,
2586 NULL,
2587 NULL);
2588 }
2589
2590 if (ErrorCode == ERROR_SUCCESS)
2591 {
2592 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2593 }
2594 else
2595 {
2596 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2597 setAX(ErrorCode);
2598 }
2599
2600 break;
2601 }
2602 #endif
2603
2604 /* Terminate With Return Code */
2605 case 0x4C:
2606 {
2607 DosTerminateProcess(CurrentPsp, getAL(), 0);
2608 break;
2609 }
2610
2611 /* Get Return Code (ERRORLEVEL) */
2612 case 0x4D:
2613 {
2614 /*
2615 * According to Ralf Brown: http://www.ctyme.com/intr/rb-2976.htm
2616 * DosErrorLevel is cleared after being read by this function.
2617 */
2618 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2619 setAX(DosErrorLevel);
2620 DosErrorLevel = 0x0000; // Clear it
2621 break;
2622 }
2623
2624 /* Find First File */
2625 case 0x4E:
2626 {
2627 WORD Result = (WORD)demFileFindFirst(FAR_POINTER(DiskTransferArea),
2628 SEG_OFF_TO_PTR(getDS(), getDX()),
2629 getCX());
2630
2631 setAX(Result);
2632
2633 if (Result == ERROR_SUCCESS)
2634 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2635 else
2636 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2637
2638 break;
2639 }
2640
2641 /* Find Next File */
2642 case 0x4F:
2643 {
2644 WORD Result = (WORD)demFileFindNext(FAR_POINTER(DiskTransferArea));
2645
2646 setAX(Result);
2647
2648 if (Result == ERROR_SUCCESS)
2649 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2650 else
2651 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2652
2653 break;
2654 }
2655
2656 /* Internal - Set Current Process ID (Set PSP Address) */
2657 case 0x50:
2658 {
2659 // FIXME: Is it really what it's done ??
2660 CurrentPsp = getBX();
2661 break;
2662 }
2663
2664 /* Internal - Get Current Process ID (Get PSP Address) */
2665 case 0x51:
2666 /* Get Current PSP Address */
2667 case 0x62:
2668 {
2669 /*
2670 * Undocumented AH=51h is identical to the documented AH=62h.
2671 * See Ralf Brown: http://www.ctyme.com/intr/rb-2982.htm
2672 * and http://www.ctyme.com/intr/rb-3140.htm
2673 * for more information.
2674 */
2675 setBX(CurrentPsp);
2676 break;
2677 }
2678
2679 /* Internal - Get "List of lists" (SYSVARS) */
2680 case 0x52:
2681 {
2682 /*
2683 * On return, ES points at the DOS data segment (see also INT 2F/AX=1203h).
2684 * See Ralf Brown: http://www.ctyme.com/intr/rb-2983.htm
2685 * for more information.
2686 */
2687
2688 /* Return the DOS "list of lists" in ES:BX */
2689 setES(0x0000);
2690 setBX(0x0000);
2691
2692 DPRINT1("INT 21h, AH=52h: This application requires the internal DOS List of lists (SYSVARS). UNIMPLEMENTED\n");
2693 break;
2694 }
2695
2696 /* Rename File */
2697 case 0x56:
2698 {
2699 LPSTR ExistingFileName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2700 LPSTR NewFileName = (LPSTR)SEG_OFF_TO_PTR(getES(), getDI());
2701
2702 /*
2703 * See Ralf Brown: http://www.ctyme.com/intr/rb-2990.htm
2704 * for more information.
2705 */
2706
2707 if (MoveFileA(ExistingFileName, NewFileName))
2708 {
2709 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2710 }
2711 else
2712 {
2713 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2714 setAX(GetLastError());
2715 }
2716
2717 break;
2718 }
2719
2720 /* Get/Set Memory Management Options */
2721 case 0x58:
2722 {
2723 if (getAL() == 0x00)
2724 {
2725 /* Get allocation strategy */
2726 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2727 setAX(DosAllocStrategy);
2728 }
2729 else if (getAL() == 0x01)
2730 {
2731 /* Set allocation strategy */
2732
2733 if ((getBL() & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2734 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
2735 {
2736 /* Can't set both */
2737 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2738 setAX(ERROR_INVALID_PARAMETER);
2739 break;
2740 }
2741
2742 if ((getBL() & 0x3F) > DOS_ALLOC_LAST_FIT)
2743 {
2744 /* Invalid allocation strategy */
2745 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2746 setAX(ERROR_INVALID_PARAMETER);
2747 break;
2748 }
2749
2750 DosAllocStrategy = getBL();
2751 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2752 }
2753 else if (getAL() == 0x02)
2754 {
2755 /* Get UMB link state */
2756 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2757 setAL(DosUmbLinked ? 0x01 : 0x00);
2758 }
2759 else if (getAL() == 0x03)
2760 {
2761 /* Set UMB link state */
2762 if (getBX()) DosLinkUmb();
2763 else DosUnlinkUmb();
2764 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2765 }
2766 else
2767 {
2768 /* Invalid or unsupported function */
2769 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2770 setAX(ERROR_INVALID_FUNCTION);
2771 }
2772
2773 break;
2774 }
2775
2776 /* Get Extended Error Information */
2777 case 0x59:
2778 {
2779 DPRINT1("INT 21h, AH = 59h, BX = %04Xh - Get Extended Error Information is UNIMPLEMENTED\n",
2780 getBX());
2781 break;
2782 }
2783
2784 /* Create Temporary File */
2785 case 0x5A:
2786 {
2787 LPSTR PathName = (LPSTR)SEG_OFF_TO_PTR(getDS(), getDX());
2788 LPSTR FileName = PathName; // The buffer for the path and the full file name is the same.
2789 UINT uRetVal;
2790 WORD FileHandle;
2791 WORD ErrorCode;
2792
2793 /*
2794 * See Ralf Brown: http://www.ctyme.com/intr/rb-3014.htm
2795 * for more information.
2796 */
2797
2798 // FIXME: Check for buffer validity?
2799 // It should be a ASCIZ path ending with a '\' + 13 zero bytes
2800 // to receive the generated filename.
2801
2802 /* First create the temporary file */
2803 uRetVal = GetTempFileNameA(PathName, NULL, 0, FileName);
2804 if (uRetVal == 0)
2805 {
2806 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2807 setAX(GetLastError());
2808 break;
2809 }
2810
2811 /* Now try to open it in read/write access */
2812 ErrorCode = DosOpenFile(&FileHandle, FileName, 2);
2813 if (ErrorCode == ERROR_SUCCESS)
2814 {
2815 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2816 setAX(FileHandle);
2817 }
2818 else
2819 {
2820 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2821 setAX(ErrorCode);
2822 }
2823
2824 break;
2825 }
2826
2827 /* Create New File */
2828 case 0x5B:
2829 {
2830 WORD FileHandle;
2831 WORD ErrorCode = DosCreateFile(&FileHandle,
2832 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getDX()),
2833 CREATE_NEW,
2834 getCX());
2835
2836 if (ErrorCode == ERROR_SUCCESS)
2837 {
2838 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2839 setAX(FileHandle);
2840 }
2841 else
2842 {
2843 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2844 setAX(ErrorCode);
2845 }
2846
2847 break;
2848 }
2849
2850 /* Lock/Unlock Region of File */
2851 case 0x5C:
2852 {
2853 PDOS_SFT_ENTRY SftEntry = DosGetSftEntry(getBX());
2854
2855 if (SftEntry == NULL || SftEntry->Type != DOS_SFT_ENTRY_WIN32)
2856 {
2857 /* The handle is invalid */
2858 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2859 setAX(ERROR_INVALID_HANDLE);
2860 break;
2861 }
2862
2863 if (getAL() == 0x00)
2864 {
2865 /* Lock region of file */
2866 if (LockFile(SftEntry->Handle,
2867 MAKELONG(getCX(), getDX()), 0,
2868 MAKELONG(getSI(), getDI()), 0))
2869 {
2870 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2871 }
2872 else
2873 {
2874 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2875 setAX(GetLastError());
2876 }
2877 }
2878 else if (getAL() == 0x01)
2879 {
2880 /* Unlock region of file */
2881 if (UnlockFile(SftEntry->Handle,
2882 MAKELONG(getCX(), getDX()), 0,
2883 MAKELONG(getSI(), getDI()), 0))
2884 {
2885 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2886 }
2887 else
2888 {
2889 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2890 setAX(GetLastError());
2891 }
2892 }
2893 else
2894 {
2895 /* Invalid subfunction */
2896 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2897 setAX(ERROR_INVALID_FUNCTION);
2898 }
2899
2900 break;
2901 }
2902
2903 /* Canonicalize File Name or Path */
2904 case 0x60:
2905 {
2906 /*
2907 * See Ralf Brown: http://www.ctyme.com/intr/rb-3137.htm
2908 * for more information.
2909 */
2910
2911 /*
2912 * We suppose that the DOS app gave to us a valid
2913 * 128-byte long buffer for the canonicalized name.
2914 */
2915 DWORD dwRetVal = GetFullPathNameA(SEG_OFF_TO_PTR(getDS(), getSI()),
2916 128,
2917 SEG_OFF_TO_PTR(getES(), getDI()),
2918 NULL);
2919 if (dwRetVal == 0)
2920 {
2921 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2922 setAX(GetLastError());
2923 }
2924 else
2925 {
2926 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2927 setAX(0x0000);
2928 }
2929
2930 // FIXME: Convert the full path name into short version.
2931 // We cannot reliably use GetShortPathName, because it fails
2932 // if the path name given doesn't exist. However this DOS
2933 // function AH=60h should be able to work even for non-existing
2934 // path and file names.
2935
2936 break;
2937 }
2938
2939 /* Set Handle Count */
2940 case 0x67:
2941 {
2942 if (!DosResizeHandleTable(getBX()))
2943 {
2944 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2945 setAX(DosLastError);
2946 }
2947 else Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2948
2949 break;
2950 }
2951
2952 /* Commit File */
2953 case 0x68:
2954 case 0x6A:
2955 {
2956 /*
2957 * Function 6Ah is identical to function 68h,
2958 * and sets AH to 68h if success.
2959 * See Ralf Brown: http://www.ctyme.com/intr/rb-3176.htm
2960 * for more information.
2961 */
2962 setAH(0x68);
2963
2964 if (DosFlushFileBuffers(getBX()))
2965 {
2966 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
2967 }
2968 else
2969 {
2970 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2971 setAX(GetLastError());
2972 }
2973
2974 break;
2975 }
2976
2977 /* Extended Open/Create */
2978 case 0x6C:
2979 {
2980 WORD FileHandle;
2981 WORD CreationStatus;
2982 WORD ErrorCode;
2983
2984 /* Check for AL == 00 */
2985 if (getAL() != 0x00)
2986 {
2987 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
2988 setAX(ERROR_INVALID_FUNCTION);
2989 break;
2990 }
2991
2992 /*
2993 * See Ralf Brown: http://www.ctyme.com/intr/rb-3179.htm
2994 * for the full detailed description.
2995 *
2996 * WARNING: BH contains some extended flags that are NOT SUPPORTED.
2997 */
2998
2999 ErrorCode = DosCreateFileEx(&FileHandle,
3000 &CreationStatus,
3001 (LPCSTR)SEG_OFF_TO_PTR(getDS(), getSI()),
3002 getBL(),
3003 getDL(),
3004 getCX());
3005
3006 if (ErrorCode == ERROR_SUCCESS)
3007 {
3008 Stack[STACK_FLAGS] &= ~EMULATOR_FLAG_CF;
3009 setCX(CreationStatus);
3010 setAX(FileHandle);
3011 }
3012 else
3013 {
3014 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
3015 setAX(ErrorCode);
3016 }
3017
3018 break;
3019 }
3020
3021 /* Unsupported */
3022 default:
3023 {
3024 DPRINT1("DOS Function INT 0x21, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
3025 getAH(), getAL());
3026
3027 setAL(0); // Some functions expect AL to be 0 when it's not supported.
3028 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
3029 }
3030 }
3031
3032 (*InDos)--;
3033 }
3034
3035 VOID WINAPI DosBreakInterrupt(LPWORD Stack)
3036 {
3037 /* Set CF to terminate the running process */
3038 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
3039 }
3040
3041 VOID WINAPI DosFastConOut(LPWORD Stack)
3042 {
3043 /*
3044 * This is the DOS 2+ Fast Console Output Interrupt.
3045 * The default handler under DOS 2.x and 3.x simply calls INT 10h/AH=0Eh.
3046 *
3047 * See Ralf Brown: http://www.ctyme.com/intr/rb-4124.htm
3048 * for more information.
3049 */
3050
3051 /* Save AX and BX */
3052 USHORT AX = getAX();
3053 USHORT BX = getBX();
3054
3055 /*
3056 * Set the parameters:
3057 * AL contains the character to print (already set),
3058 * BL contains the character attribute,
3059 * BH contains the video page to use.
3060 */
3061 setBL(DOS_CHAR_ATTRIBUTE);
3062 setBH(Bda->VideoPage);
3063
3064 /* Call the BIOS INT 10h, AH=0Eh "Teletype Output" */
3065 setAH(0x0E);
3066 Int32Call(&DosContext, BIOS_VIDEO_INTERRUPT);
3067
3068 /* Restore AX and BX */
3069 setBX(BX);
3070 setAX(AX);
3071 }
3072
3073 VOID WINAPI DosInt2Fh(LPWORD Stack)
3074 {
3075 switch (getAH())
3076 {
3077 /* Extended Memory Specification */
3078 case 0x43:
3079 {
3080 DWORD DriverEntry;
3081 if (!XmsGetDriverEntry(&DriverEntry)) break;
3082
3083 if (getAL() == 0x00)
3084 {
3085 /* The driver is loaded */
3086 setAL(0x80);
3087 }
3088 else if (getAL() == 0x10)
3089 {
3090 setES(HIWORD(DriverEntry));
3091 setBX(LOWORD(DriverEntry));
3092 }
3093 else
3094 {
3095 DPRINT1("Unknown DOS XMS Function: INT 0x2F, AH = 43h, AL = %xh\n", getAL());
3096 }
3097
3098 break;
3099 }
3100
3101 default:
3102 {
3103 DPRINT1("DOS Internal System Function INT 0x2F, AH = %xh, AL = %xh NOT IMPLEMENTED!\n",
3104 getAH(), getAL());
3105 Stack[STACK_FLAGS] |= EMULATOR_FLAG_CF;
3106 }
3107 }
3108 }
3109
3110 BOOLEAN DosKRNLInitialize(VOID)
3111 {
3112
3113 #if 1
3114
3115 UCHAR i;
3116 CHAR CurrentDirectory[MAX_PATH];
3117 CHAR DosDirectory[DOS_DIR_LENGTH];
3118 LPSTR Path;
3119
3120 FILE *Stream;
3121 WCHAR Buffer[256];
3122
3123 /* Setup the InDOS flag */
3124 InDos = (PBYTE)FAR_POINTER(INDOS_POINTER);
3125 *InDos = 0;
3126
3127 /* Clear the current directory buffer */
3128 RtlZeroMemory(CurrentDirectories, sizeof(CurrentDirectories));
3129
3130 /* Get the current directory */
3131 if (!GetCurrentDirectoryA(MAX_PATH, CurrentDirectory))
3132 {
3133 // TODO: Use some kind of default path?
3134 return FALSE;
3135 }
3136
3137 /* Convert that to a DOS path */
3138 if (!GetShortPathNameA(CurrentDirectory, DosDirectory, DOS_DIR_LENGTH))
3139 {
3140 // TODO: Use some kind of default path?
3141 return FALSE;
3142 }
3143
3144 /* Set the drive */
3145 CurrentDrive = DosDirectory[0] - 'A';
3146
3147 /* Get the directory part of the path */
3148 Path = strchr(DosDirectory, '\\');
3149 if (Path != NULL)
3150 {
3151 /* Skip the backslash */
3152 Path++;
3153 }
3154
3155 /* Set the directory */
3156 if (Path != NULL)
3157 {
3158 strncpy(CurrentDirectories[CurrentDrive], Path, DOS_DIR_LENGTH);
3159 }
3160
3161 /* Read CONFIG.SYS */
3162 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
3163 if (Stream != NULL)
3164 {
3165 while (fgetws(Buffer, 256, Stream))
3166 {
3167 // TODO: Parse the line
3168 }
3169 fclose(Stream);
3170 }
3171
3172 /* Initialize the SFT */
3173 for (i = 0; i < DOS_SFT_SIZE; i++)
3174 {
3175 DosSystemFileTable[i].Type = DOS_SFT_ENTRY_NONE;
3176 DosSystemFileTable[i].RefCount = 0;
3177 }
3178
3179 #endif
3180
3181 /* Initialize the callback context */
3182 InitializeContext(&DosContext, 0x0070, 0x0000);
3183
3184 /* Register the DOS 32-bit Interrupts */
3185 RegisterDosInt32(0x20, DosInt20h );
3186 RegisterDosInt32(0x21, DosInt21h );
3187 // RegisterDosInt32(0x22, DosInt22h ); // Termination
3188 RegisterDosInt32(0x23, DosBreakInterrupt); // Ctrl-C / Ctrl-Break
3189 // RegisterDosInt32(0x24, DosInt24h ); // Critical Error
3190 RegisterDosInt32(0x29, DosFastConOut ); // DOS 2+ Fast Console Output
3191 RegisterDosInt32(0x2F, DosInt2Fh );
3192
3193 /* Load the EMS driver */
3194 if (!EmsDrvInitialize(EMS_TOTAL_PAGES))
3195 {
3196 DPRINT1("Could not initialize EMS. EMS will not be available.\n"
3197 "Try reducing the number of EMS pages.\n");
3198 }
3199
3200 /* Load the XMS driver (HIMEM) */
3201 XmsInitialize();
3202
3203 /* Load the CON driver */
3204 ConDrvInitialize();
3205
3206 return TRUE;
3207 }
3208
3209 /* EOF */