1 /* 2 * COPYRIGHT: GPL - See COPYING in the top level directory 3 * PROJECT: ReactOS Virtual DOS Machine 4 * FILE: subsystems/mvdm/ntvdm/ntvdm.c 5 * PURPOSE: Virtual DOS Machine 6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org> 7 */ 8 9 /* INCLUDES *******************************************************************/ 10 11 #include "ntvdm.h" 12 13 #define NDEBUG 14 #include <debug.h> 15 16 #include "emulator.h" 17 18 #include "bios/bios.h" 19 #include "cpu/cpu.h" 20 21 #include "dos/dem.h" 22 23 /* Extra PSDK/NDK Headers */ 24 #include <ndk/psfuncs.h> 25 26 /* VARIABLES ******************************************************************/ 27 28 NTVDM_SETTINGS GlobalSettings; 29 30 // Command line of NTVDM 31 INT NtVdmArgc; 32 WCHAR** NtVdmArgv; 33 34 /* Full directory where NTVDM resides, or the SystemRoot\System32 path */ 35 WCHAR NtVdmPath[MAX_PATH]; 36 ULONG NtVdmPathSize; // Length without NULL terminator. 37 38 /* PRIVATE FUNCTIONS **********************************************************/ 39 40 static NTSTATUS 41 NTAPI 42 NtVdmConfigureBios(IN PWSTR ValueName, 43 IN ULONG ValueType, 44 IN PVOID ValueData, 45 IN ULONG ValueLength, 46 IN PVOID Context, 47 IN PVOID EntryContext) 48 { 49 PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context; 50 UNICODE_STRING ValueString; 51 52 /* Check for the type of the value */ 53 if (ValueType != REG_SZ) 54 { 55 RtlInitEmptyAnsiString(&Settings->BiosFileName, NULL, 0); 56 return STATUS_SUCCESS; 57 } 58 59 /* Convert the UNICODE string to ANSI and store it */ 60 RtlInitEmptyUnicodeString(&ValueString, (PWCHAR)ValueData, ValueLength); 61 ValueString.Length = ValueString.MaximumLength; 62 RtlUnicodeStringToAnsiString(&Settings->BiosFileName, &ValueString, TRUE); 63 64 return STATUS_SUCCESS; 65 } 66 67 static NTSTATUS 68 NTAPI 69 NtVdmConfigureRom(IN PWSTR ValueName, 70 IN ULONG ValueType, 71 IN PVOID ValueData, 72 IN ULONG ValueLength, 73 IN PVOID Context, 74 IN PVOID EntryContext) 75 { 76 PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context; 77 UNICODE_STRING ValueString; 78 79 /* Check for the type of the value */ 80 if (ValueType != REG_MULTI_SZ) 81 { 82 RtlInitEmptyAnsiString(&Settings->RomFiles, NULL, 0); 83 return STATUS_SUCCESS; 84 } 85 86 /* Convert the UNICODE string to ANSI and store it */ 87 RtlInitEmptyUnicodeString(&ValueString, (PWCHAR)ValueData, ValueLength); 88 ValueString.Length = ValueString.MaximumLength; 89 RtlUnicodeStringToAnsiString(&Settings->RomFiles, &ValueString, TRUE); 90 91 return STATUS_SUCCESS; 92 } 93 94 static NTSTATUS 95 NTAPI 96 NtVdmConfigureFloppy(IN PWSTR ValueName, 97 IN ULONG ValueType, 98 IN PVOID ValueData, 99 IN ULONG ValueLength, 100 IN PVOID Context, 101 IN PVOID EntryContext) 102 { 103 BOOLEAN Success; 104 PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context; 105 ULONG DiskNumber = PtrToUlong(EntryContext); 106 107 ASSERT(DiskNumber < ARRAYSIZE(Settings->FloppyDisks)); 108 109 /* Check whether the Hard Disk entry was not already configured */ 110 if (Settings->FloppyDisks[DiskNumber].Buffer != NULL) 111 { 112 DPRINT1("Floppy Disk %d -- '%wZ' already configured\n", DiskNumber, &Settings->FloppyDisks[DiskNumber]); 113 return STATUS_SUCCESS; 114 } 115 116 /* Check for the type of the value */ 117 if (ValueType != REG_SZ) 118 { 119 RtlInitEmptyUnicodeString(&Settings->FloppyDisks[DiskNumber], NULL, 0); 120 return STATUS_SUCCESS; 121 } 122 123 /* Initialize the string */ 124 Success = RtlCreateUnicodeString(&Settings->FloppyDisks[DiskNumber], (PCWSTR)ValueData); 125 ASSERT(Success); 126 127 return STATUS_SUCCESS; 128 } 129 130 static NTSTATUS 131 NTAPI 132 NtVdmConfigureHDD(IN PWSTR ValueName, 133 IN ULONG ValueType, 134 IN PVOID ValueData, 135 IN ULONG ValueLength, 136 IN PVOID Context, 137 IN PVOID EntryContext) 138 { 139 BOOLEAN Success; 140 PNTVDM_SETTINGS Settings = (PNTVDM_SETTINGS)Context; 141 ULONG DiskNumber = PtrToUlong(EntryContext); 142 143 ASSERT(DiskNumber < ARRAYSIZE(Settings->HardDisks)); 144 145 /* Check whether the Hard Disk entry was not already configured */ 146 if (Settings->HardDisks[DiskNumber].Buffer != NULL) 147 { 148 DPRINT1("Hard Disk %d -- '%wZ' already configured\n", DiskNumber, &Settings->HardDisks[DiskNumber]); 149 return STATUS_SUCCESS; 150 } 151 152 /* Check for the type of the value */ 153 if (ValueType != REG_SZ) 154 { 155 RtlInitEmptyUnicodeString(&Settings->HardDisks[DiskNumber], NULL, 0); 156 return STATUS_SUCCESS; 157 } 158 159 /* Initialize the string */ 160 Success = RtlCreateUnicodeString(&Settings->HardDisks[DiskNumber], (PCWSTR)ValueData); 161 ASSERT(Success); 162 163 return STATUS_SUCCESS; 164 } 165 166 static RTL_QUERY_REGISTRY_TABLE 167 NtVdmConfigurationTable[] = 168 { 169 { 170 NtVdmConfigureBios, 171 0, 172 L"BiosFile", 173 NULL, 174 REG_NONE, 175 NULL, 176 0 177 }, 178 179 { 180 NtVdmConfigureRom, 181 RTL_QUERY_REGISTRY_NOEXPAND, 182 L"RomFiles", 183 NULL, 184 REG_NONE, 185 NULL, 186 0 187 }, 188 189 { 190 NtVdmConfigureFloppy, 191 0, 192 L"FloppyDisk0", 193 (PVOID)0, 194 REG_NONE, 195 NULL, 196 0 197 }, 198 199 { 200 NtVdmConfigureFloppy, 201 0, 202 L"FloppyDisk1", 203 (PVOID)1, 204 REG_NONE, 205 NULL, 206 0 207 }, 208 209 { 210 NtVdmConfigureHDD, 211 0, 212 L"HardDisk0", 213 (PVOID)0, 214 REG_NONE, 215 NULL, 216 0 217 }, 218 219 { 220 NtVdmConfigureHDD, 221 0, 222 L"HardDisk1", 223 (PVOID)1, 224 REG_NONE, 225 NULL, 226 0 227 }, 228 229 { 230 NtVdmConfigureHDD, 231 0, 232 L"HardDisk2", 233 (PVOID)2, 234 REG_NONE, 235 NULL, 236 0 237 }, 238 239 { 240 NtVdmConfigureHDD, 241 0, 242 L"HardDisk3", 243 (PVOID)3, 244 REG_NONE, 245 NULL, 246 0 247 }, 248 249 /* End of table */ 250 {0} 251 }; 252 253 static BOOL 254 LoadGlobalSettings(IN PNTVDM_SETTINGS Settings) 255 { 256 NTSTATUS Status; 257 258 ASSERT(Settings); 259 260 /* 261 * Now we can do: 262 * - CPU core choice 263 * - Video choice 264 * - Sound choice 265 * - Mem? 266 * - ... 267 * - Standalone mode? 268 * - Debug settings 269 */ 270 Status = RtlQueryRegistryValues(RTL_REGISTRY_CONTROL, 271 L"NTVDM", 272 NtVdmConfigurationTable, 273 Settings, 274 NULL); 275 if (!NT_SUCCESS(Status)) 276 { 277 DPRINT1("NTVDM registry settings cannot be fully initialized, using default ones. Status = 0x%08lx\n", Status); 278 } 279 280 return NT_SUCCESS(Status); 281 } 282 283 static VOID 284 FreeGlobalSettings(IN PNTVDM_SETTINGS Settings) 285 { 286 USHORT i; 287 288 ASSERT(Settings); 289 290 if (Settings->BiosFileName.Buffer) 291 RtlFreeAnsiString(&Settings->BiosFileName); 292 293 if (Settings->RomFiles.Buffer) 294 RtlFreeAnsiString(&Settings->RomFiles); 295 296 for (i = 0; i < ARRAYSIZE(Settings->FloppyDisks); ++i) 297 { 298 if (Settings->FloppyDisks[i].Buffer) 299 RtlFreeUnicodeString(&Settings->FloppyDisks[i]); 300 } 301 302 for (i = 0; i < ARRAYSIZE(Settings->HardDisks); ++i) 303 { 304 if (Settings->HardDisks[i].Buffer) 305 RtlFreeUnicodeString(&Settings->HardDisks[i]); 306 } 307 } 308 309 static VOID 310 ConsoleCleanup(VOID); 311 312 /** HACK!! **/ 313 #include "./console/console.c" 314 /** HACK!! **/ 315 316 /*static*/ VOID 317 VdmShutdown(BOOLEAN Immediate) 318 { 319 /* 320 * Immediate = TRUE: Immediate shutdown; 321 * FALSE: Delayed shutdown. 322 */ 323 static BOOLEAN MustShutdown = FALSE; 324 325 /* If a shutdown is ongoing, just return */ 326 if (MustShutdown) 327 { 328 DPRINT1("Shutdown is ongoing...\n"); 329 Sleep(INFINITE); 330 return; 331 } 332 333 /* First notify DOS to see whether we can shut down now */ 334 MustShutdown = DosShutdown(Immediate); 335 /* 336 * In case we perform an immediate shutdown, or the DOS says 337 * we can shut down, do it now. 338 */ 339 MustShutdown = MustShutdown || Immediate; 340 341 if (MustShutdown) 342 { 343 EmulatorTerminate(); 344 345 BiosCleanup(); 346 EmulatorCleanup(); 347 ConsoleCleanup(); 348 349 FreeGlobalSettings(&GlobalSettings); 350 351 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n"); 352 /* Some VDDs rely on the fact that NTVDM calls ExitProcess on Windows */ 353 ExitProcess(0); 354 } 355 } 356 357 /* PUBLIC FUNCTIONS ***********************************************************/ 358 359 VOID 360 DisplayMessage(IN LPCWSTR Format, ...) 361 { 362 #ifndef WIN2K_COMPLIANT 363 WCHAR StaticBuffer[256]; 364 LPWSTR Buffer = StaticBuffer; // Use the static buffer by default. 365 #else 366 WCHAR Buffer[2048]; // Large enough. If not, increase it by hand. 367 #endif 368 size_t MsgLen; 369 va_list args; 370 371 va_start(args, Format); 372 373 #ifndef WIN2K_COMPLIANT 374 /* 375 * Retrieve the message length and if it is too long, allocate 376 * an auxiliary buffer; otherwise use the static buffer. 377 * The string is built to be NULL-terminated. 378 */ 379 MsgLen = _vscwprintf(Format, args); 380 if (MsgLen >= ARRAYSIZE(StaticBuffer)) 381 { 382 Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(WCHAR)); 383 if (Buffer == NULL) 384 { 385 /* Allocation failed, use the static buffer and display a suitable error message */ 386 Buffer = StaticBuffer; 387 Format = L"DisplayMessage()\nOriginal message is too long and allocating an auxiliary buffer failed."; 388 MsgLen = wcslen(Format); 389 } 390 } 391 #else 392 MsgLen = ARRAYSIZE(Buffer) - 1; 393 #endif 394 395 RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(WCHAR)); 396 _vsnwprintf(Buffer, MsgLen, Format, args); 397 398 va_end(args); 399 400 /* Display the message */ 401 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer); 402 MessageBoxW(hConsoleWnd, Buffer, L"NTVDM Subsystem", MB_OK); 403 404 #ifndef WIN2K_COMPLIANT 405 /* Free the buffer if needed */ 406 if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer); 407 #endif 408 } 409 410 /* 411 * This function, derived from DisplayMessage, is used by the BIOS and 412 * the DOS to display messages to an output device. A printer function 413 * is given for printing the characters. 414 */ 415 VOID 416 PrintMessageAnsi(IN CHAR_PRINT CharPrint, 417 IN LPCSTR Format, ...) 418 { 419 static CHAR CurChar = 0; 420 LPSTR str; 421 422 #ifndef WIN2K_COMPLIANT 423 CHAR StaticBuffer[256]; 424 LPSTR Buffer = StaticBuffer; // Use the static buffer by default. 425 #else 426 CHAR Buffer[2048]; // Large enough. If not, increase it by hand. 427 #endif 428 size_t MsgLen; 429 va_list args; 430 431 va_start(args, Format); 432 433 #ifndef WIN2K_COMPLIANT 434 /* 435 * Retrieve the message length and if it is too long, allocate 436 * an auxiliary buffer; otherwise use the static buffer. 437 * The string is built to be NULL-terminated. 438 */ 439 MsgLen = _vscprintf(Format, args); 440 if (MsgLen >= ARRAYSIZE(StaticBuffer)) 441 { 442 Buffer = RtlAllocateHeap(RtlGetProcessHeap(), HEAP_ZERO_MEMORY, (MsgLen + 1) * sizeof(CHAR)); 443 if (Buffer == NULL) 444 { 445 /* Allocation failed, use the static buffer and display a suitable error message */ 446 Buffer = StaticBuffer; 447 Format = "DisplayMessageAnsi()\nOriginal message is too long and allocating an auxiliary buffer failed."; 448 MsgLen = strlen(Format); 449 } 450 } 451 #else 452 MsgLen = ARRAYSIZE(Buffer) - 1; 453 #endif 454 455 RtlZeroMemory(Buffer, (MsgLen + 1) * sizeof(CHAR)); 456 _vsnprintf(Buffer, MsgLen, Format, args); 457 458 va_end(args); 459 460 /* Display the message */ 461 // DPRINT1("\n\nNTVDM DOS32\n%s\n\n", Buffer); 462 463 MsgLen = strlen(Buffer); 464 str = Buffer; 465 while (MsgLen--) 466 { 467 if (*str == '\n' && CurChar != '\r') 468 CharPrint('\r'); 469 470 CurChar = *str++; 471 CharPrint(CurChar); 472 } 473 474 #ifndef WIN2K_COMPLIANT 475 /* Free the buffer if needed */ 476 if (Buffer != StaticBuffer) RtlFreeHeap(RtlGetProcessHeap(), 0, Buffer); 477 #endif 478 } 479 480 INT 481 wmain(INT argc, WCHAR *argv[]) 482 { 483 BOOL Success; 484 485 #ifdef STANDALONE 486 487 if (argc < 2) 488 { 489 wprintf(L"\nReactOS Virtual DOS Machine\n\n" 490 L"Usage: NTVDM <executable> [<parameters>]\n"); 491 return 0; 492 } 493 494 #else 495 496 /* For non-STANDALONE builds, we must be started as a VDM */ 497 NTSTATUS Status; 498 ULONG VdmPower = 0; 499 Status = NtQueryInformationProcess(NtCurrentProcess(), 500 ProcessWx86Information, 501 &VdmPower, 502 sizeof(VdmPower), 503 NULL); 504 if (!NT_SUCCESS(Status) || (VdmPower == 0)) 505 { 506 /* Not a VDM, bail out */ 507 return 0; 508 } 509 510 #endif 511 512 NtVdmArgc = argc; 513 NtVdmArgv = argv; 514 515 #ifdef ADVANCED_DEBUGGING 516 { 517 INT i = 20; 518 519 printf("Waiting for debugger (10 secs).."); 520 while (i--) 521 { 522 printf("."); 523 if (IsDebuggerPresent()) 524 { 525 DbgBreakPoint(); 526 break; 527 } 528 Sleep(500); 529 } 530 printf("Continue\n"); 531 } 532 #endif 533 534 DPRINT1("\n\n\n" 535 "NTVDM - Starting...\n" 536 "Command Line: '%s'\n" 537 "\n\n", 538 GetCommandLineA()); 539 540 /* 541 * Retrieve the full directory of the current running NTVDM instance. 542 * In case of failure, use the default SystemRoot\System32 path. 543 */ 544 NtVdmPathSize = GetModuleFileNameW(NULL, NtVdmPath, _countof(NtVdmPath)); 545 NtVdmPath[_countof(NtVdmPath) - 1] = UNICODE_NULL; // Ensure NULL-termination (see WinXP bug) 546 547 Success = ((NtVdmPathSize != 0) && (NtVdmPathSize < _countof(NtVdmPath)) && 548 (GetLastError() != ERROR_INSUFFICIENT_BUFFER)); 549 if (Success) 550 { 551 /* Find the last path separator, remove it as well as the file name */ 552 PWCHAR pch = wcsrchr(NtVdmPath, L'\\'); 553 if (pch) 554 *pch = UNICODE_NULL; 555 } 556 else 557 { 558 /* We failed, use the default SystemRoot\System32 path */ 559 NtVdmPathSize = GetSystemDirectoryW(NtVdmPath, _countof(NtVdmPath)); 560 Success = ((NtVdmPathSize != 0) && (NtVdmPathSize < _countof(NtVdmPath))); 561 if (!Success) 562 { 563 /* We failed again, try to do it ourselves */ 564 NtVdmPathSize = (ULONG)wcslen(SharedUserData->NtSystemRoot) + _countof("\\System32") - 1; 565 Success = (NtVdmPathSize < _countof(NtVdmPath)); 566 if (Success) 567 { 568 Success = NT_SUCCESS(RtlStringCchPrintfW(NtVdmPath, 569 _countof(NtVdmPath), 570 L"%s\\System32", 571 SharedUserData->NtSystemRoot)); 572 } 573 if (!Success) 574 { 575 wprintf(L"FATAL: Could not retrieve NTVDM path.\n"); 576 goto Cleanup; 577 } 578 } 579 } 580 NtVdmPathSize = (ULONG)wcslen(NtVdmPath); 581 582 /* Load the global VDM settings */ 583 LoadGlobalSettings(&GlobalSettings); 584 585 /* Initialize the console */ 586 if (!ConsoleInit()) 587 { 588 wprintf(L"FATAL: A problem occurred when trying to initialize the console.\n"); 589 goto Cleanup; 590 } 591 592 /* Initialize the emulator */ 593 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput)) 594 { 595 wprintf(L"FATAL: Failed to initialize the emulator.\n"); 596 goto Cleanup; 597 } 598 599 /* Initialize the system BIOS and option ROMs */ 600 if (!BiosInitialize(GlobalSettings.BiosFileName.Buffer, 601 GlobalSettings.RomFiles.Buffer)) 602 { 603 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n"); 604 goto Cleanup; 605 } 606 607 /* Let's go! Start simulation */ 608 CpuSimulate(); 609 610 /* Quit the VDM */ 611 Cleanup: 612 VdmShutdown(TRUE); 613 return 0; 614 } 615 616 /* EOF */ 617