Skip to content

Commit 1df4877

Browse files
committed
Serve player GUID from char-select before the world loads
Vanilla doesn't populate the local player object (and its +0xC0 GUID) until SMSG_UPDATE_OBJECT in the main loop, after FrameScript_Initialize and the addon load pass — so UnitGUID("player") / PlayerGuid() returned nil/0 at addon file-load on first login. Co-hook the glue enter-world worker (FUN_0046B500, behind Script_EnterWorld) to snapshot the selected character's GUID at the moment of entry: read from the selected-character struct (VAR_GLUE_SELECTED_CHAR; GUID at offset 0, verified against Script_GetCharacterInfo), gated on the "entering world" state code so the rename/locked bail paths don't capture. PlayerGuid() returns the live engine value when present and falls back to the captured GUID otherwise — the same value, just available from addon-load time. Captured fresh per entry, so a character switch can't serve a stale GUID. This makes the GUID *value* available early (keying SavedVariables, comparisons, the aura/cast caster checks); the player *object* still doesn't exist until +0xC0 populates. Also simplify GetPlayerGuid() to a passthrough now that UnitGUID("player") is reliably available early and cheap — the memo saved only a trivial C-call + snprintf and the GUID is session-stable. Add FUN_GLUE_ENTER_WORLD / VAR_GLUE_SELECTED_CHAR offsets.
1 parent 939b52a commit 1df4877

3 files changed

Lines changed: 77 additions & 17 deletions

File tree

AddOns/!!!ClassicAPI/Util/PlayerUtil.lua

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,8 @@ function PlayerUtil.GetCurrentSpeed()
3232
return GetUnitSpeed("player") / 7 * 100
3333
end
3434

35-
local playerGuid
3635
function GetPlayerGuid()
37-
if not playerGuid then
38-
playerGuid = UnitGUID("player")
39-
end
40-
return playerGuid
36+
return UnitGUID("player")
4137
end
4238

4339
function IsPlayerGuid(guid)

src/Offsets.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4205,6 +4205,29 @@ enum Offsets {
42054205
VAR_GLUE_LOGIN_READY1 = 0x00B41DFC,
42064206
VAR_GLUE_LOGIN_READY2 = 0x00B41E04,
42074207
VAR_GLUE_LOGIN_INPROGRESS = 0x00B41DA0,
4208+
// The same `VAR_GLUE_LOGIN_INPROGRESS` global doubles as a glue
4209+
// state-code: `FUN_GLUE_ENTER_WORLD` sets it to `8` ("entering world")
4210+
// on a successful commit, which `Unit::Identity`'s enter-world co-hook
4211+
// gates on (the rename / char-locked bail paths return before it).
4212+
GLUE_STATE_ENTERING_WORLD = 8,
4213+
4214+
// `FUN_0046B500` — the enter-world worker behind `Script_EnterWorld` (the
4215+
// glue "Enter World" button: `Script_EnterWorld` is just `call this; ret`).
4216+
// Computes the selected character's struct into VAR_GLUE_SELECTED_CHAR and,
4217+
// on success, sets VAR_GLUE_LOGIN_INPROGRESS = 8. `Unit::Identity` co-hooks
4218+
// it to capture the selected character's GUID *before* the engine creates
4219+
// the in-world player object — so `UnitGUID("player")` / `PlayerGuid()`
4220+
// answer from addon-load time instead of returning nil/0 until the
4221+
// SMSG_UPDATE_OBJECT that populates the player object (≈PLAYER_LOGIN).
4222+
FUN_GLUE_ENTER_WORLD = 0x0046B500,
4223+
4224+
// Selected-character struct pointer (glue): char-array base +
4225+
// selectedIndex*0x120, written by FUN_GLUE_ENTER_WORLD. The character's
4226+
// 64-bit login GUID is at offset 0x00 (name at +0x08, race +0x100, class
4227+
// +0x101, level +0x108 — verified against `Script_GetCharacterInfo` at
4228+
// `0x004732A0`). Entries are filled from SMSG_CHAR_ENUM.
4229+
VAR_GLUE_SELECTED_CHAR = 0x00B41DF4,
4230+
OFF_GLUE_CHAR_GUID = 0x00,
42084231

42094232
// Cursor state — single global type-tag at VAR_CURSOR_TYPE
42104233
// dispatches reads through one of several payload slots depending

src/unit/Identity.cpp

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,41 @@ namespace Unit::Identity {
2626

2727
using TokenToGUID_t = uint64_t(__fastcall *)(const char *token);
2828

29+
// The selected character's GUID, captured at the glue "Enter World" click
30+
// (the FUN_GLUE_ENTER_WORLD co-hook below) — i.e. before the engine has
31+
// created the in-world player object whose +0xC0 holds the live GUID.
32+
// `PlayerGuid()` serves this as a fallback during the early-login window so
33+
// `UnitGUID("player")` answers from addon-load time instead of returning nil
34+
// until SMSG_UPDATE_OBJECT lands (≈PLAYER_LOGIN). 0 = not captured yet.
35+
// Overwritten on every enter, so a character switch can't serve a stale GUID;
36+
// only ever read when the live engine value is still 0, and identical to it
37+
// once that's populated.
38+
static uint64_t g_charSelectGuid = 0;
39+
40+
// `FUN_GLUE_ENTER_WORLD` is `void(void)` (no args). Co-hook it to snapshot the
41+
// selected character's GUID once the entry is committed.
42+
using EnterWorld_t = void(__cdecl *)();
43+
static EnterWorld_t g_origEnterWorld = nullptr;
44+
45+
static void __cdecl EnterWorld_h() {
46+
g_origEnterWorld();
47+
// Only on a genuine commit — the rename / char-locked bail paths return
48+
// before the worker sets the state code to "entering world".
49+
if (*reinterpret_cast<const int *>(
50+
static_cast<uintptr_t>(Offsets::VAR_GLUE_LOGIN_INPROGRESS)) !=
51+
Offsets::GLUE_STATE_ENTERING_WORLD)
52+
return;
53+
const uintptr_t charStruct = *reinterpret_cast<const uintptr_t *>(
54+
static_cast<uintptr_t>(Offsets::VAR_GLUE_SELECTED_CHAR));
55+
if (charStruct != 0)
56+
g_charSelectGuid = *reinterpret_cast<const uint64_t *>(
57+
charStruct + Offsets::OFF_GLUE_CHAR_GUID);
58+
}
59+
60+
static const Game::HookAutoRegister _enterWorldHook{
61+
Offsets::FUN_GLUE_ENTER_WORLD, reinterpret_cast<void *>(&EnterWorld_h),
62+
reinterpret_cast<void **>(&g_origEnterWorld)};
63+
2964
// FUN_TOKEN_TO_GUID's `"player"` path resolves the GUID by looking the
3065
// local player up in the engine's object manager: it calls FUN_00468550
3166
// to read `[VAR_LOCAL_PLAYER_PTR + 0xC0]`, then `FUN_00468460(type=0x10,
@@ -38,21 +73,27 @@ using TokenToGUID_t = uint64_t(__fastcall *)(const char *token);
3873
// either. Suffixed tokens (`"playertarget"` and the like) still go
3974
// through the engine so the chained `.target` walk works.
4075
//
41-
// This does NOT help at addon file-load time on first login. The
42-
// engine populates `+0xC0` from SMSG_UPDATE_OBJECT processing in the
43-
// main loop, which happens AFTER `FrameScript_Initialize` (and the
44-
// addon load pass it triggers) finishes. The engine itself gates its
45-
// own post-init player setup at the tail of FrameScript_Initialize on
46-
// `FUN_00468550() != 0` for the same reason — the GUID isn't ready
47-
// yet. Addons that need the player GUID at boot must wait for
48-
// `PLAYER_LOGIN`. See the trace notes in commit history.
76+
// The engine populates `+0xC0` from SMSG_UPDATE_OBJECT processing in the
77+
// main loop, which happens AFTER `FrameScript_Initialize` (and the addon
78+
// load pass it triggers) finishes — so the engine value is 0 at addon
79+
// file-load on first login. We bridge that window with `g_charSelectGuid`,
80+
// captured at the glue "Enter World" click (see the co-hook above): the
81+
// selected character's GUID is known well before the player object exists,
82+
// and it's the same value `+0xC0` will hold once it's live. So
83+
// `UnitGUID("player")` / `PlayerGuid()` answer correctly from addon-load
84+
// time. (The player *object* still doesn't exist until `+0xC0` populates —
85+
// this only makes the GUID *value* available early, for keying SavedVariables,
86+
// comparisons, our aura/cast caster checks, etc.)
4987
uint64_t PlayerGuid() {
5088
auto *player = *reinterpret_cast<uint8_t *const *>(
5189
static_cast<uintptr_t>(Offsets::VAR_LOCAL_PLAYER_PTR));
52-
if (player == nullptr)
53-
return 0;
54-
return *reinterpret_cast<const uint64_t *>(
55-
player + Offsets::OFF_LOCAL_PLAYER_GUID);
90+
if (player != nullptr) {
91+
const uint64_t guid = *reinterpret_cast<const uint64_t *>(
92+
player + Offsets::OFF_LOCAL_PLAYER_GUID);
93+
if (guid != 0)
94+
return guid; // live engine value — authoritative
95+
}
96+
return g_charSelectGuid; // early-login fallback (0 until char-select)
5697
}
5798

5899
uint64_t GuidForObject(const void *unitObject) {

0 commit comments

Comments
 (0)