mirror of
https://github.com/0ldsk00l/nestopia.git
synced 2025-04-02 10:31:51 -04:00
452 lines
14 KiB
C++
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 );
|
|
}
|
|
}
|
|
}
|