refactor: extract findOnUseSpellId helper, add warden hash comment

- spell_handler: extract duplicated item on-use spell lookup into
  findOnUseSpellId() — was copy-pasted in useItemBySlot and useItemInBag
- warden_handler: add why-comment explaining the door model HMAC-SHA1
  hash table (wall-hack detection for unmodified 3.3.5a client data)
This commit is contained in:
Kelsi 2026-03-30 13:56:45 -07:00
parent 76f493f7d9
commit a9ce22f315
3 changed files with 20 additions and 20 deletions

View file

@ -249,6 +249,9 @@ private:
void handleChannelUpdate(network::Packet& packet); void handleChannelUpdate(network::Packet& packet);
// --- Internal helpers --- // --- Internal helpers ---
// Find the on-use spell for an item (trigger=0 Use or trigger=5 NoDelay).
// CMSG_USE_ITEM requires a valid spellId or the server silently ignores it.
uint32_t findOnUseSpellId(uint32_t itemId) const;
void loadSpellNameCache() const; void loadSpellNameCache() const;
void loadSkillLineAbilityDbc(); void loadSkillLineAbilityDbc();
void categorizeTrainerSpells(); void categorizeTrainerSpells();

View file

@ -446,6 +446,18 @@ void SpellHandler::confirmPetUnlearn() {
petUnlearnCost_ = 0; petUnlearnCost_ = 0;
} }
uint32_t SpellHandler::findOnUseSpellId(uint32_t itemId) const {
if (auto* info = owner_.getItemInfo(itemId)) {
for (const auto& sp : info->spells) {
// spellTrigger 0 = "Use", 5 = "No Delay" — both are player-activated on-use effects
if (sp.spellId != 0 && (sp.spellTrigger == 0 || sp.spellTrigger == 5)) {
return sp.spellId;
}
}
}
return 0;
}
void SpellHandler::useItemBySlot(int backpackIndex) { void SpellHandler::useItemBySlot(int backpackIndex) {
if (backpackIndex < 0 || backpackIndex >= owner_.inventory.getBackpackSize()) return; if (backpackIndex < 0 || backpackIndex >= owner_.inventory.getBackpackSize()) return;
const auto& slot = owner_.inventory.getBackpackSlot(backpackIndex); const auto& slot = owner_.inventory.getBackpackSlot(backpackIndex);
@ -457,16 +469,7 @@ void SpellHandler::useItemBySlot(int backpackIndex) {
} }
if (itemGuid != 0 && owner_.state == WorldState::IN_WORLD && owner_.socket) { if (itemGuid != 0 && owner_.state == WorldState::IN_WORLD && owner_.socket) {
uint32_t useSpellId = 0; uint32_t useSpellId = findOnUseSpellId(slot.item.itemId);
if (auto* info = owner_.getItemInfo(slot.item.itemId)) {
for (const auto& sp : info->spells) {
if (sp.spellId != 0 && (sp.spellTrigger == 0 || sp.spellTrigger == 5)) {
useSpellId = sp.spellId;
break;
}
}
}
auto packet = owner_.packetParsers_ auto packet = owner_.packetParsers_
? owner_.packetParsers_->buildUseItem(0xFF, static_cast<uint8_t>(23 + backpackIndex), itemGuid, useSpellId) ? owner_.packetParsers_->buildUseItem(0xFF, static_cast<uint8_t>(23 + backpackIndex), itemGuid, useSpellId)
: UseItemPacket::build(0xFF, static_cast<uint8_t>(23 + backpackIndex), itemGuid, useSpellId); : UseItemPacket::build(0xFF, static_cast<uint8_t>(23 + backpackIndex), itemGuid, useSpellId);
@ -498,16 +501,7 @@ void SpellHandler::useItemInBag(int bagIndex, int slotIndex) {
" itemGuid=0x", std::hex, itemGuid, std::dec); " itemGuid=0x", std::hex, itemGuid, std::dec);
if (itemGuid != 0 && owner_.state == WorldState::IN_WORLD && owner_.socket) { if (itemGuid != 0 && owner_.state == WorldState::IN_WORLD && owner_.socket) {
uint32_t useSpellId = 0; uint32_t useSpellId = findOnUseSpellId(slot.item.itemId);
if (auto* info = owner_.getItemInfo(slot.item.itemId)) {
for (const auto& sp : info->spells) {
if (sp.spellId != 0 && (sp.spellTrigger == 0 || sp.spellTrigger == 5)) {
useSpellId = sp.spellId;
break;
}
}
}
uint8_t wowBag = static_cast<uint8_t>(19 + bagIndex); uint8_t wowBag = static_cast<uint8_t>(19 + bagIndex);
auto packet = owner_.packetParsers_ auto packet = owner_.packetParsers_
? owner_.packetParsers_->buildUseItem(wowBag, static_cast<uint8_t>(slotIndex), itemGuid, useSpellId) ? owner_.packetParsers_->buildUseItem(wowBag, static_cast<uint8_t>(slotIndex), itemGuid, useSpellId)

View file

@ -116,6 +116,9 @@ bool hmacSha1Matches(const uint8_t seedBytes[4], const std::string& text, const
return outLen == SHA_DIGEST_LENGTH && std::memcmp(out, expected, SHA_DIGEST_LENGTH) == 0; return outLen == SHA_DIGEST_LENGTH && std::memcmp(out, expected, SHA_DIGEST_LENGTH) == 0;
} }
// Pre-computed HMAC-SHA1 hashes of known door M2 models that Warden checks
// to verify the client hasn't modified collision geometry (wall-hack detection).
// These hashes match the unmodified 3.3.5a client data files.
const std::unordered_map<std::string, std::array<uint8_t, 20>>& knownDoorHashes() { const std::unordered_map<std::string, std::array<uint8_t, 20>>& knownDoorHashes() {
static const std::unordered_map<std::string, std::array<uint8_t, 20>> k = { static const std::unordered_map<std::string, std::array<uint8_t, 20>> k = {
{"world\\lordaeron\\stratholme\\activedoodads\\doors\\nox_door_plague.m2", {"world\\lordaeron\\stratholme\\activedoodads\\doors\\nox_door_plague.m2",