seven more windows are the reference's own files
- seven more windows run off the player's own patch chain: the durability shield (`DurabilityFrame.xml`, with a Rust-side repair for the reference's own stale side-glyph inset the director caught on screen), `Localization.xml` + `GameTime.xml` (so `LocalizeFrames` finally runs from `VARIABLES_LOADED`, and the clock's 24-hour flag comes from where 1.12 sets it), the chat-link tooltip (`ItemRef.xml` — draggable now, and its shift-click arm fills the friend/ignore/who boxes as the reference's does), the mirror timers (`MirrorTimer.xml`, which exposed that our `STATICPOPUP_NUMDIALOGS` was 2 against the reference's 4, so a third breath/fatigue/feign bar could never be claimed), the zone splash with the fading kit, the item-text reader and the combo-point dots — each under the reference's published frame names, which is what an addon reaches for - `AddMessage` swallows a text it cannot use, silently, as the reference does: `0x795590` is a `lua_isstring` gate whose failure edge jumps straight to the epilogue — no line, no error, no record — and stock FrameXML takes that path: a full keyring calls `AddMessage(NO_EMPTY_KEYRING_SLOTS, …)` and no such GlobalString exists in 1.12, so it is a dead click on a real client where benilla put a script-error dialog on it; byte-verified by a four-worker cross-check in wow-re, and NOT generalised — a bad r/g/b/a still adds the line opaque white, only a bad text shows nothing Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -36,6 +36,48 @@ pub(super) use plain::REG_MESSAGEFRAME_METHODS;
|
||||
pub(super) use scrolling::REG_SCROLLINGMESSAGEFRAME_METHODS;
|
||||
|
||||
/// Install both classes' method tables (and the chat input globals the scrolling one carries).
|
||||
/// **`AddMessage`'s text argument, and the three ways it silently swallows the whole call**
|
||||
/// (wow-5875-re `system/ui/scratch/addmessage-text-gate-silent-skip.md`, 4-worker cross-check
|
||||
/// 2026-08-31). `None` means *do nothing at all* — no line, no record, no error.
|
||||
///
|
||||
/// `CSimpleMessageFrame::AddMessage 0x795590` fetches Lua index 2 through `lua_isstring 0x6f3510`
|
||||
/// and then `lua_tostring 0x6f3690`, and **three** results jump to the function's own epilogue at
|
||||
/// `0x79582b` (`pop edi / pop esi / xor eax,eax / pop ebx / leave / ret` — zero Lua values, no
|
||||
/// push, and the virtual enqueue at `0x795825 call [eax+0x90]` never reached):
|
||||
///
|
||||
/// ```text
|
||||
/// 79561b mov edx,2
|
||||
/// 795625 call 0x6f3510 ; lua_isstring — tags 4 (string) and 3 (number) only
|
||||
/// 79562c je 0x79582b ; nil / boolean / table / function -> silent
|
||||
/// 795642 je 0x79582b ; lua_tostring returned NULL -> silent
|
||||
/// 79564b je 0x79582b ; cmp byte ptr [ebx],0 — EMPTY string -> silent
|
||||
/// ```
|
||||
///
|
||||
/// `ScrollingMessageFrame:AddMessage 0x792900` is the same shape (gate `0x79299c`, `je 0x792b81` =
|
||||
/// its own epilogue), so `DEFAULT_CHAT_FRAME:AddMessage(nil)` is silent too.
|
||||
///
|
||||
/// **This is not a general argument law and must not be read as one** (1717's rule: the shape is
|
||||
/// settled per binding). The *colour* arguments beside it behave the opposite way — a bad `r`/`g`/
|
||||
/// `b`/`a` jumps to `0x79581c`, which still calls the enqueue with the `0xffffffff` opaque white
|
||||
/// staged at `0x795658`. Only a bad **text** shows nothing. The three *receiver* guards
|
||||
/// (`0x847ef8` "used '.' instead of ':'", `0x847ec0`, `0x847e98`) do still raise, and they are
|
||||
/// upstream of this.
|
||||
///
|
||||
/// **Why it is load-bearing rather than pedantry.** Stock FrameXML takes this path: 1.12's
|
||||
/// `ContainerFrame.lua:753` reports a full keyring with
|
||||
/// `UIErrorsFrame:AddMessage(NO_EMPTY_KEYRING_SLOTS, 1.0, 0.1, 0.1, 1.0)` and **no such
|
||||
/// GlobalString exists** — the shipped `GlobalStrings.lua` defines only
|
||||
/// `NO_EMPTY_KEYRING_SLOTS_ERROR`, a rename that updated the definition and not the use. So a
|
||||
/// player dropping a key onto a full keyring gets a dead click on a real client. Taking a `String`
|
||||
/// here (which is what this did until 1751's fourth window swapped the bag bar in) raised instead,
|
||||
/// putting a script-error dialog on an ordinary player gesture that the reference never shows.
|
||||
///
|
||||
/// A **number** is accepted and rendered as its decimal text — `0x6f7c80` retags the slot in
|
||||
/// place — which is exactly [`super::binding_abi::optional_string`]'s coercion.
|
||||
pub(super) fn message_text(lua: &mlua::Lua, v: &mlua::Value) -> Option<String> {
|
||||
super::binding_abi::optional_string(lua, v).filter(|s| !s.is_empty())
|
||||
}
|
||||
|
||||
/// Install `SetJustifyH`/`GetJustifyH`/`SetJustifyV`/`GetJustifyV` on a message-frame method
|
||||
/// table. Both classes carry all four (see [`crate::widget::ScrollingMessageState::justify`] for
|
||||
/// the method-table bytes that settled it), and the law they obey is
|
||||
|
||||
@@ -70,9 +70,18 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
|
||||
// AddMessage(text [, r, g, b [, a]]) — binding 0x795590.
|
||||
//
|
||||
// The TEXT is `super::message_text` — an `lua_isstring` gate whose failure is a silent jump to
|
||||
// this function's own epilogue, not a raise, and the same for a NULL coercion and for the
|
||||
// empty string. Its doc carries the bytes and the reason it matters; the short version is that
|
||||
// stock `ContainerFrame.lua` really does reach `AddMessage(nil, …)` on a full keyring, and
|
||||
// taking a `String` here (which this did until 1751's fourth window) put a script-error dialog
|
||||
// on a gesture the reference answers with a dead click.
|
||||
//
|
||||
// r/g/b are required **as a trio** (three presence checks ANDed at 0x7956xx); absent ⇒ hard
|
||||
// 0xFFFFFFFF white. The fourth numeric is a real alpha, `lua_isnumber`-gated with a default of
|
||||
// 1.0 (`795752: mov [ebp-0x18],0x3f800000`).
|
||||
// 1.0 (`795752: mov [ebp-0x18],0x3f800000`). **Deliberately NOT the same shape as the text:** a
|
||||
// bad colour jumps to `0x79581c`, which still enqueues the line in opaque white. Only a bad
|
||||
// text shows nothing.
|
||||
//
|
||||
// **The signature stops there, and the closure's arity is the enforcement.** The corpus's three
|
||||
// blocked callers all pass a sixth argument they believe is a hold time; mlua hands us the
|
||||
@@ -82,7 +91,10 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
m.set(
|
||||
"AddMessage",
|
||||
lua.create_function(
|
||||
|lua, (this, text, r, g, b, a): (Table, String, Value, Value, Value, Value)| {
|
||||
|lua, (this, text, r, g, b, a): (Table, Value, Value, Value, Value, Value)| {
|
||||
let Some(text) = super::message_text(lua, &text) else {
|
||||
return Ok(());
|
||||
};
|
||||
let has_rgb = !matches!(
|
||||
(&r, &g, &b),
|
||||
(Value::Nil, _, _) | (_, Value::Nil, _) | (_, _, Value::Nil)
|
||||
@@ -245,6 +257,61 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
/// **A text `AddMessage` cannot use is swallowed whole — silently, with nothing added and
|
||||
/// nothing raised** (`0x795590`'s three jumps to its own epilogue; see [`super::super`]'s
|
||||
/// `message_text`).
|
||||
///
|
||||
/// The defect this pins is not hypothetical and not ours to have guessed at: our binding took
|
||||
/// its text as an mlua `String`, which raises on nil, and 1.12's own `ContainerFrame.lua:753`
|
||||
/// reports a full keyring through `UIErrorsFrame:AddMessage(NO_EMPTY_KEYRING_SLOTS, …)` — a
|
||||
/// GlobalString the shipped `GlobalStrings.lua` never defines. So dropping a key onto a full
|
||||
/// keyring put a script-error dialog on screen where the reference gives a dead click. It
|
||||
/// surfaced the moment 1751's third window made the bag bar the reference's own file.
|
||||
///
|
||||
/// The three silent cases and the two that are NOT silent are pinned together, because the
|
||||
/// pair is the finding: a bad **colour** still adds the line (opaque white), and the receiver
|
||||
/// guards still raise. Reading the text gate as a general argument law is exactly 1717's error.
|
||||
#[test]
|
||||
fn addmessage_swallows_a_text_it_cannot_use_and_only_the_text() {
|
||||
let s = UiScript::new().unwrap();
|
||||
s.run("CreateFrame('MessageFrame', 'MF')").unwrap();
|
||||
|
||||
// `lua_isstring 0x6f3510` accepts tags 4 and 3 only — everything else jumps to 0x79582b.
|
||||
for bad in ["nil", "", "true", "{}", "print"] {
|
||||
s.run(&format!("MF:AddMessage({bad})"))
|
||||
.unwrap_or_else(|e| panic!("AddMessage({bad}) must not raise: {e}"));
|
||||
}
|
||||
// …and so does the empty string, on its own `cmp byte ptr [ebx],0` at 0x79564b.
|
||||
s.run("MF:AddMessage('')").unwrap();
|
||||
assert_eq!(
|
||||
num_messages(&s, "MF"),
|
||||
0,
|
||||
"nil, absent, boolean, table, function and \"\" each add NOTHING"
|
||||
);
|
||||
|
||||
// A number IS a usable text — tag 3 passes the gate and `0x6f7c80` retags the slot in
|
||||
// place, so it renders as its decimal form.
|
||||
s.run("MF:AddMessage(42)").unwrap();
|
||||
assert_eq!(num_messages(&s, "MF"), 1, "a number is a text");
|
||||
|
||||
// The colour arguments are the OPPOSITE shape and that is the load-bearing negative: a bad
|
||||
// r/g/b jumps to 0x79581c, which still enqueues with the 0xffffffff white staged at
|
||||
// 0x795658. Suppressing the line here would be the same over-generalisation in reverse.
|
||||
s.run("MF:AddMessage('kept', {}, nil, 'x')").unwrap();
|
||||
assert_eq!(
|
||||
num_messages(&s, "MF"),
|
||||
2,
|
||||
"a bad colour still adds the line"
|
||||
);
|
||||
|
||||
// The receiver guards are upstream of all of it and DO raise (0x847ef8 — "used '.' instead
|
||||
// of ':'").
|
||||
assert!(
|
||||
s.run("MF.AddMessage('dot')").is_err(),
|
||||
"a '.'-instead-of-':' call still raises"
|
||||
);
|
||||
}
|
||||
|
||||
/// `SetInsertMode` is a MessageFrame method and **only** a MessageFrame method — a plain Frame
|
||||
/// and a ScrollingMessageFrame both resolve it to nil, which is what a duck-typing addon reads.
|
||||
#[test]
|
||||
|
||||
@@ -92,13 +92,20 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// method-table bytes that put all four on BOTH tables.
|
||||
super::install_justify(lua, &m, "ScrollingMessageFrame")?;
|
||||
|
||||
// AddMessage(text [, r, g, b [, id]]) — the id (5th arg) is accepted and ignored (v1 has no
|
||||
// per-line UpdateColorByID). rgb absent ⇒ white; the state quantizes round-half-up and forces
|
||||
// the line opaque, then the fade drives its alpha.
|
||||
// AddMessage(text [, r, g, b [, id]]) — binding 0x792900, the id (5th arg) accepted and
|
||||
// ignored (v1 has no per-line UpdateColorByID). rgb absent ⇒ white; the state quantizes
|
||||
// round-half-up and forces the line opaque, then the fade drives its alpha.
|
||||
//
|
||||
// The TEXT gate is the sibling class's, verbatim — gate `0x79299c`, `je 0x792b81` = this
|
||||
// function's own epilogue — so `DEFAULT_CHAT_FRAME:AddMessage(nil)` is silent too. See
|
||||
// `super::message_text`.
|
||||
m.set(
|
||||
"AddMessage",
|
||||
lua.create_function(
|
||||
|lua, (this, text, r, g, b): (Table, String, Value, Value, Value)| {
|
||||
|lua, (this, text, r, g, b): (Table, Value, Value, Value, Value)| {
|
||||
let Some(text) = super::message_text(lua, &text) else {
|
||||
return Ok(());
|
||||
};
|
||||
let has_rgb = !matches!(
|
||||
(&r, &g, &b),
|
||||
(Value::Nil, _, _) | (_, Value::Nil, _) | (_, _, Value::Nil)
|
||||
|
||||
@@ -185,7 +185,7 @@ fn a_region_resolves_even_when_its_owner_frame_has_no_rect() {
|
||||
/// visible geometry a few links down.
|
||||
///
|
||||
/// Reported as B264 (carni, 2026-08-13): opening the social pane drew a stray dropdown capsule at
|
||||
/// the bottom of the screen next to the action bar. `BenillaFriendsDropDown` carries no anchors —
|
||||
/// the bottom of the screen next to the action bar. `FriendsDropDown` carries no anchors —
|
||||
/// *exactly* as the reference's own `FriendsDropDown` does (`FriendsFrame.xml` l.598), and the
|
||||
/// reference draws nothing — so every texture of `UIDropDownMenuTemplate` hung off a phantom rect
|
||||
/// at (0,0). The shape below is that template's first two textures verbatim.
|
||||
|
||||
Reference in New Issue
Block a user