diff --git a/src/core/SessionLifecycle.lua b/src/core/SessionLifecycle.lua index ca9c9d8d..c8e73653 100644 --- a/src/core/SessionLifecycle.lua +++ b/src/core/SessionLifecycle.lua @@ -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 diff --git a/src/mods/Loader.lua b/src/mods/Loader.lua index ab10fb2e..86d7b796 100644 --- a/src/mods/Loader.lua +++ b/src/mods/Loader.lua @@ -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 diff --git a/tests/engine/gen2_shim_engine_require.lua b/tests/engine/gen2_shim_engine_require.lua new file mode 100644 index 00000000..1db767d5 --- /dev/null +++ b/tests/engine/gen2_shim_engine_require.lua @@ -0,0 +1,90 @@ +package.path = "./?.lua;./?/init.lua;" .. package.path + +love = love or require("tests.love_stub") + +local S = require("tests.harness").suite("gen2 shim engine require") +local check, eq = S.check, S.eq + +local Loader = require("src.mods.Loader") +local Gen2Compat = require("src.mods.Gen2Compat") +local GameVersion = require("src.core.GameVersion") +local SessionLifecycle = require("src.core.SessionLifecycle") + +local NAME = "src.core.Game" +local REAL = { load = function() end } + +local function memfs(files) + return { + files = files, + read = function(path) return files[path] end, + write = function(path, content) files[path] = content return true 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, + load = function(path) + if not files[path] then return nil, "no file: " .. path end + return load(files[path], path) + end, + createDirectory = function() return true end, + getDirectoryItems = function(path) + local seen, items = {}, {} + local prefix = path .. "/" + for key in pairs(files) do + if key:sub(1, #prefix) == prefix then + local child = key:sub(#prefix + 1):match("^[^/]+") + if child and not seen[child] then + seen[child] = true + items[#items + 1] = child + end + end + end + table.sort(items) + return items + end, + } +end + +local function requireFrom(source) + local chunk = assert(loadstring( + "local name = ... local mod = require(name) return mod", "@" .. source)) + return chunk(NAME) +end + +local savedVersion = GameVersion.get() +local savedModule = package.loaded[NAME] +package.loaded[NAME] = REAL + +GameVersion.set("gold") +local files = { + ["mods/facade/manifest.json"] = + [[{"id":"facade","name":"facade","version":"1.0.0","entry":"main.lua",]] + .. [["gen2compat":true}]], + ["mods/facade/main.lua"] = "return function(mod) end", +} +local loader = Loader.new({ fs = memfs(files) }) +loader:load({ pokemon = {} }) + +eq(loader.generation, 2, "the fixture loader is a Gen 2 one") +check(Gen2Compat.serves(NAME), "the facade serves " .. NAME) + +local facade = Gen2Compat.resolve(NAME) +eq(requireFrom("mods/facade/main.lua"), facade, + "a mod's require still resolves to the Gen 2 facade") +eq(requireFrom("main.lua"), REAL, + "main.lua's require is the engine's own, never the facade") +eq(requireFrom("conf.lua"), REAL, "conf.lua's require is the engine's own") + +SessionLifecycle.endMountedSession() +eq(requireFrom("mods/facade/main.lua"), REAL, + "after the session ends the facade stops answering for the next boot") + +package.loaded[NAME] = savedModule +GameVersion.set(savedVersion) + +S.finish()