mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
fix(switch): load Yellow/Blue assets when PhysFS mount hides them
Mirror Data:load's versioned CacheFs read in Assets so Yellow-only NX Play survives intro without needing a Red root cache mask. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+3
-11
@@ -209,18 +209,10 @@ local function loadModule(dir, name)
|
||||
-- cache explicitly when require cannot see the mounted tree.
|
||||
local CacheFs = require("src.import.CacheFs")
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local prefix = GameVersion.cachePrefix()
|
||||
local path = prefix .. "data/generated/" .. name .. ".lua"
|
||||
local saved = CacheFs.prefix
|
||||
CacheFs.prefix = ""
|
||||
local bytes = CacheFs.read(path)
|
||||
CacheFs.prefix = saved
|
||||
if type(bytes) ~= "string" then
|
||||
-- Also try with CacheFs.prefix if the caller set it for this version.
|
||||
bytes = CacheFs.read("data/generated/" .. name .. ".lua")
|
||||
end
|
||||
local path = "data/generated/" .. name .. ".lua"
|
||||
local bytes = CacheFs.readActive(path)
|
||||
if type(bytes) == "string" then
|
||||
local chunk, err = loadstring(bytes, "@" .. path)
|
||||
local chunk, err = loadstring(bytes, "@" .. GameVersion.cachePrefix() .. path)
|
||||
if not chunk then return false, err or mod end
|
||||
return pcall(chunk)
|
||||
end
|
||||
|
||||
@@ -294,6 +294,24 @@ function CacheFs.read(rel)
|
||||
return love.filesystem.read(rel)
|
||||
end
|
||||
|
||||
-- Read cache-relative `rel` for the active GameVersion when PhysFS may hide
|
||||
-- prefixed Blue/Yellow trees (fused NX mount hole). Same order Data:load
|
||||
-- already used: active version prefix with CacheFs.prefix cleared, then
|
||||
-- `rel` under the caller's CacheFs.prefix. Returns the bytes or nil.
|
||||
function CacheFs.readActive(rel)
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local prefix = GameVersion.cachePrefix()
|
||||
local saved = CacheFs.prefix
|
||||
CacheFs.prefix = ""
|
||||
local bytes = CacheFs.read(prefix .. rel)
|
||||
CacheFs.prefix = saved
|
||||
if type(bytes) ~= "string" then
|
||||
bytes = CacheFs.read(rel)
|
||||
end
|
||||
if type(bytes) == "string" then return bytes end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- does cache-relative `rel` exist as a file?
|
||||
function CacheFs.exists(rel)
|
||||
rel = withPrefix(rel)
|
||||
|
||||
+24
-2
@@ -43,11 +43,28 @@ function Assets.resolve(path)
|
||||
return loader:derivedPath(rel) or path
|
||||
end
|
||||
|
||||
-- When PhysFS hides Blue/Yellow prefixed trees (fused NX), load generated
|
||||
-- asset bytes the same way Data:load does via CacheFs.readActive.
|
||||
local function generatedFileData(resolved)
|
||||
if type(resolved) ~= "string" or resolved:sub(1, #GENERATED) ~= GENERATED then
|
||||
return nil
|
||||
end
|
||||
if exists(resolved) then return nil end
|
||||
local bytes = require("src.import.CacheFs").readActive(resolved)
|
||||
if type(bytes) ~= "string" then return nil end
|
||||
return love.filesystem.newFileData(bytes, resolved)
|
||||
end
|
||||
|
||||
function Assets.image(path)
|
||||
local resolved = Assets.resolve(path)
|
||||
local image = cache[resolved]
|
||||
if not image then
|
||||
image = love.graphics.newImage(resolved)
|
||||
local fileData = generatedFileData(resolved)
|
||||
if fileData then
|
||||
image = love.graphics.newImage(fileData)
|
||||
else
|
||||
image = love.graphics.newImage(resolved)
|
||||
end
|
||||
cache[resolved] = image
|
||||
end
|
||||
return image
|
||||
@@ -56,7 +73,12 @@ end
|
||||
-- pixel-level reads (tile-shift variants, the spinner strip blit) resolve
|
||||
-- the same way but stay uncached: the caller keeps the derived product
|
||||
function Assets.imageData(path)
|
||||
return love.image.newImageData(Assets.resolve(path))
|
||||
local resolved = Assets.resolve(path)
|
||||
local fileData = generatedFileData(resolved)
|
||||
if fileData then
|
||||
return love.image.newImageData(fileData)
|
||||
end
|
||||
return love.image.newImageData(resolved)
|
||||
end
|
||||
|
||||
function Assets.register(invalidate)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
-- Yellow/Blue-only NX: PhysFS may hide prefixed assets/generated trees.
|
||||
-- Assets must fall back through CacheFs.readActive like Data:load does.
|
||||
package.path = "./?.lua;./?/init.lua;" .. package.path
|
||||
if not _G.love then _G.love = require("tests.love_stub") end
|
||||
|
||||
local T = require("tests.harness")
|
||||
local check = T.check
|
||||
local eq = T.eq
|
||||
|
||||
local GameVersion = require("src.core.GameVersion")
|
||||
local CacheFs = require("src.import.CacheFs")
|
||||
local Assets = require("src.render.Assets")
|
||||
|
||||
local PNG = "assets/generated/tilesets/reds_house.png"
|
||||
local savedPrefix = CacheFs.prefix
|
||||
local savedVersion = GameVersion.get()
|
||||
|
||||
local function clearPath(path)
|
||||
love.filesystem.remove(path)
|
||||
end
|
||||
|
||||
-- --- readActive: Yellow-prefixed bytes without unprefixed PhysFS visibility
|
||||
GameVersion.set("yellow")
|
||||
CacheFs.prefix = ""
|
||||
love.filesystem.write("yellow/" .. PNG, "yellow-png-bytes")
|
||||
clearPath(PNG)
|
||||
eq(CacheFs.readActive(PNG), "yellow-png-bytes",
|
||||
"readActive finds yellow/<rel> when unprefixed path is missing")
|
||||
|
||||
-- --- Assets.image fallback (Yellow-only mount hole)
|
||||
Assets.flush()
|
||||
local img = Assets.image(PNG)
|
||||
check(img ~= nil, "Assets.image loads Yellow tileset via readActive fallback")
|
||||
eq(img.path, PNG, "fallback Image keeps the logical generated path name")
|
||||
|
||||
-- --- Assets.imageData fallback
|
||||
local id = Assets.imageData(PNG)
|
||||
check(id ~= nil, "Assets.imageData loads Yellow tileset via readActive fallback")
|
||||
eq(id.path, PNG, "fallback ImageData keeps the logical generated path name")
|
||||
|
||||
-- --- Blue prefix
|
||||
GameVersion.set("blue")
|
||||
Assets.flush()
|
||||
love.filesystem.write("blue/" .. PNG, "blue-png-bytes")
|
||||
clearPath(PNG)
|
||||
clearPath("yellow/" .. PNG)
|
||||
local blueImg = Assets.image(PNG)
|
||||
check(blueImg ~= nil, "Assets.image loads Blue tileset via readActive fallback")
|
||||
|
||||
-- --- Red primary path (unprefixed file visible → no fallback needed)
|
||||
GameVersion.set("red")
|
||||
Assets.flush()
|
||||
love.filesystem.write(PNG, "red-png-bytes")
|
||||
clearPath("blue/" .. PNG)
|
||||
local redImg = Assets.image(PNG)
|
||||
check(redImg ~= nil, "Assets.image loads Red tileset from unprefixed path")
|
||||
eq(love.filesystem.read(PNG), "red-png-bytes",
|
||||
"Red still stores generated assets at the save-dir root")
|
||||
|
||||
-- --- Missing generated file: no invented bytes
|
||||
GameVersion.set("yellow")
|
||||
Assets.flush()
|
||||
clearPath(PNG)
|
||||
clearPath("yellow/" .. PNG)
|
||||
clearPath("blue/" .. PNG)
|
||||
eq(CacheFs.readActive(PNG), nil,
|
||||
"readActive returns nil when the file is absent in every tree")
|
||||
|
||||
-- --- Non-generated paths never hit the versioned asset tree
|
||||
love.filesystem.write("yellow/" .. PNG, "should-not-leak")
|
||||
eq(CacheFs.readActive("assets/launcher/missing_chip.png"), nil,
|
||||
"readActive does not remap unrelated paths onto yellow generated assets")
|
||||
|
||||
-- --- readActive with CacheFs.prefix set (Data:load second try)
|
||||
GameVersion.set("yellow")
|
||||
CacheFs.prefix = "yellow/"
|
||||
love.filesystem.write("yellow/data/generated/maps.lua", "return { ok = true }")
|
||||
local luaBytes = CacheFs.readActive("data/generated/maps.lua")
|
||||
check(type(luaBytes) == "string" and luaBytes:find("ok", 1, true),
|
||||
"readActive still finds yellow/data/generated when CacheFs.prefix is set")
|
||||
|
||||
CacheFs.prefix = savedPrefix
|
||||
GameVersion.set(savedVersion)
|
||||
Assets.flush()
|
||||
clearPath(PNG)
|
||||
clearPath("yellow/" .. PNG)
|
||||
clearPath("blue/" .. PNG)
|
||||
clearPath("yellow/data/generated/maps.lua")
|
||||
|
||||
T.finish()
|
||||
+46
-5
@@ -40,8 +40,15 @@ local gstate = { shader = nil, canvas = nil, blend = "alpha",
|
||||
local gstack = {}
|
||||
|
||||
stub.graphics = {
|
||||
newImage = function(path)
|
||||
local w, h = pngSize(path)
|
||||
newImage = function(pathOrData)
|
||||
local path = pathOrData
|
||||
if type(pathOrData) == "table" and pathOrData._fileData then
|
||||
path = pathOrData.name or "<filedata>"
|
||||
elseif type(pathOrData) == "table" and pathOrData.path then
|
||||
path = pathOrData.path
|
||||
end
|
||||
local w, h = 8, 8
|
||||
if type(path) == "string" then w, h = pngSize(path) end
|
||||
return setmetatable({ w = w, h = h, path = path }, Image)
|
||||
end,
|
||||
newQuad = function(x, y, w, h) return { x = x, y = y, w = w, h = h } end,
|
||||
@@ -120,12 +127,22 @@ stub.filesystem = {
|
||||
write = function(name, content) files[name] = content return true end,
|
||||
read = function(name) return files[name] end,
|
||||
remove = function(name) files[name] = nil return true end,
|
||||
newFileData = function(contents, name)
|
||||
return { _fileData = true, contents = contents, name = name or "" }
|
||||
end,
|
||||
createDirectory = function() return true end,
|
||||
-- directories are implied by key prefixes ("mods/x/manifest.json")
|
||||
getInfo = function(name)
|
||||
if files[name] then return { type = "file" } end
|
||||
getInfo = function(name, filter)
|
||||
if files[name] then
|
||||
if filter and filter ~= "file" then return nil end
|
||||
return { type = "file" }
|
||||
end
|
||||
local prefix = name .. "/"
|
||||
for key in pairs(files) do
|
||||
if key:sub(1, #prefix) == prefix then return { type = "directory" } end
|
||||
if key:sub(1, #prefix) == prefix then
|
||||
if filter and filter ~= "directory" then return nil end
|
||||
return { type = "directory" }
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end,
|
||||
@@ -215,6 +232,30 @@ stub.mouse = {
|
||||
|
||||
stub.timer = { getTime = function() return 0 end }
|
||||
|
||||
-- Minimal image module so Assets.imageData can decode FileData fallbacks
|
||||
-- headless (full pixel stubs live in tests/mod_graphics_tests.lua).
|
||||
local ImageData = {}
|
||||
ImageData.__index = ImageData
|
||||
function ImageData:getWidth() return self.w end
|
||||
function ImageData:getHeight() return self.h end
|
||||
function ImageData:getDimensions() return self.w, self.h end
|
||||
function ImageData:getPixel() return 0, 0, 0, 1 end
|
||||
function ImageData:setPixel() end
|
||||
function ImageData:mapPixel() end
|
||||
function ImageData:encode() return { getString = function() return "" end } end
|
||||
|
||||
stub.image = {
|
||||
newImageData = function(a, b)
|
||||
if type(a) == "table" and a._fileData then
|
||||
return setmetatable({ w = 8, h = 8, path = a.name, source = a }, ImageData)
|
||||
end
|
||||
if type(a) == "string" then
|
||||
return setmetatable({ w = 8, h = 8, path = a }, ImageData)
|
||||
end
|
||||
return setmetatable({ w = a or 8, h = b or 8 }, ImageData)
|
||||
end,
|
||||
}
|
||||
|
||||
-- Desktop / headless: full-window safe area (matches LÖVE's fallback).
|
||||
stub.window = {
|
||||
getSafeArea = function()
|
||||
|
||||
Reference in New Issue
Block a user