Merge pull request #2959 from vadosnaprimer/rewind

Rewind implementation fixes and improvements
This commit is contained in:
R. Belmont 2017-12-22 13:01:05 -05:00 committed by GitHub
commit ffc0dadaa8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 320 additions and 263 deletions

View File

@ -334,6 +334,16 @@ Core Output Directory Options
Core state/playback options
---------------------------
**-[no]rewind**
When enabled and emulation is paused, automatically creates a save state in memory every time a frame is advanced. Rewind save states can then be loaded consecutively by pressing the rewind single step shortcut key (*Left Shift + Tilde by default*). The default rewind value is OFF (-norewind).
If debugger is in a 'break' state, a save state is instead created every time step in, step over, or step out occurs. In that mode, rewind save states can be loaded by executing the debugger 'rewind'(or 'rw') command.
**-rewind_capacity** *<value>*
Sets the rewind capacity value, in megabytes. It is the total amount of memory rewind savestates can occupy. When capacity is hit, old savestates get erased as new ones are captured. Setting capacity lower than the current savestate size disables rewind. Values below 0 are automatically clamped to 0.
**-state** *<slot>*
Immediately after starting the specified game, will cause the save state in the specified <slot> to be loaded.

View File

@ -27,7 +27,8 @@ All the keys below are fully configurable in the user interface. This list shows
|
| If you are running with -debug, this key sends a 'break' in emulation.
**P** | Pauses the game.
**Shift+P** | While paused, advances to next frame.
**Shift+P** | While paused, advances to next frame. If rewind is enabled, a new rewind save state is also captured.
**Shift+~** | While paused, loads the most recent rewind save state.
**F2** | Service Mode for games that support it.
**F3** | Resets the game.
**Shift+F3** | Performs a "hard reset", which tears everything down and re-creates it

View File

@ -1646,21 +1646,16 @@ void debugger_commands::execute_stateload(int ref, const std::vector<std::string
void debugger_commands::execute_rewind(int ref, const std::vector<std::string> &params)
{
if (!m_machine.save().rewind()->enabled())
{
m_console.printf("Rewind not enabled\n");
return;
}
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");
bool success = m_machine.rewind_step();
if (success)
// 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();
}
else
m_console.printf("Rewind error occured. See error.log for details.\n");
}

View File

@ -463,7 +463,7 @@ static const help_item static_help_list[] =
"The rewind command loads the most recent RAM-based state. Rewind states, when enabled, are "
"saved when \"step\", \"over\", or \"out\" command gets executed, storing the machine state as "
"of the moment before actually stepping. Consecutively loading rewind states can work like "
"reverse execution. Depending on which steps forward were taken previously, the bahavior can "
"reverse execution. Depending on which steps forward were taken previously, the behavior can "
"be similar to GDB's \"reverse-stepi\" or \"reverse-next\". All output for this command is "
"currently echoed into the running machine window. Previous memory and PC tracking statistics "
"are cleared, actual reverse execution does not occur.\n"

View File

@ -499,9 +499,6 @@ void device_execute_interface::interface_post_start()
assert_always(m_icountptr != nullptr, "m_icountptr never initialized!");
// register for save states
device().save_pointer(NAME(m_icountptr), 1);
device().save_item(NAME(m_cycles_running));
device().save_item(NAME(m_cycles_stolen));
device().save_item(NAME(m_suspend));
device().save_item(NAME(m_nextsuspend));
device().save_item(NAME(m_eatcycles));

View File

@ -63,7 +63,7 @@ const options_entry emu_options::s_option_entries[] =
{ 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, "0", OPTION_BOOLEAN, "enable rewind savestates" },
{ OPTION_REWIND_CAPACITY "(1-500)", "100", OPTION_INTEGER, "quantity of states rewind buffer can contain" },
{ OPTION_REWIND_CAPACITY "(1-2048)", "100", OPTION_INTEGER, "rewind buffer size in megabytes" },
{ 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)" },

View File

@ -825,7 +825,7 @@ inline void construct_core_types_UI(simple_list<input_type_entry> &typelist)
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_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_REWIND_SINGLE, "Rewind - Single Step", input_seq(KEYCODE_TILDE, KEYCODE_LSHIFT) )
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) )

View File

@ -687,10 +687,9 @@ void running_machine::immediate_load(const char *filename)
// state to the rewind list
//-------------------------------------------------
void running_machine::rewind_capture()
bool running_machine::rewind_capture()
{
if (m_save.rewind()->enabled())
m_save.rewind()->capture();
return m_save.rewind()->capture();
}
@ -699,10 +698,9 @@ void running_machine::rewind_capture()
// rewind states
//-------------------------------------------------
void running_machine::rewind_step()
bool running_machine::rewind_step()
{
if (m_save.rewind()->enabled())
m_save.rewind()->step();
return m_save.rewind()->step();
}
@ -713,8 +711,7 @@ void running_machine::rewind_step()
void running_machine::rewind_invalidate()
{
if (m_save.rewind()->enabled())
m_save.rewind()->invalidate();
m_save.rewind()->invalidate();
}
@ -960,6 +957,9 @@ void running_machine::handle_saveload()
if (saverr != STATERR_NONE && m_saveload_schedule == saveload_schedule::SAVE)
file.remove_on_close();
}
else if (openflags == OPEN_FLAG_READ && filerr == osd_file::error::NOT_FOUND)
// attempt to load a non-existent savestate, report empty slot
popmessage("Error: No savestate file to load.", opname);
else
popmessage("Error: Failed to open file for %s operation.", opname);
}

View File

@ -230,8 +230,8 @@ public:
void immediate_load(const char *filename);
// rewind operations
void rewind_capture();
void rewind_step();
bool rewind_capture();
bool rewind_step();
void rewind_invalidate();
// scheduled operations

View File

@ -79,7 +79,12 @@ void save_manager::allow_registration(bool allowed)
// allow/deny registration
m_reg_allowed = allowed;
if (!allowed)
{
dump_registry();
// everything is registered by now, evaluate the savestate size
m_rewind->clamp_capacity();
}
}
@ -567,34 +572,208 @@ rewinder::rewinder(save_manager &save)
: m_save(save)
, m_enabled(save.machine().options().rewind())
, m_capacity(save.machine().options().rewind_capacity())
, m_current_index(REWIND_INDEX_NONE)
, m_first_invalid_index(REWIND_INDEX_NONE)
, m_first_time_warning(true)
, m_first_time_note(true)
{
}
//-------------------------------------------------
// check_size - shrink the state list if it is
// about to hit the capacity
// clamp_capacity - safety checks for commandline
// override
//-------------------------------------------------
void rewinder::check_size()
void rewinder::clamp_capacity()
{
// safety check that shouldn't be allowed to trigger
if (m_state_list.size() > m_capacity)
if (!m_enabled)
return;
const size_t total = m_capacity * 1024 * 1024;
const size_t single = ram_state::get_size(m_save);
// can't set below zero, but allow commandline to override options' upper limit
if (total < 0)
m_capacity = 0;
// if capacity is below savestate size, can't save anything
if (total < single)
{
// drop all states beyond capacity
uint32_t count = m_state_list.size() - m_capacity;
m_enabled = false;
m_save.machine().logerror("Rewind has been disabled, because rewind capacity is smaller than savestate size.\n");
m_save.machine().logerror("Rewind buffer size: %d bytes. Savestate size: %d bytes.\n", total, single);
m_save.machine().popmessage("Rewind has been disabled. See error.log for details");
}
}
//-------------------------------------------------
// invalidate - mark all the future states as
// invalid to prevent loading them, as the
// current input might have changed
//-------------------------------------------------
void rewinder::invalidate()
{
if (!m_enabled)
return;
// is there anything to invalidate?
if (!current_index_is_last())
{
// all states starting from the current one will be invalid
m_first_invalid_index = m_current_index;
// actually invalidate
for (auto it = m_state_list.begin() + m_first_invalid_index; it < m_state_list.end(); ++it)
it->get()->m_valid = false;
}
}
//-------------------------------------------------
// capture - record a single state, returns true
// on success
//-------------------------------------------------
bool rewinder::capture()
{
if (!m_enabled)
{
report_error(STATERR_DISABLED, rewind_operation::SAVE);
return false;
}
if (current_index_is_last())
{
// we need to create a new state
std::unique_ptr<ram_state> state = std::make_unique<ram_state>(m_save);
const 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 false;
}
}
else
{
// invalidate the future states
invalidate();
// update the existing state
ram_state *state = m_state_list.at(m_current_index).get();
const save_error error = state->save();
// validate the state
if (error != STATERR_NONE)
{
// internal error, complain and evacuate
report_error(error, rewind_operation::SAVE);
return false;
}
}
// make sure we will fit in
if (!check_size())
// the list keeps growing
m_current_index++;
// update first invalid index
if (current_index_is_last())
m_first_invalid_index = REWIND_INDEX_NONE;
else
m_first_invalid_index = m_current_index + 1;
// success
report_error(STATERR_NONE, rewind_operation::SAVE);
return true;
}
//-------------------------------------------------
// step - single step back in time, returns true
// on success
//-------------------------------------------------
bool rewinder::step()
{
if (!m_enabled)
{
report_error(STATERR_DISABLED, rewind_operation::LOAD);
return false;
}
// do we have states to load?
if (m_current_index <= REWIND_INDEX_FIRST || m_first_invalid_index == REWIND_INDEX_FIRST)
{
// no valid states, complain and evacuate
report_error(STATERR_NOT_FOUND, rewind_operation::LOAD);
return false;
}
// prepare to load the last valid index if we're too far ahead
if (m_first_invalid_index > REWIND_INDEX_NONE && m_current_index > m_first_invalid_index)
m_current_index = m_first_invalid_index;
// step back and obtain the state pointer
ram_state *state = m_state_list.at(--m_current_index).get();
// try to load and report the result
const save_error error = state->load();
report_error(error, rewind_operation::LOAD);
if (error == save_error::STATERR_NONE)
return true;
return false;
}
//-------------------------------------------------
// check_size - shrink the state list if it is
// about to hit the capacity. returns true if
// the list got shrank
//-------------------------------------------------
bool rewinder::check_size()
{
if (!m_enabled)
return false;
// state sizes in bytes
const size_t singlesize = ram_state::get_size(m_save);
size_t totalsize = m_state_list.size() * singlesize;
// convert our limit from megabytes
const size_t capsize = m_capacity * 1024 * 1024;
// safety check that shouldn't be allowed to trigger
if (totalsize > capsize)
{
// states to remove
const u32 count = (totalsize - capsize) / singlesize;
// drop everything that's beyond 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();
// update before new check
totalsize = m_state_list.size() * singlesize;
// if we're not on top of the list, no need to move states around
if (!last->m_valid)
return;
// check if capacity will be hit by the newly captured state
if (totalsize + singlesize >= capsize)
{
// check if we have spare states ahead
if (!current_index_is_last())
// no need to move states around
return false;
// we can now get the first state and invalidate it
std::unique_ptr<ram_state> first(std::move(m_state_list.front()));
@ -603,246 +782,97 @@ void rewinder::check_size()
// move it to the end for future use
m_state_list.push_back(std::move(first));
m_state_list.erase(m_state_list.begin());
if (m_first_time_note)
{
m_save.machine().logerror("Rewind note: Capacity has been reached. Old savestates will be erased.\n");
m_save.machine().logerror("Capacity: %d bytes. Savestate size: %d bytes. Savestate count: %d.\n",
totalsize, singlesize, m_state_list.size());
m_first_time_note = false;
}
return true;
}
return false;
}
//-------------------------------------------------
// report_error - report rewind success or
// error type
// report_error - report rewind results
//-------------------------------------------------
void rewinder::report_error(save_error error, rewind_operation operation, int index)
void rewinder::report_error(save_error error, rewind_operation operation)
{
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);
m_save.machine().logerror("Rewind error: Unable to %s state due to illegal registrations.", opname);
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
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);
m_save.machine().logerror("Rewind error: Unable to %s state due to an invalid header. "
"Make sure the save state is correct for this machine.\n", opname);
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
break;
case STATERR_READ_ERROR:
m_save.machine().popmessage("Error: Unable to %s state due to a read error.", opname);
m_save.machine().logerror("Rewind error: Unable to %s state due to a read error.\n", opname);
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
break;
case STATERR_WRITE_ERROR:
m_save.machine().popmessage("Error: Unable to %s state due to a write error.", opname);
m_save.machine().logerror("Rewind error: Unable to %s state due to a write error.\n", opname);
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
break;
// external saveload failures
case STATERR_NOT_FOUND:
if (operation == rewind_operation::LOAD)
m_save.machine().popmessage("No rewind state to load.");
{
m_save.machine().logerror("Rewind error: No rewind state to load.\n");
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
}
break;
case STATERR_DISABLED:
if (operation == rewind_operation::LOAD)
{
m_save.machine().logerror("Rewind error: Rewind is disabled.\n");
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
}
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.";
const u64 supported = m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE;
const char *const warning = supported || !m_first_time_warning ? "" :
"Rewind warning: Save states are not officially supported for this machine.\n";
const char *const opnamed = (operation == rewind_operation::LOAD) ? "loaded" : "captured";
// 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);
// for rewinding outside of debugger, give some indication that rewind has worked, as screen doesn't update
m_save.machine().popmessage("Rewind state %i %s.\n%s", m_current_index + 1, opnamed, warning);
if (m_first_time_warning && operation == rewind_operation::LOAD && !supported)
{
m_save.machine().logerror(warning);
m_first_time_warning = false;
}
}
break;
// something that shouldn't be allowed to happen
default:
m_save.machine().popmessage("Error: Unknown error during state %s.", opname);
m_save.machine().logerror("Error: Unknown error during state %s.\n", opname);
m_save.machine().popmessage("Rewind error occured. See error.log for details.");
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
//-------------------------------------------------

View File

@ -30,7 +30,8 @@ enum save_error
STATERR_ILLEGAL_REGISTRATIONS,
STATERR_INVALID_HEADER,
STATERR_READ_ERROR,
STATERR_WRITE_ERROR
STATERR_WRITE_ERROR,
STATERR_DISABLED
};
@ -181,7 +182,7 @@ private:
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
s32 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<ram_state>> m_ramstate_list; // list of ram states
@ -191,12 +192,12 @@ private:
class ram_state
{
save_manager & m_save; // reference to save_manager
util::vectorstream m_data; // save data buffer
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
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);
@ -206,9 +207,13 @@ public:
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)
save_manager & m_save; // reference to save_manager
bool m_enabled; // enable rewind savestates
size_t m_capacity; // total memory rewind states can occupy (MB, limited to 1-2048 in options)
s32 m_current_index; // where we are in time
s32 m_first_invalid_index; // all states before this one are guarateed to be valid
bool m_first_time_warning; // keep track of warnings we report
bool m_first_time_note; // keep track of notes
std::vector<std::unique_ptr<ram_state>> m_state_list; // rewinder's own ram states
// load/save management
@ -221,20 +226,20 @@ class rewinder
enum
{
REWIND_INDEX_NONE = -1,
REWIND_INDEX_FIRST = 0
REWIND_INDEX_FIRST
};
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);
bool check_size();
bool current_index_is_last() { return m_current_index == m_state_list.size() - 1; }
void report_error(save_error type, rewind_operation operation);
public:
rewinder(save_manager &save);
bool enabled() { return m_enabled; }
int invalidate();
void capture();
void step();
void clamp_capacity();
void invalidate();
bool capture();
bool step();
};

View File

@ -201,8 +201,10 @@ bool debugwin_info::handle_key(WPARAM wparam, LPARAM lparam)
return true;
case VK_F11:
if (GetAsyncKeyState(VK_SHIFT) & 0x8000)
if ((GetAsyncKeyState(VK_SHIFT) & 0x8000) && ((GetAsyncKeyState(VK_CONTROL) & 0x8000) == 0))
SendMessage(m_wnd, WM_COMMAND, ID_STEP_OUT, 0);
else if (GetAsyncKeyState(VK_CONTROL) & 0x8000)
SendMessage(m_wnd, WM_COMMAND, ID_REWIND_STEP, 0);
else
SendMessage(m_wnd, WM_COMMAND, ID_STEP, 0);
return true;
@ -325,6 +327,21 @@ bool debugwin_info::handle_command(WPARAM wparam, LPARAM lparam)
machine().debugger().cpu().get_visible_cpu()->debug()->single_step_out();
return true;
case ID_REWIND_STEP:
machine().rewind_step();
// clear all PC & memory tracks
for (device_t &device : device_iterator(machine().root_device()))
{
device.debug()->track_pc_data_clear();
device.debug()->track_mem_data_clear();
}
// update debugger and emulator window
machine().debug_view().update_all();
machine().debugger().refresh_display();
return true;
case ID_HARD_RESET:
machine().schedule_hard_reset();
return true;
@ -533,6 +550,7 @@ HMENU debugwin_info::create_standard_menubar()
AppendMenu(debugmenu, MF_ENABLED, ID_STEP, TEXT("Step Into\tF11"));
AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OVER, TEXT("Step Over\tF10"));
AppendMenu(debugmenu, MF_ENABLED, ID_STEP_OUT, TEXT("Step Out\tShift+F11"));
AppendMenu(debugmenu, MF_ENABLED, ID_REWIND_STEP, TEXT("Rewind Step\tCtrl+F11"));
AppendMenu(debugmenu, MF_DISABLED | MF_SEPARATOR, 0, TEXT(""));
AppendMenu(debugmenu, MF_ENABLED, ID_SOFT_RESET, TEXT("Soft Reset\tF3"));
AppendMenu(debugmenu, MF_ENABLED, ID_HARD_RESET, TEXT("Hard Reset\tShift+F3"));

View File

@ -70,6 +70,7 @@ protected:
ID_STEP,
ID_STEP_OVER,
ID_STEP_OUT,
ID_REWIND_STEP,
ID_HARD_RESET,
ID_SOFT_RESET,
ID_EXIT,