[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 /* Did we get an environment segment? */
315 if (!Environment)
316 {
317 /* No, copy the one from the parent */
318 Environment = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
319 ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
320 : SYSTEM_ENV_BLOCK);
321 }
322
323 PspBlock->EnvBlock = Environment;
324
325 /* Set the handle table pointers to the internal handle table */
326 PspBlock->HandleTableSize = 20;
327 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
328
329 /* Set the DOS version */
330 PspBlock->DosVersion = DOS_VERSION;
331
332 /* Set the far call opcodes */
333 PspBlock->FarCall[0] = 0xCD; // int 0x21
334 PspBlock->FarCall[1] = 0x21;
335 PspBlock->FarCall[2] = 0xCB; // retf
336
337 /* Set the command line */
338 PspBlock->CommandLineSize = strlen(CommandLine);
339 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
340 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
341 }
342
343 BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
344 {
345 BOOLEAN Success = FALSE;
346 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
347 LPBYTE Address = NULL;
348 LPSTR ProgramFilePath, Parameters[128];
349 CHAR CommandLineCopy[128];
350 INT ParamCount = 0;
351 WORD i, Segment, FileSize, ExeSize;
352 PIMAGE_DOS_HEADER Header;
353 PDWORD RelocationTable;
354 PWORD RelocWord;
355
356 /* Save a copy of the command line */
357 strcpy(CommandLineCopy, CommandLine);
358
359 /* Get the file name of the executable */
360 ProgramFilePath = strtok(CommandLineCopy, " \t");
361
362 /* Load the parameters in the local array */
363 while ((ParamCount < 256)
364 && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
365 {
366 ParamCount++;
367 }
368
369 /* Open a handle to the executable */
370 FileHandle = CreateFileA(ProgramFilePath,
371 GENERIC_READ,
372 0,
373 NULL,
374 OPEN_EXISTING,
375 FILE_ATTRIBUTE_NORMAL,
376 NULL);
377 if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
378
379 /* Get the file size */
380 FileSize = GetFileSize(FileHandle, NULL);
381
382 /* Create a mapping object for the file */
383 FileMapping = CreateFileMapping(FileHandle,
384 NULL,
385 PAGE_READONLY,
386 0,
387 0,
388 NULL);
389 if (FileMapping == NULL) goto Cleanup;
390
391 /* Map the file into memory */
392 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
393 if (Address == NULL) goto Cleanup;
394
395 /* Check if this is an EXE file or a COM file */
396 if (Address[0] == 'M' && Address[1] == 'Z')
397 {
398 /* EXE file */
399
400 /* Get the MZ header */
401 Header = (PIMAGE_DOS_HEADER)Address;
402
403 // TODO: Verify checksum and executable!
404
405 /* Get the base size of the file, in paragraphs (rounded up) */
406 ExeSize = (((Header->e_cp - 1) << 8) + Header->e_cblp + 0x0F) >> 4;
407
408 /* Loop from the maximum to the minimum number of extra paragraphs */
409 for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--)
410 {
411 /* Try to allocate that much memory */
412 Segment = DosAllocateMemory(ExeSize + (sizeof(DOS_PSP) >> 4) + i, NULL);
413 if (Segment != 0) break;
414 }
415
416 /* Check if at least the lowest allocation was successful */
417 if (Segment == 0) goto Cleanup;
418
419 /* Initialize the PSP */
420 DosInitializePsp(Segment,
421 CommandLine, ExeSize + (sizeof(DOS_PSP) >> 4) + i,
422 EnvBlock);
423
424 /* The process owns its own memory */
425 DosChangeMemoryOwner(Segment, Segment);
426
427 /* Copy the program to Segment:0100 */
428 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
429 + TO_LINEAR(Segment, 0x100)),
430 Address + (Header->e_cparhdr << 4),
431 FileSize - (Header->e_cparhdr << 4));
432
433 /* Get the relocation table */
434 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
435
436 /* Perform relocations */
437 for (i = 0; i < Header->e_crlc; i++)
438 {
439 /* Get a pointer to the word that needs to be patched */
440 RelocWord = (PWORD)((ULONG_PTR)BaseAddress
441 + TO_LINEAR(Segment + HIWORD(RelocationTable[i]),
442 0x100 + LOWORD(RelocationTable[i])));
443
444 /* Add the number of the EXE segment to it */
445 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
446 }
447
448 /* Set the initial segment registers */
449 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
450 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
451
452 /* Set the stack to the location from the header */
453 EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
454 Header->e_sp);
455
456 /* Execute */
457 CurrentPsp = Segment;
458 DiskTransferArea = MAKELONG(0x80, Segment);
459 EmulatorExecute(Segment + Header->e_cs, sizeof(DOS_PSP) + Header->e_ip);
460
461 Success = TRUE;
462 }
463 else
464 {
465 /* COM file */
466
467 /* Allocate memory for the whole program and the PSP */
468 Segment = DosAllocateMemory((FileSize + sizeof(DOS_PSP)) >> 4, NULL);
469 if (Segment == 0) goto Cleanup;
470
471 /* Copy the program to Segment:0100 */
472 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
473 + TO_LINEAR(Segment, 0x100)),
474 Address,
475 FileSize);
476
477 /* Initialize the PSP */
478 DosInitializePsp(Segment,
479 CommandLine,
480 (FileSize + sizeof(DOS_PSP)) >> 4,
481 EnvBlock);
482
483 /* Set the initial segment registers */
484 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
485 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
486
487 /* Set the stack to the last word of the segment */
488 EmulatorSetStack(Segment, 0xFFFE);
489
490 /* Execute */
491 CurrentPsp = Segment;
492 DiskTransferArea = MAKELONG(0x80, Segment);
493 EmulatorExecute(Segment, 0x100);
494
495 Success = TRUE;
496 }
497
498 Cleanup:
499 /* Unmap the file*/
500 if (Address != NULL) UnmapViewOfFile(Address);
501
502 /* Close the file mapping object */
503 if (FileMapping != NULL) CloseHandle(FileMapping);
504
505 /* Close the file handle */
506 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
507
508 return Success;
509 }
510
511 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
512 {
513 WORD McbSegment = FIRST_MCB_SEGMENT;
514 PDOS_MCB CurrentMcb;
515 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
516 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
517
518 /* Check if this PSP is it's own parent */
519 if (PspBlock->ParentPsp == Psp) goto Done;
520
521 // TODO: Close all handles opened by the process
522
523 /* Free the memory used by the process */
524 while (TRUE)
525 {
526 /* Get a pointer to the MCB */
527 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
528
529 /* Make sure the MCB is valid */
530 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
531
532 /* If this block was allocated by the process, free it */
533 if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment);
534
535 /* If this was the last block, quit */
536 if (CurrentMcb->BlockType == 'Z') break;
537
538 /* Update the segment and continue */
539 McbSegment += CurrentMcb->Size + 1;
540 }
541
542 Done:
543 /* Restore the interrupt vectors */
544 IntVecTable[0x22] = PspBlock->TerminateAddress;
545 IntVecTable[0x23] = PspBlock->BreakAddress;
546 IntVecTable[0x24] = PspBlock->CriticalAddress;
547
548 /* Update the current PSP */
549 if (Psp == CurrentPsp)
550 {
551 CurrentPsp = PspBlock->ParentPsp;
552 if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
553 }
554
555 /* Return control to the parent process */
556 EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
557 LOWORD(PspBlock->TerminateAddress));
558 }
559
560 CHAR DosReadCharacter()
561 {
562 // TODO: STDIN can be redirected under DOS 2.0+
563 CHAR Character = 0;
564
565 /* A zero value for the character indicates a special key */
566 do Character = BiosGetCharacter();
567 while (!Character);
568
569 return Character;
570 }
571
572 VOID DosPrintCharacter(CHAR Character)
573 {
574 // TODO: STDOUT can be redirected under DOS 2.0+
575 if (Character == '\r') Character = '\n';
576 putchar(Character);
577 }
578
579 VOID DosInt20h(WORD CodeSegment)
580 {
581 /* This is the exit interrupt */
582 DosTerminateProcess(CodeSegment, 0);
583 }
584
585 VOID DosInt21h(WORD CodeSegment)
586 {
587 INT i;
588 CHAR Character;
589 SYSTEMTIME SystemTime;
590 PCHAR String;
591 PDOS_INPUT_BUFFER InputBuffer;
592 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
593 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
594 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
595 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
596 WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
597 WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
598
599 /* Check the value in the AH register */
600 switch (HIBYTE(Eax))
601 {
602 /* Terminate Program */
603 case 0x00:
604 {
605 DosTerminateProcess(CodeSegment, 0);
606 break;
607 }
608
609 /* Read Character And Echo */
610 case 0x01:
611 {
612 Character = DosReadCharacter();
613 DosPrintCharacter(Character);
614 EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
615 break;
616 }
617
618 /* Print Character */
619 case 0x02:
620 {
621 DosPrintCharacter(LOBYTE(Edx));
622 break;
623 }
624
625 /* Read Character Without Echo */
626 case 0x08:
627 {
628 EmulatorSetRegister(EMULATOR_REG_AX,
629 (Eax & 0xFFFFFF00) | DosReadCharacter());
630 break;
631 }
632
633 /* Print String */
634 case 0x09:
635 {
636 String = (PCHAR)((ULONG_PTR)BaseAddress
637 + TO_LINEAR(DataSegment, LOWORD(Edx)));
638
639 while ((*String) != '$')
640 {
641 DosPrintCharacter(*String);
642 String++;
643 }
644
645 break;
646 }
647
648 /* Read Buffered Input */
649 case 0x0A:
650 {
651 InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
652 + TO_LINEAR(DataSegment,
653 LOWORD(Edx)));
654
655 InputBuffer->Length = 0;
656 for (i = 0; i < InputBuffer->MaxLength; i ++)
657 {
658 Character = DosReadCharacter();
659 DosPrintCharacter(Character);
660 InputBuffer->Buffer[InputBuffer->Length] = Character;
661 if (Character == '\r') break;
662 InputBuffer->Length++;
663 }
664
665 break;
666 }
667
668 /* Set Disk Transfer Area */
669 case 0x1A:
670 {
671 DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment);
672 break;
673 }
674
675 /* Set Interrupt Vector */
676 case 0x25:
677 {
678 DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment);
679
680 /* Write the new far pointer to the IDT */
681 ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer;
682
683 break;
684 }
685
686 /* Get system date */
687 case 0x2A:
688 {
689 GetLocalTime(&SystemTime);
690 EmulatorSetRegister(EMULATOR_REG_CX,
691 (Ecx & 0xFFFF0000) | SystemTime.wYear);
692 EmulatorSetRegister(EMULATOR_REG_DX,
693 (Edx & 0xFFFF0000)
694 | (SystemTime.wMonth << 8)
695 | SystemTime.wDay);
696 EmulatorSetRegister(EMULATOR_REG_AX,
697 (Eax & 0xFFFFFF00) | SystemTime.wDayOfWeek);
698 break;
699 }
700
701 /* Set system date */
702 case 0x2B:
703 {
704 GetLocalTime(&SystemTime);
705 SystemTime.wYear = LOWORD(Ecx);
706 SystemTime.wMonth = HIBYTE(Edx);
707 SystemTime.wDay = LOBYTE(Edx);
708
709 if (SetLocalTime(&SystemTime))
710 {
711 /* Return success */
712 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
713 }
714 else
715 {
716 /* Return failure */
717 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
718 }
719
720 break;
721 }
722
723 /* Get system time */
724 case 0x2C:
725 {
726 GetLocalTime(&SystemTime);
727 EmulatorSetRegister(EMULATOR_REG_CX,
728 (Ecx & 0xFFFF0000)
729 | (SystemTime.wHour << 8)
730 | SystemTime.wMinute);
731 EmulatorSetRegister(EMULATOR_REG_DX,
732 (Edx & 0xFFFF0000)
733 | (SystemTime.wSecond << 8)
734 | (SystemTime.wMilliseconds / 10));
735 break;
736 }
737
738 /* Set system time */
739 case 0x2D:
740 {
741 GetLocalTime(&SystemTime);
742 SystemTime.wHour = HIBYTE(Ecx);
743 SystemTime.wMinute = LOBYTE(Ecx);
744 SystemTime.wSecond = HIBYTE(Edx);
745 SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
746
747 if (SetLocalTime(&SystemTime))
748 {
749 /* Return success */
750 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
751 }
752 else
753 {
754 /* Return failure */
755 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
756 }
757
758 break;
759 }
760
761 /* Get Disk Transfer Area */
762 case 0x2F:
763 {
764 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea));
765 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea));
766
767 break;
768 }
769
770 /* Get DOS Version */
771 case 0x30:
772 {
773 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
774
775 EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion);
776 break;
777 }
778
779 /* Get Interrupt Vector */
780 case 0x35:
781 {
782 DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)];
783
784 /* Read the address from the IDT into ES:BX */
785 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer));
786 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer));
787
788 break;
789 }
790
791 /* Create Directory */
792 case 0x39:
793 {
794 String = (PCHAR)((ULONG_PTR)BaseAddress
795 + TO_LINEAR(DataSegment, LOWORD(Edx)));
796
797 if (CreateDirectoryA(String, NULL))
798 {
799 EmulatorClearFlag(EMULATOR_FLAG_CF);
800 }
801 else
802 {
803 EmulatorSetFlag(EMULATOR_FLAG_CF);
804 EmulatorSetRegister(EMULATOR_REG_AX,
805 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
806 }
807
808 break;
809 }
810
811 /* Remove Directory */
812 case 0x3A:
813 {
814 String = (PCHAR)((ULONG_PTR)BaseAddress
815 + TO_LINEAR(DataSegment, LOWORD(Edx)));
816
817 if (RemoveDirectoryA(String))
818 {
819 EmulatorClearFlag(EMULATOR_FLAG_CF);
820 }
821 else
822 {
823 EmulatorSetFlag(EMULATOR_FLAG_CF);
824 EmulatorSetRegister(EMULATOR_REG_AX,
825 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
826 }
827
828
829 break;
830 }
831
832 /* Set Current Directory */
833 case 0x3B:
834 {
835 String = (PCHAR)((ULONG_PTR)BaseAddress
836 + TO_LINEAR(DataSegment, LOWORD(Edx)));
837
838 if (SetCurrentDirectoryA(String))
839 {
840 EmulatorClearFlag(EMULATOR_FLAG_CF);
841 }
842 else
843 {
844 EmulatorSetFlag(EMULATOR_FLAG_CF);
845 EmulatorSetRegister(EMULATOR_REG_AX,
846 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
847 }
848
849 break;
850 }
851
852 /* Allocate Memory */
853 case 0x48:
854 {
855 WORD MaxAvailable = 0;
856 WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
857
858 if (Segment != 0)
859 {
860 EmulatorSetRegister(EMULATOR_REG_AX, Segment);
861 EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
862 EmulatorClearFlag(EMULATOR_FLAG_CF);
863 }
864 else EmulatorSetFlag(EMULATOR_FLAG_CF);
865
866 break;
867 }
868
869 /* Free Memory */
870 case 0x49:
871 {
872 if (DosFreeMemory(ExtSegment))
873 {
874 EmulatorClearFlag(EMULATOR_FLAG_CF);
875 }
876 else EmulatorSetFlag(EMULATOR_FLAG_CF);
877
878 break;
879 }
880
881 /* Resize Memory Block */
882 case 0x4A:
883 {
884 WORD Size;
885
886 if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size))
887 {
888 EmulatorClearFlag(EMULATOR_FLAG_CF);
889 }
890 else
891 {
892 EmulatorSetFlag(EMULATOR_FLAG_CF);
893 EmulatorSetRegister(EMULATOR_REG_BX, Size);
894 }
895
896 break;
897 }
898
899 /* Terminate With Return Code */
900 case 0x4C:
901 {
902 DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
903 break;
904 }
905
906 /* Unsupported */
907 default:
908 {
909 DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
910 EmulatorSetFlag(EMULATOR_FLAG_CF);
911 }
912 }
913 }
914
915 VOID DosBreakInterrupt()
916 {
917 VdmRunning = FALSE;
918 }
919
920 BOOLEAN DosInitialize()
921 {
922 PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
923 FILE *Stream;
924 WCHAR Buffer[256];
925 LPWSTR SourcePtr, Environment;
926 LPSTR AsciiString;
927 LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
928 DWORD AsciiSize;
929
930 /* Initialize the MCB */
931 Mcb->BlockType = 'Z';
932 Mcb->Size = (WORD)USER_MEMORY_SIZE;
933 Mcb->OwnerPsp = 0;
934
935 /* Get the environment strings */
936 SourcePtr = Environment = GetEnvironmentStringsW();
937 if (Environment == NULL) return FALSE;
938
939 /* Fill the DOS system environment block */
940 while (*SourcePtr)
941 {
942 /* Get the size of the ASCII string */
943 AsciiSize = WideCharToMultiByte(CP_ACP,
944 0,
945 SourcePtr,
946 -1,
947 NULL,
948 0,
949 NULL,
950 NULL);
951
952 /* Allocate memory for the ASCII string */
953 AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
954 if (AsciiString == NULL)
955 {
956 FreeEnvironmentStringsW(Environment);
957 return FALSE;
958 }
959
960 /* Convert to ASCII */
961 WideCharToMultiByte(CP_ACP,
962 0,
963 SourcePtr,
964 -1,
965 AsciiString,
966 AsciiSize,
967 NULL,
968 NULL);
969
970 /* Copy the string into DOS memory */
971 strcpy(DestPtr, AsciiString);
972
973 /* Free the memory */
974 HeapFree(GetProcessHeap(), 0, AsciiString);
975
976 /* Move to the next string */
977 SourcePtr += wcslen(SourcePtr) + 1;
978 DestPtr += strlen(AsciiString) + 1;
979 }
980
981 /* Free the memory allocated for environment strings */
982 FreeEnvironmentStringsW(Environment);
983
984 /* Read CONFIG.SYS */
985 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
986 if (Stream != NULL)
987 {
988 while (fgetws(Buffer, 256, Stream))
989 {
990 // TODO: Parse the line
991 }
992 fclose(Stream);
993 }
994
995 return TRUE;
996 }
997
998 /* EOF */