Better handling of null/missing items. More consistent error handling. Reduced compression to default to speed up saves. Optimized simple binary saves as well. Device interfaces now have automatic containers. Fixed duplicate entry detection. Added logic to parse an item we're skipping. Added detection of missing/duplicate items from input JSON. Fixed timing for save/load.

This commit is contained in:
Aaron Giles 2021-04-20 02:12:47 -07:00
parent 7e33cb29e2
commit 8d047a8f0d
12 changed files with 387 additions and 161 deletions

View file

@ -63,6 +63,11 @@ void debug_view_save::build_list_recursive(save_registered_item &item, uintptr_t
if (item.unwrap_and_update_base(objbase))
return build_list_recursive(item.subitems().front(), objbase, depth, count);
// only containers are allowed to have null bases past this point; treat
// everything else as non-existent
if (objbase == 0 && item.type() != save_registered_item::TYPE_CONTAINER)
return;
// switch off the type
switch (item.type())
{
@ -305,6 +310,11 @@ std::string debug_view_save::save_item::value(save_registered_item &item, int co
if (item.unwrap_and_update_base(objbase))
return value(item.subitems().front(), 0, objbase, collapsed);
// only containers are allowed to have null bases past this point; treat
// everything else as non-existent
if (objbase == 0 && item.type() != save_registered_item::TYPE_CONTAINER)
return "(null)";
char tempbuf[256];
tempbuf[0] = 0;
int pos = 0;

View file

@ -694,8 +694,12 @@ void device_t::register_save(save_registrar &save)
.reg(NAME(m_clock_scale));
// let the interfaces save their states
save_registrar intf_container(save, "interfaces");
for (device_interface &intf : interfaces())
intf.interface_register_save(save);
{
save_registrar container(intf_container, intf.interface_type());
intf.interface_register_save(container);
}
// then the device itself
int state_registrations = machine().save().binary_size();

View file

@ -501,8 +501,7 @@ void device_execute_interface::interface_clock_changed()
void device_execute_interface::interface_register_save(save_registrar &save)
{
// put execute items in their own container
save_registrar(save, "execute")
.reg(NAME(m_suspend))
save.reg(NAME(m_suspend))
.reg(NAME(m_nextsuspend))
.reg(NAME(m_eatcycles))
.reg(NAME(m_nexteatcycles))

View file

@ -25,8 +25,7 @@ void device_network_interface::interface_pre_start()
void device_network_interface::interface_register_save(save_registrar &save)
{
save_registrar(save, "network")
.reg(NAME(m_loopback_control));
save.reg(NAME(m_loopback_control));
}
int device_network_interface::send(u8 *buf, int len)

View file

@ -102,14 +102,12 @@ void device_palette_interface::interface_post_start()
void device_palette_interface::interface_register_save(save_registrar &save)
{
save_registrar container(save, "palette");
// save indirection tables if we have them
container.reg(NAME(m_save_pen))
save.reg(NAME(m_save_pen))
.reg(NAME(m_save_contrast));
if (m_indirect_colors.size() > 0)
container.reg(NAME(m_indirect_colors))
save.reg(NAME(m_indirect_colors))
.reg(NAME(m_indirect_pens));
}

View file

@ -136,8 +136,7 @@ void device_rom_interface<AddrWidth, DataWidth, AddrShift, Endian>::interface_re
{
device_memory_interface::interface_register_save(save);
save_registrar(save, "rom")
.reg(NAME(m_cur_bank))
save.reg(NAME(m_cur_bank))
.reg(NAME(m_bank_count));
}

View file

@ -77,8 +77,7 @@ void device_serial_interface::interface_pre_start()
void device_serial_interface::interface_register_save(save_registrar &save)
{
save_registrar(save, "serial")
.reg(NAME(m_df_start_bit_count))
save.reg(NAME(m_df_start_bit_count))
.reg(NAME(m_df_word_length))
.reg(NAME(m_df_parity))
.reg(NAME(m_df_stop_bit_count))

View file

@ -192,8 +192,7 @@ protected:
{
device_serial_interface::interface_register_save(save);
save_registrar(save, "buffered_serial")
.reg(NAME(m_fifo))
save.reg(NAME(m_fifo))
.reg(NAME(m_head))
.reg(NAME(m_tail))
.reg(NAME(m_empty));

View file

@ -114,13 +114,11 @@ void device_vtlb_interface::interface_pre_start()
void device_vtlb_interface::interface_register_save(save_registrar &save)
{
save_registrar container(save, "vtlb");
container.reg(NAME(m_live))
save.reg(NAME(m_live))
.reg(NAME(m_table))
.reg(NAME(m_refcnt));
if (m_fixed > 0)
container.reg(NAME(m_fixedpages));
save.reg(NAME(m_fixedpages));
}

View file

@ -263,7 +263,7 @@ void running_machine::start()
save().register_presave(save_prepost_delegate(FUNC(running_machine::presave_all_devices), this));
start_all_devices();
for (device_t &device : device_enumerator(root_device()))
m_save.root_registrar().reg(device, device.tag(), device.type().size());
m_save.root_registrar().reg(device, string_format("device%s", device.tag()).c_str(), device.type().size());
save().register_postload(save_prepost_delegate(FUNC(running_machine::postload_all_devices), this));
// save outputs created before start time
@ -938,12 +938,12 @@ void running_machine::handle_saveload()
auto const filerr = file.open(m_saveload_pending_file);
if (filerr == osd_file::error::NONE)
{
osd_ticks_t start = osd_ticks();
const char *const opnamed = (m_saveload_schedule == saveload_schedule::LOAD) ? "loaded" : "saved";
osd_ticks_t end = osd_ticks();
// read/write the save state
osd_ticks_t start = osd_ticks();
save_error saverr = (m_saveload_schedule == saveload_schedule::LOAD) ? m_save.load_file(file) : m_save.save_file(file);
osd_ticks_t end = osd_ticks();
// handle the result
switch (saverr)

View file

@ -39,6 +39,8 @@
#define LOG(x) do { if (VERBOSE) machine().logerror x; } while (0)
#define DUMP_INITIAL_JSON_SAVE (0)
//**************************************************************************
@ -109,7 +111,7 @@ bool zlib_write_streamer::begin()
// initialize the zlib engine; the negative window size means
// no headers, which is what a .ZIP file wants
return (deflateInit2(&m_stream, Z_BEST_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) == Z_OK);
return (deflateInit2(&m_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) == Z_OK);
}
@ -215,10 +217,13 @@ public:
// simple getters
char const *json_string() { m_json[m_json_offset] = 0; return &m_json[0]; }
int json_length() const { return m_json_offset; }
u32 json_length() const { return m_json_offset; }
// setters
void json_set_length(u32 length) { m_json_offset = length; }
// append a character to the JSON stream
save_zip_state &json_append(char ch) { m_json[m_json_offset++] = ch; return *this; }
save_zip_state &json_append(char ch) { m_json[m_json_offset++] = ch; return *this; }
// append an end-of-line sequence to the JSON stream
save_zip_state &json_append_eol() { return json_append(13).json_append(10); }
@ -915,6 +920,7 @@ public:
bool json_parse_int_string(s64 &result);
bool json_parse_uint_string(u64 &result);
bool json_parse_string(std::string &result);
void json_parse_remaining_value(save_registered_item &item);
// initialize, checking the input file basic validity
save_error init();
@ -927,18 +933,24 @@ public:
// report a warning
template<typename... Params>
void report_warning(char const *format, Params &&... args)
void report_warning(save_registered_item &item, char const *format, Params &&... args)
{
m_warnings.append(string_format(format, std::forward<Params>(args)...));
m_warnings += "\n";
m_warnings.append(item.full_name()).append(": ").append(string_format(format, std::forward<Params>(args)...)).append("\n");
}
// report a warning and consume the remaining value
template<typename... Params>
void report_warning_and_consume(save_registered_item &item, char const *format, Params &&... args)
{
m_warnings.append(item.full_name()).append(": ").append(string_format(format, std::forward<Params>(args)...)).append("\n");
json_parse_remaining_value(item);
}
// report an error; this implicitly throws to exit
template<typename... Params>
void report_error(save_error type, char const *format, Params &&... args)
void report_error(save_registered_item &item, save_error type, char const *format, Params &&... args)
{
m_errors.append(string_format(format, std::forward<Params>(args)...));
m_errors += "\n";
m_errors.append(item.full_name()).append(": ").append(string_format(format, std::forward<Params>(args)...)).append("\n");
throw load_error(type);
}
@ -1105,6 +1117,67 @@ bool load_zip_state::json_parse_string(std::string &result)
}
//-------------------------------------------------
// json_parse_remaining_value - parse and throw
// away any remaining value data
//-------------------------------------------------
void load_zip_state::json_parse_remaining_value(save_registered_item &item)
{
// value ends at a comma or end of struct/array
while (json_peek() != 0 && json_peek() != ',' && json_peek() != ']' && json_peek() != '}')
{
// skip any leading whitespace
if (json_matches('['))
{
// if a start of array marker, parse until done
while (1)
{
json_parse_remaining_value(item);
if (json_matches(']'))
break;
if (!json_matches(','))
report_error(item, STATERR_MALFORMED_JSON, "Expected ','");
}
}
else if (json_matches('{'))
{
// if a start of struct market, parse until done
while (1)
{
std::string name;
if (!json_parse_string(name) || name == "")
report_error(item, STATERR_MALFORMED_JSON, "Expected name within struct");
if (!json_matches(':'))
report_error(item, STATERR_MALFORMED_JSON, "Expected ':'");
json_parse_remaining_value(item);
if (json_matches('}'))
break;
if (!json_matches(','))
report_error(item, STATERR_MALFORMED_JSON, "Expected ','");
}
}
else if (json_peek() == '"')
{
// if a string, consume it
std::string dummy;
json_parse_string(dummy);
}
else if (isdigit(json_peek()) || json_peek() == '-' || json_peek() == '+')
{
// if a number, consume it
double dummy;
json_parse_number(dummy);
}
else
{
// otherwise, just advance until we see something interesting
json_set_position(json_position() + 1);
}
}
}
//-------------------------------------------------
// init - initialize by parsing the ZIP structure
// and loading the JSON data
@ -1290,11 +1363,6 @@ save_registered_item::save_registered_item(save_registered_item &parent, uintptr
m_native_size(native_size),
m_name(name)
{
// cleanup names a bit
if (m_name[0] == '*')
m_name.erase(0, 1);
if (m_name[0] == 'm' && m_name[1] == '_')
m_name.erase(0, 2);
}
@ -1311,17 +1379,21 @@ std::string save_registered_item::full_name() const
// get the parent's full name
std::string result = m_parent->full_name();
// if we're a unique or vector, just pass it through without adding more
if (type() == TYPE_UNIQUE || type() == TYPE_VECTOR)
return result;
// if we're an array item, append an array indicator
if (is_array())
return result.append("[]");
// maybe add a separator
char end = result[result.length() - 1];
if (end != ':' && end != '.' && result.length() != 0)
result += ".";
result.append(".");
// then append our bit
if (m_name.length() == 0 && m_parent->is_array())
result += "[]";
else
result += m_name;
return result;
return result.append(m_name);
}
@ -1331,11 +1403,17 @@ std::string save_registered_item::full_name() const
save_registered_item &save_registered_item::append(uintptr_t ptr_offset, save_type type, u32 native_size, char const *name, u32 count)
{
// cleanup names a bit
if (name[0] == '*')
name++;
if (name[0] == 'm' && name[1] == '_')
name += 2;
// make sure there are no duplicates
if (find(name) != nullptr)
throw emu_fatalerror("Duplicate save state registration '%s'\n", name);
throw emu_fatalerror("%s: Duplicate save state registration '%s'\n", full_name().c_str(), name);
//printf("%s '%s': adding %s '%s' @ %llX, size %d\n", type_string(m_type, m_native_size).c_str(), m_name.c_str(), type_string(type, native_size).c_str(), name, ptr_offset, native_size);
printf("%s: adding %s '%s' @ %I64X, size %d\n", full_name().c_str(), type_string().c_str(), name, ptr_offset, native_size);
// add the item to the back of the list
m_items.emplace_back(*this, ptr_offset, type, native_size, name, count);
@ -1347,19 +1425,29 @@ save_registered_item &save_registered_item::append(uintptr_t ptr_offset, save_ty
// find - find a subitem by name
//-------------------------------------------------
save_registered_item *save_registered_item::find(char const *name)
save_registered_item *save_registered_item::find(char const *name, u32 &index)
{
// blank names can't be found this way
if (name[0] == 0)
return nullptr;
// make sure there are no duplicates
index = 0;
for (auto &item : m_items)
{
if (strcmp(item.name(), name) == 0)
return &item;
index++;
}
return nullptr;
}
save_registered_item *save_registered_item::find(char const *name)
{
u32 dummy;
return find(name, dummy);
}
//-------------------------------------------------
// is_replicatable - can this item be replicated
@ -1407,25 +1495,26 @@ bool save_registered_item::is_replicatable(bool parent_is_array) const
bool save_registered_item::sort_and_prune()
{
// only applies to arrays, structs, and containers; don't prune anything else
if (!is_array() && !is_struct_or_container())
return false;
// first prune any empty items
for (auto it = m_items.begin(); it != m_items.end(); )
// structs and containers prune empty items and sort them
if (is_struct_or_container())
{
if (it->sort_and_prune())
it = m_items.erase(it);
else
++it;
// first prune any empty items
for (auto it = m_items.begin(); it != m_items.end(); )
{
if (it->sort_and_prune())
it = m_items.erase(it);
else
++it;
}
// then sort the rest if we have more than 1
if (m_items.size() > 1)
m_items.sort([] (auto const &x, auto const &y) { return (std::strcmp(x.name(), y.name()) < 0); });
// prune if nothing is left
return (m_items.size() == 0);
}
// then sort the rest if we have more than 1
if (is_struct_or_container() && m_items.size() > 1)
m_items.sort([] (auto const &x, auto const &y) { return (std::strcmp(x.name(), y.name()) < 0); });
// return true if we have nothing
return (m_items.size() == 0);
return false;
}
@ -1475,6 +1564,11 @@ u64 save_registered_item::save_binary(u8 *ptr, u64 length, uintptr_t objbase) co
if (unwrap_and_update_base(objbase))
return m_items.front().save_binary(ptr, length, objbase);
// only containers are allowed to have null bases past this point; treat
// everything else as non-existent
if (objbase == 0 && type() != TYPE_CONTAINER)
return 0;
// switch off the type
u64 offset = 0;
switch (type())
@ -1496,8 +1590,8 @@ u64 save_registered_item::save_binary(u8 *ptr, u64 length, uintptr_t objbase) co
break;
// structs and containers iterate over owned items
case TYPE_CONTAINER:
case TYPE_STRUCT:
case TYPE_CONTAINER:
for (auto &item : m_items)
offset += item.save_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase);
break;
@ -1507,13 +1601,27 @@ u64 save_registered_item::save_binary(u8 *ptr, u64 length, uintptr_t objbase) co
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
// nothing to do if count is 0
if (count() == 0)
break;
// special case arrays of ints and floats
auto item = m_items.begin();
auto last = std::prev(m_items.end());
for (u32 rep = 0; rep < count(); rep++)
if (item == last && item->is_int_or_float())
{
offset += item->save_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
if (item != last)
++item;
u32 size = std::min<u32>(count() * item->native_size(), (offset < length) ? length - offset : 0);
memcpy(&ptr[offset], reinterpret_cast<void const *>(objbase), size);
offset += count() * item->native_size();
}
else
{
for (u32 rep = 0; rep < count(); rep++)
{
offset += item->save_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
if (item != last)
++item;
}
}
break;
}
@ -1533,6 +1641,11 @@ u64 save_registered_item::restore_binary(u8 const *ptr, u64 length, uintptr_t ob
if (unwrap_and_update_base(objbase))
return m_items.front().restore_binary(ptr, length, objbase);
// only containers are allowed to have null bases past this point; treat
// everything else as non-existent
if (objbase == 0 && type() != TYPE_CONTAINER)
return 0;
// switch off the type
u64 offset = 0;
switch (type())
@ -1554,8 +1667,8 @@ u64 save_registered_item::restore_binary(u8 const *ptr, u64 length, uintptr_t ob
break;
// structs and containers iterate over owned items
case TYPE_CONTAINER:
case TYPE_STRUCT:
case TYPE_CONTAINER:
for (auto &item : m_items)
offset += item.restore_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase);
break;
@ -1565,13 +1678,27 @@ u64 save_registered_item::restore_binary(u8 const *ptr, u64 length, uintptr_t ob
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
// nothing to do if count is 0
if (count() == 0)
break;
// special case arrays of ints and floats
auto item = m_items.begin();
auto last = std::prev(m_items.end());
for (u32 rep = 0; rep < count(); rep++)
if (item == last && item->is_int_or_float())
{
offset += item->restore_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
if (item != last)
++item;
u32 size = std::min<u32>(count() * item->native_size(), (offset < length) ? length - offset : 0);
memcpy(reinterpret_cast<void *>(objbase), &ptr[offset], size);
offset += count() * item->native_size();
}
else
{
for (u32 rep = 0; rep < count(); rep++)
{
offset += item->restore_binary(&ptr[offset], (offset < length) ? length - offset : 0, objbase + rep * m_native_size);
if (item != last)
++item;
}
}
break;
}
@ -1593,6 +1720,14 @@ void save_registered_item::save_json(save_zip_state &zipstate, int indent, bool
// output the name if present
zipstate.json_append_name(m_name.c_str());
// only containers are allowed to have null bases past this point; treat
// everything else as non-existent
if (objbase == 0 && type() != TYPE_CONTAINER)
{
zipstate.json_append('n').json_append('u').json_append('l').json_append('l');
return;
}
// switch off the type
switch (type())
{
@ -1652,6 +1787,13 @@ void save_registered_item::save_json(save_zip_state &zipstate, int indent, bool
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
// zero count is just an empty array
if (count() == 0)
{
zipstate.json_append('[').json_append(']');
break;
}
// look for large arrays of ints/floats
u32 total, unitsize;
if (is_endpoint_array(total, unitsize) && total * unitsize >= save_zip_state::JSON_EXTERNAL_BINARY_THRESHOLD)
@ -1733,145 +1875,235 @@ void save_registered_item::restore_json(load_zip_state &input, json_restore_mode
if (unwrap_and_update_base(objbase))
return m_items.front().restore_json(input, mode, objbase);
// "null" is a placeholder for a missing struct or array item; just parse and return
// which will leave the item alone and allow us to move onto the next
if (input.json_matches("null"))
return;
// only containers are allowed to have null bases past this point; treat
// everything else as non-existent
if (objbase == 0 && type() != TYPE_CONTAINER)
return;
// switch off the type
switch (type())
{
// boolean types
case TYPE_BOOL:
{
bool valid = false;
bool value = false;
// must match 'true' or 'false'
if (input.json_matches("true"))
value = true;
value = true, valid = true;
else if (input.json_matches("false"))
value = false;
else
input.report_error(STATERR_MALFORMED_JSON, "%s: Unknown boolean value", full_name().c_str());
if (mode == RESTORE_DATA)
value = false, valid = true;
// warn if not valid, otherwise process
if (!valid)
input.report_warning_and_consume(*this, "Expected boolean value, ignoring");
else if (mode == RESTORE_DATA)
write_bool(objbase, value);
else if (mode == COMPARE_DATA && read_bool(objbase) != value)
input.report_warning("%s: Compare failed: JSON says %d, data says %d", full_name().c_str(), value, read_bool(objbase));
input.report_warning(*this, "Compare failed: JSON says %d, data says %d", value, read_bool(objbase));
break;
}
// signed integral types
case TYPE_INT:
{
bool dvalid = false, ivalid = false;
std::string svalue;
double dvalue;
s64 ivalue;
s64 ivalue = 0;
// attempt to parse as a double, and then as a string with an integer
if (input.json_parse_number(dvalue))
dvalid = true;
else if (input.json_parse_string(svalue))
{
if (mode == RESTORE_DATA && !write_int_signed(objbase, m_native_size, dvalue))
input.report_warning("%s: Value of out range: %1.17g", full_name().c_str(), dvalue);
else if (mode == COMPARE_DATA && read_int_signed(objbase, m_native_size) != s64(dvalue))
input.report_warning("%s: Compare failed: JSON says %I64d, data says %1.17g", full_name().c_str(), dvalue, read_int_signed(objbase, m_native_size));
char *end;
ivalue = strtoll(&svalue[0], &end, 10);
ivalid = (end != &svalue[0]);
}
else if (input.json_parse_int_string(ivalue))
// warn if not valid, otherwise process
if (!dvalid && !ivalid)
input.report_warning_and_consume(*this, "Expected integer value, ignoring");
else if (mode == RESTORE_DATA)
{
if (mode == RESTORE_DATA && !write_int_signed(objbase, m_native_size, ivalue))
input.report_warning("%s: Value of out range: %I64d", full_name().c_str(), ivalue);
else if (mode == COMPARE_DATA && read_int_signed(objbase, m_native_size) != ivalue)
input.report_warning("%s: Compare failed: JSON says %I64d, data says %I64d", full_name().c_str(), ivalue, read_int_signed(objbase, m_native_size));
if (dvalid && !write_int_signed(objbase, m_native_size, dvalue))
input.report_warning(*this, "Value of out range: %g", dvalue);
else if (ivalid && !write_int_signed(objbase, m_native_size, ivalue))
input.report_warning(*this, "Value of out range: %I64d", ivalue);
}
else if (mode == COMPARE_DATA)
{
if (dvalid && read_int_signed(objbase, m_native_size) != s64(dvalue))
input.report_warning(*this, "Compare failed: JSON says %g, data says %I64d", dvalue, read_int_signed(objbase, m_native_size));
else if (ivalid && read_int_signed(objbase, m_native_size) != ivalue)
input.report_warning(*this, "Compare failed: JSON says %I64d, data says %I64d", ivalue, read_int_signed(objbase, m_native_size));
}
else
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected integer value", full_name().c_str());
break;
}
// unsigned integral types
case TYPE_UINT:
{
bool dvalid = false, ivalid = false;
std::string svalue;
double dvalue;
u64 ivalue;
u64 ivalue = 0;
// attempt to parse as a double, and then as a string with an integer
if (input.json_parse_number(dvalue))
dvalid = true;
else if (input.json_parse_string(svalue))
{
if (mode == RESTORE_DATA && !write_int_unsigned(objbase, m_native_size, dvalue))
input.report_warning("%s: Value of out range: %1.17g", full_name().c_str(), dvalue);
else if (mode == COMPARE_DATA && read_int_unsigned(objbase, m_native_size) != u64(dvalue))
input.report_warning("%s: Compare failed: JSON says %I64d, data says %1.17g", full_name().c_str(), dvalue, read_int_unsigned(objbase, m_native_size));
char *end;
ivalue = strtoull(&svalue[0], &end, 10);
ivalid = (end != &svalue[0]);
}
else if (input.json_parse_uint_string(ivalue))
// warn if not valid, otherwise process
if (!dvalid && !ivalid)
input.report_warning_and_consume(*this, "Expected integer value, ignoring");
else if (mode == RESTORE_DATA)
{
if (mode == RESTORE_DATA && !write_int_unsigned(objbase, m_native_size, ivalue))
input.report_warning("%s: Value of out range: %I64d", full_name().c_str(), ivalue);
else if (mode == COMPARE_DATA && read_int_unsigned(objbase, m_native_size) != ivalue)
input.report_warning("%s: Compare failed: JSON says %I64d, data says %I64d", full_name().c_str(), ivalue, read_int_unsigned(objbase, m_native_size));
if (dvalid && !write_int_unsigned(objbase, m_native_size, dvalue))
input.report_warning(*this, "Value of out range: %1.17g", dvalue);
else if (ivalid && !write_int_unsigned(objbase, m_native_size, ivalue))
input.report_warning(*this, "Value of out range: %I64u", ivalue);
}
else if (mode == COMPARE_DATA)
{
if (dvalid && read_int_unsigned(objbase, m_native_size) != u64(dvalue))
input.report_warning(*this, "Compare failed: JSON says %1.17g, data says %I64u", dvalue, read_int_unsigned(objbase, m_native_size));
else if (ivalid && read_int_unsigned(objbase, m_native_size) != ivalue)
input.report_warning(*this, "Compare failed: JSON says %I64u, data says %I64u", ivalue, read_int_unsigned(objbase, m_native_size));
}
else
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected integer value", full_name().c_str());
break;
}
// float types
case TYPE_FLOAT:
{
bool valid = false;
double value;
if (!input.json_parse_number(value))
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected number", full_name().c_str());
if (mode == RESTORE_DATA && !write_float(objbase, m_native_size, value))
input.report_warning("%s: Value of out range: %1.17g", full_name().c_str(), value);
// attempt to parse as a double
if (input.json_parse_number(value))
valid = true;
// warn if not valid, otherwise process
if (!valid)
input.report_warning_and_consume(*this, "Expected number, ignoring");
else if (mode == RESTORE_DATA && !write_float(objbase, m_native_size, value))
input.report_warning(*this, "Value of out range: %1.17g", value);
else if (mode == COMPARE_DATA && read_float(objbase, m_native_size) != value)
input.report_warning("%s: Compare failed: JSON says %1.17g, data says %1.17g", full_name().c_str(), value, read_float(objbase, m_native_size));
input.report_warning(*this, "Compare failed: JSON says %1.17g, data says %1.17g", value, read_float(objbase, m_native_size));
break;
}
// structs and containers iterate over owned items
case TYPE_CONTAINER:
case TYPE_STRUCT:
// must start with an open brace
if (!input.json_matches('{'))
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected '{'", full_name().c_str());
if (!input.json_matches('}'))
while (1)
input.report_warning_and_consume(*this, "Expected structure, ignoring");
else
{
// track which items we've found
std::vector<bool> found(m_items.size(), false);
int numfound = 0;
if (!input.json_matches('}'))
while (1)
{
// parse the name
std::string name;
if (!input.json_parse_string(name) || name == "")
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected name within struct");
// followed by a colon
if (!input.json_matches(':'))
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ':'");
// look up the name; warn if not found or if we already got that one;
// otherwise process and mark found
u32 index;
save_registered_item *target = find(name.c_str(), index);
if (target == nullptr)
input.report_warning_and_consume(*this, "Found extraneous item '%s'", name.c_str());
else if (found[index])
input.report_warning_and_consume(*this, "Found duplicate item '%s'", name.c_str());
else
{
found[index] = true;
numfound++;
target->restore_json(input, mode, objbase);
}
// end with close brace, or a comma to indicate more
if (input.json_matches('}'))
break;
if (!input.json_matches(','))
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ','");
}
// report any missing items
if (numfound != m_items.size())
{
std::string name;
if (!input.json_parse_string(name) || name == "")
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected name within struct", full_name().c_str());
if (!input.json_matches(':'))
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ':'", full_name().c_str());
save_registered_item *target = find(name.c_str());
if (target == nullptr)
input.report_warning("%s: Found extraneous item '%s'", full_name().c_str(), name.c_str());
target->restore_json(input, (target == nullptr) ? PARSE_ONLY : mode, objbase);
if (input.json_matches('}'))
break;
if (!input.json_matches(','))
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ','", full_name().c_str());
int index = 0;
for (auto &item : m_items)
if (!found[index++])
input.report_warning(*this, "Missing item '%s'", item.name());
}
}
break;
// arrays are multiples of a single item
case TYPE_STATIC_ARRAY:
case TYPE_VECTOR_ARRAY:
case TYPE_RAW_ARRAY:
{
auto item = m_items.begin();
auto last = std::prev(m_items.end());
// must start with an open bracket
if (!input.json_matches('['))
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Expected '['", full_name().c_str());
if (parse_external_data(input, mode, objbase))
input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "Expected '['");
// handle empty arrays or external data
if (count() == 0 || parse_external_data(input, mode, objbase))
{
if (!input.json_matches(']'))
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ']'", full_name().c_str());
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ']'");
}
else
{
auto item = m_items.begin();
auto last = std::prev(m_items.end());
u32 rep = 0;
if (!input.json_matches(']'))
while (1)
{
// parse the next item
item->restore_json(input, (rep >= count()) ? PARSE_ONLY : mode, objbase + rep * m_native_size);
rep++;
if (item != last)
++item;
// end with close bracket, or a comma to indicate more
if (input.json_matches(']'))
break;
if (!input.json_matches(','))
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ','", full_name().c_str());
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ','");
}
// report missing or extraneous items
if (rep != count())
input.report_warning("%s: Found %s array items than expected", full_name().c_str(), (rep < count()) ? "fewer" : "more");
input.report_warning(*this, "Found %s array items than expected", (rep < count()) ? "fewer" : "more");
}
break;
}
}
}
@ -1899,9 +2131,9 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
{
std::string name;
if (!input.json_parse_string(name) || name == "")
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected name within struct", full_name().c_str());
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected name within struct");
if (!input.json_matches(':'))
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ':'", full_name().c_str());
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ':'");
valid = false;
if (name == "external_file" && parsed_filename == "" && input.json_parse_string(parsed_filename) && parsed_filename != "")
valid = true;
@ -1921,7 +2153,7 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
if (input.json_matches('}'))
break;
if (!input.json_matches(','))
input.report_error(STATERR_MALFORMED_JSON, "%s: Expected ','", full_name().c_str());
input.report_error(*this, STATERR_MALFORMED_JSON, "Expected ','");
}
if (!valid)
{
@ -1933,11 +2165,11 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
u64 offset;
u32 size;
if (!input.find_file(parsed_filename.c_str(), offset, size))
input.report_error(STATERR_MISSING_FILE, "%s: Unable to find file '%s' in archive", full_name().c_str(), parsed_filename.c_str());
input.report_error(*this, STATERR_MISSING_FILE, "Unable to find file '%s' in archive", parsed_filename.c_str());
if (parsed_unit != 1 && parsed_unit != 2 && parsed_unit != 4 && parsed_unit != 8)
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Invalid unit size for external file, expected 1, 2, 4, or 8", full_name().c_str());
input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "Invalid unit size for external file, expected 1, 2, 4, or 8");
if (parsed_count == 0)
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: Invalid count for external file", full_name().c_str());
input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "Invalid count for external file");
// look for the innermost registered item
save_registered_item *inner = &m_items.front();
@ -1948,9 +2180,9 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
inner = &inner->subitems().front();
}
if (!inner->is_int_or_float())
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: External file specified, but not valid for this type", full_name().c_str());
input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "External file specified, but not valid for this type");
if (parsed_unit != inner->native_size())
input.report_error(STATERR_INCOMPATIBLE_DATA, "%s: External file has mismatched unit size", full_name().c_str());
input.report_error(*this, STATERR_INCOMPATIBLE_DATA, "External file has mismatched unit size");
// if parseonly, we're done
if (parseonly)
@ -1960,7 +2192,7 @@ bool save_registered_item::parse_external_data(load_zip_state &input, bool parse
bool flip = (parsed_little_endian != native_little_endian);
zlib_read_streamer reader(input.file());
if (!reader.begin(offset) || !input.read_data_recursive(reader, *this, flip, size, objbase) || !reader.end())
input.report_error(STATERR_READ_ERROR, "%s: Error reading file '%s' in archive", full_name().c_str(), parsed_filename.c_str());
input.report_error(*this, STATERR_READ_ERROR, "Error reading file '%s' in archive", parsed_filename.c_str());
return true;
}
@ -2258,6 +2490,7 @@ void save_manager::allow_registration(bool allowed)
m_root_item.sort_and_prune();
// dump out a sample JSON
if (DUMP_INITIAL_JSON_SAVE)
{
save_zip_state state;
m_root_item.save_json(state);

View file

@ -144,6 +144,7 @@ public:
save_registered_item &append(uintptr_t ptr_offset, save_type type, u32 native_size, char const *name, u32 count = 0);
// find an item by name
save_registered_item *find(char const *name, u32 &index);
save_registered_item *find(char const *name);
// is this item replicatable (i.e., can we replicate it for all elements in an array?)
@ -285,11 +286,9 @@ public:
template<typename T>
std::enable_if_t<!std::is_array<T>::value, save_registrar> &reg(std::unique_ptr<T> &data, char const *name)
{
if (data.get() == nullptr)
throw emu_fatalerror("Passed null pointer to save state registration.");
save_registrar container(*this, &data, save_registered_item::TYPE_UNIQUE, sizeof(data), name, 0, data.get(), sizeof(T));
container.reg(*data.get(), name);
if (data.get() != nullptr)
container.reg(*data.get(), name);
return *this;
}
@ -319,10 +318,6 @@ public:
template<typename T>
save_registrar &reg(std::vector<T> &data, char const *name)
{
// skip if no items
if (data.size() == 0)
return *this;
// create an outer container for the vector, then a regular array container within
save_registrar container(*this, &data, save_registered_item::TYPE_VECTOR, sizeof(data), name, 0, &data[0], sizeof(T));
container.register_array(&data[0], save_registered_item::TYPE_VECTOR_ARRAY, name, data.size());
@ -333,10 +328,6 @@ public:
template<typename T>
save_registrar &reg(std::unique_ptr<T[]> &data, char const *name, std::size_t count)
{
// skip if no items
if (count == 0)
return *this;
// create an outer container for the unique_ptr, then a regular array container within
save_registrar container(*this, &data, save_registered_item::TYPE_UNIQUE, sizeof(data), name, 0, data.get(), sizeof(T));
container.register_array(&data[0], save_registered_item::TYPE_RAW_ARRAY, name, count);
@ -384,17 +375,14 @@ private:
template<typename T>
save_registrar &register_array(T *data, save_registered_item::save_type type, char const *name, std::size_t count)
{
// skip 0-length items
if (count == 0)
return *this;
// error on null pointer
if (data == nullptr)
throw emu_fatalerror("Passed null pointer to save state registration.");
// nullptr only allowed if the count is 0
if (data == nullptr && count != 0)
throw emu_fatalerror("Save: registered pointer with a null pointer (%s.%s)", m_item.full_name().c_str(), name);
// create a container and register the first item
save_registrar container(*this, data, type, uintptr_t(&data[1]) - uintptr_t(&data[0]), name, count, &data[0], sizeof(T));
container.reg(data[0], "");
if (count != 0)
container.reg(data[0], "");
// if the first item was non-replicatable, register remaining items independently
if (!container.m_item.subitems().front().is_replicatable(true))