//////////////////////////////////////////////////////////////////////////////////////// // // 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 #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 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(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(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(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( 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(user)->events( static_cast(EVENT_PORT1_CONTROLLER + port), type ); } static void NST_CALLBACK OnAdapterPort(Nes::Input::UserData user,Nes::Input::Adapter adapter) { NST_ASSERT( user ); static_cast(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(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(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(user)->Stop(); static_cast(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(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(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(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(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; } } } }