Skip to content

Commit dc13d7b

Browse files
committed
fix(fontstring): SetFormattedText uses engine str_format, not _G
SetFormattedText resolved _G.string.format and called it, so an addon that replaced string.format could hijack or break it. Push the engine's own str_format C function instead (strlib "format" entry 0x007FD3D0, verified by decode) and pcall it -- immune to global replacement, and matching retail, whose SetFormattedText formats in C. Also simpler: one PushCClosure replaces the _G table walk and its two failure branches. Verified in-game: with both string.format and the format global replaced by a stub, SetFormattedText("%d apples", 5) still renders "5 apples".
1 parent 99c0ab3 commit dc13d7b

3 files changed

Lines changed: 22 additions & 27 deletions

File tree

docs/API.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4173,10 +4173,9 @@ f:SetText("a really long guild or player name that will not fit")
41734173
### `fontstring:SetFormattedText(format [, ...])`
41744174

41754175
Sets the text to `string.format(format, ...)`. Later clients added
4176-
the method as a convenience over `SetText(format(...))`. The format
4177-
runs through the live `string.format`, so a bad format string raises
4178-
the same Lua error it would in script. The set goes through the same
4179-
engine path as `SetText`, so escape handling is identical.
4176+
the method as a convenience over `SetText(format(...))`. A bad format
4177+
string raises a normal Lua error. The set goes through the same engine
4178+
path as `SetText`, so escape handling is identical.
41804179

41814180
```lua
41824181
f:SetFormattedText("%d/%d (%.1f%%)", cur, max, cur / max * 100)

src/Offsets.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4823,6 +4823,15 @@ enum Offsets {
48234823
// `BaseLib::StringLib` registers `string.gmatch` as a direct alias of
48244824
// this function pointer — no wrapper.
48254825
FUN_LUA_STR_GFIND = 0x007FCFA0,
4826+
// `str_format` — the Lua 5.0 `string.format` C function (`int
4827+
// __fastcall(void *L)`), strlib entry at `0x00822dc0`. Verified by decode:
4828+
// reads the format at arg 1, handles `%%`, `%d/i/c`, `%e/E/f/g/G`,
4829+
// `%o/u/x/X`, `%s`, `%q`, with the "invalid option to 'format'" / "invalid
4830+
// format (width or precision too long)" errors. FontString:SetFormattedText
4831+
// pushes THIS directly and pcalls it, rather than resolving `_G.string.format`
4832+
// — so an addon replacing `string.format` cannot hijack or break it (matches
4833+
// retail, whose SetFormattedText formats in C).
4834+
FUN_LUA_STR_FORMAT = 0x007FD3D0,
48264835
// `math_fmod` — the Lua 5.0 `math.mod` C function (mathlib entry at
48274836
// `0x00822cb0`): `luaL_checknumber(1/2)` → `fmod(x, y)` → `lua_pushnumber`.
48284837
// 5.1 renamed the Lua-facing name to `math.fmod` with the identical C

src/fontstring/Metrics.cpp

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,14 @@
5252
// re-invalidates the layout like SetText does.
5353
// 1.12 has no SetWordWrap, so SetMaxLines(1) is
5454
// how you get a single-line, ellipsized label.
55-
// SetFormattedText(fmt,...) — string.format + SetText convenience; the
56-
// format runs through Lua's own string.format
57-
// under pcall, the set through the engine's
58-
// FUN_FONTSTRING_SET_TEXT (keeping the text
59-
// sanitizer/pipe handling identical to SetText).
55+
// SetFormattedText(fmt,...) — string.format + SetText convenience. The format
56+
// runs through the engine's OWN str_format C
57+
// function (FUN_LUA_STR_FORMAT) under pcall — NOT
58+
// _G.string.format, so an addon replacing
59+
// string.format can't hijack it (matches retail).
60+
// The set goes through FUN_FONTSTRING_SET_TEXT,
61+
// keeping the sanitizer/pipe handling identical to
62+
// SetText.
6063
//
6164
// Push conversion mirrors Script_GetStringWidth (0x0079E510): resolve self,
6265
// call the internal getter, convert anchor units → UI pixels, push.
@@ -366,24 +369,8 @@ int __fastcall Script_SetFormattedText(void *L) {
366369
}
367370
const int top = Game::Lua::GetTop(L);
368371
Game::Lua::CheckStack(L, top + 4);
369-
// _G["string"]["format"] — the live library function, so any addon
370-
// replacement of string.format is honoured, matching a Lua-side
371-
// SetText(format(...)).
372-
Game::Lua::PushString(L, "string");
373-
Game::Lua::GetTable(L, Game::Lua::GLOBALS_INDEX);
374-
if (Game::Lua::Type(L, -1) != Game::Lua::TYPE_TABLE) {
375-
Game::Lua::SetTop(L, top);
376-
Game::Lua::Error(L, "SetFormattedText: string library unavailable");
377-
return 0;
378-
}
379-
Game::Lua::PushString(L, "format");
380-
Game::Lua::GetTable(L, -2);
381-
Game::Lua::Remove(L, -2);
382-
if (Game::Lua::Type(L, -1) != Game::Lua::TYPE_FUNCTION) {
383-
Game::Lua::SetTop(L, top);
384-
Game::Lua::Error(L, "SetFormattedText: string.format unavailable");
385-
return 0;
386-
}
372+
Game::Lua::PushCClosure(
373+
L, reinterpret_cast<Game::Lua::CFunction>(Offsets::FUN_LUA_STR_FORMAT), 0);
387374
for (int i = 2; i <= top; ++i)
388375
Game::Lua::PushValue(L, i);
389376
if (Game::Lua::PCall(L, top - 1, 1, 0) != 0) {

0 commit comments

Comments
 (0)