diff --git a/libopera/opera_3do.c b/libopera/opera_3do.c index bb139a5..d50210b 100644 --- a/libopera/opera_3do.c +++ b/libopera/opera_3do.c @@ -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", diff --git a/libopera/opera_dsp.c b/libopera/opera_dsp.c index ed7f70c..7972c2e 100644 --- a/libopera/opera_dsp.c +++ b/libopera/opera_dsp.c @@ -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) diff --git a/libopera/opera_mem.c b/libopera/opera_mem.c index 00e5f1d..724b105 100644 --- a/libopera/opera_mem.c +++ b/libopera/opera_mem.c @@ -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_); +} diff --git a/libopera/opera_state.h b/libopera/opera_state.h index 31a7cc6..e6e8825 100644 --- a/libopera/opera_state.h +++ b/libopera/opera_state.h @@ -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