fix(switch): unhide fused save-dir generated cache on Play

PhysFS does not merge archive data/ with save-dir data/generated, so
fused NX Play crashed after import. Prepend-mount generated trees,
fall back to CacheFs.read in Data:load, keep multiline lua-error logs,
and skip Boot.run when network is unvalidated. T24 stays open.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Andrew Quenehen
2026-08-01 04:55:08 -03:00
parent 6fb5602cb0
commit b1ad7c7254
6 changed files with 101 additions and 5 deletions
+13 -1
View File
@@ -196,7 +196,19 @@ local function loadModule(dir, name)
if not chunk then return false, err end
return pcall(chunk)
end
return pcall(require, "data.generated." .. name)
local ok, mod = pcall(require, "data.generated." .. name)
if ok then return true, mod end
-- Fused PhysFS may hide save-dir data/generated behind the archive's
-- data/ tree even after mountVersion; load bytes explicitly as fallback.
local CacheFs = require("src.import.CacheFs")
local path = "data/generated/" .. name .. ".lua"
local bytes = CacheFs.read(path)
if type(bytes) == "string" then
local chunk, err = loadstring(bytes, "@" .. path)
if not chunk then return false, err or mod end
return pcall(chunk)
end
return false, mod
end
function Data:load()
+6 -1
View File
@@ -23,10 +23,15 @@ end
local function redactString(s)
if type(s) ~= "string" then return s end
-- Keep printable ASCII + TAB/LF/CR so Lua stack traces remain readable.
-- Reject NULs and other C0 controls, and high bytes (ROM/binary dumps).
for i = 1, #s do
local b = s:byte(i)
if b < 32 or b > 126 then return "<redacted>" end
if b == 0 then return "<redacted>" end
if b < 32 and b ~= 9 and b ~= 10 and b ~= 13 then return "<redacted>" end
if b > 126 then return "<redacted>" end
end
if #s > 8192 then return s:sub(1, 8192) .. "...<truncated>" end
return s
end
+39 -3
View File
@@ -366,9 +366,40 @@ end
-- *prepended* so they win over any Red copy at the root and over the game
-- source. Called once at boot, before Game:load (main.lua). Returns true
-- when nothing was needed or the mount succeeded.
--
-- Fused builds also ship a `data/` tree (scripts, palettes) inside game.love.
-- PhysFS does not merge directories across archives: that `data/` can hide
-- `data/generated/` written to the save directory after ROM import. Loose
-- play often still works (search order / mount layout differs); fused NX
-- Play-after-import then fails Data:load with a missing-module error.
-- Explicitly prepend-mount the generated subtrees so they win.
local function mountGeneratedTrees(prefix)
prefix = prefix or ""
if not (love and love.filesystem and love.filesystem.mount) then
return false
end
local mounted = false
local pairs_ = {
{ prefix .. "data/generated", "data/generated" },
{ prefix .. "assets/generated", "assets/generated" },
}
for _, item in ipairs(pairs_) do
local src, dest = item[1], item[2]
if love.filesystem.getInfo(src, "directory") then
if love.filesystem.mount(src, dest, false) then
mounted = true
end
end
end
return mounted
end
function CacheFs.mountVersion(version)
local prefix = require("src.core.GameVersion").cachePrefix(version)
if prefix == "" then return true end -- Red: already at the root
if prefix == "" then
mountGeneratedTrees("")
return true
end
local sub = prefix:gsub("/+$", "") -- "blue/" / "yellow/" -> bare dir
-- The cache root is the portable game folder when active, else LÖVE's OS
-- save directory (where love.filesystem wrote blue/... or yellow/...).
@@ -377,11 +408,16 @@ function CacheFs.mountVersion(version)
base = love.filesystem.getSaveDirectory()
end
if not base then return false end
if mountReadable(base .. SEP .. sub, false) then return true end
if mountReadable(base .. SEP .. sub, false) then
mountGeneratedTrees("")
return true
end
-- Fallback when FFI/PHYSFS_mount is unavailable: LÖVE can mount a folder
-- that lives in the save directory by name (prepended: appendToPath=false).
if love.filesystem.mount then
return love.filesystem.mount(sub, "", false)
local ok = love.filesystem.mount(sub, "", false)
mountGeneratedTrees("")
return ok
end
return false
end
+6
View File
@@ -250,6 +250,12 @@ function Boot.run(args)
if not (love.filesystem.isFused and love.filesystem.isFused()) then
return false
end
-- Switch (and any host without validated network): never probe payloads.
local okp, Platform = pcall(require, "src.core.Platform")
if okp and Platform and Platform.networkValidated
and not Platform.networkValidated() then
return false
end
-- The chainloaded love.load calls Boot.run again; the flag makes it a no-op.
if _G.POKEPORT_PAYLOAD_MOUNTED then return false end