mirror of
https://github.com/bryanthaboi/gen1recomp
synced 2026-10-08 09:42:55 -04:00
280 lines
9.2 KiB
Lua
280 lines
9.2 KiB
Lua
package.path = "./?.lua;./?/init.lua;" .. package.path
|
|
if not _G.love then _G.love = require("tests.love_stub") end
|
|
|
|
local S = require("tests.harness").suite("rom import from archives")
|
|
local check, eq = S.check, S.eq
|
|
|
|
local RomArchive = require("src.import.RomArchive")
|
|
local RomImporter = require("src.import.RomImporter")
|
|
|
|
local KIND_7Z = "7z\188\175\39\28"
|
|
local zipProbe = RomArchive._zipProbeBytes()
|
|
local userArchive = "PK\3\4" .. "user-archive-body"
|
|
local sevenZFile = KIND_7Z .. "user-7z-body"
|
|
|
|
local goodBody = string.rep("R", 1048576)
|
|
local SUPPORT_Z7 = false
|
|
local userEntries = { ["Red.gb"] = goodBody }
|
|
|
|
local function makeVfs()
|
|
local vfs = {}
|
|
local arch = {}
|
|
local mounted = {}
|
|
local staged = {}
|
|
|
|
local function keyOf(archive)
|
|
if type(archive) == "table" then return archive end
|
|
return staged[archive] and archive or nil
|
|
end
|
|
|
|
function vfs.newFileData(data, name)
|
|
return { _fileData = true, data = data, name = name }
|
|
end
|
|
|
|
function vfs.write(path, data)
|
|
staged[path] = data
|
|
return true
|
|
end
|
|
|
|
function vfs.remove(path)
|
|
staged[path] = nil
|
|
return true
|
|
end
|
|
|
|
function vfs.mount(archive, point)
|
|
local data
|
|
if type(archive) == "table" then
|
|
data = archive.data
|
|
else
|
|
data = staged[archive]
|
|
end
|
|
if not data then return false end
|
|
local entries
|
|
if data == userArchive then
|
|
entries = userEntries
|
|
elseif data == sevenZFile then
|
|
entries = userEntries
|
|
elseif data == zipProbe then
|
|
entries = { probe = "ok" }
|
|
elseif RomArchive.kind(data) == "7z" then
|
|
if not SUPPORT_Z7 then return false end
|
|
entries = { ["p.romprobe"] = "gen1recomp-probe" }
|
|
else
|
|
return false
|
|
end
|
|
arch[point] = { dir = true }
|
|
for rel, body in pairs(entries) do
|
|
arch[point .. "/" .. rel] = { body = body }
|
|
end
|
|
mounted[keyOf(archive) or data] = point
|
|
return true
|
|
end
|
|
|
|
function vfs.unmount(archive)
|
|
local point = mounted[archive] or mounted[archive.data and archive or nil]
|
|
if not point then
|
|
for k, v in pairs(mounted) do
|
|
if k == archive or (type(k) == "table" and k.data == archive) then
|
|
point = v
|
|
end
|
|
end
|
|
end
|
|
if not point then return false end
|
|
for path in pairs(arch) do
|
|
if path == point or path:sub(1, #point + 1) == point .. "/" then
|
|
arch[path] = nil
|
|
end
|
|
end
|
|
mounted[archive] = nil
|
|
for k, v in pairs(mounted) do
|
|
if v == point then mounted[k] = nil end
|
|
end
|
|
return true
|
|
end
|
|
|
|
function vfs.getDirectoryItems(dir)
|
|
if not (arch[dir] and arch[dir].dir) then return {} end
|
|
local out = {}
|
|
for path in pairs(arch) do
|
|
if path ~= dir and path:sub(1, #dir + 1) == dir .. "/" then
|
|
local rest = path:sub(#dir + 2)
|
|
if not rest:find("/", 1, true) and not arch[path].dir then
|
|
out[#out + 1] = rest
|
|
end
|
|
end
|
|
end
|
|
table.sort(out)
|
|
return out
|
|
end
|
|
|
|
function vfs.getInfo(path)
|
|
local node = arch[path]
|
|
if node then
|
|
if node.dir then return { type = "directory" } end
|
|
return { type = "file", size = #node.body }
|
|
end
|
|
for other in pairs(arch) do
|
|
if other:sub(1, #path + 1) == path .. "/" then
|
|
return { type = "directory" }
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function vfs.read(path)
|
|
local node = arch[path]
|
|
if node and node.body then return node.body end
|
|
return nil, "not found"
|
|
end
|
|
|
|
return vfs
|
|
end
|
|
|
|
local savedFs = love.filesystem
|
|
local vfs = makeVfs()
|
|
love.filesystem = vfs
|
|
RomArchive._resetForTests()
|
|
|
|
eq(RomArchive.kind("GBROM" .. string.rep("x", 20)), nil, "raw ROM is not an archive")
|
|
eq(RomArchive.kind("PK\3\4rest"), "zip", "PK\\3\\4 is a zip")
|
|
eq(RomArchive.kind("PK\5\6rest"), "zip", "empty zip is a zip")
|
|
eq(RomArchive.kind(KIND_7Z .. "rest"), "7z", "7z magic recognised")
|
|
eq(RomArchive.kind("PK"), nil, "truncated input is not an archive")
|
|
eq(RomArchive.kind(nil), nil, "non-string input is not an archive")
|
|
|
|
local caps = RomArchive.capabilities(vfs)
|
|
check(caps.zip == true, "zip probe mounts here")
|
|
check(caps.z7 == false, "7z probe refuses while SUPPORT_Z7 is off")
|
|
|
|
SUPPORT_Z7 = true
|
|
RomArchive._resetForTests()
|
|
caps = RomArchive.capabilities(vfs)
|
|
check(caps.z7 == true, "7z probe mounts once the platform supports it")
|
|
SUPPORT_Z7 = false
|
|
RomArchive._resetForTests()
|
|
check(RomArchive.capabilities(vfs).z7 == false, "caps are re-probed after reset")
|
|
|
|
userEntries = { ["._Red.gb"] = "APPLEDOUBLE", ["Red.gb"] = goodBody }
|
|
local bytes, entry = RomArchive.unwrap(userArchive, "pack.zip", { fs = vfs })
|
|
eq(bytes, goodBody, "cart bytes come out")
|
|
eq(entry, "Red.gb", "AppleDouble ._Red.gb is skipped, Red.gb wins")
|
|
check(RomArchive.unwrap("hello", "x.zip", { fs = vfs }) == nil, "not-an-archive refused")
|
|
|
|
userEntries = { ["A.gb"] = "AAA", ["Y.gb"] = "YYY" }
|
|
bytes, entry = RomArchive.unwrap(userArchive, "pack.zip", {
|
|
fs = vfs,
|
|
acceptedSize = function(n) return n >= 3 end,
|
|
prefer = function(b) return b:sub(1, 1) == "Y" end,
|
|
})
|
|
eq(entry, "Y.gb", "prefer() picks the known-version cart over sort order")
|
|
|
|
userEntries = { ["notes.txt"] = "text" }
|
|
local noRom, noRomErr = RomArchive.unwrap(userArchive, "pack.zip", { fs = vfs })
|
|
eq(noRom, nil, "an archive with no cart refuses")
|
|
check(tostring(noRomErr):find("no .gb/.gbc/.gba", 1, true) ~= nil,
|
|
"the no-cart message says so: " .. tostring(noRomErr))
|
|
|
|
userEntries = { ["Red.gb"] = string.rep("R", 2000) }
|
|
local wrongSize, wrongErr = RomArchive.unwrap(userArchive, "pack.zip", { fs = vfs })
|
|
eq(wrongSize, nil, "a wrong-size cart is never read (zip bomb guard)")
|
|
check(tostring(wrongErr):find("2000 bytes", 1, true) ~= nil,
|
|
"the wrong-size message lists sizes: " .. tostring(wrongErr))
|
|
|
|
userEntries = { ["Red.gb"] = goodBody }
|
|
local z7bytes, z7err = RomArchive.unwrap(sevenZFile, "cart.7z", { fs = vfs })
|
|
eq(z7bytes, nil, "7z refused while the platform lacks the archiver")
|
|
check(tostring(z7err):find("cannot open .7z", 1, true) ~= nil,
|
|
"the caps refusal names the format: " .. tostring(z7err))
|
|
|
|
SUPPORT_Z7 = true
|
|
RomArchive._resetForTests()
|
|
local z7ok, z7entry = RomArchive.unwrap(sevenZFile, "cart.7z", { fs = vfs })
|
|
eq(z7ok, goodBody, "7z opens once the platform supports it")
|
|
eq(z7entry, "Red.gb", "and yields its cart")
|
|
SUPPORT_Z7 = false
|
|
RomArchive._resetForTests()
|
|
|
|
love.data = love.data or {}
|
|
local savedData = { hash = love.data.hash, encode = love.data.encode }
|
|
love.data.hash = function(_, data)
|
|
return { tag = data:sub(1, 1) }
|
|
end
|
|
love.data.encode = function(_, _, digest)
|
|
if type(digest) == "table" and digest.tag == "R" then
|
|
return require("src.core.GameVersion").info("red").sha1
|
|
end
|
|
return "0000000000000000000000000000000000000000"
|
|
end
|
|
|
|
local function freshImporter(extra)
|
|
local ri = setmetatable({ tab = "red", workState = nil }, RomImporter)
|
|
for k, v in pairs(extra or {}) do ri[k] = v end
|
|
return ri
|
|
end
|
|
|
|
userEntries = { ["Red.gb"] = string.rep("R", 2000) }
|
|
local ri = freshImporter()
|
|
ri:startData(userArchive, "pack.zip")
|
|
eq(ri.workState, "error", "wrong-size cart in a zip lands in the error state")
|
|
check(tostring(ri.detail):find("2000 bytes", 1, true) ~= nil,
|
|
"the size filter runs before any read: " .. tostring(ri.detail))
|
|
|
|
userEntries = { ["Red.gb"] = string.rep("X", 1048576) }
|
|
ri = freshImporter()
|
|
ri:startData(userArchive, "pack.zip")
|
|
eq(ri.workState, "error", "unknown-sha cart in a zip errors too")
|
|
check(tostring(ri.detail):find("Unsupported ROM (SHA-1", 1, true) ~= nil,
|
|
"the SHA-1 gate runs on the unwrapped bytes: " .. tostring(ri.detail))
|
|
|
|
local realMax = RomArchive.MAX_ARCHIVE_BYTES
|
|
RomArchive.MAX_ARCHIVE_BYTES = 10
|
|
ri = freshImporter()
|
|
ri:startData(userArchive, "pack.zip")
|
|
RomArchive.MAX_ARCHIVE_BYTES = realMax
|
|
eq(ri.workState, "error", "an oversized archive is refused before mounting")
|
|
check(tostring(ri.detail):find(".zip", 1, true) ~= nil,
|
|
"the oversize message names the format: " .. tostring(ri.detail))
|
|
|
|
ri = freshImporter()
|
|
ri:startData(sevenZFile, "cart.7z")
|
|
eq(ri.workState, "error", "a 7z drop takes the same startData path")
|
|
check(tostring(ri.detail):find("cannot open .7z", 1, true) ~= nil,
|
|
"and is refused by caps: " .. tostring(ri.detail))
|
|
|
|
userEntries = { ["Red.gb"] = goodBody }
|
|
|
|
local function fakeFile(path, content)
|
|
return {
|
|
open = function() return true end,
|
|
read = function() return content end,
|
|
close = function() end,
|
|
getSize = function() return #content end,
|
|
getFilename = function() return path end,
|
|
}
|
|
end
|
|
|
|
ri = freshImporter({ tab = "red" })
|
|
local started
|
|
ri.startData = function(self, data, name, src)
|
|
started = { data = data, name = name, src = src }
|
|
end
|
|
ri:filedropped(fakeFile("/tmp/packs/cart-pack.zip", userArchive))
|
|
check(started ~= nil, "a game-tab zip holding a cart routes to startData")
|
|
if started then
|
|
eq(started.data, userArchive, "startData receives the archive payload (it unwraps)")
|
|
eq(started.name, "/tmp/packs/cart-pack.zip", "and the dropped path for display")
|
|
end
|
|
|
|
ri = freshImporter({ tab = "red" })
|
|
ri:filedropped(fakeFile("/tmp/cart.7z", sevenZFile))
|
|
eq(ri.workState, "error", "a dropped .7z reaches startData via the generic path")
|
|
check(tostring(ri.detail):find("cannot open .7z", 1, true) ~= nil,
|
|
"and is refused when the platform lacks 7z: " .. tostring(ri.detail))
|
|
|
|
love.filesystem = savedFs
|
|
love.data.hash = savedData.hash
|
|
love.data.encode = savedData.encode
|
|
RomArchive._resetForTests()
|
|
|
|
S.finish()
|