From fab8a1e982c7617e3dd4469804f9fe55c0003d4a Mon Sep 17 00:00:00 2001
From: Jett <55758076+Jettford@users.noreply.github.com>
Date: Sun, 17 Jul 2022 07:54:36 +0100
Subject: [PATCH 1/3] Implement new chat features

---
 CMakeLists.txt                     |   9 ++--
 dChatFilter/dChatFilter.cpp        |  65 +++++++++++++++++++----------
 dChatFilter/dChatFilter.h          |  15 +++----
 dGame/dComponents/PetComponent.cpp |   2 +-
 dNet/ClientPackets.cpp             |  15 ++++++-
 dNet/WorldPackets.cpp              |  23 +++++-----
 resources/blacklist.dcf            | Bin 0 -> 16160 bytes
 7 files changed, 84 insertions(+), 45 deletions(-)
 create mode 100644 resources/blacklist.dcf

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 <regex>
 
 #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<std::string> 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<std::string> listOfBadSegments = std::vector<std::string>();
+
 	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<std::string> IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true);
 
 private:
 	bool m_DontGenerateDCF;
-	std::vector<size_t> m_Words;
+	std::vector<size_t> m_NoNoWords;
+	std::vector<size_t> m_YesYesWords;
 	std::vector<size_t> 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<char, char> unacceptedItems;
-	bool bAllClean = Game::chatFilter->IsSentenceOkay(message, user->GetLastUsedChar()->GetGMLevel());
+	std::vector<std::string> 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<char>(requestAccepted));
-	bitStream.Write(static_cast<uint16_t>(0));
-	bitStream.Write(static_cast<uint8_t>(requestID));
-	bitStream.Write(static_cast<char>(0));
+    bitStream.Write<uint8_t>(unacceptedItems.empty()); // Is sentence ok?
+    bitStream.Write<uint16_t>(0x16); // Source ID, unknown
 
-	for (uint32_t i = 0; i < 33; ++i) {
-		bitStream.Write(static_cast<uint16_t>(receiver[i]));
+    bitStream.Write(static_cast<uint8_t>(requestID)); // request ID
+	bitStream.Write(static_cast<char>(0)); // chat mode
+
+    PacketUtils::WritePacketWString(receiver, 42, &bitStream); // receiver name
+
+	for (auto it : unacceptedItems) {
+		bitStream.Write<uint8_t>(it.first); // start index
+		bitStream.Write<uint8_t>(it.second); // length
 	}
 
-	for (std::unordered_map<char, char>::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<uint16_t>(0);
+    }
 
 	SEND_PACKET
 }
diff --git a/resources/blacklist.dcf b/resources/blacklist.dcf
new file mode 100644
index 0000000000000000000000000000000000000000..cdc64c2f5b245f4526974ab9cb1770fd8355b783
GIT binary patch
literal 16160
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

literal 0
HcmV?d00001


From 945e57249393d8b3586016afd4a795c507caee87 Mon Sep 17 00:00:00 2001
From: Jett <55758076+Jettford@users.noreply.github.com>
Date: Sun, 17 Jul 2022 09:40:34 +0100
Subject: [PATCH 2/3] Add best friend check and complete blacklist

---
 dChatFilter/dChatFilter.cpp |  46 ++++++++++++-----------------
 dChatFilter/dChatFilter.h   |   5 ++--
 dGame/User.cpp              |   2 ++
 dGame/User.h                |   5 ++++
 dNet/ClientPackets.cpp      |  56 ++++++++++++++++++++++++++++++++++--
 resources/blacklist.dcf     | Bin 16160 -> 16160 bytes
 6 files changed, 82 insertions(+), 32 deletions(-)

diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp
index fe8a0d10..eb6674a4 100644
--- a/dChatFilter/dChatFilter.cpp
+++ b/dChatFilter/dChatFilter.cpp
@@ -19,12 +19,12 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) {
 	m_DontGenerateDCF = dontGenerateDCF;
 
 	if (!BinaryIO::DoesFileExist(filepath + ".dcf") || m_DontGenerateDCF) {
-		ReadWordlistPlaintext(filepath + ".txt");
-		if (!m_DontGenerateDCF) ExportWordlistToDCF(filepath + ".dcf");
+		ReadWordlistPlaintext(filepath + ".txt", true);
+		if (!m_DontGenerateDCF) ExportWordlistToDCF(filepath + ".dcf", true);
 	}
 	else if (!ReadWordlistDCF(filepath + ".dcf", true)) {
-		ReadWordlistPlaintext(filepath + ".txt");
-		ExportWordlistToDCF(filepath + ".dcf");
+		ReadWordlistPlaintext(filepath + ".txt", true);
+		ExportWordlistToDCF(filepath + ".dcf", true);
 	}
 
 	if (BinaryIO::DoesFileExist("blacklist.dcf")) {
@@ -48,14 +48,15 @@ dChatFilter::~dChatFilter() {
 	m_NoNoWords.clear();
 }
 
-void dChatFilter::ReadWordlistPlaintext(const std::string& filepath) {
+void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool whiteList) {
 	std::ifstream file(filepath);
 	if (file) {
 		std::string line;
 		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_YesYesWords.push_back(CalculateHash(line));
+			if (whiteList) m_YesYesWords.push_back(CalculateHash(line));
+			else m_NoNoWords.push_back(CalculateHash(line));
 		}
 	}
 }
@@ -94,14 +95,14 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) {
 	return false;
 }
 
-void dChatFilter::ExportWordlistToDCF(const std::string& filepath) {
+void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteList) {
 	std::ofstream file(filepath, std::ios::binary | std::ios_base::out);
 	if (file) {
 		BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::header));
 		BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::formatVersion));
-		BinaryIO::BinaryWrite(file, size_t(m_YesYesWords.size()));
+		BinaryIO::BinaryWrite(file, size_t(whiteList ? m_YesYesWords.size() : m_NoNoWords.size()));
 
-		for (size_t word : m_YesYesWords) {
+		for (size_t word : whiteList ? m_YesYesWords : m_NoNoWords) {
 			BinaryIO::BinaryWrite(file, word);
 		}
 
@@ -128,21 +129,18 @@ std::vector<std::string> dChatFilter::IsSentenceOkay(const std::string& message,
 
 		size_t hash = CalculateHash(segment);
 
-		if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end()) {
-			listOfBadSegments.push_back(originalSegment); // found word that isn't ok, just deny this code works for both white and black list
+		if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && whiteList) {
+			listOfBadSegments.push_back(originalSegment);
 		}
 
-		if (!IsInWordlist(hash, whiteList)) {
-			if (whiteList) {
-				m_UserUnapprovedWordCache.push_back(hash);
-				listOfBadSegments.push_back(originalSegment);
-			}
+		if (std::find(m_YesYesWords.begin(), m_YesYesWords.end(), hash) == m_YesYesWords.end() && whiteList) {
+			m_UserUnapprovedWordCache.push_back(hash);
+			listOfBadSegments.push_back(originalSegment);
 		}
-		else {
-			if (!whiteList) {
-				m_UserUnapprovedWordCache.push_back(hash);
-				listOfBadSegments.push_back(originalSegment);
-			}
+		
+		if (std::find(m_NoNoWords.begin(), m_NoNoWords.end(), hash) != m_NoNoWords.end() && !whiteList) {
+			m_UserUnapprovedWordCache.push_back(hash);
+			listOfBadSegments.push_back(originalSegment);
 		}
 	}
 
@@ -155,10 +153,4 @@ size_t dChatFilter::CalculateHash(const std::string& word) {
 	size_t value = hash(word);
 
 	return value;
-}
-
-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 5acd6063..62a47242 100644
--- a/dChatFilter/dChatFilter.h
+++ b/dChatFilter/dChatFilter.h
@@ -20,9 +20,9 @@ public:
 	dChatFilter(const std::string& filepath, bool dontGenerateDCF);
 	~dChatFilter();
 
-	void ReadWordlistPlaintext(const std::string& filepath);
+	void ReadWordlistPlaintext(const std::string& filepath, bool whiteList);
 	bool ReadWordlistDCF(const std::string& filepath, bool whiteList);
-	void ExportWordlistToDCF(const std::string& filepath);
+	void ExportWordlistToDCF(const std::string& filepath, bool whiteList);
 	std::vector<std::string> IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true);
 
 private:
@@ -33,5 +33,4 @@ private:
 
 	//Private functions:
 	size_t CalculateHash(const std::string& word);
-	bool IsInWordlist(size_t word, bool whiteList);
 };
\ No newline at end of file
diff --git a/dGame/User.cpp b/dGame/User.cpp
index 3efc4d0a..98a37954 100644
--- a/dGame/User.cpp
+++ b/dGame/User.cpp
@@ -18,6 +18,8 @@ User::User(const SystemAddress& sysAddr, const std::string& username, const std:
 	m_SystemAddress = sysAddr;
 	m_Username = username;
     m_LoggedInCharID = 0;
+
+	m_IsBestFriendMap = std::unordered_map<std::string, bool>();
 	
 	//HACK HACK HACK
 	//This needs to be re-enabled / updated whenever the mute stuff is moved to another table.
diff --git a/dGame/User.h b/dGame/User.h
index 3be9148e..78640b38 100644
--- a/dGame/User.h
+++ b/dGame/User.h
@@ -42,6 +42,9 @@ public:
 	bool GetLastChatMessageApproved() { return m_LastChatMessageApproved; }
 	void SetLastChatMessageApproved(bool approved) { m_LastChatMessageApproved = approved; }
 
+	std::unordered_map<std::string, bool> GetIsBestFriendMap() { return m_IsBestFriendMap; }
+	void SetIsBestFriendMap(std::unordered_map<std::string, bool> mapToSet) { m_IsBestFriendMap = mapToSet; }
+
 	bool GetIsMuted() const;
 
 	time_t GetMuteExpire() const;
@@ -63,6 +66,8 @@ private:
 	std::vector<Character*> m_Characters;
     LWOOBJID m_LoggedInCharID;
 
+	std::unordered_map<std::string, bool> m_IsBestFriendMap;
+
 	bool m_LastChatMessageApproved = false;
 	int m_AmountOfTimesOutOfSync = 0;
 	const int m_MaxDesyncAllowed = 12;
diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp
index 187ee12f..abb08688 100644
--- a/dNet/ClientPackets.cpp
+++ b/dNet/ClientPackets.cpp
@@ -30,6 +30,9 @@
 #include "VehiclePhysicsComponent.h"
 #include "dConfig.h"
 #include "CharacterComponent.h"
+#include "Database.h"
+
+
 
 void ClientPackets::HandleChatMessage(const SystemAddress& sysAddr, Packet* packet) {
 	User* user = UserManager::Instance()->GetUser(sysAddr);
@@ -276,7 +279,6 @@ 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) {
@@ -285,6 +287,12 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa
 		receiver.push_back(static_cast<uint8_t>(character));
 	}
 
+	if (!receiver.empty()) {
+		if (std::string(receiver.c_str(), 4) == "[GM]") {
+			receiver = std::string(receiver.c_str() + 4, receiver.size() - 4);
+		}
+	}
+
 	stream.Read(messageLength);
 	for (uint32_t i = 0; i < messageLength; ++i) {
 		uint16_t character;
@@ -292,8 +300,52 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa
 		message.push_back(static_cast<uint8_t>(character));
 	}
 
+	bool isBestFriend = false;
+
+	if (chatLevel == 1) {
+		// Private chat
+		LWOOBJID idOfReceiver = LWOOBJID_EMPTY;
+
+		{
+			sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT name FROM charinfo WHERE name = ?");
+			stmt->setString(1, receiver);
+
+			sql::ResultSet* res = stmt->executeQuery();
+
+			if (res->next()) {
+				idOfReceiver = res->getInt("id");
+			}
+		}
+
+		if (user->GetIsBestFriendMap().find(receiver) == user->GetIsBestFriendMap().end() && idOfReceiver != LWOOBJID_EMPTY) {
+			sql::PreparedStatement* stmt = Database::CreatePreppedStmt("SELECT * FROM friends WHERE (player_id = ? AND friend_id = ?) OR (player_id = ? AND friend_id = ?) LIMIT 1;");
+			stmt->setInt(1, entity->GetObjectID());
+			stmt->setInt(2, idOfReceiver);
+			stmt->setInt(3, idOfReceiver);
+			stmt->setInt(4, entity->GetObjectID());
+
+			sql::ResultSet* res = stmt->executeQuery();
+
+			if (res->next()) {
+				isBestFriend = res->getInt("best_friend") == 3;
+			}
+
+			if (isBestFriend) {
+				auto tmpBestFriendMap = user->GetIsBestFriendMap();
+				tmpBestFriendMap[receiver] = true;
+				user->SetIsBestFriendMap(tmpBestFriendMap);
+			}
+
+			delete res;
+			delete stmt;
+		}
+		else if (user->GetIsBestFriendMap().find(receiver) != user->GetIsBestFriendMap().end()) {
+			isBestFriend = true;
+		}
+	}
+
 	std::unordered_map<char, char> unacceptedItems;
-	std::vector<std::string> segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel());
+	std::vector<std::string> segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel(), !(isBestFriend && chatLevel == 1));
 
 	bool bAllClean = segments.empty();
 
diff --git a/resources/blacklist.dcf b/resources/blacklist.dcf
index cdc64c2f5b245f4526974ab9cb1770fd8355b783..ca4db242aa20dbc20082100845e658ab7d958bb6 100644
GIT binary patch
literal 16160
zcmb8WRaBJ?)UFNE(#=A;yFt2Jx;v#irMsl0yF;W)x*O?6I;2~=+3R3G-#><9yyN?~
z2V7&qa*1&A%=^BkxR|8qI|vAfZ8-1;@SkZ4keZ>b_c_~TRuxi_ab2-DmEvLwKAZ&h
zcctkS!LN<K<*4rHm2qxpnxH}5K6^)4rO=>zn>{0Z49O-$x05+`i#zV*AM)$b6qifT
zBO8CP+wbsVGM>|#MG6%d7wK)-wHks;T2kIV!)Qp?LMEDhV5jLE?1n+zk1xmi#msS7
zM)WqGv~kN+>e&$7zLgRx#bsh*Z0AhVup$s5D;yDH%5bOs5pk_Y4@`E%wV-7wtHuL{
z%IEZKjz$7|McMHn!4M{xufpmRW2>jV{VE|m1dZI%6gv0LFrZfL{RTOFqM%quZGi1e
z;a}bG>(xSJpW-^C4qGV-Z@1`1tH&+hj&%qhQ<JTpQKsoMNNRI;z?3Gb-ST?pzlASA
z7=Z1-N*GWkf4(Uz^{egpXeKyPfucyYKXJgmlJGJl8{ZJVw>^>XmkTzg5P#lH7euu!
zqoJg$h_U_UDqlhq#xjN#mwCVS*!Um!^VAZ8G5B4^^yaG<I4eVmJ7JaOB}Plx!Nb=f
zY}E{qbBB^ZXu@&iPdch6mZan2v4e?Py?noe!-HS0aZ}&b4-XoKzCBPy`t)}|d}k?a
zCimJlNQ8?L{gWmjGC9&Lu^_9OAp=)8mx-uS;+Vzu?z)a-Z5!U?Tyb~%0gt%mWf5Kh
z_Gw-$xpYpuArIg~xxo4ys>`d2u6RyifVvM+jN&&FS|h#1px)wriK<^prJP(l&AwT-
z&EzC`x}19*^0&=6KOckOoW2ALUQGM2bwp}+xb11@Phq?#U|h}KLKu+m>M}^c5u2n$
z|BT?HvYqyTQp~Mk@Bt5v?N=MjVxU*syQRC>;pp|IU6J<QgEeg!P$}QOw<78%Yq<@n
z_ql?2_a)TtGw3Rb&e3yZkAG19K4n;d=h)<#gK8C<rTs>CpV<uQ3F0*mbD(ZZzA3$J
zA*}jF#53X<LHCQS0mpjcWbgn-?e1VBh3Gbe??|}4jTZC0IoXsniW}}cN*T_>!z+m$
z%l!23g+Pno(V4K9@@YSor)`?}!tX@Pbm4aIDU}Dt({JJ=*@e-BZ8Pws8M7`^;OR5t
zlZv!$ET-hvlN93V8GiPwlx4p2%D8auRzX&m_hjsUmt9D)yq=x}Cj9dH0*o-q`}D@C
zWSrUVW+}g>nSe{(pnlRu4KEQ)5UChMQwdCZrh$v^vqNw^_sB@~%Qp>CVn!IAo)iHh
zKcnFJ1Ug0hPoui9y}>H;Mv%DdvH6qK+!`xauDWN0-0xhs&t|8h1k98<zeBx?C>Jve
zoj(qdSo@<p4G_a-mE>gsRlJuKt}I`rJ*2~1vN8Fwz#zK;C%X`O%_WYbU9?Ov508`n
zoIbkz2V{7)(y0C?Rf%*vZME~()xMNJW5K<e&wTjVv8=M@L3nk(cE7q*ZPvQ3$4@HL
z$7$h+y?l-?D5w8=)dbb(CMT?9LU<i5Q_9Q9`xW$s>~J89W)s*gW*BrfHBM#n_pJ*=
zHe43_{29UQZtd;ML3DUL@6&H=ukHa@gxoUb+qk1UL*yspeZl`a#SG>n|LenF>n&f=
zasOae@chL8YE&N~<9#dhy2~Mb<-2T@_mF+PY$@mPNOZ0Cr;x?pW=tP9`^2hh`=M+q
z0QoXa`DbX2Rxuv5!Qhcw><WF>#3NR;b|#JOtDCbs+ocgd;o}l@HEOapRb`JtV`tQ~
znKysY5UN3@vRoRRxrIFhEj-&|Rmb{hBIHmRuF_rhW4DyzcZA|HNPzWGW6IUlm1h9Y
zU}Z5uqVf>80rjfC>ZzXAFMhYw9hgPEyE$yd@gD3CeC#>qk-S^xvdYe|k))oZ@w>&0
z!1@%9ZV4vbr*MCCt;f-H_ztJSft0%6d-8C-U>@zs6WAcIxr>lfCM#A9W**XyUp-e3
zW)3os?}dITKQ}<;4?upOUH3?*<wpc2G`*JhCB<QY&)gjbmGn~pOe<a-2V-^}ikeov
zRx4^?y(T}3tLcn<{+WG`CG1arMIw=JQ-!+<?1aA+8}`HlBdRk@;ov0#BNj9I(PXph
z;N7*x-qo#EC@b>wYAI#ck@#w9y$e$eT)eILP6M8yeYLvwS|jR|8abTFQq&E0YvoSQ
zk?L$csCkdtKoU_Y53?QVG3Ct$Q_(D#i_kwz8I~_+m}wR1X9l2{-(UQ;Z=w&xdCh%R
zMIOgCnVUhex;UenCw)CUfjv&NX59{Ea!*Swpqc!{Mp1CfJCq5L*zozyA)+2$v2TBW
zOF6n^tHbAVs4^u1<9okUwhkufqcH@`XVxUCiLA590W*~*n+@4e)9YZ|o^0#VfIE4H
zP6ZpXV*aCL=OFi?kf-g;^QpH{R~=LIrhA0H&LltaIFQ&lVYO{1V;={!E`A+2w{P75
zjJ!W&+Kz+i|8Z8cow@4q7Q9<~vZC-w0ANUnrww_-UTpn4=Hn{F#ki})qmLMQI0-mC
zX;3$DFw`}mOak=^v3(3|v-1oWKbdUga+-m%6TL)|lHX9fkAw>7s98|^fr?mE1Ndgt
zh4*;1gSfNlcVF{c4<0^kx}C-^Qx`jKnojdeCr`$|!;@$3ML5aACH5;d&PAJG*xmE{
zJ5^5WHS?*ygG=mp8+~XDo$NteD0PFf-u^jt6TS*-yx*qRDKF{x^d>V5>2t;GX&<WZ
zWegcCo_g}eoJK<3S><(T<d9uuI_1j{*U9F>dwNY`EDb(cO{Hxfr>UQPzk?6rTx=RF
z^Tpp0buP4GGk%v5OgPgQ{1SNY`U%#IZ*cCtYmkA`aNJ*8<->)>ls;%mcL6#R;&r_J
zZnel6rfKjjL1!}G$h_ZeP(0Hphz9E}XTk8bJ3~IYYakflM_;n<#5-P9(C_?8K?4R2
zQx!z8?lK+>4_d_L(eCKgnKp%t8MI9F0QWeaQR0)w9#(ujjf1Z(T>Q;3iQ-9lurj8<
zMWCl&E9l$(RT0#G@-NYHCX8qG#-wkZq@p0ep#!-74gH;Z{X)2ws^_3u3nFnHil9au
zZ)GlQHGxJPTWBu-g&aaMa#i#cWb3$~a+MZ=K?ZyCTl_1)XM~f1lP9YR0fN2nAC6N>
zx4Bt$b6@gu2G&Zk<7cCw^`vVs<bJv;t~5vXh^3^||NU^NI=&-cLwThDioa<_H2A5b
z<P(TVIUc0-lGY{Q01!K+-?iE;${Zlw-yq-0Ij2pXgPtjjfa&FG`a>zSe7S8CRdvLK
z0S;WpsoK!RJy&0yq@Fg6qv%GT^yZk;?zBsRSJeRQ-**>Kt8ELyUeGA_l~qv{EiyIk
z0tJ=w*IRWz+w=Q!$JiMHQ;_wzHl!?s9DA+E?0FilxVUaA&6X4^rDK?7RBh`UX_<c+
z6*{3y-`fje(XqvBrcv8c1;{6*X5{4RKbIqvE!jvjo9YWenaUKMo!sk@c~e8iX7cl5
zW_N4H44BEH5ew632i);~?}Q=JMy00Z7wv<xKOnr~&{#a?d$r;&hu=?&nG|gWc+_rV
zgXTW41n^)SJO>OcsV`|3=}h~6H#T63*yA#HkI`_E+cR)RhKEa<$_V{t4zLIJH7({r
zzg1GA_%5Tms2sz-`Hf<uN&T4YEk0$`L|gM)=|t{R8i%+QBr@n4`gU%|b&1!>jnb_%
z)B#^c#@(<lyU@q9#8+hTVajdu@~fu+C+5r>E1Wxe0ffysQItu!HYgr#5D~k;Z<h7?
zG~-XDK4$Dk9^I>=qeg3eM1%^ls<U!K15|)1?Faj6)UoKu(O<OitNT_TuCM%pG>TV}
z6A1LG)?JYRl9<<AlZ+>R1;{Gz_E%E7<9Ezg@b?J^a~rq()|Yc{9UnOU(wBo~8UL8~
zio_nQ&8Ko%z{u+THPEvyI66*M663ZXYH*N(D5tonm-NSZ0;6*QXTXt+w6aQ8z81W7
zbH21-05?)-DE;r2wl#rDv$!;u9wg9N%Z=0x3(iIvIWNF>$p@uYM`G~+ZM3Dn2a(%<
z2Iyb^>CiKYEg4{A=lE7mz|?|YXa|RLA0sTl1TiSvKD7*g)XOoUQzZVqyN^b4r>%26
z{YIz9y&P<i9BbRhz1)<L2v~kIT!RGj%J)qbasH-q9+tF&e~7<R)sE;kBk<MCOHws1
z|7kLjE%xaGnVJblYWHe6oQ=tB-ivdN7Ps!`@^@;VFhE>*m#4D9tz;0&WXYX=J&!Gt
z_6gjNoVSa}>Ei0Hh;z1+<9)MGe=uR^4z#w9Q}3ph0(fw#f~^TdG5QTw8C`SpYahVI
zQOu>uWTHbJA##jPf4T<w^Wk?GUj+AWwUG~UqR`O*_M|t~E2oL40$je0&6rC{q5)_a
zZrG#2BJ{C{AUksY3rmh-xdD>S^4Si+^mgxf(;KTB*<XB>wPX+&08OI6zn|xp8^|wA
zya?8rGytEm%!FNhksGDsweEA3&Y(PV{9b3sLSO+-_11|yGeG|WI8zIr$)JBF&K0)N
z(Kd^R2kHgT8OyB##330Ywv<+9h>oIvBCxslN#O#M=iaA1BkzMH{a1=s$l&+;IXGMO
zra|xbMR2yDOD0$>G-E?KzfqlQK81EI?I{m-B5)oMemiEd(5eaD8Q7fb^n-lYtg$fH
z8G8-MPCEhrbZFsY-c-V3p<x^<KrJelST=}k*i_qiaMPoy^I&e|nCQ26-nIS*vDR2r
zX8KXG^~Ek?;BcqYZF2P;!<md=PU6>CS3e_7i$3HLKS(YU`tk+I3HvV+^b3`k0Jne3
z<93d(egTMYtMRcfG<=~yaIrQx3YpW93-(W<=KKcbcLBY0$hTF$gH<BJVD)VONrK0;
z#cpP`a5wJ2a!md(YwMDe=G#Zb&RI_q=8~0#FS)-@TzZuC{zT~XWz%EbpwKMsvpBZ=
zP9haMvsGp@9I;rMM0{sylyI65)Eh?f&IQ`BC;`1<m-5yl9d)bW$}}Pd+u^#h%=`e3
zmysv+ua)dVUP9SVCjvfDkj`O@ECluk^bXIVIi~uoom!Osoq3VfUQG{<Y=1#7273qU
zV)#`p0@<Y4^k{T-c?w>k8Fhix-cQaPyc;d)9jr!9AQJE7ni*7xvu+tpi(i+&5bv_Z
z(uLJD4+3m<?f6OG89)BOp&f#~^8Q}kXua`R^`)79!<FlvH}X(~Lk~gd_Q*qIm(h0k
zx;2$Kg826GCjO0A(;{>07wot&Px_^f1jIl!!=0N7?${`po+`Ku89Ke`pA$ZMlS}rd
zyLx>*^&YmNl>WRT@f826K)TBTFha}z8jttK$n4vhgN5bin`jgNfd}hKwAC1OGEVJF
ztLddJ{%YJBe9sKrV#E&T)vQ$crzNp<Gi+M?CDy-mmyNcr`}H!U>0{i}g{Y@hx;ls=
zXrB=t)*wmZ_je#rF#9#PL7uz_ALe%>5VfRFs`TI%Vzi=!Qr?Me=~=*<t<u}P7+X9j
zuTJlD_p~9Ej3@f~62;cTbqrAlmZLz67IGvE98f;6@jhIs33=ExdOJET$$LDyCAYeB
zMU?C5Tin7wVwI<;WS`9o2}GEd?MS>*#9TANJ!hliE`_nS#pk2dtYx8wq~k_cuVwWF
zr)#dv%&1>kP%#+D^3Uh^VyhwRv>c}s8|7hB&<--oJs#zJs*Sd>k9=U`J~M$?2$kPt
zoEe_0s4Rkf!}1i(hxK^~>9{mv{-{!OOnl_`(}F}^$RJaMTmPLi^!iB-p+=C!?dOGh
z1uN<yWvR%C_cYF|u)m$c$>r$bvKe7%=j}2-L_l06cvb1rqyMr>cJmC_T{|=9u)^pR
z(Sox8{SeTcyz@f8+adV$Xn#~HiLV0C?eE^^Dbpznz`Sng^mp^<YFkeQgGPFsyDd&E
zpU^a?AHuzq`>Mxjss(kxZ2kW@vbRlCs&AtQP!}oI+i*kU6#n-nQK_q?UcO@Q1TSd)
z&A>adYpo-Mv|CWu7zxYKlX08{>LrS=u%@ZQ=0tEHR$aNvP<+Lx&2;m`9ml3*L)n}&
zpXDp0ti@EISR?!i?V@2dm}GQx2Pl4@Csfqx$rgUkLydS6Tv2c&VuiZ=N(;4xBPJ!-
zwi^2A-CPDoxrO|YetURSHJgItX-IzF0lE2#=i9QHK9W_@aci6?G2bj%Cf);SPg#iL
zX~TMceuA3RA&q27h6Wu(7VFh)ngo^9!ToB1U5FJ#78u)1d3l-G!GB~y$(AOvJHpLO
zHau-c7JU~gg~t7*r;P|pl7{q4&krapR25CTjvGVr_vqmFHDo;}xf)DPv!<Nz+4IEl
z)P+1x+aP7_apy>HVjYm_5iaa^`#CR9MhF9EH%1S|MoUNg7S{Pp?%D+TyF5{*huf;3
ziKZ9FQ%~bOyH}6AyN=bXSiEnF24Xshs7HX*Gu|1}YSKDQ*JftUSq$p1aIpSBNtan>
z^CFB8KVXo?6A>3J2GkNwK<;06FQ#s`x&Ea1!L?Ry6E}*Yd9OZ7X$IFhyHkaFjdhte
z>4j+qi-EV)F1nG@c)POQ0=v0@tk+5BIhEK|A_n%$A;cRRW{&09-^)fPTIvVMFiCV+
zj*n5RpL0+zJRob!TDiwA=!eD{t9<L?W_4{u7YE@Eetv2S0GRSRojJ5cMF((f4?NFO
z(E*IXDIZ|W+;r+uGlyhKoLdH15y2&4F3sNs@c9q#jXiQj!gA%<?iy23F~AghUB%z&
z;T#9d)!cO)r0))eRYw?H5n=%n(}HJngjj$fCW@mU-jh`VBuR#g`|#Kx4iKSzyM119
zd>EkdPNnh!WtKd^o>5=SZ<;!=W!y^H{^)Y?TpH`aC}QX*S*b^EC@W7haJ2+srL~i|
z+6_PFu{2ybLZ(N%5iol&0IC`IW@Nqj5J?HZBYEOotp1?^6X{}={b+a6x}EUlz=G$_
zNhB#WiHH)(6B}bK*hC!PSp}i55v92Eh-hN{qJ5JRgY)?UHoEt0Z=(~1=P5-%<K*_4
zk@!#6anp4alNybpMky<yfM|H&52U6(Ngs=}DCCB7<G<(ldbz1W5(n+CcusMvbYZW{
z9=cj#0B3pT`*|NP#l8%ET@BEWzp~|!sVphj`vB0pbM{^ym+=?F<&Y=U*trnEVfO{^
zW|w3&fRZu7Oe$X;w*ayljt$A%r#U;Z`Vr~1luA}(SJ4jPOcCM_zOZs$i>d&0wORWx
z)bs~>wo#{QK_qPx;AiT^4@3E@(0jpNsSByO!XJk%3DxXm`xxAjSPaHL@$hwdPK6T`
z$4x|aE_|c-8zoQ$o-vB!BANOGhejAgSvrnDNTTTFVw^oE?iWsrMat@4<Qq(`>S&Ma
z(hzsuBneFE<bzYCH`N+b;W$2VIO=jxL(5;6a`787=qQu^CT>`8uyoel3KyA7g&%*7
z0|@gWjaXh}I0&Fp;c-eEE>B*z{S3koEdI{1TS5(|-4AOx=Mm%~fzo3>8Swvqs8q6U
z<eGj;{Dk)7+rTr@nKrKUdCt!X9Sp?iZpRSx{s|Wg{K03WS8d#F6W53#dY*oOqLq|Q
zNOg}{QoJQo;b<#yy=2NELoi?3x#LuzU}4tRy^buvb3*$_L&7u6Cc>QcSjq-Zp198Y
zO&o29P%MO-{Ox}0DR=+18}RSnGj7#>F<C+nWiZOE<cw{E3;~91GBhfQoY8k2_K9tT
z3m2x$9ec&Y#U(6Pi$sJ4@@F*r=i|FyEN5A$5FM|0{&B0`bNBoq7X%*Hh~POaZWZ5A
zFjSA8|5+@%=FsCzKCfrV{JTd@6^*ce<0GEv#avM$m<ktjYJ|kXcp17!!uq0#D#d>{
zy<>*?4XQDG0rvO=(Wtoi5SQxr^C%HL_&^YSW9*pU;1h(>fp+$JABC!z-G2Czvj%Xk
z3jDqX7jrdGjhDWJ8Y9J|C+twHBBRx>x*wrzSZ2ltBLX|xM<+MX{7zLl8h*Pa&J;Eh
z>FD(#M|4J1W=9MRSVKEU5k*NOrJTOIKvcqR+7s+_=9cIul398uoxvc?b+j8dv1iOX
zYSuird0?|?TK0&iu|21YS^(znxqCQ%YUdw{!zU`z)Xp{f!(_D=E^EV~{N`+7Yd%*!
zaxY|yVUk+pu<HtKE^o-+eM3sH;G(|oyG^n;&X&~#2xS{SJ(n?NhamcoKjN7p&gcAc
zz+GSvxTsw0tS?b<4APK~uP;&O4a)9mf5g+gn<JlFco7VTlXl3`>zc-<l)QB0eSUMI
zh4w7?R6Ff`%JBL}e)v3pg*6|78?LvEF&*13L`=ztzEH1(7_XPOqk$EPuDmm`Pe3?c
z6t5R`ui=a=D$~P9Z$xs=u~s!1myGM#@!;BOWDms&qPQ`~?|XUxEOKT;@@57<)yf1?
zneegygRx%)3mj2oqFls#Rsm*oqPdB+0L~2EUw?itoFAKg%R+IU-^nr|gPWTeG~@KT
znCvUvEVTLRz<J56<pPD@e<n!_qp+&Q5c1Vk&q{6KWT1e#ZTr=;moa)~)T>jTSChxt
zg#NIFagCO2pxW#R`&P)=X6Y*JdCF{DhWqz?(6rfj8u#yKp|6uK?XS=PF=4QfOk9+c
z*m)17<cf^-OyxQ4D1s{pU*uQi%oWQcby=p7pv@GHV~~?r&P9fse2&PC#s*jsp9td&
z$4Eooqu0b3jzI*zr{XIx9D7cCZ|YiPIQE+P-nofivyE_Fh1a?m32W(U(bdDma12Y~
zeNDH1I969u4eqNlmgHy3T_qL)JjwW?lNcLg%;U&0QDN_zC8HU~2N?10X&uXNA>zKU
zEFdZu_8%<<4RG46@E@AI{l;MVgz-nYPrMMJ^Ij=MF1#>GN;I2pm_gWv>>KQ1eq*mv
z!lMy$VU2j?$1j8qeq&P%!q{gei(aZ?K914g8J$u1m1%!+IoYdb^*z1f(WpL4fs&v|
zd-abm=|lS8dZ8Qw&HIH{yj)F!tN6cec~z9fte{1hjdMdpW-BL|jq{>J&PTd|{~dPW
z`vz~9@p$Y)2*+rb@l4u6E6hU853CepwN~YRV^1!^Yb}Aaw{P4HnFW*r-bLChbVg}E
z+Bamo4nh|)G`H$?q<ej*yd0J~>87GNv_2U1?NcW+u_NHF%F~01h`UNDoQ;63UT-vO
zx{f|(zaEf4=ei&!s2WntB<dp}+2VBH)xZow!D83+kc#b%)@3e(`Q}Nz>A^2xI7knI
z`9|M$f<XxNaE(=%_{<M&0vFyx?aq!`x$rBOMBVjUtg3QSfR?^P?<f4xu@F~!1+x<3
zG35doKdtbRDSZ-swR|E+$Zu?m9tQOvNzlV&Li^QPhv%WOqJ3j|M!AD$i|zQTR9U7h
zR*X+phLa-E!$hHy@qbMA(Y3cT(0K5ItV1O1(iwEtOK8$;*lF<8;&pKidkv-6kAHk&
z)3~!?61<cR#lw$m!w#;5$Hq=;)ntImp@fDBJA~vp@dK&1J|9G>I(9_%8BR?ikz~gN
zEN(<AeiSZK<~A)1&V}m5{Eb%cxK+u%ZrrWv>}1Rfa<!5GSotxLecdcy6<`_~cvi?)
zO*cy&`{KpFc)~1AyMEIy^09dTj6ro9k^*$O>dm1t^Gx~ZnYpRozr;=Z11U4gN2A%g
zhha{)ZU(=`Xi`x9#|Qa&sl`7R?6mz_q>JfM=VwLpr7^vaCh32TNEd^W&-0c%MbGon
zIf)AHJY>ZR(L59Vdt<&j#}@lKFfTs+f`^nHp;#REz9lj0Ypt8`ZIRN*@m+*cFg@f3
z-5;!zLE@c|Fa7XZRNrI1zdLAuhEUv9mxVJ$`*>ViO-JgdML$iG21UxtzjvB-O22qB
z09o=gQ~SsdO^IJTNsP;<z2xc%-6~feH^Q`!tYZOrg3*BxS9pVPY?7|1!+|%))Wygz
zf(yj;CV+JDI);V(0TFWVG)}8_MN)?MtaR~>YD=kuq2iY01@=CNf5OdxDjT=@wsaAL
zb*n!xH+Qi{a1vm8rMdkgf}_WZs7I^~%g+h>+s#)io<NitgR^fZcO<;nrhZQWGZB}E
zIy8z#OvRCY`+oF19%=RN2%cf$wMVB99Z&g{*54^y-(6vU6G~i^qaPy#SvzB-b6qC9
z_pDjS??OpRUKnax_=}oM=d7&hlaLwx-bQoGms$Jd<S)TojS%?t?#>vc#HxI1huijO
z_mYxDJ-C+66JFkx<b0dI`}gOE2WZ1hA(tI1FE7VgCLDSjZD+?R9+qa!`_t4ub(QD)
zL**<)tKkQjP9cI)#|thiA%|)!Q@>nR;w06o_I}DoV#y8Sf8l~m)aT`TWx#4I%nfdG
zr~vaQxXD$ywjx2g&{Sx<D(H_jx?R`n^mFA;yt;~D1_nVow>5w~MC}&0k)7e!ZYx4a
ze*)vgFpDM%q%4TpzP4`1B|>vM8e*;#wP1ziJIk4yM(HcK%0=mNt8Ez?Qi3V4Wq4Q$
zX4ICU*4Jmx+AY^UfR9^3sGt02y*$hXP(Nu*dDVVa+l{QSJoQB2tMxKiZI5hWM-Z>e
zv>U!-MiBp%YFCqQJ5iudMQob?zK2kFVuZ*gp9#;;Q}N^*GgRV>NZQBtL7de7>}#fb
zD1u9I>QvP{rauMSBx1jpv%<aeJWgvJtx5{+NS7s%GDcWQ-|3Tr%uH?D9OF`nk^*9|
zdQEz8l1mCG)R!G<JDW`E!UCnPVR<@v6BgtyA`5ag;67)XQuG&yOCTpKkoOn-6-V|b
zzt2hv^nr_Eiyv^C?P;!y<LxgvG(>j66{32>p9MvT97_err!uk#nj$Bb$VshiJhD6G
z<x8W`%Bt$>1e;m-Lx(fL>J1Y(g88C5yiAN5&V*_8KlFG+0e{|dxOUUo4^_k{eHh^+
zj0r(_Sl<;(3{_@{1t6Vu&(oPoIkv}p@cT*!n^E2Dxl@`enFGw1mC%^bC_)j2?o7=R
zj-+ZfK3&9qec{Cq2NUa+*9JHHfG|>wUKH<s5gQMYZv!^|kr3nZlemU`|Anh=ww==J
zA~rP!-vSx<@2f;o#WHCX-#exGiesr7=bro_c)Ns4EKo{oEU_XUUsGLDCu8<59C+P1
z2k0A;Y{)*78K+_H%l7FL%At7I`gzxI@)3%3%268wn}<$jt2%Z+lwcVCW}*<o(R}A6
zk<~}&Y$j~Ekhs@;8crjb-xBZy==yj>Fw^5hEzJ^MxqY#)^$NA#7Zxd|dD_i&xBG9C
z#o2ftUTzMg^l*$2cE_&$!`p4*j!oVI@#=dE`qQa4JQCF>>0?`$y!++T-uPthH8xbg
zb5XW+!@B`Y5G{!#YBDnX+x~WufU=0<gdwsqqWP}aatimUBDEXF;fLGYCBtV!6-m3f
zO9~xB!ZqGcBE=Dklk%|hm!c>}M<QVIp%}q%fysel<hk|$ejS9-Yihzy?#u^#p7G7O
zOVGCAOo(&IWs@yhv#9NrA!RPENe=7b#*wO`_$DoVuJLfDW^^fu2)8qo#1@Wb{B%q0
z=nT56(3h@>A1E@sGg@@U(lxP^piLdp-UYFb_m|f`UPwG)Va7)CjvtJ*FZmIt5=;cx
zE+Muee|IA<ILVc|m?aw9WwI2yO5vobw4OHVLbsm7iiaq$8{X9JQ0N4>dqFQ?JEBZ&
z0W>EUdWTX1#f0-J8zvz@*VkvtZ8}y)>OAFm2_oMlxnnB}mSrtLsBHDMO)v1h&(E5D
zmPZ(Bcd?e6BL!t);{S2FD!<W;Kwj6`WctHW(D48==dJxoy;LZgdA(CVz=GJcsU2dt
z+^)j?y5F94-+ZZ~80Ty~5&F%#G4-rszJ_DuZp3EZpS8#p>tngDaiyNH^M;KlwYvJ(
z-qf><KQ-4bpV7)$yDW`NGqrv)q3kXE#D!C0&pD|Iq!j;IVA~sB);6lBjvpc=@e@w}
zb?}?EOMnOJ`_S&Ox{M%f*HN<xfskWlCR3=u<P-xMuSaGdi3On&2Q)*`)gCR@l2ZyX
zYH(C3QymDU-6vFH@nC?MKX;6b-V5~{>!<-T>iaVL>oawR&R|*TFoIrlY$EcNHqRom
z?ezv^hoPUbiUlOXh8?58%TeU#7jSLVsp`FZ$yI*gFKcT2!&(Cr*#1R*S;jp=@+POh
zpt1*p@;Hm#HfgVzM%;w$5Hp8mfK}0)1rD2e_563-+bb6H-`(H80}4Y!;<X+0O&w*K
zu@GJJ4nq(w{KX<&!!s)X<Ttk8^U*c17F4Jbd`t8CB{Ei@yBWSm*-+sW_{qiUt1(9(
ztXBGqjmNeH=|aUYGBW)jHxDO9fO?myA!pIM+z-lgLJ@Sxxwc+=2^yEOVmLUZ8jLkB
zHUTddSEb<BU(pSsjcTI2brt@h?o-TkE+*yBw(o^AM|*pS$md~SQ~0PMWz08#{D-%T
zuk1a<w>(noFy19>b6){Wi13|$<gvL<v%TS9-26N!HDs|!+y^q0z-4E?BsfvuxIgyB
zTXKTiO}*gxLBtp73Icj-c})ho2Rz@4_yUZpcY=+dZ=3KZ^yN!hQ}6{i?2v}Nl|gJB
zS}nx6Wn7;Jut>BhFL)BM-1Tved#h!l<WA=Ugxl%4-Q5LjV#r@%z3NI;4UlR6ms54}
z%Dk#C-UqrXgOLnh(=Pqnbs4fbq01Y-LlenoHN1)Zm0PfKpbaFA`homJ7JY~b<`r4A
zGA^nrmBGevARCzW8^d20?*~Q?$hn#jz+XQli;ksgTcz6AM!4TJDbSE~^s?L&lsNp0
z>U7JBy!?g>IU6xC&i1!@mjT`^@dxr>hG-ZCH)l@BzCN!!n#Knd(&iu)HQ(vrQV;=`
zq6SDW^UKvshYzQI(L70MR5XPr9y3C-(lkDUP{JzgOq_hY?!e-K3@3_zcqsa74tuuX
zCL~tpM^2P1=g{qXFjJgE-x($7KKnp7Sh>{ZQ1C%2eZ9A@{2qc*>k-pOheB)M;!?vx
zjdCer3xU>_ilh)pzbdeviez3-UqJ?@Mm@bOgIf`VV+5Of38D*b$X^vNDexQ+!RGhe
zl4(tj&%q6n(?ha6@1>SE(?g0t?jukzp+<oZu+7v&JVBye?V3~qlXA7|PQC1l`;ca3
zE+=@#UhQh^l^&b>TyX$UdszN7aCb%!Ag>Lws-`Q9tgxF3+fb+{BXk}T?oACNij^Gr
z{ow*^+1a$HQJ!6G)!j&Jv2vDc0B+&a+hSGi*Z9JMS+HN@`C)<lqBcb~aZE2D>s@nP
ziqePidas=R5g9PubNY$fwbfOb(i5ir9qQn8&Iq&MFlLBRHtkULB-|xMt4MN5W)vLF
z{vu(DEKd_(=GbuxtR<uW(IB411#?`3n1~w81r6dPx}1$I3ZdX1a3&jD==VX_O2ZSs
zu8rk0B6ckxpqNaJS79w7pfyb28o@kMH?^b<okVY9J8kS$F#N6S5(Lpg$ar;VIdNx_
zCvi1W`vCbc^cr+$VuW|~7sFj(yX9x1{A<NV4vbNX{>0@9ERAW&Ow+hx4sYF;^QLjp
zZeG8l2>zDa!`%i#OjbyL8ZU*qb}lS=DCvbGZ@-)^fjaI}=rcEE6kKh4CwOt34(EOl
zTX_%KZ^Bfq0V7aJ-Pcew4F}@Iqb@>%OY|d6<jOT!jZfB&0Y%nCU-Dj3$YWC%+{?}o
zL0XBhBzhn}%|O+hkR^C&D|?x8*Ib4KgzglV&Ji46(!wlV89h*Dcwmsh64R$^P<XZq
zn5UrdZwGb}$awiRYtLt-9~h%TD|iH!x%Baw0HTOF5`HYqDQ=oZTM2lMQr${W3Bmc5
z`++<8RTIM%8rl6~aN?uyayP)>NZbazSG_x}*dP<e`OHaJC-8i(cX!Oo9rae+o$i3o
zU1j<n*N}L<%`(5P`uH60&gAJec7dtDlJb&3cZb$wz|U=`Mj=qvRFZb#wv%=Z&h!bT
zaAC6@%%_5dH+4!$+S;m4DwkYVGH+8FldB<<CseHkyOisnc<>x|!-5gpThx@4{Sryr
zTX5`@mo?Kg182DnnRqyJco%exh}kisEK9<9;bhx-phUhPZY7a9yj6Wh-DvmLIxO^%
z3^(w6sn5v9_OgZdflZRH3ru@9$x)vPFrOr_)VK|hcvG?cQX01L3=yU9ezn;3lBy$F
zMy1*HGBP6h%5v};O)1K_$9^1i$7yAnZ}*Y6VGXdU*r9ux&p46~`3bq!D!595Z<D)?
z*LET4SxGxvxwyRVP~h!VIg*!$Ii9l(&d(&r0??n|7pxY+lKf&m9mTb$ePZ4{qgEmW
zSk!(6dQ`$LRTa9zeuVPLr?+idQV@X2KMa_GT<q=%X2O`Ex~QD>A{>C<L17g&@+$cF
z6!eXE@a8;N@xWsm@ln`EfC7b?Er@s#Q$|OCRu1FNO@Yuhdxkx#kwMAmC{PnjGovHx
ze18^<Zr$LKvUSS?+lZfPm$<eVgH$Iu8TU3Ag^(wQK{suZ50}kV#|$Q{j7GfbKW<MX
z69Q0AM~_HDXBVh;{{#s?y2hVwH(BQJ=_0d|JT73$hzrodV^w_oT)Bfd27@xnVmG=3
z0}K8>J_*o9n=I};m`}=Tc!B9UDZ3F(v7CGOasv>vFaSGmBGBOZXZ%-aZ&ut7-!ifD
z{^l7pSE@BZKOtEBgf?nquwpz~+X4R^Z7(EOYiC((U)Tm$za`vBe=fc?_k9ErX5|$W
zICxGd?2IyHt_QB;X>baOkS5}Y@*5wH-U-r(uCyX3yU>zPIZ#FQ&ZFGnVMbZcsYahg
z{5U``DnppC!MVoERwTtz_1;Xh(g4$cGtqMVvDQW@V-Mg;p7b3a#-z0kHVq3t0aP}?
zW4<VAOrE4qC}&s_3)OfnPV^ZQVkK@+9xs+Gww6AFLz?_~fh$hbs0jtjN^|w7gh`A~
zcnng`Vq2r*SXER%`Ym1`@p#ajZA8)1qFcu*GdxQ&2F?uVaV|2c;I@P)=5zPagu|vq
z+LU$3b!3uoAHK|2=?I9<!G9fP!*v#9VN?d480D@#z<7gr2=q(Q+F4<kYbp0Dp!DI$
zZc|hPM@(zc2a|Ry8>vIa!S0N$Ybcy9xop}cQ72=X(FBCKLTPI<84Y2e$uK_xsLeO3
z&XK1s@={lf;>)MJNR^*tN7U*Fpz`!6c9Oc4NPdYZ6@2hqm>z{`p1Be!P8TIE$1y@v
zLi6dDkn2%I_m){j-D7p9#Pr+EX-j*gy`TD9BH@5D3coCV0f|!N0cFaE(R4zPHe@x6
zH;GVn3lxveegs+%37WrT_T|X9Bt<ocU~ZiQ!HOW%BMAa&rO_Fy*-J=eyRE_OlVzfe
znU!?%W(;WQ;Hns-i5UmWW{3<6uDw#;;V!70dw>*VIo?>I8dgZL5<Yrl8{ZaG<Lnt@
z?(0KQ?Gpx{N^J78RrvEgBimmtGY$W3byFtn3l5ag1CNVCyv9DCM9VLy{JSXyBo8}<
zhzbN@MxA1yb`Fhi8D>T4SXZBwwJ6mxy*LdMc<kWLnLtbSduedzhH^`-X^BQfnRj7`
z?+pGJ6DHorI)+4JKt|_9sFlE->*+6P@qj6(zm%diJGs>#%iRC)W+7D(YEqOrUf#4U
zn(&B4hflZO>r--C4LZLhf830YZI$MqThFF$2c2KAK6d-Q$V>kSB{uYL4_!^oh@njd
zvp6G$g!}B*fGih<b<gv!0Zl22`O=Nh)ylq@jnB$RqF?H)da5!kBj?<=T*e_-N5khQ
z(KS)o&TGUdRXvBTNDvxpEqy23(wear!^8%!x=*eN8+|a6hHr9FI*Sa&L1UL1+33my
zy4sxk00(4Ocign>ht)x$Oy|X%L@ktX<!PR5@S5=8Ig^t}5DLsRPNIz!A)B|-!joZw
zHcV&gMFJiPVVk%8!js~7Xe3)ooaHit-6z<MUak)5l@@Wkv$NNT_wpgc2Pi!l({}e8
zNfq!}Rov%yf0?^HbnRAyTTge=qWOI3RNqOk{4&gSjtHW}r?6yAJHj|hT<+sG?98Z#
zeD44LWof+!-KfkcHz}5Qm34&1;64e<MgP#KS>&~-%1UikE@?Ieq4oOQw2R|?^>F{n
zorf*mpw=C&%Z=u<-JsQZKt`Ao>xk94bxv5CQhe`JyU|zZ@2dvqOiwOWd^3jU6a;Q1
z=s&mBqO#hEna!hI^t61L?09FoR*|Q5h?RIQ9bw!I{0b{M2rfRtkA?o|%36yMjj`fo
z*pJqY3LrjfmC|9zgqlwL#wHlknuYm++BL;cIc49Wt2&XKZnes}PSFW9%tpa%r|AbB
zyrj^uZ%Xs2Th;{pF`3tL@bYGd>T6!Y!d8@@g5Db5=m&Y(-aa88jiAtVf|swf&rufF
z_L3aF;y1=#X69z<dggC_W*$h=nf6k89`TdB*aI5^pc*SV2n)6Ps2S2wwcKTX=q`b^
z(|~CO5F@nJfCf$^4cj?H^G6krZXMGRj?dZ1y_3NxOI`S?lj=Xh*gq^8Ti}SO8Pgi$
ze9o-JN9QOSDHv2_6)vRWRSX><?H4un$3iytitB34XWjH3ZYxVgP}%Hr+?`2|vGV<b
z(OIY96r`@JDJ{{G6Er{e9TW#gv#AmK-djk4Q=Weo{Zi<sqFE6W9pv6uY(9K?F@rdP
zjfFnW@;^g9(x9tKgSumKhTBwM1HVf!Gkpzyu@%q%MkU;dXF%O`_Dq4<6~Tg(e{WL%
z0p@u?j)w6QzU&WvS#BY8;qRxQ+lv4JG%*!JF>)rv*$$;>F;`+8fmRmLb=r{cnUxZ1
z>r+}oRfd|kjDG{feRL1Fzcf;EPNlW`lZtXgs8)HJe-+FuFPopianrd72~Na0@0o|{
z{tAPpcsn|tDZQ0%60zQ~iF1QaGwz^|XpS~+5;1VGS#9x6e`w*#qsEi?-g-p4!<^lD
zyXyxt>?FNaOVP#P&yy)MujU2ggOtr9yH%+lr`4+9ueUM7{`Jd)LTS6w=O)G(qoftA
zqOr~EBV@}y&!Xo*NKBGX)xH#DMjR>_XFC<JTJ_C1!}yJqi@*Q9$Q3GB8U)>7V5A(w
zb-NSd7{3<;{<-}$=wZOG_qK_&C?&>9mPo@=+Kd&J2F;oqeEJ~pSH3c``k-f*|HUbc
zdHf`%0Z!>J{I`#^0ie3OF2p_hXqnrXJj*{RP4HQ?if81MD%T~jhuW1zo**a72C~S5
zDc<F7BH8G6bXt<;hdvg`4CG8K^401swde7V(ynG%A;!Vt<-lFizjsE!ebK*nM8Mt9
z|LeWbzjs2wT@Y{&1l$1u_dmeh&wt(f0CztB+g;E9cFzOc@c?%*z?}<luL9h00CyX}
zea64{7{L7maPI)zIRJMJz+C{~^#i{CzrH-+kpuoX;Ee-bIN){v>v01fHsD$R>rDf`
z^nZEMfFBKb(f^wd{a+sRzy9;T-ZS7e1Ag*<dB%WO4EVl)uM7CL|K-;LzAWI${_Dj8
zJ}lt9{_D8{9xLFj0)8sseFB~*;Aa9}=D)rr;935cUkP}X|K(8v{v_Z{{_9KrmnRAM
zk^lOTfWHWMe}M1zufGTQdVq%q_;>&Mc7SIG_;vr~mjNExzy28Dg#kX-zupz#69HZh
z;L8Ah%zybXfCmHkFaPDe{Fm?Y|LUy(p31*I%D)~8;GY1V#{a9I@vn~o_zZx*0Qd)h
zcK~<>fIk5E0RQ&;Ko1Y}?f>@aKz|PO(?FjL^ua)n3-qx--wO1pK!5pfZwd5}K<^0j
zi$G5Z^n*YT2=rV)Zw2&HKz{`EH$Yzl^e{l*0`x3EuL1NJKyUGHKLPX-K<@u<PX2H1
z4dl}Q=Fvdz`#(AF|IKgz%~$`oJQc`Ef!q_wAAy|l-#ih>3xQnlf6D=ZoDIm&fV>OH
zpMd-b$bo>o<iGNe|GzoNzc~kxYy6vM{F_?<ImN$u#DC=w|CKWUc>$0Q0DC{M-~YQ;
z1A8>EHv@Ytutx%W9<aXwdm6Bp0ecv*Ujci|zx&Gn>?!|u&-ix_0G``{=WXD57<j$~
No@;^US>So|{{UYoH%$Nl

literal 16160
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


From 69a0d5a50bf4ae47d8d25df18117903d9cc604a5 Mon Sep 17 00:00:00 2001
From: Jett <55758076+Jettford@users.noreply.github.com>
Date: Mon, 18 Jul 2022 10:01:43 +0100
Subject: [PATCH 3/3] Efficiency and naming changes.

---
 CMakeLists.txt              |  6 ++---
 dChatFilter/dChatFilter.cpp | 44 ++++++++++++++++++++-----------------
 dChatFilter/dChatFilter.h   |  8 +++----
 dNet/ClientPackets.cpp      | 21 +++++-------------
 dNet/WorldPackets.cpp       | 18 +++++++--------
 dNet/WorldPackets.h         |  2 +-
 6 files changed, 47 insertions(+), 52 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index caa61e4e..a9218bdf 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -15,10 +15,10 @@ foreach(variable ${variables})
 	# If the string contains a #, skip it
 	if(NOT "${variable}" MATCHES "#")
 	
-	# Split the variable into name and value
+		# Split the variable into name and value
 		string(REPLACE "=" ";" variable ${variable})
 
-	# Check that the length of the variable is 2 (name and value)
+		# Check that the length of the variable is 2 (name and value)
 		list(LENGTH variable length)
 		if(${length} EQUAL 2)
 
@@ -83,7 +83,7 @@ make_directory(${CMAKE_BINARY_DIR}/locale)
 # Create a /logs directory
 make_directory(${CMAKE_BINARY_DIR}/logs)
 
-# Copy ini files on first build
+# Copy resource files on first build
 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})
diff --git a/dChatFilter/dChatFilter.cpp b/dChatFilter/dChatFilter.cpp
index eb6674a4..6389623e 100644
--- a/dChatFilter/dChatFilter.cpp
+++ b/dChatFilter/dChatFilter.cpp
@@ -37,15 +37,15 @@ dChatFilter::dChatFilter(const std::string& filepath, bool dontGenerateDCF) {
 	while (res->next()) {
 		std::string line = res->getString(1).c_str();
 		std::transform(line.begin(), line.end(), line.begin(), ::tolower); //Transform to lowercase
-		m_YesYesWords.push_back(CalculateHash(line));
+		m_ApprovedWords.push_back(CalculateHash(line));
 	}
 	delete res;
 	delete stmt;
 }
 
 dChatFilter::~dChatFilter() {
-	m_YesYesWords.clear();
-	m_NoNoWords.clear();
+	m_ApprovedWords.clear();
+	m_DeniedWords.clear();
 }
 
 void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool whiteList) {
@@ -55,8 +55,8 @@ void dChatFilter::ReadWordlistPlaintext(const std::string& filepath, bool whiteL
 		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
-			if (whiteList) m_YesYesWords.push_back(CalculateHash(line));
-			else m_NoNoWords.push_back(CalculateHash(line));
+			if (whiteList) m_ApprovedWords.push_back(CalculateHash(line));
+			else m_DeniedWords.push_back(CalculateHash(line));
 		}
 	}
 }
@@ -74,14 +74,14 @@ bool dChatFilter::ReadWordlistDCF(const std::string& filepath, bool whiteList) {
 		if (hdr.formatVersion == formatVersion) {
 			size_t wordsToRead = 0;
 			BinaryIO::BinaryRead(file, wordsToRead);
-			if (whiteList) m_YesYesWords.reserve(wordsToRead);
-			else m_NoNoWords.reserve(wordsToRead);
+			if (whiteList) m_ApprovedWords.reserve(wordsToRead);
+			else m_DeniedWords.reserve(wordsToRead);
 
 			size_t word = 0;
 			for (size_t i = 0; i < wordsToRead; ++i) {
 				BinaryIO::BinaryRead(file, word);
-				if (whiteList) m_YesYesWords.push_back(word);
-				else m_NoNoWords.push_back(word);
+				if (whiteList) m_ApprovedWords.push_back(word);
+				else m_DeniedWords.push_back(word);
 			}
 
 			return true;
@@ -100,9 +100,9 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteLis
 	if (file) {
 		BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::header));
 		BinaryIO::BinaryWrite(file, uint32_t(dChatFilterDCF::formatVersion));
-		BinaryIO::BinaryWrite(file, size_t(whiteList ? m_YesYesWords.size() : m_NoNoWords.size()));
+		BinaryIO::BinaryWrite(file, size_t(whiteList ? m_ApprovedWords.size() : m_DeniedWords.size()));
 
-		for (size_t word : whiteList ? m_YesYesWords : m_NoNoWords) {
+		for (size_t word : whiteList ? m_ApprovedWords : m_DeniedWords) {
 			BinaryIO::BinaryWrite(file, word);
 		}
 
@@ -110,16 +110,18 @@ void dChatFilter::ExportWordlistToDCF(const std::string& filepath, bool whiteLis
 	}
 }
 
-std::vector<std::string> dChatFilter::IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList) {
+std::vector<std::pair<uint8_t, uint8_t>> 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 { "" };
+	if (!whiteList && m_DeniedWords.empty()) return { { 0, message.length() } };
 
 	std::stringstream sMessage(message);
 	std::string segment;
 	std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)");
 
-	std::vector<std::string> listOfBadSegments = std::vector<std::string>();
+	std::vector<std::pair<uint8_t, uint8_t>> listOfBadSegments = std::vector<std::pair<uint8_t, uint8_t>>();
+
+	uint32_t position = 0;
 
 	while (std::getline(sMessage, segment, ' ')) {
 		std::string originalSegment = segment;
@@ -130,18 +132,20 @@ std::vector<std::string> dChatFilter::IsSentenceOkay(const std::string& message,
 		size_t hash = CalculateHash(segment);
 
 		if (std::find(m_UserUnapprovedWordCache.begin(), m_UserUnapprovedWordCache.end(), hash) != m_UserUnapprovedWordCache.end() && whiteList) {
-			listOfBadSegments.push_back(originalSegment);
+			listOfBadSegments.emplace_back(position, originalSegment.length());
 		}
 
-		if (std::find(m_YesYesWords.begin(), m_YesYesWords.end(), hash) == m_YesYesWords.end() && whiteList) {
+		if (std::find(m_ApprovedWords.begin(), m_ApprovedWords.end(), hash) == m_ApprovedWords.end() && whiteList) {
 			m_UserUnapprovedWordCache.push_back(hash);
-			listOfBadSegments.push_back(originalSegment);
+			listOfBadSegments.emplace_back(position, originalSegment.length());
 		}
 		
-		if (std::find(m_NoNoWords.begin(), m_NoNoWords.end(), hash) != m_NoNoWords.end() && !whiteList) {
+		if (std::find(m_DeniedWords.begin(), m_DeniedWords.end(), hash) != m_DeniedWords.end() && !whiteList) {
 			m_UserUnapprovedWordCache.push_back(hash);
-			listOfBadSegments.push_back(originalSegment);
+			listOfBadSegments.emplace_back(position, originalSegment.length());
 		}
+
+		position += segment.length() + 1;
 	}
 
 	return listOfBadSegments;
@@ -153,4 +157,4 @@ size_t dChatFilter::CalculateHash(const std::string& word) {
 	size_t value = hash(word);
 
 	return value;
-}
\ No newline at end of file
+}
diff --git a/dChatFilter/dChatFilter.h b/dChatFilter/dChatFilter.h
index 62a47242..7e7dd859 100644
--- a/dChatFilter/dChatFilter.h
+++ b/dChatFilter/dChatFilter.h
@@ -23,14 +23,14 @@ public:
 	void ReadWordlistPlaintext(const std::string& filepath, bool whiteList);
 	bool ReadWordlistDCF(const std::string& filepath, bool whiteList);
 	void ExportWordlistToDCF(const std::string& filepath, bool whiteList);
-	std::vector<std::string> IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true);
+	std::vector<std::pair<uint8_t, uint8_t>> IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true);
 
 private:
 	bool m_DontGenerateDCF;
-	std::vector<size_t> m_NoNoWords;
-	std::vector<size_t> m_YesYesWords;
+	std::vector<size_t> m_DeniedWords;
+	std::vector<size_t> m_ApprovedWords;
 	std::vector<size_t> m_UserUnapprovedWordCache;
 
 	//Private functions:
 	size_t CalculateHash(const std::string& word);
-};
\ No newline at end of file
+};
diff --git a/dNet/ClientPackets.cpp b/dNet/ClientPackets.cpp
index abb08688..b9f715ad 100644
--- a/dNet/ClientPackets.cpp
+++ b/dNet/ClientPackets.cpp
@@ -288,7 +288,7 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa
 	}
 
 	if (!receiver.empty()) {
-		if (std::string(receiver.c_str(), 4) == "[GM]") {
+		if (std::string(receiver.c_str(), 4) == "[GM]") { // Shift the string forward if we are speaking to a GM as the client appends "[GM]" if they are
 			receiver = std::string(receiver.c_str() + 4, receiver.size() - 4);
 		}
 	}
@@ -315,6 +315,9 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa
 			if (res->next()) {
 				idOfReceiver = res->getInt("id");
 			}
+
+			delete stmt;
+			delete res;
 		}
 
 		if (user->GetIsBestFriendMap().find(receiver) == user->GetIsBestFriendMap().end() && idOfReceiver != LWOOBJID_EMPTY) {
@@ -344,26 +347,14 @@ void ClientPackets::HandleChatModerationRequest(const SystemAddress& sysAddr, Pa
 		}
 	}
 
-	std::unordered_map<char, char> unacceptedItems;
-	std::vector<std::string> segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel(), !(isBestFriend && chatLevel == 1));
+	std::vector<std::pair<uint8_t, uint8_t>> segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel(), !(isBestFriend && chatLevel == 1));
 
 	bool bAllClean = segments.empty();
 
-	if (!bAllClean) {
-		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()) {
 		bAllClean = false;
 	}
 
 	user->SetLastChatMessageApproved(bAllClean);
-	WorldPackets::SendChatModerationResponse(sysAddr, bAllClean, requestID, receiver, unacceptedItems);
+	WorldPackets::SendChatModerationResponse(sysAddr, bAllClean, requestID, receiver, segments);
 }
diff --git a/dNet/WorldPackets.cpp b/dNet/WorldPackets.cpp
index 94792c91..54c925d6 100644
--- a/dNet/WorldPackets.cpp
+++ b/dNet/WorldPackets.cpp
@@ -188,28 +188,28 @@ void WorldPackets::SendCreateCharacter(const SystemAddress& sysAddr, Entity* ent
     Game::logger->Log("WorldPackets", "Sent CreateCharacter for ID: %llu\n", entity->GetObjectID());
 }
 
-void WorldPackets::SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::unordered_map<char, char> unacceptedItems) {
-	CBITSTREAM
-	PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_CHAT_MODERATION_STRING);
+void WorldPackets::SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector<std::pair<uint8_t, uint8_t>> unacceptedItems) {
+    CBITSTREAM
+    PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_CHAT_MODERATION_STRING);
 
     bitStream.Write<uint8_t>(unacceptedItems.empty()); // Is sentence ok?
     bitStream.Write<uint16_t>(0x16); // Source ID, unknown
 
     bitStream.Write(static_cast<uint8_t>(requestID)); // request ID
-	bitStream.Write(static_cast<char>(0)); // chat mode
+    bitStream.Write(static_cast<char>(0)); // chat mode
 
     PacketUtils::WritePacketWString(receiver, 42, &bitStream); // receiver name
 
-	for (auto it : unacceptedItems) {
-		bitStream.Write<uint8_t>(it.first); // start index
-		bitStream.Write<uint8_t>(it.second); // length
-	}
+    for (auto it : unacceptedItems) {
+        bitStream.Write<uint8_t>(it.first); // start index
+        bitStream.Write<uint8_t>(it.second); // length
+    }
 
     for (int i = unacceptedItems.size(); 64 > i; i++) {
         bitStream.Write<uint16_t>(0);
     }
 
-	SEND_PACKET
+    SEND_PACKET
 }
 
 void WorldPackets::SendGMLevelChange(const SystemAddress& sysAddr, bool success, uint8_t highestLevel, uint8_t prevLevel, uint8_t newLevel) {
diff --git a/dNet/WorldPackets.h b/dNet/WorldPackets.h
index 3508d6f0..b88602a4 100644
--- a/dNet/WorldPackets.h
+++ b/dNet/WorldPackets.h
@@ -18,7 +18,7 @@ namespace WorldPackets {
 	void SendTransferToWorld(const SystemAddress& sysAddr, const std::string& serverIP, uint32_t serverPort, bool mythranShift);
 	void SendServerState(const SystemAddress& sysAddr);
     void SendCreateCharacter(const SystemAddress& sysAddr, Entity* entity, const std::string& xmlData, const std::u16string& username, int32_t gm);
-	void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::unordered_map<char, char> unacceptedItems);
+	void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector<std::pair<uint8_t, uint8_t>> unacceptedItems);
     void SendGMLevelChange(const SystemAddress& sysAddr, bool success, uint8_t highestLevel, uint8_t prevLevel, uint8_t newLevel);
 }