diff --git a/libopera/opera_3do.c b/libopera/opera_3do.c index 8a50d55..bb139a5 100644 --- a/libopera/opera_3do.c +++ b/libopera/opera_3do.c @@ -324,6 +324,9 @@ opera_3do_runtime_state_load(void const *data_, if((field_state != 0) && (field_state != 1)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + field = field_state; g_FRAME_CYCLE_REMAINDER = remainder_state; @@ -375,6 +378,9 @@ opera_3do_prng_state_load(void const *data_, !opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + prng16_state_set(prng16_state); prng32_state_set(prng32_state); @@ -637,7 +643,8 @@ opera_3do_state_load_chunked(const void *data_, &data, end)) return 0; - opera_arm_sync_memory_map(); + if(!opera_state_validating()) + opera_arm_sync_memory_map(); if(!opera_3do_state_load_step(opera_sport_state_load, 0, &data, @@ -664,6 +671,9 @@ opera_3do_state_load_chunked(const void *data_, if(data != end) return 0; + if(opera_state_validating()) + return (uint32_t)(data - start); + opera_pbus_reset(); opera_xbus_set_legacy_no_device_abort(0); @@ -692,6 +702,24 @@ opera_3do_state_load_v3(const void *data_, true); } +bool +opera_3do_state_validate(void const *data_, + size_t size_) +{ + uint32_t rv; + + /* Only the current format: the older loaders reset parts of the + * emulator before they read, so they cannot be dry-run. */ + if(opera_state_get_version(data_,size_) != OPERA_STATE_VERSION_V3) + return false; + + opera_state_set_validating(true); + rv = opera_3do_state_load_v3(data_,size_); + opera_state_set_validating(false); + + return ((rv != 0) && (rv == size_)); +} + uint32_t opera_3do_state_load(void const *data_, size_t size_) diff --git a/libopera/opera_3do.h b/libopera/opera_3do.h index a94e3ff..cb078b6 100644 --- a/libopera/opera_3do.h +++ b/libopera/opera_3do.h @@ -4,12 +4,16 @@ #include "opera_core.h" #include "opera_vdlp.h" +#include #include #include uint32_t opera_3do_state_size(void); uint32_t opera_3do_state_save(void *buf, size_t size); uint32_t opera_3do_state_load(void const *buf, size_t size); +/* True when @buf is a current-format state opera_3do_state_load() will + * accept in full. Checks without changing any emulator state. */ +bool opera_3do_state_validate(void const *buf, size_t size); int opera_3do_init(opera_ext_interface_t callback); void opera_3do_destroy(void); diff --git a/libopera/opera_arm.c b/libopera/opera_arm.c index fe07aae..a0dbc3a 100644 --- a/libopera/opera_arm.c +++ b/libopera/opera_arm.c @@ -347,6 +347,9 @@ opera_arm_state_load(void const *data_, !opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + CPU = state; g_SOFT_RESET_PENDING = soft_reset_pending; set_rom_overlay(rom_overlay); diff --git a/libopera/opera_cdrom.c b/libopera/opera_cdrom.c index 03c85ed..f3e87ab 100644 --- a/libopera/opera_cdrom.c +++ b/libopera/opera_cdrom.c @@ -2579,6 +2579,14 @@ opera_cdrom_state_load_v2(cdrom_device_t *cd_, return 0; rv = opera_state_reader_used(&reader); + if(opera_state_validating()) + { + /* The device checks run against a scratch copy. */ + static cdrom_device_t scratch; + + return cdrom_state_v2_device_load(&scratch,&device_state) ? rv : 0; + } + launch = g_CDROM_STATE.ode.launch; ode_state_str_restore(root, g_CDROM_STATE.ode.root, @@ -2628,6 +2636,11 @@ opera_cdrom_state_load(cdrom_device_t *cd_, return rv; } + /* The older chunk layouts are not validated; a state carrying one + * is loaded the slow way, with a backup. */ + if(opera_state_validating()) + return 0; + if(data_size == sizeof(*cd_)) return opera_cdrom_state_load_v1(cd_,data_,chunk_size); diff --git a/libopera/opera_clio.c b/libopera/opera_clio.c index 10292d3..176594a 100644 --- a/libopera/opera_clio.c +++ b/libopera/opera_clio.c @@ -296,6 +296,9 @@ opera_clio_state_load(const void *buf_, !opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + CLIO = state; flagtime = flagtime_state; TIMER_VAL = timer_val_state; diff --git a/libopera/opera_clock.c b/libopera/opera_clock.c index 3a180e5..bea9346 100644 --- a/libopera/opera_clock.c +++ b/libopera/opera_clock.c @@ -235,6 +235,9 @@ opera_clock_state_load(void const *buf_, (state.field_rate == 0)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + g_CLOCK = state; recalculate_cycles_per(); diff --git a/libopera/opera_dsp.c b/libopera/opera_dsp.c index e679bb8..ed7f70c 100644 --- a/libopera/opera_dsp.c +++ b/libopera/opera_dsp.c @@ -653,6 +653,9 @@ opera_dsp_state_load(const void *buf_, !opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + DSP = state; return opera_state_reader_used(&reader); diff --git a/libopera/opera_madam.c b/libopera/opera_madam.c index 86382a9..8c51f1e 100644 --- a/libopera/opera_madam.c +++ b/libopera/opera_madam.c @@ -701,6 +701,9 @@ opera_madam_state_load(const void *buf_, if(!opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + MADAM = state; g_CEL_DRAW_PENDING = (pending != 0); // The clock chunk restores the same absolute CPU-cycle timebase. diff --git a/libopera/opera_mem.c b/libopera/opera_mem.c index ff4308d..00e5f1d 100644 --- a/libopera/opera_mem.c +++ b/libopera/opera_mem.c @@ -371,6 +371,16 @@ opera_mem_state_load(void const *data_, NVRAM_SIZE))) return 0; + /* Validating: the sizes were checked above, so the bulk payload is + * only stepped over, not copied. */ + if(opera_state_validating()) + { + if(!opera_state_skip_bytes(&payload,opera_state_reader_remaining(&payload)) || + !opera_state_reader_finished(&payload)) + return 0; + return opera_state_reader_used(&reader); + } + if(!opera_state_read_bytes(&payload,DRAM,MAX_HIRES_RAM_SIZE) || !opera_state_read_bytes(&payload,ROM1,ROM1_SIZE) || !opera_state_read_bytes(&payload,ROM2,ROM2_SIZE) || diff --git a/libopera/opera_sport.c b/libopera/opera_sport.c index d2a5cfd..fa3d1c6 100644 --- a/libopera/opera_sport.c +++ b/libopera/opera_sport.c @@ -261,6 +261,9 @@ opera_sport_state_load(const void *buf_, !opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + SPORT = state; return opera_state_reader_used(&reader); diff --git a/libopera/opera_state.c b/libopera/opera_state.c index 6748e12..44e80bd 100644 --- a/libopera/opera_state.c +++ b/libopera/opera_state.c @@ -347,6 +347,37 @@ opera_state_reader_ok(opera_state_reader_t const *reader_) return !reader_->failed; } +static bool g_STATE_VALIDATING = false; + +void +opera_state_set_validating(bool validating_) +{ + g_STATE_VALIDATING = validating_; +} + +bool +opera_state_validating(void) +{ + return g_STATE_VALIDATING; +} + +bool +opera_state_skip_bytes(opera_state_reader_t *reader_, + uint32_t size_) +{ + if(reader_->failed) + return false; + if(size_ > opera_state_reader_remaining(reader_)) + { + opera_state_reader_fail(reader_); + return false; + } + + reader_->offset += size_; + + return true; +} + bool opera_state_read_bytes(opera_state_reader_t *reader_, void *dst_, diff --git a/libopera/opera_state.h b/libopera/opera_state.h index 9e3035c..31a7cc6 100644 --- a/libopera/opera_state.h +++ b/libopera/opera_state.h @@ -113,6 +113,18 @@ bool opera_state_read_chunk(opera_state_reader_t *reader, bool opera_state_read_bytes(opera_state_reader_t *reader, void *dst, uint32_t size); +bool opera_state_skip_bytes(opera_state_reader_t *reader, + uint32_t size); + +/* + * Validation pass. While set, every current-format state loader parses + * and checks its chunk exactly as it would for a real load but stops + * short of changing any emulator state, so a state that validates is + * one the real load accepts. Lets a load skip taking a full backup of + * the running state to roll back to. + */ +void opera_state_set_validating(bool validating); +bool opera_state_validating(void); bool opera_state_read_u8(opera_state_reader_t *reader, uint8_t *value); bool opera_state_read_i8(opera_state_reader_t *reader, int8_t *value); bool opera_state_read_u16(opera_state_reader_t *reader, uint16_t *value); diff --git a/libopera/opera_vdlp.c b/libopera/opera_vdlp.c index 781bfbd..e059d41 100644 --- a/libopera/opera_vdlp.c +++ b/libopera/opera_vdlp.c @@ -1044,6 +1044,9 @@ opera_vdlp_state_load(const void *buf_, !opera_state_reader_finished(&payload)) return 0; + if(opera_state_validating()) + return opera_state_reader_used(&reader); + _migrate_vdlp_state(&state); g_VDLP = state; diff --git a/libopera/opera_xbus.c b/libopera/opera_xbus.c index 55278e6..6009c6b 100644 --- a/libopera/opera_xbus.c +++ b/libopera/opera_xbus.c @@ -611,7 +611,8 @@ opera_xbus_state_load(void const *data_, (device_count > 15)) return 0; - XBUS = state; + if(!opera_state_validating()) + XBUS = state; for(i = 0; i < device_count; i++) { @@ -624,7 +625,8 @@ opera_xbus_state_load(void const *data_, (xdev[slot] == NULL)) return 0; - xdev[slot](XBP_RESET,NULL); + if(!opera_state_validating()) + xdev[slot](XBP_RESET,NULL); remaining = opera_state_reader_remaining(&payload); savedata.data = &payload.data[payload.offset]; savedata.size = remaining; diff --git a/libretro.c b/libretro.c index 97faed1..57c2455 100644 --- a/libretro.c +++ b/libretro.c @@ -261,6 +261,25 @@ retro_unserialize(void const *data_, uint32_t restore_size; void *backup_state; + /* + * A current-format state is checked first and then loaded: a state + * that validates is one the load accepts in full, so there is + * nothing to roll back and no need to save the running state first. + * That backup was a full serialize into a fresh allocation the size + * of a state on every load -- twice the work for run-ahead and + * rewind, which load every frame. Anything else (an older format, + * a state that does not validate) takes the backup path below, which + * leaves the running state as it was if the load fails. + */ + if(opera_3do_state_validate(data_,size_)) + { + size = opera_3do_state_load(data_,size_); + if(size != size_) + opera_log_printf(OPERA_LOG_ERROR, + "[Opera]: state validated but failed to load\n"); + return (size == size_); + } + backup_state = malloc(retro_serialize_size()); if(backup_state == NULL) return false;