-
Notifications
You must be signed in to change notification settings - Fork 15
Expand file tree
/
Copy pathOffsets.h
More file actions
8481 lines (8059 loc) · 493 KB
/
Copy pathOffsets.h
File metadata and controls
8481 lines (8059 loc) · 493 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// This file is part of ClassicAPI.
//
// ClassicAPI is free software: you can redistribute it and/or modify it under the terms
// of the GNU General Public License as published by the Free Software Foundation, either
// version 3 of the License, or (at your option) any later version.
//
// ClassicAPI is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE. See the GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License along with
// ClassicAPI. If not, see <https://www.gnu.org/licenses/>.
#pragma once
enum Offsets {
FUN_FRAME_SCRIPT_INITIALIZE = 0x7039E0,
FUN_INVALID_FUNCTION_PTR_CHECK = 0x42A320,
FUN_LOAD_SCRIPT_FUNCTIONS = 0x490250,
// Fatal-error dispatcher. `__fastcall(uint code)`. Writes `code`
// to `DAT_00882738` and chains into the process-teardown path;
// `FUN_00403BC0` reads the code at exit and shows a localized
// popup keyed off it. Code `10` = "interface files are corrupt"
// — the engine fires this from FrameXML's `FUN_0048FBF0` hash
// mismatch and per-addon `FUN_0051F240` hash mismatch (plus
// other call sites we haven't pinned down by byte pattern).
// Hooked by `Security::FrameXMLBypass` to suppress code 10 so
// our in-memory file modifications (Bindings.xml splice +
// embedded `!!!ClassicAPI` addon) don't trigger termination.
FUN_FATAL_ERROR = 0x00401560,
// Master glue Lua init — clean linear caller of all 5 glue batch
// trampolines (60 main + 11 char-select + 24 char-create + 10 realm
// + 4 frame globals = 109 total). Body: 0x0046ABB0..0x0046ABD2,
// 35 bytes, single caller (FUN_0046A7B0). Runs once per glue boot:
// initial launch and every world→glue return (log out). Post-hook
// is the glue analog of `FUN_LOAD_SCRIPT_FUNCTIONS` — by the time
// it returns, `VAR_LUA_STATE` points at the freshly populated glue
// state, so `FrameScript_RegisterFunction` writes land on it.
FUN_LOAD_GLUE_SCRIPT_FUNCTIONS = 0x0046ABB0,
// Binding manager internals used by the direct-action/override binding backport.
//
// Stock Lua `SetBinding` wrapper (`int __fastcall(lua_State *)`).
// The SetBindingSpell/Item/Macro/Click wrappers delegate to it after
// constructing their action command, preserving punctuation-key
// normalization, current-set selection, the Boolean return value, and the
// native UPDATE_BINDINGS notification.
FUN_SCRIPT_SET_BINDING = 0x004B8000,
// Hardware-key dispatch, before the normal binding table is resolved:
// int __thiscall(manager, key, isDown)
// Hooking here lets owner-scoped override bindings remain a separate
// layer rather than temporarily mutating the character/account binding
// table. The normal dispatcher is called unchanged when no override wins.
FUN_BINDING_KEY_DISPATCH = 0x004B7990,
// Executes a resolved binding command:
// int __thiscall(manager, command, isDown)
// Vanilla has no SPELL/ITEM/MACRO/CLICK direct-action command handlers. The
// hook recognizes those four families and delegates every native command
// to the client.
FUN_BINDING_COMMAND_EXECUTE = 0x004B7B50,
// Binding-manager `this`-relative flag, set to 1 while a resolved binding
// command runs. FUN_BINDING_KEY_DISPATCH writes 1 immediately before its
// call to FUN_BINDING_COMMAND_EXECUTE and 0 immediately after. Override
// commands dispatched outside that engine path must mirror it so native
// command handlers observe the same manager state a real keypress produces.
// Verified in the FUN_BINDING_KEY_DISPATCH disassembly.
OFF_BINDING_MANAGER_EXECUTING = 0xD8,
// Frame-script execution context and its nesting depth (the `DAT_00ceeac0`
// dance referenced below). Shared by the binding dispatcher and the
// tooltip/frame script invokers. FUN_BINDING_KEY_DISPATCH saves the context
// pointer, zeroes it for the nested command, restores it afterward, and
// floors the depth back to 0. `Bindings::Api` mirrors this when it runs an
// override command outside the engine's own dispatch. Verified in that
// disassembly.
VAR_FRAMESCRIPT_EXEC_CONTEXT = 0x00CEEAC0,
VAR_FRAMESCRIPT_EXEC_CONTEXT_DEPTH = 0x00CEEAC4,
// GameTooltip script-method prologue helpers (used to resolve self → CFrameScriptObject*).
// Underlying Lua C API names per [[docs/LuaCAPI.md]] are `lua_rawgeti`
// (0x6F3BC0) and `lua_touserdata` (0x6F3740). The Set* method prologue's
// call sequence
// PushObject(L, idx, 0); // == lua_rawgeti(L, idx, 0) — pushes the
// // lightuserdata at table[0]
// GetObject(L, -1); // == lua_touserdata(L, -1) — extracts
// // the raw CFrameScriptObject *
// is the canonical "Lua frame table → C++ object" path. The reverse
// (`GameTooltip:GetOwner` etc.) is just `lua_rawgeti(L, REGISTRY,
// [cobj+OFF_COBJECT_LUA_REGISTRY_REF])` — the engine pre-allocates
// the registry handle when the frame is created from Lua.
FUN_FRAMESCRIPT_PUSH_OBJECT = 0x6F3BC0,
FUN_FRAMESCRIPT_GET_OBJECT = 0x6F3740,
// CFrameScriptObject layout: the registry refkey allocated when the
// engine first exposes the C++ object to Lua. Pushing a frame back
// to Lua is `lua_rawgeti(L, LUA_REGISTRYINDEX, [cobj+0x08])`.
OFF_COBJECT_LUA_REGISTRY_REF = 0x08,
// Within a CGFrame, the embedded LayoutFrame sub-object starts here.
// Engine code that wants to invoke LayoutFrame methods polymorphically
// (anchoring, positioning) stores the LayoutFrame* — i.e.
// `(Frame*)obj + 0x24` — instead of the bare Frame*. Reverse
// lookups (e.g. GameTooltip:GetOwner) subtract this back out.
OFF_FRAME_LAYOUT_SUBOBJECT = 0x24,
// Inner spell-tooltip builder, called from Script_GameTooltip_SetSpell at 0x00532E92.
// __thiscall(spellID, 0, 0, isPet, 0, 0, 0); we always pass isPet=0.
FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP = 0x0052E610,
// Existing GameTooltip method-table entries we dispatch to from
// backported convenience methods. Each is `int __fastcall(void *L)`
// expecting the standard self+args layout on the Lua stack.
// (Slot numbers are the method-registry index per `docs/raw_methods.txt`.)
FUN_SCRIPT_GAMETOOLTIP_SET_HYPERLINK = 0x00531FD0, // slot 12
FUN_SCRIPT_GAMETOOLTIP_SET_INVENTORY_ITEM = 0x00532EE0, // slot 19
FUN_SCRIPT_GAMETOOLTIP_SET_UNIT_BUFF = 0x00534AC0, // slot 32
FUN_SCRIPT_GAMETOOLTIP_SET_UNIT_DEBUFF = 0x00534E30, // slot 33
FUN_SCRIPT_GAMETOOLTIP_SET_TALENT = 0x00535170, // slot 34
// Iterator that registers an array of frame-method bindings on a per-frame-type
// method registry (e.g. VAR_GAMETOOLTIP_METHOD_REGISTRY for GameTooltip).
// __fastcall(ecx = MethodEntry table, edx = count, [stack] = context).
FUN_REGISTER_FRAME_METHODS = 0x00701D80,
VAR_GAMETOOLTIP_METHOD_REGISTRY = 0x00C0CF20,
// CGameTooltip line pool (see TODO #97). The engine caps AddLine
// (FUN_00530270) at `numLinesAllocated`, which the XML-OnLoad scan
// (FUN_00529650, vtable slot 9) sets to the count of contiguous
// `<name>TextLeftN`/`TextRightN` FontStrings the template declares
// (30). Tooltip::LinePool grows it: it appends its own C++-created
// FontStrings to the three parallel arrays and bumps this count. Each
// array is a {count@+0x0, cap@+0x4, data@+0x8} descriptor; the
// reallocators realloc `data` to newCount*4 and copy `cap` old elements.
VAR_GAMETOOLTIP_VTABLE = 0x00808F60, // type guard for a resolved tooltip object
OFF_GAMETOOLTIP_NUM_LINES_ALLOC = 0x320, // int — pool size AddLine gates on
OFF_GAMETOOLTIP_TEXTLEFT_DESC = 0x324, // {count,cap,data}; data (+0x8) = CSimpleFontString*[]
OFF_GAMETOOLTIP_TEXTRIGHT_DESC = 0x330, // symmetric right column
OFF_GAMETOOLTIP_WRAPFLAG_DESC = 0x33C, // per-line wrap int[]
FUN_TOOLTIP_REALLOC_PTR_ARRAY = 0x00536C80, // __thiscall(desc, newCount) — pointer arrays
FUN_TOOLTIP_REALLOC_INT_ARRAY = 0x004368C0, // __thiscall(desc, newCount) — wrap-flag array
// GameTooltip:SetHyperlinkCompareItem (Tooltip::Compare) — builds the
// equipped item's tooltip natively (no Lua roundtrip) and appends
// colored stat deltas. FUN_GAMETOOLTIP_BUILD_ITEM is the engine's own
// item-tooltip builder that SetInventoryItem/SetHyperlink call:
// __thiscall(self, itemID, guid*, guid*, a4, a5, a6, headerFlag, a8, a9)
// Proven arg shape from FUN_005353b0/FUN_00535700 (all flags 0 → a clean
// item tooltip). The builder's own headerFlag (param_7) path merges the
// "Currently Equipped" text into the name line and force-wraps it, so we
// build clean (flag 0) and prepend the grey header ourselves by shifting
// the line FontStrings — see FUN_FONTSTRING_SET_* / OFF_FONTSTRING_*.
// FUN_GAMETOOLTIP_ADD_LINE is the raw add-line body (see LinePool notes);
// its color args are pointers to a 4-byte packed color {b,g,r,a} — the
// Lua AddLine handler (FUN_00531630) builds it as 0xFF<rr><gg><bb>, i.e.
// uint32 `0xFF000000 | r<<16 | g<<8 | b` in little-endian memory.
FUN_GAMETOOLTIP_BUILD_ITEM = 0x0052B650,
// OnTooltipSetItem support. FUN_GAMETOOLTIP_SCRIPT_RESOLVER is the
// CGGameTooltip vtable method (vtable 0x00808F60) that maps a script name
// to its handler slot; __thiscall(self, const char *name) -> int* slot,
// 0 if the name isn't a tooltip script. It first delegates to the base
// frame resolver, then checks OnTooltipSetDefaultAnchor(+0x444) /
// OnTooltipCleared(+0x44c) / OnTooltipAddMoney(+0x454) — each an 8-byte
// {handler, context} slot. Vanilla has no OnTooltipSetItem, and the object
// (alloc size 0x460) has no free 8-byte slot, so we co-hook this to hand
// out a C-side per-tooltip cell for that name. FUN_FRAME_INVOKE_SCRIPT is
// the engine's real script invoker __fastcall(handler /*ecx = slot[0]*/,
// frame /*edx*/): it binds the global `this` = frame and runs the handler
// under its own protected lua_pcall (0 args). We call it directly from the
// item-builder co-hook to fire OnTooltipSetItem.
// NOTE: the clear fires OnTooltipCleared via the wrapper FUN_00702690
// (self, &slot), which additionally stamps the global exec-context
// (DAT_00ceeac0) from slot[1]. That context mutation is only valid from
// the engine's top-level frame-script dispatch — firing it from deep in a
// nested Lua->C->Lua stack (e.g. an addon's SetAuctionItem wrapper)
// corrupts the outer script's context and faults. So we skip the wrapper
// and call the inner invoker, which is self-contained.
FUN_GAMETOOLTIP_SCRIPT_RESOLVER = 0x005295D0,
FUN_FRAME_INVOKE_SCRIPT = 0x00704D50,
// The arg-passing sibling of FUN_FRAME_INVOKE_SCRIPT — every frame
// script that carries values (OnClick(button), OnMouseWheel(delta),
// OnUpdate(elapsed), OnValueChanged(value), OnKeyDown(key), OnEvent's
// args, …) funnels through here. __cdecl(int handlerRef, void *frame,
// const char *fmt, void *vaPtr): fmt is a printf-style spec string
// (`%d`/`%u`/`%f`/`%s`), vaPtr points at the packed vararg buffer
// (4-byte stride for d/u/s, 8 for f). It sets the `arg1..argN` globals
// from the format (saving/restoring the previous values), sets the
// `this` global to `frame`, then runs the handler under a protected
// lua_pcall with **zero Lua args** and the engine error handler
// (VAR_FRAMESCRIPT_ERROR_HANDLER_REF) as errfunc. FUN_FRAME_INVOKE_SCRIPT
// is the same shape for the no-arg scripts (OnShow/OnHide/OnEnter/…).
// `Frame::ScriptArgs` co-hooks both to additionally pass the handler
// modern positional args (self, arg1..argN). The frame's own Lua object
// ref lives at frame+OFF_COBJECT_LUA_REF (refcount at
// OFF_COBJECT_LUA_REFCOUNT); FUN_FRAMESCRIPT_OBJECT_SCRIPT_REGISTER
// lazily creates it when the refcount is 0.
FUN_FRAME_RUN_SCRIPT_ARGS = 0x00704F10,
// The base ScriptObject's OnEvent handler slot — an 8-byte
// {handler, context} pair at frame+0x0C (from FUN_00702590, the base
// resolver every frame-type resolver chains through, and confirmed by
// the event dispatchers FUN_00703E50 / FUN_00703F50 passing `frame+0xC`
// as the slot). `*(int*)(frame + OFF_FRAME_ONEVENT_SLOT)` is the OnEvent
// handler ref; `Frame::ScriptArgs` compares it against the handler ref
// the runner is invoking to detect an OnEvent dispatch (exact — each
// SetScript makes a distinct ref, so a function bound to two scripts
// still differs per slot) and prepend the `event` positional.
OFF_FRAME_ONEVENT_SLOT = 0x0C,
// Registry ref (an int, not a pointer) to the frame-script message
// handler the engine passes as the `errfunc` to every script pcall —
// read as `*(int*)VAR_FRAMESCRIPT_ERROR_HANDLER_REF`, pushed via
// lua_rawgeti(REGISTRY, ref). Set once at engine init.
VAR_FRAMESCRIPT_ERROR_HANDLER_REF = 0x008722C8,
// The base Frame script-name resolver — __thiscall(frame, const char *name)
// -> int* slot, 0 for an unknown name. Maps the standard base-frame scripts
// to their 8-byte {handler, context} slots on the frame (OnLoad@+0x118,
// OnUpdate@+0x128, OnEnter@+0x140, … OnKeyUp@+0x190) after delegating to the
// ScriptObject base FUN_00702590 (OnEvent@+0xC). Every frame TYPE's resolver
// chains through this (the tooltip resolver above calls it first), so
// co-hooking it here lets `Frame::Attributes` make `OnAttributeChanged`
// SetScript/GetScript/HookScript-able on ALL frames — for that one name the
// co-hook hands back an external per-frame cell (frames are immortal in 1.12,
// so a pointer-keyed cell never goes stale). Same pattern + ABI modeling as
// FUN_GAMETOOLTIP_SCRIPT_RESOLVER.
FUN_FRAME_SCRIPT_RESOLVER = 0x0076A0D0,
// Button widget script-name resolver — __thiscall(button, const char *name)
// -> int* slot, 0 for an unknown name. Sits at vtable+0xC on all four
// button-family vtables (Button + subclasses). Delegates to the base frame
// resolver FUN_FRAME_SCRIPT_RESOLVER first, then maps the two button
// scripts: OnClick -> button+0x4CC, OnDoubleClick -> button+0x4D4 (each an
// 8-byte {handler, context} slot). Vanilla has no PreClick/PostClick, so
// `Frame::ClickEvents` co-hooks this to hand out an external per-button cell
// for those two names — same technique as FUN_GAMETOOLTIP_SCRIPT_RESOLVER.
FUN_BUTTON_SCRIPT_RESOLVER = 0x00778C50,
// Button OnClick script slot offset (the resolver's OnClick return). Used
// by `Frame::ClickEvents` to recognize an OnClick fire at the runner hook
// below: the fire passes slotPtr == button + this offset.
OFF_BUTTON_ONCLICK_HANDLER = 0x4CC,
// Frame-script runner WITH exec-context stamping — __cdecl(void *frame,
// int *slotPtr, const char *fmt, void *vaPtr). Saves DAT_00ceeac0, stamps
// it from slotPtr[1] (the cell's context), then calls FUN_FRAME_RUN_SCRIPT_ARGS
// (the arg'd runner Frame::ScriptArgs hooks) and restores. Every arg'd
// input-script fire funnels through here via FUN_007026F0 (its variadic
// forwarder); the event dispatcher FUN_00703F50 is the only other caller.
// ONE hook, owned by `Frame::RunnerHook`, fans out to subscribers that each
// claim a disjoint slot address (a second MinHook here would abort the
// whole install — subscribe instead): `Frame::ClickEvents` gates on
// `slotPtr == frame + OFF_BUTTON_ONCLICK_HANDLER` (an exact OnClick match —
// no other fire passes that slot) and brackets OnClick with PreClick /
// PostClick by re-invoking with the same (fmt, vaPtr) so the button-name
// arg is reused; `Frame::UnitEvent` gates on `slotPtr == frame +
// OFF_FRAME_ONEVENT_SLOT` (the dispatcher's per-frame OnEvent fire) and
// suppresses the call when a RegisterUnitEvent filter rejects arg1.
// Deliberately NOT the button click vmethod FUN_00779540, which SuperWoW
// inline-hooks for click-casting (a second hook there faults, ERROR #132 —
// see the note near FUN_SCRIPT_FRAME_GET_STRATA); this runner is uncontested.
FUN_FRAME_RUN_SCRIPT_WITH_CONTEXT = 0x00702710,
// The other per-object tooltip builders, co-hooked the same way as
// FUN_GAMETOOLTIP_BUILD_ITEM to back OnTooltipSetSpell / OnTooltipSetUnit /
// OnTooltipSetGameObject (see Tooltip::SetEvents). Each is the single funnel
// its Set*/mouseover paths converge on and clears the tooltip
// (FUN_00530050) before repopulating, so firing after it covers every way
// that object type gets set:
// - Spell FUN_GAMETOOLTIP_BUILD_SPELL_TOOLTIP (0x0052E610, 7 stack args,
// RET 0x1c) — SetSpell/SetSpellByID/SetTalent/SetShapeshift funnel.
// - Unit (0x00529fe0, __thiscall(self, guid*), RET 4) — SetUnit +
// the two engine mouseover paths (FUN_004919d0 / FUN_00492890).
// Returns an int the caller (Script_GameTooltip_SetUnit) tests, so the
// co-hook must forward the original's return value.
// - GameObject (0x0052aa20, __thiscall(self, guid*), RET 4) — the GO
// hover populator; resolves the GO (TYPEMASK_GAMEOBJECT) and writes the
// GO field at +0x370.
FUN_GAMETOOLTIP_BUILD_UNIT = 0x00529FE0,
FUN_GAMETOOLTIP_BUILD_GAMEOBJECT = 0x0052AA20,
// Pointer to the GameTooltip CFrameScriptObject the engine's mouseover
// setter builds into (`DAT_00b4b3c4`; the value at this address is the
// tooltip object). `Frame::Attributes` reads it to build the unit tooltip
// for offline/out-of-range party & raid members, which FUN_00492890 skips.
VAR_GAMETOOLTIP_OBJECT_PTR = 0x00B4B3C4,
// The tooltip's OnTooltipSetDefaultAnchor handler slot (the `{handler,
// context}` pair the script resolver maps that name to). The engine
// mouseover setter fires this before building to (re)establish the
// tooltip owner/anchor via FrameXML's GameTooltip_SetDefaultAnchor —
// without it a rebuild after the tooltip was hidden has no owner and
// stays invisible. `Frame::Attributes` fires it (via the self-contained
// FUN_FRAME_INVOKE_SCRIPT) before the offline roster build.
OFF_TOOLTIP_SET_DEFAULT_ANCHOR_HANDLER = 0x444,
FUN_GAMETOOLTIP_ADD_LINE = 0x00530270, // __thiscall(self, left, right, lColorBGRA*, rColorBGRA*, wrap)
OFF_GAMETOOLTIP_NUM_LINES = 0x31C, // int — live line count (AddLine index; +0x320 is the cap)
// The displayed item's identity is read via the existing
// OFF_TOOLTIP_ITEM_ID (+0x398) / OFF_TOOLTIP_ITEM_GUID_LO (+0x380) —
// link paths set the itemID, CGItem paths (SetInventoryItem/…) set only
// the GUID (see Tooltip::Compare::TooltipItemID and item/Tooltip.cpp).
// Line-shift primitives for prepending the grey "Currently Equipped"
// header. The per-line CSimpleFontString objects live in the parallel
// arrays at OFF_GAMETOOLTIP_TEXTLEFT/RIGHT_DESC (+8 = data). Each stores
// its text buffer pointer at +0xF0 and its 4-byte {b,g,r,a} color behind
// the pointer at +0xB8 (both verified from the setters below). We read a
// line's text/color and re-apply them one slot down, then set line 0.
FUN_FONTSTRING_SET_TEXT = 0x00771D80, // __thiscall(fs, text, flag=0)
FUN_FONTSTRING_SET_COLOR = 0x0077F750, // __thiscall(region, colorBGRA*) — generic region color setter (fontstrings + textures)
// Generic region color GETTER — `void __thiscall(region, uint32* outBGRA)`,
// writes the packed {b,g,r,a} (0xFFFFFFFF when the region has no explicit
// color). The read half of Texture:GetVertexColor (FUN_0079aa50); paired
// with FUN_FONTSTRING_SET_COLOR above. Backs EditBox:GetHighlightColor.
FUN_REGION_GET_COLOR = 0x0077F8C0,
OFF_FONTSTRING_TEXT = 0xF0, // char* — current text buffer
OFF_FONTSTRING_COLOR_PTR = 0xB8, // ptr → 4-byte {b,g,r,a} color storage
// A line's FontString is only positioned once shown: set the desired
// flag at +0xC4 then call SHOW (FUN_0077fcb0) / HIDE (FUN_0077fc60) —
// the exact pair AddLine (FUN_00530270) and the clear use. Moving text
// into a previously-empty (hidden) cell without this leaves it
// unpositioned, so the line-shift must replicate it.
FUN_FONTSTRING_SHOW = 0x0077FCB0, // __fastcall(fs) — realizes +0xC4
FUN_FONTSTRING_HIDE = 0x0077FC60, // __fastcall(fs)
OFF_FONTSTRING_SHOWN_FLAG = 0xC4, // int — desired-shown flag
// The per-tooltip clear only HIDES line FontStrings (sets +0xC8 = 0)
// and clears the left text; right-column text buffers are left intact.
// So a shift must decide a cell's content by its actually-shown flag
// (+0xC8), not by whether +0xF0 still holds (stale) text.
OFF_FONTSTRING_VISIBLE = 0xC8, // int — actually-shown flag
// FontString creation primitives used by Tooltip::LinePool to build the
// extra lines entirely in C++ (no addon Lua). Calling conventions and
// offsets mirrored from Script_CreateFontString (0x00773C30),
// Script_SetFontObject (0x0079D1A0 → 0x00770C60), and Script_SetPoint
// (0x007A2540 → 0x00767C70) — see TODO #97's investigation update.
FUN_GAMETOOLTIP_SETUP_LINES = 0x00529650, // vtable slot 9 — the pool scan we co-hook
VAR_SIMPLEFONTSTRING_POOL = 0x00CF4D10, // CSimpleFontString free-list pool (allocator `this`)
VAR_SIMPLEFONTSTRING_CLASS_TAG = 0x00846544, // ".?AVCSimpleFontString@@" (alloc debug tag)
FUN_REGION_POOL_ALLOC = 0x00760450, // __thiscall(pool, zeroInit=0, tag, line=-2) -> raw mem
FUN_SIMPLEFONTSTRING_CTOR = 0x00770D30, // __thiscall(mem, parent, layer, sublayer) -> fs
FUN_REGION_SET_NAME = 0x0076C650, // __thiscall(region, name) — registers _G[name]
FUN_FONTSTRING_SET_FONT = 0x00770C60, // __thiscall(fs + OFF_FONTSTRING_FONT_HOLDER, fontObject)
FUN_REGION_SET_POINT = 0x00767C70, // __thiscall(region+OFF_REGION_ANCHOR, point, relAnchor, relPoint, x, y, flag=1)
OFF_FONTSTRING_FONT_HOLDER = 0xCC, // font-reference sub-object (SetFont `this`)
OFF_FONTSTRING_FONT_OBJECT = 0xD0, // font object pointer (holder + 4)
OFF_REGION_ANCHOR = 0x24, // LayoutFrame anchor sub-object (SetPoint `this` / relativeTo base)
DRAWLAYER_ARTWORK = 2,
FRAMEPOINT_TOPLEFT = 0,
FRAMEPOINT_TOPRIGHT = 2,
FRAMEPOINT_LEFT = 3,
FRAMEPOINT_RIGHT = 5,
FRAMEPOINT_BOTTOMLEFT = 6,
FRAMEPOINT_BOTTOMRIGHT = 8,
// FUN_REGION_SET_POINT stores offsets in *internal* coordinates, not
// pixels. Script_SetPoint converts: `internal = pixel * [0x00832A44] /
// ([0x00832A4C] * 1024)` (FUN_0041ae60's factor × input ÷
// (FUN_0041ad70's return × DAT_007ffd68)). Both globals are runtime
// UI-scale floats; passing raw pixels makes offsets ~1000× too large.
// The REVERSE conversion (internal → UI pixels) is what the Script_*
// measure getters push: Script_GetStringWidth (0x0079E510) — and our
// GetStringHeight backport — compute `px = internal * [0x00832A4C] *
// 1024 / [0x00832A44]` (FUN_0041AE40(FUN_0041AD70() × DAT_007FFD68 × v)).
VAR_UI_COORD_SCALE_MUL = 0x00832A44, // float numerator
VAR_UI_COORD_SCALE_DIV = 0x00832A4C, // float denominator base
UI_COORD_SCALE_UNIT = 1024, // DAT_007ffd68
// Semantically, [0x832A44] is the SCREEN WIDTH IN ANCHOR UNITS (the
// full-screen x extent of the internal layout space) — that's why it's
// the px→internal numerator above — and [0x832A48] is its Y sibling
// (screen HEIGHT in anchor units). Verified via the gxu text position
// converter FUN_0041ade0: block/node positions are stored NORMALIZED,
// x = anchor/[0x832A44], y = anchor/[0x832A48] (the y global was
// previously unmapped — the x/y pair sits at +0x00/+0x04, the SetPoint
// denominator base at +0x08).
VAR_UI_ANCHOR_SCREEN_H = 0x00832A48,
// gxu text-raster dimensions — INT dwords holding the live render-target
// size in PIXELS ({0,0,640,480} fallback), written by FUN_005c2b50 from
// the render-target rect (FUN_0058a240) whenever it changes; every font
// page re-rasterizes on change (the FUN_005ca6f0 walk). These are the
// normalized→pen multipliers: the origin finalize FUN_005cdf70 computes
// node origin (+0x70/+0x74) = round(normalizedPos × [raster]) after
// folding the justify shift (right: +widthLimit, centre: +half) and
// vertical align; the emitter's font-height helper FUN_005C6FA0 is
// round(fontSize × [VAR_TEXT_RASTER_Y]) the same way (x sibling
// FUN_005C7010 uses _X). So TEXT PEN UNITS ARE RENDER-TARGET PIXELS, and
// pen-per-anchor = [VAR_TEXT_RASTER_*] / [VAR_UI_ANCHOR_SCREEN_*] per
// axis — the exact conversion Text::InlineTexture's flush uses to place
// icon regions (verified against a live probe: rasterX/anchorW matched
// the measured origin÷rect-edge quotient to four digits).
VAR_TEXT_RASTER_Y = 0x00C2B9A0,
VAR_TEXT_RASTER_X = 0x00C2B9A4,
// gxu text-node invalidate/reset — `__fastcall(node)`. Zeroes the
// built-line count (+0x9C, the draw builder's early-out gate), resets the
// link-rect state (+0x80/+0x90) and wrap cache (+0x68/+0x6C), and frees
// all 8 page buffers — the next paint's pre-pass re-runs the builder, so
// the emitter re-bakes the node from scratch. This is what the node
// colour setter FUN_005CCB40 calls on a colour change for accumulation
// (bit-3-clear) nodes; InlineTexture's flush calls it for a SEGMENTED
// bit-3 node whose BASE colour RGB changed (glue AddonList toggle), since
// baked per-glyph RGB can't be patched in place (|c runs own theirs).
FUN_TEXT_NODE_INVALIDATE = 0x005CDEF0,
// gxu font-face flags word. Bit 0 (0x1) = thin outline, bit 3 (0x8) =
// thick outline: the emitter's prologue (FUN_005CCBE0) reads
// *(fontFace+0x180) and grows the line height by [FLOAT_OUTLINE_EXTRA_*]
// pen px for outlined faces, because outline INK extends past the glyph
// metrics; the rebuild/color-sync shadow paths test the same bits. The
// inline-texture lead/trail pads read the same flags so an icon clears an
// outlined neighbour's ink (the coin-into-digits clip).
OFF_FONTFACE_FLAGS = 0x180,
// The engine's outline ink allowance, .rdata float constants (4.0 / 2.0
// pen px total, i.e. ~2 / ~1 px per side) — the exact values the emitter
// adds to line height for bit 3 / bit 0 outlined faces. Read live so we
// stay bit-identical with the engine's own compensation. The 2.0 constant
// double-duties as the MINIMUM font size numerator in the node ctor
// (FUN_005cd6d0 / FUN_005ccb80: fontSize floored at [0x801628]/rasterY =
// 2 pen px).
FLOAT_OUTLINE_EXTRA_THICK = 0x0080306C,
FLOAT_OUTLINE_EXTRA_THIN = 0x00801628,
// The node's BUILT line count — incremented once per emitted wrapped line
// by the draw builder (FUN_005CDC20, including the \n break case) and the
// builder's own early-out gate (`if (node+0x9C != 0) return`); zeroed by
// the node invalidate (FUN_TEXT_NODE_INVALIDATE). The render truth for
// FontString:GetNumLines once the node has painted.
OFF_TEXT_NODE_BUILT_LINES = 0x9C,
// Wrap break-array computer: `__thiscall(fs, const char *text, float
// wrapWidth /*fs-internal units*/, int *outBreaks, int cap)` → segment
// count (byte offsets into text, [0]=0). Routes through FUN_005C2430 →
// the wrap stepper, so it is icon-aware via the stepper co-hook. Verified
// from the GameTooltip auto-size (FUN_00530640), which fills a 20-entry
// array and measures each segment via FUN_FONTSTRING_MEASURE_SUBSTRING.
FUN_FONTSTRING_BREAK_ARRAY = 0x00772B60,
// Substring width measure: `__thiscall(fs, const char *text, int len)` →
// ST0 (len 0 = strlen). Same measure-core call + `out / fs+0x7C` shape as
// GetStringWidthInternal, but for an ARBITRARY string in the fs's font —
// no cache. Callers (xrefs): the GameTooltip auto-size FUN_00530640, which
// measures each WRAPPED SEGMENT of a wrap-enabled line between the
// FUN_00772B60 break positions and takes the max as the tooltip width —
// the icon-relevant consumer (an earlier note claimed nothing consumed
// this function; the xref list refutes it) — plus the editbox
// caret/selection cluster (FUN_0077DA80, FUN_0077DE70, FUN_0077D0D0),
// which must stay raw and is excluded by the editable/focused-buffer
// gates in the co-hook.
FUN_FONTSTRING_MEASURE_SUBSTRING = 0x00772AE0,
// FontString → gxu face resolution, for fs-level (measure-hook) callers
// that need the face the RENDER will use. The rebuild (FUN_007724a0)
// passes [fs+0xE0] (the font HANDLE) to the block creator FUN_0044d420,
// which hands [handle+0x20] to FUN_005c1c30 → FUN_005cd6d0, which stores
// it at node+0x44 — the emitter's face (`this` for every glyph call, and
// the +0x180 outline-flags carrier). So face = [[fs+0xE0]+0x20].
OFF_FONTSTRING_FONT_HANDLE = 0xE0,
OFF_FONT_HANDLE_FACE = 0x20,
// CSimpleTexture (`Texture` widget) creation + operate primitives, used by
// Text::InlineTexturePool to render inline |T icons as engine-owned,
// managed-pool, resident-kept textures (the residency fix — see
// docs/InlineTextureResidency.md). Mirrors Script_CreateTexture
// (FUN_00773A20) and the Texture frame-method handlers; the sibling of the
// CSimpleFontString path above. Same FUN_REGION_POOL_ALLOC / SetPoint /
// Show / Hide / SetColor (FUN_FONTSTRING_SET_COLOR) as the FontString pool.
VAR_SIMPLETEXTURE_POOL = 0x00CF4CE0, // &DAT_00cf4ce0 CSimpleTexture free-list pool (`this`)
VAR_SIMPLETEXTURE_CLASS_TAG = 0x00846588, // ".?AVCSimpleTexture@@" (alloc debug tag)
FUN_SIMPLETEXTURE_CTOR = 0x0076FC40, // __thiscall(mem, parent, layer, sublayer) -> tex
// --- MaskTexture object API (frame:CreateMaskTexture / Texture:AddMaskTexture) ---
// CreateMaskTexture mints its mask region by calling the engine's own
// Script_CreateTexture (reads (frame,name,layer,template) off the Lua stack and
// pushes the new CSimpleTexture), then hides it — the mask is a source, never drawn.
// The draw hook reads the mask's HTEXTURE (OFF_SIMPLETEXTURE_HTEXTURE, +0xCC, set
// by SetTexture and valid while hidden) + its rect (FUN_REGION_GET_RECT); the
// +0xC4 shown flag is OFF_REGION_DESIRED_SHOWN (defined below).
FUN_SCRIPT_CREATE_TEXTURE = 0x00773A20, // Script frame:CreateTexture, __fastcall(L)->int
// CSimpleTexture::SetTexture(path). Loads via FUN_00449D90, stores the owned
// HTEXTURE at +0xCC (releases the old via DecRef FUN_0041AED0), marks dirty.
// `__thiscall(tex, path, 0, *VAR_TEXTURE_BLEND_DEFAULT, 0) -> u32`. The
// ownership + engine batched region-draw is the residency win. Same-path
// early-out makes pooled reuse cheap.
FUN_SIMPLETEXTURE_SET_TEXTURE = 0x00770200,
// Script_Texture_SetTexture — the Lua `texture:SetTexture(...)` handler. Its
// NUMERIC branch (arg 2 a number) clamps r/g/b/a to [0,1] and fills the
// texture with a solid colour via FUN_00770360; its string branch loads a
// path through FUN_00770200 above. texture/ColorTexture.cpp aliases this whole
// handler as the 7.0 `SetColorTexture` (which IS exactly the numeric form), so
// the clamp and opaque-alpha default come straight from the engine.
FUN_SCRIPT_TEXTURE_SET_TEXTURE = 0x0079BB40,
// Script_Texture_SetTexCoord — the Lua `texture:SetTexCoord(...)` handler
// (entry 17 of the Texture method batch at 0x0087C128). Takes the 4-arg
// (left, right, top, bottom) and 8-arg corner forms and writes through to
// FUN_SIMPLETEXTURE_SET_TEXCOORD below. texture/Atlas.cpp delegates to this
// handler rather than the native setter so an atlas set inherits the engine's
// own arg validation and both coordinate forms.
FUN_SCRIPT_TEXTURE_SET_TEXCOORD = 0x0079BEB0,
// `texture:GetTexCoord()` (entry 16 of the same batch). Always returns 8
// values — four (x, y) corner pairs read from region +0x104 in the order
// UL, LL, UR, LR. texture/Atlas.cpp co-hooks both this and the setter so an
// atlas'd texture's coordinates address the sprite rather than the file it
// sits in; the pair is the only place that mapping is applied.
FUN_SCRIPT_TEXTURE_GET_TEXCOORD = 0x0079BDF0,
// CSimpleTexture::SetTexCoord — `__thiscall(tex, float[4])`. Struct field
// order is {top, left, bottom, right} = {v0, u0, v1, u1} (verified from the
// SetTexCoord handler 0x0079BEB0's Lua-arg → struct mapping). Natural
// texcoords (no v-flip — the engine region path is top-left origin).
FUN_SIMPLETEXTURE_SET_TEXCOORD = 0x00770410,
OFF_SIMPLETEXTURE_HTEXTURE = 0xCC, // owned HTEXTURE ref (diagnostic)
// The four drawn-quad corner POSITIONS: 4 vertices of {x, y, z} floats (0xC
// stride), order BL, TL, BR, TR (from FUN_REGION_STORE_CORNERS's writes). The
// region draw enqueue FUN_00772fd0 (0x00772fd0) hands region+0xD4 straight to
// the vertex batch as FOUR INDEPENDENT vertices — and region+0x104 as the
// matching 4 per-corner texcoords — never re-deriving an axis-aligned rect.
// So writing ROTATED x,y here draws a rotated quad with NO corner clipping
// (unlike 4.3.4's SetRotation, which rotates the texcoords instead). Used by
// texture/Rotation.cpp for Texture:SetRotation.
OFF_SIMPLETEXTURE_CORNERS = 0xD4,
// Region corner-store: `__thiscall(region, const float rect[4])` writes the
// drawn quad corners into region+0xD4..+0x100 from the rect ({yA,left,yB,right}
// screen px). The renderer only draws a region whose +0xD4 corners are
// populated (verified: the draw gate tests region+0xD4 != 0), and normally
// SetPoint→layout-resolve calls this. We call it directly to place an icon by
// its screen rect with NO anchors (anchoring 100+ textures/frame corrupts the
// UI-manager pending-layout list — the FUN_00765650 crash). Co-hooked by
// texture/Rotation.cpp, which re-applies rotation right after the engine
// restores the axis-aligned corners (layout resolve / SetTexCoord).
FUN_REGION_STORE_CORNERS = 0x007705B0,
// Texcoord crop applied to a {top,left,bottom,right} screen rect before the
// corner-store: shrinks right→left and bottom→top by the region's per-corner
// texcoord span (ABS of the +0x104 U/V differences). No-op for full 0..1
// texcoords. `__thiscall(region, float rect[4])`, rect in/out. The crop
// itself is unconditional; every engine call site gates it on
// OFF_REGION_TEXCOORD_MODIFIES_RECT below — with the flag clear (the
// default) a partial SetTexCoord draws at the region's FULL size, so
// callers replicating an engine store must apply the same gate.
FUN_REGION_TEXCOORD_CROP = 0x00770570,
// The SetTexCoordModifiesRect flag (int). Written from the Lua boolean by
// Script_SetTexCoordModifiesRect (0x0079C080, `piVar4[0x49]`), read back
// 1/nil by Script_GetTexCoordModifiesRect (0x0079C120). All three
// FUN_REGION_TEXCOORD_CROP call sites test it: the SetTexCoord writers
// (FUN_00770410 4-arg, FUN_007704C0 8-arg corner form) crop+store only
// when set, and the layout-resolve store path (FUN_00770670) crops when
// set / stores the raw anchor rect (+0x40..+0x4C) when clear. Zero by
// default (region ctor), so the crop never runs unless an addon opts in.
OFF_REGION_TEXCOORD_MODIFIES_RECT = 0x124,
// Get the region's resolved screen rect: `__thiscall(region+OFF_REGION_ANCHOR,
// float out[4]) -> int` (1 = valid, 0 = not laid out yet). Reads the anchor
// sub-object's +0x40..+0x4C = {top, left, bottom, right}, gated on the
// rect-valid bit [+0x3c]&1. This is the engine's own source for the corner
// rect (see FUN_00770410's SetTexCoord path); texture/Rotation.cpp reads it to
// rebuild axis-aligned corners before rotating.
FUN_REGION_GET_RECT = 0x00768320,
// __thiscall(region+OFF_REGION_ANCHOR) -> int (nonzero = layout dirty, needs a
// resolve). GetLeft gates its resolve on this. Paired with the flag-1 resolve
// below to read a hidden region's rect synchronously.
FUN_REGION_LAYOUT_DIRTY = 0x00768310,
// __thiscall(region+OFF_REGION_ANCHOR, int flag /*stack; pass 0*/) — force a
// synchronous layout resolve: recomputes the region's rect (+0x64) from its
// anchors right now, instead of waiting for the render's pending-layout pass.
// Verified from Show (FUN_0077FCB0 at 0x0077FCE1: PUSH 0; LEA ECX,[ESI+0x24];
// CALL). Load-bearing for tick-time icon placement: SetTexCoord
// (FUN_00770410) stores the DRAW CORNERS (+0xD4) from the rect as a side
// effect, so without a realize between SetPoint and SetTexCoord the corners
// are stored from the STALE pre-anchor rect — the region's rect then resolves
// correctly (diagnostics look perfect) but the renderer draws the corners,
// which stay zero/stale → invisible icons (and the old build's frozen
// top-left icons: corners stored from the zero rect at first apply).
FUN_REGION_LAYOUT_REALIZE = 0x00768060,
// CSimpleFontString::RebuildString — destroys the old gxu text node and
// creates the fresh one (FUN_0044d420), storing it at fs+0xF8. Gated on the
// fs dirty bit (+0x60 & 1), so it fires on TEXT CHANGES, not per frame.
// Co-hooked by Text::InlineTexture to map text node → owning fontstring —
// the key that lets inline-icon regions anchor to their owning line
// (1.12 chat lines ARE CSimpleFontStrings: the ScrollingMessageFrame's
// display refresh FUN_00788750 SetTexts/anchors/shows one fs per visible
// line — verified against the 4.3.4 CSimpleEmbeddedTexture model, which
// anchors its icon regions to the owning fontstring the same way).
FUN_FONTSTRING_REBUILD_STRING = 0x007724A0,
// fs+0xF8 holds an HTEXTBLOCK handle, NOT the node itself: FUN_0044d420
// allocates the 12-byte handle {vtbl, refcount, node}, FUN_005c1c30 writes
// the created text node into handle+8, and FUN_0041af10 AddRefs and returns
// the handle. The layout's node (what the emitter/paint hooks see) is
// therefore *( *(fs+0xF8) + 8 ).
OFF_FONTSTRING_TEXT_BLOCK = 0xF8, // HTEXTBLOCK handle (0 when dirty/empty)
// CSimpleFontString live text color: count at +0xB4, array pointer at +0xB8
// (uint32 BGRA per slot; slot 0 = the base color, alpha = byte 3; a parallel
// per-glyph alpha-byte array hangs off +0xA8/+0xA4). Written by the fs
// SetColor (FUN_FONTSTRING_SET_COLOR 0x0077F750: stores the dword at
// (*(u32**)(fs+0xB8))[0] + the alpha byte at (*(u8**)(fs+0xA8))[0], then
// fires the color-update vmethod +0x20). Count 0 = never colored = default
// opaque white. THE CHAT-FADE SIGNAL: the ScrollingMessageFrame's fader
// (FUN_00788460, gated on the SetFading flag at smf+0x360; per-line state
// {rgba@+4, shown@+8, timeVisibleLeft@+0xC, fadeLeft@+0x10} in the
// stride-0x10 line array at smf+0x3A4) animates each visible line
// fontstring's alpha byte through this same SetColor every frame, then
// hides the fs at fade end. Text::InlineTexturePool mirrors this byte onto
// the line's icon regions each tick so inline icons fade with their line
// (regions parent to the CHAT FRAME, so the frame's own alpha byte —
// frame+0xC8, per Script_GetAlpha 0x00774DC0 — already modulates them via
// the engine's parent×region product; only the fs's own component needs
// mirroring).
OFF_FONTSTRING_COLOR_COUNT = 0xB4,
OFF_FONTSTRING_COLOR_ARRAY = 0xB8,
OFF_TEXTBLOCK_NODE = 0x8, // the gxu text node inside the handle
// Byte of fontstring state flags; bit 1 = "text block needs rebuild".
// RebuildString (0x7724A0) gates on it at entry, releases the old block
// (+0xF8 = 0), and only CREATES a new one if the fs rect is resolved —
// a SetText during an unresolved rect leaves the fs blockless with the
// refcounted zombie node still painting. If the bit is also clear at that
// point, nothing ever rebuilds (the stuck-blockless state InlineTexture's
// flush nudges by re-setting this bit).
OFF_FONTSTRING_DIRTY_FLAGS = 0x60,
// The per-node draw builder: walks a node's wrapped lines and calls the
// glyph emitter (FUN_TEXT_EMITTER) once per line. Called from the paint's
// per-node pre-pass FUN_005cd6a0 (`__thiscall(node)`, no stack args). We
// co-hook it to stamp exact BUILD BOUNDARIES for the emitter's first-line
// detection — the old `text == node+text-ptr` heuristic silently failed on
// pfUI-processed chat lines (stale/preprocessed pointer), leaving inherited
// records on reused node addresses (ghost icons) or never clearing them.
FUN_TEXT_DRAW_BUILDER = 0x005CDC20,
// The gxu text-node FREE: unlinks the node from its layout lists and pushes
// it onto the node free list (DAT_00c2b98c) for reuse. Single caller —
// FUN_005c1d00, the HTEXTBLOCK handle release (handle vtbl 0x008026e4 dtor
// 0x0044D5C0 → 0x005c1d00 → here). This is the choke point where every
// text node dies and its address becomes reusable: InlineTexture hooks it
// to erase ALL per-node state exactly at death, making stale-record and
// stale-owner bugs (ghost icons, orphaned records) structurally impossible
// instead of heuristically guarded.
FUN_TEXT_NODE_FREE = 0x005CD950,
// Ensure a text node is laid out: `__fastcall(node)` -> FUN_005cd3f0 + the
// draw builder FUN_005CDC20 (runs the glyph emitter and finalizes the node
// origin +0x70/+0x74), then clears node+0xc0. Same "ensure built" the paint
// pass (FUN_005c8fe0) calls per node; safe to call directly (the builder
// re-lays the node — a clean node just re-emits the same glyphs). Verified
// callers: the paint pass and FUN_005cd4d0 (the width/resize helper).
// Text::InlineTexture drives it PRE-RENDER from the SMF-refresh early-apply
// so a chat line's icons are recorded before the frame draws.
FUN_TEXT_ENSURE_BUILT = 0x005CD6A0,
// CScrollingMessageFrame display refresh — `__thiscall(smf, int newestIdx)`.
// Rebuilds the visible line set bottom-up: per slot it SetTexts the line
// fontstring (FUN_00788af0 -> SetText FUN_00771d80 + enqueue-resolve
// FUN_007680e0 flag 1), (re)anchors newly-allocated lines, shows/hides by
// message. Every new message shifts the newest index, so EVERY visible slot
// is re-SetText'd -> its inline icons must be recomputed; the engine builds
// the line lazily at paint, so icons land one frame after the glyphs (the
// one-frame lag). Text::InlineTexture co-hooks this and, post-refresh,
// forces each icon-bearing line to resolve+build and applies its icon
// placement synchronously — pre-render (the refresh runs from the SMF's
// Lua/event-driven methods, never mid-render), so icons draw with their
// glyphs the same frame. Line-entry layout verified from the decompile:
// count@+0x3A0, stride-0x10 entry array@+0x3A4, entry+8 = the line fs.
FUN_SMF_DISPLAY_REFRESH = 0x00788750,
OFF_SMF_LINE_COUNT = 0x3A0, // int: allocated visible-line count
OFF_SMF_LINE_ARRAY = 0x3A4, // base of the stride-0x10 line-entry array
SMF_LINE_STRIDE = 0x10, // per-line-entry stride
OFF_SMF_LINE_FONTSTRING = 0x8, // entry+8 = the line's CSimpleFontString
// THE pen↔anchor unit bridge. RebuildString (0x7724A0) multiplies every
// text-unit quantity by region+0x7C when crossing into node creation:
// nodePos = inset×s + rect corner, nodeFontH = fontPx×s, spacing = +0xF4×s,
// and divides rect extents by s for the text-unit width/height. SetParent
// (FUN_0076AB10) propagates the parent's +0x7C down the frame tree — it's
// the per-object layout/UI-scale chain. This single scalar is what every
// earlier "derive the scale" attempt (13/16, ownerH/fontH) was guessing at.
OFF_LAYOUT_SCALE = 0x7C, // float: anchor units per text/pen unit
OFF_FONTSTRING_INSET_X = 0x110, // float, text units (RebuildString posX term)
OFF_FONTSTRING_INSET_Y = 0x114, // float, text units (RebuildString posY term)
// CSimpleRegion::SetParentAndLayer — __thiscall(region, parentFrame, layer,
// show). Handles old-parent unlink + new-parent region-registry insert +
// conditional Show. Verified from the region base ctor FUN_0077F640 and the
// message frame's per-line setup (FUN_00788750 calls it with (frame, 2, 1)).
FUN_REGION_SET_PARENT_AND_LAYER = 0x0077FD10,
OFF_REGION_PARENT = 0x9C, // region's parent frame ptr (read by Show's gates)
OFF_REGION_DESIRED_SHOWN = 0xC4, // Show (FUN_0077FCB0) NO-OPS unless this is set;
// engine callers always write it before show/hide
OFF_REGION_ACTUALLY_SHOWN = 0xC8, // 1 after Show completed (the realize latch)
// Owning-frame recovery for the inline-icon pool (the 3.3.5 ownership model,
// no overlay). Chat renders through the strata walker FUN_007657d0 →
// per-frame draw-list rebuild FUN_00765920 → per-layer render FUN_0076FB00 →
// text paint (NOT through the child-frame render FUN_0076B3F0). So
// Text::InlineTexturePool co-hooks the two below:
// • FUN_FRAME_DRAWLIST_REBUILD — `__fastcall(frame)`, receives the frame
// cleanly and owns its 5 inline draw layers at frame + OFF_FRAME_LAYER_BASE
// + i*FRAME_LAYER_STRIDE. We record layer→frame so the layer-render hook
// can recover the owning frame with no offset-scan / vtable guessing.
// • FUN_FRAME_LAYER_RENDER — `__fastcall(layer)`, the per-layer paint that
// directly wraps the chat text paint. We bracket the icon pool's pass here
// (the flush runs nested) and look the frame up in the layer→frame map.
FUN_FRAME_DRAWLIST_REBUILD = 0x00765920,
FUN_FRAME_LAYER_RENDER = 0x0076FB00,
OFF_FRAME_LAYER_BASE = 0x1C, // first inline draw layer, region-relative
FRAME_LAYER_STRIDE = 0x30, // per-layer stride
FRAME_LAYER_COUNT = 5, // BACKGROUND..OVERLAY
// Resolved on-screen rect of a region: 4 floats at regionBase+0x64 (=
// LayoutFrame+0x40), in GxU SCREEN PIXELS — verified: FUN_00770670 reads
// them and FUN_007705b0 stores them straight as GxU vertex corners. Layout is
// {yA, left, yB, right} (x at [1]/[3], y at [0]/[2]); use min/max to get
// left/top without pinning which y index is top.
OFF_REGION_RECT = 0x64,
// "Currently displayed thing" state fields on a GameTooltip frame
// instance. Each Set* path writes one of these (and zero or two
// others), and the per-tooltip Clear at FUN_00530050 zeroes all of
// them on Hide/before-redraw. The Get* methods are simple reads —
// whichever field is non-zero tells us what kind of tooltip is up.
//
// Verified by decoding the builder functions:
// - BuildItemTooltip (0x0052B650) writes +0x380/+0x384 (item
// GUID, only when there's a real CGItem) and
// +0x398 (itemID) at 0x0052B6CE / 0x0052B6FE.
// - BuildSpellTooltip (0x0052E610) writes +0x39C (spellID) at
// 0x0052E6D5 (param_7==0 branch — skipped for
// the next-rank tooltip side-build).
// - BuildUnitTooltip (FUN_00529FE0) writes +0x368/+0x36C (unit
// GUID) — see SetUnit block below.
// - BuildGameObjectTooltip (0x0052AA20) writes +0x370/+0x374
// (gameobject GUID) at 0x0052AA52 / 0x0052AA59.
// Only call site is the in-world hover handler
// FUN_00492890; no Lua `SetGameObject` method.
// - Clear (FUN_GAMETOOLTIP_CLEAR) zeroes unit(+0x368)/GO(+0x370)/
// itemID(+0x398)/spell(+0x39c) — but NOT the item GUID +0x380/+0x384.
// That omission means the item GUID goes stale across a switch to a
// tooltip that shows no item (SetUnitAura, SetEquipmentSet, …), so
// GameTooltip:GetItem would resolve the *previous* item. We co-hook
// the clear (Tooltip::ClearItemGuid) to zero +0x380/+0x384 too, so
// every fresh tooltip resets it and only the item builder re-sets it.
// All 14 Set*/builder paths route through this clear, so the co-hook
// covers them uniformly. __fastcall(self).
FUN_GAMETOOLTIP_CLEAR = 0x00530050,
OFF_TOOLTIP_ITEM_GUID_LO = 0x380, // 0 for SetItemByID (no CGItem); stale-safe via the clear co-hook
OFF_TOOLTIP_ITEM_GUID_HI = 0x384,
OFF_TOOLTIP_ITEM_ID = 0x398,
OFF_TOOLTIP_SPELL_ID = 0x39C,
// Auction/compare descriptor. Script_GameTooltip_SetAuctionItem
// (0x00535810) has no CGItem instance, so it stashes the listing's
// random-property (suffix) id at tooltip+0x424 and marks the
// compare-descriptor valid by writing the builder's compare flag to
// this[0x110] (tooltip+0x440) = 1 (SetAuctionItem passes param_6=1;
// every other Set* path passes 0, and the builder writes it on each
// param_7==0 build — so +0x440 is a reliable per-build gate, not
// stale). This is 1.12's analog of 3.3.5's `tooltip[0x130]`-gated
// `piVar5[0x2d]` random-property read in GameTooltip:GetItem. GetItem
// reads +0x424 (only when +0x440 is set) to put the suffix in the link.
OFF_TOOLTIP_COMPARE_FLAG = 0x440,
OFF_TOOLTIP_COMPARE_SUFFIX = 0x424,
// Unit GUID written by the inner unit-tooltip builder
// (FUN_00529FE0) when `tooltip:SetUnit(token)` resolves the token
// to a non-zero GUID. Cleared by the same `FUN_00530050` clear
// that handles the item/spell IDs above, so `(lo|hi) == 0` means
// "no unit currently displayed" — same gating pattern HasSpell /
// HasItem use.
OFF_TOOLTIP_UNIT_GUID_LO = 0x368,
OFF_TOOLTIP_UNIT_GUID_HI = 0x36C,
// GameObject GUID written by the in-world hover tooltip populator
// (FUN_0052AA20) — there is no Lua-callable `SetGameObject` method
// in vanilla, so this slot only ever fills when the player mouses
// over a gameobject in the world (nodes, chests, doors, etc.).
// Same clear/gating semantics as the unit GUID slot.
OFF_TOOLTIP_GAMEOBJECT_GUID_LO = 0x370,
OFF_TOOLTIP_GAMEOBJECT_GUID_HI = 0x374,
// Owner frame stored by `tooltip:SetOwner(frame, anchor)` and
// compared by `IsOwned`. Holds `owner_CObject + OFF_FRAME_LAYOUT_SUBOBJECT`
// (i.e. the LayoutFrame*, not the bare Frame*), or 0 if unowned.
// Verified via the helper at 0x0052FFE0 invoked by SetOwner's tail
// (writes `[this+0x314] = arg+0x24`) and Script_GameTooltip_IsOwned
// at 0x00530FE0 (reads `[edi+0x314]` and compares against the same
// `+0x24`-offset value).
OFF_TOOLTIP_OWNER = 0x314,
// CGItem → fully-dressed item link string. __fastcall(ecx = CGItem *)
// → const char *. Reads the item's itemID, quality, permanent
// enchant ID, random-properties seed/factor, and unique ID off the
// CGItem's instance block + descriptor, builds the dressed name via
// FUN_005D8BC0 (handles random-suffix decoration like "Foo of the
// Bear"), then sprintf's into the global buffer at DAT_00C0CF68 and
// returns its address. The returned pointer is to a long-lived
// engine global, safe to read until the next call.
//
// Same helper Script_GetContainerItemLink (0x004F9930) and
// Script_GetInventoryItemLink (0x004C8C10) call after resolving
// their slot-form args. Bypassing them lets us build dressed links
// for tooltips set via SetMerchantItem / SetLootItem / etc. where
// the item isn't in the player's bag/equipment.
FUN_GAMETOOLTIP_BUILD_ITEM_LINK = 0x0052AE00,
// CGItem → decorated instance display name. __thiscall(ecx = CGItem *,
// char *outBuf, uint outSize). Reads the instance's random-suffix ID
// off the descriptor (+0x98, gated on the broken flag) and writes the
// plain — uncolored, unbracketed — display name into outBuf:
// "Ethereum Torque of the Sorcerer" when a suffix is present, the base
// ItemStats name otherwise (via the item cache + ItemRandomProperties
// localized suffix at record +0x1c). This is the inner name builder
// FUN_GAMETOOLTIP_BUILD_ITEM_LINK wraps in brackets, so the name it
// produces matches GetItemLink's bracketed name and modern
// C_Item.GetItemName exactly. Inner formatter FUN_005D8B00.
FUN_ITEM_BUILD_INSTANCE_NAME = 0x005D8BC0,
// Inner name formatter behind FUN_ITEM_BUILD_INSTANCE_NAME, callable
// WITHOUT a CGItem: `__fastcall(char *out /*ecx*/, uint outSize /*edx*/,
// uint32 itemID, int suffixID)`. Fetches the ItemStats record for itemID
// and, when suffixID is a valid ItemRandomProperties row with a localized
// suffix name (record +0x1c + locale*4), formats base + suffix via
// ITEM_SUFFIX_TEMPLATE; otherwise the base name. Lets the by-STRING item
// paths (GetItemNameByID, C_Item.GetItemInfo) apply an item link's random
// suffix without a live item instance.
FUN_ITEM_BUILD_NAME_FROM_ID = 0x005D8B00,
// Engine's inventory swap-and-send. Same primitive
// `Script_EquipCursorItem` (0x00489660) uses after the cursor's
// source location has been resolved. Sends opcode 0x10D
// (CMSG_SWAP_INV_ITEM) for same-container swaps or 0x10C
// (CMSG_SWAP_ITEM) for cross-container, then runs the packet
// through the engine's own send pipeline at FUN_005AB630.
// (0x10C is SWAP_ITEM; CMSG_AUTOEQUIP_ITEM is 0x10A and belongs to
// the cursor/equip builder FUN_005E1480, not this one.)
//
// Signature:
// void __thiscall(
// CGPlayer *this,
// u32 srcItemGuidLo, u32 srcItemGuidHi,
// u32 srcContainerGuidLo, u32 srcContainerGuidHi,
// u32 srcLinearSlot,
// u32 dstContainerGuidLo, u32 dstContainerGuidHi,
// u32 dstLinearSlot,
// int flag);
//
// `flag` IS NOT COSMETIC — it gates a pre-send confirmation check,
// and with 0 this function can decide to send NOTHING AT ALL.
// Before building the packet, and only when the DESTINATION is not
// an equipment or bag-container slot, it resolves the item being
// moved (the destination's item when the source is an equipment
// slot, otherwise the source's) and then either:
// - stashes the parameters and returns, when that item is not in
// the item cache yet; or
// - stashes the parameters, fires event 0x120 (the bind
// confirmation dialog) and returns, when the item's `m_bonding`
// (record +0x194) is 2 (BIND_WHEN_EQUIPPED) and `FUN_005EA930`
// reports this character could equip it.
// Both paths send no packet and report nothing, since the function
// returns void — so a caller that moves a Bind-on-Equip item it
// could wear just silently does not move it.
//
// Passing 1 skips the gate, which is what the engine itself does
// when it re-issues a swap after the player accepts the dialog. That
// is correct for any caller whose two endpoints are both bag content
// slots, since nothing there can bind an item.
//
// Linear-slot encoding for sources/dests in player invMgr:
// 0..18 paperdoll (1-based slot - 1)
// 19..22 equipped bag containers (bag IDs 1..4 themselves)
// 23..38 backpack contents (1-based bag-0 slot S → 22 + S)
// For sources in a CGContainer (equipped bag B = 1..4), the
// container GUID is the bag's own GUID and srcLinearSlot is
// 0-based within that bag (1-based Lua slot - 1).
//
// This call neither reads nor writes the cursor-state globals at
// [0xBE0810] / [0xBE0814]; cursor visibility is purely a side
// effect of the cursor-pickup path that normally precedes it.
// Calling this directly produces a server-side swap with no
// client-side cursor manipulation.
FUN_INVENTORY_SWAP = 0x005E0C40,
// Sister helper to FUN_INVENTORY_SWAP — packet builder for
// `CMSG_SPLIT_ITEM` (opcode 0x10E). Same __thiscall ABI shape;
// the item-GUID args (param_1, param_2) are unused padding for
// ABI parity. Packet wire format:
// [0x10E, srcBag, srcSlot, dstBag, dstSlot, count]
// where srcBag/dstBag are byte-converted from container GUIDs by
// the same `FUN_005e13b0` helper the swap function uses
// (`0xFF` = INVENTORY_SLOT_BAG_0 / player, `19..22` = equipped bags).
//
// Server semantics are all-or-nothing: any failure
// (insufficient source, dest has different item, dest would
// overflow maxStack) leaves source untouched and emits
// `SMSG_INVENTORY_CHANGE_FAILURE`. No cursor involvement on send
// or response.
//
// Signature:
// void __thiscall(
// CGPlayer *this,
// u32 unused1, u32 unused2, // ABI padding
// u32 srcContainerLo, u32 srcContainerHi,
// u32 srcLinearSlot, // only low byte hits the wire
// u32 dstContainerLo, u32 dstContainerHi,
// u32 dstLinearSlot, // only low byte
// u32 count); // only low byte
FUN_INVENTORY_SPLIT = 0x005E1210,
// Third sibling in the same packet-builder family — `__thiscall`,
// same shared bag-byte converter (`FUN_005E13B0`), same send
// pipeline (`FUN_005AB630`). Builds `CMSG_AUTOSTORE_BAG_ITEM`
// (opcode 0x10B): "take this item and put it wherever it belongs
// in that container", with the DESTINATION SLOT CHOSEN BY THE
// SERVER. Wire format:
// [0x10B, srcBag, srcLinearSlot, dstBag]
//
// Signature — EIGHT stack args, `RET 0x20`. The count is from the
// RET, not from a decompiler parameter list: the last slot
// (`EBP+0x24`) is never READ by the body, but it IS popped, so a
// 7-arg declaration leaves the callee popping four bytes too many
// and ESP walks on every call. Trailing-ignored-arg is this
// family's habit — it is where swap keeps its `flag` and split its
// `count` — but the family is NOT uniform in arity: those two take
// nine (`RET 0x24`), this one eight.
// void __thiscall(
// CGPlayer *this,
// u32 unused1, u32 unused2, // EBP+0x08/+0x0C, unread
// u32 srcContainerLo, u32 srcContainerHi,
// u32 srcLinearSlot, // only low byte hits the wire
// u32 dstContainerLo, u32 dstContainerHi,
// u32 unused3); // EBP+0x24, unread but popped
//
// Note there is no dst slot argument at all — that is the point of
// the opcode. Server-side (`HandleAutoStoreBagItemOpcode`) it runs
// `CanStoreItem(dstBag, NULL_SLOT, …)`, which is a TWO-PASS search:
// first "merge into existing non-full stacks of this item" (filling
// a position/count vector, so one stack can be distributed across
// several destinations), then "find a free slot" for whatever count
// is left. So this single packet performs partial-stack
// consolidation with no stack-size lookup on our side at all.
//
// Note the value in that is NOT that the client's stack size could
// be wrong: 1.12 has no item DBC, so stack size arrives from the
// server and is cached in itemcache.wdb, and the client's copy
// normally agrees by construction. The value is availability —
// `C_Item.GetItemMaxStackSizeByID` is nil until an item's data has
// arrived, so anything that sizes stacks first has a cold-cache
// hole, and the server never needs to size them.
//
// The converter returns `0xFF` for any GUID absent from the
// player's invMgr container array, and the player's own GUID is
// absent — so passing the PLAYER as the destination sends
// `dstBag = 0xFF` (INVENTORY_SLOT_BAG_0), which the server reads as
// "search every bag". The builder guards that case: a converted
// `0xFF` is only allowed through when the destination GUID really is
// the player's, otherwise it drops the send silently. Consequence:
// the backpack is not separately addressable as a destination (it
// IS the player container), so bagID 0 means "anywhere it fits".
FUN_INVENTORY_AUTOSTORE = 0x005E12E0,
// Bank counterpart — `CMSG_AUTOSTORE_BANK_ITEM` (opcode 0x282).
// THREE stack args, `RET 0xC`. Wire: [0x282, srcBag, srcSlot].
// void __thiscall(
// CGPlayer *this,
// u32 srcContainerLo, u32 srcContainerHi,
// u32 srcLinearSlot); // only low byte hits the wire
//
// No destination of any kind, because the server DERIVES THE
// DIRECTION FROM THE SOURCE (`HandleAutoStoreBankItemOpcode`):
// a bank source runs `CanStoreItem(NULL_BAG, NULL_SLOT, …)` and
// lands in the inventory; an inventory source runs
// `CanBankItem(NULL_BAG, NULL_SLOT, …)` and lands in the bank.
// Both are the same merge-into-existing-stacks-then-free-slot
// search as 0x10B, just aimed at the other side.
//
// So this opcode moves an item ACROSS the inventory/bank boundary
// and cannot move one within its own side. Bank-internal
// consolidation is not expressible through autostore at all — it
// needs per-pair moves (`FUN_INVENTORY_SPLIT` / `_SWAP`).
//
// Unlike its siblings this one validates the source itself before
// sending: resolves the container by GUID (`FUN_00468460` with
// typeMask 1), bounds-checks the slot against the container's item
// array, and bails when the GUID in that slot is zero.
FUN_INVENTORY_AUTOSTORE_BANK = 0x005E18F0,
// Registers a single global Lua function. __fastcall(name, func).
FUN_FRAMESCRIPT_REGISTER_FUNCTION = 0x00704120,
// `FrameScript_Object::ScriptRegister(this, name)` — `__thiscall`,
// `this` = a `CFrameScriptObject *`. On first call (when `this+0x04`
// is zero) builds a Lua wrapper table `{[0] = lightuserdata(this)}`
// with `_G["__framescript_meta"]` as metatable, `luaL_ref`s it into
// the registry, stores the refkey at `this+0x08`. Always increments
// `this+0x04` (the Lua-side refcount). Optional `name` argument
// installs `_G[name] = wrapper` for engine-named frames.
//
// We call this in `PushNamePlateFrame` so the engine and our own
// C_NamePlate getters operate on the **same** wrapper table —
// every push through `lua_rawgeti(REGISTRY, this+0x08)` (the
// canonical engine path) lands on the same Lua object pfUI
// received in `NAME_PLATE_CREATED`, so addon-set fields
// (`plate.nameplate = decoratedButton`) survive engine-side
// re-fetches. Earlier note in `Info.cpp` warned about pinning the
// refcount; for pool-managed nameplates the engine never
// un-registers them anyway, so the pin is benign.
FUN_FRAMESCRIPT_OBJECT_SCRIPT_REGISTER = 0x00701BD0,
// `this+0x04` Lua refcount, incremented by `ScriptRegister`. We
// read it as a "has the engine ever exposed this CObject to Lua"
// probe — equivalent to checking `this+0x08 > 0` but more direct.
OFF_COBJECT_LUA_REFCOUNT = 0x04,
// `this+0x08` — the CObject's Lua-registry ref (an int key into the
// registry table). `lua_rawgeti(REGISTRY, this[+0x08])` pushes the
// frame's Lua-side object. Lazily populated by ScriptRegister when the
// refcount above is 0.
OFF_COBJECT_LUA_REF = 0x08,
// Direct cvar lookup — `__fastcall(const char *name) → CVar* | NULL`.
// Hash-table by-name lookup over the CVar registry; same call
// `Script_GetCVar` makes internally before the engine wraps the
// result in lua_pushstring + a "CVar doesn't exist" error path.
// Calling it directly lets us skip both the Lua roundtrip and the
// unknown-cvar error — we coerce NULL to false instead, matching