[NTVDM] Retrieve the full directory of the current running NTVDM instance, to be...
[reactos.git] / subsystems / mvdm / ntvdm / ntvdm.c
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 */