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