@@ -3301,3 +3301,174 @@ the matching read. Both should be straightforward once the field
33013301offset is identified — 3.3.5's ` Script_* ` equivalents are a good
33023302starting point for what the parameter shape and bounds-clamp
33033303behavior 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