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