From fef3de8bd9de03a101d5882ca14d0fcb52aa7e02 Mon Sep 17 00:00:00 2001 From: libretroadmin Date: Sun, 14 Jun 2026 18:17:57 +0000 Subject: [PATCH] libretro: relax retro_unserialize size check to permit cross-version state loads The previous `size != retro_serialize_size()` guard rejected any state whose serialized length didn't exactly match the current build's SFORMAT-derived size. That sounds defensive, but the file format already carries its own 16-byte header with an explicit `totalsize` and `ReadStateChunk()` already skips unknown chunk tags safely (size- bounded `memstream_seek` past the payload), so the outer check adds no parser safety - it just turns every SFORMAT change into a hard break of existing saves with no user recourse. This came up in the recent FDS audio rewrite for #560: the backport added/removed audio-register chunk tags, leaving pre-rewrite states 240 bytes smaller than the new core's expected size, and users with in-progress FDS games hit "unserialize FAIL" on first reload after updating the core. Accept any buffer that's at least the 16-byte header and at most 4x the current serialize size (a sanity cap against pathological input). ReadStateChunk does the actual work of validating each chunk's size against the header's totalsize, and the FCS magic in the header rejects non-fceumm states. Verified on Vs. Excitebike (Japan): - pre-rewrite state -> new core: load OK, audio plays normally - new-rewrite state -> new core: round-trip OK (same audio as before) - 15-byte input: rejected (< header size) - 4 MB input: rejected (> 4 * serialize_size) --- src/drivers/libretro/libretro.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/drivers/libretro/libretro.c b/src/drivers/libretro/libretro.c index fde6342..a48a42c 100644 --- a/src/drivers/libretro/libretro.c +++ b/src/drivers/libretro/libretro.c @@ -3185,7 +3185,17 @@ bool retro_unserialize(const void * data, size_t size) if (geniestage == 1) return false; - if (!data || size != retro_serialize_size()) + /* The state file's own 16-byte header carries an explicit totalsize + * and ReadStateChunk already skips unknown chunk tags, so a strict + * size-equality check against the current build's serialize_size is + * not necessary for parser safety - and is actively harmful when + * SFORMAT contents change between builds (recent example: the FDS + * audio rewrite for #560 added/removed chunk tags, leaving older + * savestates a fixed delta smaller than the new core's expected + * size, with no recourse for the user). Accept any buffer at least + * as large as the header; cap the upper end to a generous multiple + * of the current size as a sanity guard against pathological input. */ + if (!data || size < 16 || size > retro_serialize_size() * 4) return false; memstream_set_buffer((uint8_t*)data, size);