summaryrefslogtreecommitdiffstatshomepage
path: root/src/emu/save.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/emu/save.cpp')
-rw-r--r--src/emu/save.cpp448
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;
+ }
}
}