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 #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 0000000000000000000000000000000000000000..cdc64c2f5b245f4526974ab9cb1770fd8355b783 GIT binary patch literal 16160 zcmb8WQ*@ng+^rqkwyh>jnxsL4#)`&)c}=08B7$HbARxQY|MT@fpElkFH(YVzllBzTzeX_kifo?`EU;^FVlctFCR;Gt zVO*!NUR^kIRvaVO41_6+xzcPAR zI#^)SH38|@5H3U9xz%`P$Lw}BTEL+2xWxKVrYHRKYWIzS>kmVV^A?N%XRcP^;!i`I zvsPEa#fs35lm0ncQoploec61(zBGPt$yy_BB+&IC53NPMzg^zTd}SO4EHZ zjF{6jaIFXqQCJ|64;|3G@eqBj1NdTyy{SD+djC`g>uWglue-&yBe?MAbC!+;#Cx|C z6?W>qUjJwn=fWzHQOfFe8a)v%>=nQ(tw{)gc`Y#$9{h&0c}UOEzuD^BSoY~djX>DV zD8nRb{1WNw=SQfdnKKN9qy@+4$OXFl2o=(d@$PtXKaNervM(D%U{gxq2#$Lfptdo+ z&So+wFDpxKfGu`glWF4S#J6Y{5O0V|C|vzkd~FPgT|BC4WMjsFND_k-U|_y3po8i$ z!?qXu;~Jd;VBL4$g7+W_u$zi;krq7SKWQELFf<70!Ve+l3oaC-HMQ&|5L3$Cvh;5E z@e(zO6_zeT8Fn+?yDXPKG?9Cmg<5Y8sK1g$A?>tX-}Orw zeJ~#uwltVL+4b$BMHE7 z3OgaWj*=3#-BP&JuD6mm{S}eryCe?7_ABpFD?(YyUs-tS65;p<h8} zuIy3W84<*hXyDkGp0_!=1k93-znu#{=~uKeYTlB*ctLMn9ga^r6cGk6&S^vfT61um zxqF+)yUXzv299U>tx{sFemWS}ziN%iKF(mx$sN*53taJR<2a@|XCcdk3_ckbZ73nm zYg(f$j={+a_jM^4aob%b$JcA1^8yN1qD}3jH~8nEd4#sPCLxU`FAZn>;SZ5;O>M=y z9PB4;-=P}#_7Uj}rgRr$e2e1Ue72UnJKtzIEo@K&wsz@Y<>iCb1P;)?AN-ot_E4E1 zW(a4wB;arQX(0vv!sZs)8xO^rTb|IZtsJFf8xs9Vc*cjo_T<50elP@O;un zkvDeDI5>j?tSK7h7H-2_Ioc*Ns0;kd7#m>-7gYjIZD{0}b8_sRk0ZWDe@wh*HH&el zWzPtoX=ceI3hKVxI3EU0gsf){9kma#rh7D~ua7YS+%lE1GhTDFJ{>-~ccv#H12c$T z$s`7(XWEytr8}R7f_4zPZDrgQ0P~TX2KXIcWXM^PMWWw{({|%@r?&-q`P`+V0#TZ? z$E(^fiWpQ-sfjBQf_%;_UbBOuvr(H0Yx#4mpNg8>XC<0wz}x2EVL`?o0`ut7vyPtm zZb-_}S{#43gQGAp8JG}xXatNCwi55d9MZeSxm}K@XTpZ_!71mv&e5w%iexFp{!~P+ zmm}Xf;C;T2Q`MQqLFZQISKxhaWoFr6V{KvF*b!}4Sytbud*y&3K?Il|%-C;E$(Ia} zN@IE8R$!%Lb*|AEGvYWYo9ILz5)bOHi^HX z@bcTwWRU@AY?O$bRZ6JQNHYU*S^d-v~_Dcmn-jD#9`;VJ*QOSp__;Vr_HCO~nH z!LR>Q{lI&nYR(z#s1`0LoBRj?I{~)ZC1MCx!z!EVApg+HGiQ6{z6KWF<%X(OY-=w| z4cQP;8kcvT7q@~gd7PT$`;*ifC9}pQx8VWV=WSoBsKRbIuV4H}qUQbrODx71A8zjN zAdNb5#<~Xbt-9&OkjyK&m}EB?$ib@>JPcuzM6TF_x%5<5fyyKk2(4u)m{>xOUFG$2 z-ek?aS??!EK3BG77%oUb*G-m?>ScOUCBk>k)0s^ltV_xMvi~!5W{~YBbVG6uXvOVI@bTEk&V>e~vpmpu zy0m2x-N%E!EB<*<)hjeS)Q=i<6SvLWV9<;EX zjh%=<>CmRjOM19|N-`WAtmrgRUKQI~nCM#z`A3@2`x9ms=<`C(0i14`mLk)+YR^af zO)VpN3bA|uYwQ!}lwodc2Cq2PeVbLXYhSb&9u2TZ0#W4>$;!OKedNHz?omGD5Pf(} zgs_{l=rRZrFF@Kxe^(7YWqOzydyCAw%LC3)YIKBLpdABXlvA}$N`Hb`7TATNCpDPy z?1b80Eyk)R-}KIic?Vs&%e~`35QLFNqtDUZQX60Iry}dg589a?fViMl6V87MrL_J| z`Un#25p#C4m25TUne%>Iw&+knhhD_7dQfJnHm((hy7NxyaJndxy74(q+kX!@z@<1Y z_@o`D5UV8bwN|OhpVV)+Ed=OETJfT37Ox5rJv*yEkIZ@9{~%(|_>2AOVzR zG_C)})&KC7o8?)lb9cliDM)5RuM>G&;5+o|@XJPJ!w(Z0YGR+R@nn%h3@X>1&Mp_D zuE&vth?=#X=ExTrX2Y(0Zyz@z&YxT7`5mn20f-$!O480EbTwEdCriU~#a3+%lo&EH zPS`RNKPKzssp3u&u76ul1y({TwJ6z-3QzfeO$LM5a+|YYy zFE>xF(Y{~4nzJKkPzo5HO?~Kp#c+o~c4gp+rx~f*S}l%pSU&a>VzUy-e_kYPX~A5y z0w|@reW5&_Cjl_I7an8ZL(J}c28hd1+D*%# z(_j{Vxr;6`eKG8F1(PiII1D=0(K3UGt~IIqu?Ck}3-FfDckNyyq_YN$u6o#AfJiqD zinSdz32GiKErqp*Cj(#}oNBf;%{>yXrE)nlL*Qc72dY!so%%C1)dTTQlNJ8VqXnWJV zDM}@j7Pfm7#12B_YlC*60VYBW9zE5k)&MN( zQx^C0u!;pJyBC8Ckrg=&FiH$YaYkR|ydt8N^|}6wF2LQSNs$aIiN@eSi>r{LF<8ZX z2%*gGM<)c@q&0n+TvGFm{@*030%0)jOXKOQpI&pD{fRg9@UJ(W{;0l+10SqIVLzHY z3zJypd-M$~MjND~YYxFE_MisPTSWpXyDtTjyvk>14?$O7tu=j4Fzymc)N{qILT7#j z99@)Ye9xL#7&1ZYD{?K?>YkKi;&XU>`B3@Nzm6+~$>MJPr~4dWe_E#LJ;-GjO8Stc z=%}qQKr?4XnqT<)e_0u>?TR~gsWQy(a?mt52V-P-1?!E+Bmg?Ok@OUa^Q!|C%onrz zK0R~;5G8iIK-+F@lroeIyki;dxDOY??4egW7NE+9CIb(xS)?uMueVV`7#Dz7$NO@R z)ZnNGer>M?Z2{N-Ctqp&c{<)1xDKlYHu_BL&bep36}fT01H5R1-(GeaVgQJsxX2~R z`qIl6SWeg-0lA$;h!}(gPJ#rmeDSMC7MczB6h-9zwC$P8^_1R<_gAZgGK)p!0G~$0 zzeW3EHAz=yI@dP_B-r!AGpx{)oPx zi4(71R3$@$dHA7Tbjc-ul|M3mP+@frrq$11!{mGpn~Xz2n~yj+#R*B-NwdS@kUNQ{MO5j*e$jz zm|JK{NBQ+Y1|u&2W;|@ABTVPbxx>d+k{j#W&Qc*PDt8+?gMC|dIXl53iPlMqakMT_ zkFI4I-$L^#NW1eiNG<7Vg;QNmU)lvPv-F4Jfx;8dbK?3 zx-}mlfb}N=<@@7mp_ErhOy zUx>4<1`W$>Cyg-3*L|o2o8v-;Ef$!YNTq^v1*W!fNkZD^4+i^$JX7+Ebd4VIW9(kL zd{COmOhS6FC2&5&FkI0f`8qUAgUu}p2R=bDb}Sf9H+VtQ%~Dlp!kuy_LHz0*b30E4 z5^t`YmAaCm%DyaBB@$2Y=rx-U6KcZS7Fk{sQkiFX2F9)!4ZpmO%k|k1(g={sa5DLP zkH{KgJ5_>ZP%7r z?;sMT4O5&n>U~mM6Frxzj;vAuAq!P(qaR+uvi(5Ow&6A4BheQ$IFBEXj5dRpOYy=| zDdBFfzfRx_?a==ctW~78c%l_7pGPbT1p_UTm8Anv#|I-(H|hiupuCps&x>DQ*bail z%fOCThlH0|zp@6hV5%2?Y9sPK>r^dQDDpfgJ^&2l8T|dEe@+9isdE*-Sb#o0sS*JPgWcn2UG+Xhdr#?UwJ2KuKZ z(98z6pJE*4hJ(_{caA)$hZ$m*eT@_#sV3$_cAsit>*|d6!4)4&fpPpoUjAIEwSSAB ziKG@?sWHz8gr-C|5S2b>5Yibal1959e>-L3Y*h^1v+HxZbbwv(<$XmLy01KLVtqwl zCo_uyft^N^p7D>pKGYjnO#S}G4{twH64KJ^BRS@jN}E-4r9O$4|9@Ni13B zT%2}VW-w3|`WVe1G0fy_O?vI}h2Uy=h6JBJ&3F5r&JS~?W2f1%I3)m0X{mFvn7iTT zXyX()7VH}2yHbdu$cBY@N^2G<>;(n2eAaa|Kawptb!mo9yFu>ZyEmyqD?F|g zCZwUIoH>)!!%pwzy%8kK%+~AUW--4FQ(_}Lx)5cfjHjE2<^(=*lVC9iA1aQmKiKG9 zg#?xB*f+Q*v3=5@k(Vjj4X5L+~gb)Wz}lDj^71(+yH9^nGsbw|H=bwG=x>Si3oKw z{N*!SM0Pq25CYRypIvHG`nz`92L^g#qh+%HyINs4GC&6LbIU7wenx+}0HKc8Bra>DF-2ZND0nW)p>mwXWSrIvH0-1+KS!`MSzC@Ibb! zuo1S>bNp4Dm&C|{T88GG&1CiuSpdK>yT&{$?1lc&Lu_Qx+kW?erY46+GsjbaQ|~V_ zKgJKN0`Gs&{f5fv1Zd@e4NanTIu9@cn>{KoPx&68%JWULfTnq80XrTIQP@2`#~X-s z?vqDsJ$pF)ivmqOK#T5I`l*_b@rr{#V(Bl_o&ebp&!u=Uay25cuxvC;3WSD=+9>GequgQ_HOm;JK{U)EfM5r zfz4VBZ48V@E`O|k5F+;uhoZ;O6_-DD7RRz8Q~}$XE!Q+YP=6U5Woj001;%zAt}uX(6>~^CEh}sCf(h!x9_LLpucjNHI zR9(piT{at!6CmEF*Z?XH{7yWP#xU6+@JOSk+#EumA$v-3=XY-Zny@wPOzP6$)1lM} zKF}tL2)oHvfGBtXVa<|RMuNf+Em1-!#yI4aLXUi>swl80vYK1>&NI*FBrjTrwudiv zD1c*$nWHk51uG-vR+@QoYnvNYBq}WaVA${}dzBUmUP#`In0y2;f4{@5G|A1IINsSa zaP%#cf>3$|y&!fcRFA8b;&MvkkE_pU!MO2RiRVeq`A)j?mlqlDioM5<=z@-&_o&UY zxgqu%=Fix*W)h8n(xZUD{vnFT!PCOU_@S^(ek@ghP$`tFHLDHVLPt|4L!pJt(B|%n zpXp$kZX(Nu*$}v|JPN;sr1X=R0iqGHDYkuJ8mPdiz|a-QC#PT_a~;872HDcRC=Z4q ztoRFK4ubMMy)Is@cO%joN~!GaGhOP0io5-3#(@JtKHRj-QH1YZgWMY?{oSLhx!~8- z!ku@-ZdY^nt}Yv4{%HRd$5I?vSp_Lb`X!HgpObrfQ;^$%nFrTTl@75?121GQ z$0Oc-zE&TFn{d5A=#5Btis;WXQMJl#LGA0T`&x?dE{ti|qVM`QgnjUgwn?QcQAM{1zpMDw3Hba}IzgFL$j9i$4yA zIc=5ok&CVhTB6Kzn+D+M3}$^=WVWqyW4Vj zyEt?@+3?WVuDz0rx`Ua6Uq^Td-YbT6bWj=nD?|bsP2FYPM1>%`1p&oWl}8p%fuo`r zg|lWX>em2MvK=+oS9si)HqAhbK;B_x^Mrp~>38qrseK<+;X{6-T;On5;w1T90Sd z692*#x6F#U>ruB3how%9L>JqkB@tl5;`=iNz^|qLxk@OHA>r4L= z!@-4I9MX?AGseXLjeFwQ-mr;b-pn&Kw$U`wM|OUS)Mg|31Spq^Ama!$Ne->bRo*As zEIkWHjQGcOzb;PWzuQb7v@?b&Mj_u_N}Q{WED@?e_I^yN_fvUYQiL|s{egpFe?T{d z5;1a)Nt@@~Y*-sXZou35Wr(L_>GI1 zzhgeS8S<pwdd%ooRlkBXmG*b~&7kCEnxU8uL6{Va>U2t=2J?uDJ6(fwt=j zyC(2_(ai+^kXlz*Jtxm4iG{mbve`j&r;xpRX@sXK1-W*_Zlug!^`PJp5@FU{7tH2(^RB zXd~ixD}B8uZFlH+!Yq2*Q$?D5;&Gjd;jMS5mmpUXfhq>RCzLMC#W7@@vVHv5eZm|U z)l7<6O8>8WR&I;*x8&2|V4PnGIk#fB>y!>ELoS~ep7CdhJt734Jh@s5l{&e zhdb+E;Ml5(WL@HEMRAQDwam8!cyXXk9F*>%x6xRy!CllvX-NI;mNFS>v+^VYV0+Xg zoS+4|Wa^q`*&gcwPS%y#3B6Uk1I$;fgRa9ISogfty=sI8;f~_54vqf0QzTAhV}%)043wHH}R+7>)&ue3(boV;BzVIK`;xmRMb}c!(mReAIA`T7`{dTPG-$>gWmN zl;Dfu%ls{jH%6ky=&;iP9DSMCcY{y1y)C0)BLe&*FxNGrL}Ks)+k zL~AAuD(B~#Ai~p^K5CuvvLcV|GfviQ%yZt_+I(E{!n7$S?YlC{NRTd$(3n$s^O=b7 zaf+!Mg$5JLsOnS^IB>tV1u|CJQ>F#lktW@5E-wUEg^7QYt6qpt(7euya~0K~3Z~x% zFUd4>fUF~=7Ipk(a)pGAc*<5!Q;fNA;_?LY#bD>9WYb{(^f+0f#WV9=;gpHcGYxcg)bu=D>EoPWl-_73HY-sZVbxwa+xs8%Bvi|#mmdy z;XUVZe-Uy*>%;saJJO9bua^zLti$offgRPJ})7gy>Ur`uftK+mxL*Z{t zmXmOY|D|kw^EY}ji-DQgfZ4Op&$tlozDy|pQtR4<-)*2H6&goh%idX@ZRgM3S6;oY za7YH?ixLn^j`Lr=37P_q6BnNuhlCZcVq$ACqmhr|M=S1w`{t%69^H&3P!UYhD)O6m zjup8}^G&_4&ru%MKj1MnPz7`O9gI^4A+sMlX zVQNnxG`%m^%cXfcO%$a8@!?|b=Qk{@S(x4ripE^h{06@=MU^QrY--~Ws+2`t0|bQK zbO+Q9Ki-jb6wje>wd!v(+F|R`U>7yhYaW&-ym#nbVx;sS*#YqzSKq{&6YaAa2}s_e zL_cuKI}I}T+NoA@f^cc_wbjLHXYSA%bdP6Ts;^Nl)U(Y$L4$P*a(Xt!AEQVkRlbiR z5)gmL+@o`{f@_-5N2Jeg1@`7npWcq5;8ov4+f~;zlqhSwC*|I1qG-Gug}rx4g0w91 z5DM}NDnaTxPK4)`m(T4%5(P~R%ieI(l(BC`$GZ2O;f?r0Fq!&0L>_k9eQ6`vmQX@C zGvi6oH&xD-WXh-Pb+(yU2Iv-HaQ>!0bB39qey1IHLpR&%D6AQBH>m8aH@YdZngWnd zkc*!*eSn(RVvCrK^mcBq=9^c_#9IcyrX;mOZ?-z;Gz&$UMrb<*p~MLFl^$PzVk{wR z#LJD%4>uh{Fjy)>K29)Qv}(BOm})p&e9248zwE)RXxkk|^m9bfmFsRh-|UL9$!*73 z_G|=PEMc4@h2L(xwod^pAVBCEZ( z`S$SsptLfl((oG^u9gfVE%9rKJmosi7i4R5f~~_C_~iY#kYlQjhzO{B!(-TVGF)Z8y&!fE$GJc zpX@A@3erKd3r&;8NxL9*6Vh*=fAo{i%b3|on30O=AviqCvn%8;fE;bjn4ii^^=53O46^5T_%rR3-$3A<=>a1ZVQ&V0f09!47|Iw ztbd1Ebxr)kqfSh^E-`i9*+fp&Loaice!QM6;G5K3dOR6uz(0zZxTn?PvO)!{?1Axa zqJ$s5x`j##JC3j&Z4!uq`_2Zcwm=AJpmiRyw+_)iqjs6LxaE9fh?T6wX-&qPn6_2n zip7$kgE31v;MW`sKx-9p*a|kvIlymBOV|@Y61Q3B?men1XP!0Mv}D92C?nq5-mQ_6 z4EqlEvO0`{af|x)$Pu^3(oRJaT z&W_q4y{MCgn!(P4UcT$Bf8&^BR!7FW7)+!bqtb6v{eF5+BnoNfAYE9v_HMK~Ykd9g zc#`9&0eFx{=`_0Y2til6zMNSJayv>%>Ou02mn?eG*IM7H0`J>5GxO7%UN-fxubs$> zh6#x6jTUOPnynJt!s{B-4OCeNN@5T`7n1&*?{)EmT89i#xI9!1q*@p;#0^p+5xWacP$N7of_4u)}cy5mlh8C2&DcSM}aR@@>?F z<1jw@q@*_19bU$w%k-=b&HYqq z?liigm5kxi!c>TE;_;{cVhvn0St=RGtTca*>xRAgPBg6i@c!<;`SqkSa)41O2m2Zg zE?hB+_Ile2{N{T#tt0mLJqGPzvm(|*iDXxXK?g~~$$ANid;vMQ*o25Q040J<-aQ`E zKI}U~D|O}h`y4K=cPztA#^KH8P~JO0BB#!OS2vThc8zGveHA79$J^idY;?cxtK(O#|6A%NHy0>9v-W zp!HY&p=q}C>Xl91dMGqkIK?T$Rq0e#TV$j`0k#HCfK|x9-3r#)^+iB zG@L)hU7d}r7IfEBudJld&0uobbX!^DRr!NDSB`Z(FZA@B)dm>0d$KPpK6}a4+R^j- z8y^Y#MH9=YB!PCPcbDuJ$n;Gv$YWw&;c!^XX)F0PVXwzA7TithaF-LEj_s&YKw zN|oD?e;!G8jyiF{$G5p>_6u(`MRwJW7zBI@&4I5bYDnJ!s%2&DaRN76eKElKUWd}8 zFa(NkaP5iMO*vx9xHRoNp(iv9S56oo5@wa)q=**#lj;jE?x1YcH<ZNfK zu<>6Ena+UMo|7PCM8O-JxiDQfQN92#uaT#=tjNIK++wbezUz zve#~Q@Fi|T?9gB*j0L}`Hv4?QW-Q6*;-s#Dy38yP33Pz)qRw!z{^DVVW9J!zN>yR} zgpePB_h8d%i1&Q54)-s$)p1_@eMFVw3vLw1GH#<{s~xG2`dX6Ik48A!kE}D^^bq{Z z-hbv#tB(e>n~pq&vnIF^HZ4tp(5qmF(;UETr|l31C`pZ`8yQ|OWls9J-En1+3$EKU zYRft0zfpRux;O5eu!E2-5YTrpF?|VXzbYyiL>TOwoGYal!nCLAHu(aI;Ga;H6#|Ob zuOGp6m59+YCtKPg@+^18kIY2RBII1#!u?`O)BK>@YC3otf_F(T*Hntag4WF$coHCC zQ4adm@Ho<&6Rl!RlpYLCcE|eK-R!o*6qnwta*U&`<9R&h8hzT=vFT7y>gvhxjkjy4 zrV)q{N`H@Is@y(WO>RE0rFK>(YMEY%8lYpP!8ui(>IwGni`k8$?NrjrRwqyGa(6kd zCX7JCJdzOr9KIQB;wqZ9dA;VJf)k|kM$bAnVLAKu=8lZ#JS#GiNlXO>$RDcHu6x$ytCg?rNQM9NcXAZXj1494ttXZ~xK0XQH>KlcHubZ|@qz^xtQr@DEIanYvO^Eigfw;fM-gpe)yTa2dS<7lbyF1BX<9 zSgHAuRLD}%pomY2!9InrOZaxuVt}XGBBjkgm8FC598&0MC%Q@2n7>^*`n$>Yy_*Q; zt#bdMqD;B^xcFU`uOGkJY&r8R?j-{cKNUB*>q1s(`v*ZQeXBR-D1hyA&u{a~-9Coe zZW9Z1q5D4Qno&Pr=gLgegFZtVCkb}S;r_LOtJ!WRG;hZApTJnR?n|?6#Xxg~HGKS6 z*aJAJ6VnOHv1z>0MXHc&awP_)uDkzglfPx>I<&jKUNEp!r6w+qX$3PeG?lJt zlBi90ySIYw1R z%9+v`-gSDDZzsEh!-u1&>zuw=CJYUM4@E}fM%DbX@b~iM_w#aS=Z?m@2Y*33 z8H+mmIm*>zp&J`a#&v|M%FYP}I~!7>A``f}aU%)qwrMopPoldeC;guA!k?&?5IJ=H zdo3{f7WQ8xO-`J9!AcTlFP*SA-hZ03<3o%{Cs^mfBSwkzw32o6Thf$HQ!`j~F!Ae| zchM)QvFo?pAm!(hv$r42@vT4Atcg;G;j%Wg$%7wOdLYzxjew`CtJO0jr+jEe9+W?l z&PSP_+GlC#R&J|S`W^M|f2}>5)LrYDBv9e7XE|6<0q?h@p+DpgdS(Hb`|}Q^-npd% zwYSh;PBj5WrlB5so^Of_)fH8MMCBFn9VHBkQ6BVTc0^p_Q`6mMuKbi0bnG-aa~RU7 zwC;{RJfn}c`Uck7^W(<4grh}vEYOw{mXN33D3S@UnVq!g}{oZBM9xCs$5 z17n>x8hAmAXejYau04ekNVylBY0W636@Xm|8oD7pP=a(Sxl3OcZz7UYop+9f$EWY+ z!qqGG-lHK$p~KO8w-2ZQrQB3NW9a_i=t~iri;}S}CZQJU=oW@=xw9%;wEMn#c}+(Z zmWV;G)$zpKNiL5vl`BUaMJOZOC#6Lp-Qjt^B#hO0(7skSGjix`Jj z?|iS^gpIfF6m$=%|vsK*1VQ@pZ& z#5E&|)%uWxH9du3^}EOY5h1|y)=kg#Mc2X&+bIdu_{puu#-(LXYwx=qCBcWvWGq_U zZ@9NX9|Fh5u4w3%l%0>%F39gUHsg{Z1{`7+B}vw*ww$~(v8K=<&LM6%d#U211=fGE zS<+|=qsJ-kCvvI+=bb2gb2*T1gg&ynjG`S|(ZQ1`&cdI}woV96^ye26ik= zy-o@TGDIv;hrL?X0%j`BG#|6B_+|0MC;8-MVw8DhHNc(bhh(urdm}m3H7vGuchFJg zq|lQ`AbE@cTF3yGl4Wn@Pl*d=+%*uW0}&Py|Dkm?Va;P`t>Y^!iikzr=lJyP=NS2W zXoxLNgc@P$7gS+&?9eO4^&tM(rW3x`dKvkwrYrBQ^k-K!lsK3G`dI`76@I79ZEumW z8{~?DRBe}jgul({Lkoq|gz&1UwwLxWs)vxK0r=;&ujw$CG9yT}hI0awBuVNYw2<4! zi_B=^IKlx!9e9EEM7v$1H(#^gMe+=Gaty_QUDCfhBVb?j?~VxA4gJ6Fh5p?M0lOez z4+QLhfc+1!`}wcE53uw3-*!F!+nxv5@c_FRVCMqtRe&7_u-gFk8UOAvfc*rpcK~({ zz^(z<1pr<@;OqbE%L5)c;Ew~|IN*f?UiZHqH|PKLumR8dUvC=lrT@#52K;Eii~iqy z=>PJd|Mj2$^_~H*8Ss<;%QFVNV!-zWd|kk|{V%^3@MQr{_Fpd+@L>V(^>K2l%(& z2YPs*Z~wPX2l{iMp9cD5pbrLmT%eBy`c|M%1^UZ>drP2)1bRoHUj%wWpdSQ!K%nOW zdMlup0{SDMzXAFhpoana7NBPVdJUk*0D6mm`w5_z0CN9-bMk+4Zy=ZcH;)E#Um)iN z^4ov&)&DI|1#(g#_XP4sAZPqHPXzKpAQ$}KazG$w1M)K~a|K=J0<`zIs@oygSUpd5o 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 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 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(); //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 GetIsBestFriendMap() { return m_IsBestFriendMap; } + void SetIsBestFriendMap(std::unordered_map mapToSet) { m_IsBestFriendMap = mapToSet; } + bool GetIsMuted() const; time_t GetMuteExpire() const; @@ -63,6 +66,8 @@ private: std::vector m_Characters; LWOOBJID m_LoggedInCharID; + std::unordered_map 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(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(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 unacceptedItems; - std::vector segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel()); + std::vector 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 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 literal 16160 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 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 dChatFilter::IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList) { +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 { "" }; + if (!whiteList && m_DeniedWords.empty()) return { { 0, message.length() } }; std::stringstream sMessage(message); std::string segment; std::regex reg("(!*|\\?*|\\;*|\\.*|\\,*)"); - std::vector listOfBadSegments = std::vector(); + std::vector> listOfBadSegments = std::vector>(); + + uint32_t position = 0; while (std::getline(sMessage, segment, ' ')) { std::string originalSegment = segment; @@ -130,18 +132,20 @@ std::vector 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 IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true); + std::vector> IsSentenceOkay(const std::string& message, int gmLevel, bool whiteList = true); private: bool m_DontGenerateDCF; - std::vector m_NoNoWords; - std::vector m_YesYesWords; + std::vector m_DeniedWords; + std::vector m_ApprovedWords; std::vector 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 unacceptedItems; - std::vector segments = Game::chatFilter->IsSentenceOkay(message, entity->GetGMLevel(), !(isBestFriend && chatLevel == 1)); + std::vector> 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 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> unacceptedItems) { + CBITSTREAM + PacketUtils::WriteHeader(bitStream, CLIENT, MSG_CLIENT_CHAT_MODERATION_STRING); bitStream.Write(unacceptedItems.empty()); // Is sentence ok? bitStream.Write(0x16); // Source ID, unknown bitStream.Write(static_cast(requestID)); // request ID - bitStream.Write(static_cast(0)); // chat mode + 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 (auto it : unacceptedItems) { + bitStream.Write(it.first); // start index + bitStream.Write(it.second); // length + } for (int i = unacceptedItems.size(); 64 > i; i++) { bitStream.Write(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 unacceptedItems); + void SendChatModerationResponse(const SystemAddress& sysAddr, bool requestAccepted, uint32_t requestID, const std::string& receiver, std::vector> unacceptedItems); void SendGMLevelChange(const SystemAddress& sysAddr, bool success, uint8_t highestLevel, uint8_t prevLevel, uint8_t newLevel); }