-- A real Gen 2 cart save is 32786 bytes, and that must not be held against it -- (#1832). -- luajit tests/gen2_save_import_message_test.lua -- Also dofile'd by tests/run_tests.lua. -- -- Gen 2 carts are MBC3+TIMER, so a real Gold/Silver/Crystal battery save -- carries an RTC footer past the 32768 bytes of SRAM. SaveFileIO.importToSlot -- judges anything that is not exactly SAVE_SIZE, and it used to judge it with -- pokered's main-data checksum whatever game it was for, so every real Gen 2 -- save came back "save data checksum invalid" -- a perfectly good save -- reported as corrupt. -- -- This is the size gate specifically. tests/gen2_save_import_test.lua covers -- the codec. package.path = "./?.lua;./?/init.lua;" .. package.path love = love or require("tests.love_stub") -- SaveData is stubbed so this stays about the gate: the real one wants a -- filesystem and a registry, and neither is the subject here. local written = {} package.loaded["src.core.SaveData"] = { load = function() return { meta = {} } end, activeSlot = function() return "slot1" end, buildMeta = function(_, m) return m or {} end, createSlot = function() return "slot1" end, writeSlot = function(_, _, save) written[#written + 1] = save return true end, setActiveSlot = function() return true end, } local T = require("tests.harness") local check, eq = T.check, T.eq local Gen2Save = require("src.save_convert.Gen2Save") local SaveConvert = require("src.save_convert.SaveConvert") local SaveFileIO = require("src.import.SaveFileIO") -- The size a real Gen 2 cart save actually is: 32768 of SRAM plus an 18-byte -- RTC footer. Verified against a real Gold cartridge in an emulator core, -- which reports the cart's backup size as exactly this. local GEN2_CART_SAVE_SIZE = 32786 local function validSave(version, trailing) local L = Gen2Save.layoutFor(version) local b = {} for i = 0, Gen2Save.SAVE_SIZE - 1 do b[i] = 0 end b[L.wPlayerName] = 0x80 -- "A", so the save is not entirely blank b[L.wPlayerName + 1] = 0x50 b[L.sCheckValue1] = 0x63 b[L.sCheckValue2] = 0x7F local sum = 0 for i = L.sGameData, L.sGameDataEnd - 1 do sum = (sum + b[i]) % 65536 end b[L.sChecksum] = sum % 256 b[L.sChecksum + 1] = math.floor(sum / 256) % 256 local out = {} for i = 0, Gen2Save.SAVE_SIZE - 1 do out[i + 1] = string.char(b[i]) end return table.concat(out) .. string.rep("\0", trailing or 0) end local function savFile(bytes) local path = os.tmpname() local f = assert(io.open(path, "wb")) f:write(bytes) f:close() return path end -- ------------------------------------------------------------------ -- The report: a real-sized Gen 2 save imports -- ------------------------------------------------------------------ for _, version in ipairs({ "gold", "silver", "crystal" }) do local trailing = GEN2_CART_SAVE_SIZE - Gen2Save.SAVE_SIZE local path = savFile(validSave(version, trailing)) written = {} -- force is deliberately NOT passed: the footer is the normal shape of a Gen -- 2 cart save, so it must not raise the oversize confirmation either. local ok, err = SaveFileIO.importToSlot(path, version) check(ok == true, version .. ": a 32786-byte cart save imports -- " .. tostring(err)) check(type(err) ~= "string" or err:find("checksum", 1, true) == nil, version .. ": and is never blamed on a checksum -- " .. tostring(err)) eq(#written, 1, version .. ": the slot is written") end -- ------------------------------------------------------------------ -- The checksum question is asked of the right generation -- ------------------------------------------------------------------ do local gen2 = validSave("gold") eq(SaveConvert.mainChecksumValid(gen2, "gold"), true, "a Gen 2 save is valid under Gen 2's rule") eq(SaveConvert.mainChecksumValid(gen2), false, "and would read as invalid under Gen 1's, which is the whole bug") check(SaveConvert.isGen2Cart("crystal"), "Crystal is a Gen 2 cart") check(not SaveConvert.isGen2Cart("red"), "Red is not") end -- ------------------------------------------------------------------ -- Gen 1 keeps its own diagnosis -- ------------------------------------------------------------------ do local path = savFile(string.rep("\0", GEN2_CART_SAVE_SIZE)) local ok, err = SaveFileIO.importToSlot(path, "red", true) check(ok == false, "red: a corrupt oversize save is still refused") check(type(err) == "string" and err:find("checksum", 1, true) ~= nil, "red: still diagnosed by pokered's checksum -- got: " .. tostring(err)) end -- ------------------------------------------------------------------ -- Export is still one-way, and still says so -- ------------------------------------------------------------------ for _, version in ipairs({ "gold", "silver", "crystal" }) do local ok, why = SaveConvert.exportSupported(version) eq(ok, false, version .. ": export is still refused") check(type(why) == "string" and why:find("exporting", 1, true) ~= nil, version .. ": and the sentence is about exporting -- " .. tostring(why)) end T.finish()