xref: /reactos/subsystems/mvdm/ntvdm/ntvdm.c (revision 1bd0e3f9)
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
NtVdmConfigureBios(IN PWSTR ValueName,IN ULONG ValueType,IN PVOID ValueData,IN ULONG ValueLength,IN PVOID Context,IN PVOID EntryContext)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
NtVdmConfigureRom(IN PWSTR ValueName,IN ULONG ValueType,IN PVOID ValueData,IN ULONG ValueLength,IN PVOID Context,IN PVOID EntryContext)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
NtVdmConfigureFloppy(IN PWSTR ValueName,IN ULONG ValueType,IN PVOID ValueData,IN ULONG ValueLength,IN PVOID Context,IN PVOID EntryContext)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
NtVdmConfigureHDD(IN PWSTR ValueName,IN ULONG ValueType,IN PVOID ValueData,IN ULONG ValueLength,IN PVOID Context,IN PVOID EntryContext)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
LoadGlobalSettings(IN PNTVDM_SETTINGS Settings)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
FreeGlobalSettings(IN PNTVDM_SETTINGS Settings)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
VdmShutdown(BOOLEAN Immediate)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
DisplayMessage(IN LPCWSTR Format,...)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
PrintMessageAnsi(IN CHAR_PRINT CharPrint,IN LPCSTR Format,...)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
wmain(INT argc,WCHAR * argv[])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