Decompile fn_22016 (CGameMode::ReceiveClanChat)

Batch #9023 by youko: 0 byte-identical, 1 confirmed by peer review.

- fn_22016 CGameMode::ReceiveClanChat: logic confirmed by peer review

Peer-reviewed-by: ragnarok-clanker, pedrao, dori
Accepted without human review: peers confirmed the logic ("same" ahead by at least 3, with 70% of the votes).
This commit is contained in:
ragnarok-clanker 2026-10-31 23:45:34 +00:00
commit afaf9ab6db
4 changed files with 45 additions and 12 deletions

View file

@ -360,6 +360,7 @@ public:
void ReceiveGameOptions(int count, const GameOptionEntry* entries);
void ReceiveActorClanName(const unsigned char* packet);
void ReceiveClanMemberCount(const unsigned char* packet);
void ReceiveClanChat(const unsigned char* packet);
void ReceiveGuildEmblemVersion(std::uint32_t aid, int guildId, int emblemVersion);
int RejectRequest(int, int, int, int, int);
void EnsureWindow259(const unsigned char* packet);

View file

@ -16,6 +16,7 @@
#include "Contents/Effects/Effect.h"
#include "Contents/Effects/EffectLists.h"
#include "Contents/Effects/MonsterSkillEffects.h"
#include "Contents/ForbiddenWords.h"
#include "Contents/Ground/TerrainHeight.h"
#include "Contents/GuildMgr.h"
#include "Contents/Items/ItemDescription.h"
@ -2882,3 +2883,26 @@ void CGameMode::ReceivePendingItem1718List(const unsigned char* packet) {
window->SendMsg(nullptr, 0x24, 1, 0, 0, 0);
window->SendMsg(nullptr, 0x17, 0, 0, 0, 0);
}
// OFFSET: 0x00D66860
// FUNCTION: fn_22016
void CGameMode::ReceiveClanChat(const unsigned char* packet) {
const int messageLength = *reinterpret_cast<const std::int16_t*>(packet + 2) - 28;
if (messageLength < 0 || messageLength > 220) {
return;
}
char message[220];
std::memset(message, 0, sizeof(message));
strcpy_s(message, sizeof(message), reinterpret_cast<const char*>(packet + 28));
if (g_forbiddenWords.Contains(message) == true) {
std::string masked;
g_forbiddenWords.Mask(message, &masked);
}
if (!g_serviceOptions.Get(148)) {
g_windowMgr.SendNotice(1, reinterpret_cast<std::intptr_t>(message), 0xB4B4FF, 21, 0, 0);
}
}

View file

@ -1,5 +1,6 @@
#include "Contents/ForbiddenWords.h"
#include "File/CommonObject.h"
#include "Network/TextEncoding.h"
#include "Utility/StackAlloc.h"
#include <atlconv.h>
@ -10,6 +11,9 @@
#include <mbstring.h>
#include <windows.h>
// GLOBAL: gl_24641
ForbiddenWords g_forbiddenWords;
namespace {
// OFFSET: 0x00AE7060
// FUNCTION: fn_18065
@ -30,7 +34,8 @@ bool ForbiddenWords::Load(const char* name, CCommonObject& storage, ResourceByte
std::vector<char*> tokens;
const bool loaded = storage.LoadTokens(tokens, name, 1, file);
for (char* token : tokens) {
for (std::vector<char*>::iterator it = tokens.begin(); it != tokens.end(); ++it) {
char* token = *it;
char* first = token;
while (*first == ' ' || *first == '\t') {
@ -38,7 +43,7 @@ bool ForbiddenWords::Load(const char* name, CCommonObject& storage, ResourceByte
}
if (*first != '\0') {
this->words.push_back(Lowercase(token, this->codePage));
this->words.push_back(Lowercase(token, static_cast<unsigned short>(g_textCodePage)));
}
}
@ -56,7 +61,7 @@ bool ForbiddenWords::Allows(const char* text) const {
}
std::strncpy(storage, text, size);
const char* current = Lowercase(storage, this->codePage);
const char* current = Lowercase(storage, static_cast<unsigned short>(g_textCodePage));
const char* end = current + std::strlen(current);
while (current < end) {
@ -67,7 +72,7 @@ bool ForbiddenWords::Allows(const char* text) const {
const auto skip = reinterpret_cast<const char*>(close) + 8 - reinterpret_cast<const char*>(open);
for (std::ptrdiff_t index = 0; index < skip; ++index) {
current = CharNextExA(this->codePage, current, 0);
current = CharNextExA(static_cast<WORD>(g_textCodePage), current, 0);
}
}
@ -78,7 +83,7 @@ bool ForbiddenWords::Allows(const char* text) const {
}
}
current = CharNextExA(this->codePage, current, 0);
current = CharNextExA(static_cast<WORD>(g_textCodePage), current, 0);
}
_freea(storage);
@ -117,7 +122,7 @@ void ForbiddenWords::Mask(char* text, std::string* value) const {
}
strcpy_s(scanBuffer, sizeof(scanBuffer), text);
char* current = Lowercase(scanBuffer, this->codePage);
char* current = Lowercase(scanBuffer, static_cast<unsigned short>(g_textCodePage));
while (std::strlen(current) != 0) {
const auto* open = _mbsstr(reinterpret_cast<const unsigned char*>(current), reinterpret_cast<const unsigned char*>("<iteml>"));
@ -129,7 +134,7 @@ void ForbiddenWords::Mask(char* text, std::string* value) const {
char* next = current;
for (std::ptrdiff_t index = 0; index < skip; ++index) {
next = CharNextExA(this->codePage, current, 0);
next = CharNextExA(static_cast<WORD>(g_textCodePage), current, 0);
}
current = next;
@ -143,11 +148,11 @@ void ForbiddenWords::Mask(char* text, std::string* value) const {
}
std::strcpy(matchedWord, word);
Lowercase(matchedWord, this->codePage);
Lowercase(matchedWord, static_cast<unsigned short>(g_textCodePage));
advance = 0;
const char* end = matchedWord + std::strlen(matchedWord);
for (const char* character = matchedWord; character < end; character = CharNextExA(this->codePage, character, 0)) {
for (const char* character = matchedWord; character < end; character = CharNextExA(static_cast<WORD>(g_textCodePage), character, 0)) {
++advance;
}
@ -172,7 +177,7 @@ void ForbiddenWords::Mask(char* text, std::string* value) const {
}
for (int index = 0; index < advance; ++index) {
current = CharNextExA(this->codePage, current, 0);
current = CharNextExA(static_cast<WORD>(g_textCodePage), current, 0);
}
}

View file

@ -8,8 +8,7 @@ class CCommonObject;
class ResourceBytes;
struct ForbiddenWords {
std::vector<const char*>& words;
const unsigned short& codePage;
/* 0000 */ std::vector<const char*> words;
bool Load(const char* name, CCommonObject& storage, ResourceBytes& file);
bool Allows(const char* text) const;
@ -17,4 +16,8 @@ struct ForbiddenWords {
void Mask(char* text, std::string* value = nullptr) const;
};
static_assert(sizeof(ForbiddenWords) == 0xC);
extern ForbiddenWords g_forbiddenWords;
#endif