afdb59550daf3740687398155b6d87c436925010
[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 #define NDEBUG
12
13 #include "dos.h"
14 #include "bios.h"
15 #include "emulator.h"
16
17 /* PRIVATE VARIABLES **********************************************************/
18
19 static WORD CurrentPsp = SYSTEM_PSP;
20 static WORD DosLastError = 0;
21 static DWORD DiskTransferArea;
22 static HANDLE DosSystemFileTable[DOS_SFT_SIZE];
23 static WORD DosSftRefCount[DOS_SFT_SIZE];
24 static BYTE DosAllocStrategy = DOS_ALLOC_BEST_FIT;
25 static BOOLEAN DosUmbLinked = FALSE;
26
27 /* PRIVATE FUNCTIONS **********************************************************/
28
29 static VOID DosCombineFreeBlocks(WORD StartBlock)
30 {
31 PDOS_MCB CurrentMcb = SEGMENT_TO_MCB(StartBlock), NextMcb;
32
33 /* If this is the last block or it's not free, quit */
34 if (CurrentMcb->BlockType == 'Z' || CurrentMcb->OwnerPsp != 0) return;
35
36 while (TRUE)
37 {
38 /* Get a pointer to the next MCB */
39 NextMcb = SEGMENT_TO_MCB(StartBlock + CurrentMcb->Size + 1);
40
41 /* Check if the next MCB is free */
42 if (NextMcb->OwnerPsp == 0)
43 {
44 /* Combine them */
45 CurrentMcb->Size += NextMcb->Size + 1;
46 CurrentMcb->BlockType = NextMcb->BlockType;
47 NextMcb->BlockType = 'I';
48 }
49 else
50 {
51 /* No more adjoining free blocks */
52 break;
53 }
54 }
55 }
56
57 static WORD DosCopyEnvironmentBlock(WORD SourceSegment)
58 {
59 PCHAR Ptr, SourceBuffer, DestBuffer = NULL;
60 ULONG TotalSize = 0;
61 WORD DestSegment;
62
63 Ptr = SourceBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(SourceSegment, 0));
64
65 /* Calculate the size of the environment block */
66 while (*Ptr)
67 {
68 TotalSize += strlen(Ptr) + 1;
69 Ptr += strlen(Ptr) + 1;
70 }
71 TotalSize++;
72
73 /* Allocate the memory for the environment block */
74 DestSegment = DosAllocateMemory((TotalSize + 0x0F) >> 4, NULL);
75 if (!DestSegment) return 0;
76
77 Ptr = SourceBuffer;
78
79 DestBuffer = (PCHAR)((ULONG_PTR)BaseAddress + TO_LINEAR(DestSegment, 0));
80 while (*Ptr)
81 {
82 /* Copy the string */
83 strcpy(DestBuffer, Ptr);
84
85 /* Advance to the next string */
86 Ptr += strlen(Ptr) + 1;
87 DestBuffer += strlen(Ptr);
88
89 /* Put a zero after the string */
90 *(DestBuffer++) = 0;
91 }
92
93 /* Set the final zero */
94 *DestBuffer = 0;
95
96 return DestSegment;
97 }
98
99 static VOID DosChangeMemoryOwner(WORD Segment, WORD NewOwner)
100 {
101 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment - 1);
102
103 /* Just set the owner */
104 Mcb->OwnerPsp = NewOwner;
105 }
106
107 static WORD DosOpenHandle(HANDLE Handle)
108 {
109 BYTE i;
110 WORD DosHandle;
111 PDOS_PSP PspBlock;
112 LPBYTE HandleTable;
113
114 /* The system PSP has no handle table */
115 if (CurrentPsp == SYSTEM_PSP) return INVALID_DOS_HANDLE;
116
117 /* Get a pointer to the handle table */
118 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
119 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
120
121 /* Find a free entry in the JFT */
122 for (DosHandle = 0; DosHandle < PspBlock->HandleTableSize; DosHandle++)
123 {
124 if (HandleTable[DosHandle] == 0xFF) break;
125 }
126
127 /* If there are no free entries, fail */
128 if (DosHandle == PspBlock->HandleTableSize) return INVALID_DOS_HANDLE;
129
130 /* Check if the handle is already in the SFT */
131 for (i = 0; i < DOS_SFT_SIZE; i++)
132 {
133 /* Check if this is the same handle */
134 if (DosSystemFileTable[i] != Handle) continue;
135
136 /* Already in the table, reference it */
137 DosSftRefCount[i]++;
138
139 /* Set the JFT entry to that SFT index */
140 HandleTable[DosHandle] = i;
141
142 /* Return the new handle */
143 return DosHandle;
144 }
145
146 /* Add the handle to the SFT */
147 for (i = 0; i < DOS_SFT_SIZE; i++)
148 {
149 /* Make sure this is an empty table entry */
150 if (DosSystemFileTable[i] != INVALID_HANDLE_VALUE) continue;
151
152 /* Initialize the empty table entry */
153 DosSystemFileTable[i] = Handle;
154 DosSftRefCount[i] = 1;
155
156 /* Set the JFT entry to that SFT index */
157 HandleTable[DosHandle] = i;
158
159 /* Return the new handle */
160 return DosHandle;
161 }
162
163 /* The SFT is full */
164 return INVALID_DOS_HANDLE;
165 }
166
167 static HANDLE DosGetRealHandle(WORD DosHandle)
168 {
169 PDOS_PSP PspBlock;
170 LPBYTE HandleTable;
171
172 /* The system PSP has no handle table */
173 if (CurrentPsp == SYSTEM_PSP) return INVALID_HANDLE_VALUE;
174
175 /* Get a pointer to the handle table */
176 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
177 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
178
179 /* Make sure the handle is open */
180 if (HandleTable[DosHandle] == 0xFF) return INVALID_HANDLE_VALUE;
181
182 /* Return the Win32 handle */
183 return DosSystemFileTable[HandleTable[DosHandle]];
184 }
185
186 static VOID DosCopyHandleTable(LPBYTE DestinationTable)
187 {
188 INT i;
189 PDOS_PSP PspBlock;
190 LPBYTE SourceTable;
191
192 /* Clear the table first */
193 for (i = 0; i < 20; i++) DestinationTable[i] = 0xFF;
194
195 /* Check if this is the initial process */
196 if (CurrentPsp == SYSTEM_PSP)
197 {
198 /* Set up the standard I/O devices */
199 for (i = 0; i <= 2; i++)
200 {
201 /* Set the index in the SFT */
202 DestinationTable[i] = i;
203
204 /* Increase the reference count */
205 DosSftRefCount[i]++;
206 }
207
208 /* Done */
209 return;
210 }
211
212 /* Get the parent PSP block and handle table */
213 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
214 SourceTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
215
216 /* Copy the first 20 handles into the new table */
217 for (i = 0; i < 20; i++)
218 {
219 DestinationTable[i] = SourceTable[i];
220
221 /* Increase the reference count */
222 DosSftRefCount[SourceTable[i]]++;
223 }
224 }
225
226 /* PUBLIC FUNCTIONS ***********************************************************/
227
228 WORD DosAllocateMemory(WORD Size, WORD *MaxAvailable)
229 {
230 WORD Result = 0, Segment = FIRST_MCB_SEGMENT, MaxSize = 0;
231 PDOS_MCB CurrentMcb, NextMcb;
232 BOOLEAN SearchUmb = FALSE;
233
234 if (DosUmbLinked && (DosAllocStrategy & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW)))
235 {
236 /* Search UMB first */
237 Segment = UMB_START_SEGMENT;
238 SearchUmb = TRUE;
239 }
240
241 while (TRUE)
242 {
243 /* Get a pointer to the MCB */
244 CurrentMcb = SEGMENT_TO_MCB(Segment);
245
246 /* Make sure it's valid */
247 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType != 'Z')
248 {
249 DosLastError = ERROR_ARENA_TRASHED;
250 return 0;
251 }
252
253 /* Only check free blocks */
254 if (CurrentMcb->OwnerPsp != 0) goto Next;
255
256 /* Combine this free block with adjoining free blocks */
257 DosCombineFreeBlocks(Segment);
258
259 /* Update the maximum block size */
260 if (CurrentMcb->Size > MaxSize) MaxSize = CurrentMcb->Size;
261
262 /* Check if this block is big enough */
263 if (CurrentMcb->Size < Size) goto Next;
264
265 switch (DosAllocStrategy & 0x3F)
266 {
267 case DOS_ALLOC_FIRST_FIT:
268 {
269 /* For first fit, stop immediately */
270 Result = Segment;
271 goto Done;
272 }
273
274 case DOS_ALLOC_BEST_FIT:
275 {
276 /* For best fit, update the smallest block found so far */
277 if ((Result == 0) || (CurrentMcb->Size < SEGMENT_TO_MCB(Result)->Size))
278 {
279 Result = Segment;
280 }
281
282 break;
283 }
284
285 case DOS_ALLOC_LAST_FIT:
286 {
287 /* For last fit, make the current block the result, but keep searching */
288 Result = Segment;
289 break;
290 }
291 }
292
293 Next:
294 /* If this was the last MCB in the chain, quit */
295 if (CurrentMcb->BlockType == 'Z')
296 {
297 /* Check if nothing was found while searching through UMBs */
298 if ((Result == 0) && SearchUmb && (DosAllocStrategy & DOS_ALLOC_HIGH_LOW))
299 {
300 /* Search low memory */
301 Segment = FIRST_MCB_SEGMENT;
302 continue;
303 }
304
305 break;
306 }
307
308 /* Otherwise, update the segment and continue */
309 Segment += CurrentMcb->Size + 1;
310 }
311
312 Done:
313
314 /* If we didn't find a free block, return 0 */
315 if (Result == 0)
316 {
317 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
318 if (MaxAvailable) *MaxAvailable = MaxSize;
319 return 0;
320 }
321
322 /* Get a pointer to the MCB */
323 CurrentMcb = SEGMENT_TO_MCB(Result);
324
325 /* Check if the block is larger than requested */
326 if (CurrentMcb->Size > Size)
327 {
328 /* It is, split it into two blocks */
329 NextMcb = SEGMENT_TO_MCB(Result + Size + 1);
330
331 /* Initialize the new MCB structure */
332 NextMcb->BlockType = CurrentMcb->BlockType;
333 NextMcb->Size = CurrentMcb->Size - Size - 1;
334 NextMcb->OwnerPsp = 0;
335
336 /* Update the current block */
337 CurrentMcb->BlockType = 'M';
338 CurrentMcb->Size = Size;
339 }
340
341 /* Take ownership of the block */
342 CurrentMcb->OwnerPsp = CurrentPsp;
343
344 /* Return the segment of the data portion of the block */
345 return Result + 1;
346 }
347
348 BOOLEAN DosResizeMemory(WORD BlockData, WORD NewSize, WORD *MaxAvailable)
349 {
350 BOOLEAN Success = TRUE;
351 WORD Segment = BlockData - 1, ReturnSize = 0, NextSegment;
352 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment), NextMcb;
353
354 DPRINT("DosResizeMemory: BlockData 0x%04X, NewSize 0x%04X\n",
355 BlockData,
356 NewSize);
357
358 /* Make sure this is a valid, allocated block */
359 if ((Mcb->BlockType != 'M' && Mcb->BlockType != 'Z') || Mcb->OwnerPsp == 0)
360 {
361 Success = FALSE;
362 DosLastError = ERROR_INVALID_HANDLE;
363 goto Done;
364 }
365
366 ReturnSize = Mcb->Size;
367
368 /* Check if we need to expand or contract the block */
369 if (NewSize > Mcb->Size)
370 {
371 /* We can't expand the last block */
372 if (Mcb->BlockType != 'M')
373 {
374 Success = FALSE;
375 goto Done;
376 }
377
378 /* Get the pointer and segment of the next MCB */
379 NextSegment = Segment + Mcb->Size + 1;
380 NextMcb = SEGMENT_TO_MCB(NextSegment);
381
382 /* Make sure the next segment is free */
383 if (NextMcb->OwnerPsp != 0)
384 {
385 DPRINT("Cannot expand memory block: next segment is not free!\n");
386 DosLastError = ERROR_NOT_ENOUGH_MEMORY;
387 Success = FALSE;
388 goto Done;
389 }
390
391 /* Combine this free block with adjoining free blocks */
392 DosCombineFreeBlocks(NextSegment);
393
394 /* Set the maximum possible size of the block */
395 ReturnSize += NextMcb->Size + 1;
396
397 /* Maximize the current block */
398 Mcb->Size = ReturnSize;
399 Mcb->BlockType = NextMcb->BlockType;
400
401 /* Invalidate the next block */
402 NextMcb->BlockType = 'I';
403
404 /* Check if the block is larger than requested */
405 if (Mcb->Size > NewSize)
406 {
407 DPRINT("Block too large, reducing size from 0x%04X to 0x%04X\n",
408 Mcb->Size,
409 NewSize);
410
411 /* It is, split it into two blocks */
412 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
413
414 /* Initialize the new MCB structure */
415 NextMcb->BlockType = Mcb->BlockType;
416 NextMcb->Size = Mcb->Size - NewSize - 1;
417 NextMcb->OwnerPsp = 0;
418
419 /* Update the current block */
420 Mcb->BlockType = 'M';
421 Mcb->Size = NewSize;
422 }
423 }
424 else if (NewSize < Mcb->Size)
425 {
426 DPRINT("Shrinking block from 0x%04X to 0x%04X\n",
427 Mcb->Size,
428 NewSize);
429
430 /* Just split the block */
431 NextMcb = SEGMENT_TO_MCB(Segment + NewSize + 1);
432 NextMcb->BlockType = Mcb->BlockType;
433 NextMcb->Size = Mcb->Size - NewSize - 1;
434 NextMcb->OwnerPsp = 0;
435
436 /* Update the MCB */
437 Mcb->BlockType = 'M';
438 Mcb->Size = NewSize;
439 }
440
441 Done:
442 /* Check if the operation failed */
443 if (!Success)
444 {
445 DPRINT("DosResizeMemory FAILED. Maximum available: 0x%04X\n",
446 ReturnSize);
447
448 /* Return the maximum possible size */
449 if (MaxAvailable) *MaxAvailable = ReturnSize;
450 }
451
452 return Success;
453 }
454
455 BOOLEAN DosFreeMemory(WORD BlockData)
456 {
457 PDOS_MCB Mcb = SEGMENT_TO_MCB(BlockData - 1);
458
459 DPRINT("DosFreeMemory: BlockData 0x%04X\n", BlockData);
460
461 /* Make sure the MCB is valid */
462 if (Mcb->BlockType != 'M' && Mcb->BlockType != 'Z')
463 {
464 DPRINT("MCB block type '%c' not valid!\n", Mcb->BlockType);
465 return FALSE;
466 }
467
468 /* Mark the block as free */
469 Mcb->OwnerPsp = 0;
470
471 return TRUE;
472 }
473
474 BOOLEAN DosLinkUmb(VOID)
475 {
476 DWORD Segment = FIRST_MCB_SEGMENT;
477 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
478
479 DPRINT("Linking UMB\n");
480
481 /* Check if UMBs are already linked */
482 if (DosUmbLinked) return FALSE;
483
484 /* Find the last block */
485 while ((Mcb->BlockType == 'M') && (Segment <= 0xFFFF))
486 {
487 Segment += Mcb->Size + 1;
488 Mcb = SEGMENT_TO_MCB(Segment);
489 }
490
491 /* Make sure it's valid */
492 if (Mcb->BlockType != 'Z') return FALSE;
493
494 /* Connect the MCB with the UMB chain */
495 Mcb->BlockType = 'M';
496
497 DosUmbLinked = TRUE;
498 return TRUE;
499 }
500
501 BOOLEAN DosUnlinkUmb(VOID)
502 {
503 DWORD Segment = FIRST_MCB_SEGMENT;
504 PDOS_MCB Mcb = SEGMENT_TO_MCB(Segment);
505
506 DPRINT("Unlinking UMB\n");
507
508 /* Check if UMBs are already unlinked */
509 if (!DosUmbLinked) return FALSE;
510
511 /* Find the block preceding the MCB that links it with the UMB chain */
512 while (Segment <= 0xFFFF)
513 {
514 if ((Segment + Mcb->Size) == (FIRST_MCB_SEGMENT + USER_MEMORY_SIZE))
515 {
516 /* This is the last non-UMB segment */
517 break;
518 }
519
520 /* Advance to the next MCB */
521 Segment += Mcb->Size + 1;
522 Mcb = SEGMENT_TO_MCB(Segment);
523 }
524
525 /* Mark the MCB as the last MCB */
526 Mcb->BlockType = 'Z';
527
528 DosUmbLinked = FALSE;
529 return TRUE;
530 }
531
532 WORD DosCreateFile(LPWORD Handle, LPCSTR FilePath, WORD Attributes)
533 {
534 HANDLE FileHandle;
535 WORD DosHandle;
536
537 DPRINT("DosCreateFile: FilePath \"%s\", Attributes 0x%04X\n",
538 FilePath,
539 Attributes);
540
541 /* Create the file */
542 FileHandle = CreateFileA(FilePath,
543 GENERIC_READ | GENERIC_WRITE,
544 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
545 NULL,
546 CREATE_ALWAYS,
547 Attributes,
548 NULL);
549
550 if (FileHandle == INVALID_HANDLE_VALUE)
551 {
552 /* Return the error code */
553 return GetLastError();
554 }
555
556 /* Open the DOS handle */
557 DosHandle = DosOpenHandle(FileHandle);
558
559 if (DosHandle == INVALID_DOS_HANDLE)
560 {
561 /* Close the handle */
562 CloseHandle(FileHandle);
563
564 /* Return the error code */
565 return ERROR_TOO_MANY_OPEN_FILES;
566 }
567
568 /* It was successful */
569 *Handle = DosHandle;
570 return ERROR_SUCCESS;
571 }
572
573 WORD DosOpenFile(LPWORD Handle, LPCSTR FilePath, BYTE AccessMode)
574 {
575 HANDLE FileHandle;
576 ACCESS_MASK Access = 0;
577 WORD DosHandle;
578
579 DPRINT("DosOpenFile: FilePath \"%s\", AccessMode 0x%04X\n",
580 FilePath,
581 AccessMode);
582
583 /* Parse the access mode */
584 switch (AccessMode & 3)
585 {
586 case 0:
587 {
588 /* Read-only */
589 Access = GENERIC_READ;
590 break;
591 }
592
593 case 1:
594 {
595 /* Write only */
596 Access = GENERIC_WRITE;
597 break;
598 }
599
600 case 2:
601 {
602 /* Read and write */
603 Access = GENERIC_READ | GENERIC_WRITE;
604 break;
605 }
606
607 default:
608 {
609 /* Invalid */
610 return ERROR_INVALID_PARAMETER;
611 }
612 }
613
614 /* Open the file */
615 FileHandle = CreateFileA(FilePath,
616 Access,
617 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
618 NULL,
619 OPEN_EXISTING,
620 FILE_ATTRIBUTE_NORMAL,
621 NULL);
622
623 if (FileHandle == INVALID_HANDLE_VALUE)
624 {
625 /* Return the error code */
626 return GetLastError();
627 }
628
629 /* Open the DOS handle */
630 DosHandle = DosOpenHandle(FileHandle);
631
632 if (DosHandle == INVALID_DOS_HANDLE)
633 {
634 /* Close the handle */
635 CloseHandle(FileHandle);
636
637 /* Return the error code */
638 return ERROR_TOO_MANY_OPEN_FILES;
639 }
640
641 /* It was successful */
642 *Handle = DosHandle;
643 return ERROR_SUCCESS;
644 }
645
646 WORD DosReadFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesRead)
647 {
648 WORD Result = ERROR_SUCCESS;
649 DWORD BytesRead32 = 0;
650 HANDLE Handle = DosGetRealHandle(FileHandle);
651
652 DPRINT("DosReadFile: FileHandle 0x%04X, Count 0x%04X\n", FileHandle, Count);
653
654 /* Make sure the handle is valid */
655 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
656
657 /* Read the file */
658 if (!ReadFile(Handle, Buffer, Count, &BytesRead32, NULL))
659 {
660 /* Store the error code */
661 Result = GetLastError();
662 }
663
664 /* The number of bytes read is always 16-bit */
665 *BytesRead = LOWORD(BytesRead32);
666
667 /* Return the error code */
668 return Result;
669 }
670
671 WORD DosWriteFile(WORD FileHandle, LPVOID Buffer, WORD Count, LPWORD BytesWritten)
672 {
673 WORD Result = ERROR_SUCCESS;
674 DWORD BytesWritten32 = 0;
675 HANDLE Handle = DosGetRealHandle(FileHandle);
676
677 DPRINT("DosWriteFile: FileHandle 0x%04X, Count 0x%04X\n",
678 FileHandle,
679 Count);
680
681 /* Make sure the handle is valid */
682 if (Handle == INVALID_HANDLE_VALUE) return ERROR_INVALID_HANDLE;
683
684 /* Write the file */
685 if (!WriteFile(Handle, Buffer, Count, &BytesWritten32, NULL))
686 {
687 /* Store the error code */
688 Result = GetLastError();
689 }
690
691 /* The number of bytes written is always 16-bit */
692 *BytesWritten = LOWORD(BytesWritten32);
693
694 /* Return the error code */
695 return Result;
696 }
697
698 BOOLEAN DosCloseHandle(WORD DosHandle)
699 {
700 BYTE SftIndex;
701 PDOS_PSP PspBlock;
702 LPBYTE HandleTable;
703
704 DPRINT("DosCloseHandle: DosHandle 0x%04X\n", DosHandle);
705
706 /* The system PSP has no handle table */
707 if (CurrentPsp == SYSTEM_PSP) return FALSE;
708
709 /* Get a pointer to the handle table */
710 PspBlock = SEGMENT_TO_PSP(CurrentPsp);
711 HandleTable = (LPBYTE)FAR_POINTER(PspBlock->HandleTablePtr);
712
713 /* Make sure the handle is open */
714 if (HandleTable[DosHandle] == 0xFF) return FALSE;
715
716 /* Decrement the reference count of the SFT entry */
717 SftIndex = HandleTable[DosHandle];
718 DosSftRefCount[SftIndex]--;
719
720 /* Check if the reference count fell to zero */
721 if (!DosSftRefCount[SftIndex])
722 {
723 /* Close the file, it's no longer needed */
724 CloseHandle(DosSystemFileTable[SftIndex]);
725
726 /* Clear the handle */
727 DosSystemFileTable[SftIndex] = INVALID_HANDLE_VALUE;
728 }
729
730 return TRUE;
731 }
732
733 VOID DosInitializePsp(WORD PspSegment, LPCSTR CommandLine, WORD ProgramSize, WORD Environment)
734 {
735 PDOS_PSP PspBlock = SEGMENT_TO_PSP(PspSegment);
736 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
737
738 ZeroMemory(PspBlock, sizeof(DOS_PSP));
739
740 /* Set the exit interrupt */
741 PspBlock->Exit[0] = 0xCD; // int 0x20
742 PspBlock->Exit[1] = 0x20;
743
744 /* Set the number of the last paragraph */
745 PspBlock->LastParagraph = PspSegment + ProgramSize - 1;
746
747 /* Save the interrupt vectors */
748 PspBlock->TerminateAddress = IntVecTable[0x22];
749 PspBlock->BreakAddress = IntVecTable[0x23];
750 PspBlock->CriticalAddress = IntVecTable[0x24];
751
752 /* Set the parent PSP */
753 PspBlock->ParentPsp = CurrentPsp;
754
755 /* Copy the parent handle table */
756 DosCopyHandleTable(PspBlock->HandleTable);
757
758 /* Set the environment block */
759 PspBlock->EnvBlock = Environment;
760
761 /* Set the handle table pointers to the internal handle table */
762 PspBlock->HandleTableSize = 20;
763 PspBlock->HandleTablePtr = MAKELONG(0x18, PspSegment);
764
765 /* Set the DOS version */
766 PspBlock->DosVersion = DOS_VERSION;
767
768 /* Set the far call opcodes */
769 PspBlock->FarCall[0] = 0xCD; // int 0x21
770 PspBlock->FarCall[1] = 0x21;
771 PspBlock->FarCall[2] = 0xCB; // retf
772
773 /* Set the command line */
774 PspBlock->CommandLineSize = strlen(CommandLine);
775 RtlCopyMemory(PspBlock->CommandLine, CommandLine, PspBlock->CommandLineSize);
776 PspBlock->CommandLine[PspBlock->CommandLineSize] = '\r';
777 }
778
779 BOOLEAN DosCreateProcess(LPCSTR CommandLine, WORD EnvBlock)
780 {
781 BOOLEAN Success = FALSE, AllocatedEnvBlock = FALSE;
782 HANDLE FileHandle = INVALID_HANDLE_VALUE, FileMapping = NULL;
783 LPBYTE Address = NULL;
784 LPSTR ProgramFilePath, Parameters[128];
785 CHAR CommandLineCopy[128];
786 INT ParamCount = 0;
787 DWORD Segment = 0;
788 WORD MaxAllocSize;
789 DWORD i, FileSize, ExeSize;
790 PIMAGE_DOS_HEADER Header;
791 PDWORD RelocationTable;
792 PWORD RelocWord;
793
794 DPRINT("DosCreateProcess: CommandLine \"%s\", EnvBlock 0x%04X\n",
795 CommandLine,
796 EnvBlock);
797
798 /* Save a copy of the command line */
799 strcpy(CommandLineCopy, CommandLine);
800
801 /* Get the file name of the executable */
802 ProgramFilePath = strtok(CommandLineCopy, " \t");
803
804 /* Load the parameters in the local array */
805 while ((ParamCount < 256)
806 && ((Parameters[ParamCount] = strtok(NULL, " \t")) != NULL))
807 {
808 ParamCount++;
809 }
810
811 /* Open a handle to the executable */
812 FileHandle = CreateFileA(ProgramFilePath,
813 GENERIC_READ,
814 0,
815 NULL,
816 OPEN_EXISTING,
817 FILE_ATTRIBUTE_NORMAL,
818 NULL);
819 if (FileHandle == INVALID_HANDLE_VALUE) goto Cleanup;
820
821 /* Get the file size */
822 FileSize = GetFileSize(FileHandle, NULL);
823
824 /* Create a mapping object for the file */
825 FileMapping = CreateFileMapping(FileHandle,
826 NULL,
827 PAGE_READONLY,
828 0,
829 0,
830 NULL);
831 if (FileMapping == NULL) goto Cleanup;
832
833 /* Map the file into memory */
834 Address = (LPBYTE)MapViewOfFile(FileMapping, FILE_MAP_READ, 0, 0, 0);
835 if (Address == NULL) goto Cleanup;
836
837 /* Did we get an environment segment? */
838 if (!EnvBlock)
839 {
840 /* Set a flag to know if the environment block was allocated here */
841 AllocatedEnvBlock = TRUE;
842
843 /* No, copy the one from the parent */
844 EnvBlock = DosCopyEnvironmentBlock((CurrentPsp != SYSTEM_PSP)
845 ? SEGMENT_TO_PSP(CurrentPsp)->EnvBlock
846 : SYSTEM_ENV_BLOCK);
847 }
848
849 /* Check if this is an EXE file or a COM file */
850 if (Address[0] == 'M' && Address[1] == 'Z')
851 {
852 /* EXE file */
853
854 /* Get the MZ header */
855 Header = (PIMAGE_DOS_HEADER)Address;
856
857 /* Get the base size of the file, in paragraphs (rounded up) */
858 ExeSize = (((Header->e_cp - 1) * 512) + Header->e_cblp + 0x0F) >> 4;
859
860 /* Add the PSP size, in paragraphs */
861 ExeSize += sizeof(DOS_PSP) >> 4;
862
863 /* Add the maximum size that should be allocated */
864 ExeSize += Header->e_maxalloc;
865
866 /* Make sure it does not pass 0xFFFF */
867 if (ExeSize > 0xFFFF) ExeSize = 0xFFFF;
868
869 /* Reduce the size one by one until the allocation is successful */
870 for (i = Header->e_maxalloc; i >= Header->e_minalloc; i--, ExeSize--)
871 {
872 /* Try to allocate that much memory */
873 Segment = DosAllocateMemory(ExeSize, NULL);
874 if (Segment != 0) break;
875 }
876
877 /* Check if at least the lowest allocation was successful */
878 if (Segment == 0) goto Cleanup;
879
880 /* Initialize the PSP */
881 DosInitializePsp(Segment,
882 CommandLine,
883 ExeSize,
884 EnvBlock);
885
886 /* The process owns its own memory */
887 DosChangeMemoryOwner(Segment, Segment);
888 DosChangeMemoryOwner(EnvBlock, Segment);
889
890 /* Copy the program to Segment:0100 */
891 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
892 + TO_LINEAR(Segment, 0x100)),
893 Address + (Header->e_cparhdr << 4),
894 min(FileSize - (Header->e_cparhdr << 4),
895 (ExeSize << 4) - sizeof(DOS_PSP)));
896
897 /* Get the relocation table */
898 RelocationTable = (PDWORD)(Address + Header->e_lfarlc);
899
900 /* Perform relocations */
901 for (i = 0; i < Header->e_crlc; i++)
902 {
903 /* Get a pointer to the word that needs to be patched */
904 RelocWord = (PWORD)((ULONG_PTR)BaseAddress
905 + TO_LINEAR(Segment + HIWORD(RelocationTable[i]),
906 0x100 + LOWORD(RelocationTable[i])));
907
908 /* Add the number of the EXE segment to it */
909 *RelocWord += Segment + (sizeof(DOS_PSP) >> 4);
910 }
911
912 /* Set the initial segment registers */
913 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
914 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
915
916 /* Set the stack to the location from the header */
917 EmulatorSetStack(Segment + (sizeof(DOS_PSP) >> 4) + Header->e_ss,
918 Header->e_sp);
919
920 /* Execute */
921 CurrentPsp = Segment;
922 DiskTransferArea = MAKELONG(0x80, Segment);
923 EmulatorExecute(Segment + Header->e_cs + (sizeof(DOS_PSP) >> 4),
924 Header->e_ip);
925
926 Success = TRUE;
927 }
928 else
929 {
930 /* COM file */
931
932 /* Find the maximum amount of memory that can be allocated */
933 DosAllocateMemory(0xFFFF, &MaxAllocSize);
934
935 /* Make sure it's enough for the whole program and the PSP */
936 if ((MaxAllocSize << 4) < (FileSize + sizeof(DOS_PSP))) goto Cleanup;
937
938 /* Allocate all of it */
939 Segment = DosAllocateMemory(MaxAllocSize, NULL);
940 if (Segment == 0) goto Cleanup;
941
942 /* The process owns its own memory */
943 DosChangeMemoryOwner(Segment, Segment);
944 DosChangeMemoryOwner(EnvBlock, Segment);
945
946 /* Copy the program to Segment:0100 */
947 RtlCopyMemory((PVOID)((ULONG_PTR)BaseAddress
948 + TO_LINEAR(Segment, 0x100)),
949 Address,
950 FileSize);
951
952 /* Initialize the PSP */
953 DosInitializePsp(Segment,
954 CommandLine,
955 (FileSize + sizeof(DOS_PSP)) >> 4,
956 EnvBlock);
957
958 /* Set the initial segment registers */
959 EmulatorSetRegister(EMULATOR_REG_DS, Segment);
960 EmulatorSetRegister(EMULATOR_REG_ES, Segment);
961
962 /* Set the stack to the last word of the segment */
963 EmulatorSetStack(Segment, 0xFFFE);
964
965 /* Execute */
966 CurrentPsp = Segment;
967 DiskTransferArea = MAKELONG(0x80, Segment);
968 EmulatorExecute(Segment, 0x100);
969
970 Success = TRUE;
971 }
972
973 Cleanup:
974 if (!Success)
975 {
976 /* It was not successful, cleanup the DOS memory */
977 if (AllocatedEnvBlock) DosFreeMemory(EnvBlock);
978 if (Segment) DosFreeMemory(Segment);
979 }
980
981 /* Unmap the file*/
982 if (Address != NULL) UnmapViewOfFile(Address);
983
984 /* Close the file mapping object */
985 if (FileMapping != NULL) CloseHandle(FileMapping);
986
987 /* Close the file handle */
988 if (FileHandle != INVALID_HANDLE_VALUE) CloseHandle(FileHandle);
989
990 return Success;
991 }
992
993 VOID DosTerminateProcess(WORD Psp, BYTE ReturnCode)
994 {
995 WORD i;
996 WORD McbSegment = FIRST_MCB_SEGMENT;
997 PDOS_MCB CurrentMcb;
998 LPDWORD IntVecTable = (LPDWORD)((ULONG_PTR)BaseAddress);
999 PDOS_PSP PspBlock = SEGMENT_TO_PSP(Psp);
1000
1001 DPRINT("DosTerminateProcess: Psp 0x%04X, ReturnCode 0x%02X\n",
1002 Psp,
1003 ReturnCode);
1004
1005 /* Check if this PSP is it's own parent */
1006 if (PspBlock->ParentPsp == Psp) goto Done;
1007
1008 for (i = 0; i < PspBlock->HandleTableSize; i++)
1009 {
1010 /* Close the handle */
1011 DosCloseHandle(i);
1012 }
1013
1014 /* Free the memory used by the process */
1015 while (TRUE)
1016 {
1017 /* Get a pointer to the MCB */
1018 CurrentMcb = SEGMENT_TO_MCB(McbSegment);
1019
1020 /* Make sure the MCB is valid */
1021 if (CurrentMcb->BlockType != 'M' && CurrentMcb->BlockType !='Z') break;
1022
1023 /* If this block was allocated by the process, free it */
1024 if (CurrentMcb->OwnerPsp == Psp) DosFreeMemory(McbSegment);
1025
1026 /* If this was the last block, quit */
1027 if (CurrentMcb->BlockType == 'Z') break;
1028
1029 /* Update the segment and continue */
1030 McbSegment += CurrentMcb->Size + 1;
1031 }
1032
1033 Done:
1034 /* Restore the interrupt vectors */
1035 IntVecTable[0x22] = PspBlock->TerminateAddress;
1036 IntVecTable[0x23] = PspBlock->BreakAddress;
1037 IntVecTable[0x24] = PspBlock->CriticalAddress;
1038
1039 /* Update the current PSP */
1040 if (Psp == CurrentPsp)
1041 {
1042 CurrentPsp = PspBlock->ParentPsp;
1043 if (CurrentPsp == SYSTEM_PSP) VdmRunning = FALSE;
1044 }
1045
1046 /* Return control to the parent process */
1047 EmulatorExecute(HIWORD(PspBlock->TerminateAddress),
1048 LOWORD(PspBlock->TerminateAddress));
1049 }
1050
1051 CHAR DosReadCharacter(VOID)
1052 {
1053 CHAR Character = '\0';
1054 WORD BytesRead;
1055
1056 /* Use the file reading function */
1057 DosReadFile(DOS_INPUT_HANDLE, &Character, sizeof(CHAR), &BytesRead);
1058
1059 return Character;
1060 }
1061
1062 VOID DosPrintCharacter(CHAR Character)
1063 {
1064 WORD BytesWritten;
1065
1066 /* Use the file writing function */
1067 DosWriteFile(DOS_OUTPUT_HANDLE, &Character, sizeof(CHAR), &BytesWritten);
1068 }
1069
1070 VOID DosHandleIoctl(BYTE ControlCode, WORD FileHandle)
1071 {
1072 HANDLE Handle = DosGetRealHandle(FileHandle);
1073
1074 if (Handle == INVALID_HANDLE_VALUE)
1075 {
1076 /* Doesn't exist */
1077 EmulatorSetFlag(EMULATOR_FLAG_CF);
1078 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_FILE_NOT_FOUND);
1079 }
1080
1081 switch (ControlCode)
1082 {
1083 /* Get Device Information */
1084 case 0x00:
1085 {
1086 WORD InfoWord = 0;
1087
1088 if (Handle == DosSystemFileTable[0])
1089 {
1090 /* Console input */
1091 InfoWord |= 1 << 0;
1092 }
1093 else if (Handle == DosSystemFileTable[1])
1094 {
1095 /* Console output */
1096 InfoWord |= 1 << 1;
1097 }
1098
1099 /* It is a character device */
1100 InfoWord |= 1 << 7;
1101
1102 /* Return the device information word */
1103 EmulatorClearFlag(EMULATOR_FLAG_CF);
1104 EmulatorSetRegister(EMULATOR_REG_DX, InfoWord);
1105
1106 break;
1107 }
1108
1109 /* Unsupported control code */
1110 default:
1111 {
1112 DPRINT1("Unsupported IOCTL: 0x%02X\n", ControlCode);
1113
1114 EmulatorSetFlag(EMULATOR_FLAG_CF);
1115 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER);
1116 }
1117 }
1118 }
1119
1120 VOID DosInt20h(WORD CodeSegment)
1121 {
1122 /* This is the exit interrupt */
1123 DosTerminateProcess(CodeSegment, 0);
1124 }
1125
1126 VOID DosInt21h(WORD CodeSegment)
1127 {
1128 INT i;
1129 CHAR Character;
1130 SYSTEMTIME SystemTime;
1131 PCHAR String;
1132 PDOS_INPUT_BUFFER InputBuffer;
1133 DWORD Eax = EmulatorGetRegister(EMULATOR_REG_AX);
1134 DWORD Ecx = EmulatorGetRegister(EMULATOR_REG_CX);
1135 DWORD Edx = EmulatorGetRegister(EMULATOR_REG_DX);
1136 DWORD Ebx = EmulatorGetRegister(EMULATOR_REG_BX);
1137 WORD DataSegment = EmulatorGetRegister(EMULATOR_REG_DS);
1138 WORD ExtSegment = EmulatorGetRegister(EMULATOR_REG_ES);
1139
1140 /* Check the value in the AH register */
1141 switch (HIBYTE(Eax))
1142 {
1143 /* Terminate Program */
1144 case 0x00:
1145 {
1146 DosTerminateProcess(CodeSegment, 0);
1147 break;
1148 }
1149
1150 /* Read Character And Echo */
1151 case 0x01:
1152 {
1153 Character = DosReadCharacter();
1154 DosPrintCharacter(Character);
1155 EmulatorSetRegister(EMULATOR_REG_AX, (Eax & 0xFFFFFF00) | Character);
1156 break;
1157 }
1158
1159 /* Print Character */
1160 case 0x02:
1161 {
1162 DosPrintCharacter(LOBYTE(Edx));
1163 break;
1164 }
1165
1166 /* Read Character Without Echo */
1167 case 0x07:
1168 case 0x08:
1169 {
1170 EmulatorSetRegister(EMULATOR_REG_AX,
1171 (Eax & 0xFFFFFF00) | DosReadCharacter());
1172 break;
1173 }
1174
1175 /* Print String */
1176 case 0x09:
1177 {
1178 String = (PCHAR)((ULONG_PTR)BaseAddress
1179 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1180
1181 while ((*String) != '$')
1182 {
1183 DosPrintCharacter(*String);
1184 String++;
1185 }
1186
1187 break;
1188 }
1189
1190 /* Read Buffered Input */
1191 case 0x0A:
1192 {
1193 InputBuffer = (PDOS_INPUT_BUFFER)((ULONG_PTR)BaseAddress
1194 + TO_LINEAR(DataSegment,
1195 LOWORD(Edx)));
1196
1197 InputBuffer->Length = 0;
1198 for (i = 0; i < InputBuffer->MaxLength; i ++)
1199 {
1200 Character = DosReadCharacter();
1201 DosPrintCharacter(Character);
1202 InputBuffer->Buffer[InputBuffer->Length] = Character;
1203 if (Character == '\r') break;
1204 InputBuffer->Length++;
1205 }
1206
1207 break;
1208 }
1209
1210 /* Set Disk Transfer Area */
1211 case 0x1A:
1212 {
1213 DiskTransferArea = MAKELONG(LOWORD(Edx), DataSegment);
1214 break;
1215 }
1216
1217 /* Set Interrupt Vector */
1218 case 0x25:
1219 {
1220 DWORD FarPointer = MAKELONG(LOWORD(Edx), DataSegment);
1221
1222 /* Write the new far pointer to the IDT */
1223 ((PDWORD)BaseAddress)[LOBYTE(Eax)] = FarPointer;
1224
1225 break;
1226 }
1227
1228 /* Get system date */
1229 case 0x2A:
1230 {
1231 GetLocalTime(&SystemTime);
1232 EmulatorSetRegister(EMULATOR_REG_CX,
1233 (Ecx & 0xFFFF0000) | SystemTime.wYear);
1234 EmulatorSetRegister(EMULATOR_REG_DX,
1235 (Edx & 0xFFFF0000)
1236 | (SystemTime.wMonth << 8)
1237 | SystemTime.wDay);
1238 EmulatorSetRegister(EMULATOR_REG_AX,
1239 (Eax & 0xFFFFFF00) | SystemTime.wDayOfWeek);
1240 break;
1241 }
1242
1243 /* Set system date */
1244 case 0x2B:
1245 {
1246 GetLocalTime(&SystemTime);
1247 SystemTime.wYear = LOWORD(Ecx);
1248 SystemTime.wMonth = HIBYTE(Edx);
1249 SystemTime.wDay = LOBYTE(Edx);
1250
1251 if (SetLocalTime(&SystemTime))
1252 {
1253 /* Return success */
1254 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
1255 }
1256 else
1257 {
1258 /* Return failure */
1259 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
1260 }
1261
1262 break;
1263 }
1264
1265 /* Get system time */
1266 case 0x2C:
1267 {
1268 GetLocalTime(&SystemTime);
1269 EmulatorSetRegister(EMULATOR_REG_CX,
1270 (Ecx & 0xFFFF0000)
1271 | (SystemTime.wHour << 8)
1272 | SystemTime.wMinute);
1273 EmulatorSetRegister(EMULATOR_REG_DX,
1274 (Edx & 0xFFFF0000)
1275 | (SystemTime.wSecond << 8)
1276 | (SystemTime.wMilliseconds / 10));
1277 break;
1278 }
1279
1280 /* Set system time */
1281 case 0x2D:
1282 {
1283 GetLocalTime(&SystemTime);
1284 SystemTime.wHour = HIBYTE(Ecx);
1285 SystemTime.wMinute = LOBYTE(Ecx);
1286 SystemTime.wSecond = HIBYTE(Edx);
1287 SystemTime.wMilliseconds = LOBYTE(Edx) * 10;
1288
1289 if (SetLocalTime(&SystemTime))
1290 {
1291 /* Return success */
1292 EmulatorSetRegister(EMULATOR_REG_AX, Eax & 0xFFFFFF00);
1293 }
1294 else
1295 {
1296 /* Return failure */
1297 EmulatorSetRegister(EMULATOR_REG_AX, Eax | 0xFF);
1298 }
1299
1300 break;
1301 }
1302
1303 /* Get Disk Transfer Area */
1304 case 0x2F:
1305 {
1306 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(DiskTransferArea));
1307 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(DiskTransferArea));
1308
1309 break;
1310 }
1311
1312 /* Get DOS Version */
1313 case 0x30:
1314 {
1315 PDOS_PSP PspBlock = SEGMENT_TO_PSP(CurrentPsp);
1316
1317 EmulatorSetRegister(EMULATOR_REG_AX, PspBlock->DosVersion);
1318 break;
1319 }
1320
1321 /* Get Interrupt Vector */
1322 case 0x35:
1323 {
1324 DWORD FarPointer = ((PDWORD)BaseAddress)[LOBYTE(Eax)];
1325
1326 /* Read the address from the IDT into ES:BX */
1327 EmulatorSetRegister(EMULATOR_REG_ES, HIWORD(FarPointer));
1328 EmulatorSetRegister(EMULATOR_REG_BX, LOWORD(FarPointer));
1329
1330 break;
1331 }
1332
1333 /* Create Directory */
1334 case 0x39:
1335 {
1336 String = (PCHAR)((ULONG_PTR)BaseAddress
1337 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1338
1339 if (CreateDirectoryA(String, NULL))
1340 {
1341 EmulatorClearFlag(EMULATOR_FLAG_CF);
1342 }
1343 else
1344 {
1345 EmulatorSetFlag(EMULATOR_FLAG_CF);
1346 EmulatorSetRegister(EMULATOR_REG_AX,
1347 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1348 }
1349
1350 break;
1351 }
1352
1353 /* Remove Directory */
1354 case 0x3A:
1355 {
1356 String = (PCHAR)((ULONG_PTR)BaseAddress
1357 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1358
1359 if (RemoveDirectoryA(String))
1360 {
1361 EmulatorClearFlag(EMULATOR_FLAG_CF);
1362 }
1363 else
1364 {
1365 EmulatorSetFlag(EMULATOR_FLAG_CF);
1366 EmulatorSetRegister(EMULATOR_REG_AX,
1367 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1368 }
1369
1370
1371 break;
1372 }
1373
1374 /* Set Current Directory */
1375 case 0x3B:
1376 {
1377 String = (PCHAR)((ULONG_PTR)BaseAddress
1378 + TO_LINEAR(DataSegment, LOWORD(Edx)));
1379
1380 if (SetCurrentDirectoryA(String))
1381 {
1382 EmulatorClearFlag(EMULATOR_FLAG_CF);
1383 }
1384 else
1385 {
1386 EmulatorSetFlag(EMULATOR_FLAG_CF);
1387 EmulatorSetRegister(EMULATOR_REG_AX,
1388 (Eax & 0xFFFF0000) | LOWORD(GetLastError()));
1389 }
1390
1391 break;
1392 }
1393
1394 /* Create File */
1395 case 0x3C:
1396 {
1397 WORD FileHandle;
1398 WORD ErrorCode = DosCreateFile(&FileHandle,
1399 (LPCSTR)(ULONG_PTR)BaseAddress
1400 + TO_LINEAR(DataSegment, LOWORD(Edx)),
1401 LOWORD(Ecx));
1402
1403 if (ErrorCode == 0)
1404 {
1405 /* Clear CF */
1406 EmulatorClearFlag(EMULATOR_FLAG_CF);
1407
1408 /* Return the handle in AX */
1409 EmulatorSetRegister(EMULATOR_REG_AX,
1410 (Eax & 0xFFFF0000) | FileHandle);
1411 }
1412 else
1413 {
1414 /* Set CF */
1415 EmulatorSetFlag(EMULATOR_FLAG_CF);
1416
1417 /* Return the error code in AX */
1418 EmulatorSetRegister(EMULATOR_REG_AX,
1419 (Eax & 0xFFFF0000) | ErrorCode);
1420 }
1421
1422 break;
1423 }
1424
1425 /* Open File */
1426 case 0x3D:
1427 {
1428 WORD FileHandle;
1429 WORD ErrorCode = DosCreateFile(&FileHandle,
1430 (LPCSTR)(ULONG_PTR)BaseAddress
1431 + TO_LINEAR(DataSegment, LOWORD(Edx)),
1432 LOBYTE(Eax));
1433
1434 if (ErrorCode == 0)
1435 {
1436 /* Clear CF */
1437 EmulatorClearFlag(EMULATOR_FLAG_CF);
1438
1439 /* Return the handle in AX */
1440 EmulatorSetRegister(EMULATOR_REG_AX,
1441 (Eax & 0xFFFF0000) | FileHandle);
1442 }
1443 else
1444 {
1445 /* Set CF */
1446 EmulatorSetFlag(EMULATOR_FLAG_CF);
1447
1448 /* Return the error code in AX */
1449 EmulatorSetRegister(EMULATOR_REG_AX,
1450 (Eax & 0xFFFF0000) | ErrorCode);
1451 }
1452
1453 break;
1454 }
1455
1456 /* Close File */
1457 case 0x3E:
1458 {
1459 if (DosCloseHandle(LOWORD(Ebx)))
1460 {
1461 /* Clear CF */
1462 EmulatorClearFlag(EMULATOR_FLAG_CF);
1463 }
1464 else
1465 {
1466 /* Set CF */
1467 EmulatorSetFlag(EMULATOR_FLAG_CF);
1468
1469 /* Return the error code in AX */
1470 EmulatorSetRegister(EMULATOR_REG_AX,
1471 (Eax & 0xFFFF0000) | ERROR_INVALID_HANDLE);
1472 }
1473
1474 break;
1475 }
1476
1477 /* Read File */
1478 case 0x3F:
1479 {
1480 WORD BytesRead = 0;
1481 WORD ErrorCode = DosReadFile(LOWORD(Ebx),
1482 (LPVOID)((ULONG_PTR)BaseAddress
1483 + TO_LINEAR(DataSegment, LOWORD(Edx))),
1484 LOWORD(Ecx),
1485 &BytesRead);
1486
1487 if (ErrorCode == 0)
1488 {
1489 /* Clear CF */
1490 EmulatorClearFlag(EMULATOR_FLAG_CF);
1491
1492 /* Return the number of bytes read in AX */
1493 EmulatorSetRegister(EMULATOR_REG_AX,
1494 (Eax & 0xFFFF0000) | BytesRead);
1495 }
1496 else
1497 {
1498 /* Set CF */
1499 EmulatorSetFlag(EMULATOR_FLAG_CF);
1500
1501 /* Return the error code in AX */
1502 EmulatorSetRegister(EMULATOR_REG_AX,
1503 (Eax & 0xFFFF0000) | ErrorCode);
1504 }
1505 break;
1506 }
1507
1508 /* Write File */
1509 case 0x40:
1510 {
1511 WORD BytesWritten = 0;
1512 WORD ErrorCode = DosWriteFile(LOWORD(Ebx),
1513 (LPVOID)((ULONG_PTR)BaseAddress
1514 + TO_LINEAR(DataSegment, LOWORD(Edx))),
1515 LOWORD(Ecx),
1516 &BytesWritten);
1517
1518 if (ErrorCode == 0)
1519 {
1520 /* Clear CF */
1521 EmulatorClearFlag(EMULATOR_FLAG_CF);
1522
1523 /* Return the number of bytes written in AX */
1524 EmulatorSetRegister(EMULATOR_REG_AX,
1525 (Eax & 0xFFFF0000) | BytesWritten);
1526 }
1527 else
1528 {
1529 /* Set CF */
1530 EmulatorSetFlag(EMULATOR_FLAG_CF);
1531
1532 /* Return the error code in AX */
1533 EmulatorSetRegister(EMULATOR_REG_AX,
1534 (Eax & 0xFFFF0000) | ErrorCode);
1535 }
1536
1537 break;
1538 }
1539
1540 /* IOCTL */
1541 case 0x44:
1542 {
1543 DosHandleIoctl(LOBYTE(Eax), LOWORD(Ebx));
1544
1545 break;
1546 }
1547
1548 /* Allocate Memory */
1549 case 0x48:
1550 {
1551 WORD MaxAvailable = 0;
1552 WORD Segment = DosAllocateMemory(LOWORD(Ebx), &MaxAvailable);
1553
1554 if (Segment != 0)
1555 {
1556 EmulatorSetRegister(EMULATOR_REG_AX, Segment);
1557 EmulatorClearFlag(EMULATOR_FLAG_CF);
1558 }
1559 else
1560 {
1561 EmulatorSetRegister(EMULATOR_REG_AX, DosLastError);
1562 EmulatorSetRegister(EMULATOR_REG_BX, MaxAvailable);
1563 EmulatorSetFlag(EMULATOR_FLAG_CF);
1564 }
1565
1566 break;
1567 }
1568
1569 /* Free Memory */
1570 case 0x49:
1571 {
1572 if (DosFreeMemory(ExtSegment))
1573 {
1574 EmulatorClearFlag(EMULATOR_FLAG_CF);
1575 }
1576 else
1577 {
1578 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_ARENA_TRASHED);
1579 EmulatorSetFlag(EMULATOR_FLAG_CF);
1580 }
1581
1582 break;
1583 }
1584
1585 /* Resize Memory Block */
1586 case 0x4A:
1587 {
1588 WORD Size;
1589
1590 if (DosResizeMemory(ExtSegment, LOWORD(Ebx), &Size))
1591 {
1592 EmulatorClearFlag(EMULATOR_FLAG_CF);
1593 }
1594 else
1595 {
1596 EmulatorSetRegister(EMULATOR_REG_AX, DosLastError);
1597 EmulatorSetFlag(EMULATOR_FLAG_CF);
1598 EmulatorSetRegister(EMULATOR_REG_BX, Size);
1599 }
1600
1601 break;
1602 }
1603
1604 /* Terminate With Return Code */
1605 case 0x4C:
1606 {
1607 DosTerminateProcess(CurrentPsp, LOBYTE(Eax));
1608 break;
1609 }
1610
1611 /* Get/Set Memory Management Options */
1612 case 0x58:
1613 {
1614 if (LOBYTE(Eax) == 0x00)
1615 {
1616 /* Get allocation strategy */
1617
1618 EmulatorSetRegister(EMULATOR_REG_AX, DosAllocStrategy);
1619 EmulatorClearFlag(EMULATOR_FLAG_CF);
1620 }
1621 else if (LOBYTE(Eax) == 0x01)
1622 {
1623 /* Set allocation strategy */
1624
1625 if ((LOBYTE(Ebx) & (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
1626 == (DOS_ALLOC_HIGH | DOS_ALLOC_HIGH_LOW))
1627 {
1628 /* Can't set both */
1629 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER);
1630 EmulatorSetFlag(EMULATOR_FLAG_CF);
1631 break;
1632 }
1633
1634 if ((LOBYTE(Ebx) & 0x3F) > DOS_ALLOC_LAST_FIT)
1635 {
1636 /* Invalid allocation strategy */
1637 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_PARAMETER);
1638 EmulatorSetFlag(EMULATOR_FLAG_CF);
1639 break;
1640 }
1641
1642 DosAllocStrategy = LOBYTE(Ebx);
1643 EmulatorClearFlag(EMULATOR_FLAG_CF);
1644 }
1645 else if (LOBYTE(Eax) == 0x02)
1646 {
1647 /* Get UMB link state */
1648
1649 Eax &= 0xFFFFFF00;
1650 if (DosUmbLinked) Eax |= 1;
1651 EmulatorSetRegister(EMULATOR_REG_AX, Eax);
1652 EmulatorClearFlag(EMULATOR_FLAG_CF);
1653 }
1654 else if (LOBYTE(Eax) == 0x03)
1655 {
1656 /* Set UMB link state */
1657
1658 if (Ebx) DosLinkUmb();
1659 else DosUnlinkUmb();
1660 EmulatorClearFlag(EMULATOR_FLAG_CF);
1661 }
1662 else
1663 {
1664 /* Invalid or unsupported function */
1665
1666 EmulatorSetFlag(EMULATOR_FLAG_CF);
1667 EmulatorSetRegister(EMULATOR_REG_AX, ERROR_INVALID_FUNCTION);
1668 }
1669
1670 break;
1671 }
1672
1673 /* Unsupported */
1674 default:
1675 {
1676 DPRINT1("DOS Function INT 0x21, AH = 0x%02X NOT IMPLEMENTED!\n", HIBYTE(Eax));
1677 EmulatorSetFlag(EMULATOR_FLAG_CF);
1678 }
1679 }
1680 }
1681
1682 VOID DosBreakInterrupt(VOID)
1683 {
1684 VdmRunning = FALSE;
1685 }
1686
1687 BOOLEAN DosInitialize(VOID)
1688 {
1689 BYTE i;
1690 PDOS_MCB Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT);
1691 FILE *Stream;
1692 WCHAR Buffer[256];
1693 LPWSTR SourcePtr, Environment;
1694 LPSTR AsciiString;
1695 LPSTR DestPtr = (LPSTR)((ULONG_PTR)BaseAddress + TO_LINEAR(SYSTEM_ENV_BLOCK, 0));
1696 DWORD AsciiSize;
1697
1698 /* Initialize the MCB */
1699 Mcb->BlockType = 'Z';
1700 Mcb->Size = USER_MEMORY_SIZE;
1701 Mcb->OwnerPsp = 0;
1702
1703 /* Initialize the link MCB to the UMB area */
1704 Mcb = SEGMENT_TO_MCB(FIRST_MCB_SEGMENT + USER_MEMORY_SIZE + 1);
1705 Mcb->BlockType = 'M';
1706 Mcb->Size = UMB_START_SEGMENT - FIRST_MCB_SEGMENT - USER_MEMORY_SIZE - 2;
1707 Mcb->OwnerPsp = SYSTEM_PSP;
1708
1709 /* Initialize the UMB area */
1710 Mcb = SEGMENT_TO_MCB(UMB_START_SEGMENT);
1711 Mcb->BlockType = 'Z';
1712 Mcb->Size = UMB_END_SEGMENT - UMB_START_SEGMENT;
1713 Mcb->OwnerPsp = 0;
1714
1715 /* Get the environment strings */
1716 SourcePtr = Environment = GetEnvironmentStringsW();
1717 if (Environment == NULL) return FALSE;
1718
1719 /* Fill the DOS system environment block */
1720 while (*SourcePtr)
1721 {
1722 /* Get the size of the ASCII string */
1723 AsciiSize = WideCharToMultiByte(CP_ACP,
1724 0,
1725 SourcePtr,
1726 -1,
1727 NULL,
1728 0,
1729 NULL,
1730 NULL);
1731
1732 /* Allocate memory for the ASCII string */
1733 AsciiString = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, AsciiSize);
1734 if (AsciiString == NULL)
1735 {
1736 FreeEnvironmentStringsW(Environment);
1737 return FALSE;
1738 }
1739
1740 /* Convert to ASCII */
1741 WideCharToMultiByte(CP_ACP,
1742 0,
1743 SourcePtr,
1744 -1,
1745 AsciiString,
1746 AsciiSize,
1747 NULL,
1748 NULL);
1749
1750 /* Copy the string into DOS memory */
1751 strcpy(DestPtr, AsciiString);
1752
1753 /* Free the memory */
1754 HeapFree(GetProcessHeap(), 0, AsciiString);
1755
1756 /* Move to the next string */
1757 SourcePtr += wcslen(SourcePtr) + 1;
1758 DestPtr += strlen(AsciiString);
1759 *(DestPtr++) = 0;
1760 }
1761 *DestPtr = 0;
1762
1763 /* Free the memory allocated for environment strings */
1764 FreeEnvironmentStringsW(Environment);
1765
1766 /* Read CONFIG.SYS */
1767 Stream = _wfopen(DOS_CONFIG_PATH, L"r");
1768 if (Stream != NULL)
1769 {
1770 while (fgetws(Buffer, 256, Stream))
1771 {
1772 // TODO: Parse the line
1773 }
1774 fclose(Stream);
1775 }
1776
1777 /* Initialize the SFT */
1778 for (i = 0; i < DOS_SFT_SIZE; i++)
1779 {
1780 DosSystemFileTable[i] = INVALID_HANDLE_VALUE;
1781 DosSftRefCount[i] = 0;
1782 }
1783
1784 /* Get handles to standard I/O devices */
1785 DosSystemFileTable[0] = GetStdHandle(STD_INPUT_HANDLE);
1786 DosSystemFileTable[1] = GetStdHandle(STD_OUTPUT_HANDLE);
1787 DosSystemFileTable[2] = GetStdHandle(STD_ERROR_HANDLE);
1788
1789 return TRUE;
1790 }
1791
1792 /* EOF */