Harden playerbot logout & packet dispatch; add null-safety in chat hooks and RPG checks (#1529)

This commit is contained in:
Alex Dcnh
2025-08-11 16:27:25 +02:00
committed by GitHub
parent ddfa919154
commit e4ea8e2694
6 changed files with 276 additions and 42 deletions

View File

@@ -947,8 +947,21 @@ bool BroadcastHelper::BroadcastSuggestThunderfury(PlayerbotAI* ai, Player* bot)
{
std::map<std::string, std::string> placeholders;
ItemTemplate const* thunderfuryProto = sObjectMgr->GetItemTemplate(19019);
placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto);
// placeholders["%thunderfury_link"] = GET_PLAYERBOT_AI(bot)->GetChatHelper()->FormatItem(thunderfuryProto); // Old code
// [Crash fix] Protect from nil AI : a real player doesn't have PlayerbotAI.
// Before: direct deref GET_PLAYERBOT_AI(bot)->... could crash World/General.
if (auto* ai = GET_PLAYERBOT_AI(bot))
{
if (auto* chat = ai->GetChatHelper())
placeholders["%thunderfury_link"] = chat->FormatItem(thunderfuryProto);
else
placeholders["%thunderfury_link"] = ""; // fallback: no chat helper
}
else
{
placeholders["%thunderfury_link"] = ""; // fallback: no d'AI (real player)
}
// End crash fix
return BroadcastToChannelWithGlobalChance(
ai,
BOT_TEXT2("thunderfury_spam", placeholders),

View File

@@ -2373,7 +2373,7 @@ std::string PlayerbotAI::GetLocalizedGameObjectName(uint32 entry)
return name;
}
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
/*std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
{
std::vector<Player*> members;
@@ -2392,6 +2392,34 @@ std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
members.push_back(ref->GetSource());
}
return members;
}*/
std::vector<Player*> PlayerbotAI::GetPlayersInGroup()
{
std::vector<Player*> members;
Group* group = bot->GetGroup();
if (!group)
return members;
for (GroupReference* ref = group->GetFirstMember(); ref; ref = ref->next())
{
Player* member = ref->GetSource();
if (!member)
continue;
// Celaning, we don't call 2 times GET_PLAYERBOT_AI and never reference it if nil
if (auto* ai = GET_PLAYERBOT_AI(member))
{
// If it's a bot (not real player) => we ignor it
if (!ai->IsRealPlayer())
continue;
}
members.push_back(member);
}
return members;
}

View File

@@ -36,10 +36,28 @@
#include "BroadcastHelper.h"
#include "PlayerbotDbStore.h"
#include "WorldSessionMgr.h"
#include "DatabaseEnv.h" // Added for gender choice
#include <algorithm> // Added for gender choice
#include "Log.h" // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
#include "DatabaseEnv.h"
#include <algorithm>
#include "Log.h"
#include <shared_mutex> // removes a long-standing crash (0xC0000005 ACCESS_VIOLATION)
#include "TravelMgr.h"
namespace {
// [Crash fix] Centralize clearing of pointer values in the AI context
static void ClearAIContextPointerValues(PlayerbotAI* ai)
{
if (!ai) return;
if (AiObjectContext* ctx = ai->GetAiObjectContext())
{
// Known today
if (auto* tt = ctx->GetValue<TravelTarget*>("travel target"))
tt->Set(nullptr);
// TODO: add other pointer-type values here if you have any
// e.g.: ctx->GetValue<SomePtr>("some key")->Set(nullptr);
}
}
}
class BotInitGuard
{
@@ -124,7 +142,7 @@ void PlayerbotHolder::AddPlayerBot(ObjectGuid playerGuid, uint32 masterAccountId
PlayerbotMgr* mgr = GET_PLAYERBOT_MGR(masterPlayer);
if (!mgr)
{
LOG_DEBUG("playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
LOG_DEBUG("mod-playerbots", "PlayerbotMgr not found for master player with GUID: {}", masterPlayer->GetGUID().GetRawValue());
return;
}
uint32 count = mgr->GetPlayerbotsCount() + botLoading.size();
@@ -222,13 +240,33 @@ void PlayerbotHolder::UpdateSessions()
}
}
void PlayerbotHolder::HandleBotPackets(WorldSession* session)
/*void PlayerbotHolder::HandleBotPackets(WorldSession* session)
{
WorldPacket* packet;
while (session->GetPacketQueue().next(packet))
{
OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
ClientOpcodeHandler const* opHandle = opcodeTable[opcode];
opHandle->Call(session, *packet);
delete packet;
}
}*/
void PlayerbotHolder::HandleBotPackets(WorldSession* session) // [Crash Fix] Secure packet dispatch (avoid calling on a null handler)
{
WorldPacket* packet;
while (session->GetPacketQueue().next(packet))
{
const OpcodeClient opcode = static_cast<OpcodeClient>(packet->GetOpcode());
const ClientOpcodeHandler* opHandle = opcodeTable[opcode];
if (!opHandle)
{
// Unknown handler: drop cleanly
delete packet;
continue;
}
opHandle->Call(session, *packet);
delete packet;
}
@@ -318,10 +356,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
sPlayerbotDbStore->Save(botAI);
}
LOG_DEBUG("playerbots", "Bot {} logging out", bot->GetName().c_str());
LOG_DEBUG("mod-playerbots", "Bot {} logging out", bot->GetName().c_str());
bot->SaveToDB(false, false);
WorldSession* botWorldSessionPtr = bot->GetSession();
// WorldSession* botWorldSessionPtr = bot->GetSession();
WorldSession* botWorldSessionPtr = bot->GetSession(); // Small safeguard on the session (as a precaution)
if (!botWorldSessionPtr)
return;
WorldSession* masterWorldSessionPtr = nullptr;
if (botWorldSessionPtr->isLogingOut())
@@ -354,11 +395,13 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
logout = true;
}
TravelTarget* target = nullptr;
/*TravelTarget* target = nullptr;
if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values.
{
target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
}
}*/
// [Crash fix] Centralized cleanup of pointer values in the context
ClearAIContextPointerValues(botAI);
// Peiru: Allow bots to always instant logout to see if this resolves logout crashes
logout = true;
@@ -375,19 +418,25 @@ void PlayerbotHolder::LogoutPlayerBot(ObjectGuid guid)
botWorldSessionPtr->HandleLogoutRequestOpcode(data);
if (!bot)
{
RemoveFromPlayerbotsMap(guid);
/*RemoveFromPlayerbotsMap(guid);
delete botWorldSessionPtr;
if (target)
delete target;
delete target;*/
// [Crash fix] bot can be destroyed by the logout request: clean up without touching old pointers
RemoveFromPlayerbotsMap(guid);
delete botWorldSessionPtr;
}
return;
}
else
{
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
/*RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botWorldSessionPtr; // finally delete the bot's WorldSession
if (target)
delete target;
delete target;*/
// [Crash fix] no more deleting 'target' here: ownership handled by the AI/Context
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botWorldSessionPtr; // finally delete the bot's WorldSession
}
return;
} // if instant logout possible, do it
@@ -420,11 +469,11 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
sPlayerbotDbStore->Save(botAI);
}
LOG_DEBUG("playerbots", "Bot {} logged out", bot->GetName().c_str());
LOG_DEBUG("mod-playerbots", "Bot {} logged out", bot->GetName().c_str());
bot->SaveToDB(false, false);
if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values.
/*if (botAI->GetAiObjectContext()) // Maybe some day re-write to delate all pointer values.
{
TravelTarget* target = botAI->GetAiObjectContext()->GetValue<TravelTarget*>("travel target")->Get();
if (target)
@@ -433,7 +482,9 @@ void PlayerbotHolder::DisablePlayerBot(ObjectGuid guid)
RemoveFromPlayerbotsMap(guid); // deletes bot player ptr inside this WorldSession PlayerBotMap
delete botAI;
delete botAI;*/
// [Crash fix] Centralized cleanup of pointer values in the context
ClearAIContextPointerValues(botAI);
}
}
@@ -564,7 +615,7 @@ void PlayerbotHolder::OnBotLogin(Player* const bot)
//}
mgroup->AddMember(bot);
LOG_DEBUG("playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}",
LOG_DEBUG("mod-playerbots", "[GROUP] after add: members={}, isRaid={}, isLFG={}",
(int)mgroup->GetMembersCount(), mgroup->isRaidGroup() ? 1 : 0, mgroup->isLFGGroup() ? 1 : 0);
}
else
@@ -744,9 +795,11 @@ std::string const PlayerbotHolder::ProcessBotCommand(std::string const cmd, Obje
}
}
if (GET_PLAYERBOT_AI(bot))
// if (GET_PLAYERBOT_AI(bot))
if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Tidy/Crash fix] Acquire AI once and reuse; avoid multiple GET_PLAYERBOT_AI calls.
{
if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster())
// if (Player* master = GET_PLAYERBOT_AI(bot)->GetMaster())
if (Player* master = ai->GetMaster())
{
if (master->GetSession()->GetSecurity() <= SEC_PLAYER && sPlayerbotAIConfig->autoInitOnly &&
cmd != "init=auto")
@@ -1601,7 +1654,7 @@ void PlayerbotMgr::OnBotLoginInternal(Player* const bot)
botAI->SetMaster(master);
botAI->ResetStrategies();
LOG_INFO("playerbots", "Bot {} logged in", bot->GetName().c_str());
LOG_INFO("mod-playerbots", "Bot {} logged in", bot->GetName().c_str());
}
void PlayerbotMgr::OnPlayerLogin(Player* player)
@@ -1794,7 +1847,7 @@ void PlayerbotsMgr::RemovePlayerbotAI(ObjectGuid const& guid, bool removeMgrEntr
{
delete it->second;
_playerbotsAIMap.erase(it);
LOG_DEBUG("playerbots", "Removed stale AI for GUID {}",
LOG_DEBUG("mod-playerbots", "Removed stale AI for GUID {}",
static_cast<uint64>(guid.GetRawValue()));
}

View File

@@ -137,12 +137,21 @@ public:
bool OnPlayerCanUseChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Player* receiver) override
{
if (type == CHAT_MSG_WHISPER)
/*if (type == CHAT_MSG_WHISPER)
{
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver))
{
botAI->HandleCommand(type, msg, player);
return false;
}
}*/
if (type == CHAT_MSG_WHISPER && receiver) // [Crash Fix] Add non-null receiver check to avoid calling on a null pointer in edge cases.
{
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(receiver))
{
botAI->HandleCommand(type, msg, player);
return false;
}
}
@@ -152,7 +161,7 @@ public:
void OnPlayerChat(Player* player, uint32 type, uint32 /*lang*/, std::string& msg, Group* group) override
{
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
/*for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
if (Player* member = itr->GetSource())
{
@@ -161,6 +170,18 @@ public:
botAI->HandleCommand(type, msg, player);
}
}
}*/
if (!group) return; // [Crash Fix] 'group' should not be null in this hook, but this safeguard prevents a crash if the caller changes or in case of an unexpected call.
for (GroupReference* itr = group->GetFirstMember(); itr != nullptr; itr = itr->next())
{
Player* member = itr->GetSource();
if (!member) continue;
if (PlayerbotAI* botAI = GET_PLAYERBOT_AI(member))
{
botAI->HandleCommand(type, msg, player);
}
}
}
@@ -177,7 +198,9 @@ public:
{
if (bot->GetGuildId() == player->GetGuildId())
{
GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player);
// GET_PLAYERBOT_AI(bot)->HandleCommand(type, msg, player);
if (PlayerbotAI* ai = GET_PLAYERBOT_AI(bot)) // [Crash Fix] Possible crash source because we don't check if the returned pointer is not null
ai->HandleCommand(type, msg, player);
}
}
}

View File

@@ -1725,7 +1725,11 @@ void RandomPlayerbotMgr::RandomTeleport(Player* bot, std::vector<WorldLocation>&
}
// Prevent blink to be detected by visible real players
if (botAI->HasPlayerNearby(150.0f))
/*if (botAI->HasPlayerNearby(150.0f))
{
break;
}*/
if (botAI && botAI->HasPlayerNearby(150.0f)) // [Crash fix] 'botAI' can be null earlier in the function.
{
break;
}
@@ -2333,8 +2337,10 @@ void RandomPlayerbotMgr::RandomizeFirst(Player* bot)
PlayerbotsDatabase.Execute(stmt);
// teleport to a random inn for bot level
if (GET_PLAYERBOT_AI(bot))
GET_PLAYERBOT_AI(bot)->Reset(true);
/*if (GET_PLAYERBOT_AI(bot))
GET_PLAYERBOT_AI(bot)->Reset(true);*/
if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference.
ai->Reset(true);
if (bot->GetGroup())
bot->RemoveFromGroup();
@@ -2374,8 +2380,10 @@ void RandomPlayerbotMgr::RandomizeMin(Player* bot)
PlayerbotsDatabase.Execute(stmt);
// teleport to a random inn for bot level
if (GET_PLAYERBOT_AI(bot))
GET_PLAYERBOT_AI(bot)->Reset(true);
/*if (GET_PLAYERBOT_AI(bot))
GET_PLAYERBOT_AI(bot)->Reset(true);*/
if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Avoid 2 calls to GET_PLAYERBOT_AI and protect the dereference.
ai->Reset(true);
if (bot->GetGroup())
bot->RemoveFromGroup();
@@ -2468,7 +2476,7 @@ void RandomPlayerbotMgr::Refresh(Player* bot)
bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
{
if (bot && GET_PLAYERBOT_AI(bot))
/*if (bot && GET_PLAYERBOT_AI(bot))
{
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
@@ -2478,6 +2486,17 @@ bool RandomPlayerbotMgr::IsRandomBot(Player* bot)
return IsRandomBot(bot->GetGUID().GetCounter());
}
return false;*/
if (bot) // [Tidy] Single AI acquisition + same logic.
{
if (auto* ai = GET_PLAYERBOT_AI(bot))
{
if (ai->IsRealPlayer())
return false;
}
return IsRandomBot(bot->GetGUID().GetCounter());
}
return false;
}
@@ -2495,7 +2514,7 @@ bool RandomPlayerbotMgr::IsRandomBot(ObjectGuid::LowType bot)
bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
{
if (bot && GET_PLAYERBOT_AI(bot))
/*if (bot && GET_PLAYERBOT_AI(bot))
{
if (GET_PLAYERBOT_AI(bot)->IsRealPlayer())
return false;
@@ -2505,6 +2524,17 @@ bool RandomPlayerbotMgr::IsAddclassBot(Player* bot)
return IsAddclassBot(bot->GetGUID().GetCounter());
}
return false;*/
if (bot) // [Tidy] Single AI acquisition + same logic.
{
if (auto* ai = GET_PLAYERBOT_AI(bot))
{
if (ai->IsRealPlayer())
return false;
}
return IsAddclassBot(bot->GetGUID().GetCounter());
}
return false;
}
@@ -2844,8 +2874,9 @@ void RandomPlayerbotMgr::HandleCommand(uint32 type, std::string const text, Play
continue;
}
}
GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer);
// GET_PLAYERBOT_AI(bot)->HandleCommand(type, text, fromPlayer); // Possible crash source because we don't check if the returned pointer is not null
if (auto* ai = GET_PLAYERBOT_AI(bot)) // [Crash fix] Protect the call on a null AI (World/General chat path).
ai->HandleCommand(type, text, fromPlayer);
}
}
@@ -2918,7 +2949,7 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
for (GroupReference* gref = group->GetFirstMember(); gref; gref = gref->next())
{
Player* member = gref->GetSource();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
/*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI && member == player && (!botAI->GetMaster() || GET_PLAYERBOT_AI(botAI->GetMaster())))
{
if (!bot->InBattleground())
@@ -2929,6 +2960,20 @@ void RandomPlayerbotMgr::OnPlayerLogin(Player* player)
}
break;
}*/
if (auto* botAI = GET_PLAYERBOT_AI(bot)) // [Tidy] Avoid GET_PLAYERBOT_AI(...) on a potentially null master.
{
Player* master = botAI->GetMaster();
if (member == player && (!master || GET_PLAYERBOT_AI(master)))
{
if (!bot->InBattleground())
{
botAI->SetMaster(player);
botAI->ResetStrategies();
botAI->TellMaster("Hello");
}
break;
}
}
}
}
@@ -3067,12 +3112,28 @@ void RandomPlayerbotMgr::PrintStats()
lvlPerClass[bot->getClass()] += bot->GetLevel();
lvlPerRace[bot->getRace()] += bot->GetLevel();
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
/*PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (botAI->AllowActivity())
++active;
if (botAI->GetAiObjectContext()->GetValue<bool>("random bot update")->Get())
++update;*/
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot); // [Crash fix] Declare botAI in the loop scope and exit early if null,
if (!botAI)
continue; // real player / no AI → ignore this bot for stats
if (botAI->AllowActivity())
++active;
// Secure access to the context and the value
if (AiObjectContext* ctx = botAI->GetAiObjectContext())
{
if (auto* v = ctx->GetValue<bool>("random bot update"))
if (v->Get())
++update;
}
// End CrashFix
uint32 botId = bot->GetGUID().GetCounter();
if (!GetEventValue(botId, "randomize"))

View File

@@ -1227,7 +1227,7 @@ std::string const QuestObjectiveTravelDestination::getTitle()
return out.str();
}
bool RpgTravelDestination::isActive(Player* bot)
/*bool RpgTravelDestination::isActive(Player* bot) // Old Code
{
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
AiObjectContext* context = botAI->GetAiObjectContext();
@@ -1264,6 +1264,62 @@ bool RpgTravelDestination::isActive(Player* bot)
ReputationRank reaction = bot->GetReputationRank(factionEntry->faction);
return reaction > REP_NEUTRAL;
}*/
bool RpgTravelDestination::isActive(Player* bot)
{
// [Crash fix] Never dereference the AI if the player is real (null AI).
if (!bot)
return false;
PlayerbotAI* botAI = GET_PLAYERBOT_AI(bot);
if (!botAI)
return false; // real player (no AI) => inactive destination
AiObjectContext* context = botAI->GetAiObjectContext();
if (!context)
return false;
CreatureTemplate const* cInfo = GetCreatureTemplate();
if (!cInfo)
return false;
bool isUsefull = false;
if (cInfo->npcflag & UNIT_NPC_FLAG_VENDOR)
if (AI_VALUE2_LAZY(bool, "group or", "should sell,can sell,following party,near leader"))
isUsefull = true;
if (cInfo->npcflag & UNIT_NPC_FLAG_REPAIR)
if (AI_VALUE2_LAZY(bool, "group or", "should repair,can repair,following party,near leader"))
isUsefull = true;
if (!isUsefull)
return false;
// [Crash fix] Read the ignore list via 'context' and check that the Value exists
GuidSet const* ignoreList = nullptr;
if (auto* value = context->GetValue<GuidSet&>("ignore rpg target"))
ignoreList = &value->Get();
if (ignoreList)
{
for (ObjectGuid const& guid : *ignoreList)
{
if (guid.GetEntry() == getEntry())
return false;
}
}
// Secure access to the faction template
if (FactionTemplateEntry const* factionEntry = sFactionTemplateStore.LookupEntry(cInfo->faction))
{
ReputationRank reaction = bot->GetReputationRank(factionEntry->faction);
return reaction > REP_NEUTRAL;
}
// As a precaution, if the faction is not found, consider inactive
return false;
}
CreatureTemplate const* RpgTravelDestination::GetCreatureTemplate() { return sObjectMgr->GetCreatureTemplate(entry); }