diff options
author | 2017-12-13 21:31:27 -0700 | |
---|---|---|
committer | 2017-12-13 21:31:27 -0700 | |
commit | 9ece34eb218c5c1960468294c733fd00ac7fa696 (patch) | |
tree | 7160ed8d2cbe2127aaaef0a21f8736403b0f8a7c /src/emu/save.cpp | |
parent | 54155441e9ba9941e85d80c4834a66376a11e791 (diff) |
Revert "Revert "Merge branch 'master' of https://github.com/mamedev/mame""
This reverts commit 54155441e9ba9941e85d80c4834a66376a11e791.
Diffstat (limited to 'src/emu/save.cpp')
-rw-r--r-- | src/emu/save.cpp | 465 |
1 files changed, 447 insertions, 18 deletions
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() |