diff --git a/src/common/PKeyboardHandler.cxx b/src/common/PKeyboardHandler.cxx index 6c6f14b70..0f72ac6fa 100644 --- a/src/common/PKeyboardHandler.cxx +++ b/src/common/PKeyboardHandler.cxx @@ -144,8 +144,10 @@ void PhysicalKeyboardHandler::setDefaultMapping(Event::Type event, EventMode mod setDefaultKey(Event::ConsoleRightDiffA , KBDK_F7); setDefaultKey(Event::ConsoleRightDiffB , KBDK_F8); setDefaultKey(Event::SaveState , KBDK_F9); + setDefaultKey(Event::SaveAllStates , KBDK_F9, KBDM_ALT); setDefaultKey(Event::ChangeState , KBDK_F10); setDefaultKey(Event::LoadState , KBDK_F11); + setDefaultKey(Event::LoadAllStates , KBDK_F11, KBDM_ALT); setDefaultKey(Event::TakeSnapshot , KBDK_F12); setDefaultKey(Event::Fry , KBDK_BACKSPACE); setDefaultKey(Event::TogglePauseMode , KBDK_PAUSE); diff --git a/src/common/RewindManager.cxx b/src/common/RewindManager.cxx index 0ca09c48f..7c812414c 100644 --- a/src/common/RewindManager.cxx +++ b/src/common/RewindManager.cxx @@ -36,6 +36,7 @@ RewindManager::RewindManager(OSystem& system, StateManager& statemgr) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RewindManager::setup() { + myStateSize = 0; myLastTimeMachineAdd = false; const string& prefix = myOSystem.settings().getBool("dev.settings") ? "dev." : "plr."; @@ -128,6 +129,7 @@ bool RewindManager::addState(const string& message, bool timeMachine) s.rewind(); // rewind Serializer internal buffers if(myStateManager.saveState(s) && myOSystem.console().tia().saveDisplay(s)) { + myStateSize = std::max(myStateSize, uInt32(s.size())); state.message = message; state.cycles = myOSystem.console().tia().cycles(); myLastTimeMachineAdd = timeMachine; @@ -218,6 +220,129 @@ uInt32 RewindManager::windStates(uInt32 numStates, bool unwind) return rewindStates(numStates); } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string RewindManager::saveAllStates() +{ + uInt8* buffer = NULL; + try + { + ostringstream buf; + buf << myOSystem.stateDir() + << myOSystem.console().properties().get(PropType::Cart_Name) + << ".sta"; + + // Truncate existing file to 0 + FILE* fp; + errno_t err = fopen_s(&fp, buf.str().c_str(), "w"); + // Make sure the file can be opened for writing + if (err != NULL) + return "Can't save to all states file"; + fclose(fp); + + Serializer out(buf.str()); + + int numStates = rewindStates(1000) + 1; + // Save header + buf.str(""); + out.putString(STATE_HEADER); + out.putShort(numStates); + out.putInt(myStateSize); + + buffer = new uInt8[myStateSize]; + for (int i = 0; i < numStates; i++) + { + RewindState& state = myStateList.current(); + Serializer& s = state.data; + // Rewind Serializer internal buffers + s.rewind(); + // Save state + s.getByteArray(buffer, myStateSize); + out.putByteArray(buffer, myStateSize); + out.putString(state.message); + out.putLong(state.cycles); + + if (i < numStates) + unwindStates(1); + } + delete[] buffer; + + buf.str(""); + buf << "Saved " << numStates << " states"; + return buf.str(); + } + catch (...) + { + if (buffer) + delete[] buffer; + + return "Error loading all states"; + } +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +string RewindManager::loadAllStates() +{ + uInt8* buffer = NULL; + try + { + ostringstream buf; + buf << myOSystem.stateDir() + << myOSystem.console().properties().get(PropType::Cart_Name) + << ".sta"; + + // Make sure the file can be opened for reading + Serializer in(buf.str(), true); + if (!in) + return "Can't load from all states file"; + + clear(); + int numStates; + + // Load header + buf.str(""); + // Check compatibility + if (in.getString() != STATE_HEADER) + return "Incompatible all states file"; + numStates = in.getShort(); + myStateSize = in.getInt(); + + buffer = new uInt8[myStateSize]; + for (int i = 0; i < numStates; i++) + { + if (myStateList.full()) + compressStates(); + + // Add new state at the end of the list (queue adds at end) + // This updates the 'current' iterator inside the list + myStateList.addLast(); + RewindState& state = myStateList.current(); + Serializer& s = state.data; + // Rewind Serializer internal buffers + s.rewind(); + + // Fill new state with saved values + in.getByteArray(buffer, myStateSize); + s.putByteArray(buffer, myStateSize); + state.message = in.getString(); + state.cycles = in.getLong(); + } + delete[] buffer; + + // initialize current state (parameters ignored) + loadState(0, 0); + + buf.str(""); + buf << "Loaded " << numStates << " states"; + return buf.str(); + } + catch (...) + { + if (buffer) + delete[] buffer; + + return "Error saving all states"; + } +} // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void RewindManager::compressStates() diff --git a/src/common/RewindManager.hxx b/src/common/RewindManager.hxx index 3c9953721..447406642 100644 --- a/src/common/RewindManager.hxx +++ b/src/common/RewindManager.hxx @@ -136,10 +136,16 @@ class RewindManager */ uInt32 windStates(uInt32 numStates, bool unwind); + string saveAllStates(); + string loadAllStates(); + bool atFirst() const { return myStateList.atFirst(); } bool atLast() const { return myStateList.atLast(); } void resize(uInt32 size) { myStateList.resize(size); } - void clear() { myStateList.clear(); } + void clear() { + myStateSize = 0; + myStateList.clear(); + } /** Convert the cycles into a unit string. @@ -169,6 +175,7 @@ class RewindManager uInt64 myHorizon; double myFactor; bool myLastTimeMachineAdd; + uInt32 myStateSize; struct RewindState { Serializer data; // actual save state diff --git a/src/common/StateManager.cxx b/src/common/StateManager.cxx index b175639ff..e64cf891c 100644 --- a/src/common/StateManager.cxx +++ b/src/common/StateManager.cxx @@ -27,7 +27,6 @@ #include "StateManager.hxx" -#define STATE_HEADER "06000003state" // #define MOVIE_HEADER "03030000movie" // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/common/StateManager.hxx b/src/common/StateManager.hxx index 570216746..211b5fe63 100644 --- a/src/common/StateManager.hxx +++ b/src/common/StateManager.hxx @@ -18,6 +18,8 @@ #ifndef STATE_MANAGER_HXX #define STATE_MANAGER_HXX +#define STATE_HEADER "06000003state" + class OSystem; class RewindManager; diff --git a/src/emucore/Event.hxx b/src/emucore/Event.hxx index 99377a395..1c33f0c4f 100644 --- a/src/emucore/Event.hxx +++ b/src/emucore/Event.hxx @@ -103,7 +103,7 @@ class Event Rewind1Menu, Rewind10Menu, RewindAllMenu, Unwind1Menu, Unwind10Menu, UnwindAllMenu, - StartPauseMode, + StartPauseMode, SaveAllStates, LoadAllStates, LastType }; diff --git a/src/emucore/EventHandler.cxx b/src/emucore/EventHandler.cxx index e5d39ca57..f783857bc 100644 --- a/src/emucore/EventHandler.cxx +++ b/src/emucore/EventHandler.cxx @@ -35,6 +35,7 @@ #include "Settings.hxx" #include "Sound.hxx" #include "StateManager.hxx" +#include "RewindManager.hxx" #include "TimerManager.hxx" #include "Switches.hxx" #include "M6532.hxx" @@ -612,6 +613,11 @@ void EventHandler::handleEvent(Event::Type event, bool pressed) if(pressed) myOSystem.state().saveState(); return; + case Event::SaveAllStates: + if (pressed) + myOSystem.frameBuffer().showMessage(myOSystem.state().rewindManager().saveAllStates()); + return; + case Event::ChangeState: if(pressed) myOSystem.state().changeState(); return; @@ -620,6 +626,11 @@ void EventHandler::handleEvent(Event::Type event, bool pressed) if(pressed) myOSystem.state().loadState(); return; + case Event::LoadAllStates: + if (pressed) + myOSystem.frameBuffer().showMessage(myOSystem.state().rewindManager().loadAllStates()); + return; + case Event::Rewind: if (pressed) myOSystem.state().rewindStates(); return; @@ -1687,6 +1698,8 @@ EventHandler::ActionList EventHandler::ourEmulActionList[EMUL_ACTIONLIST_SIZE] = { Event::Unwind1Menu, "Unwind one state & enter TM UI", "" }, { Event::Unwind10Menu, "Unwind 10 states & enter TM UI", "" }, { Event::UnwindAllMenu, "Unwind all states & enter TM UI", "" }, + { Event::SaveAllStates, "Save all TM states of current game", "" }, + { Event::LoadAllStates, "Load saved TM states for current game", "" }, { Event::Combo1, "Combo 1", "" }, { Event::Combo2, "Combo 2", "" }, diff --git a/src/emucore/EventHandler.hxx b/src/emucore/EventHandler.hxx index 273c3fff2..e981e21e7 100644 --- a/src/emucore/EventHandler.hxx +++ b/src/emucore/EventHandler.hxx @@ -368,10 +368,11 @@ class EventHandler COMBO_SIZE = 16, EVENTS_PER_COMBO = 8, #ifdef PNG_SUPPORT - EMUL_ACTIONLIST_SIZE = 136 + COMBO_SIZE, + PNG_SIZE = 2, #else - EMUL_ACTIONLIST_SIZE = 136 - 2 + COMBO_SIZE, + PNG_SIZE = 0, #endif + EMUL_ACTIONLIST_SIZE = 136 + PNG_SIZE + COMBO_SIZE, MENU_ACTIONLIST_SIZE = 18 ; diff --git a/src/gui/TimeMachineDialog.cxx b/src/gui/TimeMachineDialog.cxx index 6d2d03dad..4f6011fba 100644 --- a/src/gui/TimeMachineDialog.cxx +++ b/src/gui/TimeMachineDialog.cxx @@ -158,6 +158,41 @@ static uInt32 UNWIND_ALL[BUTTON_H] = 0b11000011000011, 0 }; +static uInt32 SAVE_ALL[BUTTON_H] = +{ + 0b00000111100000, + 0b00000111100000, + 0b00000111100000, + 0b00000111100000, + 0b11111111111111, + 0b01111111111110, + 0b00111111111100, + 0b00011111111000, + 0b00001111110000, + 0b00000111100000, + 0b00000011000000, + 0b00000000000000, + 0b11111111111111, + 0b11111111111111, +}; +static uInt32 LOAD_ALL[BUTTON_H] = +{ + 0b00000011000000, + 0b00000111100000, + 0b00001111110000, + 0b00011111111000, + 0b00111111111100, + 0b01111111111110, + 0b11111111111111, + 0b00000111100000, + 0b00000111100000, + 0b00000111100000, + 0b00000111100000, + 0b00000000000000, + 0b11111111111111, + 0b11111111111111, +}; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, @@ -232,6 +267,14 @@ TimeMachineDialog::TimeMachineDialog(OSystem& osystem, DialogContainer& parent, BUTTON_W, BUTTON_H, kUnwindAll); xpos = myUnwindAllWidget->getRight() + BUTTON_GAP * 4; + mySaveAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, SAVE_ALL, + BUTTON_W, BUTTON_H, kSaveAll); + xpos = mySaveAllWidget->getRight() + BUTTON_GAP; + + myLoadAllWidget = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, LOAD_ALL, + BUTTON_W, BUTTON_H, kLoadAll); + xpos = myLoadAllWidget->getRight() + BUTTON_GAP * 4; + // Add message myMessageWidget = new StaticTextWidget(this, font, xpos, ypos + 3, " ", TextAlign::Left, kBGColor); @@ -250,14 +293,6 @@ void TimeMachineDialog::center() // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - void TimeMachineDialog::loadConfig() { - RewindManager& r = instance().state().rewindManager(); - IntArray cycles = r.cyclesList(); - - // Set range and intervals for timeline - uInt32 maxValue = cycles.size() > 1 ? uInt32(cycles.size() - 1) : 0; - myTimeline->setMaxValue(maxValue); - myTimeline->setStepValues(cycles); - // Enable blending (only once is necessary) if(!surface().attributes().blending) { @@ -266,10 +301,7 @@ void TimeMachineDialog::loadConfig() surface().applyAttributes(); } - myMessageWidget->setLabel(""); - handleWinds(_enterWinds); - _enterWinds = 0; - handleToggle(); + initBar(); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -355,11 +387,38 @@ void TimeMachineDialog::handleCommand(CommandSender* sender, int cmd, handleWinds(1000); break; + case kSaveAll: + instance().frameBuffer().showMessage(instance().state().rewindManager().saveAllStates()); + break; + + case kLoadAll: + instance().frameBuffer().showMessage(instance().state().rewindManager().loadAllStates()); + initBar(); + break; + default: Dialog::handleCommand(sender, cmd, data, 0); } } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void TimeMachineDialog::initBar() +{ + RewindManager& r = instance().state().rewindManager(); + IntArray cycles = r.cyclesList(); + + // Set range and intervals for timeline + uInt32 maxValue = cycles.size() > 1 ? uInt32(cycles.size() - 1) : 0; + myTimeline->setMaxValue(maxValue); + myTimeline->setStepValues(cycles); + + myMessageWidget->setLabel(""); + handleWinds(_enterWinds); + _enterWinds = 0; + + handleToggle(); +} + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - string TimeMachineDialog::getTimeString(uInt64 cycles) { @@ -416,6 +475,7 @@ void TimeMachineDialog::handleWinds(Int32 numWinds) myRewind1Widget->setEnabled(!r.atFirst()); myUnwindAllWidget->setEnabled(!r.atLast()); myUnwind1Widget->setEnabled(!r.atLast()); + mySaveAllWidget->setEnabled(r.getLastIdx() != 0); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -424,4 +484,3 @@ void TimeMachineDialog::handleToggle() myToggleWidget->setBitmap(instance().state().mode() == StateManager::Mode::Off ? RECORD : STOP, BUTTON_W, BUTTON_H); } - diff --git a/src/gui/TimeMachineDialog.hxx b/src/gui/TimeMachineDialog.hxx index 0672b380e..b54c02185 100644 --- a/src/gui/TimeMachineDialog.hxx +++ b/src/gui/TimeMachineDialog.hxx @@ -40,6 +40,9 @@ class TimeMachineDialog : public Dialog void handleKeyDown(StellaKey key, StellaMod mod) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override; + /** initialize timeline bar */ + void initBar(); + /** This dialog uses its own positioning, so we override Dialog::center() */ void center() override; @@ -62,6 +65,8 @@ class TimeMachineDialog : public Dialog kUnwindAll = 'TMua', kUnwind10 = 'TMu1', kUnwind1 = 'TMun', + kSaveAll = 'TMsv', + kLoadAll = 'TMld', }; TimeLineWidget* myTimeline; @@ -72,6 +77,8 @@ class TimeMachineDialog : public Dialog ButtonWidget* myRewind1Widget; ButtonWidget* myUnwind1Widget; ButtonWidget* myUnwindAllWidget; + ButtonWidget* mySaveAllWidget; + ButtonWidget* myLoadAllWidget; StaticTextWidget* myCurrentTimeWidget; StaticTextWidget* myLastTimeWidget;