Validate save imports by main-data checksum, not raw file size

The SAVE FILES card only accepted saves of exactly 32768 bytes and
refused anything else. importToSlot now classifies a non-32768 file by
the integrity of its main-data checksum instead:

- Oversize + valid checksum -> an emulator RTC footer, so the launcher
  asks for confirmation, then truncates to 32768 on force.
- Oversize + invalid checksum -> rejected.
- Undersize + valid checksum -> imports zero-padded; otherwise refused.

Adds the "Oversized save file" confirm modal, a new vendor-oracle test
built by gen1lib (PKHeX-derived) run as its own Lua 5.4 tier, oversize/
truncated policy tests, and the LUA54 wiring in test.sh.

# Conflicts:
#	src/import/LauncherView.lua
This commit is contained in:
kikimanjaro
2026-08-04 22:34:43 +02:00
parent 0fe7a96781
commit 272305f3a4
8 changed files with 352 additions and 10 deletions
+24 -4
View File
@@ -1564,7 +1564,7 @@ end
-- the target tab forward so the notice (and, on success, the new active slot)
-- is visible. Requires the ROM to be imported first, since a save is only
-- playable with its game's data present.
function RomImporter:_importSave(version, source)
function RomImporter:_importSave(version, source, force)
if self.workState == "working" then return end
if GameVersion.VERSIONS[self.tab] or self.tab == "mods" then
self.tab = version
@@ -1574,15 +1574,35 @@ function RomImporter:_importSave(version, source)
.. GameVersion.info(version).displayName .. " ROM before importing a save." }
return
end
local ok, res = require("src.import.SaveFileIO").importToSlot(source, version)
local ok, res, info = require("src.import.SaveFileIO").importToSlot(source, version, force)
if ok then
self:_refreshSlots(version)
self.activeSlot[version] = res
self.slotScroll[version] = math.huge -- pin the new row on screen (clamped in draw)
self.saveNotice[version] = { ok = true, text = "Imported save into " .. tostring(res) .. "." }
else
self.saveNotice[version] = { ok = false, text = tostring(res) }
return
end
if res == nil and info and info.needsConfirm then
-- A .sav larger than 32 KB whose first 32768 bytes checksum: the surplus
-- is almost certainly an emulator RTC footer, so ask before truncating.
-- The yes arm re-enters with force=true; cancel leaves the file untouched.
self._modConfirm = {
kind = "importOversize",
version = version,
source = source,
title = "Oversized save file",
lines = {
("This save is %d bytes; a cartridge save is exactly %d bytes (32 KB).")
:format(info.size, 32768),
"It may come from a ROM that saved the battery image with an emulator.",
"The extra bytes would be discarded.",
"Import it anyway?",
},
yesLabel = "Import anyway",
}
return
end
self.saveNotice[version] = { ok = false, text = tostring(res) }
end
-- "Import save" button: open a native .sav picker and import the pick.