From 3555ce616ecbf8df1ccfa1dc229a0f81c1c1ea7f Mon Sep 17 00:00:00 2001 From: Olivier Galibert Date: Sun, 3 Dec 2017 16:55:38 +0100 Subject: More fixes and simplifications (nw) --- src/emu/debug/debugcpu.cpp | 86 +++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 63 deletions(-) (limited to 'src/emu/debug/debugcpu.cpp') diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp index eab6f7771e7..9a1b35cf210 100644 --- a/src/emu/debug/debugcpu.cpp +++ b/src/emu/debug/debugcpu.cpp @@ -389,34 +389,20 @@ u8 debugger_cpu::read_byte(address_space &space, offs_t address, bool apply_tran u16 debugger_cpu::read_word(address_space &space, offs_t address, bool apply_translation) { + device_memory_interface &memory = space.device().memory(); + /* mask against the logical byte mask */ address &= space.logaddrmask(); u16 result; - if (!WORD_ALIGNED(address)) - { /* if this is misaligned read, or if there are no word readers, just read two bytes */ - u8 byte0 = read_byte(space, address + 0, apply_translation); - u8 byte1 = read_byte(space, address + 1, apply_translation); - - /* based on the endianness, the result is assembled differently */ - if (space.endianness() == ENDIANNESS_LITTLE) - result = byte0 | (byte1 << 8); - else - result = byte1 | (byte0 << 8); + /* translate if necessary; if not mapped, return 0xffff */ + if (apply_translation && !memory.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) + { + result = 0xffff; } else - { /* otherwise, this proceeds like the byte case */ - device_memory_interface &memory = space.device().memory(); - - /* translate if necessary; if not mapped, return 0xffff */ - if (apply_translation && !memory.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) - { - result = 0xffff; - } - else - { /* otherwise, call the byte reading function for the translated address */ - result = space.read_word(address); - } + { /* otherwise, call the byte reading function for the translated address */ + result = space.read_word_unaligned(address); } return result; @@ -430,33 +416,20 @@ u16 debugger_cpu::read_word(address_space &space, offs_t address, bool apply_tra u32 debugger_cpu::read_dword(address_space &space, offs_t address, bool apply_translation) { + device_memory_interface &memory = space.device().memory(); + /* mask against the logical byte mask */ address &= space.logaddrmask(); u32 result; - if (!DWORD_ALIGNED(address)) - { /* if this is a misaligned read, or if there are no dword readers, just read two words */ - u16 word0 = read_word(space, address + 0, apply_translation); - u16 word1 = read_word(space, address + 2, apply_translation); - /* based on the endianness, the result is assembled differently */ - if (space.endianness() == ENDIANNESS_LITTLE) - result = word0 | (word1 << 16); - else - result = word1 | (word0 << 16); + if (apply_translation && !memory.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) + { /* translate if necessary; if not mapped, return 0xffffffff */ + result = 0xffffffff; } else - { /* otherwise, this proceeds like the byte case */ - device_memory_interface &memory = space.device().memory(); - - if (apply_translation && !memory.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) - { /* translate if necessary; if not mapped, return 0xffffffff */ - result = 0xffffffff; - } - else - { /* otherwise, call the byte reading function for the translated address */ - result = space.read_dword(address); - } + { /* otherwise, call the byte reading function for the translated address */ + result = space.read_dword_unaligned(address); } return result; @@ -470,34 +443,21 @@ u32 debugger_cpu::read_dword(address_space &space, offs_t address, bool apply_tr u64 debugger_cpu::read_qword(address_space &space, offs_t address, bool apply_translation) { + device_memory_interface &memory = space.device().memory(); + /* mask against the logical byte mask */ address &= space.logaddrmask(); u64 result; - if (!QWORD_ALIGNED(address)) - { /* if this is a misaligned read, or if there are no qword readers, just read two dwords */ - u32 dword0 = read_dword(space, address + 0, apply_translation); - u32 dword1 = read_dword(space, address + 4, apply_translation); - /* based on the endianness, the result is assembled differently */ - if (space.endianness() == ENDIANNESS_LITTLE) - result = dword0 | (u64(dword1) << 32); - else - result = dword1 | (u64(dword0) << 32); + /* translate if necessary; if not mapped, return 0xffffffffffffffff */ + if (apply_translation && !memory.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) + { + result = ~u64(0); } else - { /* otherwise, this proceeds like the byte case */ - device_memory_interface &memory = space.device().memory(); - - /* translate if necessary; if not mapped, return 0xffffffffffffffff */ - if (apply_translation && !memory.translate(space.spacenum(), TRANSLATE_READ_DEBUG, address)) - { - result = ~u64(0); - } - else - { /* otherwise, call the byte reading function for the translated address */ - result = space.read_qword(address); - } + { /* otherwise, call the byte reading function for the translated address */ + result = space.read_qword_unaligned(address); } return result; -- cgit v1.2.3-70-g09d2 From a66cb36cc79dc9ecefd9e78441b321153ed4ac95 Mon Sep 17 00:00:00 2001 From: vadosnaprimer Date: Tue, 10 Oct 2017 21:31:01 +0300 Subject: Rewind feature and RAM savestates. This starts the work requested in #2398. How RAM states work. Implemented using util::vectorstream. Instead of dumping m_save.m_entry_list to file, it writes them as binary to vectorstream. Compression is not used, as it would slow down the process. The header is written as usual, also in binary. When a state is loaded, the savestate data gets binary-read from vectorstream. How rewind works. Rewind is optional, it can be turned off through MAME GUI while not running. Rewind capacity is available there too. Rewind step hotkey is available from the standard hotkey menu. In the debugger you have the "rewind" command ("rw" shortcut) that works the same as the hotkey. Every time you advance a frame (pause step), rewinder captures a RAM savestate of the frame you were at. It does the same when you do step into/over/out in the debugger. Every time it captures a new state (and when you unpause), it marks as invalid all its states that go after the current machine time, because input might change, so they are not relevant anymore. It keeps their buffers allocated though, for future use. When rewinder runs out of allowed amount of savestates it can have, it invalidates the first state in the list and tosses its unique_ptr to the end of the list, then it uses its buffer to capture a new state. When you hit the rewind step key, or use "rewind" command in the debugger, it loads a state that is immediately before the current machine time. Invalid states between valid ones are not allowed to appear, as that breaks rewinder integrity and causes problems. Rewinder keeps its own set of ram states as a vector of unique_ptr's. All rewinder operations and errors get reported using machine().popmessage(). --- src/emu/debug/debugcmd.cpp | 23 +- src/emu/debug/debugcmd.h | 1 + src/emu/debug/debugcpu.cpp | 8 + src/emu/emuopts.cpp | 2 + src/emu/emuopts.h | 4 + src/emu/inpttype.h | 7 +- src/emu/ioport.h | 1 + src/emu/machine.cpp | 39 ++++ src/emu/machine.h | 5 + src/emu/save.cpp | 465 +++++++++++++++++++++++++++++++++++++-- src/emu/save.h | 68 +++++- src/frontend/mame/ui/submenu.cpp | 2 + src/frontend/mame/ui/ui.cpp | 5 + 13 files changed, 603 insertions(+), 27 deletions(-) (limited to 'src/emu/debug/debugcpu.cpp') 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::vectortrack_pc_data_clear(); @@ -1637,6 +1640,24 @@ void debugger_commands::execute_stateload(int ref, const std::vector ¶ms) +{ + 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 ¶ms); void execute_statesave(int ref, const std::vector ¶ms); void execute_stateload(int ref, const std::vector ¶ms); + void execute_rewind(int ref, const std::vector ¶ms); void execute_save(int ref, const std::vector ¶ms); void execute_load(int ref, const std::vector ¶ms); void execute_dump(int ref, const std::vector ¶ms); 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 &typelist) inline void construct_core_types_UI(simple_list &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 @@ -682,6 +682,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(*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>::iterator insert_after = m_entry_list.begin(); - for (auto it = m_entry_list.begin(); it!= m_entry_list.end(); ++it) + std::vector>::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(val, totalname.c_str(), device, module, tag ? tag : "", index, valsize, valcount)); + m_entry_list.insert(insert_after, std::make_unique(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 //------------------------------------------------- @@ -414,27 +418,452 @@ 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 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 state = std::make_unique(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 struct type_checker { static const bool is_atom = false; static const bool is_pointer = false; }; template 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 m_rewind; // rewinder + bool m_reg_allowed; // are registrations allowed? + int m_illegal_regs; // number of illegal registrations - std::vector> m_entry_list; // list of registered entries + std::vector> m_entry_list; // list of registered entries + std::vector> m_ramstate_list; // list of ram states std::vector> m_presave_list; // list of pre-save functions std::vector> 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> 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 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()); -- cgit v1.2.3-70-g09d2 From ab9396830799f43d10b9cb463a3df3a12f727656 Mon Sep 17 00:00:00 2001 From: AJR Date: Wed, 6 Dec 2017 21:51:46 -0500 Subject: Fix watchpoint hit message for address-shifted processors (nw) --- src/emu/debug/debugcpu.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/emu/debug/debugcpu.cpp') diff --git a/src/emu/debug/debugcpu.cpp b/src/emu/debug/debugcpu.cpp index 38b21a8bd7f..17c26191186 100644 --- a/src/emu/debug/debugcpu.cpp +++ b/src/emu/debug/debugcpu.cpp @@ -2790,14 +2790,14 @@ void debugger_cpu::watchpoint_check(address_space& space, int type, offs_t addre if (type & WATCHPOINT_WRITE) { - buffer = string_format("Stopped at watchpoint %X writing %s to %08X (PC=%X)", wp->index(), sizes[size], space.byte_to_address(address), pc); + buffer = string_format("Stopped at watchpoint %X writing %s to %08X (PC=%X)", wp->index(), sizes[size], address, pc); if (value_to_write >> 32) buffer.append(string_format(" (data=%X%08X)", u32(value_to_write >> 32), u32(value_to_write))); else buffer.append(string_format(" (data=%X)", u32(value_to_write))); } else - buffer = string_format("Stopped at watchpoint %X reading %s from %08X (PC=%X)", wp->index(), sizes[size], space.byte_to_address(address), pc); + buffer = string_format("Stopped at watchpoint %X reading %s from %08X (PC=%X)", wp->index(), sizes[size], address, pc); m_machine.debugger().console().printf("%s\n", buffer.c_str()); space.device().debug()->compute_debug_flags(); } -- cgit v1.2.3-70-g09d2