[NTVDM]
[reactos.git] / subsystems / ntvdm / ntvdm.c
1 /*
2 * COPYRIGHT: GPL - See COPYING in the top level directory
3 * PROJECT: ReactOS Virtual DOS Machine
4 * FILE: ntvdm.c
5 * PURPOSE: Virtual DOS Machine
6 * PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
7 */
8
9 /* INCLUDES *******************************************************************/
10
11 #define NDEBUG
12
13 #include "ntvdm.h"
14 #include "emulator.h"
15
16 #include "bios/bios.h"
17 #include "dos/dem.h"
18 #include "hardware/cmos.h"
19 #include "hardware/ps2.h"
20 #include "hardware/timer.h"
21 #include "hardware/vga.h"
22
23 /*
24 * Activate this line if you want to be able to test NTVDM with:
25 * ntvdm.exe <program>
26 */
27 #define TESTING
28
29 /* PUBLIC VARIABLES ***********************************************************/
30
31 static HANDLE ConsoleInput = INVALID_HANDLE_VALUE;
32 static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
33 static DWORD OrgConsoleInputMode, OrgConsoleOutputMode;
34 static CONSOLE_CURSOR_INFO OrgConsoleCursorInfo;
35 static CONSOLE_SCREEN_BUFFER_INFO OrgConsoleBufferInfo;
36
37 /* PUBLIC FUNCTIONS ***********************************************************/
38
39 VOID DisplayMessage(LPCWSTR Format, ...)
40 {
41 WCHAR Buffer[256];
42 va_list Parameters;
43
44 va_start(Parameters, Format);
45 _vsnwprintf(Buffer, 256, Format, Parameters);
46 DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
47 MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
48 va_end(Parameters);
49 }
50
51 BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
52 {
53 switch (ControlType)
54 {
55 case CTRL_C_EVENT:
56 case CTRL_BREAK_EVENT:
57 {
58 /* Call INT 23h */
59 EmulatorInterrupt(0x23);
60 break;
61 }
62 default:
63 {
64 /* Stop the VDM if the user logs out or closes the console */
65 VdmRunning = FALSE;
66 }
67 }
68 return TRUE;
69 }
70
71 BOOL ConsoleInit(VOID)
72 {
73 /* Set the handler routine */
74 SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
75
76 /* Get the input handle to the real console, and check for success */
77 ConsoleInput = CreateFileW(L"CONIN$",
78 GENERIC_READ | GENERIC_WRITE,
79 FILE_SHARE_READ | FILE_SHARE_WRITE,
80 NULL,
81 OPEN_EXISTING,
82 0,
83 NULL);
84 if (ConsoleInput == INVALID_HANDLE_VALUE)
85 {
86 wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
87 return FALSE;
88 }
89
90 /* Get the output handle to the real console, and check for success */
91 ConsoleOutput = CreateFileW(L"CONOUT$",
92 GENERIC_READ | GENERIC_WRITE,
93 FILE_SHARE_READ | FILE_SHARE_WRITE,
94 NULL,
95 OPEN_EXISTING,
96 0,
97 NULL);
98 if (ConsoleOutput == INVALID_HANDLE_VALUE)
99 {
100 CloseHandle(ConsoleInput);
101 wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
102 return FALSE;
103 }
104
105 /* Save the original input and output console modes */
106 if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
107 !GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
108 {
109 CloseHandle(ConsoleOutput);
110 CloseHandle(ConsoleInput);
111 wprintf(L"FATAL: Cannot save console in/out modes\n");
112 return FALSE;
113 }
114
115 /* Save the original cursor and console screen buffer information */
116 if (!GetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo) ||
117 !GetConsoleScreenBufferInfo(ConsoleOutput, &OrgConsoleBufferInfo))
118 {
119 CloseHandle(ConsoleOutput);
120 CloseHandle(ConsoleInput);
121 wprintf(L"FATAL: Cannot save console cursor/screen-buffer info\n");
122 return FALSE;
123 }
124
125 return TRUE;
126 }
127
128 VOID ConsoleCleanup(VOID)
129 {
130 SMALL_RECT ConRect;
131 CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
132
133 /* Restore the old screen buffer */
134 SetConsoleActiveScreenBuffer(ConsoleOutput);
135
136 /* Restore the original console size */
137 GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo);
138 ConRect.Left = 0; // OrgConsoleBufferInfo.srWindow.Left;
139 // ConRect.Top = ConsoleInfo.dwCursorPosition.Y / (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
140 // ConRect.Top *= (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
141 ConRect.Top = ConsoleInfo.dwCursorPosition.Y;
142 ConRect.Right = ConRect.Left + OrgConsoleBufferInfo.srWindow.Right - OrgConsoleBufferInfo.srWindow.Left;
143 ConRect.Bottom = ConRect.Top + OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top ;
144 /* See the following trick explanation in vga.c:VgaEnterTextMode() */
145 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
146 SetConsoleWindowInfo(ConsoleOutput, TRUE, &ConRect);
147 // SetConsoleWindowInfo(ConsoleOutput, TRUE, &OrgConsoleBufferInfo.srWindow);
148 SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
149
150 /* Restore the original cursor shape */
151 SetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo);
152
153 /* Restore the original input and output console modes */
154 SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
155 SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
156
157 /* Close the console handles */
158 if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
159 if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
160 }
161
162 INT wmain(INT argc, WCHAR *argv[])
163 {
164 INT i;
165 CHAR CommandLine[DOS_CMDLINE_LENGTH];
166 LARGE_INTEGER StartPerfCount;
167 LARGE_INTEGER Frequency, LastTimerTick, LastRtcTick, Counter;
168 LONGLONG TimerTicks;
169 DWORD StartTickCount, CurrentTickCount;
170 DWORD LastClockUpdate;
171 DWORD LastVerticalRefresh;
172 #if 0
173 DWORD LastCyclePrintout;
174 DWORD Cycles = 0;
175 #endif
176 INT KeyboardIntCounter = 0;
177
178 #ifndef TESTING
179 UNREFERENCED_PARAMETER(argc);
180 UNREFERENCED_PARAMETER(argv);
181
182 /* The DOS command line must be ASCII */
183 WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, sizeof(CommandLine), NULL, NULL);
184 #else
185 if (argc == 2 && argv[1] != NULL)
186 {
187 WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
188 }
189 else
190 {
191 wprintf(L"\nReactOS Virtual DOS Machine\n\n"
192 L"Usage: NTVDM <executable>\n");
193 return 0;
194 }
195 #endif
196
197 DPRINT1("\n\n\nNTVDM - Starting '%s'...\n\n\n", CommandLine);
198
199 /* Initialize the console */
200 if (!ConsoleInit())
201 {
202 wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
203 goto Cleanup;
204 }
205
206 /* Initialize the emulator */
207 if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
208 {
209 wprintf(L"FATAL: Failed to initialize the emulator\n");
210 goto Cleanup;
211 }
212
213 /* Initialize the performance counter (needed for hardware timers) */
214 if (!QueryPerformanceFrequency(&Frequency))
215 {
216 wprintf(L"FATAL: Performance counter not available\n");
217 goto Cleanup;
218 }
219
220 /* Initialize the system BIOS */
221 if (!BiosInitialize(ConsoleInput, ConsoleOutput))
222 {
223 wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
224 goto Cleanup;
225 }
226
227 /* Initialize the VDM DOS kernel */
228 if (!DosInitialize(NULL))
229 {
230 wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
231 goto Cleanup;
232 }
233
234 /* Start the process from the command line */
235 if (!DosCreateProcess(CommandLine, 0))
236 {
237 DisplayMessage(L"Could not start program: %S", CommandLine);
238 goto Cleanup;
239 }
240
241 /* Find the starting performance and tick count */
242 StartTickCount = GetTickCount();
243 QueryPerformanceCounter(&StartPerfCount);
244
245 /* Set the different last counts to the starting count */
246 LastClockUpdate = LastVerticalRefresh = /*LastCyclePrintout =*/ StartTickCount;
247
248 /* Set the last timer ticks to the current time */
249 LastTimerTick = LastRtcTick = StartPerfCount;
250
251 /* Main loop */
252 while (VdmRunning)
253 {
254 DWORD PitResolution = PitGetResolution();
255 DWORD RtcFrequency = RtcGetTicksPerSecond();
256
257 /* Get the current number of ticks */
258 CurrentTickCount = GetTickCount();
259
260 if ((PitResolution <= 1000) && (RtcFrequency <= 1000))
261 {
262 /* Calculate the approximate performance counter value instead */
263 Counter.QuadPart = StartPerfCount.QuadPart
264 + (CurrentTickCount - StartTickCount)
265 * (Frequency.QuadPart / 1000);
266 }
267 else
268 {
269 /* Get the current performance counter value */
270 QueryPerformanceCounter(&Counter);
271 }
272
273 /* Get the number of PIT ticks that have passed */
274 TimerTicks = ((Counter.QuadPart - LastTimerTick.QuadPart)
275 * PIT_BASE_FREQUENCY) / Frequency.QuadPart;
276
277 /* Update the PIT */
278 if (TimerTicks > 0)
279 {
280 PitClock(TimerTicks);
281 LastTimerTick = Counter;
282 }
283
284 /* Check for RTC update */
285 if ((CurrentTickCount - LastClockUpdate) >= 1000)
286 {
287 RtcTimeUpdate();
288 LastClockUpdate = CurrentTickCount;
289 }
290
291 /* Check for RTC periodic tick */
292 if ((Counter.QuadPart - LastRtcTick.QuadPart)
293 >= (Frequency.QuadPart / (LONGLONG)RtcFrequency))
294 {
295 RtcPeriodicTick();
296 LastRtcTick = Counter;
297 }
298
299 /* Check for vertical retrace */
300 if ((CurrentTickCount - LastVerticalRefresh) >= 15)
301 {
302 VgaRefreshDisplay();
303 LastVerticalRefresh = CurrentTickCount;
304 }
305
306 KeyboardIntCounter++;
307 if (KeyboardIntCounter == KBD_INT_CYCLES)
308 {
309 GenerateKeyboardInterrupts();
310 KeyboardIntCounter = 0;
311 }
312
313 /* Horizontal retrace occurs as fast as possible */
314 VgaHorizontalRetrace();
315
316 /* Continue CPU emulation */
317 for (i = 0; (i < STEPS_PER_CYCLE) && VdmRunning; i++)
318 {
319 EmulatorStep();
320 #if 0
321 Cycles++;
322 #endif
323 }
324
325 #if 0
326 if ((CurrentTickCount - LastCyclePrintout) >= 1000)
327 {
328 DPRINT1("NTVDM: %lu Instructions Per Second\n", Cycles);
329 LastCyclePrintout = CurrentTickCount;
330 Cycles = 0;
331 }
332 #endif
333 }
334
335 /* Perform another screen refresh */
336 VgaRefreshDisplay();
337
338 Cleanup:
339 BiosCleanup();
340 EmulatorCleanup();
341 ConsoleCleanup();
342
343 DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
344
345 return 0;
346 }
347
348 /* EOF */