gpsp/savestate.c
David Guillen Fandos 7068cbc95b New savestate implementation
This uses BSON as savestate format, to allow external tools to parse it
(so that we can add proper test of the states). The BSON is not 100%
correct according to spec (no ordered keys) but can be parsed by most
libraries.

This fixes also a bug in the savestate palette color recalculation that
was wrongly overwritting the original palette (which could cause some
problems on some games).

Also fixes some potential issues by serializing some more stuff and
cleans up unused stuff.

Testing shows that states look good and there's only minor differences
in audio ticks, related to buffer sizes (since buffer flushes are
de-synced from video frames due to different frequency).
2021-08-24 17:15:27 +02:00

153 lines
3.5 KiB
C

#include "common.h"
const u8 *state_mem_read_ptr;
u8 *state_mem_write_ptr;
const u8* bson_find_key(const u8 *srcp, const char *key)
{
unsigned keyl = strlen(key) + 1;
unsigned doclen = bson_read_u32(srcp);
const u8* p = &srcp[4];
while (*p != 0 && (p - srcp) < doclen) {
u8 tp = *p;
unsigned tlen = strlen((char*)&p[1]) + 1;
if (keyl == tlen && !memcmp(key, &p[1], tlen))
return &p[tlen + 1];
p += 1 + tlen;
if (tp == 3 || tp == 4)
p += bson_read_u32(p);
else if (tp == 5)
p += bson_read_u32(p) + 1 + 4;
else if (tp == 0x10)
p += 4;
}
return NULL;
}
bool bson_read_int32(const u8 *srcp, const char *key, u32* value)
{
const u8* p = srcp ? bson_find_key(srcp, key) : NULL;
if (!p)
return false;
*value = bson_read_u32(p);
return true;
}
bool bson_read_int32_array(const u8 *srcp, const char *key, u32* value, unsigned cnt)
{
const u8* p = srcp ? bson_find_key(srcp, key) : NULL;
if (p) {
unsigned arrsz = bson_read_u32(p);
p += 4;
if (arrsz < 5)
return false;
arrsz = (arrsz - 5) >> 3;
while (arrsz--) {
p += 4; // type and name
*value++ = bson_read_u32(p);
p += 4; // value
}
return true;
}
*value = bson_read_u32(p);
return false;
}
bool bson_read_bytes(const u8 *srcp, const char *key, void* buffer, unsigned cnt)
{
const u8* p = srcp ? bson_find_key(srcp, key) : NULL;
if (p) {
unsigned bufsz = bson_read_u32(p);
if (bufsz != cnt)
return false;
// Skip byte array type and size
memcpy(buffer, &p[5], cnt);
return true;
}
return false;
}
bool gba_load_state(const void* src)
{
u32 i, tmp;
u8* srcptr = (u8*)src;
u32 docsize = bson_read_u32(srcptr);
if (docsize != GBA_STATE_MEM_SIZE)
return false;
if (!bson_read_int32(srcptr, "info-magic", &tmp) || tmp != GBA_STATE_MAGIC)
return false;
if (!bson_read_int32(srcptr, "info-version", &tmp) || tmp != GBA_STATE_VERSION)
return false;
if (!(cpu_read_savestate(srcptr) &&
input_read_savestate(srcptr) &&
main_read_savestate(srcptr) &&
memory_read_savestate(srcptr) &&
sound_read_savestate(srcptr)))
{
// TODO: reset state instead! Should revert instead??
return false;
}
// Generate converted palette (since it is not saved)
for(i = 0; i < 512; i++)
{
palette_ram_converted[i] = convert_palette(palette_ram[i]);
}
video_reload_counters();
// Reset most of the frame state and dynarec state
#ifdef HAVE_DYNAREC
if (dynarec_enable)
init_caches();
#endif
instruction_count = 0;
reg[CHANGED_PC_STATUS] = 1;
reg[COMPLETED_FRAME] = 0;
reg[OAM_UPDATED] = 1;
gbc_sound_update = 1;
return true;
}
void gba_save_state(void* dst)
{
u8 *stptr = (u8*)dst;
u8 *wrptr = (u8*)dst;
// Initial bson size
bson_write_u32(wrptr, 0);
// Add some info fields
bson_write_int32(wrptr, "info-magic", GBA_STATE_MAGIC);
bson_write_int32(wrptr, "info-version", GBA_STATE_VERSION);
wrptr += cpu_write_savestate(wrptr);
wrptr += input_write_savestate(wrptr);
wrptr += main_write_savestate(wrptr);
wrptr += memory_write_savestate(wrptr);
wrptr += sound_write_savestate(wrptr);
// The padding space is pushed into a padding field for easy parsing
{
unsigned padsize = GBA_STATE_MEM_SIZE - (wrptr - stptr);
padsize -= 1 + 9 + 4 + 1 + 1;
*wrptr++ = 0x05; // Byte array
bson_write_cstring(wrptr, "zpadding");
bson_write_u32(wrptr, padsize);
*wrptr++ = 0;
wrptr += padsize;
}
*wrptr++ = 0;
// Update the doc size
bson_write_u32(stptr, wrptr - stptr);
}