diff --git a/CMakeLists.txt b/CMakeLists.txt index 0817412b..caa61e4e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -84,13 +84,14 @@ make_directory(${CMAKE_BINARY_DIR}/locale) make_directory(${CMAKE_BINARY_DIR}/logs) # Copy ini files on first build -set(INI_FILES "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini") -foreach(ini ${INI_FILES}) - if (NOT EXISTS ${PROJECT_BINARY_DIR}/${ini}) +set(RESOURCE_FILES "authconfig.ini" "chatconfig.ini" "worldconfig.ini" "masterconfig.ini" "blacklist.dcf") +foreach(resource_file ${RESOURCE_FILES}) + if (NOT EXISTS ${PROJECT_BINARY_DIR}/${resource_file}) configure_file( - ${CMAKE_SOURCE_DIR}/resources/${ini} ${PROJECT_BINARY_DIR}/${ini} + ${CMAKE_SOURCE_DIR}/resources/${resource_file} ${PROJECT_BINARY_DIR}/${resource_file} COPYONLY ) + message("Moved ${resource_file} to project binary directory") endif() endforeach() diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp index 7f8187a5..fe8a0d10 100644 --- a/dChatFilter/dChatFilter.cpp +++ b/dChatFilter/dChatFilter.cpp @@ -8,8 +8,9 @@ #include #include "dCommonVars.h" -#include "Database.h" #include "dLogger.h" +#include "dConfig.h" +#include "Database.h" #include "Game.h" using namespace dChatFilterDCF; @@ -21,25 +22,30 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) { ReadWordlistPlaintext(filepath + ".txt"); if (!m_DontGenerateDCF) ExportWordlistToDCF(filepath + ".dcf"); } - else if (!ReadWordlistDCF(filepath + ".dcf")) { + else if (!ReadWordlistDCF(filepath + ".dcf", true)) { ReadWordlistPlaintext(filepath + ".txt"); ExportWordlistToDCF(filepath + ".dcf"); } + if (BinaryIO::DoesFileExist("blacklist.dcf")) { + ReadWordlistDCF("blacklist.dcf", false); + } + //Read player names that are ok as well: auto stmt = Database::CreatePreppedStmt("select name from charinfo;"); auto res = stmt->executeQuery(); while (res->next()) { std::string line = res->getString(1).c_str(); std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase - m_Words.push_back(CalculateHash(line)); + m_YesYesWords.push_back(CalculateHash(line)); } delete res; delete stmt; } dChatFilter::~dChatFilter() { - m_Words.clear(); + m_YesYesWords.clear(); + m_NoNoWords.clear(); } void dChatFilter::ReadWordlistPlaintext(const std::string& filepath) { @@ -49,12 +55,12 @@ void dChatFilter::ReadWordlistPlaintext(const std::string& filepath) { while (std::getline(file, line)) { line.erase(std::remove(line.begin(), line.end(), '\r'), line.end()); std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase - m_Words.push_back(CalculateHash(line)); + m_YesYesWords.push_back(CalculateHash(line)); } } } -bool dChatFilter::ReadWordlistDCF(const std::string& filepath) { +bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) { std::ifstream file(filepath, std::ios::binary); if (file) { fileHeader hdr; @@ -67,12 +73,14 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath) { if (hdr.formatVersion == formatVersion) { size_t wordsToRead = 0; BinaryIO::BinaryRead(file, wordsToRead); - m_Words.reserve(wordsToRead); + if (whiteList) m_YesYesWords.reserve(wordsToRead); + else m_NoNoWords.reserve(wordsToRead); size_t word = 0; for (size_t i = 0; i < wordsToRead; ++i) { BinaryIO::BinaryRead(file, word); - m_Words.push_back(word); + if (whiteList) m_YesYesWords.push_back(word); + else m_NoNoWords.push_back(word); } return true; @@ -91,9 +99,9 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath) { if (file) { BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::header)); BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::formatVersion)); - BinaryIO::BinaryWrite(file, size_t(m_Words.size())); + BinaryIO::BinaryWrite(file, size_t(m_YesYesWords.size())); - for (size_t word : m_Words) { + for (size_t word : m_YesYesWords) { BinaryIO::BinaryWrite(file, word); } @@ -101,31 +109,44 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath) { } } -bool dChatFilter::IsSentenceOkay(const std::string& message, int gmLevel) { - if (gmLevel > GAME_MASTER_LEVEL_FORUM_MODERATOR) return true; //If anything but a forum mod, return true. - if (message.empty()) return true; +std::vector dChatFilter::IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList) { + if (gmLevel > GAME_MASTER_LEVEL_FORUM_MODERATOR) return { }; //If anything but a forum mod, return true. + if (message.empty()) return { }; + if (!whiteList && m_NoNoWords.empty()) return { "" }; std::stringstream sMessage(message); std::string segment; std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)"); + std::vector listOfBadSegments = std::vector(); + while (std::getline(sMessage, segment, ' ')) { + std::string originalSegment = segment; + std::transform(segment.begin(), segment.end(), segment.begin(), ::tolower); //Transform to lowercase segment = std::regex_replace(segment, reg, ""); size_t hash = CalculateHash(segment); if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end()) { - return false; + listOfBadSegments.push_back(originalSegment); // found word that isn't ok, just deny this code works for both white and black list } - if (!IsInWordlist(hash)) { - m_UserUnapprovedWordCache.push_back(hash); - return false; + if (!IsInWordlist(hash, whiteList)) { + if (whiteList) { + m_UserUnapprovedWordCache.push_back(hash); + listOfBadSegments.push_back(originalSegment); + } + } + else { + if (!whiteList) { + m_UserUnapprovedWordCache.push_back(hash); + listOfBadSegments.push_back(originalSegment); + } } } - return true; + return listOfBadSegments; } size_t dChatFilter::CalculateHash(const std::string& word) { @@ -136,6 +157,8 @@ size_t dChatFilter::CalculateHash(const std::string& word) { return value; } -bool dChatFilter::IsInWordlist(size_t word) { - return std::find(m_Words.begin(), m_Words.end(), word) != m_Words.end(); -} +bool dChatFilter::IsInWordlist(size_t word, bool whiteList) { + auto* list = whiteList ? &m_YesYesWords : &m_NoNoWords; + + return std::find(list->begin(), list->end(), word) != list->end(); +} \ No newline at end of file diff --git a/dChatFilter/dChatFilter.h b/dChatFilter/dChatFilter.h index e8ae67d0..5acd6063 100644 --- a/dChatFilter/dChatFilter.h +++ b/dChatFilter/dChatFilter.h @@ -20,17 +20,18 @@ public: dChatFilter(const std::string& filepath, bool dontGenerateDCF); ~dChatFilter(); - void ReadWordlistPlaintext(const std::string & filepath); - bool ReadWordlistDCF(const std::string & filepath); - void ExportWordlistToDCF(const std::string & filepath); - bool IsSentenceOkay(const std::string& message, int gmLevel); + void ReadWordlistPlaintext(const std::string& filepath); + bool ReadWordlistDCF(const std::string& filepath, bool whiteList); + void ExportWordlistToDCF(const std::string& filepath); + std::vector IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true); private: bool m_DontGenerateDCF; - std::vector m_Words; + std::vector m_NoNoWords; + std::vector m_YesYesWords; std::vector m_UserUnapprovedWordCache; //Private functions: size_t CalculateHash(const std::string& word); - bool IsInWordlist(size_t word); -}; + bool IsInWordlist(size_t word, bool whiteList); +}; \ No newline at end of file diff --git a/dGame/dComponents/PetComponent.cpp b/dGame/dComponents/PetComponent.cpp index d54087aa..3cfb4e8d 100644 --- a/dGame/dComponents/PetComponent.cpp +++ b/dGame/dComponents/PetComponent.cpp @@ -1193,7 +1193,7 @@ void PetComponent::SetPetNameForModeration(const std::string& petName) { int approved = 1; //default, in mod //Make sure that the name isn't already auto-approved: - if (Game::chatFilter->IsSentenceOkay(petName, 0)) { + if (Game::chatFilter->IsSentenceOkay(petName, 0).empty()) { approved = 2; //approved } diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp index ef5b68ef..187ee12f 100644 --- a/dNet/ClientPackets.cpp +++ b/dNet/ClientPackets.cpp @@ -276,6 +276,7 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa std::string message = ""; stream.Read(chatLevel); + printf("%d", chatLevel); stream.Read(requestID); for (uint32_t i = 0; i < 42; ++i) { @@ -292,9 +293,19 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa } std::unordered_map unacceptedItems; - bool bAllClean = Game::chatFilter->IsSentenceOkay(message, user->GetLastUsedChar()->GetGMLevel()); + std::vector segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel()); + + bool bAllClean = segments.empty(); + if (!bAllClean) { - unacceptedItems.insert(std::make_pair((char)0, (char)message.length())); + for (const auto& item : segments) { + if (item == "") { + unacceptedItems.insert({ (char)0, (char)message.length()}); + break; + } + + unacceptedItems.insert({ message.find(item), item.length() }); + } } if (user->GetIsMuted()) { diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp index ae8f71a4..94792c91 100644 --- a/dNet/WorldPackets.cpp +++ b/dNet/WorldPackets.cpp @@ -192,19 +192,22 @@ void WorldPackets::SendChatModerationResponse(const SystemAddress& sysAddr, bool CBITSTREAM PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_CHAT_MODERATION_STRING); - bitStream.Write(static_cast(requestAccepted)); - bitStream.Write(static_cast(0)); - bitStream.Write(static_cast(requestID)); - bitStream.Write(static_cast(0)); + bitStream.Write(unacceptedItems.empty()); // Is sentence ok? + bitStream.Write(0x16); // Source ID, unknown - for (uint32_t i = 0; i < 33; ++i) { - bitStream.Write(static_cast(receiver[i])); + bitStream.Write(static_cast(requestID)); // request ID + bitStream.Write(static_cast(0)); // chat mode + + PacketUtils::WritePacketWString(receiver, 42, &bitStream); // receiver name + + for (auto it : unacceptedItems) { + bitStream.Write(it.first); // start index + bitStream.Write(it.second); // length } - for (std::unordered_map::iterator it = unacceptedItems.begin(); it != unacceptedItems.end(); ++it) { - bitStream.Write(it->first); - bitStream.Write(it->second); - } + for (int i = unacceptedItems.size(); 64 > i; i++) { + bitStream.Write(0); + } SEND_PACKET } diff --git a/resources/blacklist.dcf b/resources/blacklist.dcf new file mode 100644 index 00000000..cdc64c2f Binary files /dev/null and b/resources/blacklist.dcf differ