From 15299d73fabd0b4a886cec582b9e99d4c2499ca0 Mon Sep 17 00:00:00 2001 From: Wall <90075138+Wall-core@users.noreply.github.com> Date: Wed, 4 Feb 2026 19:10:59 -0500 Subject: [PATCH 1/4] Fully implement PageQuery handler Implements the full packet structure handling for PageQuery (including 3-byte GUID/entry handling). Implement anti-exploit checks and validation. --- src/game/Handlers/QueryHandler.cpp | 129 ++++++++++++++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/src/game/Handlers/QueryHandler.cpp b/src/game/Handlers/QueryHandler.cpp index 0cb1239fbd1..5def7087971 100644 --- a/src/game/Handlers/QueryHandler.cpp +++ b/src/game/Handlers/QueryHandler.cpp @@ -432,8 +432,135 @@ void WorldSession::HandleNpcTextQueryOpcode(WorldPacket& recv_data) void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) { + // Check map here for GAMEOBJECT_ITEM_TEXT + ObjectGuid pGuid = _player->GetObjectGuid(); + Map* map = _player->GetMap(); + if (!map || !pGuid) + return; + + // Page Text ID uint32 pageID; recv_data >> pageID; + uint32 origPageID = pageID; + + // Object GUID - written in 3 bytes + uint8 guid1; + recv_data >> guid1; + uint8 guid2; + recv_data >> guid2; + uint8 guid3; + recv_data >> guid3; + uint32 guids; + + // Pack into uint32 + guids = ((uint32)guid1 << 0) | + ((uint32)guid2 << 8) | + ((uint32)guid3 << 16); + + // Object Entry - written in 3 bytes + uint8 entry1; + recv_data >> entry1; + uint8 entry2; + recv_data >> entry2; + uint8 entry3; + recv_data >> entry3; + uint32 entry; + + // Pack into uint32 + entry = ((uint32)entry1 << 0) | + ((uint32)entry2 << 8) | + ((uint32)entry3 << 16); + + // Lock Type - The spell & animation which the client will use + uint8 lockType; + recv_data >> lockType; + + // Flags - TODO: find out what these mean. Book Items = 0x40, Book Objects = 0xF1. + uint8 flags; + recv_data >> flags; + + // HandlePageTextQuery is only called from three things: + // - GAMEOBJECT_TYPE_TEXT + // - GAMEOBJECT_TYPE_GOOBER with page_text (two exist in vanilla (Ameth'Aran tablets)) + // - Items with page_text (including Eye of Divinity) + ObjectGuid guid; + bool error = false; + bool isItem = (flags == 0x40 ? true : false); + if (isItem) + { + guid = ObjectGuid(HIGHGUID_ITEM, guids); + Item* item = _player->GetItemByGuid(guid); + if (!item) + { + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid item sent by PlayerGuid: %u ItemGuid: %u", + pGuid, guid); + error = true; + } + } + else if (!isItem) + { + guid = ObjectGuid(HIGHGUID_GAMEOBJECT, entry, guids); + GameObject* obj = map->GetGameObject(guid); + if (obj) + { + GameObjectInfo const* info = obj->GetGOInfo(); + if (info) + { + if (info->type == GAMEOBJECT_TYPE_TEXT) + { + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None)) // Should be 5.55556f but we allow extra distance incase of lag + { + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_TEXT out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", + pGuid, guid, entry); + error = true; + } + } + else if (info->type == GAMEOBJECT_TYPE_GOOBER) + { + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None)) // Should be 5.55556f but we allow extra distance incase of lag + { + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_GOOBER out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", + pGuid, guid, entry); + error = true; + } + } + else // Cheat + { + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid object type sent PageText by PlayerGuid: %u ObjectGuid: %u Entry: %u Type: %u", + pGuid, guid, entry, info->type); + error = true; + } + } + else // Error + { + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid object info sent by PlayerGuid: %u ObjectGuid: %u Entry: %u", + pGuid, guid, entry); + error = true; + } + } + else // Cheat + { + sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid object sent by PlayerGuid: %u ObjectGuid: %u Entry: %u", + pGuid, guid, entry); + error = true; + } + } + + // Return if bad client data + if (error) + { + if (pageID) + { + // Cheating + WorldPacket data(SMSG_PAGE_TEXT_QUERY_RESPONSE, 50); + data << pageID; + data << "It appears you were attempting to do something illegal..."; + data << uint32(0); + SendPacket(&data); + } + + return; + } while (pageID) { @@ -467,10 +594,10 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) data << uint32(pPage->next_page); pageID = pPage->next_page; } + SendPacket(&data); } } - void WorldSession::SendQueryTimeResponse() { WorldPacket data(SMSG_QUERY_TIME_RESPONSE, 4); From 4e062ec789e0d3aef1edebba86a194bac6b8ebad Mon Sep 17 00:00:00 2001 From: Wall <90075138+Wall-core@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:08:42 -0500 Subject: [PATCH 2/4] Add anticheat checks for matching PageText Prevent cheaters from sending nonmatching PageText --- src/game/Handlers/QueryHandler.cpp | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/game/Handlers/QueryHandler.cpp b/src/game/Handlers/QueryHandler.cpp index 5def7087971..1db6b2f26ad 100644 --- a/src/game/Handlers/QueryHandler.cpp +++ b/src/game/Handlers/QueryHandler.cpp @@ -490,9 +490,19 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) { guid = ObjectGuid(HIGHGUID_ITEM, guids); Item* item = _player->GetItemByGuid(guid); - if (!item) + if (item) + { + ItemPrototype const* proto = item->GetProto(); + if (!proto || proto->PageText != origPageID) // Cheat + { + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - Invalid item info sent PageText by PlayerGuid: %u ObjectGuid: %u", + pGuid, guid); + error = true; + } + } + else // Cheat { - sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid item sent by PlayerGuid: %u ItemGuid: %u", + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - Invalid item sent by PlayerGuid: %u ItemGuid: %u", pGuid, guid); error = true; } @@ -508,39 +518,39 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) { if (info->type == GAMEOBJECT_TYPE_TEXT) { - if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None)) // Should be 5.55556f but we allow extra distance incase of lag + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->text.pageID == origPageID && info->text.language) // Should be 5.55556f but we allow extra distance incase of lag { - sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_TEXT out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_TEXT out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); error = true; } } else if (info->type == GAMEOBJECT_TYPE_GOOBER) { - if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None)) // Should be 5.55556f but we allow extra distance incase of lag + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->goober.pageId == origPageID && info->goober.language) // Should be 5.55556f but we allow extra distance incase of lag { - sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_GOOBER out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_GOOBER out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); error = true; } } else // Cheat { - sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid object type sent PageText by PlayerGuid: %u ObjectGuid: %u Entry: %u Type: %u", + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - Invalid object type sent PageText by PlayerGuid: %u ObjectGuid: %u Entry: %u Type: %u", pGuid, guid, entry, info->type); error = true; } } else // Error { - sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid object info sent by PlayerGuid: %u ObjectGuid: %u Entry: %u", + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - Invalid object info sent by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); error = true; } } else // Cheat { - sLog.Out(LOG_BASIC, LOG_LVL_ERROR, "CMSG_PAGE_TEXT_QUERY - Invalid object sent by PlayerGuid: %u ObjectGuid: %u Entry: %u", + sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - Invalid object sent by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); error = true; } From c170dc81d3816ca04086e31dd74838f8eff7a9cb Mon Sep 17 00:00:00 2001 From: Wall <90075138+Wall-core@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:39:25 -0500 Subject: [PATCH 3/4] Fix leftover code --- src/game/Handlers/QueryHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/game/Handlers/QueryHandler.cpp b/src/game/Handlers/QueryHandler.cpp index 1db6b2f26ad..f0bb7d6f5be 100644 --- a/src/game/Handlers/QueryHandler.cpp +++ b/src/game/Handlers/QueryHandler.cpp @@ -518,7 +518,7 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) { if (info->type == GAMEOBJECT_TYPE_TEXT) { - if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->text.pageID == origPageID && info->text.language) // Should be 5.55556f but we allow extra distance incase of lag + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->text.pageID == origPageID) // Should be 5.55556f but we allow extra distance incase of lag { sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_TEXT out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); @@ -527,7 +527,7 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) } else if (info->type == GAMEOBJECT_TYPE_GOOBER) { - if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->goober.pageId == origPageID && info->goober.language) // Should be 5.55556f but we allow extra distance incase of lag + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->goober.pageId == origPageID) // Should be 5.55556f but we allow extra distance incase of lag { sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_GOOBER out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); From db70a998aa6e47c6f26daecd40adea4b1a912c79 Mon Sep 17 00:00:00 2001 From: Wall <90075138+Wall-core@users.noreply.github.com> Date: Thu, 5 Feb 2026 00:41:39 -0500 Subject: [PATCH 4/4] Fix leftover code --- src/game/Handlers/QueryHandler.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/game/Handlers/QueryHandler.cpp b/src/game/Handlers/QueryHandler.cpp index f0bb7d6f5be..a93bf384fb1 100644 --- a/src/game/Handlers/QueryHandler.cpp +++ b/src/game/Handlers/QueryHandler.cpp @@ -516,18 +516,18 @@ void WorldSession::HandlePageTextQueryOpcode(WorldPacket& recv_data) GameObjectInfo const* info = obj->GetGOInfo(); if (info) { - if (info->type == GAMEOBJECT_TYPE_TEXT) + if (info->type == GAMEOBJECT_TYPE_TEXT && info->text.pageID == origPageID) { - if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->text.pageID == origPageID) // Should be 5.55556f but we allow extra distance incase of lag + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None)) // Should be 5.55556f but we allow extra distance incase of lag { sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_TEXT out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry); error = true; } } - else if (info->type == GAMEOBJECT_TYPE_GOOBER) + else if (info->type == GAMEOBJECT_TYPE_GOOBER && info->goober.pageId == origPageID) { - if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None) && info->goober.pageId == origPageID) // Should be 5.55556f but we allow extra distance incase of lag + if (!_player->IsWithinDist(obj, 10.0f, true, SizeFactor::None)) // Should be 5.55556f but we allow extra distance incase of lag { sLog.Out(LOG_BASIC, LOG_LVL_MINIMAL, "CMSG_PAGE_TEXT_QUERY - GAMEOBJECT_TYPE_GOOBER out of distance by PlayerGuid: %u ObjectGuid: %u Entry: %u", pGuid, guid, entry);