Skip to content

Commit d558776

Browse files
committed
item, unit: drop hand-rolled unit-token grammar mirrors
FUN_TOKEN_TO_GUID (0x00515970) is a pure parser: it returns 0 for any input it doesn't recognize and never raises -- verified from the disassembly (plain stack frame, no assert/SEH prologue; every unknown-input path is xor eax,eax; ret). Both call sites had mirrored the engine's token grammar by hand to pre-screen input, on the mistaken belief this function throws (it was conflated with its sibling FUN_RESOLVE_UNIT_TOKEN 0x00515940, which does throw). - item/Use.cpp UseItemByName: delete LooksLikeToken / MatchBase / SkipTargetSuffixes / HasPrefix; just call FUN_TOKEN_TO_GUID and let it return 0 for non-tokens. - unit/Flags.cpp UnitIsInMyGuild: delete LooksLikeKnownToken / HasPrefix / AllDigits. Resolve the input to a GUID via FUN_TOKEN_TO_GUID, then resolve GUID -> object via the non-throwing FUN_OBJECT_RESOLVE_BY_GUID (unit|player mask) for the fast guild-key compare, and GUID -> name via the NameCache for the roster fallback. This avoids the throwing resolver entirely, and -- because the token->GUID step goes through our TokenExtensions hook -- now supports focus / nameplateN, which the old grammar mirror silently treated as literal names. Matches what 3.3.5's Script_UnitIsInMyGuild does (resolve to a GUID first). No grammar to keep in sync with the engine anymore. Behavior otherwise unchanged (self, guildmate/other-guild target, NPC, OOR raid member, literal name, empty slot all verified case-by-case).
1 parent f55779f commit d558776

2 files changed

Lines changed: 57 additions & 129 deletions

File tree

src/item/Use.cpp

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,6 @@
4848
#include "item/Location.h"
4949

5050
#include <cstdint>
51-
#include <cstring>
5251

5352
namespace Item::Use {
5453

@@ -59,67 +58,10 @@ using UseItem_t = unsigned(__thiscall *)(const void *item, const uint64_t *targe
5958

6059
using TokenToGUID_t = uint64_t(__fastcall *)(const char *token);
6160

62-
bool HasPrefix(const char *s, const char *prefix) {
63-
return std::strncmp(s, prefix, std::strlen(prefix)) == 0;
64-
}
65-
66-
// Walk past any "target" suffixes (`targettarget`, `focustargettarget`,
67-
// …); returns a pointer at the first non-"target" character.
68-
const char *SkipTargetSuffixes(const char *s) {
69-
while (HasPrefix(s, "target"))
70-
s += 6;
71-
return s;
72-
}
73-
74-
// Predicate mirror of the engine's token grammar inside
75-
// `FUN_TOKEN_TO_GUID` (`0x00515970`) plus the families our own
76-
// `Unit::TokenExtensions` hook adds (`focus`, `nameplateN`). The
77-
// engine raises a Lua error on unknown input, so we gate before
78-
// dispatching to keep our "silently no-ops on bad input" contract.
79-
//
80-
// Every base token may be followed by zero or more `target` suffixes
81-
// (`playertarget`, `party1targettarget`, …) which the engine's
82-
// resolver walks via `LAB_005159d3..0x00515A2C`.
83-
bool MatchBase(const char *s, const char *base, bool requireDigits) {
84-
if (!HasPrefix(s, base))
85-
return false;
86-
const char *rest = s + std::strlen(base);
87-
if (requireDigits) {
88-
const char *p = rest;
89-
while (*p >= '0' && *p <= '9')
90-
++p;
91-
if (p == rest)
92-
return false;
93-
rest = p;
94-
}
95-
return *SkipTargetSuffixes(rest) == '\0';
96-
}
97-
98-
bool LooksLikeToken(const char *s) {
99-
if (s == nullptr || *s == '\0')
100-
return false;
101-
// Order matters for shared roots: longer prefix first
102-
// (`partypet`/`raidpet` before `party`/`raid`).
103-
if (MatchBase(s, "partypet", true)) return true;
104-
if (MatchBase(s, "raidpet", true)) return true;
105-
if (MatchBase(s, "party", true)) return true;
106-
if (MatchBase(s, "raid", true)) return true;
107-
if (MatchBase(s, "nameplate", true)) return true;
108-
if (MatchBase(s, "mouseover", false)) return true;
109-
if (MatchBase(s, "player", false)) return true;
110-
if (MatchBase(s, "target", false)) return true;
111-
if (MatchBase(s, "focus", false)) return true;
112-
if (MatchBase(s, "npc", false)) return true;
113-
if (MatchBase(s, "pet", false)) return true;
114-
return false;
115-
}
116-
11761
uint64_t ResolveUnitGuid(void *L, int idx) {
11862
if (!Game::Lua::IsString(L, idx))
11963
return 0;
12064
const char *token = Game::Lua::ToString(L, idx);
121-
if (!LooksLikeToken(token))
122-
return 0;
12365
auto fn = reinterpret_cast<TokenToGUID_t>(Offsets::FUN_TOKEN_TO_GUID);
12466
return fn(token);
12567
}

src/unit/Flags.cpp

Lines changed: 57 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)