diff --git a/cpu.c b/cpu.c index 1c5d4c2..bd3767a 100644 --- a/cpu.c +++ b/cpu.c @@ -3711,6 +3711,19 @@ void init_cpu(void) REG_MODE(MODE_SUPERVISOR)[5] = 0x03007FE0; } +bool cpu_check_savestate(const u8 *src) +{ + const u8 *cpudoc = bson_find_key(src, "cpu"); + if (!cpudoc) + return false; + + return bson_contains_key(cpudoc, "bus-value", BSON_TYPE_INT32) && + bson_contains_key(cpudoc, "regs", BSON_TYPE_ARR) && + bson_contains_key(cpudoc, "spsr", BSON_TYPE_ARR) && + bson_contains_key(cpudoc, "regmod", BSON_TYPE_ARR); +} + + bool cpu_read_savestate(const u8 *src) { const u8 *cpudoc = bson_find_key(src, "cpu"); diff --git a/cpu.h b/cpu.h index 99285ed..0887a98 100644 --- a/cpu.h +++ b/cpu.h @@ -125,6 +125,8 @@ void function_cc execute_store_u32(u32 address, u32 source); void function_cc execute_store_aligned_u32(u32 address, u32 source); u32 execute_arm_translate(u32 cycles); void init_translater(void); + +bool cpu_check_savestate(const u8 *src); unsigned cpu_write_savestate(u8* dst); bool cpu_read_savestate(const u8 *src); diff --git a/gba_memory.c b/gba_memory.c index b832564..a14a40c 100644 --- a/gba_memory.c +++ b/gba_memory.c @@ -2524,6 +2524,58 @@ void memory_term(void) } } +bool memory_check_savestate(const u8 *src) +{ + static const char *vars32[] = { + "backup-type", "sram-size", + "flash-mode", "flash-cmd-pos", "flash-bank-num", "flash-dev-id", "flash-size", + "eeprom-size", "eeprom-mode", "eeprom-addr", "eeprom-counter", + "rtc-state", "rtc-write-mode", "rtc-cmd", "rtc-status", "rtc-data-byte-cnt", "rtc-bit-cnt", + }; + static const char *dmavars32[] = { + "src-addr", "dst-addr", "src-dir", "dst-dir", + "len", "size", "repeat", "start", "dsc", "irq" + }; + int i; + const u8 *memdoc = bson_find_key(src, "memory"); + const u8 *bakdoc = bson_find_key(src, "backup"); + const u8 *dmadoc = bson_find_key(src, "dma"); + if (!memdoc || !bakdoc || !dmadoc) + return false; + + // Check memory buffers (TODO: check sizes!) + if (!bson_contains_key(memdoc, "iwram", BSON_TYPE_BIN) || + !bson_contains_key(memdoc, "ewram", BSON_TYPE_BIN) || + !bson_contains_key(memdoc, "vram", BSON_TYPE_BIN) || + !bson_contains_key(memdoc, "oamram", BSON_TYPE_BIN) || + !bson_contains_key(memdoc, "palram", BSON_TYPE_BIN) || + !bson_contains_key(memdoc, "ioregs", BSON_TYPE_BIN)) + return false; + + // Check backup variables + for (i = 0; i < sizeof(vars32)/sizeof(vars32[0]); i++) + if (!bson_contains_key(bakdoc, vars32[i], BSON_TYPE_INT32)) + return false; + + if (!bson_contains_key(bakdoc, "rtc-regs", BSON_TYPE_BIN) || + !bson_contains_key(bakdoc, "rtc-data-words", BSON_TYPE_ARR)) + return false; + + for (i = 0; i < DMA_CHAN_CNT; i++) + { + char tname[2] = {'0' + i, 0}; + const u8 *dmastr = bson_find_key(dmadoc, tname); + if (!dmastr) + return false; + + for (i = 0; i < sizeof(dmavars32)/sizeof(dmavars32[0]); i++) + if (!bson_contains_key(dmastr, dmavars32[i], BSON_TYPE_INT32)) + return false; + } + return true; +} + + bool memory_read_savestate(const u8 *src) { int i; diff --git a/gba_memory.h b/gba_memory.h index 2316f4e..656fd98 100644 --- a/gba_memory.h +++ b/gba_memory.h @@ -318,7 +318,8 @@ static inline void clear_gamepak_stickybits(void) memset(gamepak_sticky_bit, 0, sizeof(gamepak_sticky_bit)); } -unsigned memory_write_savestate(u8 *dst); +bool memory_check_savestate(const u8*src); bool memory_read_savestate(const u8*src); +unsigned memory_write_savestate(u8 *dst); #endif diff --git a/input.c b/input.c index 4a81a78..23b5069 100644 --- a/input.c +++ b/input.c @@ -139,6 +139,12 @@ u32 update_input(void) return 0; } +bool input_check_savestate(const u8 *src) +{ + const u8 *p = bson_find_key(src, "input"); + return (p && bson_contains_key(p, "prevkey", BSON_TYPE_INT32)); +} + bool input_read_savestate(const u8 *src) { const u8 *p = bson_find_key(src, "input"); diff --git a/input.h b/input.h index cc6b3e0..b8b11e8 100644 --- a/input.h +++ b/input.h @@ -75,6 +75,8 @@ extern unsigned turbo_b_counter; void init_input(void); u32 update_input(void); + +bool input_check_savestate(const u8 *src); unsigned input_write_savestate(u8* dst); bool input_read_savestate(const u8 *src); diff --git a/main.c b/main.c index f345894..b912bb7 100644 --- a/main.c +++ b/main.c @@ -284,6 +284,39 @@ void print_regs(void) } #endif +bool main_check_savestate(const u8 *src) +{ + int i; + const u8 *p1 = bson_find_key(src, "emu"); + const u8 *p2 = bson_find_key(src, "timers"); + if (!p1 || !p2) + return false; + + if (!bson_contains_key(p1, "cpu-ticks", BSON_TYPE_INT32) || + !bson_contains_key(p1, "exec-cycles", BSON_TYPE_INT32) || + !bson_contains_key(p1, "video-count", BSON_TYPE_INT32)) + return false; + + for (i = 0; i < 4; i++) + { + char tname[2] = {'0' + i, 0}; + const u8 *p = bson_find_key(p2, tname); + if (!p) + return false; + + if (!bson_contains_key(p, "count", BSON_TYPE_INT32) || + !bson_contains_key(p, "reload", BSON_TYPE_INT32) || + !bson_contains_key(p, "prescale", BSON_TYPE_INT32) || + !bson_contains_key(p, "freq-step", BSON_TYPE_INT32) || + !bson_contains_key(p, "dsc", BSON_TYPE_INT32) || + !bson_contains_key(p, "irq", BSON_TYPE_INT32) || + !bson_contains_key(p, "status", BSON_TYPE_INT32)) + return false; + } + + return true; +} + bool main_read_savestate(const u8 *src) { int i; @@ -311,7 +344,7 @@ bool main_read_savestate(const u8 *src) bson_read_int32(p, "irq", &timer[i].irq) && bson_read_int32(p, "status", &timer[i].status))) return false; - } + } return true; } diff --git a/main.h b/main.h index 047a30f..33ca5aa 100644 --- a/main.h +++ b/main.h @@ -85,6 +85,8 @@ void reset_gba(void); void init_main(void); void game_name_ext(char *src, char *buffer, char *extension); + +bool main_check_savestate(const u8 *src); unsigned main_write_savestate(u8* ptr); bool main_read_savestate(const u8 *src); diff --git a/savestate.c b/savestate.c index 36c64e8..4cdcb3b 100644 --- a/savestate.c +++ b/savestate.c @@ -1,9 +1,48 @@ +/* gameplaySP + * + * Copyright (C) 2023 David Guillen Fandos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ #include "common.h" const u8 *state_mem_read_ptr; u8 *state_mem_write_ptr; +bool bson_contains_key(const u8 *srcp, const char *key, u8 keytype) +{ + 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 tp == keytype; // Found it, check type + p += 1 + tlen; + if (tp == BSON_TYPE_DOC || tp == BSON_TYPE_ARR) + p += bson_read_u32(p); + else if (tp == BSON_TYPE_BIN) + p += bson_read_u32(p) + 1 + 4; + else if (tp == BSON_TYPE_INT32) + p += 4; + } + return false; +} + const u8* bson_find_key(const u8 *srcp, const char *key) { unsigned keyl = strlen(key) + 1; @@ -15,11 +54,11 @@ const u8* bson_find_key(const u8 *srcp, const char *key) if (keyl == tlen && !memcmp(key, &p[1], tlen)) return &p[tlen + 1]; p += 1 + tlen; - if (tp == 3 || tp == 4) + if (tp == BSON_TYPE_DOC || tp == BSON_TYPE_ARR) p += bson_read_u32(p); - else if (tp == 5) + else if (tp == BSON_TYPE_BIN) p += bson_read_u32(p) + 1 + 4; - else if (tp == 0x10) + else if (tp == BSON_TYPE_INT32) p += 4; } return NULL; @@ -82,13 +121,21 @@ bool gba_load_state(const void* src) if (!bson_read_int32(srcptr, "info-version", &tmp) || tmp != GBA_STATE_VERSION) return false; + // Validate that the state file makes sense before unconditionally reading it. + if (!cpu_check_savestate(srcptr) || + !input_check_savestate(srcptr) || + !main_check_savestate(srcptr) || + !memory_check_savestate(srcptr) || + !sound_check_savestate(srcptr)) + 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?? + // TODO: this should not happen if the validation above is accurate. return false; } diff --git a/savestate.h b/savestate.h index 92f5141..2fce2b8 100644 --- a/savestate.h +++ b/savestate.h @@ -1,7 +1,31 @@ +/* gameplaySP + * + * Copyright (C) 2023 David Guillen Fandos + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ #ifndef SAVESTATE_H #define SAVESTATE_H +#define BSON_TYPE_STR 0x02 +#define BSON_TYPE_DOC 0x03 +#define BSON_TYPE_ARR 0x04 +#define BSON_TYPE_BIN 0x05 +#define BSON_TYPE_INT32 0x10 + #define bson_write_u32(p, value) \ { \ u32 __tval = (value); \ @@ -59,6 +83,7 @@ bson_write_u32(hdrptr, _sz); \ } +bool bson_contains_key(const u8 *srcp, const char *key, u8 keytype); const u8* bson_find_key(const u8 *srcp, const char *key); bool bson_read_int32(const u8 *srcp, const char *key, u32* value); bool bson_read_int32_array(const u8 *srcp, const char *key, u32* value, unsigned cnt); diff --git a/sound.c b/sound.c index 2c07f27..822ea2c 100644 --- a/sound.c +++ b/sound.c @@ -586,6 +586,67 @@ void init_sound() reset_sound(); } + +bool sound_check_savestate(const u8 *src) +{ + static const char *gvars[] = { + "on", "buf-base", "gbc-buf-idx", "gbc-last-cpu-ticks", + "gbc-partial-ticks", "gbc-ms-vol-left", "gbc-ms-vol-right", "gbc-ms-vol" + }; + static const char *dsvars[] = { + "status", "volume", "fifo-base", "fifo-top", "fifo-frac", "buf-idx" + }; + static const char *gsvars[] = { + "status", "rate", "freq-step", "sample-idx", "tick-cnt", "volume", + "active", "enable", "env-vol0", "env-vol", "env-dir", "env-status", + "env-ticks0", "env-ticks", "sweep-status", "sweep-dir", "sweep-ticks0", + "sweep-ticks","sweep-shift", "wav-type", "wav-bank", "wav-vol", + "len-status", "len-ticks", "noise-type", "sample-tbl" + }; + + int i; + const u8 *snddoc = bson_find_key(src, "sound"); + if (!snddoc) + return false; + + for (i = 0; i < sizeof(gvars)/sizeof(gvars[0]); i++) + if (!bson_contains_key(snddoc, gvars[i], BSON_TYPE_INT32)) + return false; + if (!bson_contains_key(snddoc, "wav-samples", BSON_TYPE_BIN)) + return false; + + + for (i = 0; i < 2; i++) + { + char tn[4] = {'d', 's', '0' + i, 0}; + const u8 *sndchan = bson_find_key(snddoc, tn); + if (!sndchan) + return false; + + for (i = 0; i < sizeof(dsvars)/sizeof(dsvars[0]); i++) + if (!bson_contains_key(sndchan, dsvars[i], BSON_TYPE_INT32)) + return false; + + if (!bson_contains_key(sndchan, "fifo-bytes", BSON_TYPE_BIN)) + return false; + } + + for (i = 0; i < 4; i++) + { + char tn[4] = {'g', 's', '0' + i, 0}; + const u8 *sndchan = bson_find_key(snddoc, tn); + if (!sndchan) + return false; + + for (i = 0; i < sizeof(gsvars)/sizeof(gsvars[0]); i++) + if (!bson_contains_key(sndchan, gsvars[i], BSON_TYPE_INT32)) + return false; + } + + return true; +} + + bool sound_read_savestate(const u8 *src) { int i; diff --git a/sound.h b/sound.h index 7bfa3f8..c6541de 100644 --- a/sound.h +++ b/sound.h @@ -102,6 +102,8 @@ unsigned sound_timer(fixed8_24 frequency_step, u32 channel); void sound_reset_fifo(u32 channel); void render_gbc_sound(); void init_sound(); + +bool sound_check_savestate(const u8 *src); unsigned sound_write_savestate(u8 *dst); bool sound_read_savestate(const u8 *src);