[NTVDM]
[reactos.git] / subsystems / ntvdm / dos.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: dos.c
5 * PURPOSE: VDM DOS Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #include "dos.h"
12 #include "bios.h"
13 #include "emulator.h"
14
15 /* PRIVATE VARIABLES **********************************************************/
16
17 static WORD CurrentPsp = SYSTEM_PSP;
18 static DWORD DiskTransferArea;
19
20 /* PRIVATE FUNCTIONS **********************************************************/
21
22 static VOID DosCombineFreeBlocks(WORD StartBlock)
23 {
24 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
25
26 /* If this is the last block or it's not free, quit */
27 if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
28
29 while (TRUE)
30 {
31 /* Get a pointer to the next MCB */
32 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
33
34 /* Check if the next MCB is free */
35 if (NextMcb->OwnerPsp == 0)
36 {
37 /* Combine them */
38 CurrentMcb->Size += NextMcb->Size + 1;
39 CurrentMcb->BlockType = NextMcb->BlockType;
40 NextMcb->BlockType = 'I';
41 }
42 else
43 {
44 /* No more adjoining free blocks */
45 break;
46 }
47 }
48 }
49
50 static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
51 {
52 PCHAR Ptr, SourceBuffer, DestBuffer = NULL;
53 ULONG TotalSize = 0;
54 WORD DestSegment;
55
56 Ptr = SourceBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(SourceSegment, 0));
57
58 /* Calculate the size of the environment block */
59 while (*Ptr)
60 {
61 TotalSize += strlen(Ptr) + 1;
62 Ptr += strlen(Ptr) + 1;
63 }
64 TotalSize++;
65
66 /* Allocate the memory for the environment block */
67 DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
68 if (!DestSegment) return 0;
69
70 Ptr = SourceBuffer;
71
72 DestBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DestSegment, 0));
73 while (*Ptr)
74 {
75 /* Copy the string */
76 strcpy(DestBuffer, Ptr);
77
78 /* Advance to the next string */
79 Ptr += strlen(Ptr) + 1;
80 DestBuffer += strlen(Ptr) + 1;
81 }
82
83 /* Set the final zero */
84 *DestBuffer = 0;
85
86 return DestSegment;
87 }
88
89 static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
90 {
91 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
92
93 /* Just set the owner */
94 Mcb->OwnerPsp = NewOwner;
95 }
96
97 /* PUBLIC FUNCTIONS ***********************************************************/
98
99 WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
100 {
101 WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
102 PDOS_MCB CurrentMcb, NextMcb;
103
104 while (TRUE)
105 {
106 /* Get a pointer to the MCB */
107 CurrentMcb = SEGMENT_TO_MCB(Segment);
108
109 /* Make sure it's valid */
110 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
111 {
112 return 0;
113 }
114
115 /* Only check free blocks */
116 if (CurrentMcb->OwnerPsp != 0) goto Next;
117
118 /* Combine this free block with adjoining free blocks */
119 DosCombineFreeBlocks(Segment);
120
121 /* Update the maximum block size */
122 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
123
124 /* Check if this block is big enough */
125 if (CurrentMcb->Size < Size) goto Next;
126
127 /* It is, update the smallest found so far */
128 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
129 {
130 Result = Segment;
131 }
132
133 Next:
134 /* If this was the last MCB in the chain, quit */
135 if (CurrentMcb->BlockType == 'Z') break;
136
137 /* Otherwise, update the segment and continue */
138 Segment += CurrentMcb->Size + 1;
139 }
140
141 /* If we didn't find a free block, return 0 */
142 if (Result == 0)
143 {
144 if (MaxAvailable) *MaxAvailable = MaxSize;
145 return 0;
146 }
147
148 /* Get a pointer to the MCB */
149 CurrentMcb = SEGMENT_TO_MCB(Result);
150
151 /* Check if the block is larger than requested */
152 if (CurrentMcb->Size > Size)
153 {
154 /* It is, split it into two blocks */
155 NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
156
157 /* Initialize the new MCB structure */
158 NextMcb->BlockType = CurrentMcb->BlockType;
159 NextMcb->Size = CurrentMcb->Size - Size - 1;
160 NextMcb->OwnerPsp = 0;
161
162 /* Update the current block */
163 CurrentMcb->BlockType = 'M';
164 CurrentMcb->Size = Size;
165 }
166
167 /* Take ownership of the block */
168 CurrentMcb->OwnerPsp = CurrentPsp;
169
170 /* Return the segment of the data portion of the block */
171 return Result + 1;
172 }
173
174 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
175 {
176 BOOLEAN Success = TRUE;
177 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
178 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
179
180 /* Make sure this is a valid, allocated block */
181 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
182 {
183 Success = FALSE;
184 goto Done;
185 }
186
187 ReturnSize = Mcb->Size;
188
189 /* Check if we need to expand or contract the block */
190 if (NewSize > Mcb->Size)
191 {
192 /* We can't expand the last block */
193 if (Mcb->BlockType != 'M')
194 {
195 Success = FALSE;
196 goto Done;
197 }
198
199 /* Get the pointer and segment of the next MCB */
200 NextSegment = Segment + Mcb->Size + 1;
201 NextMcb = SEGMENT_TO_MCB(NextSegment);
202
203 /* Make sure the next segment is free */
204 if (NextMcb->OwnerPsp != 0)
205 {
206 Success = FALSE;
207 goto Done;
208 }
209
210 /* Combine this free block with adjoining free blocks */
211 DosCombineFreeBlocks(NextSegment);
212
213 /* Set the maximum possible size of the block */
214 ReturnSize += NextMcb->Size + 1;
215
216 /* Maximize the current block */
217 Mcb->Size = ReturnSize;
218 Mcb->BlockType = NextMcb->BlockType;
219
220 /* Invalidate the next block */
221 NextMcb->BlockType = 'I';
222
223 /* Check if the block is larger than requested */
224 if (Mcb->Size > NewSize)
225 {
226 /* It is, split it into two blocks */
227 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
228
229 /* Initialize the new MCB structure */
230 NextMcb->BlockType = Mcb->BlockType;
231 NextMcb->Size = Mcb->Size - NewSize - 1;
232 NextMcb->OwnerPsp = 0;
233
234 /* Update the current block */
235 Mcb->BlockType = 'M';
236 Mcb->Size = NewSize;
237 }
238 }
239 else if (NewSize < Mcb->Size)
240 {
241 /* Just split the block */
242 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
243 NextMcb->BlockType = Mcb->BlockType;
244 NextMcb->Size = Mcb->Size - NewSize - 1;
245 NextMcb->OwnerPsp = 0;
246
247 /* Update the MCB */
248 Mcb->BlockType = 'M';
249 Mcb->Size = NewSize;
250 }
251
252 Done:
253 /* Check if the operation failed */
254 if (!Success)
255 {
256 /* Return the maximum possible size */
257 if (MaxAvailable) *MaxAvailable = ReturnSize;
258 }
259
260 return Success;
261 }
262
263 BOOLEAN DosFreeMemory(WORD BlockData)
264 {
265 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
266
267 /* Make sure the MCB is valid */
268 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') return FALSE;
269
270 /* Mark the block as free */
271 Mcb->OwnerPsp = 0;
272
273 return TRUE;
274 }
275
276 WORD DosCreateFile(LPCSTR FilePath)
277 {
278 // TODO: NOT IMPLEMENTED
279 return 0;
280 }
281
282 WORD DosOpenFile(LPCSTR FilePath)
283 {
284 // TODO: NOT IMPLEMENTED
285 return 0;
286 }
287
288 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
289 {
290 INT i;
291 PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
292 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
293
294 ZeroMemory(PspBlock, sizeof(DOS_PSP));
295
296 /* Set the exit interrupt */
297 PspBlock->Exit[0] = 0xCD; // int 0x20
298 PspBlock->Exit[1] = 0x20;
299
300 /* Set the program size */
301 PspBlock->MemSize = ProgramSize;
302
303 /* Save the interrupt vectors */
304 PspBlock->TerminateAddress = IntVecTable[0x22];
305 PspBlock->BreakAddress = IntVecTable[0x23];
306 PspBlock->CriticalAddress = IntVecTable[0x24];
307
308 /* Set the parent PSP */
309 PspBlock->ParentPsp = CurrentPsp;
310
311 /* Initialize the handle table */
312 for (i = 0; i < 20; i++) PspBlock->HandleTable[i] = 0xFF;
313
314
315
316 PspBlock->EnvBlock = Environment;
317
318 /* Set the handle table pointers to the internal handle table */
319 PspBlock->HandleTableSize = 20;
320 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
321
322 /* Set the DOS version */
323 PspBlock->DosVersion = DOS_VERSION;
324
325 /* Set the far call opcodes */
326 PspBlock->FarCall[0] = 0xCD; // int 0x21
327 PspBlock->FarCall[1] = 0x21;
328 PspBlock->FarCall[2] = 0xCB; // retf
329
330 /* Set the command line */
331 PspBlock->CommandLineSize = strlen(CommandLine);
332 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
333 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
334 }
335
336 BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
337 {
338 BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
339 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
340 LPBYTE Address = NULL;
341 LPSTR ProgramFilePath, Parameters[128];
342 CHAR CommandLineCopy[128];
343 INT ParamCount = 0;
344 WORD i, Segment = 0, FileSize, ExeSize;
345 PIMAGE_DOS_HEADER Header;
346 PDWORD RelocationTable;
347 PWORD RelocWord;
348
349 /* Save a copy of the command line */
350 strcpy(CommandLineCopy, CommandLine);
351
352 /* Get the file name of the executable */
353 ProgramFilePath = strtok(CommandLineCopy, " \t");
354
355 /* Load the parameters in the local array */
356 while ((ParamCount < 256)
357 && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
358 {
359 ParamCount++;
360 }
361
362 /* Open a handle to the executable */
363 FileHandle = CreateFileA(ProgramFilePath,
364 GENERIC_READ,
365 0,
366 NULL,
367 OPEN_EXISTING,
368 FILE_ATTRIBUTE_NORMAL,
369 NULL);
370 if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
371
372 /* Get the file size */
373 FileSize = GetFileSize(FileHandle, NULL);
374
375 /* Create a mapping object for the file */
376 FileMapping = CreateFileMapping(FileHandle,
377 NULL,
378 PAGE_READONLY,
379 0,
380 0,
381 NULL);
382 if (FileMapping == NULL) goto Cleanup;
383
384 /* Map the file into memory */
385 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
386 if (Address == NULL) goto Cleanup;
387
388 /* Did we get an environment segment? */
389 if (!EnvBlock)
390 {
391 /* Set a flag to know if the environment block was allocated here */
392 AllocatedEnvBlock = TRUE;
393
394 /* No, copy the one from the parent */
395 EnvBlock = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
396 ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
397 : SYSTEM_ENV_BLOCK);
398 }
399
400 /* Check if this is an EXE file or a COM file */
401 if (Address[0] == 'M' && Address[1] == 'Z')
402 {
403 /* EXE file */
404
405 /* Get the MZ header */
406 Header = (PIMAGE_DOS_HEADER)Address;
407
408 // TODO: Verify checksum and executable!
409
410 /* Get the base size of the file, in paragraphs (rounded up) */
411 ExeSize = (((Header->e_cp - 1) << 8) + Header->e_cblp + 0x0F) >> 4;
412
413 /* Loop from the maximum to the minimum number of extra paragraphs */
414 for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--)
415 {
416 /* Try to allocate that much memory */
417 Segment = DosAllocateMemory(ExeSize + (sizeof(DOS_PSP) >> 4) + i, NULL);
418 if (Segment != 0) break;
419 }
420
421 /* Check if at least the lowest allocation was successful */
422 if (Segment == 0) goto Cleanup;
423
424 /* Initialize the PSP */
425 DosInitializePsp(Segment,
426 CommandLine, ExeSize + (sizeof(DOS_PSP) >> 4) + i,
427 EnvBlock);
428
429 /* The process owns its own memory */
430 DosChangeMemoryOwner(Segment, Segment);
431 DosChangeMemoryOwner(EnvBlock, Segment);
432
433 /* Copy the program to Segment:0100 */
434 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
435 + TO_LINEAR(Segment, 0x100)),
436 Address + (Header->e_cparhdr << 4),
437 FileSize - (Header->e_cparhdr << 4));
438
439 /* Get the relocation table */
440 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
441
442 /* Perform relocations */
443 for (i = 0; i < Header->e_crlc; i++)
444 {
445 /* Get a pointer to the word that needs to be patched */
446 RelocWord = (PWORD)((ULONG_PTR)BaseAddress
447 + TO_LINEAR(Segment + HIWORD(RelocationTable[i]),
448 0x100 + LOWORD(RelocationTable[i])));
449
450 /* Add the number of the EXE segment to it */
451 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
452 }
453
454 /* Set the initial segment registers */
455 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
456 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
457
458 /* Set the stack to the location from the header */
459 EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
460 Header->e_sp);
461
462 /* Execute */
463 CurrentPsp = Segment;
464 DiskTransferArea = MAKELONG(0x80, Segment);
465 EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
466 Header->e_ip);
467
468 Success = TRUE;
469 }
470 else
471 {
472 /* COM file */
473
474 /* Allocate memory for the whole program and the PSP */
475 Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4, NULL);
476 if (Segment == 0) goto Cleanup;
477
478 /* Copy the program to Segment:0100 */
479 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
480 + TO_LINEAR(Segment, 0x100)),
481 Address,
482 FileSize);
483
484 /* Initialize the PSP */
485 DosInitializePsp(Segment,
486 CommandLine,
487 (FileSize + sizeof(DOS_PSP)) >> 4,
488 EnvBlock);
489
490 /* Set the initial segment registers */
491 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
492 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
493
494 /* Set the stack to the last word of the segment */
495 EmulatorSetStack(Segment, 0xFFFE);
496
497 /* Execute */
498 CurrentPsp = Segment;
499 DiskTransferArea = MAKELONG(0x80, Segment);
500 EmulatorExecute(Segment, 0x100);
501
502 Success = TRUE;
503 }
504
505 Cleanup:
506 if (!Success)
507 {
508 /* It was not successful, cleanup the DOS memory */
509 if (AllocatedEnvBlock) DosFreeMemory(EnvBlock);
510 if (Segment) DosFreeMemory(Segment);
511 }
512
513 /* Unmap the file*/
514 if (Address != NULL) UnmapViewOfFile(Address);
515
516 /* Close the file mapping object */
517 if (FileMapping != NULL) CloseHandle(FileMapping);
518
519 /* Close the file handle */
520 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
521
522 return Success;
523 }
524
525 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
526 {
527 WORD McbSegment = FIRST_MCB_SEGMENT;
528 PDOS_MCB CurrentMcb;
529 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
530 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
531
532 /* Check if this PSP is it's own parent */
533 if (PspBlock->ParentPsp == Psp) goto Done;
534
535 // TODO: Close all handles opened by the process
536
537 /* Free the memory used by the process */
538 while (TRUE)
539 {
540 /* Get a pointer to the MCB */
541 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
542
543 /* Make sure the MCB is valid */
544 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
545
546 /* If this block was allocated by the process, free it */
547 if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment);
548
549 /* If this was the last block, quit */
550 if (CurrentMcb->BlockType == 'Z') break;
551
552 /* Update the segment and continue */
553 McbSegment += CurrentMcb->Size + 1;
554 }
555
556 Done:
557 /* Restore the interrupt vectors */
558 IntVecTable[0x22] = PspBlock->TerminateAddress;
559 IntVecTable[0x23] = PspBlock->BreakAddress;
560 IntVecTable[0x24] = PspBlock->CriticalAddress;
561
562 /* Update the current PSP */
563 if (Psp == CurrentPsp)
564 {
565 CurrentPsp = PspBlock->ParentPsp;
566 if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
567 }
568
569 /* Return control to the parent process */
570 EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
571 LOWORD(PspBlock->TerminateAddress));
572 }
573
574 CHAR DosReadCharacter()
575 {
576 // TODO: STDIN can be redirected under DOS 2.0+
577 CHAR Character = 0;
578
579 /* A zero value for the character indicates a special key */
580 do Character = BiosGetCharacter();
581 while (!Character);
582
583 return Character;
584 }
585
586 VOID DosPrintCharacter(CHAR Character)
587 {
588 // TODO: STDOUT can be redirected under DOS 2.0+
589 if (Character == '\r') Character = '\n';
590 putchar(Character);
591 }
592
593 VOID DosInt20h(WORD CodeSegment)
594 {
595 /* This is the exit interrupt */
596 DosTerminateProcess(CodeSegment, 0);
597 }
598
599 VOID DosInt21h(WORD CodeSegment)
600 {
601 INT i;
602 CHAR Character;
603 SYSTEMTIME SystemTime;
604 PCHAR String;
605 PDOS_INPUT_BUFFER InputBuffer;
606 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
607 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
608 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
609 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
610 WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
611 WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
612
613 /* Check the value in the AH register */
614 switch (HIBYTE(Eax))
615 {
616 /* Terminate Program */
617 case 0x00:
618 {
619 DosTerminateProcess(CodeSegment, 0);
620 break;
621 }
622
623 /* Read Character And Echo */
624 case 0x01:
625 {
626 Character = DosReadCharacter();
627 DosPrintCharacter(Character);
628 EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
629 break;
630 }
631
632 /* Print Character */
633 case 0x02:
634 {
635 DosPrintCharacter(LOBYTE(Edx));
636 break;
637 }
638
639 /* Read Character Without Echo */
640 case 0x08:
641 {
642 EmulatorSetRegister(EMULATOR_REG_AX,
643 (Eax & 0xFFFFFF00) | DosReadCharacter());
644 break;
645 }
646
647 /* Print String */
648 case 0x09:
649 {
650 String = (PCHAR)((ULONG_PTR)BaseAddress
651 + TO_LINEAR(DataSegment, LOWORD(Edx)));
652
653 while ((*String) != '$')
654 {
655 DosPrintCharacter(*String);
656 String++;
657 }
658
659 break;
660 }
661
662 /* Read Buffered Input */
663 case 0x0A:
664 {
665 InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
666 + TO_LINEAR(DataSegment,
667 LOWORD(Edx)));
668
669 InputBuffer->Length = 0;
670 for (i = 0; i < InputBuffer->MaxLength; i ++)
671 {
672 Character = DosReadCharacter();
673 DosPrintCharacter(Character);
674 InputBuffer->Buffer[InputBuffer->Length] = Character;
675 if (Character == '\r') break;
676 InputBuffer->Length++;
677 }
678
679 break;
680 }
681
682 /* Set Disk Transfer Area */
683 case 0x1A:
684 {
685 DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment);
686 break;
687 }
688
689 /* Set Interrupt Vector */
690 case 0x25:
691 {
692 DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment);
693
694 /* Write the new far pointer to the IDT */
695 ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer;
696
697 break;
698 }
699
700 /* Get system date */
701 case 0x2A:
702 {
703 GetLocalTime(&SystemTime);
704 EmulatorSetRegister(EMULATOR_REG_CX,
705 (Ecx & 0xFFFF0000) | SystemTime.wYear);
706 EmulatorSetRegister(EMULATOR_REG_DX,
707 (Edx & 0xFFFF0000)
708 | (SystemTime.wMonth << 8)
709 | SystemTime.wDay);
710 EmulatorSetRegister(EMULATOR_REG_AX,
711 (Eax & 0xFFFFFF00) | SystemTime.wDayOfWeek);
712 break;
713 }
714
715 /* Set system date */
716 case 0x2B:
717 {
718 GetLocalTime(&SystemTime);
719 SystemTime.wYear = LOWORD(Ecx);
720 SystemTime.wMonth = HIBYTE(Edx);
721 SystemTime.wDay = LOBYTE(Edx);
722
723 if (SetLocalTime(&SystemTime))
724 {
725 /* Return success */
726 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
727 }
728 else
729 {
730 /* Return failure */
731 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
732 }
733
734 break;
735 }
736
737 /* Get system time */
738 case 0x2C:
739 {
740 GetLocalTime(&SystemTime);
741 EmulatorSetRegister(EMULATOR_REG_CX,
742 (Ecx & 0xFFFF0000)
743 | (SystemTime.wHour << 8)
744 | SystemTime.wMinute);
745 EmulatorSetRegister(EMULATOR_REG_DX,
746 (Edx & 0xFFFF0000)
747 | (SystemTime.wSecond << 8)
748 | (SystemTime.wMilliseconds / 10));
749 break;
750 }
751
752 /* Set system time */
753 case 0x2D:
754 {
755 GetLocalTime(&SystemTime);
756 SystemTime.wHour = HIBYTE(Ecx);
757 SystemTime.wMinute = LOBYTE(Ecx);
758 SystemTime.wSecond = HIBYTE(Edx);
759 SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
760
761 if (SetLocalTime(&SystemTime))
762 {
763 /* Return success */
764 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
765 }
766 else
767 {
768 /* Return failure */
769 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
770 }
771
772 break;
773 }
774
775 /* Get Disk Transfer Area */
776 case 0x2F:
777 {
778 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea));
779 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea));
780
781 break;
782 }
783
784 /* Get DOS Version */
785 case 0x30:
786 {
787 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
788
789 EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion);
790 break;
791 }
792
793 /* Get Interrupt Vector */
794 case 0x35:
795 {
796 DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)];
797
798 /* Read the address from the IDT into ES:BX */
799 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer));
800 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer));
801
802 break;
803 }
804
805 /* Create Directory */
806 case 0x39:
807 {
808 String = (PCHAR)((ULONG_PTR)BaseAddress
809 + TO_LINEAR(DataSegment, LOWORD(Edx)));
810
811 if (CreateDirectoryA(String, NULL))
812 {
813 EmulatorClearFlag(EMULATOR_FLAG_CF);
814 }
815 else
816 {
817 EmulatorSetFlag(EMULATOR_FLAG_CF);
818 EmulatorSetRegister(EMULATOR_REG_AX,
819 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
820 }
821
822 break;
823 }
824
825 /* Remove Directory */
826 case 0x3A:
827 {
828 String = (PCHAR)((ULONG_PTR)BaseAddress
829 + TO_LINEAR(DataSegment, LOWORD(Edx)));
830
831 if (RemoveDirectoryA(String))
832 {
833 EmulatorClearFlag(EMULATOR_FLAG_CF);
834 }
835 else
836 {
837 EmulatorSetFlag(EMULATOR_FLAG_CF);
838 EmulatorSetRegister(EMULATOR_REG_AX,
839 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
840 }
841
842
843 break;
844 }
845
846 /* Set Current Directory */
847 case 0x3B:
848 {
849 String = (PCHAR)((ULONG_PTR)BaseAddress
850 + TO_LINEAR(DataSegment, LOWORD(Edx)));
851
852 if (SetCurrentDirectoryA(String))
853 {
854 EmulatorClearFlag(EMULATOR_FLAG_CF);
855 }
856 else
857 {
858 EmulatorSetFlag(EMULATOR_FLAG_CF);
859 EmulatorSetRegister(EMULATOR_REG_AX,
860 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
861 }
862
863 break;
864 }
865
866 /* Allocate Memory */
867 case 0x48:
868 {
869 WORD MaxAvailable = 0;
870 WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
871
872 if (Segment != 0)
873 {
874 EmulatorSetRegister(EMULATOR_REG_AX, Segment);
875 EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
876 EmulatorClearFlag(EMULATOR_FLAG_CF);
877 }
878 else EmulatorSetFlag(EMULATOR_FLAG_CF);
879
880 break;
881 }
882
883 /* Free Memory */
884 case 0x49:
885 {
886 if (DosFreeMemory(ExtSegment))
887 {
888 EmulatorClearFlag(EMULATOR_FLAG_CF);
889 }
890 else EmulatorSetFlag(EMULATOR_FLAG_CF);
891
892 break;
893 }
894
895 /* Resize Memory Block */
896 case 0x4A:
897 {
898 WORD Size;
899
900 if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size))
901 {
902 EmulatorClearFlag(EMULATOR_FLAG_CF);
903 }
904 else
905 {
906 EmulatorSetFlag(EMULATOR_FLAG_CF);
907 EmulatorSetRegister(EMULATOR_REG_BX, Size);
908 }
909
910 break;
911 }
912
913 /* Terminate With Return Code */
914 case 0x4C:
915 {
916 DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
917 break;
918 }
919
920 /* Unsupported */
921 default:
922 {
923 DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
924 EmulatorSetFlag(EMULATOR_FLAG_CF);
925 }
926 }
927 }
928
929 VOID DosBreakInterrupt()
930 {
931 VdmRunning = FALSE;
932 }
933
934 BOOLEAN DosInitialize()
935 {
936 PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
937 FILE *Stream;
938 WCHAR Buffer[256];
939 LPWSTR SourcePtr, Environment;
940 LPSTR AsciiString;
941 LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
942 DWORD AsciiSize;
943
944 /* Initialize the MCB */
945 Mcb->BlockType = 'Z';
946 Mcb->Size = (WORD)USER_MEMORY_SIZE;
947 Mcb->OwnerPsp = 0;
948
949 /* Get the environment strings */
950 SourcePtr = Environment = GetEnvironmentStringsW();
951 if (Environment == NULL) return FALSE;
952
953 /* Fill the DOS system environment block */
954 while (*SourcePtr)
955 {
956 /* Get the size of the ASCII string */
957 AsciiSize = WideCharToMultiByte(CP_ACP,
958 0,
959 SourcePtr,
960 -1,
961 NULL,
962 0,
963 NULL,
964 NULL);
965
966 /* Allocate memory for the ASCII string */
967 AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
968 if (AsciiString == NULL)
969 {
970 FreeEnvironmentStringsW(Environment);
971 return FALSE;
972 }
973
974 /* Convert to ASCII */
975 WideCharToMultiByte(CP_ACP,
976 0,
977 SourcePtr,
978 -1,
979 AsciiString,
980 AsciiSize,
981 NULL,
982 NULL);
983
984 /* Copy the string into DOS memory */
985 strcpy(DestPtr, AsciiString);
986
987 /* Free the memory */
988 HeapFree(GetProcessHeap(), 0, AsciiString);
989
990 /* Move to the next string */
991 SourcePtr += wcslen(SourcePtr) + 1;
992 DestPtr += strlen(AsciiString) + 1;
993 }
994
995 /* Free the memory allocated for environment strings */
996 FreeEnvironmentStringsW(Environment);
997
998 /* Read CONFIG.SYS */
999 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
1000 if (Stream != NULL)
1001 {
1002 while (fgetws(Buffer, 256, Stream))
1003 {
1004 // TODO: Parse the line
1005 }
1006 fclose(Stream);
1007 }
1008
1009 return TRUE;
1010 }
1011
1012 /* EOF */