mirror of
https://github.com/reactos/reactos.git
synced 2024-09-06 02:37:06 +00:00
d25d9ea618
- Enable experimental sound support (only PC speaker for the moment, aka. uses beep.sys). - Introduce a #define WORKING_TIMER which aim is to disable the currently problematic approximate performance counter value calculation done in order not to call QueryPerformanceCounter each time. The problem is that we then compute a number of clock ticks for the PIT, which becomes negative, and therefore everything starts to hang. Disabling this code and calling each time QueryPerformanceCounter, fixes everything; we gain in precision but we loose in performance... A definitive fix must be found, [TheFlash] !! This fixes sound (and hangs) in Advanced NetWars, Dangerous Dave, ElitePlus and Rescue Rover (the games that I've tested so far). svn path=/branches/ntvdm/; revision=61875
369 lines
11 KiB
C
369 lines
11 KiB
C
/*
|
|
* COPYRIGHT: GPL - See COPYING in the top level directory
|
|
* PROJECT: ReactOS Virtual DOS Machine
|
|
* FILE: ntvdm.c
|
|
* PURPOSE: Virtual DOS Machine
|
|
* PROGRAMMERS: Aleksandar Andrejevic <theflash AT sdf DOT lonestar DOT org>
|
|
*/
|
|
|
|
/* INCLUDES *******************************************************************/
|
|
|
|
#define NDEBUG
|
|
|
|
#include "ntvdm.h"
|
|
#include "emulator.h"
|
|
|
|
#include "bios/bios.h"
|
|
#include "dos/dem.h"
|
|
#include "hardware/cmos.h"
|
|
#include "hardware/ps2.h"
|
|
#include "hardware/timer.h"
|
|
#include "hardware/vga.h"
|
|
|
|
/*
|
|
* Activate this line if you want to be able to test NTVDM with:
|
|
* ntvdm.exe <program>
|
|
*/
|
|
#define TESTING
|
|
|
|
/*
|
|
* Activate IPS_DISPLAY if you want to display the
|
|
* number of instructions per second, as well as
|
|
* the computed number of ticks for the PIT.
|
|
*/
|
|
// #define IPS_DISPLAY
|
|
|
|
/*
|
|
* Activate WORKING_TIMER when the PIT timing problem is fixed.
|
|
*/
|
|
// #define WORKING_TIMER
|
|
|
|
/* PUBLIC VARIABLES ***********************************************************/
|
|
|
|
static HANDLE ConsoleInput = INVALID_HANDLE_VALUE;
|
|
static HANDLE ConsoleOutput = INVALID_HANDLE_VALUE;
|
|
static DWORD OrgConsoleInputMode, OrgConsoleOutputMode;
|
|
static CONSOLE_CURSOR_INFO OrgConsoleCursorInfo;
|
|
static CONSOLE_SCREEN_BUFFER_INFO OrgConsoleBufferInfo;
|
|
|
|
/* PUBLIC FUNCTIONS ***********************************************************/
|
|
|
|
VOID DisplayMessage(LPCWSTR Format, ...)
|
|
{
|
|
WCHAR Buffer[256];
|
|
va_list Parameters;
|
|
|
|
va_start(Parameters, Format);
|
|
_vsnwprintf(Buffer, 256, Format, Parameters);
|
|
DPRINT1("\n\nNTVDM Subsystem\n%S\n\n", Buffer);
|
|
MessageBoxW(NULL, Buffer, L"NTVDM Subsystem", MB_OK);
|
|
va_end(Parameters);
|
|
}
|
|
|
|
BOOL WINAPI ConsoleCtrlHandler(DWORD ControlType)
|
|
{
|
|
switch (ControlType)
|
|
{
|
|
case CTRL_C_EVENT:
|
|
case CTRL_BREAK_EVENT:
|
|
{
|
|
/* Call INT 23h */
|
|
EmulatorInterrupt(0x23);
|
|
break;
|
|
}
|
|
default:
|
|
{
|
|
/* Stop the VDM if the user logs out or closes the console */
|
|
VdmRunning = FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL ConsoleInit(VOID)
|
|
{
|
|
/* Set the handler routine */
|
|
SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE);
|
|
|
|
/* Get the input handle to the real console, and check for success */
|
|
ConsoleInput = CreateFileW(L"CONIN$",
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
if (ConsoleInput == INVALID_HANDLE_VALUE)
|
|
{
|
|
wprintf(L"FATAL: Cannot retrieve a handle to the console input\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Get the output handle to the real console, and check for success */
|
|
ConsoleOutput = CreateFileW(L"CONOUT$",
|
|
GENERIC_READ | GENERIC_WRITE,
|
|
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
|
NULL,
|
|
OPEN_EXISTING,
|
|
0,
|
|
NULL);
|
|
if (ConsoleOutput == INVALID_HANDLE_VALUE)
|
|
{
|
|
CloseHandle(ConsoleInput);
|
|
wprintf(L"FATAL: Cannot retrieve a handle to the console output\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Save the original input and output console modes */
|
|
if (!GetConsoleMode(ConsoleInput , &OrgConsoleInputMode ) ||
|
|
!GetConsoleMode(ConsoleOutput, &OrgConsoleOutputMode))
|
|
{
|
|
CloseHandle(ConsoleOutput);
|
|
CloseHandle(ConsoleInput);
|
|
wprintf(L"FATAL: Cannot save console in/out modes\n");
|
|
return FALSE;
|
|
}
|
|
|
|
/* Save the original cursor and console screen buffer information */
|
|
if (!GetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo) ||
|
|
!GetConsoleScreenBufferInfo(ConsoleOutput, &OrgConsoleBufferInfo))
|
|
{
|
|
CloseHandle(ConsoleOutput);
|
|
CloseHandle(ConsoleInput);
|
|
wprintf(L"FATAL: Cannot save console cursor/screen-buffer info\n");
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID ConsoleCleanup(VOID)
|
|
{
|
|
SMALL_RECT ConRect;
|
|
CONSOLE_SCREEN_BUFFER_INFO ConsoleInfo;
|
|
|
|
/* Restore the old screen buffer */
|
|
SetConsoleActiveScreenBuffer(ConsoleOutput);
|
|
|
|
/* Restore the original console size */
|
|
GetConsoleScreenBufferInfo(ConsoleOutput, &ConsoleInfo);
|
|
ConRect.Left = 0; // OrgConsoleBufferInfo.srWindow.Left;
|
|
// ConRect.Top = ConsoleInfo.dwCursorPosition.Y / (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
|
|
// ConRect.Top *= (OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top + 1);
|
|
ConRect.Top = ConsoleInfo.dwCursorPosition.Y;
|
|
ConRect.Right = ConRect.Left + OrgConsoleBufferInfo.srWindow.Right - OrgConsoleBufferInfo.srWindow.Left;
|
|
ConRect.Bottom = ConRect.Top + OrgConsoleBufferInfo.srWindow.Bottom - OrgConsoleBufferInfo.srWindow.Top ;
|
|
/* See the following trick explanation in vga.c:VgaEnterTextMode() */
|
|
SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
|
|
SetConsoleWindowInfo(ConsoleOutput, TRUE, &ConRect);
|
|
// SetConsoleWindowInfo(ConsoleOutput, TRUE, &OrgConsoleBufferInfo.srWindow);
|
|
SetConsoleScreenBufferSize(ConsoleOutput, OrgConsoleBufferInfo.dwSize);
|
|
|
|
/* Restore the original cursor shape */
|
|
SetConsoleCursorInfo(ConsoleOutput, &OrgConsoleCursorInfo);
|
|
|
|
/* Restore the original input and output console modes */
|
|
SetConsoleMode(ConsoleOutput, OrgConsoleOutputMode);
|
|
SetConsoleMode(ConsoleInput , OrgConsoleInputMode );
|
|
|
|
/* Close the console handles */
|
|
if (ConsoleOutput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleOutput);
|
|
if (ConsoleInput != INVALID_HANDLE_VALUE) CloseHandle(ConsoleInput);
|
|
}
|
|
|
|
INT wmain(INT argc, WCHAR *argv[])
|
|
{
|
|
INT i;
|
|
CHAR CommandLine[DOS_CMDLINE_LENGTH];
|
|
LARGE_INTEGER StartPerfCount;
|
|
LARGE_INTEGER Frequency, LastTimerTick, LastRtcTick, Counter;
|
|
LONGLONG TimerTicks;
|
|
DWORD StartTickCount, CurrentTickCount;
|
|
DWORD LastClockUpdate;
|
|
DWORD LastVerticalRefresh;
|
|
#ifdef IPS_DISPLAY
|
|
DWORD LastCyclePrintout;
|
|
DWORD Cycles = 0;
|
|
#endif
|
|
INT KeyboardIntCounter = 0;
|
|
|
|
#ifndef TESTING
|
|
UNREFERENCED_PARAMETER(argc);
|
|
UNREFERENCED_PARAMETER(argv);
|
|
|
|
/* The DOS command line must be ASCII */
|
|
WideCharToMultiByte(CP_ACP, 0, GetCommandLine(), -1, CommandLine, sizeof(CommandLine), NULL, NULL);
|
|
#else
|
|
if (argc == 2 && argv[1] != NULL)
|
|
{
|
|
WideCharToMultiByte(CP_ACP, 0, argv[1], -1, CommandLine, sizeof(CommandLine), NULL, NULL);
|
|
}
|
|
else
|
|
{
|
|
wprintf(L"\nReactOS Virtual DOS Machine\n\n"
|
|
L"Usage: NTVDM <executable>\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
DPRINT1("\n\n\nNTVDM - Starting '%s'...\n\n\n", CommandLine);
|
|
|
|
/* Initialize the console */
|
|
if (!ConsoleInit())
|
|
{
|
|
wprintf(L"FATAL: A problem occurred when trying to initialize the console\n");
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Initialize the emulator */
|
|
if (!EmulatorInitialize(ConsoleInput, ConsoleOutput))
|
|
{
|
|
wprintf(L"FATAL: Failed to initialize the emulator\n");
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Initialize the performance counter (needed for hardware timers) */
|
|
if (!QueryPerformanceFrequency(&Frequency))
|
|
{
|
|
wprintf(L"FATAL: Performance counter not available\n");
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Initialize the system BIOS */
|
|
if (!BiosInitialize(ConsoleInput, ConsoleOutput))
|
|
{
|
|
wprintf(L"FATAL: Failed to initialize the VDM BIOS.\n");
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Initialize the VDM DOS kernel */
|
|
if (!DosInitialize(NULL))
|
|
{
|
|
wprintf(L"FATAL: Failed to initialize the VDM DOS kernel.\n");
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Start the process from the command line */
|
|
if (!DosCreateProcess(CommandLine, 0))
|
|
{
|
|
DisplayMessage(L"Could not start program: %S", CommandLine);
|
|
goto Cleanup;
|
|
}
|
|
|
|
/* Find the starting performance and tick count */
|
|
StartTickCount = GetTickCount();
|
|
QueryPerformanceCounter(&StartPerfCount);
|
|
|
|
/* Set the different last counts to the starting count */
|
|
LastClockUpdate = LastVerticalRefresh =
|
|
#ifdef IPS_DISPLAY
|
|
LastCyclePrintout =
|
|
#endif
|
|
StartTickCount;
|
|
|
|
/* Set the last timer ticks to the current time */
|
|
LastTimerTick = LastRtcTick = StartPerfCount;
|
|
|
|
/* Main loop */
|
|
while (VdmRunning)
|
|
{
|
|
#ifdef WORKING_TIMER
|
|
DWORD PitResolution = PitGetResolution();
|
|
#endif
|
|
DWORD RtcFrequency = RtcGetTicksPerSecond();
|
|
|
|
/* Get the current number of ticks */
|
|
CurrentTickCount = GetTickCount();
|
|
|
|
#ifdef WORKING_TIMER
|
|
if ((PitResolution <= 1000) && (RtcFrequency <= 1000))
|
|
{
|
|
/* Calculate the approximate performance counter value instead */
|
|
Counter.QuadPart = StartPerfCount.QuadPart
|
|
+ (CurrentTickCount - StartTickCount)
|
|
* (Frequency.QuadPart / 1000);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
/* Get the current performance counter value */
|
|
QueryPerformanceCounter(&Counter);
|
|
}
|
|
|
|
/* Get the number of PIT ticks that have passed */
|
|
TimerTicks = ((Counter.QuadPart - LastTimerTick.QuadPart)
|
|
* PIT_BASE_FREQUENCY) / Frequency.QuadPart;
|
|
|
|
/* Update the PIT */
|
|
if (TimerTicks > 0)
|
|
{
|
|
PitClock(TimerTicks);
|
|
LastTimerTick = Counter;
|
|
}
|
|
|
|
/* Check for RTC update */
|
|
if ((CurrentTickCount - LastClockUpdate) >= 1000)
|
|
{
|
|
RtcTimeUpdate();
|
|
LastClockUpdate = CurrentTickCount;
|
|
}
|
|
|
|
/* Check for RTC periodic tick */
|
|
if ((Counter.QuadPart - LastRtcTick.QuadPart)
|
|
>= (Frequency.QuadPart / (LONGLONG)RtcFrequency))
|
|
{
|
|
RtcPeriodicTick();
|
|
LastRtcTick = Counter;
|
|
}
|
|
|
|
/* Check for vertical retrace */
|
|
if ((CurrentTickCount - LastVerticalRefresh) >= 15)
|
|
{
|
|
VgaRefreshDisplay();
|
|
LastVerticalRefresh = CurrentTickCount;
|
|
}
|
|
|
|
KeyboardIntCounter++;
|
|
if (KeyboardIntCounter == KBD_INT_CYCLES)
|
|
{
|
|
GenerateKeyboardInterrupts();
|
|
KeyboardIntCounter = 0;
|
|
}
|
|
|
|
/* Horizontal retrace occurs as fast as possible */
|
|
VgaHorizontalRetrace();
|
|
|
|
/* Continue CPU emulation */
|
|
for (i = 0; (i < STEPS_PER_CYCLE) && VdmRunning; i++)
|
|
{
|
|
EmulatorStep();
|
|
#ifdef IPS_DISPLAY
|
|
Cycles++;
|
|
#endif
|
|
}
|
|
|
|
#ifdef IPS_DISPLAY
|
|
if ((CurrentTickCount - LastCyclePrintout) >= 1000)
|
|
{
|
|
DPRINT1("NTVDM: %lu Instructions Per Second; TimerTicks = %I64d\n", Cycles, TimerTicks);
|
|
LastCyclePrintout = CurrentTickCount;
|
|
Cycles = 0;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Perform another screen refresh */
|
|
VgaRefreshDisplay();
|
|
|
|
Cleanup:
|
|
BiosCleanup();
|
|
EmulatorCleanup();
|
|
ConsoleCleanup();
|
|
|
|
DPRINT1("\n\n\nNTVDM - Exiting...\n\n\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* EOF */
|