mirror of
https://github.com/libretro/opera-libretro.git
synced 2026-11-07 04:25:58 -10:00
Savestates: format V4, 25.5 MB down to 6.6 MB
A state stored the whole 22 MB DRAM allocation -- sized for the
largest memory configuration and the high-resolution VRAM copies --
plus both ROMs, whatever the game used, and the DSP's 70 KB of decode
tables. V4 keeps everything else as it was and changes two chunks:
- MEM2 replaces MEM. It holds the memory configuration, then only the
RAM that configuration uses (DRAM, VRAM and VRAM's three high-
resolution copies), then NVRAM. The copies are kept whether or not
high resolution is on, because it can be switched mid-game and the
state size must not change under a frontend that sized its buffers
once; the configuration itself is fixed for a session. The ROMs are
left out: they are the BIOS and font loaded for the session and
never change. For the stock 2 MB/1 MB configuration that is 6 MB of
RAM instead of 22 MB, and 2 MB of ROM gone.
- DSP2 replaces DSPP. INSTTRAS, REGCONV and BRCONDTAB are decode tables
opera_dsp_init() builds and nothing changes, so they are not stored;
a load keeps the running core's.
V3 states still load: MEM and DSPP are still read, and the tables a
DSPP chunk carries are stepped over. A core without this change
refuses a V4 state as an unknown version.
Measured with a libretro driver, stock configuration, together with
the array I/O change before this:
size serialize unserialize
before 25550222 3.8 ms 4.2 ms
V4 6605970 0.5 ms 0.6 ms
Checked: a V3 state saved by the previous core loads and plays the
next 240 frames with audio and video identical to the original run; a
V4 state does the same; save-load-save is byte-identical; a replay of
60 frames from a loaded state matches; a sweep flipping 9721 bytes
never lets a refused load change the running state; and the changed
files build big-endian (PowerPC).
This commit is contained in:
1 parent
02f675e3c3
commit
54fe4f3754
4 files changed
+228
-103
No files matched your search
+22
-4
@@ -702,19 +702,35 @@ opera_3do_state_load_v3(const void *data_,
|
||||
true);
|
||||
}
|
||||
|
||||
/* V4 differs from V3 only in its memory chunk, which
|
||||
* opera_mem_state_load() tells apart by name. */
|
||||
static
|
||||
uint32_t
|
||||
opera_3do_state_load_v4(const void *data_,
|
||||
size_t const size_)
|
||||
{
|
||||
return opera_3do_state_load_chunked(data_,
|
||||
size_,
|
||||
OPERA_STATE_VERSION_V4,
|
||||
true);
|
||||
}
|
||||
|
||||
bool
|
||||
opera_3do_state_validate(void const *data_,
|
||||
size_t size_)
|
||||
{
|
||||
uint32_t rv;
|
||||
uint32_t version;
|
||||
|
||||
/* 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)
|
||||
/* Only the chunked V3/V4 formats: the older loaders reset parts of
|
||||
* the emulator before they read, so they cannot be dry-run. */
|
||||
version = opera_state_get_version(data_,size_);
|
||||
if((version != OPERA_STATE_VERSION_V3) &&
|
||||
(version != OPERA_STATE_VERSION_V4))
|
||||
return false;
|
||||
|
||||
opera_state_set_validating(true);
|
||||
rv = opera_3do_state_load_v3(data_,size_);
|
||||
rv = opera_3do_state_load_chunked(data_,size_,version,true);
|
||||
opera_state_set_validating(false);
|
||||
|
||||
return ((rv != 0) && (rv == size_));
|
||||
@@ -738,6 +754,8 @@ opera_3do_state_load(void const *data_,
|
||||
return opera_3do_state_load_v2(data_,size_);
|
||||
case OPERA_STATE_VERSION_V3:
|
||||
return opera_3do_state_load_v3(data_,size_);
|
||||
case OPERA_STATE_VERSION_V4:
|
||||
return opera_3do_state_load_v4(data_,size_);
|
||||
default:
|
||||
opera_log_printf(OPERA_LOG_ERROR,
|
||||
"[Opera]: unable to load state - unknown state version %x\n",
|
||||
|
||||
+106
-79
@@ -472,29 +472,19 @@ opera_dsp_state_size_v1(void)
|
||||
return opera_state_save_size(sizeof(DSP));
|
||||
}
|
||||
|
||||
/*
|
||||
* INSTTRAS, REGCONV and BRCONDTAB are decode tables opera_dsp_init()
|
||||
* builds and nothing changes afterwards, so the DSP2 chunk (state V4)
|
||||
* leaves them out and a load keeps the running core's. The older DSPP
|
||||
* chunk carried them; it is still read.
|
||||
*/
|
||||
/* Everything after the tables. */
|
||||
static
|
||||
bool
|
||||
dsp_state_write_payload(opera_state_writer_t *writer_,
|
||||
dsp_t const *state_)
|
||||
dsp_state_write_live(opera_state_writer_t *writer_,
|
||||
dsp_t const *state_)
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t j;
|
||||
|
||||
if(!opera_state_write_u32(writer_,state_->RBASEx4))
|
||||
return false;
|
||||
|
||||
for(i = 0; i < 0x8000; i++)
|
||||
if(!opera_state_write_u8(writer_,state_->INSTTRAS[i].req.raw) ||
|
||||
!opera_state_write_i8(writer_,(int8_t)state_->INSTTRAS[i].BS))
|
||||
return false;
|
||||
|
||||
if(!opera_state_write_u16_array(writer_,&state_->REGCONV[0][0],8 * 16))
|
||||
return false;
|
||||
|
||||
for(i = 0; i < 32; i++)
|
||||
for(j = 0; j < 32; j++)
|
||||
if(!opera_state_write_i32(writer_,state_->BRCONDTAB[i][j]))
|
||||
return false;
|
||||
|
||||
if(!opera_state_write_u16_array(writer_,state_->NMem,2048) ||
|
||||
!opera_state_write_u16_array(writer_,state_->IMem,1024) ||
|
||||
@@ -527,6 +517,15 @@ dsp_state_write_payload(opera_state_writer_t *writer_,
|
||||
return true;
|
||||
}
|
||||
|
||||
static
|
||||
bool
|
||||
dsp_state_write_payload(opera_state_writer_t *writer_,
|
||||
dsp_t const *state_)
|
||||
{
|
||||
return (opera_state_write_u32(writer_,state_->RBASEx4) &&
|
||||
dsp_state_write_live(writer_,state_));
|
||||
}
|
||||
|
||||
static
|
||||
uint32_t
|
||||
dsp_state_payload_size(void)
|
||||
@@ -553,7 +552,7 @@ opera_dsp_state_save(void *buf_)
|
||||
|
||||
payload_size = dsp_state_payload_size();
|
||||
opera_state_writer_init(&writer,buf_,opera_state_chunk_size(payload_size));
|
||||
opera_state_write_chunk_header(&writer,"DSPP",payload_size);
|
||||
opera_state_write_chunk_header(&writer,"DSP2",payload_size);
|
||||
dsp_state_write_payload(&writer,&DSP);
|
||||
|
||||
return opera_state_writer_ok(&writer) ? opera_state_writer_used(&writer) : 0;
|
||||
@@ -566,101 +565,129 @@ opera_dsp_state_load_v1(const void *buf_,
|
||||
return opera_state_load_sized(&DSP,"DSPP",buf_,size_,sizeof(DSP));
|
||||
}
|
||||
|
||||
/* The state a DSP2 chunk holds: the DSP minus its decode tables. */
|
||||
typedef struct dsp_live_s dsp_live_t;
|
||||
struct dsp_live_s
|
||||
{
|
||||
uint32_t RBASEx4;
|
||||
uint16_t NMem[2048];
|
||||
uint16_t IMem[1024];
|
||||
int REGi;
|
||||
REGSTAG_t dregs;
|
||||
INTAG_t flags;
|
||||
int CPUSupply[16];
|
||||
};
|
||||
|
||||
static
|
||||
bool
|
||||
dsp_state_read_payload(opera_state_reader_t *reader_,
|
||||
dsp_t *state_)
|
||||
dsp_state_read_live(opera_state_reader_t *reader_,
|
||||
dsp_live_t *live_)
|
||||
{
|
||||
uint32_t i;
|
||||
uint32_t j;
|
||||
int8_t bs;
|
||||
uint8_t b;
|
||||
|
||||
memset(state_,0,sizeof(*state_));
|
||||
|
||||
if(!opera_state_read_u32(reader_,&state_->RBASEx4))
|
||||
return false;
|
||||
|
||||
for(i = 0; i < 0x8000; i++)
|
||||
{
|
||||
if(!opera_state_read_u8(reader_,&state_->INSTTRAS[i].req.raw) ||
|
||||
!opera_state_read_i8(reader_,&bs))
|
||||
return false;
|
||||
state_->INSTTRAS[i].BS = (char)bs;
|
||||
}
|
||||
|
||||
if(!opera_state_read_u16_array(reader_,&state_->REGCONV[0][0],8 * 16))
|
||||
return false;
|
||||
|
||||
for(i = 0; i < 32; i++)
|
||||
for(j = 0; j < 32; j++)
|
||||
{
|
||||
int32_t v;
|
||||
if(!opera_state_read_i32(reader_,&v))
|
||||
return false;
|
||||
state_->BRCONDTAB[i][j] = v;
|
||||
}
|
||||
|
||||
if(!opera_state_read_u16_array(reader_,state_->NMem,2048) ||
|
||||
!opera_state_read_u16_array(reader_,state_->IMem,1024) ||
|
||||
!opera_state_read_i32(reader_,&state_->REGi) ||
|
||||
!opera_state_read_u32(reader_,&state_->dregs.PC) ||
|
||||
!opera_state_read_u16(reader_,&state_->dregs.AudioOutStatus) ||
|
||||
!opera_state_read_u16(reader_,&state_->dregs.Sema4Status) ||
|
||||
!opera_state_read_u16(reader_,&state_->dregs.Sema4Data) ||
|
||||
!opera_state_read_i16(reader_,&state_->dregs.DSPPCNT) ||
|
||||
!opera_state_read_i16(reader_,&state_->dregs.DSPPRLD) ||
|
||||
!opera_state_read_i16(reader_,&state_->dregs.AUDCNT) ||
|
||||
!opera_state_read_u16(reader_,&state_->dregs.INT) ||
|
||||
!opera_state_read_i16(reader_,&state_->flags.MULT1) ||
|
||||
!opera_state_read_i16(reader_,&state_->flags.MULT2) ||
|
||||
!opera_state_read_i16(reader_,&state_->flags.ALU1) ||
|
||||
!opera_state_read_i16(reader_,&state_->flags.ALU2) ||
|
||||
!opera_state_read_i32(reader_,&state_->flags.BS) ||
|
||||
!opera_state_read_u16(reader_,&state_->flags.RMAP) ||
|
||||
!opera_state_read_u16(reader_,&state_->flags.nOP_MASK) ||
|
||||
!opera_state_read_u16(reader_,&state_->flags.WRITEBACK) ||
|
||||
!opera_state_read_u8(reader_,&state_->flags.req.raw) ||
|
||||
if(!opera_state_read_u16_array(reader_,live_->NMem,2048) ||
|
||||
!opera_state_read_u16_array(reader_,live_->IMem,1024) ||
|
||||
!opera_state_read_i32(reader_,&live_->REGi) ||
|
||||
!opera_state_read_u32(reader_,&live_->dregs.PC) ||
|
||||
!opera_state_read_u16(reader_,&live_->dregs.AudioOutStatus) ||
|
||||
!opera_state_read_u16(reader_,&live_->dregs.Sema4Status) ||
|
||||
!opera_state_read_u16(reader_,&live_->dregs.Sema4Data) ||
|
||||
!opera_state_read_i16(reader_,&live_->dregs.DSPPCNT) ||
|
||||
!opera_state_read_i16(reader_,&live_->dregs.DSPPRLD) ||
|
||||
!opera_state_read_i16(reader_,&live_->dregs.AUDCNT) ||
|
||||
!opera_state_read_u16(reader_,&live_->dregs.INT) ||
|
||||
!opera_state_read_i16(reader_,&live_->flags.MULT1) ||
|
||||
!opera_state_read_i16(reader_,&live_->flags.MULT2) ||
|
||||
!opera_state_read_i16(reader_,&live_->flags.ALU1) ||
|
||||
!opera_state_read_i16(reader_,&live_->flags.ALU2) ||
|
||||
!opera_state_read_i32(reader_,&live_->flags.BS) ||
|
||||
!opera_state_read_u16(reader_,&live_->flags.RMAP) ||
|
||||
!opera_state_read_u16(reader_,&live_->flags.nOP_MASK) ||
|
||||
!opera_state_read_u16(reader_,&live_->flags.WRITEBACK) ||
|
||||
!opera_state_read_u8(reader_,&live_->flags.req.raw) ||
|
||||
!opera_state_read_u8(reader_,&b))
|
||||
return false;
|
||||
state_->flags.Running = (b != 0);
|
||||
live_->flags.Running = (b != 0);
|
||||
if(!opera_state_read_u8(reader_,&b))
|
||||
return false;
|
||||
state_->flags.GenFIQ = (b != 0);
|
||||
live_->flags.GenFIQ = (b != 0);
|
||||
|
||||
for(i = 0; i < 16; i++)
|
||||
{
|
||||
int32_t v;
|
||||
if(!opera_state_read_i32(reader_,&v))
|
||||
return false;
|
||||
state_->CPUSupply[i] = v;
|
||||
live_->CPUSupply[i] = v;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
opera_dsp_state_load(const void *buf_,
|
||||
uint32_t const size_)
|
||||
static
|
||||
void
|
||||
dsp_state_apply_live(dsp_t *dsp_,
|
||||
dsp_live_t const *live_)
|
||||
{
|
||||
dsp_t state;
|
||||
dsp_->RBASEx4 = live_->RBASEx4;
|
||||
memcpy(dsp_->NMem,live_->NMem,sizeof(dsp_->NMem));
|
||||
memcpy(dsp_->IMem,live_->IMem,sizeof(dsp_->IMem));
|
||||
dsp_->REGi = live_->REGi;
|
||||
dsp_->dregs = live_->dregs;
|
||||
dsp_->flags = live_->flags;
|
||||
memcpy(dsp_->CPUSupply,live_->CPUSupply,sizeof(dsp_->CPUSupply));
|
||||
}
|
||||
|
||||
/* The DSPP chunk (state V3): the tables are in the stream. They are
|
||||
* the ones opera_dsp_init() builds, so they are stepped over and the
|
||||
* running core's are kept. */
|
||||
static
|
||||
bool
|
||||
dsp_state_skip_tables(opera_state_reader_t *reader_)
|
||||
{
|
||||
return opera_state_skip_bytes(reader_,
|
||||
(0x8000 * 2) +
|
||||
(8 * 16 * sizeof(uint16_t)) +
|
||||
(32 * 32 * sizeof(int32_t)));
|
||||
}
|
||||
|
||||
static
|
||||
uint32_t
|
||||
opera_dsp_state_load_chunk(const void *buf_,
|
||||
uint32_t const size_,
|
||||
char const *name_,
|
||||
bool tables_)
|
||||
{
|
||||
dsp_live_t live;
|
||||
opera_state_reader_t reader;
|
||||
opera_state_reader_t payload;
|
||||
|
||||
opera_state_reader_init(&reader,buf_,size_);
|
||||
if(!opera_state_read_chunk(&reader,"DSPP",&payload) ||
|
||||
!dsp_state_read_payload(&payload,&state) ||
|
||||
if(!opera_state_read_chunk(&reader,name_,&payload) ||
|
||||
!opera_state_read_u32(&payload,&live.RBASEx4) ||
|
||||
(tables_ && !dsp_state_skip_tables(&payload)) ||
|
||||
!dsp_state_read_live(&payload,&live) ||
|
||||
!opera_state_reader_finished(&payload))
|
||||
return 0;
|
||||
|
||||
if(opera_state_validating())
|
||||
return opera_state_reader_used(&reader);
|
||||
|
||||
DSP = state;
|
||||
dsp_state_apply_live(&DSP,&live);
|
||||
|
||||
return opera_state_reader_used(&reader);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
opera_dsp_state_load(const void *buf_,
|
||||
uint32_t const size_)
|
||||
{
|
||||
if((size_ >= 4) && !memcmp(buf_,"DSP2",4))
|
||||
return opera_dsp_state_load_chunk(buf_,size_,"DSP2",false);
|
||||
|
||||
return opera_dsp_state_load_chunk(buf_,size_,"DSPP",true);
|
||||
}
|
||||
|
||||
static
|
||||
uint16_t
|
||||
dsp_operand_load1(void)
|
||||
|
||||
+96
-19
@@ -280,36 +280,59 @@ opera_mem_state_size_v1()
|
||||
return size;
|
||||
}
|
||||
|
||||
/*
|
||||
* The MEM2 chunk: the memory configuration, how many RAM bytes follow,
|
||||
* that much of DRAM, then NVRAM.
|
||||
*
|
||||
* The RAM stored is what the configuration uses: DRAM plus VRAM and its
|
||||
* three high-resolution copies, which sit directly above it. That is
|
||||
* fixed for a session -- the configuration is chosen once, and the
|
||||
* copies are kept whether or not high resolution is on, since it can
|
||||
* be switched at any time -- so the state size does not change under a
|
||||
* frontend that sized its buffers once. The ROMs are not stored: they
|
||||
* are the BIOS and font loaded for the session and never change.
|
||||
*
|
||||
* The older MEM chunk (V3) stored the whole 22 MB DRAM allocation and
|
||||
* both ROMs, 24.1 MB, whatever the configuration.
|
||||
*/
|
||||
static
|
||||
uint32_t
|
||||
opera_mem_state_ram_bytes(opera_mem_cfg_t const cfg_)
|
||||
{
|
||||
return (opera_mem_dram_size(cfg_) + (opera_mem_vram_size(cfg_) * 4));
|
||||
}
|
||||
|
||||
static
|
||||
uint32_t
|
||||
opera_mem_state_payload_size(void)
|
||||
{
|
||||
return (sizeof(uint8_t) +
|
||||
sizeof(uint32_t) +
|
||||
opera_mem_state_ram_bytes(g_MEM_CFG) +
|
||||
NVRAM_SIZE);
|
||||
}
|
||||
|
||||
uint32_t
|
||||
opera_mem_state_size()
|
||||
{
|
||||
return opera_state_chunk_size(sizeof(uint8_t) +
|
||||
MAX_HIRES_RAM_SIZE +
|
||||
ROM1_SIZE +
|
||||
ROM2_SIZE +
|
||||
NVRAM_SIZE);
|
||||
return opera_state_chunk_size(opera_mem_state_payload_size());
|
||||
}
|
||||
|
||||
uint32_t
|
||||
opera_mem_state_save(void *data_)
|
||||
{
|
||||
opera_mem_state_t memstate;
|
||||
uint32_t ram_bytes;
|
||||
opera_state_writer_t writer;
|
||||
|
||||
memstate.mem_cfg = opera_mem_cfg();
|
||||
ram_bytes = opera_mem_state_ram_bytes(g_MEM_CFG);
|
||||
|
||||
opera_state_writer_init(&writer,data_,opera_mem_state_size());
|
||||
opera_state_write_chunk_header(&writer,
|
||||
"MEM",
|
||||
sizeof(memstate.mem_cfg) +
|
||||
MAX_HIRES_RAM_SIZE +
|
||||
ROM1_SIZE +
|
||||
ROM2_SIZE +
|
||||
NVRAM_SIZE);
|
||||
opera_state_write_u8(&writer,memstate.mem_cfg);
|
||||
opera_state_write_bytes(&writer,DRAM,MAX_HIRES_RAM_SIZE);
|
||||
opera_state_write_bytes(&writer,ROM1,ROM1_SIZE);
|
||||
opera_state_write_bytes(&writer,ROM2,ROM2_SIZE);
|
||||
"MEM2",
|
||||
opera_mem_state_payload_size());
|
||||
opera_state_write_u8(&writer,(uint8_t)g_MEM_CFG);
|
||||
opera_state_write_u32(&writer,ram_bytes);
|
||||
opera_state_write_bytes(&writer,DRAM,ram_bytes);
|
||||
opera_state_write_bytes(&writer,NVRAM,NVRAM_SIZE);
|
||||
|
||||
return opera_state_writer_ok(&writer) ? opera_state_writer_used(&writer) : 0;
|
||||
@@ -352,9 +375,10 @@ opera_mem_state_load_v1(void const *data_,
|
||||
return (data - start);
|
||||
}
|
||||
|
||||
static
|
||||
uint32_t
|
||||
opera_mem_state_load(void const *data_,
|
||||
uint32_t const size_)
|
||||
opera_mem_state_load_mem(void const *data_,
|
||||
uint32_t const size_)
|
||||
{
|
||||
uint8_t mem_cfg;
|
||||
opera_state_reader_t reader;
|
||||
@@ -393,3 +417,56 @@ opera_mem_state_load(void const *data_,
|
||||
|
||||
return opera_state_reader_used(&reader);
|
||||
}
|
||||
|
||||
static
|
||||
uint32_t
|
||||
opera_mem_state_load_mem2(void const *data_,
|
||||
uint32_t const size_)
|
||||
{
|
||||
uint8_t mem_cfg;
|
||||
uint32_t ram_bytes;
|
||||
opera_state_reader_t reader;
|
||||
opera_state_reader_t payload;
|
||||
|
||||
opera_state_reader_init(&reader,data_,size_);
|
||||
|
||||
if(!opera_state_read_chunk(&reader,"MEM2",&payload) ||
|
||||
!opera_state_read_u8(&payload,&mem_cfg) ||
|
||||
!opera_mem_is_valid_cfg((opera_mem_cfg_t)mem_cfg) ||
|
||||
!opera_state_read_u32(&payload,&ram_bytes) ||
|
||||
(ram_bytes != opera_mem_state_ram_bytes((opera_mem_cfg_t)mem_cfg)) ||
|
||||
(ram_bytes > MAX_HIRES_RAM_SIZE) ||
|
||||
(opera_state_reader_remaining(&payload) != (ram_bytes + NVRAM_SIZE)))
|
||||
return 0;
|
||||
|
||||
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,ram_bytes) ||
|
||||
!opera_state_read_bytes(&payload,NVRAM,NVRAM_SIZE) ||
|
||||
!opera_state_reader_finished(&payload))
|
||||
return 0;
|
||||
|
||||
_setup_dram_vram((opera_mem_cfg_t)mem_cfg);
|
||||
g_MEM_CFG = (opera_mem_cfg_t)mem_cfg;
|
||||
|
||||
return opera_state_reader_used(&reader);
|
||||
}
|
||||
|
||||
/* MEM2 from V4 states, MEM from V3; the chunk name says which. */
|
||||
uint32_t
|
||||
opera_mem_state_load(void const *data_,
|
||||
uint32_t const size_)
|
||||
{
|
||||
uint8_t const *name = (uint8_t const*)data_;
|
||||
|
||||
if((size_ >= 4) && !memcmp(name,"MEM2",4))
|
||||
return opera_mem_state_load_mem2(data_,size_);
|
||||
|
||||
return opera_mem_state_load_mem(data_,size_);
|
||||
}
|
||||
@@ -8,7 +8,10 @@
|
||||
#define OPERA_STATE_VERSION_V1 0x01
|
||||
#define OPERA_STATE_VERSION_V2 0x02
|
||||
#define OPERA_STATE_VERSION_V3 0x03
|
||||
#define OPERA_STATE_VERSION_CURRENT OPERA_STATE_VERSION_V3
|
||||
/* V4: the MEM chunk became MEM2 -- only the RAM the memory
|
||||
* configuration uses, and no ROMs. Otherwise laid out as V3. */
|
||||
#define OPERA_STATE_VERSION_V4 0x04
|
||||
#define OPERA_STATE_VERSION_CURRENT OPERA_STATE_VERSION_V4
|
||||
|
||||
typedef struct opera_state_data_t opera_state_data_t;
|
||||
struct opera_state_data_t
|
||||
|
||||
Reference in new issue
Block a user