//////////////////////////////////////////////////////////////////////////////////////// // // 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 "NstResourceString.hpp" #include "NstIoScreen.hpp" #include "NstManagerPaths.hpp" #include "NstDialogAutoSaver.hpp" #include "NstManagerSaveStates.hpp" #include "NstWindowMain.hpp" namespace Nestopia { namespace Managers { NST_COMPILE_ASSERT ( IDM_FILE_QUICK_SAVE_STATE_SLOT_1 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 1 && IDM_FILE_QUICK_SAVE_STATE_SLOT_2 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 2 && IDM_FILE_QUICK_SAVE_STATE_SLOT_3 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 3 && IDM_FILE_QUICK_SAVE_STATE_SLOT_4 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 4 && IDM_FILE_QUICK_SAVE_STATE_SLOT_5 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 5 && IDM_FILE_QUICK_SAVE_STATE_SLOT_6 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 6 && IDM_FILE_QUICK_SAVE_STATE_SLOT_7 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 7 && IDM_FILE_QUICK_SAVE_STATE_SLOT_8 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 8 && IDM_FILE_QUICK_SAVE_STATE_SLOT_9 == IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST + 9 && IDM_FILE_QUICK_LOAD_STATE_SLOT_1 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 1 && IDM_FILE_QUICK_LOAD_STATE_SLOT_2 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 2 && IDM_FILE_QUICK_LOAD_STATE_SLOT_3 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 3 && IDM_FILE_QUICK_LOAD_STATE_SLOT_4 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 4 && IDM_FILE_QUICK_LOAD_STATE_SLOT_5 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 5 && IDM_FILE_QUICK_LOAD_STATE_SLOT_6 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 6 && IDM_FILE_QUICK_LOAD_STATE_SLOT_7 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 7 && IDM_FILE_QUICK_LOAD_STATE_SLOT_8 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 8 && IDM_FILE_QUICK_LOAD_STATE_SLOT_9 == IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST + 9 ); struct SaveStates::Slot::Compare { bool operator () (const Slot& a,const Slot& b) const { return a.time == b.time ? &a < &b : a.time < b.time; } }; SaveStates::SaveStates(Emulator& e,Window::Menu& m,const Paths& p,const Window::Main& w) : Manager ( e, m, this, &SaveStates::OnEmuEvent ), window ( w ), paths ( p ), autoSaveEnabled ( false ), autoSaver ( new Window::AutoSaver(paths) ) { static const Window::Menu::CmdHandler::Entry commands[] = { { IDM_FILE_LOAD_NST, &SaveStates::OnCmdStateLoad }, { IDM_FILE_SAVE_NST, &SaveStates::OnCmdStateSave }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST, &SaveStates::OnCmdSlotLoadNewest }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_1, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_2, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_3, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_4, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_5, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_6, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_7, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_8, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_LOAD_STATE_SLOT_9, &SaveStates::OnCmdSlotLoad }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST, &SaveStates::OnCmdSlotSaveOldest }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_1, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_2, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_3, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_4, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_5, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_6, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_7, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_8, &SaveStates::OnCmdSlotSave }, { IDM_FILE_QUICK_SAVE_STATE_SLOT_9, &SaveStates::OnCmdSlotSave }, { IDM_OPTIONS_AUTOSAVER, &SaveStates::OnCmdAutoSaverOptions }, { IDM_OPTIONS_AUTOSAVER_START, &SaveStates::OnCmdAutoSaverStart } }; menu.Commands().Add( this, commands ); menu[IDM_FILE_LOAD_NST].Disable(); menu[IDM_FILE_SAVE_NST].Disable(); menu[IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST].Disable(); menu[IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST].Disable(); for (uint i=0; i < NUM_SLOTS; ++i) { menu[IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i].Disable(); menu[IDM_FILE_QUICK_SAVE_STATE_SLOT_1 + i].Disable(); } menu[IDM_POS_FILE][IDM_POS_FILE_QUICKLOADSTATE].Disable(); menu[IDM_POS_FILE][IDM_POS_FILE_QUICKSAVESTATE].Disable(); menu[IDM_OPTIONS_AUTOSAVER_START].Disable(); ToggleAutoSaver( false ); UpdateMenuTexts(); } SaveStates::~SaveStates() { ToggleAutoSaver( false ); } void SaveStates::UpdateMenuTexts() const { bool useSeconds = false; for (const Slot* a=slots; a != slots+NUM_SLOTS-1; ++a) { if (a->data.Size()) { for (const Slot* b=a+1; b != slots+NUM_SLOTS; ++b) { if (b->data.Size() && a->time.Almost( b->time )) { useSeconds = true; a = slots+NUM_SLOTS-2; break; } } } } HeapString string( "&1 ..." ); for (uint i=0; i < NUM_SLOTS; ++i) { string[1] = '1' + i; if (slots[i].data.Size()) { string.ShrinkTo( 4 ); string << slots[i].time.ToString( useSeconds ); } else { string.ShrinkTo( 7 ); string[4] = '.'; string[5] = '.'; string[6] = '.'; } menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i ].Text() << string; menu[ IDM_FILE_QUICK_SAVE_STATE_SLOT_1 + i ].Text() << string; } } void SaveStates::OnEmuEvent(const Emulator::Event event,const Emulator::Data data) { switch (event) { case Emulator::EVENT_POWER_ON: case Emulator::EVENT_POWER_OFF: if (emulator.IsGame()) { const bool on = (event == Emulator::EVENT_POWER_ON); const bool single = (on && emulator.NetPlayers() == 0); menu[ IDM_FILE_SAVE_NST ].Enable( single ); menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKSAVESTATE ].Enable( on ); menu[ IDM_OPTIONS_AUTOSAVER_START ].Enable( single ); for (uint i=IDM_FILE_QUICK_SAVE_STATE_SLOT_OLDEST; i <= IDM_FILE_QUICK_SAVE_STATE_SLOT_9; ++i) menu[i].Enable( on ); ToggleAutoSaver( false ); } break; case Emulator::EVENT_LOAD: if (emulator.IsGame()) { const bool single = (emulator.NetPlayers() == 0); menu[ IDM_FILE_LOAD_NST ].Enable( single ); menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKLOADSTATE ].Enable( single ); if (paths.SaveSlotImportingEnabled()) ImportSlots( single ); } break; case Emulator::EVENT_UNLOAD: menu[ IDM_FILE_LOAD_NST ].Disable(); menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKLOADSTATE ].Disable(); menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST ].Disable(); for (uint i=0; i < NUM_SLOTS; ++i) { menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i ].Disable(); slots[i].time.Clear(); slots[i].data.Destroy(); } UpdateMenuTexts(); break; case Emulator::EVENT_MOVIE_PLAYING: case Emulator::EVENT_MOVIE_PLAYING_STOPPED: { const bool notPlaying = (event != Emulator::EVENT_MOVIE_PLAYING); menu[ IDM_FILE_LOAD_NST ].Enable( notPlaying ); menu[ IDM_POS_FILE ][ IDM_POS_FILE_QUICKLOADSTATE ].Enable( notPlaying ); break; } case Emulator::EVENT_NETPLAY_MODE: menu[IDM_POS_OPTIONS][IDM_POS_OPTIONS_AUTOSAVER].Enable( !data ); break; } } void SaveStates::SaveToSlot(const uint index,const bool notify) { if (emulator.SaveState( slots[index].data, paths.UseStateCompression(), Emulator::STICKY )) { if (emulator.NetPlayers() == 0) { menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + index ].Enable(); menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST ].Enable(); } slots[index].time.Set(); if (paths.SaveSlotExportingEnabled()) ExportSlot( index ); if (notify) Io::Screen() << Resource::String( IDS_SCREEN_SAVE_STATE_TO_SLOT ).Invoke( wchar_t('1'+index) ); } else { slots[index].time.Clear(); slots[index].data.Destroy(); } UpdateMenuTexts(); } void SaveStates::LoadFromSlot(const uint index,const bool notify) { if (emulator.LoadState( slots[index].data, Emulator::STICKY )) { if (notify) Io::Screen() << Resource::String( IDS_SCREEN_LOAD_STATE_FROM_SLOT ).Invoke( wchar_t('1'+index) ); } } void SaveStates::Load(Collection::Buffer& data,const GenericString name) const { if (emulator.LoadState( data )) { const uint length = window.GetMaxMessageLength(); if (name.Length() && length > 20) Io::Screen() << Resource::String( IDS_SCREEN_LOAD_STATE_FROM ).Invoke( Path::Compact( name, length - 18 ) ); } } void SaveStates::ExportSlot(const uint index) { Path path( emulator.GetImagePath().Target().File() ); NST_ASSERT( slots[index].data.Size() && path.Length() ); path.Extension() = L"ns1"; path.Back() = '1' + index; paths.Save( slots[index].data.Ptr(), slots[index].data.Size(), Paths::File::SLOTS, path ); } void SaveStates::ImportSlots(const bool canLoad) { bool anyLoaded = false; Path path( emulator.GetImagePath().Target().File() ); NST_ASSERT( path.Length() ); path.Extension() = L"ns1"; Paths::File file; for (uint i=0; i < NUM_SLOTS; ++i) { path.Back() = '1' + i; if (paths.Load( file, Paths::File::SLOTS, path, Paths::QUIETLY )) { anyLoaded = true; slots[i].data.Import( file.data ); slots[i].time.Set( file.name.Ptr() ); if (canLoad) { menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_1 + i ].Enable(); menu[ IDM_FILE_QUICK_LOAD_STATE_SLOT_NEWEST ].Enable(); } } } if (anyLoaded) UpdateMenuTexts(); } void SaveStates::OnCmdStateLoad(uint) { Paths::File file; if (paths.Load( file, Paths::File::STATE|Paths::File::SLOTS|Paths::File::ARCHIVE ) && emulator.LoadState( file.data )) { const uint length = window.GetMaxMessageLength(); if (length > 20) Io::Screen() << Resource::String( IDS_SCREEN_LOAD_STATE_FROM ).Invoke( Path::Compact( file.name, length - 18 ) ); } } void SaveStates::OnCmdStateSave(uint) { const Path path( paths.BrowseSave( Paths::File::STATE, Paths::SUGGEST ) ); if (path.Length()) { Collection::Buffer buffer; if (emulator.SaveState( buffer, paths.UseStateCompression() ) && paths.Save( buffer.Ptr(), buffer.Size(), Paths::File::STATE, path )) { const uint length = window.GetMaxMessageLength(); if (length > 20) Io::Screen() << Resource::String( IDS_SCREEN_SAVE_STATE_TO ).Invoke( Path::Compact( path, length - 18 ) ); } } } void SaveStates::OnCmdSlotSave(uint id) { SaveToSlot( id - IDM_FILE_QUICK_SAVE_STATE_SLOT_1 ); Resume(); } void SaveStates::OnCmdSlotSaveOldest(uint) { SaveToSlot( std::min_element( slots, slots + NUM_SLOTS, Slot::Compare() ) - slots ); Resume(); } void SaveStates::OnCmdSlotLoad(uint id) { LoadFromSlot( id - IDM_FILE_QUICK_LOAD_STATE_SLOT_1 ); Resume(); } void SaveStates::OnCmdSlotLoadNewest(uint) { LoadFromSlot( std::max_element( slots, slots + NUM_SLOTS, Slot::Compare() ) - slots ); Resume(); } void SaveStates::OnCmdAutoSaverOptions(uint) { autoSaver->Open(); ToggleAutoSaver( false ); } void SaveStates::OnCmdAutoSaverStart(uint) { ToggleAutoSaver( !autoSaveEnabled ); Resume(); } uint SaveStates::OnTimerAutoSave() { if (autoSaveEnabled) { const Path stateFile( autoSaver->GetStateFile() ); if (stateFile.Length()) { Collection::Buffer buffer; if ( emulator.SaveState( buffer, paths.UseStateCompression(), Emulator::STICKY ) && paths.Save( buffer.Ptr(), buffer.Size(), Paths::File::STATE, stateFile, Paths::STICKY ) && autoSaver->ShouldNotify() ) { const uint length = window.GetMaxMessageLength(); if (length > 20) Io::Screen() << Resource::String( IDS_SCREEN_SAVE_STATE_TO ).Invoke( Path::Compact( stateFile, length - 18 ) ); } } else { SaveToSlot( Window::AutoSaver::DEFAULT_SAVE_SLOT, autoSaver->ShouldNotify() ); } } return autoSaveEnabled; } void SaveStates::ToggleAutoSaver(const bool enable) { menu[ IDM_OPTIONS_AUTOSAVER_START ].Text() << Resource::String(enable ? IDS_TEXT_STOP : IDS_TEXT_START); if (autoSaveEnabled != enable) { autoSaveEnabled = enable; Io::Screen() << Resource::String( enable ? IDS_AUTOSAVER_START : IDS_AUTOSAVER_STOP ); } if (enable) window.Get().StartTimer( this, &SaveStates::OnTimerAutoSave, autoSaver->GetInterval() ); else window.Get().StopTimer( this, &SaveStates::OnTimerAutoSave ); } } }