nestopia/source/core/NstMachine.cpp
2012-09-02 13:13:55 -04:00

560 lines
12 KiB
C++

////////////////////////////////////////////////////////////////////////////////////////
//
// Nestopia - NES/Famicom emulator written in C++
//
// Copyright (C) 2003-2008 Martin Freij
//
// This file is part of Nestopia.
//
// Nestopia is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// Nestopia is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nestopia; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
////////////////////////////////////////////////////////////////////////////////////////
#include "NstMachine.hpp"
#include "NstCartridge.hpp"
#include "NstCheats.hpp"
#include "NstNsf.hpp"
#include "NstImageDatabase.hpp"
#include "input/NstInpDevice.hpp"
#include "input/NstInpAdapter.hpp"
#include "input/NstInpPad.hpp"
#include "api/NstApiMachine.hpp"
#include "api/NstApiUser.hpp"
namespace Nes
{
namespace Core
{
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("s", on)
#endif
Machine::Machine()
:
state (Api::Machine::NTSC),
frame (0),
extPort (new Input::AdapterTwo( *new Input::Pad(cpu,0), *new Input::Pad(cpu,1) )),
expPort (new Input::Device( cpu )),
image (NULL),
cheats (NULL),
imageDatabase (NULL),
ppu (cpu)
{
}
Machine::~Machine()
{
Unload();
delete imageDatabase;
delete cheats;
delete expPort;
for (uint ports=extPort->NumPorts(), i=0; i < ports; ++i)
delete &extPort->GetDevice(i);
delete extPort;
}
Result Machine::Load
(
std::istream& imageStream,
FavoredSystem system,
bool ask,
std::istream* const patchStream,
bool patchBypassChecksum,
Result* patchResult,
uint type
)
{
Unload();
Image::Context context
(
static_cast<Image::Type>(type),
cpu,
cpu.GetApu(),
ppu,
imageStream,
patchStream,
patchBypassChecksum,
patchResult,
system,
ask,
imageDatabase
);
image = Image::Load( context );
switch (image->GetType())
{
case Image::CARTRIDGE:
state |= Api::Machine::CARTRIDGE;
switch (static_cast<const Cartridge*>(image)->GetProfile().system.type)
{
case Api::Cartridge::Profile::System::VS_UNISYSTEM:
state |= Api::Machine::VS;
break;
case Api::Cartridge::Profile::System::PLAYCHOICE_10:
state |= Api::Machine::PC10;
break;
}
break;
case Image::DISK:
state |= Api::Machine::DISK;
break;
case Image::SOUND:
state |= Api::Machine::SOUND;
break;
}
UpdateModels();
Api::Machine::eventCallback( Api::Machine::EVENT_LOAD, context.result );
return context.result;
}
Result Machine::Unload()
{
if (!image)
return RESULT_OK;
const Result result = PowerOff();
tracker.Unload();
Image::Unload( image );
image = NULL;
state &= (Api::Machine::NTSC|Api::Machine::PAL);
Api::Machine::eventCallback( Api::Machine::EVENT_UNLOAD, result );
return result;
}
void Machine::UpdateModels()
{
const Region region = (state & Api::Machine::NTSC) ? REGION_NTSC : REGION_PAL;
CpuModel cpuModel;
PpuModel ppuModel;
if (image)
{
image->GetDesiredSystem( region, &cpuModel, &ppuModel );
}
else
{
cpuModel = (region == REGION_NTSC ? CPU_RP2A03 : CPU_RP2A07);
ppuModel = (region == REGION_NTSC ? PPU_RP2C02 : PPU_RP2C07);
}
cpu.SetModel( cpuModel );
UpdateVideo( ppuModel, GetColorMode() );
renderer.EnableForcedFieldMerging( ppuModel != PPU_RP2C02 );
}
Machine::ColorMode Machine::GetColorMode() const
{
return
(
renderer.GetPaletteType() == Video::Renderer::PALETTE_YUV ? COLORMODE_YUV :
renderer.GetPaletteType() == Video::Renderer::PALETTE_CUSTOM ? COLORMODE_CUSTOM :
COLORMODE_RGB
);
}
Result Machine::UpdateColorMode()
{
return UpdateColorMode( GetColorMode() );
}
Result Machine::UpdateColorMode(const ColorMode mode)
{
return UpdateVideo( ppu.GetModel(), mode );
}
Result Machine::UpdateVideo(const PpuModel ppuModel,const ColorMode mode)
{
ppu.SetModel( ppuModel, mode == COLORMODE_YUV );
Video::Renderer::PaletteType palette;
switch (mode)
{
case COLORMODE_RGB:
switch (ppuModel)
{
case PPU_RP2C04_0001: palette = Video::Renderer::PALETTE_VS1; break;
case PPU_RP2C04_0002: palette = Video::Renderer::PALETTE_VS2; break;
case PPU_RP2C04_0003: palette = Video::Renderer::PALETTE_VS3; break;
case PPU_RP2C04_0004: palette = Video::Renderer::PALETTE_VS4; break;
default: palette = Video::Renderer::PALETTE_PC10; break;
}
break;
case COLORMODE_CUSTOM:
palette = Video::Renderer::PALETTE_CUSTOM;
break;
default:
palette = Video::Renderer::PALETTE_YUV;
break;
}
return renderer.SetPaletteType( palette );
}
Result Machine::PowerOff(Result result)
{
if (state & Api::Machine::ON)
{
tracker.PowerOff();
if (image && !image->PowerOff() && NES_SUCCEEDED(result))
result = RESULT_WARN_SAVEDATA_LOST;
ppu.PowerOff();
cpu.PowerOff();
state &= ~uint(Api::Machine::ON);
frame = 0;
Api::Machine::eventCallback( Api::Machine::EVENT_POWER_OFF, result );
}
return result;
}
void Machine::Reset(bool hard)
{
if (state & Api::Machine::SOUND)
hard = true;
try
{
frame = 0;
cpu.Reset( hard );
if (!(state & Api::Machine::SOUND))
{
InitializeInputDevices();
cpu.Map( 0x4016 ).Set( this, &Machine::Peek_4016, &Machine::Poke_4016 );
cpu.Map( 0x4017 ).Set( this, &Machine::Peek_4017, &Machine::Poke_4017 );
extPort->Reset();
expPort->Reset();
bool acknowledged = true;
if (image)
{
switch (image->GetDesiredSystem((state & Api::Machine::NTSC) ? REGION_NTSC : REGION_PAL))
{
case SYSTEM_FAMICOM:
case SYSTEM_DENDY:
acknowledged = false;
break;
}
}
ppu.Reset( hard, acknowledged );
if (image)
image->Reset( hard );
if (cheats)
cheats->Reset();
tracker.Reset();
}
else
{
image->Reset( true );
}
cpu.Boot( hard );
if (state & Api::Machine::ON)
{
Api::Machine::eventCallback( hard ? Api::Machine::EVENT_RESET_HARD : Api::Machine::EVENT_RESET_SOFT );
}
else
{
state |= Api::Machine::ON;
Api::Machine::eventCallback( Api::Machine::EVENT_POWER_ON );
}
}
catch (...)
{
PowerOff();
throw;
}
}
void Machine::SwitchMode()
{
NST_ASSERT( !(state & Api::Machine::ON) );
if (state & Api::Machine::NTSC)
state = (state & ~uint(Api::Machine::NTSC)) | Api::Machine::PAL;
else
state = (state & ~uint(Api::Machine::PAL)) | Api::Machine::NTSC;
UpdateModels();
Api::Machine::eventCallback( (state & Api::Machine::NTSC) ? Api::Machine::EVENT_MODE_NTSC : Api::Machine::EVENT_MODE_PAL );
}
void Machine::InitializeInputDevices() const
{
if (state & Api::Machine::GAME)
{
const bool arcade = state & Api::Machine::VS;
extPort->Initialize( arcade );
expPort->Initialize( arcade );
}
}
void Machine::SaveState(State::Saver& saver) const
{
NST_ASSERT( (state & (Api::Machine::GAME|Api::Machine::ON)) > Api::Machine::ON );
saver.Begin( AsciiId<'N','S','T'>::V | 0x1AUL << 24 );
saver.Begin( AsciiId<'N','F','O'>::V ).Write32( image->GetPrgCrc() ).Write32( frame ).End();
cpu.SaveState( saver, AsciiId<'C','P','U'>::V, AsciiId<'A','P','U'>::V );
ppu.SaveState( saver, AsciiId<'P','P','U'>::V );
image->SaveState( saver, AsciiId<'I','M','G'>::V );
saver.Begin( AsciiId<'P','R','T'>::V );
if (extPort->NumPorts() == 4)
{
static_cast<const Input::AdapterFour*>(extPort)->SaveState
(
saver, AsciiId<'4','S','C'>::V
);
}
for (uint i=0; i < extPort->NumPorts(); ++i)
extPort->GetDevice( i ).SaveState( saver, Ascii<'0'>::V + i );
expPort->SaveState( saver, Ascii<'X'>::V );
saver.End();
saver.End();
}
bool Machine::LoadState(State::Loader& loader,const bool resetOnError)
{
NST_ASSERT( (state & (Api::Machine::GAME|Api::Machine::ON)) > Api::Machine::ON );
try
{
if (loader.Begin() != (AsciiId<'N','S','T'>::V | 0x1AUL << 24))
throw RESULT_ERR_INVALID_FILE;
while (const dword chunk = loader.Begin())
{
switch (chunk)
{
case AsciiId<'N','F','O'>::V:
{
const dword crc = loader.Read32();
if
(
loader.CheckCrc() && !(state & Api::Machine::DISK) &&
crc && crc != image->GetPrgCrc() &&
Api::User::questionCallback( Api::User::QUESTION_NST_PRG_CRC_FAIL_CONTINUE ) == Api::User::ANSWER_NO
)
{
for (uint i=0; i < 2; ++i)
loader.End();
return false;
}
frame = loader.Read32();
break;
}
case AsciiId<'C','P','U'>::V:
case AsciiId<'A','P','U'>::V:
cpu.LoadState( loader, AsciiId<'C','P','U'>::V, AsciiId<'A','P','U'>::V, chunk );
break;
case AsciiId<'P','P','U'>::V:
ppu.LoadState( loader );
break;
case AsciiId<'I','M','G'>::V:
image->LoadState( loader );
break;
case AsciiId<'P','R','T'>::V:
extPort->Reset();
expPort->Reset();
while (const dword subId = loader.Begin())
{
if (subId == AsciiId<'4','S','C'>::V)
{
if (extPort->NumPorts() == 4)
static_cast<Input::AdapterFour*>(extPort)->LoadState( loader );
}
else switch (const uint index = (subId >> 16 & 0xFF))
{
case Ascii<'2'>::V:
case Ascii<'3'>::V:
if (extPort->NumPorts() != 4)
break;
case Ascii<'0'>::V:
case Ascii<'1'>::V:
extPort->GetDevice( index - Ascii<'0'>::V ).LoadState( loader, subId & 0xFF00FFFF );
break;
case Ascii<'X'>::V:
expPort->LoadState( loader, subId & 0xFF00FFFF );
break;
}
loader.End();
}
break;
}
loader.End();
}
loader.End();
}
catch (...)
{
if (resetOnError)
Reset( true );
throw;
}
return true;
}
#ifdef NST_MSVC_OPTIMIZE
#pragma optimize("", on)
#endif
void Machine::Execute
(
Video::Output* const video,
Sound::Output* const sound,
Input::Controllers* const input
)
{
NST_ASSERT( state & Api::Machine::ON );
if (!(state & Api::Machine::SOUND))
{
if (state & Api::Machine::CARTRIDGE)
static_cast<Cartridge*>(image)->BeginFrame( Api::Input(*this), input );
extPort->BeginFrame( input );
expPort->BeginFrame( input );
ppu.BeginFrame( tracker.IsFrameLocked() );
if (cheats)
cheats->BeginFrame( tracker.IsFrameLocked() );
cpu.ExecuteFrame( sound );
ppu.EndFrame();
if (video)
renderer.Blit( *video, ppu.GetScreen(), ppu.GetBurstPhase() );
cpu.EndFrame();
if (image)
image->VSync();
extPort->EndFrame();
expPort->EndFrame();
frame++;
}
else
{
static_cast<Nsf*>(image)->BeginFrame();
cpu.ExecuteFrame( sound );
cpu.EndFrame();
image->VSync();
}
}
NES_POKE_D(Machine,4016)
{
extPort->Poke( data );
expPort->Poke( data );
}
NES_PEEK_A(Machine,4016)
{
cpu.Update( address );
return OPEN_BUS | extPort->Peek(0) | expPort->Peek(0);
}
NES_POKE_D(Machine,4017)
{
cpu.GetApu().WriteFrameCtrl( data );
}
NES_PEEK_A(Machine,4017)
{
cpu.Update( address );
return OPEN_BUS | extPort->Peek(1) | expPort->Peek(1);
}
}
}