From bbe894fb4fe52b3e50144e566af012e10d739935 Mon Sep 17 00:00:00 2001 From: johnjohto Date: Wed, 29 Jul 2026 14:01:19 -0400 Subject: [PATCH] keep imported save cache on export Red restores the current-map cache from SRAM before Continue. Dropping the import template made a later export boot with zeroed map data. --- src/save_convert/SaveConvert.lua | 9 ++++----- tests/engine/save_file_io_tests.lua | 11 ++++++++++- tests/save_convert_tests.lua | 7 ++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/save_convert/SaveConvert.lua b/src/save_convert/SaveConvert.lua index e908abea..b9b050df 100644 --- a/src/save_convert/SaveConvert.lua +++ b/src/save_convert/SaveConvert.lua @@ -116,13 +116,12 @@ local function defaultsSave() end -- Merge a GenSave.decode() result over the new-game defaults, exactly the --- way convert.lua did, then stamp the requested version. The 32768-byte --- import template GenSave stashes as `rawImport` and the decode `warnings` --- are dropped here: neither belongs in a serialized slot file (a fresh --- export always starts zero-filled -- see GenSave.lua's header). +-- way convert.lua did, then stamp the requested version. Keep the imported +-- SRAM image with the slot: Pokémon Red restores its saved current-map cache +-- before Continue, and an export needs that unmodeled data to remain bootable. +-- Decode warnings are only import diagnostics and do not belong in the slot. local function mergeDefaults(decoded, version) decoded.warnings = nil - decoded.rawImport = nil local save = defaultsSave() for k, v in pairs(decoded) do save[k] = v end save.lastHeal = { map = save.player.map, x = save.player.x, y = save.player.y } diff --git a/tests/engine/save_file_io_tests.lua b/tests/engine/save_file_io_tests.lua index bf6b0e2a..6645c7c9 100644 --- a/tests/engine/save_file_io_tests.lua +++ b/tests/engine/save_file_io_tests.lua @@ -96,7 +96,14 @@ local function syntheticSave(name) moves = { { id = "TACKLE", pp = 35, ppUps = 0 } }, nickname = "SQ", ot = name, otId = seed.player.id, catchRate = 45, } } - return GenSave.encode(seed, data, nil) + -- The current-map view pointer is part of wMainData's map cache, not a + -- modeled save field. Pokémon Red restores that cache before Continue, + -- so it is a useful canary for the import -> slot -> export path. + local raw = GenSave.encode(seed, data, nil) + local cacheOff = OFF.mainData + 104 + local cacheTemplate = raw:sub(1, cacheOff) .. string.char(0xA5) + .. raw:sub(cacheOff + 2) + return GenSave.encode(seed, data, cacheTemplate) end -- ---------------------------------------------- importToSlot -> listSlots @@ -149,6 +156,8 @@ do eq(outBytes and #outBytes, GenSave.SAVE_SIZE, "the export is exactly 32768 bytes") check(outBytes and mainChecksumValid(outBytes), "the export carries a valid main-data checksum") + eq(outBytes and outBytes:byte(OFF.mainData + 105), 0xA5, + "the export keeps the saved current-map cache") -- the export re-imports to an equivalent save local re = SaveConvert.importSav(outBytes, "red") diff --git a/tests/save_convert_tests.lua b/tests/save_convert_tests.lua index 29c77e18..a775f0c4 100644 --- a/tests/save_convert_tests.lua +++ b/tests/save_convert_tests.lua @@ -297,9 +297,10 @@ check(scSave and scSave.lastHeal and scSave.lastHeal.map == scSave.player.map, "SaveConvert.importSav: lastHeal derives from the decoded position") check(scSave and scSave.lastOutdoor and scSave.lastOutdoor.id ~= nil, "SaveConvert.importSav: lastOutdoor is set") --- the import template + decode warnings never leak into the slot table -check(scSave and scSave.rawImport == nil and scSave.warnings == nil, - "SaveConvert.importSav: rawImport/warnings stripped from the returned table") +-- The original SRAM image carries the current-map cache that Red restores on +-- Continue, while decode warnings are only import diagnostics. +check(scSave and type(scSave.rawImport) == "string" and scSave.warnings == nil, + "SaveConvert.importSav: keeps the SRAM template but drops warnings") -- size / type validation local badSize, badSizeErr = SaveConvert.importSav("too short", 2)