Skip to content

Commit eb4c42b

Browse files
committed
Update TODO.md
1 parent 2968f51 commit eb4c42b

1 file changed

Lines changed: 171 additions & 0 deletions

File tree

TODO.md

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3301,3 +3301,174 @@ the matching read. Both should be straightforward once the field
33013301
offset is identified — 3.3.5's `Script_*` equivalents are a good
33023302
starting point for what the parameter shape and bounds-clamp
33033303
behavior should be.
3304+
3305+
## 97. Lift GameTooltip's 30-line cap — hard
3306+
3307+
Vanilla 1.12's `GameTooltip:AddLine` silently drops any line past the
3308+
pool of `TextLeftN`/`TextRightN` FontStrings the XML template
3309+
pre-declares (30). Modern (3.3.5+) engines grow the pool on demand.
3310+
Addons that append comparison summaries, item-info footers, or aura
3311+
details to a full item tooltip get their tail chopped. Practical
3312+
example — pfUI's eqcompare block (base-stat deltas inline + extended
3313+
stats appended at the bottom) hits the cap on stat-heavy items where
3314+
the vendor tooltip already has ~22 lines and the compare block adds
3315+
another 10; extended-stat rows past line 30 disappear silently.
3316+
3317+
Also affects `ShoppingTooltip1`/`ShoppingTooltip2` (same
3318+
`GameTooltipTemplate`).
3319+
3320+
### Root cause (verified in Ghidra on the 1.12 Octo binary)
3321+
3322+
`AddLine` internal impl at `FUN_00530270` — the shared body called
3323+
from both `Script_GameTooltipAddLine` (`FUN_00531630`, method ptr
3324+
stored in the GameTooltip method table alongside the `"AddLine"`
3325+
string at `0x00854560`) and `Script_GameTooltipAddDoubleLine`:
3326+
3327+
```c
3328+
void FUN_00530270(int this, char *leftText, char *rightText,
3329+
colorLeft, colorRight, wrapFlag) {
3330+
if ((*(int *)(this + 0x31c) != *(int *)(this + 0x320)) && anyHasText) {
3331+
// set leftText on textLeft[numLines_used],
3332+
// set rightText on textRight[numLines_used],
3333+
// wrapFlag[numLines_used] = wrapFlag,
3334+
// ++numLines_used
3335+
}
3336+
// else: silently drop the line
3337+
}
3338+
```
3339+
3340+
`0x320 == 800` decimal, matches decompile. Pool exhausted ⇒ nothing
3341+
happens.
3342+
3343+
### CGameTooltip struct offsets (1.12)
3344+
3345+
Derived from `FUN_00530270` and the pool-init at `FUN_00529650`:
3346+
3347+
| Offset | Field |
3348+
|--------|----------------------------------------------------|
3349+
| `+0x31c` | `numLines` — current write index (0-based) |
3350+
| `+0x320` | `numLinesAllocated` — pool size (30 default) |
3351+
| `+0x328..+0x32c` | `textLeft[]` — array of `CSimpleFontString *`, count `+0x320` |
3352+
| `+0x334..+0x338` | `textRight[]` — same, symmetric right column |
3353+
| `+0x340..+0x344` | `wrapFlag[]` — per-line wrap bool |
3354+
3355+
Actual layout of the array slots: `+0x328` is the first ptr in an
3356+
external heap block whose base is `param_1[0xcb]` = `+0x32c`. The
3357+
init function reallocates all three arrays whenever `numLinesAllocated`
3358+
would change — see `FUN_00536c80` (the reallocator) and `FUN_004368c0`
3359+
(a matching alloc for the wrap-flag array of a different element size).
3360+
3361+
### The pool-init function
3362+
3363+
`FUN_00529650` — walks `%sTextLeftN` / `%sTextRightN` FontString names
3364+
from the FrameXML-declared pool, counts them, reallocates the three
3365+
arrays, stores the pointers. Called via vtable slot at `0x00808f84`
3366+
(only xref) — likely `SetOwner` or an equivalent virtual reset method
3367+
runs it. It's the natural growth path — if we can force it to see a
3368+
larger pool of Lua-visible FontStrings and trigger it, the arrays
3369+
resize themselves.
3370+
3371+
### The 3.3.5 reference (already REd)
3372+
3373+
3.3.5's `GameTooltip::AddLine` at `0x0061fec0` has the grow-on-demand
3374+
branch we want to backport. Shape:
3375+
3376+
```c
3377+
if (currentLine == numLinesAllocated - 1) {
3378+
format(buf, 256, "%sTextLeft%d", tooltipName, currentLine + 1);
3379+
auto *fs = SimpleFontString::create(this, /*layer*/2, /*subLayer*/1);
3380+
fs->setName(buf);
3381+
fs->setFontObject(prevLeft->fontObject);
3382+
fs->anchor(TOPLEFT, prevLeft, BOTTOMLEFT, 0.0, calculatedYOffset);
3383+
// ... mirror for TextRight ...
3384+
// ... append fs to textLeft[]/textRight[] at [numLinesAllocated],
3385+
// then ++numLinesAllocated.
3386+
}
3387+
// fall through to the existing "set text on line[currentLine]" path
3388+
```
3389+
3390+
3.3.5 helpers (offsets 3.3.5-specific — 1.12 equivalents need to be
3391+
re-derived):
3392+
- `FUN_00485240` — SimpleFontString constructor `(parent, layer, sublayer)`
3393+
- `FUN_0048b6c0` — set-name helper (registers under global `_G[name]`)
3394+
- `FUN_0048a260` — anchor helper `(this, anchorPoint, relativeTo, relativePoint, xoff, yoff)`
3395+
3396+
Struct offsets in 3.3.5 were `+0x2a4` (numLines) / `+0x2a8` (numAlloc) /
3397+
`+0x2b4` (textLeft) / `+0x2c0` (textRight) / `+0x2cc` (wrapFlag). All
3398+
shifted vs. 1.12 (see above), unsurprising given TBC/WotLK bloat.
3399+
3400+
### Implementation plan
3401+
3402+
**Approach A — pre-hook AddLine with a grow-if-needed prelude
3403+
(preferred)**:
3404+
3405+
1. Locate the 1.12 equivalents of the three 3.3.5 helpers. Trace them
3406+
from existing `CSimpleFontString`-creating sites in the engine —
3407+
the CreateFontString API dispatch is a good starting point (it's
3408+
Lua-visible and calls the same constructor).
3409+
2. Locate the array reallocators used by `FUN_00529650`
3410+
(`FUN_00536c80` for the two pointer arrays, `FUN_004368c0` for the
3411+
wrap-flag byte array).
3412+
3. MinHook `FUN_00530270`. Pre-hook body:
3413+
- If `numLines != numLinesAllocated`, call original — done.
3414+
- Otherwise: realloc the three arrays to `numLinesAllocated + 1`,
3415+
create two new `CSimpleFontString` (left + right), name them
3416+
`"%sTextLeft%d"` / `"%sTextRight%d"` with `tooltip->GetName()` and
3417+
the new index, anchor each below the prior-slot FontString on the
3418+
same side, use the prior FontString's `fontObject` (`+0xd8` in the
3419+
3.3.5 layout — confirm on 1.12) as the default, write the pointers
3420+
into `textLeft[numLinesAllocated]` / `textRight[numLinesAllocated]`,
3421+
zero-init `wrapFlag[numLinesAllocated]`, and bump
3422+
`numLinesAllocated`.
3423+
- Then tail-call the original — it now succeeds normally.
3424+
3425+
**Approach B — pre-declare a wider pool via Lua** (fallback if the
3426+
constructor helpers turn out to be too tangled):
3427+
3428+
1. In `!!!ClassicAPI` addon, at load time, call
3429+
`GameTooltip:CreateFontString("GameTooltipTextLeft" .. i, "ARTWORK",
3430+
"GameTooltipText")` for `i = 31..60`, plus `TextRightN`, plus set
3431+
anchors matching the pattern in the template.
3432+
2. Force the engine's pool-init to re-scan. If we can't reach the
3433+
vtable trigger cleanly, expose a small C entrypoint that walks the
3434+
pool the same way `FUN_00529650` does — takes `tooltip` as arg,
3435+
reallocates arrays, refills.
3436+
3437+
Approach B is less code but ties the cap to a fixed number (60 lines
3438+
is enough for pfUI's use case but arbitrary). Approach A grows on
3439+
demand like retail and has no ceiling.
3440+
3441+
### Testing
3442+
3443+
- Hover a random-suffix weapon with several equip-spell bonuses (Krol
3444+
Blade, Ironfoe, any ZG/AQ trinket) — tooltip should render >30 lines
3445+
without truncation.
3446+
- Hover an item that produces a 40+ line tooltip when pfUI's eqcompare
3447+
block is enabled (Priest Judgement Set chest against another
3448+
Judgement chest: item tooltip ~22 lines + compare block ~10 lines).
3449+
- Verify no crash / no visual glitches on:
3450+
- ShoppingTooltip1/2 (same class, same `GameTooltipTemplate`)
3451+
- Rapid re-hovers (create/destroy pressure on the added FontStrings)
3452+
- Item tooltips that stay under 30 lines (pre-existing behavior unchanged)
3453+
3454+
### Risks
3455+
3456+
- If the array reallocation logic in `FUN_00536c80` doesn't null-out
3457+
the tail on grow, `textLeft[N]`/`textRight[N]` might contain
3458+
garbage for a frame — anchor before write, or memset to zero.
3459+
- FontString lifetime: 3.3.5 leaks the extras for the tooltip
3460+
lifetime (they stay hidden when unused). GameTooltip is a
3461+
singleton, so leak is bounded. Fine.
3462+
- Interaction with `pfUI`'s existing tooltip skinning (frame borders,
3463+
status bar reposition, etc.): the layer we're modifying is beneath
3464+
the skinning; shouldn't touch anything visible unless anchor math
3465+
is wrong.
3466+
3467+
### Related
3468+
3469+
If we ever consider dropping the whole "GameTooltipTemplate declares
3470+
30 FontStrings in XML" scheme and instead having the engine start
3471+
with 0 and grow entirely on demand — that would be even cleaner, but
3472+
requires patching every consumer of the initial pool (there aren't
3473+
many, but they need audit). Filed for future if the incremental grow
3474+
turns out to be fragile.

0 commit comments

Comments
 (0)