2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
5 * PURPOSE: VDM DOS Kernel
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
11 WORD CurrentPsp
= SYSTEM_PSP
, LastError
= 0;
13 static VOID
DosCombineFreeBlocks()
15 WORD Segment
= FIRST_MCB_SEGMENT
;
16 PDOS_MCB CurrentMcb
, NextMcb
;
18 /* Loop through all the blocks */
21 /* Get a pointer to the MCB */
22 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
24 /* Ignore the last block */
25 if (CurrentMcb
->BlockType
== 'Z') break;
27 /* Get a pointer to the next MCB */
28 NextMcb
= SEGMENT_TO_MCB(Segment
+ CurrentMcb
->Size
+ 1);
30 /* If both this block and the next one are free, combine them */
31 if ((CurrentMcb
->OwnerPsp
== 0) && (NextMcb
->OwnerPsp
== 0))
33 CurrentMcb
->Size
+= NextMcb
->Size
+ 1;
34 CurrentMcb
->BlockType
= NextMcb
->BlockType
;
36 /* Invalidate the next MCB */
37 NextMcb
->BlockType
= 'I';
39 /* Try to combine the current block again with the next one */
43 /* Update the segment and continue */
44 Segment
+= CurrentMcb
->Size
+ 1;
48 static WORD
DosCopyEnvironmentBlock(WORD SourceSegment
)
50 PCHAR Ptr
, SourceBuffer
, DestBuffer
= NULL
;
54 Ptr
= SourceBuffer
= (PCHAR
)((ULONG_PTR
)BaseAddress
+ TO_LINEAR(SourceSegment
, 0));
56 /* Calculate the size of the environment block */
59 TotalSize
+= strlen(Ptr
) + 1;
60 Ptr
+= strlen(Ptr
) + 1;
64 /* Allocate the memory for the environment block */
65 DestSegment
= DosAllocateMemory((TotalSize
+ 0x0F) >> 4);
66 if (!DestSegment
) return 0;
70 DestBuffer
= (PCHAR
)((ULONG_PTR
)BaseAddress
+ TO_LINEAR(DestSegment
, 0));
74 strcpy(DestBuffer
, Ptr
);
76 /* Advance to the next string */
77 Ptr
+= strlen(Ptr
) + 1;
78 DestBuffer
+= strlen(Ptr
) + 1;
81 /* Set the final zero */
87 WORD
DosAllocateMemory(WORD Size
)
89 WORD Result
= 0, Segment
= FIRST_MCB_SEGMENT
;
90 PDOS_MCB CurrentMcb
, NextMcb
;
92 /* Find an unallocated block */
95 /* Get a pointer to the MCB */
96 CurrentMcb
= SEGMENT_TO_MCB(Segment
);
98 /* Make sure it's valid */
99 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!= 'Z')
104 /* Only check free blocks */
105 if (CurrentMcb
->OwnerPsp
!= 0) goto Next
;
107 /* Check if the block is big enough */
108 if (CurrentMcb
->Size
< Size
) goto Next
;
110 /* It is, update the smallest found so far */
111 if ((Result
== 0) || (CurrentMcb
->Size
< SEGMENT_TO_MCB(Result
)->Size
))
117 /* If this was the last MCB in the chain, quit. */
118 if (CurrentMcb
->BlockType
== 'Z') break;
120 /* Otherwise, update the segment and continue */
121 Segment
+= CurrentMcb
->Size
+ 1;
124 /* If we didn't find a free block, return zero */
125 if (Result
== 0) return 0;
127 /* Get a pointer to the MCB */
128 CurrentMcb
= SEGMENT_TO_MCB(Result
);
130 /* Check if the block is larger than requested */
131 if (CurrentMcb
->Size
> Size
)
133 /* It is, split it into two blocks */
134 NextMcb
= SEGMENT_TO_MCB(Result
+ Size
+ 1);
136 /* Initialize the new MCB structure */
137 NextMcb
->BlockType
= CurrentMcb
->BlockType
;
138 NextMcb
->Size
= Size
- CurrentMcb
->Size
- 1;
139 NextMcb
->OwnerPsp
= 0;
141 /* Update the current block */
142 CurrentMcb
->BlockType
= 'M';
143 CurrentMcb
->Size
= Size
;
145 /* Combine consecutive free blocks into larger blocks */
146 DosCombineFreeBlocks();
149 /* Take ownership of the block */
150 CurrentMcb
->OwnerPsp
= CurrentPsp
;
155 WORD
DosResizeMemory(WORD Segment
, WORD NewSize
)
157 WORD ReturnSize
= 0, CurrentSeg
;
158 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
), CurrentMcb
;
159 BOOLEAN FinalBlockUsed
= FALSE
;
161 /* We can't expand the last block */
162 if (Mcb
->BlockType
!= 'M') return 0;
164 /* Check if need to expand or contract the block */
165 if (NewSize
> Mcb
->Size
)
167 ReturnSize
= Mcb
->Size
;
169 /* Get the segment of the next MCB */
170 CurrentSeg
= Segment
+ Mcb
->Size
+ 1;
172 /* Calculate the maximum amount of memory this block could expand to */
173 while (ReturnSize
< NewSize
)
176 CurrentMcb
= SEGMENT_TO_MCB(CurrentSeg
);
178 /* We can't expand the block over an allocated block */
179 if (CurrentMcb
->OwnerPsp
!= 0) break;
181 ReturnSize
+= CurrentMcb
->Size
+ 1;
183 /* Check if this is the last block */
184 if (CurrentMcb
->BlockType
== 'Z')
186 FinalBlockUsed
= TRUE
;
190 /* Update the segment and continue */
191 CurrentSeg
+= CurrentMcb
->Size
+ 1;
194 /* Check if we need to split the last block */
195 if (ReturnSize
> NewSize
)
197 /* Initialize the new MCB structure */
198 CurrentMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
199 CurrentMcb
->BlockType
= (FinalBlockUsed
) ? 'Z' : 'M';
200 CurrentMcb
->Size
= ReturnSize
- NewSize
- 1;
201 CurrentMcb
->OwnerPsp
= 0;
204 /* Calculate the new size of the block */
205 ReturnSize
= min(ReturnSize
, NewSize
);
208 if (FinalBlockUsed
) Mcb
->BlockType
= 'Z';
209 Mcb
->Size
= ReturnSize
;
211 else if (NewSize
< Mcb
->Size
)
213 /* Just split the block */
214 CurrentMcb
= SEGMENT_TO_MCB(Segment
+ NewSize
+ 1);
215 CurrentMcb
->BlockType
= Mcb
->BlockType
;
216 CurrentMcb
->Size
= Mcb
->Size
- NewSize
- 1;
217 CurrentMcb
->OwnerPsp
= 0;
220 Mcb
->BlockType
= 'M';
223 ReturnSize
= NewSize
;
226 /* Combine consecutive free blocks into larger blocks */
227 DosCombineFreeBlocks();
232 BOOLEAN
DosFreeMemory(WORD Segment
)
234 PDOS_MCB Mcb
= SEGMENT_TO_MCB(Segment
);
236 /* Make sure the MCB is valid */
237 if (Mcb
->BlockType
!= 'M' && Mcb
->BlockType
!= 'Z') return FALSE
;
239 /* Mark the block as free */
242 /* Combine consecutive free blocks into larger blocks */
243 DosCombineFreeBlocks();
248 WORD
DosCreateFile(LPCSTR FilePath
)
250 // TODO: NOT IMPLEMENTED
254 WORD
DosOpenFile(LPCSTR FilePath
)
256 // TODO: NOT IMPLEMENTED
260 VOID
DosInitializePsp(WORD PspSegment
, LPCSTR CommandLine
, WORD ProgramSize
, WORD Environment
)
263 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(PspSegment
);
264 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
266 ZeroMemory(PspBlock
, sizeof(DOS_PSP
));
268 /* Set the exit interrupt */
269 PspBlock
->Exit
[0] = 0xCD; // int 0x20
270 PspBlock
->Exit
[1] = 0x20;
272 /* Set the program size */
273 PspBlock
->MemSize
= ProgramSize
;
275 /* Save the interrupt vectors */
276 PspBlock
->TerminateAddress
= IntVecTable
[0x22];
277 PspBlock
->BreakAddress
= IntVecTable
[0x23];
278 PspBlock
->CriticalAddress
= IntVecTable
[0x24];
280 /* Set the parent PSP */
281 PspBlock
->ParentPsp
= CurrentPsp
;
283 /* Initialize the handle table */
284 for (i
= 0; i
< 20; i
++) PspBlock
->HandleTable
[i
] = 0xFF;
286 /* Did we get an environment segment? */
289 /* No, copy the one from the parent */
290 Environment
= DosCopyEnvironmentBlock((CurrentPsp
!= SYSTEM_PSP
)
291 ? SEGMENT_TO_PSP(CurrentPsp
)->EnvBlock
295 PspBlock
->EnvBlock
= Environment
;
297 /* Set the handle table pointers to the internal handle table */
298 PspBlock
->HandleTableSize
= 20;
299 PspBlock
->HandleTablePtr
= MAKELONG(0x18, PspSegment
);
301 /* Set the DOS version */
302 PspBlock
->DosVersion
= DOS_VERSION
;
304 /* Set the far call opcodes */
305 PspBlock
->FarCall
[0] = 0xCD; // int 0x21
306 PspBlock
->FarCall
[1] = 0x21;
307 PspBlock
->FarCall
[2] = 0xCB; // retf
309 /* Set the command line */
310 PspBlock
->CommandLineSize
= strlen(CommandLine
);
311 RtlCopyMemory(PspBlock
->CommandLine
, CommandLine
, PspBlock
->CommandLineSize
);
312 PspBlock
->CommandLine
[PspBlock
->CommandLineSize
] = '\r';
315 BOOLEAN
DosCreateProcess(LPCSTR CommandLine
, WORD EnvBlock
)
317 BOOLEAN Success
= FALSE
;
318 HANDLE FileHandle
= INVALID_HANDLE_VALUE
, FileMapping
= NULL
;
319 LPBYTE Address
= NULL
;
320 LPSTR ProgramFilePath
, Parameters
[128];
321 CHAR CommandLineCopy
[128];
323 WORD Segment
, FileSize
;
325 /* Save a copy of the command line */
326 strcpy(CommandLineCopy
, CommandLine
);
328 /* Get the file name of the executable */
329 ProgramFilePath
= strtok(CommandLineCopy
, " \t");
331 /* Load the parameters in the local array */
332 while ((ParamCount
< 256)
333 && ((Parameters
[ParamCount
] = strtok(NULL
, " \t")) != NULL
))
338 /* Open a handle to the executable */
339 FileHandle
= CreateFileA(ProgramFilePath
,
344 FILE_ATTRIBUTE_NORMAL
,
346 if (FileHandle
== INVALID_HANDLE_VALUE
) goto Cleanup
;
348 /* Get the file size */
349 FileSize
= GetFileSize(FileHandle
, NULL
);
351 /* Create a mapping object for the file */
352 FileMapping
= CreateFileMapping(FileHandle
,
358 if (FileMapping
== NULL
) goto Cleanup
;
360 /* Map the file into memory */
361 Address
= (LPBYTE
)MapViewOfFile(FileMapping
, FILE_MAP_READ
, 0, 0, 0);
362 if (Address
== NULL
) goto Cleanup
;
364 /* Check if this is an EXE file or a COM file */
365 if (Address
[0] == 'M' && Address
[1] == 'Z')
369 // TODO: NOT IMPLEMENTED
370 DisplayMessage(L
"EXE files are not yet supported!");
376 /* Allocate memory for the whole program and the PSP */
377 Segment
= DosAllocateMemory((FileSize
+ sizeof(DOS_PSP
)) >> 4);
378 if (Segment
== 0) goto Cleanup
;
380 /* Copy the program to Segment:0100 */
381 RtlCopyMemory((PVOID
)((ULONG_PTR
)BaseAddress
382 + TO_LINEAR(Segment
, 0x100)),
386 /* Initialize the PSP */
387 DosInitializePsp(Segment
,
389 (FileSize
+ sizeof(DOS_PSP
)) >> 4,
392 /* Set the initial segment registers */
393 EmulatorSetRegister(EMULATOR_REG_DS
, Segment
);
394 EmulatorSetRegister(EMULATOR_REG_ES
, Segment
);
396 /* Set the stack to the last word of the segment */
397 EmulatorSetStack(Segment
, 0xFFFE);
400 CurrentPsp
= Segment
;
401 EmulatorExecute(Segment
, 0x100);
408 if (Address
!= NULL
) UnmapViewOfFile(Address
);
410 /* Close the file mapping object */
411 if (FileMapping
!= NULL
) CloseHandle(FileMapping
);
413 /* Close the file handle */
414 if (FileHandle
!= INVALID_HANDLE_VALUE
) CloseHandle(FileHandle
);
419 VOID
DosTerminateProcess(WORD Psp
, BYTE ReturnCode
)
421 WORD McbSegment
= FIRST_MCB_SEGMENT
;
423 LPDWORD IntVecTable
= (LPDWORD
)((ULONG_PTR
)BaseAddress
);
424 PDOS_PSP PspBlock
= SEGMENT_TO_PSP(Psp
);
426 /* Check if this PSP is it's own parent */
427 if (PspBlock
->ParentPsp
== Psp
) goto Done
;
429 // TODO: Close all handles opened by the process
431 /* Free the memory used by the process */
434 /* Get a pointer to the MCB */
435 CurrentMcb
= SEGMENT_TO_MCB(McbSegment
);
437 /* Make sure the MCB is valid */
438 if (CurrentMcb
->BlockType
!= 'M' && CurrentMcb
->BlockType
!='Z') break;
440 /* If this block was allocated by the process, free it */
441 if (CurrentMcb
->OwnerPsp
== Psp
) DosFreeMemory(McbSegment
);
443 /* If this was the last block, quit */
444 if (CurrentMcb
->BlockType
== 'Z') break;
446 /* Update the segment and continue */
447 McbSegment
+= CurrentMcb
->Size
+ 1;
451 /* Restore the interrupt vectors */
452 IntVecTable
[0x22] = PspBlock
->TerminateAddress
;
453 IntVecTable
[0x23] = PspBlock
->BreakAddress
;
454 IntVecTable
[0x24] = PspBlock
->CriticalAddress
;
456 /* Update the current PSP */
457 if (Psp
== CurrentPsp
)
459 CurrentPsp
= PspBlock
->ParentPsp
;
460 if (CurrentPsp
== SYSTEM_PSP
) VdmRunning
= FALSE
;
463 /* Return control to the parent process */
464 EmulatorExecute(HIWORD(PspBlock
->TerminateAddress
),
465 LOWORD(PspBlock
->TerminateAddress
));
468 CHAR
DosReadCharacter()
470 // TODO: STDIN can be redirected under DOS 2.0+
474 VOID
DosPrintCharacter(CHAR Character
)
476 // TODO: STDOUT can be redirected under DOS 2.0+
477 if (Character
== '\r') Character
= '\n';
481 VOID
DosInt20h(WORD CodeSegment
)
483 /* This is the exit interrupt */
484 DosTerminateProcess(CodeSegment
, 0);
487 VOID
DosInt21h(WORD CodeSegment
)
491 SYSTEMTIME SystemTime
;
493 PDOS_INPUT_BUFFER InputBuffer
;
494 DWORD Eax
= EmulatorGetRegister(EMULATOR_REG_AX
);
495 DWORD Ecx
= EmulatorGetRegister(EMULATOR_REG_CX
);
496 DWORD Edx
= EmulatorGetRegister(EMULATOR_REG_DX
);
497 DWORD Ebx
= EmulatorGetRegister(EMULATOR_REG_BX
);
498 WORD DataSegment
= EmulatorGetRegister(EMULATOR_REG_DS
);
499 WORD ExtSegment
= EmulatorGetRegister(EMULATOR_REG_ES
);
501 /* Check the value in the AH register */
504 /* Terminate Program */
507 DosTerminateProcess(CodeSegment
, 0);
511 /* Read Character And Echo */
514 Character
= DosReadCharacter();
515 DosPrintCharacter(Character
);
516 EmulatorSetRegister(EMULATOR_REG_AX
, (Eax
& 0xFFFFFF00) | Character
);
520 /* Print Character */
523 DosPrintCharacter(LOBYTE(Edx
));
527 /* Read Character Without Echo */
530 EmulatorSetRegister(EMULATOR_REG_AX
,
531 (Eax
& 0xFFFFFF00) | DosReadCharacter());
538 String
= (PCHAR
)((ULONG_PTR
)BaseAddress
539 + TO_LINEAR(DataSegment
, LOWORD(Edx
)));
541 while ((*String
) != '$')
543 DosPrintCharacter(*String
);
550 /* Read Buffered Input */
553 InputBuffer
= (PDOS_INPUT_BUFFER
)((ULONG_PTR
)BaseAddress
554 + TO_LINEAR(DataSegment
,
557 InputBuffer
->Length
= 0;
558 for (i
= 0; i
< InputBuffer
->MaxLength
; i
++)
560 Character
= DosReadCharacter();
561 DosPrintCharacter(Character
);
562 InputBuffer
->Buffer
[InputBuffer
->Length
] = Character
;
563 if (Character
== '\r') break;
564 InputBuffer
->Length
++;
570 /* Get system date */
573 GetLocalTime(&SystemTime
);
574 EmulatorSetRegister(EMULATOR_REG_CX
,
575 (Ecx
& 0xFFFF0000) | SystemTime
.wYear
);
576 EmulatorSetRegister(EMULATOR_REG_DX
,
578 | (SystemTime
.wMonth
<< 8)
580 EmulatorSetRegister(EMULATOR_REG_AX
,
581 (Eax
& 0xFFFFFF00) | SystemTime
.wDayOfWeek
);
585 /* Set system date */
588 GetLocalTime(&SystemTime
);
589 SystemTime
.wYear
= LOWORD(Ecx
);
590 SystemTime
.wMonth
= HIBYTE(Edx
);
591 SystemTime
.wDay
= LOBYTE(Edx
);
593 if (SetLocalTime(&SystemTime
))
596 EmulatorSetRegister(EMULATOR_REG_AX
, Eax
& 0xFFFFFF00);
601 EmulatorSetRegister(EMULATOR_REG_AX
, Eax
| 0xFF);
607 /* Get system time */
610 GetLocalTime(&SystemTime
);
611 EmulatorSetRegister(EMULATOR_REG_CX
,
613 | (SystemTime
.wHour
<< 8)
614 | SystemTime
.wMinute
);
615 EmulatorSetRegister(EMULATOR_REG_DX
,
617 | (SystemTime
.wSecond
<< 8)
618 | (SystemTime
.wMilliseconds
/ 10));
622 /* Set system time */
625 GetLocalTime(&SystemTime
);
626 SystemTime
.wHour
= HIBYTE(Ecx
);
627 SystemTime
.wMinute
= LOBYTE(Ecx
);
628 SystemTime
.wSecond
= HIBYTE(Edx
);
629 SystemTime
.wMilliseconds
= LOBYTE(Edx
) * 10;
631 if (SetLocalTime(&SystemTime
))
634 EmulatorSetRegister(EMULATOR_REG_AX
, Eax
& 0xFFFFFF00);
639 EmulatorSetRegister(EMULATOR_REG_AX
, Eax
| 0xFF);
645 /* Allocate Memory */
648 WORD Segment
= DosAllocateMemory(LOWORD(Ebx
));
651 EmulatorSetRegister(EMULATOR_REG_AX
, Segment
);
652 EmulatorClearFlag(EMULATOR_FLAG_CF
);
654 else EmulatorSetFlag(EMULATOR_FLAG_CF
);
662 if (DosFreeMemory(ExtSegment
))
664 EmulatorClearFlag(EMULATOR_FLAG_CF
);
666 else EmulatorSetFlag(EMULATOR_FLAG_CF
);
671 /* Resize Memory Block */
674 WORD Size
= DosResizeMemory(ExtSegment
, LOWORD(Ebx
));
678 EmulatorSetRegister(EMULATOR_REG_BX
, Size
);
679 EmulatorClearFlag(EMULATOR_FLAG_CF
);
681 else EmulatorSetFlag(EMULATOR_FLAG_CF
);
686 /* Terminate With Return Code */
689 DosTerminateProcess(CurrentPsp
, LOBYTE(Eax
));
696 EmulatorSetFlag(EMULATOR_FLAG_CF
);
701 VOID
DosBreakInterrupt()
706 BOOLEAN
DosInitialize()
708 PDOS_MCB Mcb
= SEGMENT_TO_MCB(FIRST_MCB_SEGMENT
);
711 LPWSTR SourcePtr
, Environment
;
713 LPSTR DestPtr
= (LPSTR
)((ULONG_PTR
)BaseAddress
+ TO_LINEAR(SYSTEM_ENV_BLOCK
, 0));
716 /* Initialize the MCB */
717 Mcb
->BlockType
= 'Z';
718 Mcb
->Size
= (WORD
)USER_MEMORY_SIZE
;
721 /* Get the environment strings */
722 SourcePtr
= Environment
= GetEnvironmentStringsW();
723 if (Environment
== NULL
) return FALSE
;
725 /* Fill the DOS system environment block */
728 /* Get the size of the ASCII string */
729 AsciiSize
= WideCharToMultiByte(CP_ACP
,
738 /* Allocate memory for the ASCII string */
739 AsciiString
= HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY
, AsciiSize
);
740 if (AsciiString
== NULL
)
742 FreeEnvironmentStringsW(Environment
);
746 /* Convert to ASCII */
747 WideCharToMultiByte(CP_ACP
,
756 /* Copy the string into DOS memory */
757 strcpy(DestPtr
, AsciiString
);
759 /* Free the memory */
760 HeapFree(GetProcessHeap(), 0, AsciiString
);
762 /* Move to the next string */
763 SourcePtr
+= wcslen(SourcePtr
) + 1;
764 DestPtr
+= strlen(AsciiString
) + 1;
767 /* Free the memory allocated for environment strings */
768 FreeEnvironmentStringsW(Environment
);
770 /* Read CONFIG.SYS */
771 Stream
= _wfopen(DOS_CONFIG_PATH
, L
"r");
774 while (fgetws(Buffer
, 256, Stream
))
776 // TODO: Parse the line