fix(mods): stop the Gen 2 require facade answering the engine

bootGame's require("src.core.Game") was resolving to Gen2Compat's Game
facade after a Gold session, so the next Gen 1 boot got an empty proxy
whose rawget(load) is nil (iOS: "src.core.Game missing load after
reload"; Android: Game:load called on a nil value).

Two independent causes, both needed:

  * callerIsMod decided "not under src/" meant "a mod", and main.lua is
    not under src/, so the engine's own require was gated as a mod's.
    Root chunks main.lua and conf.lua are now matched exactly, which a
    mod's own mods/<id>/main.lua cannot collide with.

  * devShim.generation was only ever set, never cleared, so the
    generation Gold declared outlived Gold's session. Loader.endSession
    drops it from SessionLifecycle.endMountedSession, and the facade
    gate now requires generation == 2 rather than ~= 1.

Only platforms that return to the launcher in-process (Android, and iOS
since #1808) keep the shim alive across sessions; everywhere else the
process restart cleared it.
This commit is contained in:
bryanthaboi
2026-08-25 15:14:47 -04:00
parent 54c7a3aeae
commit 51908faabf
3 changed files with 106 additions and 2 deletions
+2
View File
@@ -30,6 +30,8 @@ function SessionLifecycle.endMountedSession(version)
local Runtime = require("src.mods.Runtime")
if Runtime.reset then Runtime.reset() end
if Assets.installLoader then Assets.installLoader(nil) end
local Loader = package.loaded["src.mods.Loader"]
if Loader and Loader.endSession then Loader.endSession() end
local okCompat, LegacyCompat = pcall(require, "src.mods.LegacyCompat")
if okCompat and LegacyCompat.reset then LegacyCompat.reset() end
end
+14 -2
View File
@@ -147,12 +147,19 @@ local SUPPORTED_REQUIRES = {
local ENGINE_PREFIX = (debug.getinfo(1, "S").source or "")
:gsub("^@", ""):gsub("mods[/\\]Loader%.lua$", "")
local ENGINE_CHUNKS = {
["main.lua"] = true,
["conf.lua"] = true,
}
local function callerIsMod(level)
if ENGINE_PREFIX == "" then return false end
local info = debug.getinfo(level, "S")
local source = info and info.source
if not source or source:sub(1, 1) ~= "@" then return false end
return source:sub(2, 1 + #ENGINE_PREFIX) ~= ENGINE_PREFIX
local path = source:sub(2)
if ENGINE_CHUNKS[path] then return false end
return path:sub(1, #ENGINE_PREFIX) ~= ENGINE_PREFIX
end
local function scanRequire(name)
@@ -207,6 +214,11 @@ local function engineRequire(name)
return module
end
function Loader.endSession()
devShim.generation = nil
devShim.errors = nil
end
function Loader:_installDevShim()
for id, mod in pairs(self.mods) do
devShim.permissions[id] = mod.manifest.permissionSet
@@ -234,7 +246,7 @@ function Loader:_installDevShim()
-- The Gen 1 name a mod asked for, answered by the Gen 2 arm behind it.
-- Engine code keeps the real module: src/render/PaletteFX.lua:776
-- requires src.core.Game on both generations and means it.
if devShim.generation ~= 1 and Gen2Compat.serves(name)
if devShim.generation == 2 and Gen2Compat.serves(name)
and (owner or callerIsMod(3)) then
local adapter = Gen2Compat.resolve(name, Runtime.currentMod)
if adapter then