AxibugEmuOnline/virtuanessrc097-master/EmuThread.cpp

1152 lines
56 KiB
C++
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// エミュレータスレッドクラス
//
#include "DebugOut.h"
#include "VirtuaNESres.h"
#include "EmuThread.h"
#include "MainFrame.h"
#include "Pathlib.h"
#include "NetPlay.h"
#include "DirectDraw.h"
#include "DirectSound.h"
#include "DirectInput.h"
// 自分自身
CEmuThread Emu;
// Thisポインタ
CEmuThread* CEmuThread::g_pThis = NULL;
// ウインドウハンドル
HWND CEmuThread::g_hWnd = NULL;
// エミュレータオブジェクトポインタ
NES* CEmuThread::g_nes = NULL;
// WAVEレコーダ
CWaveRec CEmuThread::g_WaveRec;
// NetEvent Queue
deque<NETEV> CEmuThread::NetEventQueue;
string CEmuThread::strNetStateName;
// ステータス
INT CEmuThread::g_Status = CEmuThread::STATUS_NONE;
// スレッドイベントとイベントハンドル
INT CEmuThread::g_Event = CEmuThread::EV_NONE;
LONG CEmuThread::g_EventParam = 0;
LONG CEmuThread::g_EventParam2 = 0;
HANDLE CEmuThread::g_hEvent = NULL;
HANDLE CEmuThread::g_hEventAccept = NULL;
// エラーメッセージ
CHAR CEmuThread::g_szErrorMessage[512];
// ストリングテーブル
LPCSTR CEmuThread::g_lpSoundMuteStringTable[] = {
"Master ",
"Rectangle 1",
"Rectangle 2",
"Triangle ",
"Noise ",
"DPCM ",
"Ex CH1 ",
"Ex CH2 ",
"Ex CH3 ",
"Ex CH4 ",
"Ex CH5 ",
"Ex CH6 ",
"Ex CH7 ",
"Ex CH8 ",
NULL,
NULL,
};
// スレッドプライオリティテーブル
INT CEmuThread::g_PriorityTable[] = {
THREAD_PRIORITY_IDLE,
THREAD_PRIORITY_LOWEST,
THREAD_PRIORITY_BELOW_NORMAL,
THREAD_PRIORITY_NORMAL,
THREAD_PRIORITY_ABOVE_NORMAL,
THREAD_PRIORITY_HIGHEST,
THREAD_PRIORITY_TIME_CRITICAL
};
CEmuThread::CEmuThread()
{
g_pThis = this;
m_hThread = NULL;
g_Status = STATUS_NONE;
m_nPriority = 3; // Normal
g_nes = NULL;
m_nPauseCount = 0;
}
CEmuThread::~CEmuThread()
{
Stop();
}
void CEmuThread::SetPriority( INT nPriority )
{
m_nPriority = nPriority;
if( IsRunning() ) {
::SetThreadPriority( m_hThread, g_PriorityTable[m_nPriority] );
}
}
BOOL CEmuThread::Start( HWND hWnd, NES* nes )
{
Stop();
if( !g_hEvent ) {
if( !(g_hEvent = ::CreateEvent( NULL, FALSE, FALSE, NULL )) ) {
DEBUGOUT( "CreateEvent failed.\n" );
goto _Start_Failed;
}
}
if( !g_hEventAccept ) {
if( !(g_hEventAccept = ::CreateEvent( NULL, FALSE, FALSE, NULL )) ) {
DEBUGOUT( "CreateEvent failed.\n" );
goto _Start_Failed;
}
}
::ResetEvent( g_hEvent );
::ResetEvent( g_hEventAccept );
g_hWnd = hWnd;
g_nes = nes;
g_Event = EV_INITIAL;
g_Status = STATUS_NONE;
::SetEvent( g_hEvent );
m_nPauseCount = 0;
if( !(m_hThread = ::CreateThread( NULL, 0, ThreadProc, 0, 0, &m_dwThreadID )) ) {
DEBUGOUT( "CreateThread failed.\n" );
goto _Start_Failed;
}
// スレッドプライオリティの設定
::SetThreadPriority( m_hThread, g_PriorityTable[m_nPriority] );
// ちゃんと起動できたか確認の為イベントを待つ
::WaitForSingleObject( g_hEventAccept, INFINITE );
g_Status = STATUS_RUN;
// DEBUGOUT( "CEmuThread:Start() Thread started.\n" );
return TRUE;
_Start_Failed:
CLOSEHANDLE( g_hEvent );
CLOSEHANDLE( g_hEventAccept );
DEBUGOUT( "CEmuThread:Start() Thread startup failed!!." );
return FALSE;
}
void CEmuThread::Stop()
{
if( IsRunning() ) {
::ResetEvent( g_hEventAccept );
g_Event = EV_EXIT;
::SetEvent( g_hEvent );
::WaitForSingleObject( m_hThread, INFINITE );
CLOSEHANDLE( m_hThread );
m_hThread = NULL;
g_Status = STATUS_NONE;
CLOSEHANDLE( g_hEvent );
CLOSEHANDLE( g_hEventAccept );
// ネットプレイ時の切断処理
NetPlay.Disconnect();
// DEBUGOUT( "CEmuThread::Stop() Thread stoped.\n" );
}
}
void CEmuThread::Pause()
{
if( IsRunning() ) {
if( !IsPausing() ) {
::ResetEvent( g_hEventAccept );
g_Event = EV_PAUSE;
::SetEvent( g_hEvent );
::WaitForSingleObject( g_hEventAccept, INFINITE );
g_Status = STATUS_PAUSE;
m_nPauseCount++;
} else {
m_nPauseCount++;
}
// DEBUGOUT( "CEmuThread::Pause() Thread paused. Count=%d\n", m_nPauseCount );
}
}
void CEmuThread::Resume()
{
if( IsRunning() ) {
if( IsPausing() ) {
if( --m_nPauseCount <= 0 ) {
m_nPauseCount = 0;
::ResetEvent( g_hEventAccept );
g_Event = EV_RESUME;
::SetEvent( g_hEvent );
::WaitForSingleObject( g_hEventAccept, INFINITE );
g_Status = STATUS_RUN;
}
}
// DEBUGOUT( "CEmuThread::Resume() Thread resumed. Count=%d\n", m_nPauseCount );
}
}
void CEmuThread::Event( EMUEVENT ev )
{
if( IsRunning() ) {
::ResetEvent( g_hEventAccept );
g_Event = ev;
g_EventParam = 0;
g_EventParam2 = -1;
::SetEvent( g_hEvent );
::WaitForSingleObject( g_hEventAccept, INFINITE );
}
}
void CEmuThread::EventParam( EMUEVENT ev, LONG param )
{
if( IsRunning() ) {
::ResetEvent( g_hEventAccept );
g_Event = ev;
g_EventParam = param;
g_EventParam2 = -1;
::SetEvent( g_hEvent );
::WaitForSingleObject( g_hEventAccept, INFINITE );
}
}
void CEmuThread::EventParam2( EMUEVENT ev, LONG param, LONG param2 )
{
if( IsRunning() ) {
::ResetEvent( g_hEventAccept );
g_Event = ev;
g_EventParam = param;
g_EventParam2 = param2;
::SetEvent( g_hEvent );
::WaitForSingleObject( g_hEventAccept, INFINITE );
}
}
void CEmuThread::DiskCommand( BYTE cmd )
{
switch( cmd ) {
case 0: // Eject
if( g_nes->rom->GetDiskNo() > 0 ) {
g_nes->Command( NES::NESCMD_DISK_EJECT );
DirectDraw.SetMessageString( "Disk Eject." );
}
break;
case 1: // Disk0 SideA
if( g_nes->rom->GetDiskNo() > 0 ) {
g_nes->Command( NES::NESCMD_DISK_0A );
DirectDraw.SetMessageString( "Change Disk1 SideA." );
}
break;
case 2: // Disk0 SideB
if( g_nes->rom->GetDiskNo() > 1 ) {
g_nes->Command( NES::NESCMD_DISK_0B );
DirectDraw.SetMessageString( "Change Disk1 SideB." );
}
break;
case 3: // Disk1 SideA
if( g_nes->rom->GetDiskNo() > 2 ) {
g_nes->Command( NES::NESCMD_DISK_1A );
DirectDraw.SetMessageString( "Change Disk2 SideA." );
}
break;
case 4: // Disk1 SideB
if( g_nes->rom->GetDiskNo() > 3 ) {
g_nes->Command( NES::NESCMD_DISK_1B );
DirectDraw.SetMessageString( "Change Disk2 SideB." );
}
case 5: // Disk1 SideA
if( g_nes->rom->GetDiskNo() > 4 ) {
g_nes->Command( NES::NESCMD_DISK_2A );
DirectDraw.SetMessageString( "Change Disk3 SideA." );
}
break;
case 6: // Disk1 SideB
if( g_nes->rom->GetDiskNo() > 5 ) {
g_nes->Command( NES::NESCMD_DISK_2B );
DirectDraw.SetMessageString( "Change Disk3 SideB." );
}
case 7: // Disk1 SideA
if( g_nes->rom->GetDiskNo() > 6 ) {
g_nes->Command( NES::NESCMD_DISK_3A );
DirectDraw.SetMessageString( "Change Disk4 SideA." );
}
break;
case 8: // Disk1 SideB
if( g_nes->rom->GetDiskNo() > 7 ) {
g_nes->Command( NES::NESCMD_DISK_3B );
DirectDraw.SetMessageString( "Change Disk4 SideB." );
}
break;
}
}
BOOL CEmuThread::FrameInput()
{
static CHAR szMes[256];
DirectInput.Poll();
if( DirectDraw.GetZapperMode() ) {
LONG x, y;
DirectDraw.GetZapperPos( x, y );
if( g_nes ) {
g_nes->SetZapperPos( x, y );
}
}
g_nes->pad->Sync();
if( !NetPlay.IsConnect() ) {
g_nes->Movie();
} else {
DWORD paddata = g_nes->pad->GetSyncData();
BYTE player1[CNetPlay::SOCKET_BLOCK_SIZE], player2[CNetPlay::SOCKET_BLOCK_SIZE];
player1[0] = player1[1] = player1[2] = player1[3] = 0;
if( !NetEventQueue.empty() ) {
NETEV& ev = NetEventQueue.front();
switch( ev.Event ) {
case EV_HWRESET:
player1[0] = 0x80;
break;
case EV_SWRESET:
player1[0] = 0x81;
break;
case EV_DISK_COMMAND:
player1[0] = 0x88;
player1[1] = (BYTE)ev.Param;
break;
case EV_STATE_LOAD:
player1[0] = 0xF0;
break;
case EV_STATE_SAVE:
player1[0] = 0xF1;
break;
default:
break;
}
}
player1[4] = (BYTE) paddata;
player1[5] = (BYTE)(paddata>>8);
player1[6] = (BYTE)(paddata>>16);
player1[7] = (BYTE)(paddata>>24);
INT ret = NetPlay.ModifyPlayer( player1, player2 );
if( ret < 0 ) {
// Network Error
NetPlay.Disconnect();
return FALSE;
} else if( ret == 0 ) {
// Queueの分は送信したのでFIFOから取り除く
if( !NetEventQueue.empty() ) {
NetEventQueue.pop_front();
}
// Command check(同時はP1優先)
BYTE event = 0;
BYTE param = 0;
if( player1[0] ) {
event = player1[0];
param = player1[1];
} else if( player2[0] ) {
event = player2[0];
param = player2[1];
}
switch( event ) {
case 0x80:
g_nes->Command( NES::NESCMD_HWRESET );
DirectDraw.SetMessageString( "Hardware reset." );
break;
case 0x81:
g_nes->Command( NES::NESCMD_SWRESET );
DirectDraw.SetMessageString( "Software reset." );
break;
case 0x88:
DiskCommand( param );
break;
case 0xF0:
if( g_nes->LoadState( strNetStateName.c_str() ) ) {
::wsprintf( szMes, "Net State Load." );
DirectDraw.SetMessageString( szMes );
}
break;
case 0xF1:
if( g_nes->SaveState( strNetStateName.c_str() ) ) {
::wsprintf( szMes, "Net State Save." );
DirectDraw.SetMessageString( szMes );
}
break;
default:
break;
}
}
LPBYTE p1, p2;
if( NetPlay.IsServer() ) {
p1 = player1;
p2 = player2;
} else {
p1 = player2;
p2 = player1;
}
//DEBUGOUT( "P1:%02X P2:%02X P3:%02X P4:%02X\n", player1[4], player2[4], player1[5], player2[5] );
g_nes->pad->SetSyncData( ((DWORD)p1[4])|((DWORD)p2[4]<<8)|((DWORD)p1[5]<<16)|((DWORD)p2[5]<<24) );
}
return TRUE;
}
DWORD WINAPI CEmuThread::ThreadProc( LPVOID lpParam )
{
INT i;
INT Ev;
LONG Param, Param2;
BOOL bLoop = TRUE;
BOOL bPause = FALSE; // Thread pause
BOOL bEmuPause = FALSE; // Emulation pause
BOOL bThrottle = FALSE; // Emulation throttle
INT nFrameSkip = 0; // Emulation frameskip
BOOL bOneStep = FALSE; // Emulation one step
BOOL bSleep = FALSE; // Sleep use
INT frameskipno;
double frame_time = 0.0f;
double frame_period;
DWORD start_time, current_time, now_time;
LONG sleep_time;
// FPS
INT nFrameCount = 0;
DWORD dwFrameTime[32];
DWORD FPS = 0;
// Str
CHAR szStr[256];
// Netplay
NETEV netev;
INT nNetTimeoutCount = 0;
while( bLoop ) {
try {
bOneStep = FALSE;
Ev = EV_NONE;
if( WAIT_OBJECT_0 == ::WaitForSingleObject(g_hEvent,0) ) {
Ev = g_Event;
Param = g_EventParam;
Param2 = g_EventParam2;
}
switch( Ev ) {
case EV_NONE:
break;
case EV_INITIAL:
DirectDraw.SetMessageString( "Emulation start." );
DirectSound.StreamPlay();
frame_time = 0.0f;
start_time = ::timeGetTime();
if( g_nes ) {
g_nes->ppu->SetScreenPtr( DirectDraw.GetRenderScreen(), DirectDraw.GetLineColormode() );
}
::SetEvent( g_hEventAccept );
break;
case EV_EXIT:
DirectSound.StreamStop();
g_WaveRec.Stop();
bLoop = FALSE;
return 0;
case EV_PAUSE:
DirectSound.StreamPause();
bPause = TRUE;
::SetEvent( g_hEventAccept );
break;
case EV_RESUME:
if( !bEmuPause )
DirectSound.StreamResume();
frame_time = 0.0f;
start_time = ::timeGetTime();
bPause = FALSE;
::SetEvent( g_hEventAccept );
break;
case EV_FULLSCREEN_GDI:
DirectDraw.SetFullScreenGDI( (BOOL)Param );
::SetEvent( g_hEventAccept );
break;
case EV_HWRESET:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( !g_nes->IsMoviePlay() ) {
g_nes->Command( NES::NESCMD_HWRESET );
DirectDraw.SetMessageString( "Hardware reset." );
}
} else {
netev.Event = EV_HWRESET;
netev.Param = 0;
NetEventQueue.push_back( netev );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_SWRESET:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( !g_nes->IsMoviePlay() ) {
g_nes->Command( NES::NESCMD_SWRESET );
DirectDraw.SetMessageString( "Software reset." );
}
} else {
netev.Event = EV_SWRESET;
netev.Param = 0;
NetEventQueue.push_back( netev );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_NETPLAY_START:
if( g_nes ) {
// 一応
g_nes->MovieStop();
string pathstr;
if( Config.path.bStatePath ) {
pathstr = CPathlib::CreatePath( CApp::GetModulePath(), Config.path.szStatePath );
::CreateDirectory( pathstr.c_str(), NULL );
DEBUGOUT( "Path: %s\n", pathstr.c_str() );
} else {
pathstr = g_nes->rom->GetRomPath();
}
strNetStateName = CPathlib::MakePathExt( pathstr.c_str(), g_nes->rom->GetRomName(), "stn" );
DEBUGOUT( "NetState Path: %s\n", strNetStateName.c_str() );
g_nes->Command( NES::NESCMD_HWRESET );
DirectDraw.SetMessageString( "Netplay start!" );
}
bThrottle = FALSE;
// Queueをクリア
NetEventQueue.clear();
// 最初はSync
NetPlay.Sync();
::SetEvent( g_hEventAccept );
break;
case EV_EMUPAUSE:
if( !NetPlay.IsConnect() ) {
bEmuPause = !bEmuPause;
if( bEmuPause ) {
DirectSound.StreamPause();
} else if( !bPause ) {
DirectSound.StreamResume();
}
DirectDraw.SetMessageString( "Pause." );
}
::SetEvent( g_hEventAccept );
break;
case EV_ONEFRAME:
if( !NetPlay.IsConnect() ) {
bEmuPause = TRUE;
DirectSound.StreamPause();
bOneStep = TRUE;
DirectDraw.SetMessageString( "One Frame." );
}
::SetEvent( g_hEventAccept );
break;
case EV_THROTTLE:
if( !NetPlay.IsConnect() ) {
bThrottle = !bThrottle;
if( bThrottle ) {
DirectDraw.SetMessageString( "Throttle ON." );
} else {
DirectDraw.SetMessageString( "Throttle OFF." );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_FRAMESKIP_AUTO:
if( !NetPlay.IsConnect() ) {
DirectDraw.SetMessageString( "FrameSkip Auto." );
nFrameSkip = 0;
}
::SetEvent( g_hEventAccept );
break;
case EV_FRAMESKIP_UP:
if( !NetPlay.IsConnect() ) {
if( nFrameSkip < 20 )
nFrameSkip++;
::wsprintf( szStr, "FrameSkip %d", nFrameSkip );
DirectDraw.SetMessageString( szStr );
}
::SetEvent( g_hEventAccept );
break;
case EV_FRAMESKIP_DOWN:
if( !NetPlay.IsConnect() ) {
if( nFrameSkip )
nFrameSkip--;
if( nFrameSkip ) {
::wsprintf( szStr, "FrameSkip %d", nFrameSkip );
DirectDraw.SetMessageString( szStr );
} else {
DirectDraw.SetMessageString( "FrameSkip Auto." );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_STATE_LOAD:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->LoadState( (const char*)Param ) ) {
if( Param2 < 0 )
::wsprintf( szStr, "State Load." );
else
::wsprintf( szStr, "State Load #%d", Param2 );
DirectDraw.SetMessageString( szStr );
}
} else {
netev.Event = EV_STATE_LOAD;
netev.Param = 0;
NetEventQueue.push_back( netev );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_STATE_SAVE:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->SaveState( (const char*)Param ) ) {
if( Param2 < 0 )
::wsprintf( szStr, "State Save." );
else
::wsprintf( szStr, "State Save #%d", Param2 );
DirectDraw.SetMessageString( szStr );
}
} else {
netev.Event = EV_STATE_SAVE;
netev.Param = 0;
NetEventQueue.push_back( netev );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_DISK_COMMAND:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
DiskCommand( Param );
} else {
netev.Event = EV_DISK_COMMAND;
netev.Param = Param;
NetEventQueue.push_back( netev );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_EXCONTROLLER:
if( g_nes )
g_nes->CommandParam( NES::NESCMD_EXCONTROLLER, Param );
::SetEvent( g_hEventAccept );
break;
case EV_SOUND_MUTE:
{
if( g_nes )
if( g_nes->CommandParam( NES::NESCMD_SOUND_MUTE, Param ) ) {
::wsprintf( szStr, "%s Enable.", g_lpSoundMuteStringTable[Param] );
} else {
::wsprintf( szStr, "%s Mute.", g_lpSoundMuteStringTable[Param] );
}
DirectDraw.SetMessageString( szStr );
}
::SetEvent( g_hEventAccept );
break;
case EV_MOVIE_PLAY:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->MoviePlay( (const char*)Param ) ) {
DirectDraw.SetMessageString( "Movie replay." );
}
}
}
::SetEvent( g_hEventAccept );
break;
case EV_MOVIE_REC:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->MovieRec( (const char*)Param ) ) {
DirectDraw.SetMessageString( "Movie record." );
}
}
}
::SetEvent( g_hEventAccept );
break;
case EV_MOVIE_RECAPPEND:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->MovieRecAppend( (const char*)Param ) ) {
DirectDraw.SetMessageString( "Movie append record." );
}
}
}
::SetEvent( g_hEventAccept );
break;
case EV_MOVIE_STOP:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
g_nes->MovieStop();
}
}
::SetEvent( g_hEventAccept );
break;
case EV_SNAPSHOT:
if( g_nes ) {
if( g_nes->Snapshot() ) {
DirectDraw.SetMessageString( "Snap shot." );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_WAVEREC_START:
if( g_nes ) {
DWORD nRate, nBits;
DirectSound.GetSamplingRate( nRate, nBits );
g_WaveRec.Start( (LPSTR)Param, nRate, nBits, FALSE );
}
DirectDraw.SetMessageString( "Wave recording start." );
::SetEvent( g_hEventAccept );
break;
case EV_WAVEREC_STOP:
if( g_nes ) {
g_WaveRec.Stop();
DirectDraw.SetMessageString( "Wave recording stop." );
}
::SetEvent( g_hEventAccept );
break;
case EV_TAPE_PLAY:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->TapePlay( (const char*)Param ) ) {
DirectDraw.SetMessageString( "Tape play." );
}
}
}
::SetEvent( g_hEventAccept );
break;
case EV_TAPE_REC:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
if( g_nes->TapeRec( (const char*)Param ) ) {
DirectDraw.SetMessageString( "Tape record." );
}
}
}
::SetEvent( g_hEventAccept );
break;
case EV_TAPE_STOP:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
g_nes->TapeStop();
}
}
::SetEvent( g_hEventAccept );
break;
case EV_BARCODE:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
g_nes->SetBarcodeData( (LPBYTE)Param, (INT)Param2 );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_TURBOFILE:
if( g_nes ) {
if( !NetPlay.IsConnect() ) {
g_nes->SetTurboFileBank( (INT)Param );
}
}
::SetEvent( g_hEventAccept );
break;
case EV_MESSAGE_OUT:
DirectDraw.SetMessageString( (LPSTR)Param );
::SetEvent( g_hEventAccept );
break;
default:
DEBUGOUT( "ThreadProc:Unknown event.\n" );
::SetEvent( g_hEventAccept );
break;
}
} catch( CHAR* str ) {
bPause = TRUE;
::strcpy( g_szErrorMessage, str );
::PostMessage( g_hWnd, WM_VNS_ERRORMSG, 0, (LPARAM)g_szErrorMessage );
::SetEvent( g_hEventAccept );
#ifndef _DEBUG
} catch(...) {
bPause = TRUE;
::PostMessage( g_hWnd, WM_VNS_ERRORMSG, 0, (LPARAM)CApp::GetErrorString( IDS_ERROR_UNKNOWN ) );
::SetEvent( g_hEventAccept );
#endif
}
if( bPause ) {
// イベント発生の為
DirectInput.Poll();
// かっこ悪い…
CMainFrame::OnKeyControl();
// 呼ばないとWindowsが重くなるので(NT系以外は特に)
::Sleep( 20 );
} else {
try {
INT nNetBuffer = 0;
BOOL bNoFrame = FALSE;
BOOL bAddFrame = FALSE;
bSleep = TRUE;
if( !NetPlay.IsConnect() ) {
// 通常
BOOL bKeyThrottle = FALSE;
// キーチェック
{
BYTE* pKey = (BYTE*)DirectInput.m_Sw;
WORD* pShortCutKey = Config.shortcut.nShortCut;
INT* pShortCutKeyID = Config.ShortcutKeyID;
for( INT i = 0; pShortCutKeyID[i*3+0]; i++ ) {
if( pShortCutKeyID[i*3+0] == ID_KEYTHROTTLE ) {
if( (pKey[pShortCutKey[pShortCutKeyID[i*3+2] ]] & 0x80) && pShortCutKey[pShortCutKeyID[i*3+2] ]
|| (pKey[pShortCutKey[pShortCutKeyID[i*3+2]+128]] & 0x80) && pShortCutKey[pShortCutKeyID[i*3+2]+128] ) {
bKeyThrottle = TRUE;
}
break;
}
}
}
if( Config.general.bScreenMode ) {
// FullScreen
// if( Config.graphics.bSyncDraw ) {
if( Config.graphics.bSyncDraw && Config.graphics.bSyncNoSleep ) {
bSleep = FALSE;
} else if( Config.emulator.bAutoFrameSkip ) {
bSleep = TRUE;
} else {
bSleep = FALSE;
}
} else {
// Window
// if( Config.graphics.bWindowVSync ) {
if( Config.graphics.bWindowVSync && Config.graphics.bSyncNoSleep ) {
bSleep = FALSE;
} else if( Config.emulator.bAutoFrameSkip ) {
bSleep = TRUE;
} else {
bSleep = FALSE;
}
}
frame_period = 1000.0/g_nes->nescfg->FrameRate;
// フレームスキップ数の計算
current_time = ::timeGetTime();
now_time = current_time - start_time;
if( Config.emulator.bAutoFrameSkip && bSleep ) {
if( g_nes->IsDiskThrottle() ) {
frame_period = g_nes->nescfg->FramePeriod/10.0f;
} else if( !nFrameSkip ) {
// Auto
double fps = 0.0;
if( (bThrottle||bKeyThrottle) ) {
fps = (double)Config.emulator.nThrottleFPS;
} else {
fps = g_nes->nescfg->FrameRate;
}
if( fps < 0.0 ) fps = 60.0;
if( fps > 600.0 ) fps = 600.0;
frame_period = 1000.0/fps;
} else {
frame_period = (1000.0/g_nes->nescfg->FrameRate)/(nFrameSkip+1);
}
if( !nFrameSkip && !g_nes->IsDiskThrottle() ) {
frameskipno = (INT)(((double)now_time-frame_time) / frame_period);
if( frameskipno < 0 || frameskipno > 20 ) {
frameskipno = 1;
frame_time = 0.0;
start_time = ::timeGetTime();
}
} else if( g_nes->IsDiskThrottle() ) {
frameskipno = 10;
frame_time = 0.0;
start_time = ::timeGetTime();
} else {
if( nFrameSkip < 0 )
frameskipno = 1;
else
frameskipno = nFrameSkip+1;
}
} else {
if( g_nes->IsDiskThrottle() ) {
frameskipno = 10;
}
if( bThrottle||bKeyThrottle ) {
frameskipno = (INT)(((double)Config.emulator.nThrottleFPS+30)/60.0);
} else {
// オートを外してVSYNC同期の時にもフレームスキップをする為の措置
if( nFrameSkip < 0 ) {
frameskipno = 1;
} else {
frameskipno = nFrameSkip+1;
}
}
}
} else {
bSleep = TRUE;
// ネットプレイ中はフレームスキップ不可
frame_period = 1000.0/g_nes->nescfg->FrameRate;
// フレームスキップ数の計算
current_time = ::timeGetTime();
now_time = current_time - start_time;
frameskipno = (INT)(((double)now_time-frame_time) / frame_period);
if( frameskipno > 20-1 ) {
frameskipno = 20;
frame_time = 0.0f;
start_time = ::timeGetTime();
}
if( !NetPlay.RecvBuffer() ) {
bPause = TRUE;
goto _emulate_error;
}
// バッファ不足で少し遅らせるべきかどうかのチェック
INT ret = NetPlay.BufferCheck();
if( ret > 0 ) {
bAddFrame = TRUE;
if( frameskipno > 1 ) {
frameskipno++;
} else {
frameskipno = 2;
}
}
if( ret < 0 ) {
bNoFrame = TRUE;
}
// Timeout check
if( bNoFrame ) {
nNetTimeoutCount++;
if( nNetTimeoutCount > 10*60 ) {
NetPlay.Disconnect();
bPause = TRUE;
goto _emulate_error;
}
} else {
nNetTimeoutCount = 0;
}
nNetBuffer = NetPlay.GetRecvBufferSize();
}
// Emulation
if( (!bEmuPause || bOneStep) && g_nes ) {
g_nes->ppu->SetScreenPtr( DirectDraw.GetRenderScreen(), DirectDraw.GetLineColormode() );
if( !bOneStep ) {
for( i = 0; i < frameskipno-1; i++ ) {
if( !bNoFrame ) {
// Skip frames
if( !FrameInput() ) {
bPause = TRUE;
goto _emulate_error;
}
g_nes->EmulateFrame( FALSE );
}
if( !bAddFrame ) {
frame_time += frame_period;
}
bAddFrame = FALSE;
// Sound streaming
StreamProcess( bEmuPause );
}
}
if( !bNoFrame ) {
if( !FrameInput() ) {
bPause = TRUE;
goto _emulate_error;
}
g_nes->EmulateFrame( TRUE );
}
frame_time += frame_period;
} else {
// イベント発生の為
DirectInput.Poll();
for( i = 0; i < frameskipno-1; i++ ) {
frame_time += frame_period;
}
frame_time += frame_period;
}
// かっこ悪い…
CMainFrame::OnKeyControl();
// // 描画
// if( g_nes->ppu->GetExtMonoMode() ) {
// DirectDraw.SetPaletteMode( (PPUREG[1]&PPU_BGCOLOR_BIT)>>5, 0 );
// } else {
// DirectDraw.SetPaletteMode( (PPUREG[1]&PPU_BGCOLOR_BIT)>>5, PPUREG[1]&PPU_COLORMODE_BIT );
// }
// ディスクアクセスランプ
DirectDraw.SetDiskAccessLamp( (Config.graphics.bDiskAccessLamp && g_nes->mapper->ExCmdRead( Mapper::EXCMDRD_DISKACCESS ))?TRUE:FALSE );
// blit
DirectDraw.Blt();
// Sound streaming
StreamProcess( bEmuPause );
// 暇な時間待ち
sleep_time = (frame_time+frame_period) - (LONG)(::timeGetTime() - start_time);
if( bSleep && (sleep_time > 0) ) {
::Sleep( sleep_time-1 );
} else {
::Sleep( 0 );
}
DirectDraw.Flip();
#if _DEBUGOUT
{
static BOOL bPalettePut = FALSE;
if( ::GetAsyncKeyState( VK_ESCAPE ) & 0x8000 ) {
if( !bPalettePut ) {
int i;
DEBUGOUT( "BG\t" );
for( i = 0; i < 16; i++ ) {
DEBUGOUT( "%02X ", BGPAL[i] );
}
DEBUGOUT( "\n" );
DEBUGOUT( "SP\t" );
for( i = 0; i < 16; i++ ) {
DEBUGOUT( "%02X ", SPPAL[i] );
}
DEBUGOUT( "\n" );
}
bPalettePut = TRUE;
} else {
bPalettePut = FALSE;
}
}
#endif
// FPS表示
dwFrameTime[nFrameCount] = ::timeGetTime();
if( ++nFrameCount > 32-1 ) {
nFrameCount = 0;
if( Config.graphics.bFPSDisp ) {
if( dwFrameTime[32-1]-dwFrameTime[0] > 0 ) {
FPS = 1000*10*(32-1)/(dwFrameTime[32-1]-dwFrameTime[0]);
} else {
FPS = 0;
}
} else {
FPS = 0;
}
}
if( Config.graphics.bFPSDisp ) {
#if !(defined(_DEBUG)||defined(_DEBUGOUT))
sprintf( szStr, "FPS:%d.%01d", FPS/10, FPS%10 );
#else
if( !NetPlay.IsConnect() ) {
sprintf( szStr, "FPS:%d.%01d", FPS/10, FPS%10 );
} else {
sprintf( szStr, "FPS:%d.%01d NET:%3d NOFRM:%d TOUT:%3d", FPS/10, FPS%10, nNetBuffer, bNoFrame, nNetTimeoutCount );
}
#endif
DirectDraw.SetInfoString( szStr );
} else {
DirectDraw.SetInfoString( NULL );
}
_emulate_error:;
} catch( CHAR* str ) {
bPause = TRUE;
::strcpy( g_szErrorMessage, str );
::PostMessage( g_hWnd, WM_VNS_ERRORMSG, 0, (LPARAM)g_szErrorMessage );
#ifndef _DEBUG
} catch(...) {
bPause = TRUE;
::PostMessage( g_hWnd, WM_VNS_ERRORMSG, 0, (LPARAM)CApp::GetErrorString( IDS_ERROR_UNKNOWN ) );
#endif
}
}
}
g_WaveRec.Stop();
return 0;
}
void CEmuThread::StreamProcess( BOOL bPause )
{
if( g_pThis && g_nes && !bPause && !DirectSound.IsStreamPause() ) {
DWORD dwWrite, dwSize;
LPVOID lpLockPtr;
DWORD dwLockSize;
if( DirectSound.GetStreamLockPosition( &dwWrite, &dwSize ) ) {
if( DirectSound.StreamLock( dwWrite, dwSize, &lpLockPtr, &dwLockSize, NULL, NULL, 0 ) ) {
g_nes->apu->Process( (LPBYTE)lpLockPtr, dwLockSize );
g_WaveRec.Out( lpLockPtr, dwLockSize );
DirectSound.StreamUnlock( lpLockPtr, dwLockSize, NULL, NULL );
}
}
}
}