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