mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
1511 lines
36 KiB
C++
1511 lines
36 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 <new>
|
|
#include "NstIoFile.hpp"
|
|
#include "NstIoLog.hpp"
|
|
#include "NstIoStream.hpp"
|
|
#include "NstIoArchive.hpp"
|
|
#include "NstIoScreen.hpp"
|
|
#include "NstIoWave.hpp"
|
|
#include "NstResourceString.hpp"
|
|
#include "NstWindowUser.hpp"
|
|
#include "NstApplicationInstance.hpp"
|
|
#include "NstManagerEmulator.hpp"
|
|
#include "../core/api/NstApiMachine.hpp"
|
|
#include "../core/api/NstApiCartridge.hpp"
|
|
#include "../core/api/NstApiFds.hpp"
|
|
#include "../core/api/NstApiNsf.hpp"
|
|
#include "../core/api/NstApiMovie.hpp"
|
|
#include "../core/api/NstApiTapeRecorder.hpp"
|
|
#include "../core/api/NstApiRewinder.hpp"
|
|
#include "../core/api/NstApiUser.hpp"
|
|
|
|
namespace Nestopia
|
|
{
|
|
namespace Managers
|
|
{
|
|
struct Emulator::Callbacks
|
|
{
|
|
static Nes::User::Answer NST_CALLBACK Confirm(Nes::User::UserData,Nes::User::Question question)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::User::NUM_QUESTION_CALLBACKS == 2 );
|
|
|
|
switch (question)
|
|
{
|
|
case Nes::User::QUESTION_NST_PRG_CRC_FAIL_CONTINUE:
|
|
case Nes::User::QUESTION_NSV_PRG_CRC_FAIL_CONTINUE:
|
|
|
|
return Window::User::Confirm( IDS_EMU_CRC_MISSMATCH_CONTINUE, IDS_EMU_CRC_MISSMATCH ) ? Nes::User::ANSWER_YES : Nes::User::ANSWER_NO;
|
|
|
|
default:
|
|
|
|
return Nes::User::ANSWER_DEFAULT;
|
|
}
|
|
}
|
|
|
|
static uint NST_CALLBACK ChooseProfile(Nes::User::UserData,const Nes::Cartridge::Profile*,const std::wstring* names,const uint count)
|
|
{
|
|
std::vector<wcstring> strings( count );
|
|
|
|
for (uint i=0; i < count; ++i)
|
|
strings[i] = names[i].c_str();
|
|
|
|
const uint choice = Window::User::Choose( IDS_CHOOSE_GAME, IDS_TEXT_DEFAULT, &strings.front(), count );
|
|
|
|
return choice ? choice - 1 : Nes::Cartridge::CHOOSE_DEFAULT_PROFILE;
|
|
}
|
|
|
|
static void NST_CALLBACK DoFileIO(Nes::User::UserData user,Nes::User::File& context)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::User::NUM_FILE_CALLBACKS == 17 );
|
|
NST_ASSERT( user );
|
|
|
|
Emulator& emulator = *static_cast<Emulator*>(user);
|
|
|
|
switch (context.GetAction())
|
|
{
|
|
case Nes::User::File::LOAD_ROM:
|
|
case Nes::User::File::LOAD_SAMPLE:
|
|
|
|
emulator.LoadFileData
|
|
(
|
|
context,
|
|
context.GetAction() == Nes::User::File::LOAD_SAMPLE,
|
|
emulator.settings.paths.image.Archive(),
|
|
context.GetName()
|
|
);
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_BATTERY:
|
|
case Nes::User::File::LOAD_EEPROM:
|
|
case Nes::User::File::LOAD_TAPE:
|
|
case Nes::User::File::LOAD_TURBOFILE:
|
|
|
|
emulator.LoadImageData( context );
|
|
break;
|
|
|
|
case Nes::User::File::SAVE_BATTERY:
|
|
case Nes::User::File::SAVE_EEPROM:
|
|
case Nes::User::File::SAVE_TAPE:
|
|
case Nes::User::File::SAVE_TURBOFILE:
|
|
|
|
emulator.SaveImageData( context );
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_FDS:
|
|
|
|
emulator.LoadDiskData( context );
|
|
break;
|
|
|
|
case Nes::User::File::SAVE_FDS:
|
|
|
|
emulator.SaveDiskData( context );
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU:
|
|
|
|
emulator.LoadSampleData( L"moepro", context );
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_SAMPLE_MOERO_PRO_YAKYUU_88:
|
|
|
|
emulator.LoadSampleData( L"moepro88", context );
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_SAMPLE_MOERO_PRO_TENNIS:
|
|
|
|
emulator.LoadSampleData( L"mptennis", context );
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_SAMPLE_TERAO_NO_DOSUKOI_OOZUMOU:
|
|
|
|
emulator.LoadSampleData( L"terao", context );
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_SAMPLE_AEROBICS_STUDIO:
|
|
|
|
emulator.LoadSampleData( L"ftaerobi", context );
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void NST_CALLBACK OnMachine(Nes::User::UserData user,Nes::Machine::Event event,Nes::Result result)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::Machine::NUM_EVENT_CALLBACKS == 8 );
|
|
NST_ASSERT( user );
|
|
|
|
Emulator& emulator = *static_cast<Emulator*>(user);
|
|
|
|
switch (event)
|
|
{
|
|
case Nes::Machine::EVENT_LOAD:
|
|
|
|
emulator.events( EVENT_LOAD );
|
|
break;
|
|
|
|
case Nes::Machine::EVENT_UNLOAD:
|
|
|
|
emulator.events( EVENT_UNLOAD );
|
|
Io::Log() << "Emulator: unloaded \"" << emulator.settings.paths.image << "\"\r\n";
|
|
emulator.settings.Reset();
|
|
break;
|
|
|
|
case Nes::Machine::EVENT_POWER_ON:
|
|
|
|
emulator.events( EVENT_POWER_ON );
|
|
break;
|
|
|
|
case Nes::Machine::EVENT_POWER_OFF:
|
|
|
|
if (NES_FAILED(result))
|
|
{
|
|
if (const uint msg = ResultToString( result ))
|
|
Window::User::Fail( msg );
|
|
}
|
|
|
|
case Nes::Machine::EVENT_RESET_SOFT:
|
|
case Nes::Machine::EVENT_RESET_HARD:
|
|
|
|
emulator.Stop();
|
|
|
|
if (emulator.state.paused)
|
|
{
|
|
emulator.state.paused = false;
|
|
emulator.events( EVENT_RESUME );
|
|
}
|
|
|
|
emulator.events
|
|
(
|
|
event == Nes::Machine::EVENT_RESET_HARD ? EVENT_RESET_HARD :
|
|
event == Nes::Machine::EVENT_RESET_SOFT ? EVENT_RESET_SOFT :
|
|
EVENT_POWER_OFF
|
|
);
|
|
break;
|
|
|
|
case Nes::Machine::EVENT_MODE_NTSC:
|
|
case Nes::Machine::EVENT_MODE_PAL:
|
|
|
|
emulator.events( event == Nes::Machine::EVENT_MODE_NTSC ? EVENT_MODE_NTSC : EVENT_MODE_PAL );
|
|
|
|
if (emulator.settings.timing.baseSpeed == DEFAULT_SPEED)
|
|
{
|
|
emulator.events( EVENT_BASE_SPEED );
|
|
emulator.events( EVENT_SPEED );
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void NST_CALLBACK OnEvent(Nes::User::UserData,Nes::User::Event event,const void* data)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::User::NUM_EVENT_CALLBACKS == 3 );
|
|
|
|
switch (event)
|
|
{
|
|
case Nes::User::EVENT_CPU_UNOFFICIAL_OPCODE:
|
|
|
|
NST_ASSERT( data );
|
|
Io::Log() << "Cpu: warning, " << static_cast<cstring>(data) << " opcode executed\r\n";
|
|
break;
|
|
|
|
case Nes::User::EVENT_CPU_JAM:
|
|
|
|
Io::Screen() << Resource::String(IDS_SCREEN_CPU_JAM);
|
|
Io::Log() << "Cpu: jammed!\r\n";
|
|
break;
|
|
|
|
case Nes::User::EVENT_DISPLAY_TIMER:
|
|
|
|
NST_ASSERT( data );
|
|
Io::Screen() << static_cast<cstring>( data );
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void NST_CALLBACK OnControllerPort(Nes::Input::UserData user,uint port,Nes::Input::Type type)
|
|
{
|
|
NST_ASSERT( user && port < Nes::Input::NUM_PORTS );
|
|
|
|
static_cast<Emulator*>(user)->events( static_cast<Event>(EVENT_PORT1_CONTROLLER + port), type );
|
|
}
|
|
|
|
static void NST_CALLBACK OnAdapterPort(Nes::Input::UserData user,Nes::Input::Adapter adapter)
|
|
{
|
|
NST_ASSERT( user );
|
|
|
|
static_cast<Emulator*>(user)->events( EVENT_PORT_ADAPTER, adapter );
|
|
}
|
|
|
|
static void NST_CALLBACK OnDisk(Nes::Nsf::UserData user,Nes::Fds::Event event,uint disk,uint side)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::Fds::NUM_DISK_CALLBACKS == 3 );
|
|
NST_ASSERT( user && side <= 1 );
|
|
|
|
static_cast<Emulator*>(user)->events
|
|
(
|
|
event == Nes::Fds::DISK_INSERT ? EVENT_DISK_INSERT :
|
|
event == Nes::Fds::DISK_EJECT ? EVENT_DISK_EJECT :
|
|
EVENT_DISK_NONSTANDARD,
|
|
disk * 2 + side
|
|
);
|
|
}
|
|
|
|
static void NST_CALLBACK OnTape(Nes::TapeRecorder::UserData user,Nes::TapeRecorder::Event event)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::TapeRecorder::NUM_EVENT_CALLBACKS == 3 );
|
|
NST_ASSERT( user );
|
|
|
|
static_cast<Emulator*>(user)->events
|
|
(
|
|
event == Nes::TapeRecorder::EVENT_PLAYING ? EVENT_TAPE_PLAYING :
|
|
event == Nes::TapeRecorder::EVENT_RECORDING ? EVENT_TAPE_RECORDING :
|
|
EVENT_TAPE_STOPPED
|
|
);
|
|
}
|
|
|
|
static void NST_CALLBACK OnNsf(Nes::Nsf::UserData user,Nes::Nsf::Event event)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::Nsf::NUM_EVENT_CALLBACKS == 3 );
|
|
NST_ASSERT( user );
|
|
|
|
if (event == Nes::Nsf::EVENT_STOP_SONG)
|
|
static_cast<Emulator*>(user)->Stop();
|
|
|
|
static_cast<Emulator*>(user)->events
|
|
(
|
|
event == Nes::Nsf::EVENT_SELECT_SONG ? EVENT_NSF_SELECT :
|
|
event == Nes::Nsf::EVENT_PLAY_SONG ? EVENT_NSF_PLAY :
|
|
EVENT_NSF_STOP
|
|
);
|
|
}
|
|
|
|
static void NST_CALLBACK OnMovie(Nes::Nsf::UserData user,Nes::Movie::Event event,Nes::Result result)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::Movie::NUM_EVENT_CALLBACKS == 4 );
|
|
NST_ASSERT( user );
|
|
|
|
static_cast<Emulator*>(user)->events
|
|
(
|
|
event == Nes::Movie::EVENT_PLAYING ? EVENT_MOVIE_PLAYING :
|
|
event == Nes::Movie::EVENT_PLAYING_STOPPED ? EVENT_MOVIE_PLAYING_STOPPED :
|
|
event == Nes::Movie::EVENT_RECORDING ? EVENT_MOVIE_RECORDING :
|
|
EVENT_MOVIE_RECORDING_STOPPED,
|
|
result
|
|
);
|
|
}
|
|
|
|
static void NST_CALLBACK OnRewind(Nes::Rewinder::UserData user,Nes::Rewinder::State state)
|
|
{
|
|
NST_COMPILE_ASSERT( Nes::Rewinder::NUM_STATE_CALLBACKS == 3 );
|
|
NST_ASSERT( user );
|
|
|
|
static_cast<Emulator*>(user)->events
|
|
(
|
|
state == Nes::Rewinder::PREPARING ? EVENT_REWINDING_PREPARE :
|
|
state == Nes::Rewinder::REWINDING ? EVENT_REWINDING_START :
|
|
EVENT_REWINDING_STOP
|
|
);
|
|
}
|
|
};
|
|
|
|
Emulator::EventHandler::~EventHandler()
|
|
{
|
|
NST_VERIFY( callbacks.Empty() );
|
|
}
|
|
|
|
void Emulator::EventHandler::Add(const Callback& callback)
|
|
{
|
|
NST_ASSERT( bool(callback) && !callbacks.Find( callback ) );
|
|
callbacks.PushBack( callback );
|
|
}
|
|
|
|
void Emulator::EventHandler::Remove(const void* const instance)
|
|
{
|
|
for (Callbacks::Iterator it(callbacks.Begin()), end(callbacks.End()); it != end; ++it)
|
|
{
|
|
if (it->VoidPtr() == instance)
|
|
{
|
|
callbacks.Erase( it );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
void Emulator::EventHandler::operator () (const Event event,const Data data) const
|
|
{
|
|
for (Callbacks::ConstIterator it(callbacks.Begin()), end(callbacks.End()); it != end; ++it)
|
|
(*it)( event, data );
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
Emulator::Context::Context()
|
|
: mode(UNKNOWN)
|
|
{
|
|
for (uint i=0; i < Nes::Input::NUM_PORTS; ++i)
|
|
controllers[i] = UNKNOWN;
|
|
}
|
|
|
|
Emulator::Context::~Context()
|
|
{
|
|
}
|
|
|
|
inline Emulator::Settings::Cartridge::Cartridge()
|
|
: writeProtect(false) {}
|
|
|
|
inline Emulator::Settings::Fds::Fds()
|
|
: save(DISKIMAGE_SAVE_TO_IMAGE) {}
|
|
|
|
inline Emulator::Settings::Timing::Timing()
|
|
:
|
|
speed (DEFAULT_SPEED),
|
|
baseSpeed (DEFAULT_SPEED),
|
|
speeding (false),
|
|
sync (false),
|
|
tripleBuffering (false),
|
|
rewinding (false)
|
|
{}
|
|
|
|
Emulator::Settings::Settings()
|
|
: askSave(false) {}
|
|
|
|
Emulator::Settings::~Settings()
|
|
{
|
|
}
|
|
|
|
void Emulator::Settings::Reset()
|
|
{
|
|
paths.start.Clear();
|
|
paths.image.Clear();
|
|
paths.save.Clear();
|
|
paths.tape.Clear();
|
|
askSave = false;
|
|
}
|
|
|
|
inline Emulator::State::State()
|
|
:
|
|
running (false),
|
|
paused (false),
|
|
activator (this,&State::NoActivator),
|
|
inactivator (this,&State::NoInactivator)
|
|
{}
|
|
|
|
bool Emulator::State::NoActivator()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
void Emulator::State::NoInactivator()
|
|
{
|
|
}
|
|
|
|
Emulator::Netplay::Netplay()
|
|
: player(0), players(0) {}
|
|
|
|
Emulator::Emulator()
|
|
{
|
|
Nes::Machine::eventCallback.Set( &Callbacks::OnMachine, this );
|
|
Nes::User::eventCallback.Set( &Callbacks::OnEvent, NULL );
|
|
Nes::User::questionCallback.Set( &Callbacks::Confirm, NULL );
|
|
Nes::User::fileIoCallback.Set( &Callbacks::DoFileIO, this );
|
|
Nes::Cartridge::chooseProfileCallback.Set( &Callbacks::ChooseProfile, NULL );
|
|
Nes::Input::adapterCallback.Set( &Callbacks::OnAdapterPort, this );
|
|
Nes::Input::controllerCallback.Set( &Callbacks::OnControllerPort, this );
|
|
Nes::Fds::diskCallback.Set( &Callbacks::OnDisk, this );
|
|
Nes::TapeRecorder::eventCallback.Set( &Callbacks::OnTape, this );
|
|
Nes::Movie::eventCallback.Set( &Callbacks::OnMovie, this );
|
|
Nes::Nsf::eventCallback.Set( &Callbacks::OnNsf, this );
|
|
Nes::Rewinder::stateCallback.Set( &Callbacks::OnRewind, this );
|
|
}
|
|
|
|
Emulator::~Emulator()
|
|
{
|
|
Unload();
|
|
|
|
Nes::Machine::eventCallback.Unset();
|
|
Nes::User::eventCallback.Unset();
|
|
Nes::User::questionCallback.Unset();
|
|
Nes::User::fileIoCallback.Unset();
|
|
Nes::Cartridge::chooseProfileCallback.Unset();
|
|
Nes::Input::adapterCallback.Unset();
|
|
Nes::Input::controllerCallback.Unset();
|
|
Nes::Fds::diskCallback.Unset();
|
|
Nes::TapeRecorder::eventCallback.Unset();
|
|
Nes::Movie::eventCallback.Unset();
|
|
Nes::Nsf::eventCallback.Unset();
|
|
Nes::Rewinder::stateCallback.Unset();
|
|
}
|
|
|
|
bool Emulator::IsImage()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::IMAGE);
|
|
}
|
|
|
|
bool Emulator::IsGame()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::GAME);
|
|
}
|
|
|
|
bool Emulator::IsCart()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::CARTRIDGE);
|
|
}
|
|
|
|
bool Emulator::IsFds()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::DISK);
|
|
}
|
|
|
|
bool Emulator::IsNsf()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::SOUND);
|
|
}
|
|
|
|
bool Emulator::IsOn()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::ON);
|
|
}
|
|
|
|
bool Emulator::IsImageOn()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::IMAGE,Nes::Machine::ON);
|
|
}
|
|
|
|
bool Emulator::IsGameOn()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::GAME,Nes::Machine::ON);
|
|
}
|
|
|
|
bool Emulator::IsCartOn()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::CARTRIDGE,Nes::Machine::ON);
|
|
}
|
|
|
|
bool Emulator::IsFdsOn()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::DISK,Nes::Machine::ON);
|
|
}
|
|
|
|
bool Emulator::IsNsfOn()
|
|
{
|
|
return Nes::Machine(*this).Is(Nes::Machine::SOUND,Nes::Machine::ON);
|
|
}
|
|
|
|
bool Emulator::IsLocked()
|
|
{
|
|
return Nes::Machine(*this).IsLocked();
|
|
}
|
|
|
|
void Emulator::Unhook()
|
|
{
|
|
state.activator.Set( &state, &State::NoActivator );
|
|
state.inactivator.Set( &state, &State::NoInactivator );
|
|
}
|
|
|
|
bool Emulator::Start()
|
|
{
|
|
if (!IsOn() || state.paused || (IsNsf() && !Nes::Nsf(*this).IsPlaying()))
|
|
return false;
|
|
|
|
state.running = state.activator();
|
|
|
|
return state.running;
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("t", on)
|
|
#endif
|
|
|
|
void Emulator::Execute
|
|
(
|
|
Nes::Video::Output* const video,
|
|
Nes::Sound::Output* const sound,
|
|
Nes::Input::Controllers* const input
|
|
)
|
|
{
|
|
if (state.running)
|
|
{
|
|
if (netplay)
|
|
netplay.executor( *input );
|
|
|
|
Nes::Emulator::Execute( video, sound, input );
|
|
}
|
|
}
|
|
|
|
#ifdef NST_MSVC_OPTIMIZE
|
|
#pragma optimize("", on)
|
|
#endif
|
|
|
|
void Emulator::Stop()
|
|
{
|
|
if (state.running)
|
|
{
|
|
state.running = false;
|
|
state.inactivator();
|
|
}
|
|
}
|
|
|
|
void Emulator::Pause(const bool pause)
|
|
{
|
|
NST_VERIFY( IsOn() );
|
|
|
|
if (IsOn())
|
|
{
|
|
if (state.paused != pause)
|
|
{
|
|
Stop();
|
|
state.paused = pause;
|
|
events( pause ? EVENT_PAUSE : EVENT_RESUME );
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Emulator::UsesBaseSpeed() const
|
|
{
|
|
return
|
|
(
|
|
(settings.timing.speeding|settings.timing.rewinding) == 0 ||
|
|
(settings.timing.speed == DEFAULT_SPEED)
|
|
);
|
|
}
|
|
|
|
uint Emulator::GetDefaultSpeed()
|
|
{
|
|
if (Nes::Machine(*this).GetMode() == Nes::Machine::NTSC)
|
|
return Nes::Machine::CLK_NTSC_DOT / Nes::Machine::CLK_NTSC_VSYNC;
|
|
else
|
|
return Nes::Machine::CLK_PAL_DOT / Nes::Machine::CLK_PAL_VSYNC;
|
|
}
|
|
|
|
uint Emulator::GetBaseSpeed()
|
|
{
|
|
if (settings.timing.baseSpeed != DEFAULT_SPEED)
|
|
return settings.timing.baseSpeed;
|
|
else
|
|
return GetDefaultSpeed();
|
|
}
|
|
|
|
uint Emulator::GetSpeed()
|
|
{
|
|
return UsesBaseSpeed() ? GetBaseSpeed() : settings.timing.speed;
|
|
}
|
|
|
|
void Emulator::ToggleSpeed(const bool speeding)
|
|
{
|
|
if (settings.timing.speeding != speeding && !netplay)
|
|
{
|
|
settings.timing.speeding = speeding;
|
|
events( speeding ? EVENT_SPEEDING_ON : EVENT_SPEEDING_OFF );
|
|
}
|
|
}
|
|
|
|
void Emulator::ToggleRewind(const bool rewinding)
|
|
{
|
|
if (settings.timing.rewinding != rewinding && !netplay)
|
|
{
|
|
settings.timing.rewinding = rewinding;
|
|
events( rewinding ? EVENT_REWINDING_ON : EVENT_REWINDING_OFF );
|
|
}
|
|
}
|
|
|
|
void Emulator::ResetSpeed(const uint baseSpeed,const bool sync,const bool tripleBuffering)
|
|
{
|
|
settings.timing.speed = DEFAULT_SPEED;
|
|
settings.timing.baseSpeed = baseSpeed;
|
|
settings.timing.speeding = false;
|
|
settings.timing.sync = sync;
|
|
settings.timing.tripleBuffering = tripleBuffering;
|
|
settings.timing.rewinding = false;
|
|
|
|
events( EVENT_BASE_SPEED );
|
|
events( EVENT_SPEED );
|
|
}
|
|
|
|
void Emulator::SetSpeed(const uint speed)
|
|
{
|
|
if (settings.timing.speed != speed)
|
|
{
|
|
settings.timing.speed = speed;
|
|
events( EVENT_SPEED );
|
|
}
|
|
}
|
|
|
|
void Emulator::LoadFileData(Nes::User::File& context,const bool loadSamples,Path path,wcstring const filename) const
|
|
{
|
|
NST_VERIFY( filename && *filename );
|
|
|
|
if (!filename || !*filename)
|
|
return;
|
|
|
|
cstring errReason = NULL;
|
|
|
|
try
|
|
{
|
|
if (path.Length())
|
|
{
|
|
Io::File file( path, Io::File::COLLECT );
|
|
Io::Archive archive( file );
|
|
|
|
uint index = archive.Find( filename );
|
|
|
|
if (index && index-- != Io::Archive::NO_FILES)
|
|
{
|
|
Collection::Buffer buffer( archive[index].Size() );
|
|
|
|
if (buffer.Size())
|
|
{
|
|
archive[index].Uncompress( buffer.Ptr() );
|
|
|
|
archive.Close();
|
|
file.Close();
|
|
|
|
if (loadSamples)
|
|
{
|
|
WAVEFORMATEX format;
|
|
Io::Wave wave( Io::Wave::MODE_READ );
|
|
|
|
if (const uint size = wave.Open( buffer.Ptr(), buffer.Size(), format ))
|
|
{
|
|
buffer.Resize( size );
|
|
wave.Read( buffer.Ptr() );
|
|
wave.Close();
|
|
|
|
const Nes::Result result = context.SetSampleContent
|
|
(
|
|
buffer.Ptr(),
|
|
buffer.Size() / format.nBlockAlign,
|
|
format.nChannels == 2,
|
|
format.wBitsPerSample,
|
|
format.nSamplesPerSec
|
|
);
|
|
|
|
if (NES_FAILED(result))
|
|
throw result;
|
|
}
|
|
else
|
|
{
|
|
throw Nes::RESULT_ERR_INVALID_FILE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const Nes::Result result = context.SetContent( buffer.Ptr(), buffer.Size() );
|
|
|
|
if (NES_FAILED(result))
|
|
throw result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw Nes::RESULT_ERR_INVALID_FILE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw Io::File::ERR_NOT_FOUND;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
path.Set( settings.paths.image.Directory(), filename );
|
|
|
|
if (path.FileExists())
|
|
{
|
|
if (loadSamples)
|
|
{
|
|
WAVEFORMATEX format;
|
|
Io::Wave wave( Io::Wave::MODE_READ );
|
|
|
|
if (const uint size = wave.Open( path, format ))
|
|
{
|
|
Collection::Buffer buffer( size );
|
|
wave.Read( buffer.Ptr() );
|
|
wave.Close();
|
|
|
|
const Nes::Result result = context.SetSampleContent
|
|
(
|
|
buffer.Ptr(),
|
|
buffer.Size() / format.nBlockAlign,
|
|
format.nChannels == 2,
|
|
format.wBitsPerSample,
|
|
format.nSamplesPerSec
|
|
);
|
|
|
|
if (NES_FAILED(result))
|
|
throw result;
|
|
}
|
|
else
|
|
{
|
|
throw Nes::RESULT_ERR_INVALID_FILE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Io::Stream::In stream( path );
|
|
const Nes::Result result = context.SetContent( stream );
|
|
|
|
if (NES_FAILED(result))
|
|
throw result;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
throw Io::File::ERR_NOT_FOUND;
|
|
}
|
|
}
|
|
}
|
|
catch (Nes::Result r)
|
|
{
|
|
if (r == Nes::RESULT_ERR_UNSUPPORTED)
|
|
errReason = "unsupported format";
|
|
else
|
|
errReason = "invalid data";
|
|
}
|
|
catch (Io::File::Exception e)
|
|
{
|
|
if (e == Io::File::ERR_NOT_FOUND)
|
|
errReason = "file not found";
|
|
else
|
|
errReason = "file I/O error";
|
|
}
|
|
catch (Io::Wave::Exception)
|
|
{
|
|
errReason = "invalid data";
|
|
}
|
|
catch (...)
|
|
{
|
|
NST_DEBUG_MSG("Emulator::LoadFileData() generic error!");
|
|
errReason = "generic error";
|
|
}
|
|
|
|
if (errReason)
|
|
Io::Log() << "Emulator: error loading \"" << filename << "\" - " << errReason << "!\r\n";
|
|
else
|
|
Io::Log() << "Emulator: loading \"" << filename << "\"\r\n";
|
|
}
|
|
|
|
void Emulator::LoadImageData(Nes::User::File& context) const
|
|
{
|
|
Path path( settings.paths.save );
|
|
|
|
cstring desc = "image save";
|
|
|
|
switch (context.GetAction())
|
|
{
|
|
case Nes::User::File::LOAD_BATTERY:
|
|
|
|
desc = "battery";
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_EEPROM:
|
|
|
|
desc = "EEPROM";
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_TAPE:
|
|
|
|
desc = "tape";
|
|
path = settings.paths.tape;
|
|
break;
|
|
|
|
case Nes::User::File::LOAD_TURBOFILE:
|
|
|
|
desc = "turbo file";
|
|
|
|
if (path.Length())
|
|
path.Extension() = L"tf";
|
|
|
|
break;
|
|
}
|
|
|
|
if (path.FileExists())
|
|
{
|
|
try
|
|
{
|
|
Io::Stream::In stream( path );
|
|
const Nes::Result result = context.SetContent( stream );
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
Io::Log() << "Emulator: loaded " << desc << " data from \"" << path << "\"\r\n";
|
|
}
|
|
else
|
|
{
|
|
HeapString msg( Resource::String( IDS_CARTRIDGE_LOAD_FAILED ).Invoke( path ) );
|
|
|
|
if (const uint id = ResultToString( result ))
|
|
msg << ' ' << Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
catch (Io::File::Exception id)
|
|
{
|
|
HeapString msg;
|
|
|
|
msg << Resource::String( IDS_CARTRIDGE_LOAD_FAILED ).Invoke( path )
|
|
<< ' '
|
|
<< Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
else if (context.GetAction() != Nes::User::File::LOAD_TAPE)
|
|
{
|
|
if (path.Length())
|
|
Io::Log() << "Emulator: " << desc << " data file \"" << path << "\" not found\r\n";
|
|
else
|
|
Io::Log() << "Emulator: " << desc << " data was not loaded!\r\n";
|
|
}
|
|
}
|
|
|
|
void Emulator::SaveImageData(Nes::User::File& context) const
|
|
{
|
|
Path path( settings.paths.save );
|
|
|
|
cstring desc = "image save";
|
|
uint ids = IDS_EMU_MOVIE_SAVE_IMAGEDATA;
|
|
|
|
switch (context.GetAction())
|
|
{
|
|
case Nes::User::File::SAVE_BATTERY:
|
|
|
|
desc = "battery";
|
|
break;
|
|
|
|
case Nes::User::File::SAVE_EEPROM:
|
|
|
|
desc = "EEPROM";
|
|
ids = IDS_EMU_MOVIE_SAVE_EEPROM;
|
|
break;
|
|
|
|
case Nes::User::File::SAVE_TAPE:
|
|
|
|
desc = "cassette tape";
|
|
ids = IDS_EMU_MOVIE_SAVE_TAPE;
|
|
path = settings.paths.tape;
|
|
break;
|
|
|
|
case Nes::User::File::SAVE_TURBOFILE:
|
|
|
|
desc = "turbo file";
|
|
ids = IDS_EMU_MOVIE_SAVE_TURBOFILE;
|
|
|
|
if (path.Length())
|
|
path.Extension() = L"tf";
|
|
|
|
break;
|
|
}
|
|
|
|
if (settings.cartridge.writeProtect)
|
|
{
|
|
Io::Log() << "Emulator: write-protection enabled, discarding " << desc << " data..\r\n";
|
|
}
|
|
else if (path.Length())
|
|
{
|
|
if (!settings.askSave || Window::User::Confirm( ids ))
|
|
{
|
|
try
|
|
{
|
|
Io::Stream::Out stream( path );
|
|
const Nes::Result result = context.GetContent( stream );
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
Io::Log() << "Emulator: " << desc << " data was saved to \"" << path << "\"\r\n";
|
|
}
|
|
else
|
|
{
|
|
HeapString msg( Resource::String( IDS_CARTRIDGE_SAVE_FAILED ).Invoke( path ) );
|
|
|
|
if (const uint id = ResultToString( result ))
|
|
msg << ' ' << Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
catch (Io::File::Exception id)
|
|
{
|
|
HeapString msg;
|
|
|
|
msg << Resource::String( IDS_CARTRIDGE_SAVE_FAILED ).Invoke( path )
|
|
<< ' '
|
|
<< Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Io::Log() << "Emulator: warning, " << desc << " data was not saved!\r\n";
|
|
}
|
|
}
|
|
|
|
void Emulator::LoadDiskData(Nes::User::File& context) const
|
|
{
|
|
switch (settings.fds.save)
|
|
{
|
|
case DISKIMAGE_SAVE_TO_PATCH:
|
|
|
|
if (settings.paths.save.FileExists())
|
|
{
|
|
try
|
|
{
|
|
Io::Stream::In stream( settings.paths.save );
|
|
const Nes::Result result = context.SetPatchContent( stream );
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
Io::Log() << "Emulator: patched disk image with \"" << settings.paths.save << "\"\r\n";
|
|
}
|
|
else
|
|
{
|
|
HeapString msg( Resource::String( IDS_FDS_PATCHDATALOAD_FAILED ).Invoke( settings.paths.save ) );
|
|
|
|
if (const uint id = ResultToString( result ))
|
|
msg << ' ' << Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
catch (Io::File::Exception id)
|
|
{
|
|
HeapString msg;
|
|
|
|
msg << Resource::String( IDS_FDS_PATCHDATALOAD_FAILED ).Invoke( settings.paths.save )
|
|
<< ' '
|
|
<< Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
else if (settings.paths.save.Length())
|
|
{
|
|
Io::Log() << "Emulator: patch file \"" << settings.paths.save << "\" not found\r\n";
|
|
}
|
|
break;
|
|
|
|
case DISKIMAGE_SAVE_TO_IMAGE:
|
|
|
|
if (!settings.paths.image.FileExists())
|
|
Window::User::Warn( IDS_EMU_FDS_NO_FILE );
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Emulator::SaveDiskData(Nes::User::File& context) const
|
|
{
|
|
if (!settings.askSave || Window::User::Confirm( IDS_EMU_MOVIE_SAVE_FDS ))
|
|
{
|
|
switch (settings.fds.save)
|
|
{
|
|
case DISKIMAGE_SAVE_TO_IMAGE:
|
|
|
|
if (settings.paths.image.FileExists())
|
|
{
|
|
try
|
|
{
|
|
Io::Stream::Out stream( settings.paths.image );
|
|
const Nes::Result result = context.GetContent( stream );
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
Io::Log() << "Emulator: saved disk image to \"" << settings.paths.image << "\"\r\n";
|
|
}
|
|
else
|
|
{
|
|
HeapString msg( Resource::String( IDS_FDS_SAVE_FAILED ) );
|
|
|
|
if (const uint id = ResultToString( result ))
|
|
msg << ' ' << Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
catch (Io::File::Exception id)
|
|
{
|
|
HeapString msg;
|
|
|
|
msg << Resource::String( IDS_FDS_SAVE_FAILED )
|
|
<< ' '
|
|
<< Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Io::Log() << "Emulator: warning, disk image file \"" << settings.paths.image << "\" not found, discarding changes!\r\n";
|
|
}
|
|
break;
|
|
|
|
case DISKIMAGE_SAVE_TO_PATCH:
|
|
|
|
if (settings.paths.save.Length())
|
|
{
|
|
try
|
|
{
|
|
Io::Stream::Out stream( settings.paths.save );
|
|
|
|
const Nes::Result result = context.GetPatchContent
|
|
(
|
|
settings.paths.save.Extension() == L"ips" ? Nes::User::File::PATCH_IPS : Nes::User::File::PATCH_UPS,
|
|
stream
|
|
);
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
Io::Log() << "Emulator: saved disk image patch to \"" << settings.paths.save << "\"\r\n";
|
|
}
|
|
else
|
|
{
|
|
HeapString msg( Resource::String( IDS_FDS_PATCHDATASAVE_FAILED ).Invoke( settings.paths.save ) );
|
|
|
|
if (const uint id = ResultToString( result ))
|
|
msg << ' ' << Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
catch (Io::File::Exception id)
|
|
{
|
|
HeapString msg;
|
|
|
|
msg << Resource::String( IDS_FDS_PATCHDATASAVE_FAILED ).Invoke( settings.paths.save )
|
|
<< ' '
|
|
<< Resource::String( id );
|
|
|
|
Window::User::Warn( msg.Ptr() );
|
|
}
|
|
}
|
|
break;
|
|
|
|
case DISKIMAGE_SAVE_DISABLED:
|
|
|
|
Io::Log() << "Emulator: disk image changes were not saved\r\n";
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Emulator::LoadSampleData(wcstring const filename,Nes::User::File& context) const
|
|
{
|
|
NST_ASSERT( filename && *filename );
|
|
|
|
Path path( settings.paths.samples, filename );
|
|
|
|
for (uint i=0; ; ++i)
|
|
{
|
|
static const wchar_t types[][4] =
|
|
{
|
|
L"zip",
|
|
L"rar",
|
|
L"7z\0"
|
|
};
|
|
|
|
path.Extension() = types[i];
|
|
|
|
if (path.FileExists())
|
|
break;
|
|
|
|
if (i == 2)
|
|
{
|
|
Io::Log() << "Emulator: warning, sound sample pack not found!";
|
|
return;
|
|
}
|
|
}
|
|
|
|
wchar_t wavefile[] = L"xx.wav";
|
|
|
|
uint id = context.GetId();
|
|
|
|
if (id > 99)
|
|
return;
|
|
|
|
wavefile[0] = '0' + id / 10;
|
|
wavefile[1] = '0' + id % 10;
|
|
|
|
LoadFileData( context, true, path, wavefile );
|
|
}
|
|
|
|
bool Emulator::Load
|
|
(
|
|
const Collection::Buffer& imageBuffer,
|
|
const Path& start,
|
|
const Collection::Buffer& patchBuffer,
|
|
const bool bypassPatchValidation,
|
|
const Context& context,
|
|
const Nes::Machine::FavoredSystem favoredSystem,
|
|
const Nes::Machine::AskProfile ask,
|
|
const bool warn
|
|
)
|
|
{
|
|
Application::Instance::Waiter wait;
|
|
|
|
Unload();
|
|
|
|
Io::Log() << "Emulator: loading \"" << context.image << "\"\r\n";
|
|
|
|
settings.paths.start = start;
|
|
settings.paths.image = context.image;
|
|
settings.paths.save = context.save;
|
|
settings.paths.tape = context.tape;
|
|
settings.paths.samples = context.samples;
|
|
|
|
Nes::Result result = Load( imageBuffer, patchBuffer, bypassPatchValidation, favoredSystem, ask, warn );
|
|
|
|
if (result == Nes::RESULT_ERR_MISSING_BIOS && !netplay)
|
|
{
|
|
if (Window::User::Confirm( IDS_EMU_FDS_SUPPLY_BIOS ))
|
|
{
|
|
events( EVENT_DISK_QUERY_BIOS );
|
|
|
|
if (Nes::Fds(*this).HasBIOS())
|
|
result = Load( imageBuffer, patchBuffer, bypassPatchValidation, favoredSystem, ask, warn );
|
|
}
|
|
else
|
|
{
|
|
settings.Reset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (const uint msg = ResultToString( result ))
|
|
{
|
|
if (NES_FAILED(result))
|
|
{
|
|
Window::User::Fail( msg );
|
|
}
|
|
else if (warn)
|
|
{
|
|
Window::User::Warn( msg );
|
|
}
|
|
else
|
|
{
|
|
Io::Log() << "Emulator: warning, " << Resource::String( msg ) << "\r\n";
|
|
}
|
|
}
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
if (context.mode != Context::UNKNOWN)
|
|
Nes::Machine(*this).SetMode( static_cast<Nes::Machine::Mode>(context.mode) );
|
|
|
|
if (IsGame())
|
|
{
|
|
for (uint port=0; port < Nes::Input::NUM_PORTS; ++port)
|
|
{
|
|
if (context.controllers[port] != Context::UNKNOWN)
|
|
Nes::Input(*this).ConnectController( port, static_cast<Nes::Input::Type>(context.controllers[port]) );
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
settings.Reset();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
Nes::Result Emulator::Load
|
|
(
|
|
const Collection::Buffer& imageBuffer,
|
|
const Collection::Buffer& patchBuffer,
|
|
const bool bypassPatchValidation,
|
|
const Nes::Machine::FavoredSystem favoredSystem,
|
|
const Nes::Machine::AskProfile ask,
|
|
const bool warn
|
|
)
|
|
{
|
|
Io::Stream::In imageStream( imageBuffer );
|
|
|
|
if (patchBuffer.Size())
|
|
{
|
|
Io::Stream::In patchStream( patchBuffer );
|
|
Nes::Machine::Patch patch( patchStream, bypassPatchValidation );
|
|
const Nes::Result result = Nes::Machine(*this).Load( imageStream, favoredSystem, patch, ask );
|
|
|
|
if (NES_FAILED(patch.result))
|
|
{
|
|
HeapString msg( Resource::String(IDS_EMU_WARN_PATCHING_FAILED) );
|
|
|
|
if (const uint id = ResultToString( patch.result ))
|
|
msg << ' ' << Resource::String( id );
|
|
|
|
if (warn)
|
|
Window::User::Warn( msg.Ptr() );
|
|
else
|
|
Io::Log() << "Emulator: warning, " << msg << "\r\n";
|
|
}
|
|
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
return Nes::Machine(*this).Load( imageStream, favoredSystem, ask );
|
|
}
|
|
}
|
|
|
|
void Emulator::Unload()
|
|
{
|
|
Nes::Machine(*this).Unload();
|
|
}
|
|
|
|
bool Emulator::SaveState(Collection::Buffer& buffer,const bool compress,const Alert alert)
|
|
{
|
|
buffer.Clear();
|
|
|
|
Nes::Result result;
|
|
|
|
{
|
|
Io::Stream::Out stream( buffer );
|
|
result = Nes::Machine(*this).SaveState( stream, compress ? Nes::Machine::USE_COMPRESSION : Nes::Machine::NO_COMPRESSION );
|
|
}
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
return true;
|
|
|
|
if (const uint msg = ResultToString( result ))
|
|
{
|
|
if (alert == NOISY)
|
|
{
|
|
Window::User::Fail( msg, IDS_EMU_ERR_SAVE_STATE );
|
|
}
|
|
else if (alert == STICKY)
|
|
{
|
|
Io::Screen() << Resource::String( IDS_EMU_ERR_SAVE_STATE )
|
|
<< ' '
|
|
<< Resource::String( msg );
|
|
}
|
|
else
|
|
{
|
|
Io::Log() << "Emulator: "
|
|
<< Resource::String( IDS_EMU_ERR_SAVE_STATE )
|
|
<< ' '
|
|
<< Resource::String( msg )
|
|
<< "\r\n";
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Emulator::LoadState(Collection::Buffer& buffer,const Alert alert)
|
|
{
|
|
const bool on = IsOn();
|
|
|
|
if (!Power( true ))
|
|
return false;
|
|
|
|
Nes::Result result;
|
|
|
|
{
|
|
Io::Stream::In stream( buffer );
|
|
result = Nes::Machine(*this).LoadState( stream );
|
|
}
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
return true;
|
|
|
|
if (!on)
|
|
Power( false );
|
|
|
|
if (const uint msg = ResultToString( result ))
|
|
{
|
|
if (alert == NOISY)
|
|
{
|
|
Window::User::Fail( msg, IDS_EMU_ERR_LOAD_STATE );
|
|
}
|
|
else if (alert == STICKY)
|
|
{
|
|
Io::Screen() << Resource::String( IDS_EMU_ERR_LOAD_STATE )
|
|
<< ' '
|
|
<< Resource::String( msg );
|
|
}
|
|
else
|
|
{
|
|
Io::Log() << "Emulator: "
|
|
<< Resource::String( IDS_EMU_ERR_LOAD_STATE )
|
|
<< ' '
|
|
<< Resource::String( msg )
|
|
<< "\r\n";
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool Emulator::Power(const bool state)
|
|
{
|
|
const Nes::Result result = Nes::Machine(*this).Power( state );
|
|
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (const uint msg = ResultToString( result ))
|
|
Window::User::Fail( msg );
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void Emulator::SendCommand(Command command,const Data data)
|
|
{
|
|
if (netplay)
|
|
{
|
|
netplay.commander( command, data );
|
|
}
|
|
else switch (command)
|
|
{
|
|
case COMMAND_RESET:
|
|
{
|
|
const Nes::Result result = Nes::Machine(*this).Reset( data != 0 );
|
|
|
|
if (NES_FAILED(result))
|
|
{
|
|
if (const uint msg = ResultToString( result ))
|
|
Window::User::Fail( msg );
|
|
}
|
|
break;
|
|
}
|
|
|
|
case COMMAND_DISK_INSERT:
|
|
|
|
Nes::Fds(*this).InsertDisk( data / 2, data % 2 );
|
|
break;
|
|
|
|
case COMMAND_DISK_EJECT:
|
|
|
|
Nes::Fds(*this).EjectDisk();
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Emulator::BeginNetplayMode()
|
|
{
|
|
NST_ASSERT( !netplay && !IsImage() );
|
|
|
|
if (netplay.players == 0)
|
|
{
|
|
netplay.players = 1;
|
|
events( EVENT_NETPLAY_MODE, true );
|
|
}
|
|
}
|
|
|
|
void Emulator::StartNetplay
|
|
(
|
|
const Netplay::Executor& executor,
|
|
const Netplay::Commander& commander,
|
|
const uint player,
|
|
const uint players
|
|
)
|
|
{
|
|
NST_ASSERT( netplay.players && players && player-1 < players );
|
|
|
|
netplay.executor = executor;
|
|
netplay.commander = commander;
|
|
netplay.player = player - 1;
|
|
netplay.players = players;
|
|
}
|
|
|
|
void Emulator::StopNetplay()
|
|
{
|
|
NST_VERIFY( netplay.players );
|
|
|
|
netplay.executor.Unset();
|
|
netplay.commander.Unset();
|
|
netplay.player = 0;
|
|
netplay.players = 1;
|
|
}
|
|
|
|
void Emulator::EndNetplayMode()
|
|
{
|
|
NST_ASSERT( !netplay && !IsImage() );
|
|
|
|
if (netplay.players)
|
|
{
|
|
netplay.players = 0;
|
|
events( EVENT_NETPLAY_MODE, false );
|
|
}
|
|
}
|
|
|
|
uint Emulator::ResultToString(const Nes::Result result)
|
|
{
|
|
if (NES_SUCCEEDED(result))
|
|
{
|
|
if (result != Nes::RESULT_OK && result != Nes::RESULT_NOP)
|
|
{
|
|
switch (result)
|
|
{
|
|
case Nes::RESULT_WARN_BAD_DUMP: return IDS_EMU_WARN_BAD_DUMP;
|
|
case Nes::RESULT_WARN_BAD_PROM: return IDS_EMU_WARN_BAD_PROM;
|
|
case Nes::RESULT_WARN_BAD_CROM: return IDS_EMU_WARN_BAD_CROM;
|
|
case Nes::RESULT_WARN_BAD_FILE_HEADER: return IDS_EMU_WARN_BAD_INES;
|
|
}
|
|
|
|
NST_DEBUG_MSG("warning result not handled");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
switch (result)
|
|
{
|
|
case Nes::RESULT_ERR_WRONG_MODE: return IDS_EMU_ERR_WRONG_MODE;
|
|
case Nes::RESULT_ERR_MISSING_BIOS: return IDS_EMU_ERR_BIOS_FILE_MISSING;
|
|
case Nes::RESULT_ERR_UNSUPPORTED_MAPPER: return IDS_EMU_ERR_UNSUPPORTED_MAPPER;
|
|
case Nes::RESULT_ERR_UNSUPPORTED_VSSYSTEM: return IDS_EMU_ERR_UNSUPPORTED_VSSYSTEM;
|
|
case Nes::RESULT_ERR_UNSUPPORTED_FILE_VERSION: return IDS_EMU_ERR_UNSUPPORTED_FILE_VERSION;
|
|
case Nes::RESULT_ERR_CORRUPT_FILE: return IDS_FILE_ERR_CORRUPT;
|
|
case Nes::RESULT_ERR_INVALID_FILE: return IDS_FILE_ERR_INVALID;
|
|
case Nes::RESULT_ERR_OUT_OF_MEMORY: return IDS_ERR_OUT_OF_MEMORY;
|
|
case Nes::RESULT_ERR_GENERIC: return IDS_ERR_GENERIC;
|
|
case Nes::RESULT_ERR_INVALID_CRC: return IDS_ERR_INVALID_CHECKSUM;
|
|
case Nes::RESULT_ERR_NOT_READY: return 0;
|
|
}
|
|
|
|
NST_DEBUG_MSG("error result not handled");
|
|
|
|
return IDS_ERR_GENERIC;
|
|
}
|
|
}
|
|
}
|
|
}
|