@@ -149,64 +149,44 @@ using PlayerInfoLookup_t = const uint8_t *(__thiscall *)(
149149 void *cache, uint32_t guidLo, uint32_t guidHi, uint64_t *cookie,
150150 void *callback, void *userData, int retryFlag);
151151
152- bool AllDigits (const char *s) {
153- if (s == nullptr || *s == ' \0 ' )
154- return false ;
155- for (; *s != ' \0 ' ; ++s) {
156- if (*s < ' 0' || *s > ' 9' )
157- return false ;
158- }
159- return true ;
160- }
161-
162- bool HasPrefix (const char *s, const char *prefix) {
163- return std::strncmp (s, prefix, std::strlen (prefix)) == 0 ;
164- }
165-
166- // Inline mirror of the engine's token-prefix tree inside
167- // `FUN_TOKEN_TO_GUID` (`0x00515970`). The engine raises a Lua error
168- // on unknown input via `"Unknown unit name: %s"`; we want a
169- // non-erroring "is this a token?" predicate so we can dispatch only
170- // recognized tokens to the engine and treat everything else as a
171- // literal character name.
172- //
173- // Order matters: `partypet`/`raidpet` must be checked before
174- // `party`/`raid` because they share the shorter prefix.
175- bool LooksLikeKnownToken (const char *s) {
176- if (s == nullptr )
177- return false ;
178- if (std::strcmp (s, " player" ) == 0 ) return true ;
179- if (std::strcmp (s, " pet" ) == 0 ) return true ;
180- if (std::strcmp (s, " target" ) == 0 ) return true ;
181- if (std::strcmp (s, " mouseover" ) == 0 ) return true ;
182- if (std::strcmp (s, " npc" ) == 0 ) return true ;
183- if (HasPrefix (s, " partypet" )) return AllDigits (s + 8 );
184- if (HasPrefix (s, " raidpet" )) return AllDigits (s + 7 );
185- if (HasPrefix (s, " party" )) return AllDigits (s + 5 );
186- if (HasPrefix (s, " raid" )) return AllDigits (s + 4 );
187- return false ;
152+ using ResolveObjectByGuid_t = void *(__fastcall *)(int typeMask,
153+ const char *debugName,
154+ uint32_t guidLo,
155+ uint32_t guidHi,
156+ int priority);
157+
158+ // Resolve a GUID to its live CGUnit / CGPlayer object, or nullptr if
159+ // the object isn't currently loaded. Unlike a unit-*token* resolve
160+ // (`FUN_RESOLVE_UNIT_TOKEN`), this takes the GUID directly, so it works
161+ // for any GUID `FUN_TOKEN_TO_GUID` produced — including `focus` /
162+ // `nameplateN` — and never raises. The type arg is a bitmask of
163+ // `(1 << objectType)`; `FUN_OBJECT_RESOLVE_BY_GUID` keeps the object
164+ // only if its type flags intersect the mask, so unit|player matches a
165+ // player, a pet, or an MC'd creature in one call.
166+ const uint8_t *ResolveUnitOrPlayerByGuid (uint64_t guid) {
167+ if (guid == 0 )
168+ return nullptr ;
169+ constexpr int kUnitOrPlayerMask =
170+ (1 << Offsets::OBJECT_TYPE_UNIT ) | (1 << Offsets::OBJECT_TYPE_PLAYER );
171+ auto fn = reinterpret_cast <ResolveObjectByGuid_t>(
172+ Offsets::FUN_OBJECT_RESOLVE_BY_GUID );
173+ return static_cast <const uint8_t *>(
174+ fn (kUnitOrPlayerMask , " UnitIsInMyGuild" ,
175+ static_cast <uint32_t >(guid), static_cast <uint32_t >(guid >> 32 ), 0 ));
188176}
189177
190- // Resolves a known unit token to a player name via:
191- // 1. `FUN_TOKEN_TO_GUID` — OOR-safe (reads the SMSG_GROUP_LIST GUID
192- // tables, not CGObject lookups). Returns 0 for empty slots like
193- // `"party6"` when not in a 6-person group.
194- // 2. `FUN_PLAYER_INFO_LOOKUP` with a NULL callback — a pure
195- // NameCache read; returns the entry block for any GUID the
196- // engine has seen a `SMSG_NAME_QUERY_RESPONSE` for. Same path
197- // `GetPlayerInfoByGUID` uses.
178+ // Resolves a player GUID to its character name via
179+ // `FUN_PLAYER_INFO_LOOKUP` with a NULL callback — a pure NameCache
180+ // read that returns the entry block for any GUID the engine has seen a
181+ // `SMSG_NAME_QUERY_RESPONSE` for (same path `GetPlayerInfoByGUID`
182+ // uses). This is the OOR-safe fallback: it works for a raid member in
183+ // another zone whose CGUnit isn't loaded.
198184//
199185// Returns `nameBuf` on success (copied out — the entry's name pointer
200186// isn't guaranteed stable across reentrant cache writes), or nullptr
201- // if either step fails (empty token slot, NPC/creature GUID not in
202- // the player cache, or player not yet name-queried).
203- //
204- // Caller must have already prefix-gated the input via
205- // `LooksLikeKnownToken` — `FUN_TOKEN_TO_GUID` raises a Lua error for
206- // unrecognized tokens.
207- const char *TokenNameViaNameCache (const char *token, char (&nameBuf)[64]) {
208- auto toGuid = reinterpret_cast <TokenToGUID_t>(Offsets::FUN_TOKEN_TO_GUID );
209- const uint64_t guid = toGuid (token);
187+ // when the GUID isn't a player the engine has name-queried (NPC GUID,
188+ // or not yet queried).
189+ const char *NameFromGuid (uint64_t guid, char (&nameBuf)[64]) {
210190 if (guid == 0 )
211191 return nullptr ;
212192
@@ -288,14 +268,25 @@ int __fastcall Script_UnitIsInMyGuild(void *L) {
288268 return 1 ;
289269 }
290270
291- const bool inputIsToken = LooksLikeKnownToken (input);
271+ // Resolve the input to a GUID via the non-throwing token resolver
272+ // (`FUN_TOKEN_TO_GUID`, which our `Unit::TokenExtensions` hook also
273+ // teaches `focus` / `nameplateN`). A recognized unit token yields
274+ // its GUID; a literal character name — or an empty token slot like
275+ // `"party6"` — yields 0, in which case the input is treated as a
276+ // name for the roster strcmp. This replaces a hand-rolled mirror of
277+ // the engine's token grammar: the engine already knows the grammar,
278+ // and unlike its throwing sibling `FUN_RESOLVE_UNIT_TOKEN` this one
279+ // just returns 0 on anything it doesn't recognize. (It's also what
280+ // 3.3.5's `Script_UnitIsInMyGuild` does — resolve to a GUID first.)
281+ auto toGuid = reinterpret_cast <TokenToGUID_t>(Offsets::FUN_TOKEN_TO_GUID );
282+ const uint64_t guid = toGuid (input);
292283
293- if (inputIsToken) {
294- // Fast path: direct guild-key comparison for visible/loaded
295- // units. `resolve()` is safe to call now that we've prefix-
296- // gated — the engine raises a Lua error only on unrecognized
297- // tokens .
298- auto *unit = static_cast < const uint8_t *>( resolve (input) );
284+ const char *nameToFind;
285+ char nameBuf[ 64 ];
286+ if (guid != 0 ) {
287+ // Fast path: GUID → live object → direct guild-key comparison.
288+ // Covers visible/loaded units without touching the roster .
289+ const uint8_t *unit = ResolveUnitOrPlayerByGuid (guid );
299290 if (unit != nullptr ) {
300291 const uint32_t unitKey = ReadGuildKey (unit);
301292 if (unitKey == playerKey) {
@@ -309,22 +300,17 @@ int __fastcall Script_UnitIsInMyGuild(void *L) {
309300 }
310301 // unitKey == 0: data not synced. Fall through to roster.
311302 }
312- }
313-
314- // Slow path: derive a name and strcmp against the roster.
315- const char *nameToFind;
316- char nameBuf[64 ];
317- if (inputIsToken) {
318- nameToFind = TokenNameViaNameCache (input, nameBuf);
303+ // Slow path: GUID → name via the player NameCache → roster
304+ // strcmp (handles OOR raid members in another zone).
305+ nameToFind = NameFromGuid (guid, nameBuf);
319306 if (nameToFind == nullptr ) {
320- // Token resolved to an empty slot, an NPC GUID, or a
321- // player the engine hasn't name-queried yet. Either way,
322- // not a guildmate we can identify.
307+ // Empty slot, an NPC GUID, or a player not yet name-queried
308+ // — not a guildmate we can identify.
323309 Game::Lua::PushNil (L);
324310 return 1 ;
325311 }
326312 } else {
327- // Literal name — used directly for the roster strcmp .
313+ // Not a recognized token — treat as a literal character name .
328314 nameToFind = input;
329315 }
330316
0 commit comments