Files
LibretroAdmin 93ae3b9520 retro_unserialize: validate instead of backing up the running state
Every state load first serialized the running state into a fresh
allocation the size of a state -- 25.5 MB -- so a failed load could be
rolled back. That doubled the cost of every load, and run-ahead and
rewind load every frame.

A current-format state is now checked first and then loaded. Each
current-format loader already parses its chunk into locals and checks
it before touching emulator state; with opera_state_set_validating()
set they stop there, so a state that validates is one the real load
accepts in full and there is nothing to roll back. In that pass:

- the MEM chunk's 25 MB payload is stepped over (sizes and config are
  still checked), not copied;
- the xbus devices are not reset, and the CD-ROM device fields are
  checked against a scratch copy;
- the memory-map sync, pbus reset and other side effects after loading
  are skipped.

An older format, or a state carrying an older CD-ROM chunk, is not
validated and takes the old backup path, as does anything that fails
validation, so a refused state still leaves the running state as it
was.

Measured with a libretro driver over 200 loads: retro_unserialize went
from 9.08 ms to 4.20 ms on average (best 7.80 ms to 3.58 ms). Checked:
save, load and save again is byte-identical; a state loaded and run 60
frames matches the original run byte for byte; and a sweep flipping
each of 9721 bytes (every 64th byte of the first 360 KB, every byte of
the last 4 KB) is accepted or refused exactly as before, with no
refused load ever changing the running state.
2026-10-28 20:21:15 +01:00

822 lines
17 KiB
C

#include "file/file_path.h"
#include "libretro.h"
#include "libretro_core_options.h"
#include "retro_dirent.h"
#include "retro_miscellaneous.h"
#include "streams/file_stream.h"
#include "lr_input.h"
#include "lr_input_crosshair.h"
#include "lr_input_descs.h"
#include "opera_lr_callbacks.h"
#include "opera_lr_dsp.h"
#include "opera_lr_nvram.h"
#include "opera_lr_opts.h"
#include "retro_cdimage.h"
#include "libopera/opera_3do.h"
#include "libopera/opera_arm.h"
#include "libopera/opera_bios.h"
#include "libopera/opera_cdrom.h"
#include "libopera/opera_clock.h"
#include "libopera/opera_core.h"
#include "libopera/opera_log.h"
#include "libopera/opera_madam.h"
#include "libopera/opera_mem.h"
#include "libopera/opera_nvram.h"
#include "libopera/opera_pbus.h"
#include "libopera/opera_region.h"
#include "libopera/opera_vdlp.h"
#include "libopera/prng16.h"
#include "libopera/prng32.h"
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#define CDIMAGE_SECTOR_SIZE 2048
typedef enum retro_reset_flags_t
{
RETRO_RESET_FLAG_NONE = 0,
RETRO_RESET_FLAG_SAVE_NVRAM = (1 << 0)
} retro_reset_flags_t;
static cdimage_t CDIMAGE;
static uint32_t CDIMAGE_SECTOR;
static char *g_GAME_INFO_PATH = NULL;
static
void
retro_environment_set_support_no_game(void)
{
bool support_no_game = true;
retro_environment_cb(RETRO_ENVIRONMENT_SET_SUPPORT_NO_GAME,&support_no_game);
}
static
void
retro_environment_set_controller_info(void)
{
static const struct retro_controller_description port[] =
{
{ "3DO Joypad", RETRO_DEVICE_JOYPAD },
{ "3DO Flightstick", RETRO_DEVICE_FLIGHTSTICK },
{ "3DO Mouse", RETRO_DEVICE_MOUSE },
{ "3DO Lightgun", RETRO_DEVICE_LIGHTGUN },
{ "Arcade Lightgun", RETRO_DEVICE_ARCADE_LIGHTGUN },
{ "Orbatak Trackball", RETRO_DEVICE_ORBATAK_TRACKBALL },
};
static const struct retro_controller_info ports[LR_INPUT_MAX_DEVICES+1] =
{
{port, 6},
{port, 6},
{port, 6},
{port, 6},
{port, 6},
{port, 6},
{port, 6},
{port, 6},
{NULL, 0}
};
retro_environment_cb(RETRO_ENVIRONMENT_SET_CONTROLLER_INFO,(void*)ports);
}
static
void
retro_vfs_initialize(void)
{
struct retro_vfs_interface_info vfs_info;
vfs_info.required_interface_version = 3;
vfs_info.iface = NULL;
if(retro_environment_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE,&vfs_info) &&
(vfs_info.iface != NULL))
{
filestream_vfs_init(&vfs_info);
path_vfs_init(&vfs_info);
dirent_vfs_init(&vfs_info);
return;
}
vfs_info.required_interface_version = FILESTREAM_REQUIRED_VFS_VERSION;
vfs_info.iface = NULL;
if(retro_environment_cb(RETRO_ENVIRONMENT_GET_VFS_INTERFACE,&vfs_info) &&
(vfs_info.iface != NULL))
filestream_vfs_init(&vfs_info);
}
void
retro_set_environment(retro_environment_t cb_)
{
opera_lr_callbacks_set_environment(cb_);
retro_vfs_initialize();
retro_environment_set_controller_info();
libretro_init_core_options();
libretro_set_core_options();
retro_environment_set_support_no_game();
}
void
retro_set_video_refresh(retro_video_refresh_t cb_)
{
opera_lr_callbacks_set_video_refresh(cb_);
}
void
retro_set_audio_sample(retro_audio_sample_t cb_)
{
opera_lr_callbacks_set_audio_sample(cb_);
}
void
retro_set_audio_sample_batch(retro_audio_sample_batch_t cb_)
{
opera_lr_callbacks_set_audio_sample_batch(cb_);
}
void
retro_set_input_poll(retro_input_poll_t cb_)
{
opera_lr_callbacks_set_input_poll(cb_);
}
void
retro_set_input_state(retro_input_state_t cb_)
{
opera_lr_callbacks_set_input_state(cb_);
}
static
uint32_t
cdimage_get_size(void)
{
return retro_cdimage_get_number_of_logical_blocks(&CDIMAGE);
}
static
void
cdimage_set_sector(const uint32_t sector_)
{
CDIMAGE_SECTOR = sector_;
}
static
void
cdimage_read_sector(void *buf_,
size_t len_)
{
retro_cdimage_read_sector(&CDIMAGE,CDIMAGE_SECTOR,buf_,len_);
}
static
void
cdimage_get_toc(uint8_t *track_first_,
uint8_t *track_last_,
uint8_t *disc_id_,
void *disc_toc_,
uint32_t disc_toc_size_)
{
retro_cdimage_get_toc(&CDIMAGE,
track_first_,
track_last_,
disc_id_,
disc_toc_,
disc_toc_size_);
}
static
void
content_runtime_reset(void)
{
cdimage_set_sector(0);
opera_cdrom_ode_set_root(NULL);
}
static
void*
libopera_callback(int cmd_,
void *data_)
{
switch(cmd_)
{
case EXT_DSP_TRIGGER:
opera_lr_dsp_process();
break;
default:
break;
}
return NULL;
}
#ifndef GIT_VERSION
#define GIT_VERSION ""
#endif
void
retro_get_system_info(struct retro_system_info *info_)
{
memset(info_,0,sizeof(*info_));
info_->library_name = "Opera";
info_->library_version = "1.0.0" GIT_VERSION;
info_->need_fullpath = true;
info_->valid_extensions = "iso|bin|chd|cue";
}
size_t
retro_serialize_size(void)
{
return opera_3do_state_size();
}
bool
retro_serialize(void *data_,
size_t size_)
{
uint32_t size;
if(size_ == 0)
return false;
size = opera_3do_state_save(data_,size_);
return (size == size_);
}
bool
retro_unserialize(void const *data_,
size_t size_)
{
uint32_t size;
uint32_t backup_size;
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;
backup_size = retro_serialize_size();
size = retro_serialize(backup_state,backup_size);
if(size)
{
size = opera_3do_state_load(data_,size_);
if(size != size_)
{
restore_size = opera_3do_state_load(backup_state,backup_size);
if(restore_size != backup_size)
opera_log_printf(OPERA_LOG_ERROR,
"[Opera]: failed to restore previous state after unsuccessful state load\n");
size = 0;
}
}
free(backup_state);
return (size == size_);
}
void
retro_cheat_reset(void)
{
}
void
retro_cheat_set(unsigned index_,
bool enabled_,
const char *code_)
{
}
void
retro_set_controller_port_device(unsigned port_,
unsigned device_)
{
lr_input_device_set_with_descs(port_,device_);
}
static
enum retro_pixel_format
vdlp_pixel_format_to_libretro(vdlp_pixel_format_e pf_)
{
switch (pf_)
{
case VDLP_PIXEL_FORMAT_0RGB1555:
return RETRO_PIXEL_FORMAT_0RGB1555;
case VDLP_PIXEL_FORMAT_RGB565:
return RETRO_PIXEL_FORMAT_RGB565;
case VDLP_PIXEL_FORMAT_XRGB8888:
return RETRO_PIXEL_FORMAT_XRGB8888;
}
return RETRO_PIXEL_FORMAT_XRGB8888;
}
static
int
set_pixel_format(void)
{
int rv;
enum retro_pixel_format fmt;
fmt = vdlp_pixel_format_to_libretro(g_OPTS.vdlp_pixel_format);
rv = retro_environment_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT,&fmt);
if(rv == 0)
{
retro_log_printf_cb(RETRO_LOG_ERROR,
"[Opera]: pixel format is not supported.\n");
return -1;
}
return 0;
}
static
int
print_cdimage_open_fail(const char *path_)
{
retro_log_printf_cb(RETRO_LOG_ERROR,
"[Opera]: failure opening image - %s\n",
path_);
return -1;
}
static
int
open_cdimage_if_needed(const struct retro_game_info *info_)
{
int rv;
if(!info_)
return 0;
rv = retro_cdimage_open(info_->path,&CDIMAGE);
if(rv == -1)
return print_cdimage_open_fail(info_->path);
return 0;
}
static
void
game_info_path_free(void)
{
if(g_GAME_INFO_PATH == NULL)
return;
free(g_GAME_INFO_PATH);
g_GAME_INFO_PATH = NULL;
}
static
void
game_info_path_save(const struct retro_game_info *info_)
{
size_t len;
game_info_path_free();
if((info_ == NULL) || (info_->path == NULL))
return;
g_GAME_INFO_PATH = strdup(info_->path);
}
static
void
game_info_path_save_path(const char *path_)
{
game_info_path_free();
if(path_ == NULL)
return;
g_GAME_INFO_PATH = strdup(path_);
}
static
const
char*
game_info_path_get(void)
{
return g_GAME_INFO_PATH;
}
static
int
cdimage_ode_launch(const char *path_)
{
cdimage_t next;
int rv;
memset(&next,0,sizeof(next));
rv = retro_cdimage_open(path_,&next);
if(rv == -1)
{
retro_log_printf_cb(RETRO_LOG_ERROR,
"[Opera]: ODE launch failed opening image - %s\n",
path_);
return -1;
}
opera_lr_nvram_save(game_info_path_get(),
g_OPTS.nvram_shared,
g_OPTS.nvram_version);
retro_cdimage_close(&CDIMAGE);
CDIMAGE = next;
cdimage_set_sector(0);
game_info_path_save_path(path_);
retro_log_printf_cb(RETRO_LOG_INFO,
"[Opera]: ODE launched image - %s\n",
path_);
return 0;
}
static
void
ode_root_set_for_content(const struct retro_game_info *info_)
{
char root[PATH_MAX_LENGTH];
if((info_ == NULL) || (info_->path == NULL))
{
opera_cdrom_ode_set_root(NULL);
return;
}
strncpy(root,info_->path,sizeof(root) - 1);
root[sizeof(root) - 1] = 0;
path_basedir(root);
opera_cdrom_ode_set_root(root);
}
static
bool
_init_core(void)
{
if((opera_lr_opts_process() & OPERA_LR_OPTS_CHANGE_ERROR) == 0)
{
if(opera_3do_init(libopera_callback) == 0)
return true;
retro_log_printf_cb(RETRO_LOG_ERROR,"[Opera]: unable to initialize core\n");
}
opera_lr_opts_reset();
return false;
}
bool
retro_load_game(const struct retro_game_info *info_)
{
int rv;
content_runtime_reset();
game_info_path_save(info_);
rv = open_cdimage_if_needed(info_);
if(rv == -1)
return false;
ode_root_set_for_content(info_);
if(!_init_core())
{
content_runtime_reset();
retro_cdimage_close(&CDIMAGE);
game_info_path_free();
return false;
}
cdimage_set_sector(0);
rv = set_pixel_format();
if(rv < 0)
return false;
opera_lr_nvram_load(game_info_path_get(),
g_OPTS.nvram_shared,
g_OPTS.nvram_version);
return true;
}
bool
retro_load_game_special(unsigned game_type_,
const struct retro_game_info *info_,
size_t num_info_)
{
return false;
}
void
retro_unload_game(void)
{
opera_lr_nvram_save(game_info_path_get(),
g_OPTS.nvram_shared,
g_OPTS.nvram_version);
game_info_path_free();
opera_3do_destroy();
opera_mem_destroy();
content_runtime_reset();
retro_cdimage_close(&CDIMAGE);
opera_lr_opts_reset();
}
static
void
get_system_geometry(struct retro_game_geometry *geometry_)
{
geometry_->base_width = (opera_region_width() << g_OPTS.high_resolution);
geometry_->base_height = (opera_region_height() << g_OPTS.high_resolution);
geometry_->max_width = (opera_region_max_width() * 2);
geometry_->max_height = (opera_region_max_height() * 2);
geometry_->aspect_ratio = 4.0 / 3.0;
}
static
void
get_system_av_info(struct retro_system_av_info *info_)
{
memset(info_,0,sizeof(*info_));
info_->timing.fps = opera_region_refresh_rate();
info_->timing.sample_rate = 44100;
get_system_geometry(&info_->geometry);
}
void
retro_get_system_av_info(struct retro_system_av_info *info_)
{
get_system_av_info(info_);
}
static
void
set_system_av_info(void)
{
struct retro_system_av_info info;
get_system_av_info(&info);
retro_environment_cb(RETRO_ENVIRONMENT_SET_SYSTEM_AV_INFO,&info);
}
static
void
set_system_geometry(void)
{
struct retro_game_geometry geometry;
get_system_geometry(&geometry);
retro_environment_cb(RETRO_ENVIRONMENT_SET_GEOMETRY,&geometry);
}
unsigned
retro_get_region(void)
{
switch(opera_region_get())
{
case OPERA_REGION_PAL1:
case OPERA_REGION_PAL2:
return RETRO_REGION_PAL;
case OPERA_REGION_NTSC:
default:
break;
}
return RETRO_REGION_NTSC;
}
unsigned
retro_api_version(void)
{
return RETRO_API_VERSION;
}
void*
retro_get_memory_data(unsigned id_)
{
switch(id_)
{
case RETRO_MEMORY_SAVE_RAM:
return NULL;
case RETRO_MEMORY_SYSTEM_RAM:
return DRAM;
case RETRO_MEMORY_VIDEO_RAM:
return VRAM;
}
return NULL;
}
size_t
retro_get_memory_size(unsigned id_)
{
switch(id_)
{
case RETRO_MEMORY_SAVE_RAM:
return 0;
case RETRO_MEMORY_SYSTEM_RAM:
return DRAM_SIZE;
case RETRO_MEMORY_VIDEO_RAM:
return VRAM_SIZE;
}
return 0;
}
void
retro_init(void)
{
bool fixed_random_seed;
struct retro_log_callback log;
uint32_t random_seed;
unsigned level;
uint64_t serialization_quirks;
level = 5;
serialization_quirks = (RETRO_SERIALIZATION_QUIRK_ENDIAN_DEPENDENT |
RETRO_SERIALIZATION_QUIRK_PLATFORM_DEPENDENT);
if(retro_environment_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE,&log))
{
opera_lr_callbacks_set_log_printf(log.log);
opera_log_set_func(log.log);
}
retro_environment_cb(RETRO_ENVIRONMENT_SET_PERFORMANCE_LEVEL,&level);
retro_environment_cb(RETRO_ENVIRONMENT_SET_SERIALIZATION_QUIRKS,&serialization_quirks);
opera_cdrom_set_callbacks(cdimage_get_size,
cdimage_set_sector,
cdimage_read_sector,
cdimage_get_toc);
opera_cdrom_ode_set_launch_callback(cdimage_ode_launch);
fixed_random_seed = opera_lr_opts_get_random_seed(&random_seed);
if(!fixed_random_seed)
random_seed = (uint32_t)time(NULL);
prng16_seed(random_seed);
prng32_seed(random_seed);
retro_log_printf_cb(RETRO_LOG_INFO,
"[Opera]: random seed 0x%08X (%s)\n",
(unsigned)random_seed,
fixed_random_seed ? "fixed" : "time-based");
}
void
retro_deinit(void)
{
}
static
void
retro_reset_core(retro_reset_flags_t flags_)
{
if(flags_ & RETRO_RESET_FLAG_SAVE_NVRAM)
opera_lr_nvram_save(game_info_path_get(),
g_OPTS.nvram_shared,
g_OPTS.nvram_version);
opera_3do_destroy();
opera_lr_opts_reset();
if(!_init_core())
{
retro_environment_cb(RETRO_ENVIRONMENT_SHUTDOWN,NULL);
return;
}
cdimage_set_sector(0);
opera_lr_nvram_load(game_info_path_get(),
g_OPTS.nvram_shared,
g_OPTS.nvram_version);
}
void
retro_reset(void)
{
retro_reset_core(RETRO_RESET_FLAG_SAVE_NVRAM);
}
static
bool
ode_reset_if_requested(void)
{
if(!opera_cdrom_ode_consume_restart_request())
return false;
retro_log_printf_cb(RETRO_LOG_INFO,
"[Opera]: ODE media launch requested core reset\n");
/* cdimage_ode_launch already saved NVRAM before switching game paths. */
retro_reset_core(RETRO_RESET_FLAG_NONE);
return true;
}
static
bool
variable_updated()
{
bool updated;
updated = false;
if(!retro_environment_cb(RETRO_ENVIRONMENT_GET_VARIABLE_UPDATE,&updated))
return false;
return updated;
}
static
void
process_opts_if_updated()
{
uint32_t changes;
if(!variable_updated())
return;
changes = opera_lr_opts_process();
if(changes & OPERA_LR_OPTS_CHANGE_ERROR)
return;
if(changes & OPERA_LR_OPTS_CHANGE_TIMING)
set_system_av_info();
else if(changes & OPERA_LR_OPTS_CHANGE_GEOMETRY)
set_system_geometry();
}
static
void
draw_crosshairs_if_enabled()
{
if(g_OPTS.hide_lightgun_crosshairs)
return;
lr_input_crosshairs_draw(g_OPTS.video_buffer,
g_OPTS.video_width,
g_OPTS.video_height);
}
void
retro_run(void)
{
if(!g_OPTS.initialized_opera)
return;
if(ode_reset_if_requested())
return;
process_opts_if_updated();
lr_input_update(g_OPTS.active_devices);
opera_3do_process_frame();
if(ode_reset_if_requested())
return;
draw_crosshairs_if_enabled();
opera_lr_dsp_upload();
retro_video_refresh_cb(g_OPTS.video_buffer,
g_OPTS.video_width,
g_OPTS.video_height,
g_OPTS.video_width << g_OPTS.video_pitch_shift);
}