diff options
Diffstat (limited to 'src/emu/save.cpp')
-rw-r--r-- | src/emu/save.cpp | 448 |
1 files changed, 274 insertions, 174 deletions
diff --git a/src/emu/save.cpp b/src/emu/save.cpp index a8a59c90817..35599c2e679 100644 --- a/src/emu/save.cpp +++ b/src/emu/save.cpp @@ -2,7 +2,7 @@ // copyright-holders:Aaron Giles /*************************************************************************** - save.c + save.cpp Save state management functions. @@ -24,7 +24,11 @@ #include "emu.h" #include "emuopts.h" -#include "coreutil.h" + +#include "main.h" + +#include "util/ioprocs.h" +#include "util/ioprocsfilter.h" //************************************************************************** @@ -63,7 +67,7 @@ enum save_manager::save_manager(running_machine &machine) : m_machine(machine) , m_reg_allowed(true) - , m_illegal_regs(0) + , m_supported(false) { m_rewind = std::make_unique<rewinder>(*this); } @@ -82,14 +86,14 @@ void save_manager::allow_registration(bool allowed) { // look for duplicates std::sort(m_entry_list.begin(), m_entry_list.end(), - [](std::unique_ptr<state_entry> const& a, std::unique_ptr<state_entry> const& b) { return a->m_name < b->m_name; }); + [] (std::unique_ptr<state_entry> const& a, std::unique_ptr<state_entry> const& b) { return a->m_name < b->m_name; }); int dupes_found = 0; - for (int i = 0; i < m_entry_list.size() - 1; i++) + for (int i = 1; i < m_entry_list.size(); i++) { - if (m_entry_list[i]->m_name == m_entry_list[i + 1]->m_name) + if (m_entry_list[i - 1]->m_name == m_entry_list[i]->m_name) { - osd_printf_error("Duplicate save state registration entry (%s)\n", m_entry_list[i]->m_name.c_str()); + osd_printf_error("Duplicate save state registration entry (%s)\n", m_entry_list[i]->m_name); dupes_found++; } } @@ -97,6 +101,16 @@ void save_manager::allow_registration(bool allowed) if (dupes_found) fatalerror("%d duplicate save state entries found.\n", dupes_found); + m_supported = true; + for (device_t &device : device_enumerator(machine().root_device())) + { + if (device.type().emulation_flags() & device_t::flags::SAVE_UNSUPPORTED) + { + m_supported = false; + break; + } + } + dump_registry(); // everything is registered by now, evaluate the savestate size @@ -110,7 +124,7 @@ void save_manager::allow_registration(bool allowed) // index //------------------------------------------------- -const char *save_manager::indexed_item(int index, void *&base, u32 &valsize, u32 &valcount) const +const char *save_manager::indexed_item(int index, void *&base, u32 &valsize, u32 &valcount, u32 &blockcount, u32 &stride) const { if (index >= m_entry_list.size() || index < 0) return nullptr; @@ -119,6 +133,8 @@ const char *save_manager::indexed_item(int index, void *&base, u32 &valsize, u32 base = entry->m_data; valsize = entry->m_typesize; valcount = entry->m_typecount; + blockcount = entry->m_blockcount; + stride = entry->m_stride; return entry->m_name.c_str(); } @@ -171,29 +187,28 @@ void save_manager::register_postload(save_prepost_delegate func) // memory //------------------------------------------------- -void save_manager::save_memory(device_t *device, const char *module, const char *tag, u32 index, const char *name, void *val, u32 valsize, u32 valcount) +void save_manager::save_memory(device_t *device, const char *module, const char *tag, u32 index, const char *name, void *val, u32 valsize, u32 valcount, u32 blockcount, u32 stride) { assert(valsize == 1 || valsize == 2 || valsize == 4 || valsize == 8); + assert(((blockcount <= 1) && (stride == 0)) || (stride >= valcount)); // check for invalid timing if (!m_reg_allowed) { machine().logerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name); - if (machine().system().flags & machine_flags::SUPPORTS_SAVE) - fatalerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name); - m_illegal_regs++; + fatalerror("Attempt to register save state entry after state registration is closed!\nModule %s tag %s name %s\n", module, tag, name); return; } // create the full name std::string totalname; - if (tag != nullptr) + if (tag) totalname = string_format("%s/%s/%X/%s", module, tag, index, name); else totalname = string_format("%s/%X/%s", module, index, name); // insert us into the list - m_entry_list.emplace_back(std::make_unique<state_entry>(val, totalname.c_str(), device, module, tag ? tag : "", index, valsize, valcount)); + m_entry_list.emplace_back(std::make_unique<state_entry>(val, std::move(totalname), device, module, tag ? tag : "", index, valsize, valcount, blockcount, stride)); } @@ -202,20 +217,20 @@ void save_manager::save_memory(device_t *device, const char *module, const char // state //------------------------------------------------- -save_error save_manager::check_file(running_machine &machine, emu_file &file, const char *gamename, void (CLIB_DECL *errormsg)(const char *fmt, ...)) +save_error save_manager::check_file(running_machine &machine, util::core_file &file, const char *gamename, void (CLIB_DECL *errormsg)(const char *fmt, ...)) { // if we want to validate the signature, compute it u32 sig; sig = machine.save().signature(); // seek to the beginning and read the header - file.compress(FCOMPRESS_NONE); file.seek(0, SEEK_SET); u8 header[HEADER_SIZE]; - if (file.read(header, sizeof(header)) != sizeof(header)) + auto const [err, actual] = read(file, header, sizeof(header)); + if (err || (actual != sizeof(header))) { if (errormsg != nullptr) - (*errormsg)("Could not read %s save file header",emulator_info::get_appname()); + (*errormsg)("Could not read %s save file header", emulator_info::get_appname()); return STATERR_READ_ERROR; } @@ -237,71 +252,173 @@ void save_manager::dispatch_postload() //------------------------------------------------- +// 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 +//------------------------------------------------- + +save_error save_manager::write_file(util::core_file &file) +{ + util::write_stream::ptr writer; + save_error err = do_write( + [] (size_t total_size) { return true; }, + [&writer] (const void *data, size_t size) + { + auto const [filerr, written] = write(*writer, data, size); + return !filerr; + }, + [&file, &writer] () + { + if (file.seek(0, SEEK_SET)) + return false; + util::core_file::ptr proxy; + std::error_condition filerr = util::core_file::open_proxy(file, proxy); + writer = std::move(proxy); + return !filerr && writer; + }, + [&file, &writer] () + { + writer = util::zlib_write(file, 6, 16384); + return bool(writer); + }); + return (STATERR_NONE != err) ? err : writer->finalize() ? STATERR_WRITE_ERROR : STATERR_NONE; +} + + +//------------------------------------------------- // read_file - read the data from a file //------------------------------------------------- -save_error save_manager::read_file(emu_file &file) +save_error save_manager::read_file(util::core_file &file) { - // if we have illegal registrations, return an error - if (m_illegal_regs > 0) - return STATERR_ILLEGAL_REGISTRATIONS; + util::read_stream::ptr reader; + return do_read( + [] (size_t total_size) { return true; }, + [&reader] (void *data, size_t size) + { + auto const [filerr, actual] = read(*reader, data, size); + return !filerr && (actual == size); + }, + [&file, &reader] () + { + if (file.seek(0, SEEK_SET)) + return false; + util::core_file::ptr proxy; + std::error_condition filerr = util::core_file::open_proxy(file, proxy); + reader = std::move(proxy); + return !filerr && reader; + }, + [&file, &reader] () + { + reader = util::zlib_read(file, 16384); + return bool(reader); + }); +} - // read the header and turn on compression for the rest of the file - file.compress(FCOMPRESS_NONE); - file.seek(0, SEEK_SET); - u8 header[HEADER_SIZE]; - if (file.read(header, sizeof(header)) != sizeof(header)) - return STATERR_READ_ERROR; - file.compress(FCOMPRESS_MEDIUM); - // verify the header and report an error if it doesn't match - u32 sig = signature(); - if (validate_header(header, machine().system().name, sig, nullptr, "Error: ") != STATERR_NONE) - return STATERR_INVALID_HEADER; +//------------------------------------------------- +// write_stream - write the current machine state +// to an output stream +//------------------------------------------------- - // 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); +save_error save_manager::write_stream(std::ostream &str) +{ + return do_write( + [] (size_t total_size) { return true; }, + [&str] (const void *data, size_t size) + { + return bool(str.write(reinterpret_cast<const char *>(data), size)); + }, + [] () { return true; }, + [] () { return true; }); +} - // read all the data, flipping if necessary - for (auto &entry : m_entry_list) - { - u32 totalsize = entry->m_typesize * entry->m_typecount; - if (file.read(entry->m_data, totalsize) != totalsize) - return STATERR_READ_ERROR; - // handle flipping - if (flip) - entry->flip_data(); - } +//------------------------------------------------- +// read_stream - restore the machine state from +// an input stream +//------------------------------------------------- - // call the post-load functions - dispatch_postload(); +save_error save_manager::read_stream(std::istream &str) +{ + return do_read( + [] (size_t total_size) { return true; }, + [&str] (void *data, size_t size) + { + return bool(str.read(reinterpret_cast<char *>(data), size)); + }, + [] () { return true; }, + [] () { return true; }); +} - return STATERR_NONE; + +//------------------------------------------------- +// write_buffer - write the current machine state +// to an allocated buffer +//------------------------------------------------- + +save_error save_manager::write_buffer(void *buf, size_t size) +{ + return do_write( + [size] (size_t total_size) { return size == total_size; }, + [ptr = reinterpret_cast<u8 *>(buf)] (const void *data, size_t size) mutable + { + memcpy(ptr, data, size); + ptr += size; + return true; + }, + [] () { return true; }, + [] () { return true; }); } //------------------------------------------------- -// dispatch_presave - invoke all registered -// presave callbacks for updates +// read_buffer - restore the machine state from a +// buffer //------------------------------------------------- -void save_manager::dispatch_presave() +save_error save_manager::read_buffer(const void *buf, size_t size) { - for (auto &func : m_presave_list) - func->m_func(); + const u8 *ptr = reinterpret_cast<const u8 *>(buf); + const u8 *const end = ptr + size; + return do_read( + [size] (size_t total_size) { return size == total_size; }, + [&ptr, &end] (void *data, size_t size) -> bool + { + if ((ptr + size) > end) + return false; + memcpy(data, ptr, size); + ptr += size; + return true; + }, + [] () { return true; }, + [] () { return true; }); } //------------------------------------------------- -// write_file - writes the data to a file +// do_write - serialisation logic //------------------------------------------------- -save_error save_manager::write_file(emu_file &file) +template <typename T, typename U, typename V, typename W> +inline save_error save_manager::do_write(T check_space, U write_block, V start_header, W start_data) { - // if we have illegal registrations, return an error - if (m_illegal_regs > 0) - return STATERR_ILLEGAL_REGISTRATIONS; + // check for sufficient space + size_t total_size = HEADER_SIZE; + for (const auto &entry : m_entry_list) + total_size += entry->m_typesize * entry->m_typecount * entry->m_blockcount; + if (!check_space(total_size)) + return STATERR_WRITE_ERROR; // generate the header u8 header[HEADER_SIZE]; @@ -313,11 +430,8 @@ save_error save_manager::write_file(emu_file &file) *(u32 *)&header[0x1c] = little_endianize_int32(sig); // write the header and turn on compression for the rest of the file - file.compress(FCOMPRESS_NONE); - file.seek(0, SEEK_SET); - if (file.write(header, sizeof(header)) != sizeof(header)) + if (!start_header() || !write_block(header, sizeof(header)) || !start_data()) return STATERR_WRITE_ERROR; - file.compress(FCOMPRESS_MEDIUM); // call the pre-save functions dispatch_presave(); @@ -325,10 +439,60 @@ save_error save_manager::write_file(emu_file &file) // then write all the data for (auto &entry : m_entry_list) { - u32 totalsize = entry->m_typesize * entry->m_typecount; - if (file.write(entry->m_data, totalsize) != totalsize) - return STATERR_WRITE_ERROR; + const u32 blocksize = entry->m_typesize * entry->m_typecount; + const u8 *data = reinterpret_cast<const u8 *>(entry->m_data); + for (u32 b = 0; entry->m_blockcount > b; ++b, data += entry->m_stride) + if (!write_block(data, blocksize)) + return STATERR_WRITE_ERROR; + } + return STATERR_NONE; +} + + +//------------------------------------------------- +// do_read - deserialisation logic +//------------------------------------------------- + +template <typename T, typename U, typename V, typename W> +inline save_error save_manager::do_read(T check_length, U read_block, V start_header, W start_data) +{ + // check for sufficient space + size_t total_size = HEADER_SIZE; + for (const auto &entry : m_entry_list) + total_size += entry->m_typesize * entry->m_typecount * entry->m_blockcount; + if (!check_length(total_size)) + return STATERR_READ_ERROR; + + // read the header and turn on compression for the rest of the file + u8 header[HEADER_SIZE]; + if (!start_header() || !read_block(header, sizeof(header)) || !start_data()) + return STATERR_READ_ERROR; + + // verify the header and report an error if it doesn't match + u32 sig = signature(); + if (validate_header(header, machine().system().name, sig, nullptr, "Error: ") != STATERR_NONE) + return STATERR_INVALID_HEADER; + + // determine whether or not to flip the data when done + const 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_entry_list) + { + const u32 blocksize = entry->m_typesize * entry->m_typecount; + u8 *data = reinterpret_cast<u8 *>(entry->m_data); + for (u32 b = 0; entry->m_blockcount > b; ++b, data += entry->m_stride) + if (!read_block(data, blocksize)) + return STATERR_READ_ERROR; + + // handle flipping + if (flip) + entry->flip_data(); } + + // call the post-load functions + dispatch_postload(); + return STATERR_NONE; } @@ -341,19 +505,21 @@ save_error save_manager::write_file(emu_file &file) u32 save_manager::signature() const { // iterate over entries - u32 crc = 0; + util::crc32_creator crc; for (auto &entry : m_entry_list) { // add the entry name to the CRC - crc = core_crc32(crc, (u8 *)entry->m_name.c_str(), entry->m_name.length()); + crc.append(entry->m_name.data(), entry->m_name.length()); // add the type and size to the CRC - u32 temp[2]; - temp[0] = little_endianize_int32(entry->m_typecount); - temp[1] = little_endianize_int32(entry->m_typesize); - crc = core_crc32(crc, (u8 *)&temp[0], sizeof(temp)); + u32 temp[4]; + temp[0] = little_endianize_int32(entry->m_typesize); + temp[1] = little_endianize_int32(entry->m_typecount); + temp[2] = little_endianize_int32(entry->m_blockcount); + temp[3] = 0; + crc.append(&temp[0], sizeof(temp)); } - return crc; + return crc.finish(); } @@ -365,7 +531,7 @@ u32 save_manager::signature() const void save_manager::dump_registry() const { for (auto &entry : m_entry_list) - LOG(("%s: %d x %d\n", entry->m_name.c_str(), entry->m_typesize, entry->m_typecount)); + LOG(("%s: %u x %u x %u (%u)\n", entry->m_name.c_str(), entry->m_typesize, entry->m_typecount, entry->m_blockcount, entry->m_stride)); } @@ -381,7 +547,7 @@ save_error save_manager::validate_header(const u8 *header, const char *gamename, if (memcmp(header, STATE_MAGIC_NUM, 8)) { if (errormsg != nullptr) - (*errormsg)("%sThis is not a %s save file", error_prefix,emulator_info::get_appname()); + (*errormsg)("%sThis is not a %s save file", error_prefix, emulator_info::get_appname()); return STATERR_INVALID_HEADER; } @@ -454,9 +620,7 @@ 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; - } + totalsize += entry->m_typesize * entry->m_typecount * entry->m_blockcount; return totalsize + HEADER_SIZE; } @@ -473,39 +637,10 @@ save_error ram_state::save() 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; - } + // get the save manager to write state + const save_error err = m_save.write_stream(m_data); + if (err != STATERR_NONE) + return err; // final confirmation m_valid = true; @@ -525,45 +660,8 @@ 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; + // get the save manager to load state + return m_save.read_stream(m_data); } @@ -811,11 +909,6 @@ void rewinder::report_error(save_error error, rewind_operation operation) switch (error) { // internal saveload failures - case STATERR_ILLEGAL_REGISTRATIONS: - 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().logerror("Rewind error: Unable to %s state due to an invalid header. " "Make sure the save state is correct for this machine.\n", opname); @@ -852,7 +945,7 @@ void rewinder::report_error(save_error error, rewind_operation operation) // success case STATERR_NONE: { - const u64 supported = m_save.machine().system().flags & MACHINE_SUPPORTS_SAVE; + const u64 supported = m_save.supported(); 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"; @@ -880,16 +973,20 @@ void rewinder::report_error(save_error error, rewind_operation operation) // 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) +save_manager::state_entry::state_entry( + void *data, + std::string &&name, device_t *device, std::string &&module, std::string &&tag, int index, + u8 size, u32 valcount, u32 blockcount, u32 stride) : m_data(data) - , m_name(name) + , m_name(std::move(name)) , m_device(device) - , m_module(module) - , m_tag(tag) + , m_module(std::move(module)) + , m_tag(std::move(tag)) , m_index(index) , m_typesize(size) - , m_typecount(count) - , m_offset(0) + , m_typecount(valcount) + , m_blockcount(blockcount) + , m_stride(stride) { } @@ -899,31 +996,34 @@ state_entry::state_entry(void *data, const char *name, device_t *device, const c // block of data //------------------------------------------------- -void state_entry::flip_data() +void save_manager::state_entry::flip_data() { - u16 *data16; - u32 *data32; - u64 *data64; - int count; - - switch (m_typesize) + u8 *data = reinterpret_cast<u8 *>(m_data); + for (u32 b = 0; m_blockcount > b; ++b, data += m_stride) { + u16 *data16; + u32 *data32; + u64 *data64; + + switch (m_typesize) + { case 2: - data16 = (u16 *)m_data; - for (count = 0; count < m_typecount; count++) + data16 = reinterpret_cast<u16 *>(data); + for (u32 count = 0; count < m_typecount; count++) data16[count] = swapendian_int16(data16[count]); break; case 4: - data32 = (u32 *)m_data; - for (count = 0; count < m_typecount; count++) + data32 = reinterpret_cast<u32 *>(data); + for (u32 count = 0; count < m_typecount; count++) data32[count] = swapendian_int32(data32[count]); break; case 8: - data64 = (u64 *)m_data; - for (count = 0; count < m_typecount; count++) + data64 = reinterpret_cast<u64 *>(data); + for (u32 count = 0; count < m_typecount; count++) data64[count] = swapendian_int64(data64[count]); break; + } } } |