Files
gen1recomp/tests/save_compat/gen3_entry_paths_test.lua
T
2026-10-01 17:30:52 -04:00

158 lines
6.3 KiB
Lua

package.path = "./?.lua;./?/init.lua;" .. package.path
love = love or require("tests.love_stub")
local T = require("tests.harness")
local check, eq = T.check, T.eq
local K = require("tests.save_compat._codec")
local G3 = require("tests.fixtures.save.gen3_build")
local B = require("tests.fixtures.save.bytes")
local Gen3Save = require("src.save_convert.Gen3Save")
local SaveConvert = require("src.save_convert.SaveConvert")
local SaveFileIO = require("src.import.SaveFileIO")
local SaveData = require("src.core.SaveData")
local GameVersion = require("src.core.GameVersion")
local fixtures = {}
for _, c in ipairs(G3.cases()) do fixtures[c.id] = c end
local function memfs(files)
return {
files = files,
write = function(path, content) files[path] = content return true end,
read = function(path) return files[path] end,
remove = function(path) files[path] = nil return true end,
getInfo = function(path)
if files[path] then return { type = "file" } end
local prefix = path .. "/"
for key in pairs(files) do
if key:sub(1, #prefix) == prefix then return { type = "directory" } end
end
return nil
end,
createDirectory = function() return true end,
getSaveDirectory = function() return "/fake/save" end,
}
end
local realFs = love.filesystem
local function freshFs(version)
local files = {}
love.filesystem = memfs(files)
SaveData.resetSlotState()
GameVersion.set(version)
return files
end
local function rsImage(version)
local w = G3.base("emerald")
for i = 0x890, w.sb2.size - 1 do w.sb2[i] = 0 end
for i = 0x3AC0, w.sb1.size - 1 do w.sb1[i] = 0 end
return G3.emit(w)
end
local function withMons(version, metGame, matchOwner)
local w = G3.base(version)
local tid, sid = B.getLE(w.sb2, 0x0A, 2), B.getLE(w.sb2, 0x0C, 2)
local list = {}
for i = 1, 3 do
list[i] = G3.mon({
pid = 0x01010101 * i, tid = matchOwner and tid or (tid + 1) % 65536, sid = matchOwner and sid or (sid + 1) % 65536,
origins = 5 + metGame * 128 + 4 * 2048, species = 4 + i, nick = "M" .. i,
})
end
B.fill(w.storage, 4, 14 * 30 * 80, 0)
G3.setParty(w, list)
return G3.emit(w)
end
local function importAs(bytes, version)
return SaveConvert.importSav(bytes, version, version)
end
for _, cfg in ipairs({
{ version = "firered", met = 4, want = "firered" },
{ version = "firered", met = 5, want = "leafgreen" },
{ version = "leafgreen", met = 4, want = "firered" },
{ version = "leafgreen", met = 5, want = "leafgreen" },
}) do
local bytes = withMons(cfg.version, cfg.met, true)
local s = assert(importAs(bytes, cfg.version))
eq(s.modData.cartGame, cfg.want, ("%s slot of a cart whose own mons were met in game %d is marked %s"):format(cfg.version, cfg.met, cfg.want))
local traded = withMons(cfg.version, cfg.met == 4 and 5 or 4, false)
local s2 = assert(importAs(traded, cfg.version))
eq(s2.modData.cartGame, cfg.version, cfg.version .. ": mons the player did not catch never decide the marking")
local fresh = assert(importAs(fixtures["g3." .. cfg.version .. ".basic"].bytes, cfg.version))
check(fresh.modData.cartGame == "firered" or fresh.modData.cartGame == "leafgreen",
cfg.version .. ": a cart without own mons is still marked FireRed or LeafGreen (" .. tostring(fresh.modData.cartGame) .. ")")
end
do
local s = assert(importAs(fixtures["g3.emerald.basic"].bytes, "emerald"))
eq(s.modData.cartGame, "emerald", "an Emerald slot is marked Emerald")
end
local variants = { { "synthetic RS-shaped image", rsImage() } }
local f = io.open(".claude/skills/pygba-headless/assets/pokemon-ruby.sav", "rb")
if f then
variants[#variants + 1] = { "real Ruby save", f:read("*a") }
f:close()
else
print("[skip] real Ruby save not present")
end
for _, var in ipairs(variants) do
local label, bytes = var[1], var[2]
eq(Gen3Save.sniff(bytes), "rs", label .. " sniffs as Ruby/Sapphire")
for _, v in ipairs({ "firered", "leafgreen", "emerald" }) do
local s, err = SaveConvert.importSav(bytes, v, v)
eq(s, nil, ("%s refused by importSav for %s"):format(label, v))
check(type(err) == "string" and err:find("Ruby/Sapphire", 1, true) ~= nil,
("%s: importSav for %s names Ruby/Sapphire (%s)"):format(label, v, tostring(err)))
end
for _, v in ipairs({ "red", "blue", "yellow", "gold", "silver", "crystal" }) do
local ok, why = SaveConvert.mainChecksumValid(bytes, v)
check(ok == nil and type(why) == "string" and why:find("Ruby/Sapphire", 1, true) ~= nil,
("%s: the %s launcher check names Ruby/Sapphire (%s)"):format(label, v, tostring(why)))
end
for _, v in ipairs({ "firered", "leafgreen", "emerald", "red", "gold" }) do
local files = freshFs(v)
files["picked.sav"] = bytes
local ok, slotOrErr = SaveFileIO.importToSlot("picked.sav", v)
eq(ok, false, ("%s: SaveFileIO refuses it for %s"):format(label, v))
check(type(slotOrErr) == "string" and slotOrErr:find("Ruby/Sapphire", 1, true) ~= nil,
("%s: SaveFileIO message for %s names Ruby/Sapphire (%s)"):format(label, v, tostring(slotOrErr)))
local created = false
for path in pairs(files) do if path:find("^saves/") then created = true end end
check(not created, ("%s: SaveFileIO leaves no slot behind for %s"):format(label, v))
end
end
do
local dir = os.getenv("TMPDIR") or "/tmp"
local src = ("%s/pokeport-entry-paths-%d.sav"):format(dir:gsub("/$", ""), os.time())
local out = src .. ".lua"
local fh = assert(io.open(src, "wb"))
fh:write(variants[1][2])
fh:close()
local cmd = ("luajit tools/save_convert/convert.lua import '%s' '%s' red >/dev/null 2>&1"):format(src, out)
local rc = os.execute(cmd)
check(rc ~= 0 and rc ~= true, "tools/save_convert/convert.lua refuses a Ruby/Sapphire image")
local produced = io.open(out, "rb")
check(produced == nil, "the CLI writes nothing for a Ruby/Sapphire image")
if produced then produced:close() end
os.remove(src)
os.remove(out)
end
do
local files = freshFs("firered")
files["picked.sav"] = withMons("firered", 5, true)
local ok, slot = SaveFileIO.importToSlot("picked.sav", "firered")
check(ok == true, "SaveFileIO imports a LeafGreen-origin cart into the FireRed game")
local save = ok and SaveData.load("firered")
eq(save and save.modData.cartGame, "leafgreen", "the stored slot keeps the cart's LeafGreen marking")
eq(save and save.version, "firered", "the slot belongs to the game it was imported into")
end
love.filesystem = realFs
T.finish()