From a38a3891e16a77b235d1ec130d2a52c459fc20ca Mon Sep 17 00:00:00 2001 From: ratkosrb <35845488+ratkosrb@users.noreply.github.com> Date: Sat, 27 Aug 2022 17:53:08 +0300 Subject: [PATCH 1/2] Antispam (#25) --- src/game/Anticheat/Anticheat.cpp | 10 + src/game/Anticheat/Anticheat.h | 5 +- src/game/Anticheat/Antispam/Antispam.cpp | 539 +++++++++++++++++++++++ src/game/Anticheat/Antispam/Antispam.h | 167 +++++++ src/game/CMakeLists.txt | 2 + src/game/World.cpp | 17 + src/game/World.h | 15 + 7 files changed, 753 insertions(+), 2 deletions(-) create mode 100644 src/game/Anticheat/Antispam/Antispam.cpp create mode 100644 src/game/Anticheat/Antispam/Antispam.h diff --git a/src/game/Anticheat/Anticheat.cpp b/src/game/Anticheat/Anticheat.cpp index 8ea866842a5..b33b8d70775 100644 --- a/src/game/Anticheat/Anticheat.cpp +++ b/src/game/Anticheat/Anticheat.cpp @@ -32,6 +32,7 @@ AnticheatManager* GetAnticheatLib() #include "World.h" #include "WorldSession.h" +#include "Antispam/Antispam.h" #include "MovementAnticheat/MovementAnticheat.h" #include "WardenAnticheat/Warden.hpp" #include "WardenAnticheat/WardenScanMgr.hpp" @@ -41,6 +42,10 @@ AnticheatManager* GetAnticheatLib() void AnticheatManager::LoadAnticheatData() { + sLog.outString(); + sLog.outString("Loading antispam system ..."); + sAntispam->loadConfig(); + sLog.outString(); sLog.outString("Loading warden checks..."); sWardenScanMgr.loadFromDB(); @@ -74,4 +79,9 @@ Warden* AnticheatManager::CreateWardenFor(WorldSession* client, BigNumber* K) return nullptr; } +AntispamInterface* AnticheatManager::GetAntispam() const +{ + return sAntispam; +} + #endif \ No newline at end of file diff --git a/src/game/Anticheat/Anticheat.h b/src/game/Anticheat/Anticheat.h index 39695554a18..b6dfe3b9550 100644 --- a/src/game/Anticheat/Anticheat.h +++ b/src/game/Anticheat/Anticheat.h @@ -128,8 +128,9 @@ class AnticheatManager #ifdef USE_ANTICHEAT void LoadAnticheatData(); - Warden * CreateWardenFor(WorldSession* client, BigNumber* K); + Warden* CreateWardenFor(WorldSession* client, BigNumber* K); MovementAnticheat* CreateAnticheatFor(Player* player); + AntispamInterface* GetAntispam() const; #else void LoadAnticheatData() {} @@ -141,10 +142,10 @@ class AnticheatManager { return new MovementAnticheat(); } + AntispamInterface* GetAntispam() const { return nullptr; } #endif // Antispam wrappers - AntispamInterface* GetAntispam() const { return nullptr; } bool CanWhisper(AccountPersistentData const& data, MasterPlayer* player) { return true; } static AnticheatManager* instance(); diff --git a/src/game/Anticheat/Antispam/Antispam.cpp b/src/game/Anticheat/Antispam/Antispam.cpp new file mode 100644 index 00000000000..92d37683f7b --- /dev/null +++ b/src/game/Anticheat/Antispam/Antispam.cpp @@ -0,0 +1,539 @@ +#include +#include +#include + +#include "Database/DatabaseEnv.h" +#include "Util.h" +#include "World.h" +#include "ChannelMgr.h" +#include "Chat.h" +#include "Anticheat.h" +#include "Antispam.h" + +static inline void ReplaceAll(std::string &str, const std::string& from, const std::string& to) +{ + size_t startPos = 0; + while ((startPos = str.find(from, startPos)) != std::string::npos) + { + str.replace(startPos, from.length(), to); + startPos += to.length(); + } +} + +static inline void ReplaceAllW(std::wstring &str, const std::wstring& from, const std::wstring& to) +{ + size_t startPos = 0; + while ((startPos = str.find(from, startPos)) != std::wstring::npos) + { + str.replace(startPos, from.length(), to); + startPos += to.length(); + } +} + +void AntispamAsyncWorker(Antispam *antispam) +{ + using namespace std::chrono_literals; + LoginDatabase.ThreadStart(); + LogsDatabase.ThreadStart(); + auto prevNow = Clock::now(); + while (!sWorld.IsStopped()) + { + auto currNow = Clock::now(); + auto diff = std::chrono::duration_cast(currNow - prevNow).count(); + antispam->processMessages(diff); + prevNow = currNow; + std::this_thread::sleep_for(50ms); + } + LogsDatabase.ThreadEnd(); + LoginDatabase.ThreadEnd(); +} + +Antispam::Antispam() + : m_enabled(false), m_restrictionLevel(0), m_originalNormalizeMask(0), m_fullyNormalizeMask(0), + m_threshold(0), m_mutetime(0), m_chatMask(0), m_worker(), m_banEnabled(false), m_detectThreshold(3), + m_messageBlockSize(5), m_updateTimer(60000), m_messageRepeatCount(5), m_frequencyCount(5.0f), m_frequencyTime(6.0f), + m_mergeAllWhispers(false) +{ + m_frequencyCoeff = m_frequencyCount / m_frequencyTime; +} + +void Antispam::loadFromDB() +{ + sLog.outString("Loading table 'antispam_blacklist'"); + m_blackList.clear(); + + QueryResult* result = LoginDatabase.Query("SELECT * FROM antispam_blacklist"); + if (result) + { + do + { + auto fields = result->Fetch(); + m_blackList.insert(fields[0].GetCppString()); + } + while (result->NextRow()); + delete result; + } + + sLog.outString(">> %u blacklist words loaded", m_blackList.size()); + sLog.outString(); + + sLog.outString("Loading table 'antispam_replacement'"); + m_replacement.clear(); + + result = LoginDatabase.Query("SELECT * FROM antispam_replacement"); + if (result) + { + do + { + auto fields = result->Fetch(); + m_replacement[fields[0].GetCppString()] = fields[1].GetCppString(); + } + while (result->NextRow()); + delete result; + } + + sLog.outString(">> %u replacements loaded", m_replacement.size()); + sLog.outString(); + + sLog.outString("Loading table 'antispam_scores'"); + + m_scores[MSG_TYPE_NORMALIZED].clear(); + m_scores[MSG_TYPE_ORIGINAL].clear(); + + result = LoginDatabase.Query("SELECT * FROM antispam_scores"); + if (result) + { + do + { + auto fields = result->Fetch(); + m_scores[fields[2].GetUInt8()][fields[0].GetCppString()] = fields[1].GetInt32(); + } + while (result->NextRow()); + delete result; + } + + sLog.outString(">> %u scores loaded", m_scores[MSG_TYPE_NORMALIZED].size() + m_scores[MSG_TYPE_ORIGINAL].size()); + sLog.outString(); + + sLog.outString("Loading table 'antispam_unicode'"); + m_unicode.clear(); + + result = LoginDatabase.Query("SELECT * FROM antispam_unicode"); + if (result) + { + std::wostringstream wss; + do + { + auto fields = result->Fetch(); + wss.str(std::wstring()); + wss << wchar_t(fields[0].GetUInt32()); + std::wstring key = wss.str(); + wss.str(std::wstring()); + wss << wchar_t(fields[1].GetUInt32()); + std::wstring value = wss.str(); + m_unicode[key] = value; + } + while (result->NextRow()); + delete result; + } + + sLog.outString(">> %u unicode symbols loaded", m_unicode.size()); + sLog.outString(); +} + +void Antispam::loadMuted() +{ + m_mutedAccounts.clear(); + QueryResult *result = LoginDatabase.Query("SELECT id FROM antispam_detected WHERE unmuteTime <> 0"); + if (result) + { + do + { + auto fields = result->Fetch(); + m_mutedAccounts.insert(fields[0].GetUInt32()); + } while (result->NextRow()); + delete result; + } +} + +void Antispam::loadConfig() +{ + loadFromDB(); // temporary solution for reload from game + + m_enabled = sWorld.getConfig(CONFIG_BOOL_AC_ANTISPAM_ENABLED); + m_restrictionLevel = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_MAX_RESTRICTION_LEVEL); + m_originalNormalizeMask = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_ORIGINAL_NORMALIZE_MASK); + m_fullyNormalizeMask = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_FULLY_NORMALIZE_MASK); + m_threshold = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_SCORE_THRESHOLD); + m_mutetime = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_MUTETIME); + m_chatMask = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_CHAT_MASK); + + m_mergeAllWhispers = sWorld.getConfig(CONFIG_BOOL_AC_ANTISPAM_MERGE_ALL_WHISPERS); + m_banEnabled = sWorld.getConfig(CONFIG_BOOL_AC_ANTISPAM_BAN_ENABLED); + m_detectThreshold = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_DETECT_THRESHOLD); + m_messageRepeatCount = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_REPEAT_COUNT); + m_updateTimer = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_UPDATE_TIMER); + m_messageBlockSize = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_MESSAGE_BLOCK_SIZE); + m_frequencyTime = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_FREQUENCY_TIME); + m_frequencyCount = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_FREQUENCY_COUNT); + m_frequencyCoeff = m_frequencyCount / m_frequencyTime; + + if (!m_worker.joinable()) + m_worker = std::thread(AntispamAsyncWorker, this); +} + +void Antispam::addMessage(const std::string& msg, uint32 type, PlayerPointer from, PlayerPointer to) +{ + if (!m_enabled || from->IsGameMaster()) + return; + + if (from->GetLevel() > m_restrictionLevel) + return; + + uint8 chatType = getConvertedChatType(type); + + if (chatType == A_CHAT_TYPE_MAX) + return; + + if (m_chatMask && (m_chatMask & (1 << chatType)) == 0) + return; + + MessageBlock messageBlock; + messageBlock.fromGuid = from->GetObjectGuid(); + messageBlock.fromAccount = from->GetSession()->GetAccountId(); + messageBlock.toGuid = (to && !m_mergeAllWhispers ? to->GetObjectGuid() : ObjectGuid()); + messageBlock.msg = msg; + messageBlock.type = chatType; + messageBlock.count = 1; + messageBlock.time = time(nullptr); + + m_messageQueue.push(messageBlock); +} + +struct FindMsg +{ + FindMsg(const std::string& m) : msg(m) {} + bool operator()(const std::string& s) { return s == msg; } + + private: + const std::string& msg; +}; + +void Antispam::processMessages(uint32 diff) +{ + if (m_updateTimer <= diff) + { + m_updateTimer = sWorld.getConfig(CONFIG_UINT32_AC_ANTISPAM_UPDATE_TIMER); + + for (auto i = 0; i < A_CHAT_TYPE_MAX; ++i) + { + m_messageBlocks[i].clear(); + m_messageCounters[i].clear(); + m_messageRepeats[i].clear(); + } + + loadMuted(); + } + else + m_updateTimer -= diff; + + while (!m_messageQueue.empty()) + { + MessageBlock messageBlock; + if (m_messageQueue.try_pop(messageBlock)) + { + if (isMuted(messageBlock.fromAccount)) + continue; + + auto type = messageBlock.type; + auto guidFrom = messageBlock.fromGuid.GetCounter(); + auto guidTo = messageBlock.toGuid.GetCounter(); + + LowGuidPair lowGuidPair(guidFrom, guidTo); + + m_messageRepeats[type][guidFrom].push_back(messageBlock.msg); + + auto counter = m_messageCounters[type].find(guidFrom); + auto hasCounter = counter != m_messageCounters[type].end(); + + if (hasCounter) + { + counter->second.count++; + counter->second.timeDiff = counter->second.timeDiff + (messageBlock.time - counter->second.timeLast); + counter->second.timeLast = messageBlock.time; + } + else + { + MessageCounter messageCounter; + messageCounter.count = 1; + messageCounter.timeDiff = 0; + messageCounter.timeLast = messageBlock.time; + messageCounter.detectMarker = false; + m_messageCounters[type][guidFrom] = messageCounter; + } + + if (hasCounter) + { + auto repeats = std::count_if(m_messageRepeats[type][guidFrom].begin(), m_messageRepeats[type][guidFrom].end(), FindMsg(messageBlock.msg)); + if (repeats > m_messageRepeatCount) + { + applySanction(messageBlock, DETECT_FLOOD, repeats); + continue; + } + + if (counter->second.detectMarker || !m_frequencyCount || + ((counter->second.count >= m_frequencyCount) && ((counter->second.count / m_frequencyCoeff) > counter->second.timeDiff))) + { + counter->second.detectMarker = true; + + auto itr = m_messageBlocks[type].find(lowGuidPair); + if (itr != m_messageBlocks[type].end()) + { + itr->second.msg.append(messageBlock.msg); + itr->second.count++; + + if (filterMessage(itr->second.msg)) + { + applySanction(itr->second, DETECT_SEPARATED); + m_messageBlocks[type].erase(itr); + continue; + } + + if (itr->second.count == m_messageBlockSize) + m_messageBlocks[type].erase(itr); + + } + else if (filterMessage(messageBlock.msg)) + applySanction(messageBlock, DETECT_STANDARD); + else + m_messageBlocks[type][lowGuidPair] = messageBlock; + } + else if (filterMessage(messageBlock.msg)) + applySanction(messageBlock, DETECT_STANDARD); + } + else if (filterMessage(messageBlock.msg)) + applySanction(messageBlock, DETECT_STANDARD); + else if (!m_frequencyCount) + m_messageBlocks[type][lowGuidPair] = messageBlock; + + } + } +} + +std::string Antispam::normalizeMessage(const std::string& msg, uint32 mask) +{ + auto newMsg = msg; + + if (!mask) + mask = m_fullyNormalizeMask; + + if (mask & NF_CUT_COLOR) + { + static std::regex regex1("(\\|c\\w{8})"); + static std::regex regex2("(\\|H[\\w|\\W]{1,}\\|h)"); + newMsg = std::regex_replace(newMsg, regex1, ""); + ReplaceAll(newMsg, "|h|r", ""); + newMsg = std::regex_replace(newMsg, regex2, ""); + } + + if (mask & NF_REPLACE_WORDS) + for (auto& e : m_replacement) + ReplaceAll(newMsg, e.first, e.second); + + if (mask & NF_CUT_CTRL) + { + static std::regex regex4("([[:cntrl:]]+)"); + newMsg = std::regex_replace(newMsg, regex4, ""); + } + + if (mask & NF_CUT_PUNCT) + { + static std::regex regex5("([[:punct:]]+)"); + newMsg = std::regex_replace(newMsg, regex5, ""); + } + + if (mask & NF_CUT_SPACE) + { + static std::regex regex6("(\\s+|_)"); + newMsg = std::regex_replace(newMsg, regex6, ""); + } + + if (mask & NF_CUT_NUMBERS) + { + static std::regex regex3("(\\d+)"); + newMsg = std::regex_replace(newMsg, regex3, ""); + } + + if (mask & NF_REPLACE_UNICODE) + { + std::wstring w_tempMsg, w_tempMsg2; + Utf8toWStr(newMsg, w_tempMsg); + wstrToUpper(w_tempMsg); + + if (!isBasicLatinString(w_tempMsg, true)) + { + for (auto& s : m_unicode) + ReplaceAllW(w_tempMsg, s.first, s.second); + + if (mask & NF_REMOVE_NON_LATIN) + { + for (size_t i = 0; i < w_tempMsg.size(); ++i) + if (isBasicLatinCharacter(w_tempMsg[i]) || isNumeric(w_tempMsg[i])) + w_tempMsg2.push_back(w_tempMsg[i]); + } + else + w_tempMsg2 = w_tempMsg; + } + else + w_tempMsg2 = w_tempMsg; + + newMsg = std::string(w_tempMsg2.begin(), w_tempMsg2.end()); + } + else + std::transform(newMsg.begin(), newMsg.end(), newMsg.begin(), ::toupper); + + if (mask & NF_REMOVE_REPEATS) + newMsg.erase(std::unique(newMsg.begin(), newMsg.end()), newMsg.end()); + + return newMsg; +} + +bool Antispam::filterMessage(const std::string &msg) +{ + auto normMsg = normalizeMessage(msg); + auto origMsg = normalizeMessage(msg, m_originalNormalizeMask); + + bool block = false; + uint32 score = 0; + + for (auto& word : m_blackList) + { + if (origMsg.find(word) != std::string::npos || + normMsg.find(word) != std::string::npos) + { + block = true; + break; + } + } + + if (!block) + { + for (auto& word : m_scores[MSG_TYPE_NORMALIZED]) + if (normMsg.find(word.first) != std::string::npos) + score += word.second; + + for (auto& word : m_scores[MSG_TYPE_ORIGINAL]) + if (origMsg.find(word.first) != std::string::npos) + score += word.second; + + if (score > m_threshold) + block = true; + } + + return block; +} + +void Antispam::logSpam(MessageBlock& messageBlock, std::string const& reason) +{ + if (!LogsDatabase || !sWorld.getConfig(CONFIG_BOOL_LOGSDB_CHAT)) + return; + + static SqlStatementID insLogSpam; + SqlStatement logStmt = LogsDatabase.CreateStatement(insLogSpam, + "INSERT INTO logs_spamdetect SET accountId=?, guid=?, message=?, reason=?"); + logStmt.addUInt32(messageBlock.fromAccount); + logStmt.addUInt32(messageBlock.fromGuid.GetCounter()); + logStmt.addString(messageBlock.msg); + logStmt.addString(reason); + logStmt.Execute(); +} + +void Antispam::applySanction(MessageBlock& messageBlock, uint32 detectType, uint32 repeats) +{ + auto chatType = std::to_string(messageBlock.type); + + switch (detectType) + { + case DETECT_STANDARD: + logSpam(messageBlock, "DETECT_STANDARD, chatType " + chatType); + break; + case DETECT_SEPARATED: + logSpam(messageBlock, "DETECT_SEPARATED, chatType " + chatType); + break; + case DETECT_FLOOD: + std::string reason = "DETECT_FLOOD, " + std::to_string(repeats) + " repeats, chatType " + chatType; + logSpam(messageBlock, reason); + break; + } + + mute(messageBlock.fromAccount); + ChannelMgr::AnnounceBothFactionsChannel("ChatSpam", messageBlock.fromGuid, messageBlock.msg.c_str()); + + static SqlStatementID insDetect; + SqlStatement stmt = LoginDatabase.CreateStatement(insDetect, "INSERT INTO `antispam_detected` VALUES (?, 1, ?, ?) " + "ON DUPLICATE KEY UPDATE `detectScore` = `detectScore` + 1, `detectTime` = ?, `unmuteTime` = ?"); + time_t currentTime = time(nullptr); + time_t unmuteTime = currentTime + m_mutetime; + stmt.addUInt32(messageBlock.fromAccount); + stmt.addUInt64(currentTime); + stmt.addUInt64(unmuteTime); + stmt.addUInt64(currentTime); + stmt.addUInt64(unmuteTime); + stmt.DirectExecute(); + + QueryResult *result = LoginDatabase.PQuery("SELECT `detectScore` FROM `antispam_detected` WHERE `id` = %u", messageBlock.fromAccount); + if (result) + { + auto fields = result->Fetch(); + if (fields[0].GetUInt8() >= m_detectThreshold) + { + logSpam(messageBlock, "BAN SANCTION"); + if (m_banEnabled) + { + sWorld.BanAccount(messageBlock.fromAccount, 0, "Spam detect. See details in logs", "Antispam"); + LoginDatabase.PExecute("DELETE FROM `antispam_detected` WHERE `id` = %u", messageBlock.fromAccount); + } + } + delete result; + } +} + +bool Antispam::isMuted(uint32 accountId, bool checkChatType, uint32 chatType) const +{ + if (checkChatType) + { + uint8 type = getConvertedChatType(chatType); + if (type == A_CHAT_TYPE_MAX || (m_chatMask && (m_chatMask & (1 << type)) == 0)) + return false; + } + + auto itr = m_mutedAccounts.find(accountId); + if (itr != m_mutedAccounts.end()) + return true; + + return false; +} + +void Antispam::mute(uint32 accountId) +{ + m_mutedAccounts.insert(accountId); +} + +void Antispam::unmute(uint32 accountId) +{ + m_mutedAccounts.erase(accountId); + LoginDatabase.PExecute("DELETE FROM `antispam_detected` WHERE `id` = %u", accountId); +} + +void Antispam::showMuted(WorldSession* session) +{ + if (m_mutedAccounts.empty()) + { + ChatHandler(session).SendSysMessage("No muted spamers"); + return; + } + + ChatHandler(session).SendSysMessage("Muted spamers accounts list:"); + for (auto& e : m_mutedAccounts) + ChatHandler(session).SendSysMessage(std::to_string(e).c_str()); +} \ No newline at end of file diff --git a/src/game/Anticheat/Antispam/Antispam.h b/src/game/Anticheat/Antispam/Antispam.h new file mode 100644 index 00000000000..8c9c8a4071b --- /dev/null +++ b/src/game/Anticheat/Antispam/Antispam.h @@ -0,0 +1,167 @@ +#include +#include +#include +#include + +#include "Util.h" +#include "World.h" +#include "ChannelMgr.h" +#include "Anticheat.h" + +enum NormalizeFlags +{ + NF_CUT_COLOR = 0x001, + NF_REPLACE_WORDS = 0x002, + NF_CUT_SPACE = 0x004, + NF_CUT_CTRL = 0x008, + NF_CUT_PUNCT = 0x010, + NF_CUT_NUMBERS = 0x020, + NF_REPLACE_UNICODE = 0x040, + NF_REMOVE_REPEATS = 0x080, + NF_REMOVE_NON_LATIN = 0x100 +}; + +enum MessageType +{ + MSG_TYPE_NORMALIZED, + MSG_TYPE_ORIGINAL, + MSG_TYPE_MAX +}; + +enum AntispamChatTypes +{ + A_CHAT_TYPE_SAY = 0, + A_CHAT_TYPE_WHISPER = 1, + A_CHAT_TYPE_CHANNEL = 2, + A_CHAT_TYPE_MAX +}; + +enum DetectType +{ + DETECT_STANDARD = 0, + DETECT_SEPARATED = 1, + DETECT_FLOOD = 2 +}; + +typedef std::unordered_set StringSet; +typedef std::unordered_map UnicodeMap; +typedef std::unordered_map StringMap; +typedef std::unordered_map ScoreMap; +typedef std::unordered_set MutedAccountsSet; + +struct MessageBlock +{ + ObjectGuid fromGuid; + uint32 fromAccount; + ObjectGuid toGuid; + std::string msg; + uint8 type; + uint8 count; + time_t time; +}; + +struct MessageCounter +{ + uint32 count; + uint32 timeDiff; + time_t timeLast; + bool detectMarker; +}; + +struct pair_hash { + template + std::size_t operator () (const std::pair &p) const { + auto h1 = std::hash{}(p.first); + auto h2 = std::hash{}(p.second); + + // Mainly for demonstration purposes, i.e. works but is overly simple + // In the real world, use sth. like boost.hash_combine + return h1 ^ h2; + } +}; + +typedef std::pair LowGuidPair; +typedef tbb::concurrent_queue MessageQueue; +typedef std::unordered_map MessageBlocks; +typedef std::unordered_map MessageCounters; +typedef std::unordered_map> MessageRepeats; + +class Antispam : public AntispamInterface +{ + friend class ACE_Singleton; + public: + Antispam(); + ~Antispam() + { + if (m_worker.joinable()) + m_worker.join(); + } + + void loadFromDB(); + void loadConfig(); + void loadMuted(); + + std::string normalizeMessage(const std::string& msg, uint32 mask = 0); + bool filterMessage(const std::string &msg); + + void addMessage(const std::string& msg, uint32 type, PlayerPointer from, PlayerPointer to); + + void processMessages(uint32 diff); + void applySanction(MessageBlock& messageBlock, uint32 detectType, uint32 repeats = 0); + void logSpam(MessageBlock& messageBlock, std::string const& reason); + + bool isMuted(uint32 accountId, bool checkChatType = false, uint32 chatType = 0) const; + void mute(uint32 accountId); + void unmute(uint32 accountId); + void showMuted(WorldSession* session); + + uint8 getConvertedChatType(uint32 type) const + { + switch (type) + { + case CHAT_MSG_SAY: + case CHAT_MSG_YELL: + return A_CHAT_TYPE_SAY; + case CHAT_MSG_WHISPER: + return A_CHAT_TYPE_WHISPER; + case CHAT_MSG_CHANNEL: + return A_CHAT_TYPE_CHANNEL; + default: + return A_CHAT_TYPE_MAX; + } + } + + private: + bool m_enabled; + uint8 m_restrictionLevel; + uint16 m_originalNormalizeMask; + uint16 m_fullyNormalizeMask; + uint32 m_threshold; + uint32 m_mutetime; + uint32 m_chatMask; + uint8 m_messageBlockSize; + uint32 m_updateTimer; + uint32 m_messageRepeatCount; + float m_frequencyCount; + float m_frequencyTime; + float m_frequencyCoeff; + uint8 m_detectThreshold; + bool m_banEnabled; + bool m_mergeAllWhispers; + + StringSet m_blackList; + StringMap m_replacement; + ScoreMap m_scores[MSG_TYPE_MAX]; + UnicodeMap m_unicode; + + MutedAccountsSet m_mutedAccounts; + + MessageQueue m_messageQueue; + MessageBlocks m_messageBlocks[A_CHAT_TYPE_MAX]; + MessageCounters m_messageCounters[A_CHAT_TYPE_MAX]; + MessageRepeats m_messageRepeats[A_CHAT_TYPE_MAX]; + + std::thread m_worker; +}; + +#define sAntispam ACE_Singleton::instance() diff --git a/src/game/CMakeLists.txt b/src/game/CMakeLists.txt index f84222ba81e..d76fd1ed7fa 100644 --- a/src/game/CMakeLists.txt +++ b/src/game/CMakeLists.txt @@ -448,6 +448,8 @@ endif() if(USE_ANTICHEAT) list(APPEND game_SRCS + Anticheat/Antispam/Antispam.cpp + Anticheat/Antispam/Antispam.h Anticheat/MovementAnticheat/MovementAnticheat.cpp Anticheat/MovementAnticheat/MovementAnticheat.h Anticheat/WardenAnticheat/Warden.cpp diff --git a/src/game/World.cpp b/src/game/World.cpp index 27fcb3419e9..401a367e27b 100644 --- a/src/game/World.cpp +++ b/src/game/World.cpp @@ -1145,6 +1145,23 @@ void World::LoadConfigSettings(bool reload) setConfig(CONFIG_UINT32_AC_WARDEN_DB_LOGLEVEL, "Warden.DBLogLevel", 0); m_wardenModuleDirectory = sConfig.GetStringDefault("Warden.ModuleDir", "warden_modules"); + // Antispam + setConfig(CONFIG_BOOL_AC_ANTISPAM_ENABLED, "Antispam.Enable", true); + setConfig(CONFIG_BOOL_AC_ANTISPAM_BAN_ENABLED, "Antispam.BanEnable", false); + setConfig(CONFIG_BOOL_AC_ANTISPAM_MERGE_ALL_WHISPERS, "Antispam.MergeAllWhispers", false); + setConfig(CONFIG_UINT32_AC_ANTISPAM_MAX_RESTRICTION_LEVEL, "Antispam.MaxRestrictionLevel", 20); + setConfig(CONFIG_UINT32_AC_ANTISPAM_ORIGINAL_NORMALIZE_MASK, "Antispam.OriginalNormalizeMask", 0); + setConfig(CONFIG_UINT32_AC_ANTISPAM_FULLY_NORMALIZE_MASK, "Antispam.FullyNormalizeMask", 0); + setConfig(CONFIG_UINT32_AC_ANTISPAM_SCORE_THRESHOLD, "Antispam.ScoreThreshold", 4); + setConfig(CONFIG_UINT32_AC_ANTISPAM_MUTETIME, "Antispam.Mutetime", 3600); + setConfig(CONFIG_UINT32_AC_ANTISPAM_CHAT_MASK, "Antispam.ChatMask", 0); + setConfig(CONFIG_UINT32_AC_ANTISPAM_DETECT_THRESHOLD, "Antispam.DetectThreshold", 3); + setConfig(CONFIG_UINT32_AC_ANTISPAM_REPEAT_COUNT, "Antispam.RepeatCount", 5); + setConfig(CONFIG_UINT32_AC_ANTISPAM_UPDATE_TIMER, "Antispam.UpdateTimer", 60000); + setConfig(CONFIG_UINT32_AC_ANTISPAM_MESSAGE_BLOCK_SIZE, "Antispam.MessageBlockSize", 5); + setConfig(CONFIG_UINT32_AC_ANTISPAM_FREQUENCY_TIME, "Antispam.FrequencyTime", 3); + setConfig(CONFIG_UINT32_AC_ANTISPAM_FREQUENCY_COUNT, "Antispam.FrequencyCount", 3); + setConfig(CONFIG_UINT32_CREATURE_SUMMON_LIMIT, "MaxCreatureSummonLimit", DEFAULT_CREATURE_SUMMON_LIMIT); // Smartlog data diff --git a/src/game/World.h b/src/game/World.h index c831ad97966..b0e69cf7854 100644 --- a/src/game/World.h +++ b/src/game/World.h @@ -331,6 +331,18 @@ enum eConfigUInt32Values CONFIG_UINT32_AC_WARDEN_DEFAULT_PENALTY, CONFIG_UINT32_AC_WARDEN_CLIENT_BAN_DURATION, CONFIG_UINT32_AC_WARDEN_DB_LOGLEVEL, + CONFIG_UINT32_AC_ANTISPAM_MAX_RESTRICTION_LEVEL, + CONFIG_UINT32_AC_ANTISPAM_ORIGINAL_NORMALIZE_MASK, + CONFIG_UINT32_AC_ANTISPAM_FULLY_NORMALIZE_MASK, + CONFIG_UINT32_AC_ANTISPAM_SCORE_THRESHOLD, + CONFIG_UINT32_AC_ANTISPAM_MUTETIME, + CONFIG_UINT32_AC_ANTISPAM_CHAT_MASK, + CONFIG_UINT32_AC_ANTISPAM_DETECT_THRESHOLD, + CONFIG_UINT32_AC_ANTISPAM_REPEAT_COUNT, + CONFIG_UINT32_AC_ANTISPAM_UPDATE_TIMER, + CONFIG_UINT32_AC_ANTISPAM_MESSAGE_BLOCK_SIZE, + CONFIG_UINT32_AC_ANTISPAM_FREQUENCY_TIME, + CONFIG_UINT32_AC_ANTISPAM_FREQUENCY_COUNT, CONFIG_UINT32_AUTOBROADCAST_INTERVAL, CONFIG_UINT32_PARTY_BOT_MAX_BOTS, CONFIG_UINT32_PARTY_BOT_AUTO_EQUIP, @@ -583,6 +595,9 @@ enum eConfigBoolValues CONFIG_BOOL_AC_WARDEN_PLAYERS_ONLY, CONFIG_BOOL_AC_WARDEN_OSX_ENABLED, CONFIG_BOOL_AC_WARDEN_WIN_ENABLED, + CONFIG_BOOL_AC_ANTISPAM_ENABLED, + CONFIG_BOOL_AC_ANTISPAM_BAN_ENABLED, + CONFIG_BOOL_AC_ANTISPAM_MERGE_ALL_WHISPERS, CONFIG_BOOL_VISIBILITY_FORCE_ACTIVE_OBJECTS, CONFIG_BOOL_PLAYER_BOT_SHOW_IN_WHO_LIST, CONFIG_BOOL_PARTY_BOT_SKIP_CHECKS, From 4d4d6248e19c8daa2bc0d14f26a7c85648d16bf5 Mon Sep 17 00:00:00 2001 From: ratkosrb Date: Fri, 23 Jun 2023 15:19:10 +0300 Subject: [PATCH 2/2] Fix build. --- src/game/Anticheat/Antispam/Antispam.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/game/Anticheat/Antispam/Antispam.cpp b/src/game/Anticheat/Antispam/Antispam.cpp index 92d37683f7b..c9ce30bff8c 100644 --- a/src/game/Anticheat/Antispam/Antispam.cpp +++ b/src/game/Anticheat/Antispam/Antispam.cpp @@ -59,7 +59,7 @@ Antispam::Antispam() void Antispam::loadFromDB() { - sLog.outString("Loading table 'antispam_blacklist'"); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, "Loading table 'antispam_blacklist'"); m_blackList.clear(); QueryResult* result = LoginDatabase.Query("SELECT * FROM antispam_blacklist"); @@ -74,10 +74,10 @@ void Antispam::loadFromDB() delete result; } - sLog.outString(">> %u blacklist words loaded", m_blackList.size()); - sLog.outString(); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ">> %u blacklist words loaded", m_blackList.size()); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ""); - sLog.outString("Loading table 'antispam_replacement'"); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, "Loading table 'antispam_replacement'"); m_replacement.clear(); result = LoginDatabase.Query("SELECT * FROM antispam_replacement"); @@ -92,10 +92,10 @@ void Antispam::loadFromDB() delete result; } - sLog.outString(">> %u replacements loaded", m_replacement.size()); - sLog.outString(); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ">> %u replacements loaded", m_replacement.size()); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ""); - sLog.outString("Loading table 'antispam_scores'"); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, "Loading table 'antispam_scores'"); m_scores[MSG_TYPE_NORMALIZED].clear(); m_scores[MSG_TYPE_ORIGINAL].clear(); @@ -112,10 +112,10 @@ void Antispam::loadFromDB() delete result; } - sLog.outString(">> %u scores loaded", m_scores[MSG_TYPE_NORMALIZED].size() + m_scores[MSG_TYPE_ORIGINAL].size()); - sLog.outString(); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ">> %u scores loaded", m_scores[MSG_TYPE_NORMALIZED].size() + m_scores[MSG_TYPE_ORIGINAL].size()); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ""); - sLog.outString("Loading table 'antispam_unicode'"); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, "Loading table 'antispam_unicode'"); m_unicode.clear(); result = LoginDatabase.Query("SELECT * FROM antispam_unicode"); @@ -137,8 +137,8 @@ void Antispam::loadFromDB() delete result; } - sLog.outString(">> %u unicode symbols loaded", m_unicode.size()); - sLog.outString(); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ">> %u unicode symbols loaded", m_unicode.size()); + sLog.Out(LOG_ANTICHEAT, LOG_LVL_BASIC, ""); } void Antispam::loadMuted()