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