From 9bdcef6019ecdc2ea535dbc6ac495200544cefa3 Mon Sep 17 00:00:00 2001 From: mimus-assa Date: Wed, 29 Jul 2026 14:18:45 +0000 Subject: [PATCH] fix: resolve module-relative UI paths when loaded from callbacks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Any g_ui.loadUI/displayUI/importStyle("barename") call made from a callback (UI button, protocol packet, scheduled event) resolves to the vfs root and fails: ResourceManager::resolvePath() relies on g_lua.getCurrentSourcePath(), which does not yield the module dir in those contexts — and the isPreDrawing() branch skips script-relative resolution entirely (also the !scriptPath.empty() guard is dead code: "/" + path is never empty). The same call works from a module init(), which is why the breakage only shows on windows opened on demand. Confirmed broken paths: imbuement shrine window (both designs), game_shop and game_stash (error on every login), character list on relog, outfit window, console channels dialog, ignore window, VIP add/edit, extra battle list instances, store/reward wall subwindows. Fix (Lua only): wrap the three entry points in corelib and resolve relative paths against the calling file directory (debug.getinfo), only when the target .otui actually exists there; every other case is forwarded untouched, so behavior is otherwise identical. Idempotent; tail calls safely degrade to the current behavior. Co-Authored-By: Claude Fable 5 --- modules/corelib/ui/uipathresolver.lua | 70 +++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 modules/corelib/ui/uipathresolver.lua diff --git a/modules/corelib/ui/uipathresolver.lua b/modules/corelib/ui/uipathresolver.lua new file mode 100644 index 0000000000..38f046bb1a --- /dev/null +++ b/modules/corelib/ui/uipathresolver.lua @@ -0,0 +1,70 @@ +-- Resolves module-relative UI paths in Lua before they reach the C++ layer. +-- +-- Root cause being worked around: ResourceManager::resolvePath() resolves a +-- relative path against g_lua.getCurrentSourcePath(), but when the call happens +-- inside a callback (UI button, protocol packet, scheduled event) that context +-- can be unavailable — and the isPreDrawing() branch skips it entirely — so +-- 'file' resolves to '/file.otui' (the root) and the load fails. This breaks +-- every g_ui.loadUI/displayUI/importStyle('barename') call made from a +-- callback (imbuing, outfit, console channels, VIP add/edit, shop, stash, +-- character list, ...), while the same call works from a module's init(). +-- +-- The fix: wrap the three entry points and, when the argument is a relative +-- path, resolve it against the *calling file's* directory (debug.getinfo), +-- but only if the target file actually exists there. In every other case the +-- argument is forwarded untouched, so behavior is identical to before. + +local function resolveCallerPath(path) + if type(path) ~= 'string' or path == '' or path:sub(1, 1) == '/' then + return path + end + + -- level 3: 1 = resolveCallerPath, 2 = wrapper, 3 = caller + local info = debug.getinfo(3, 'S') + local src = info and info.source + if not src or src:sub(1, 1) ~= '@' then + return path + end + + src = src:sub(2) + if src:sub(1, 1) ~= '/' then + return path + end + + local dir = src:match('^(.*)/[^/]*$') + if not dir or dir == '' then + return path + end + + local candidate = dir .. '/' .. path + -- mirror g_resources.guessFilePath(candidate, 'otui') + local candidateFile = candidate + if candidate:sub(-5) ~= '.otui' then + candidateFile = candidate .. '.otui' + end + + if g_resources.fileExists(candidateFile) then + return candidate + end + return path +end + +if not g_ui.__pathResolverInstalled then + g_ui.__pathResolverInstalled = true + + local origLoadUI = g_ui.loadUI + local origDisplayUI = g_ui.displayUI + local origImportStyle = g_ui.importStyle + + g_ui.loadUI = function(path, ...) + return origLoadUI(resolveCallerPath(path), ...) + end + + g_ui.displayUI = function(path, ...) + return origDisplayUI(resolveCallerPath(path), ...) + end + + g_ui.importStyle = function(path, ...) + return origImportStyle(resolveCallerPath(path), ...) + end +end