nestopia/source/win32/NstManagerSaveStates.cpp

452 lines
14 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 <algorithm>
#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<SaveStates> 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 );
}
}
}