summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/emu/debug/debugcmd.cpp23
-rw-r--r--src/emu/debug/debugcmd.h1
-rw-r--r--src/emu/debug/debugcpu.cpp8
-rw-r--r--src/emu/emuopts.cpp2
-rw-r--r--src/emu/emuopts.h4
-rw-r--r--src/emu/inpttype.h7
-rw-r--r--src/emu/ioport.h1
-rw-r--r--src/emu/machine.cpp39
-rw-r--r--src/emu/machine.h5
-rw-r--r--src/emu/save.cpp465
-rw-r--r--src/emu/save.h68
-rw-r--r--src/frontend/mame/ui/submenu.cpp2
-rw-r--r--src/frontend/mame/ui/ui.cpp5
13 files changed, 603 insertions, 27 deletions
diff --git a/src/emu/debug/debugcmd.cpp b/src/emu/debug/debugcmd.cpp
index 38bff17b8c4..dd0c918ae6e 100644
--- a/src/emu/debug/debugcmd.cpp
+++ b/src/emu/debug/debugcmd.cpp
@@ -206,6 +206,9 @@ debugger_commands::debugger_commands(running_machine& machine, debugger_cpu& cpu
m_console.register_command("stateload", CMDFLAG_NONE, 0, 1, 1, std::bind(&debugger_commands::execute_stateload, this, _1, _2));
m_console.register_command("sl", CMDFLAG_NONE, 0, 1, 1, std::bind(&debugger_commands::execute_stateload, this, _1, _2));
+ m_console.register_command("rewind", CMDFLAG_NONE, 0, 0, 0, std::bind(&debugger_commands::execute_rewind, this, _1, _2));
+ m_console.register_command("rw", CMDFLAG_NONE, 0, 0, 0, std::bind(&debugger_commands::execute_rewind, this, _1, _2));
+
m_console.register_command("save", CMDFLAG_NONE, AS_PROGRAM, 3, 4, std::bind(&debugger_commands::execute_save, this, _1, _2));
m_console.register_command("saved", CMDFLAG_NONE, AS_DATA, 3, 4, std::bind(&debugger_commands::execute_save, this, _1, _2));
m_console.register_command("savei", CMDFLAG_NONE, AS_IO, 3, 4, std::bind(&debugger_commands::execute_save, this, _1, _2));
@@ -1627,7 +1630,7 @@ void debugger_commands::execute_stateload(int ref, const std::vector<std::string
const std::string &filename(params[0]);
m_machine.immediate_load(filename.c_str());
- // Clear all PC & memory tracks
+ // clear all PC & memory tracks
for (device_t &device : device_iterator(m_machine.root_device()))
{
device.debug()->track_pc_data_clear();
@@ -1638,6 +1641,24 @@ void debugger_commands::execute_stateload(int ref, const std::vector<std::string
/*-------------------------------------------------
+ execute_rewind - execute the rewind command
+-------------------------------------------------*/
+
+void debugger_commands::execute_rewind(int ref, const std::vector<std::string> &params)
+{
+ m_machine.rewind_step();
+
+ // clear all PC & memory tracks
+ for (device_t &device : device_iterator(m_machine.root_device()))
+ {
+ device.debug()->track_pc_data_clear();
+ device.debug()->track_mem_data_clear();
+ }
+ m_console.printf("Rewind step attempted. Please refer to window message popup for results.\n");
+}
+
+
+/*-------------------------------------------------
execute_save - execute the save command
-------------------------------------------------*/
diff --git a/src/emu/debug/debugcmd.h b/src/emu/debug/debugcmd.h
index 98b6d8ae03d..41405ec0a71 100644
--- a/src/emu/debug/debugcmd.h
+++ b/src/emu/debug/debugcmd.h
@@ -131,6 +131,7 @@ private:
void execute_hotspot(int ref, const std::vector<std::string> &params);
void execute_statesave(int ref, const std::vector<std::string> &params);
void execute_stateload(int ref, const std::vector<std::string> &params);
+ void execute_rewind(int ref, const std::vector<std::string> &params);
void execute_save(int ref, const std::vector<std::string> &params);
void execute_load(int ref, const std::vector<std::string> &params);
void execute_dump(int ref, const std::vector<std::string> &params);
diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp
index eab6f7771e7..4e03e09f54a 100644
--- a/src/emu/debug/debugcpu.cpp
+++ b/src/emu/debug/debugcpu.cpp
@@ -1902,6 +1902,7 @@ void device_debug::single_step(int numsteps)
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_capture();
m_stepsleft = numsteps;
m_stepaddr = ~0;
m_flags |= DEBUG_FLAG_STEPPING;
@@ -1918,6 +1919,7 @@ void device_debug::single_step_over(int numsteps)
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_capture();
m_stepsleft = numsteps;
m_stepaddr = ~0;
m_flags |= DEBUG_FLAG_STEPPING_OVER;
@@ -1934,6 +1936,7 @@ void device_debug::single_step_out()
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_capture();
m_stepsleft = 100;
m_stepaddr = ~0;
m_flags |= DEBUG_FLAG_STEPPING_OUT;
@@ -1950,6 +1953,7 @@ void device_debug::go(offs_t targetpc)
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_invalidate();
m_stopaddr = targetpc;
m_flags |= DEBUG_FLAG_STOP_PC;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
@@ -1964,6 +1968,7 @@ void device_debug::go_vblank()
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_invalidate();
m_flags |= DEBUG_FLAG_STOP_VBLANK;
m_device.machine().debugger().cpu().go_vblank();
}
@@ -1978,6 +1983,7 @@ void device_debug::go_interrupt(int irqline)
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_invalidate();
m_stopirq = irqline;
m_flags |= DEBUG_FLAG_STOP_INTERRUPT;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
@@ -1997,6 +2003,7 @@ void device_debug::go_exception(int exception)
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_invalidate();
m_stopexception = exception;
m_flags |= DEBUG_FLAG_STOP_EXCEPTION;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
@@ -2012,6 +2019,7 @@ void device_debug::go_milliseconds(u64 milliseconds)
{
assert(m_exec != nullptr);
+ m_device.machine().rewind_invalidate();
m_stoptime = m_device.machine().time() + attotime::from_msec(milliseconds);
m_flags |= DEBUG_FLAG_STOP_TIME;
m_device.machine().debugger().cpu().set_execution_state(EXECUTION_STATE_RUNNING);
diff --git a/src/emu/emuopts.cpp b/src/emu/emuopts.cpp
index 11e369f0aca..12e674718d9 100644
--- a/src/emu/emuopts.cpp
+++ b/src/emu/emuopts.cpp
@@ -62,6 +62,8 @@ const options_entry emu_options::s_option_entries[] =
{ nullptr, nullptr, OPTION_HEADER, "CORE STATE/PLAYBACK OPTIONS" },
{ OPTION_STATE, nullptr, OPTION_STRING, "saved state to load" },
{ OPTION_AUTOSAVE, "0", OPTION_BOOLEAN, "enable automatic restore at startup, and automatic save at exit time" },
+ { OPTION_REWIND, "1", OPTION_BOOLEAN, "enable rewind savestates" },
+ { OPTION_REWIND_CAPACITY "(1-500)", "100", OPTION_INTEGER, "quantity of states rewind buffer can contain" },
{ OPTION_PLAYBACK ";pb", nullptr, OPTION_STRING, "playback an input file" },
{ OPTION_RECORD ";rec", nullptr, OPTION_STRING, "record an input file" },
{ OPTION_RECORD_TIMECODE, "0", OPTION_BOOLEAN, "record an input timecode file (requires -record option)" },
diff --git a/src/emu/emuopts.h b/src/emu/emuopts.h
index a449c5d95a1..60de574bb22 100644
--- a/src/emu/emuopts.h
+++ b/src/emu/emuopts.h
@@ -51,6 +51,8 @@
// core state/playback options
#define OPTION_STATE "state"
#define OPTION_AUTOSAVE "autosave"
+#define OPTION_REWIND "rewind"
+#define OPTION_REWIND_CAPACITY "rewind_capacity"
#define OPTION_PLAYBACK "playback"
#define OPTION_RECORD "record"
#define OPTION_RECORD_TIMECODE "record_timecode"
@@ -327,6 +329,8 @@ public:
// core state/playback options
const char *state() const { return value(OPTION_STATE); }
bool autosave() const { return bool_value(OPTION_AUTOSAVE); }
+ int rewind() const { return bool_value(OPTION_REWIND); }
+ int rewind_capacity() const { return int_value(OPTION_REWIND_CAPACITY); }
const char *playback() const { return value(OPTION_PLAYBACK); }
const char *record() const { return value(OPTION_RECORD); }
bool record_timecode() const { return bool_value(OPTION_RECORD_TIMECODE); }
diff --git a/src/emu/inpttype.h b/src/emu/inpttype.h
index b58ca37bec6..a1e5bef133b 100644
--- a/src/emu/inpttype.h
+++ b/src/emu/inpttype.h
@@ -820,11 +820,12 @@ inline void construct_core_types_keypad(simple_list<input_type_entry> &typelist)
inline void construct_core_types_UI(simple_list<input_type_entry> &typelist)
{
- INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ON_SCREEN_DISPLAY,"On Screen Display", input_seq(KEYCODE_TILDE) )
- INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DEBUG_BREAK, "Break in Debugger", input_seq(KEYCODE_TILDE) )
+ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_ON_SCREEN_DISPLAY,"On Screen Display", input_seq(KEYCODE_TILDE, input_seq::not_code, KEYCODE_LSHIFT) )
+ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_DEBUG_BREAK, "Break in Debugger", input_seq(KEYCODE_TILDE, input_seq::not_code, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_CONFIGURE, "Config Menu", input_seq(KEYCODE_TAB) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE, "Pause", input_seq(KEYCODE_P, input_seq::not_code, KEYCODE_LSHIFT, input_seq::not_code, KEYCODE_RSHIFT) )
- INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE_SINGLE, "Pause - Single Step", input_seq(KEYCODE_P, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_P, KEYCODE_RSHIFT) )
+ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_PAUSE_SINGLE, "Pause - Single Step", input_seq(KEYCODE_P, KEYCODE_LSHIFT) )
+ INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_REWIND_SINGLE, "Rewind - Single Step", input_seq(KEYCODE_TILDE, KEYCODE_LSHIFT, input_seq::or_code, KEYCODE_TILDE, KEYCODE_RSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_RESET_MACHINE, "Reset Machine", input_seq(KEYCODE_F3, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SOFT_RESET, "Soft Reset", input_seq(KEYCODE_F3, input_seq::not_code, KEYCODE_LSHIFT) )
INPUT_PORT_DIGITAL_TYPE( 0, UI, UI_SHOW_GFX, "Show Gfx", input_seq(KEYCODE_F4) )
diff --git a/src/emu/ioport.h b/src/emu/ioport.h
index 722c028e4dc..a9f25e29184 100644
--- a/src/emu/ioport.h
+++ b/src/emu/ioport.h
@@ -322,6 +322,7 @@ enum ioport_type
IPT_UI_DEBUG_BREAK,
IPT_UI_PAUSE,
IPT_UI_PAUSE_SINGLE,
+ IPT_UI_REWIND_SINGLE,
IPT_UI_RESET_MACHINE,
IPT_UI_SOFT_RESET,
IPT_UI_SHOW_GFX,
diff --git a/src/emu/machine.cpp b/src/emu/machine.cpp
index fa6375ef937..f55bfb6c5ad 100644
--- a/src/emu/machine.cpp
+++ b/src/emu/machine.cpp
@@ -683,6 +683,42 @@ void running_machine::immediate_load(const char *filename)
//-------------------------------------------------
+// rewind_capture - capture and append a new
+// state to the rewind list
+//-------------------------------------------------
+
+void running_machine::rewind_capture()
+{
+ if (m_save.rewind()->enabled())
+ m_save.rewind()->capture();
+}
+
+
+//-------------------------------------------------
+// rewind_step - a single step back through
+// rewind states
+//-------------------------------------------------
+
+void running_machine::rewind_step()
+{
+ if (m_save.rewind()->enabled())
+ m_save.rewind()->step();
+}
+
+
+//-------------------------------------------------
+// rewind_invalidate - mark all the future rewind
+// states as invalid
+//-------------------------------------------------
+
+void running_machine::rewind_invalidate()
+{
+ if (m_save.rewind()->enabled())
+ m_save.rewind()->invalidate();
+}
+
+
+//-------------------------------------------------
// pause - pause the system
//-------------------------------------------------
@@ -721,7 +757,10 @@ void running_machine::resume()
void running_machine::toggle_pause()
{
if (paused())
+ {
+ rewind_invalidate();
resume();
+ }
else
pause();
}
diff --git a/src/emu/machine.h b/src/emu/machine.h
index 4cf274c9617..efe464983db 100644
--- a/src/emu/machine.h
+++ b/src/emu/machine.h
@@ -229,6 +229,11 @@ public:
void immediate_save(const char *filename);
void immediate_load(const char *filename);
+ // rewind operations
+ void rewind_capture();
+ void rewind_step();
+ void rewind_invalidate();
+
// scheduled operations
void schedule_exit();
void schedule_hard_reset();
diff --git a/src/emu/save.cpp b/src/emu/save.cpp
index dea222227ee..eff71cd6870 100644
--- a/src/emu/save.cpp
+++ b/src/emu/save.cpp
@@ -23,6 +23,7 @@
***************************************************************************/
#include "emu.h"
+#include "emuopts.h"
#include "coreutil.h"
@@ -60,10 +61,11 @@ enum
//-------------------------------------------------
save_manager::save_manager(running_machine &machine)
- : m_machine(machine),
- m_reg_allowed(true),
- m_illegal_regs(0)
+ : m_machine(machine)
+ , m_reg_allowed(true)
+ , m_illegal_regs(0)
{
+ m_rewind = std::make_unique<rewinder>(*this);
}
@@ -169,8 +171,8 @@ void save_manager::save_memory(device_t *device, const char *module, const char
totalname = string_format("%s/%X/%s", module, index, name);
// look for duplicates and an entry to insert in front of
- std::vector<std::unique_ptr<state_entry>>::iterator insert_after = m_entry_list.begin();
- for (auto it = m_entry_list.begin(); it!= m_entry_list.end(); ++it)
+ std::vector<std::unique_ptr<state_entry>>::iterator insert_after = m_entry_list.begin();
+ for (auto it = m_entry_list.begin(); it != m_entry_list.end(); ++it)
{
// stop when we find an entry whose name is after ours
if (it->get()->m_name.compare(totalname)>0)
@@ -183,7 +185,7 @@ void save_manager::save_memory(device_t *device, const char *module, const char
}
// insert us into the list
- m_entry_list.insert(insert_after,std::make_unique<state_entry>(val, totalname.c_str(), device, module, tag ? tag : "", index, valsize, valcount));
+ m_entry_list.insert(insert_after, std::make_unique<state_entry>(val, totalname.c_str(), device, module, tag ? tag : "", index, valsize, valcount));
}
@@ -213,18 +215,19 @@ save_error save_manager::check_file(running_machine &machine, emu_file &file, co
return validate_header(header, gamename, sig, errormsg, "");
}
+
//-------------------------------------------------
// dispatch_postload - invoke all registered
// postload callbacks for updates
//-------------------------------------------------
-
void save_manager::dispatch_postload()
{
for (auto &func : m_postload_list)
func->m_func();
}
+
//-------------------------------------------------
// read_file - read the data from a file
//-------------------------------------------------
@@ -269,18 +272,19 @@ save_error save_manager::read_file(emu_file &file)
return STATERR_NONE;
}
+
//-------------------------------------------------
// dispatch_presave - invoke all registered
// presave callbacks for updates
//-------------------------------------------------
-
void save_manager::dispatch_presave()
{
for (auto &func : m_presave_list)
func->m_func();
}
+
//-------------------------------------------------
// write_file - writes the data to a file
//-------------------------------------------------
@@ -415,26 +419,451 @@ save_manager::state_callback::state_callback(save_prepost_delegate callback)
//-------------------------------------------------
+// ram_state - constructor
+//-------------------------------------------------
+
+ram_state::ram_state(save_manager &save)
+ : m_save(save)
+ , m_data()
+ , m_valid(false)
+ , m_time(m_save.machine().time())
+{
+ m_data.reserve(get_size(save));
+ m_data.clear();
+ m_data.rdbuf()->clear();
+ m_data.seekp(0);
+ m_data.seekg(0);
+}
+
+
+//-------------------------------------------------
+// get_size - utility function to get the
+// uncompressed size of a state
+//-------------------------------------------------
+
+size_t ram_state::get_size(save_manager &save)
+{
+ size_t totalsize = 0;
+
+ for (auto &entry : save.m_entry_list)
+ {
+ totalsize += entry->m_typesize * entry->m_typecount;
+ }
+
+ return totalsize + HEADER_SIZE;
+}
+
+
+//-------------------------------------------------
+// save - write the current machine state to the
+// allocated stream
+//-------------------------------------------------
+
+save_error ram_state::save()
+{
+ // initialize
+ m_valid = false;
+ m_data.seekp(0);
+
+ // if we have illegal registrations, return an error
+ if (m_save.m_illegal_regs > 0)
+ return STATERR_ILLEGAL_REGISTRATIONS;
+
+ // generate the header
+ u8 header[HEADER_SIZE];
+ memcpy(&header[0], STATE_MAGIC_NUM, 8);
+ header[8] = SAVE_VERSION;
+ header[9] = NATIVE_ENDIAN_VALUE_LE_BE(0, SS_MSB_FIRST);
+ strncpy((char *)&header[0x0a], m_save.machine().system().name, 0x1c - 0x0a);
+ u32 sig = m_save.signature();
+ *(u32 *)&header[0x1c] = little_endianize_int32(sig);
+
+ // write the header
+ m_data.write((char *)header, sizeof(header));
+
+ // check for any errors
+ if (!m_data)
+ return STATERR_WRITE_ERROR;
+
+ // call the pre-save functions
+ m_save.dispatch_presave();
+
+ // write all the data
+ for (auto &entry : m_save.m_entry_list)
+ {
+ u32 totalsize = entry->m_typesize * entry->m_typecount;
+ m_data.write((char *)entry->m_data, totalsize);
+
+ // check for any errors
+ if (!m_data)
+ return STATERR_WRITE_ERROR;
+ }
+
+ // final confirmation
+ m_valid = true;
+ m_time = m_save.machine().time();
+
+ return STATERR_NONE;
+}
+
+
+//-------------------------------------------------
+// load - restore the machine state from the
+// stream
+//-------------------------------------------------
+
+save_error ram_state::load()
+{
+ // initialize
+ m_data.seekg(0);
+
+ // if we have illegal registrations, return an error
+ if (m_save.m_illegal_regs > 0)
+ return STATERR_ILLEGAL_REGISTRATIONS;
+
+ // read the header
+ u8 header[HEADER_SIZE];
+ m_data.read((char *)header, sizeof(header));
+
+ // check for any errors
+ if (!m_data)
+ return STATERR_READ_ERROR;
+
+ // verify the header and report an error if it doesn't match
+ u32 sig = m_save.signature();
+ if (m_save.validate_header(header, m_save.machine().system().name, sig, nullptr, "Error: ") != STATERR_NONE)
+ return STATERR_INVALID_HEADER;
+
+ // determine whether or not to flip the data when done
+ bool flip = NATIVE_ENDIAN_VALUE_LE_BE((header[9] & SS_MSB_FIRST) != 0, (header[9] & SS_MSB_FIRST) == 0);
+
+ // read all the data, flipping if necessary
+ for (auto &entry : m_save.m_entry_list)
+ {
+ u32 totalsize = entry->m_typesize * entry->m_typecount;
+ m_data.read((char *)entry->m_data, totalsize);
+
+ // check for any errors
+ if (!m_data)
+ return STATERR_READ_ERROR;
+
+ // handle flipping
+ if (flip)
+ entry->flip_data();
+ }
+
+ // call the post-load functions
+ m_save.dispatch_postload();
+
+ return STATERR_NONE;
+}
+
+
+//-------------------------------------------------
+// rewinder - constuctor
+//-------------------------------------------------
+
+rewinder::rewinder(save_manager &save)
+ : m_save(save)
+ , m_enabled(save.machine().options().rewind())
+ , m_capacity(save.machine().options().rewind_capacity())
+{
+}
+
+
+//-------------------------------------------------
+// check_size - shrink the state list if it is
+// about to hit the capacity
+//-------------------------------------------------
+
+void rewinder::check_size()
+{
+ // safety check that shouldn't be allowed to trigger
+ if (m_state_list.size() > m_capacity)
+ {
+ // drop all states beyond capacity
+ uint32_t count = m_state_list.size() - m_capacity;
+ m_state_list.erase(m_state_list.begin(), m_state_list.begin() + count);
+ }
+
+ // check if we're about to hit capacity
+ if (m_state_list.size() == m_capacity)
+ {
+ // check the last state
+ ram_state *last = m_state_list.back().get();
+
+ // if we're not on top of the list, no need to move states around
+ if (!last->m_valid)
+ return;
+
+ // we can now get the first state and invalidate it
+ std::unique_ptr<ram_state> first(std::move(m_state_list.front()));
+ first->m_valid = false;
+
+ // move it to the end for future use
+ m_state_list.push_back(std::move(first));
+ m_state_list.erase(m_state_list.begin());
+ }
+}
+
+
+//-------------------------------------------------
+// report_error - report rewind success or
+// error type
+//-------------------------------------------------
+
+void rewinder::report_error(save_error error, rewind_operation operation, int index)
+{
+ const char *const opname = (operation == rewind_operation::LOAD) ? "load" : "save";
+
+ switch (error)
+ {
+ // internal saveload failures
+ case STATERR_ILLEGAL_REGISTRATIONS:
+ m_save.machine().popmessage("Error: Unable to %s state due to illegal registrations. See error.log for details.", opname);
+ break;
+
+ case STATERR_INVALID_HEADER:
+ m_save.machine().popmessage("Error: Unable to %s state due to an invalid header. Make sure the save state is correct for this machine.", opname);
+ break;
+
+ case STATERR_READ_ERROR:
+ m_save.machine().popmessage("Error: Unable to %s state due to a read error.", opname);
+ break;
+
+ case STATERR_WRITE_ERROR:
+ m_save.machine().popmessage("Error: Unable to %s state due to a write error.", opname);
+ break;
+
+ // external saveload failures
+ case STATERR_NOT_FOUND:
+ if (operation == rewind_operation::LOAD)
+ m_save.machine().popmessage("No rewind state to load.");
+ break;
+
+ // success
+ case STATERR_NONE:
+ {
+ const char *const warning = (m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE) ? ""
+ : "\nWarning: Save states are not officially supported for this machine.";
+
+ // figure out the qantity of valid states
+ int invalid = get_first_invalid_index();
+
+ // all states are valid
+ if (invalid == REWIND_INDEX_NONE)
+ invalid = m_state_list.size();
+
+ if (operation == rewind_operation::SAVE)
+ m_save.machine().popmessage("Rewind state %i captured.\nRewind state count: %i.%s", invalid - 1, invalid, warning);
+ else
+ m_save.machine().popmessage("Rewind state %i loaded.\nRewind state count: %i.%s", index, invalid, warning);
+ }
+ break;
+
+ // something that shouldn't be allowed to happen
+ default:
+ m_save.machine().popmessage("Error: Unknown error during state %s.", opname);
+ break;
+ }
+}
+
+
+//-------------------------------------------------
+// get_current_index - get the index of the
+// state to assume as current
+//-------------------------------------------------
+
+int rewinder::get_current_index()
+{
+ // nowhere to search
+ if (m_state_list.empty())
+ return REWIND_INDEX_NONE;
+
+ // fetch the current machine time
+ attotime curtime = m_save.machine().time();
+
+ // find the state at the current time, or at least the first one after
+ for (auto it = m_state_list.begin(); it < m_state_list.end(); ++it)
+ if (it->get()->m_time >= curtime)
+ return it - m_state_list.begin();
+
+ // all states are older
+ return REWIND_INDEX_NONE;
+}
+
+
+//-------------------------------------------------
+// get_first_invalid_index - get the index of the
+// first invalid state
+//-------------------------------------------------
+
+int rewinder::get_first_invalid_index()
+{
+ for (auto it = m_state_list.begin(); it < m_state_list.end(); ++it)
+ if (!it->get()->m_valid)
+ return it - m_state_list.begin();
+
+ // all states are valid
+ return REWIND_INDEX_NONE;
+}
+
+
+//-------------------------------------------------
+// invalidate - mark all the future states as
+// invalid
+//-------------------------------------------------
+
+int rewinder::invalidate()
+{
+ // fetch the current state index
+ int index = get_current_index();
+
+ // more invalid states may be farther back, account for them too
+ int invalid = get_first_invalid_index();
+
+ // roll back if we can
+ if (invalid != REWIND_INDEX_NONE && (index == REWIND_INDEX_NONE || invalid < index))
+ index = invalid;
+
+ if (index != REWIND_INDEX_NONE)
+ {
+ // if it's the last state in the list, skip further invalidation
+ if (++index >= m_state_list.size())
+ return index;
+
+ // invalidate all the future states, as the current input might have changed
+ for (auto it = m_state_list.begin() + index; it < m_state_list.end(); ++it)
+ it->get()->m_valid = false;
+ }
+
+ // index of the first invalid state
+ return index;
+}
+
+
+//-------------------------------------------------
+// capture - record a single state
+//-------------------------------------------------
+
+void rewinder::capture()
+{
+ // fetch the current state index and invalidate the future states
+ int index = invalidate();
+
+ if (index == REWIND_INDEX_NONE)
+ {
+ // no current state, create one
+ std::unique_ptr<ram_state> state = std::make_unique<ram_state>(m_save);
+ save_error error = state->save();
+
+ // validate the state
+ if (error == STATERR_NONE)
+ {
+ // it's safe to append
+ m_state_list.push_back(std::move(state));
+ }
+ else
+ {
+ // internal error, complain and evacuate
+ report_error(error, rewind_operation::SAVE);
+ return;
+ }
+ }
+ else
+ {
+ // update the existing state
+ ram_state *state = m_state_list.at(--index).get();
+ save_error error = state->save();
+
+ // validate the state
+ if (error != STATERR_NONE)
+ {
+ // internal error, complain and evacuate
+ report_error(error, rewind_operation::SAVE);
+ return;
+ }
+ }
+
+ // make sure we still fit in
+ check_size();
+
+ // success
+ report_error(STATERR_NONE, rewind_operation::SAVE);
+}
+
+
+//-------------------------------------------------
+// step - single step back in time
+//-------------------------------------------------
+
+void rewinder::step()
+{
+ // check presence of states
+ if (m_state_list.empty())
+ {
+ // no states, complain and evacuate
+ report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
+ return;
+ }
+
+ // fetch the current state index
+ int index = get_current_index();
+
+ // if there is room, retreat
+ if (index != REWIND_INDEX_FIRST)
+ {
+ // we may be on top of the list, when all states are older
+ if (index == REWIND_INDEX_NONE)
+ {
+ // use the last consecutively valid state, to ensure rewinder integrity
+ index = get_first_invalid_index();
+
+ if (index == REWIND_INDEX_NONE)
+ // all states are valid
+ index = m_state_list.size();
+ else if (index == REWIND_INDEX_FIRST)
+ {
+ // no valid states, complain and evacuate
+ report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
+ return;
+ }
+ }
+
+ // obtain the state pointer
+ ram_state *state = m_state_list.at(--index).get();
+
+ // try to load and report the result
+ report_error(state->load(), rewind_operation::LOAD, index);
+ return;
+ }
+
+ // no valid states, complain
+ report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
+}
+
+
+//-------------------------------------------------
// state_entry - constructor
//-------------------------------------------------
state_entry::state_entry(void *data, const char *name, device_t *device, const char *module, const char *tag, int index, u8 size, u32 count)
- : m_data(data),
- m_name(name),
- m_device(device),
- m_module(module),
- m_tag(tag),
- m_index(index),
- m_typesize(size),
- m_typecount(count),
- m_offset(0)
+ : m_data(data)
+ , m_name(name)
+ , m_device(device)
+ , m_module(module)
+ , m_tag(tag)
+ , m_index(index)
+ , m_typesize(size)
+ , m_typecount(count)
+ , m_offset(0)
{
}
//-------------------------------------------------
// flip_data - reverse the endianness of a
-// block of data
+// block of data
//-------------------------------------------------
void state_entry::flip_data()
diff --git a/src/emu/save.h b/src/emu/save.h
index 4f112796eaf..e5c8c2fdbbf 100644
--- a/src/emu/save.h
+++ b/src/emu/save.h
@@ -26,6 +26,7 @@
enum save_error
{
STATERR_NONE,
+ STATERR_NOT_FOUND,
STATERR_ILLEGAL_REGISTRATIONS,
STATERR_INVALID_HEADER,
STATERR_READ_ERROR,
@@ -79,18 +80,25 @@ public:
u32 m_offset; // offset within the final structure
};
+class ram_state;
+class rewinder;
+
class save_manager
{
// type_checker is a set of templates to identify valid save types
template<typename _ItemType> struct type_checker { static const bool is_atom = false; static const bool is_pointer = false; };
template<typename _ItemType> struct type_checker<_ItemType*> { static const bool is_atom = false; static const bool is_pointer = true; };
+ friend class ram_state;
+ friend class rewinder;
+
public:
// construction/destruction
save_manager(running_machine &machine);
// getters
running_machine &machine() const { return m_machine; }
+ rewinder *rewind() { return m_rewind.get(); }
int registration_count() const { return m_entry_list.size(); }
bool registration_allowed() const { return m_reg_allowed; }
@@ -166,19 +174,69 @@ private:
// construction/destruction
state_callback(save_prepost_delegate callback);
- save_prepost_delegate m_func; // delegate
+ save_prepost_delegate m_func; // delegate
};
// internal state
- running_machine & m_machine; // reference to our machine
- bool m_reg_allowed; // are registrations allowed?
- int m_illegal_regs; // number of illegal registrations
+ running_machine & m_machine; // reference to our machine
+ std::unique_ptr<rewinder> m_rewind; // rewinder
+ bool m_reg_allowed; // are registrations allowed?
+ int m_illegal_regs; // number of illegal registrations
- std::vector<std::unique_ptr<state_entry>> m_entry_list; // list of registered entries
+ std::vector<std::unique_ptr<state_entry>> m_entry_list; // list of registered entries
+ std::vector<std::unique_ptr<ram_state>> m_ramstate_list; // list of ram states
std::vector<std::unique_ptr<state_callback>> m_presave_list; // list of pre-save functions
std::vector<std::unique_ptr<state_callback>> m_postload_list; // list of post-load functions
};
+class ram_state
+{
+ save_manager & m_save; // reference to save_manager
+ util::vectorstream m_data; // save data buffer
+
+public:
+ bool m_valid; // can we load this state?
+ attotime m_time; // machine timestamp
+
+ ram_state(save_manager &save);
+ static size_t get_size(save_manager &save);
+ save_error save();
+ save_error load();
+};
+
+class rewinder
+{
+ save_manager & m_save; // reference to save_manager
+ bool m_enabled; // enable rewind savestates
+ uint32_t m_capacity; // imposed limit of total states (1-500)
+ std::vector<std::unique_ptr<ram_state>> m_state_list; // rewinder's own ram states
+
+ // load/save management
+ enum class rewind_operation
+ {
+ SAVE,
+ LOAD
+ };
+
+ enum
+ {
+ REWIND_INDEX_NONE = -1,
+ REWIND_INDEX_FIRST = 0
+ };
+
+ int get_current_index();
+ int get_first_invalid_index();
+ void check_size();
+ void report_error(save_error type, rewind_operation operation, int index = REWIND_INDEX_FIRST);
+
+public:
+ rewinder(save_manager &save);
+ bool enabled() { return m_enabled; }
+ int invalidate();
+ void capture();
+ void step();
+};
+
// template specializations to enumerate the fundamental atomic types you are allowed to save
ALLOW_SAVE_TYPE_AND_ARRAY(char)
diff --git a/src/frontend/mame/ui/submenu.cpp b/src/frontend/mame/ui/submenu.cpp
index 367fb429a53..33632cb4150 100644
--- a/src/frontend/mame/ui/submenu.cpp
+++ b/src/frontend/mame/ui/submenu.cpp
@@ -63,6 +63,8 @@ std::vector<submenu::option> const submenu::advanced_options = {
{ submenu::option_type::HEAD, __("State/Playback Options") },
{ submenu::option_type::EMU, __("Automatic save/restore"), OPTION_AUTOSAVE },
+ { submenu::option_type::EMU, __("Rewind"), OPTION_REWIND },
+ { submenu::option_type::EMU, __("Rewind capacity"), OPTION_REWIND_CAPACITY },
{ submenu::option_type::EMU, __("Bilinear snapshot"), OPTION_SNAPBILINEAR },
{ submenu::option_type::EMU, __("Burn-in"), OPTION_BURNIN },
diff --git a/src/frontend/mame/ui/ui.cpp b/src/frontend/mame/ui/ui.cpp
index 026bf0c34f7..df5089cb1c0 100644
--- a/src/frontend/mame/ui/ui.cpp
+++ b/src/frontend/mame/ui/ui.cpp
@@ -1200,10 +1200,15 @@ uint32_t mame_ui_manager::handler_ingame(render_container &container)
// pause single step
if (machine().ui_input().pressed(IPT_UI_PAUSE_SINGLE))
{
+ machine().rewind_capture();
set_single_step(true);
machine().resume();
}
+ // rewind single step
+ if (machine().ui_input().pressed(IPT_UI_REWIND_SINGLE))
+ machine().rewind_step();
+
// handle a toggle cheats request
if (machine().ui_input().pressed(IPT_UI_TOGGLE_CHEAT))
mame_machine_manager::instance()->cheat().set_enable(!mame_machine_manager::instance()->cheat().enabled());