Skip to content

fix: resolve module-relative UI paths when loaded from callbacks - #1787

Open
mimus-assa wants to merge 1 commit into
opentibiabr:mainfrom
mimus-assa:fix/relative-ui-paths-from-callbacks
Open

fix: resolve module-relative UI paths when loaded from callbacks#1787
mimus-assa wants to merge 1 commit into
opentibiabr:mainfrom
mimus-assa:fix/relative-ui-paths-from-callbacks

Conversation

@mimus-assa

@mimus-assa mimus-assa commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

The bug

Any g_ui.loadUI / g_ui.displayUI / g_ui.importStyle('barename') call made from a callback (UI button, protocol packet handler, scheduled event) resolves against the vfs root and fails with unable to open file '/barename.otui': not found. The same call works when made from a module init(), which is why the breakage only shows on windows that open on demand.

Confirmed broken on current main

  • Imbuement shrine window (game_imbuing/new_design/t_imbui.lua:35, also old design) — the shrine does not open at all
  • game_shop create() — Lua error on every login
  • game_stash importStyle on game start — stash styles never load
  • client_entergame/characterlist.lua — error when returning to the character list
  • Outfit window (game_outfit/outfit.lua:338)
  • Console channels dialog (onChannelList) and ignore window
  • VIP add/edit windows, extra battle list instances, store change-name/transfer subwindows, reward wall pick window

Root cause

ResourceManager::resolvePath() resolves relative paths via g_lua.getCurrentSourcePath(), but:

  1. the g_drawPool.isPreDrawing() branch skips script-relative resolution entirely and maps the path to the root; and
  2. the !scriptPath.empty() guard is dead code — "/" + getCurrentSourcePath() is never empty, so when the source path is unavailable the result is //barename/barename (root) instead of falling back.

The fix (Lua only)

modules/corelib/ui/uipathresolver.lua wraps the three entry points and resolves a relative path against the calling file's directory (debug.getinfo), only when the target .otui actually exists there. In every other case (absolute path, non-string, unresolvable caller, target not found) the argument is forwarded untouched, so behavior is otherwise byte-identical. Idempotent (guarded against double-wrap); tail-called sites safely degrade to the current behavior.

Alternative considered: fixing resolvePath() in C++ — but the isPreDrawing() branch is load-bearing for draw-time image lookups, and the Lua shim fixes all UI load sites without touching that path.

Testing

  • Standalone LuaJIT harness, 17/17: rewrite when target exists, passthrough otherwise, absolute paths untouched, subdir-relative paths, .otui-suffixed names, extra args forwarded, non-string args, callers without file source, return values preserved, double-load idempotence, C frames and tail calls do not error.
  • Deployed to a production 15.25 server (protocol 15.25, Canary); in-game verification of the affected windows is in progress and I will report results in this PR.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved UI resource loading for relative paths used within Lua modules.
    • UI files, styles, and imports now resolve relative to the calling module when the referenced resource exists.
    • Preserved existing behavior by falling back to the original path when no matching resource is found.

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 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 4389a3fc-d570-44fe-9de1-5fa46f786f0b

📥 Commits

Reviewing files that changed from the base of the PR and between 5636f95 and 9bdcef6.

📒 Files selected for processing (1)
  • modules/corelib/ui/uipathresolver.lua

📝 Walkthrough

Walkthrough

uipathresolver.lua adds caller-relative path resolution for UI loading and style imports, then installs one-time wrappers around g_ui.loadUI, g_ui.displayUI, and g_ui.importStyle.

Changes

UI path resolution

Layer / File(s) Summary
Caller-relative path resolution
modules/corelib/ui/uipathresolver.lua
Relative paths are resolved against the calling Lua source directory, optionally receive the .otui extension, and are rewritten only when the candidate file exists.
UI entry-point integration
modules/corelib/ui/uipathresolver.lua
The three UI entry points are wrapped once and forward resolved paths while preserving their remaining arguments.

Estimated code review effort: 3 (Moderate) | ~15 minutes

Sequence Diagram(s)

sequenceDiagram
  participant LuaCaller
  participant g_ui
  participant resolveCallerPath
  participant g_resources
  participant OriginalUIImplementation

  LuaCaller->>g_ui: loadUI, displayUI, or importStyle(path, ...)
  g_ui->>resolveCallerPath: resolveCallerPath(path)
  resolveCallerPath->>g_resources: fileExists(caller directory + path)
  g_resources-->>resolveCallerPath: existence result
  resolveCallerPath-->>g_ui: resolved or original path
  g_ui->>OriginalUIImplementation: invoke(path, ...)
Loading

Poem

I’m a rabbit with paths in my paws,
Finding UI files by Lua’s source laws.
A carrot, a callback, a file in sight—
Wrapped once, then resolved just right.
Hop, hop—styles load tonight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the core fix: resolving module-relative UI paths when invoked from callbacks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@mimus-assa

Copy link
Copy Markdown
Contributor Author

Verified in-game on macOS against our production 15.25 server (client patched with this exact change):

  • Imbuement shrine window now opens (was completely broken)
  • Outfit window opens
  • Channels dialog (Ctrl+O) opens
  • The per-login game_shop / game_stash Lua errors no longer appear in the client log

Linux and Windows ship the same patched Lua; verification pending on those OSes.

@majestyotbr

Copy link
Copy Markdown
Contributor

Is it still necessary? The main branch has been fixed.

@dudantas

Copy link
Copy Markdown
Member

Thanks for the fix. The callback-relative path issue appears to be real, and the new resolver is correctly loaded through corelib's dofiles 'ui'.

However, wrapping g_ui.importStyle() with an absolute path introduces a regression in device-specific style resolution.

For example, when a module calls:

g_ui.importStyle("foo")

the wrapper may pass /modules/example/foo to UIManager::importStyle(). That function builds the device-specific filename as:

importStyle(deviceName + "." + fileName, false);

which produces something like windows./modules/example/foo instead of /modules/example/windows.foo.

As a result, OS/device-specific styles can no longer be resolved whenever the base style is rewritten to an absolute path.

Please either preserve the original relative argument for importStyle() or update the C++ device-style lookup to preserve the resolved directory. A regression test covering checkDeviceStyles = true with a caller-relative style and a device-specific variant would also be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants