Batch #1710 by ragnarok-clanker: 0 byte-identical, 7 confirmed by peer review. - fn_18371 DecodeMessageRot: logic confirmed by peer review - fn_18372 InsertMessageRow: logic confirmed by peer review - fn_18374 ImportMessageTable: logic confirmed by peer review - fn_03416 CCommonObject::ReadCsvRow: logic confirmed by peer review - fn_03418 CCommonObject::LoadCsv: logic confirmed by peer review - fn_03420 CCommonObject::OpenCsv: logic confirmed by peer review - fn_18373 ?TransformFields@?A0x79e5ab67@@YA?AV?$vector@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@V?$allocator@V?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@@2@@std@@ABV23@@Z: logic confirmed by peer review Reviewed-by: ragnarok-clanker
347 lines
8.4 KiB
C++
347 lines
8.4 KiB
C++
#include "Client/Input/InputCharset.h"
|
|
#include "Contents/Configuration/ServiceConfiguration.h"
|
|
#include "Contents/MessageTable.h"
|
|
#include "File/CommonObject.h"
|
|
#include "File/File.h"
|
|
#include "Network/TextEncoding.h"
|
|
#include "Utility/Text/Base64.h"
|
|
#include "Utility/Text/FormatString.h"
|
|
#include "Utility/Text/LanguageCharset.h"
|
|
#include "Utility/Text/TextTokens.h"
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
// STATIC: 0x016D5780
|
|
// GLOBAL: gl_24382
|
|
std::unordered_map<int, std::string> g_messageStrings;
|
|
|
|
// GLOBAL: gl_24601
|
|
static CCommonObject g_messageTableObject;
|
|
|
|
namespace {
|
|
const char* const messageNames[] = {
|
|
#include "Contents/MessageNames.inc"
|
|
};
|
|
static_assert(sizeof(messageNames) / sizeof(messageNames[0]) == 4698);
|
|
|
|
// OFFSET: 0x005B8500
|
|
// FUNCTION: fn_03421
|
|
void DecodeField(std::string& field, std::vector<std::string>& row) {
|
|
if (field.empty()) {
|
|
return;
|
|
}
|
|
|
|
std::string decoded = DecodeBase64(field);
|
|
if (decoded.size() > 2) {
|
|
if (decoded.front() == '"' && decoded.back() == '"') {
|
|
decoded = decoded.substr(1, decoded.size() - 2);
|
|
}
|
|
|
|
for (std::size_t pos = 0; (pos = decoded.find("\\\"\"", pos)) != std::string::npos; ++pos) {
|
|
decoded.replace(pos, 3, "\"");
|
|
}
|
|
}
|
|
|
|
row.push_back(FromUtf8(decoded, g_textCodePage));
|
|
}
|
|
|
|
// OFFSET: 0x00B009D0
|
|
// FUNCTION: fn_18373
|
|
std::vector<std::string> TransformFields(const std::vector<std::string>& row) {
|
|
const char* escapes[] = {"\\r", "\\n", "\\t", "\\'", "\\\"", "\\\\"};
|
|
const char replacements[] = {'\r', '\n', '\t', '\'', '"', '\\'};
|
|
|
|
std::vector<std::string> result;
|
|
result.reserve(row.size());
|
|
for (std::string text : row) {
|
|
if (text.empty()) {
|
|
text = "NO MSG";
|
|
} else {
|
|
for (unsigned i = 0; i < 6; ++i) {
|
|
for (std::size_t pos = 0; (pos = text.find(escapes[i], pos)) != std::string::npos; ++pos) {
|
|
text.replace(pos, 2, 1, replacements[i]);
|
|
}
|
|
}
|
|
}
|
|
result.push_back(std::move(text));
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x005B8410
|
|
// FUNCTION: fn_03420
|
|
bool CCommonObject::OpenCsv(const char* name, CFile& file) {
|
|
if (!name || !std::strstr(name, ".csv") || !file.Open(name, 0, true, false)) {
|
|
return false;
|
|
}
|
|
|
|
const unsigned char* data = static_cast<const unsigned char*>(file.GetData());
|
|
if (file.GetSize() >= 2) {
|
|
if (data[0] == 0xFF && data[1] == 0xFE) {
|
|
FormatOwnedString("Not Match Encoding Type : %d", 5);
|
|
return false;
|
|
}
|
|
if (data[0] == 0xFE && data[1] == 0xFF) {
|
|
FormatOwnedString("Not Match Encoding Type : %d", 7);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (file.GetSize() >= 3 && data[0] == 0xEF && data[1] == 0xBB && data[2] == 0xBF) {
|
|
file.Seek(3, FILE_CURRENT);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// OFFSET: 0x005B7910
|
|
// FUNCTION: fn_03416
|
|
std::vector<std::string> CCommonObject::ReadCsvRow(CFile& file, char) {
|
|
std::vector<std::string> row;
|
|
std::string field;
|
|
|
|
while (!file.IsEOF()) {
|
|
char value;
|
|
file.Read(&value, 1);
|
|
if (value == '\n' || value == '\r' || value == ',') {
|
|
DecodeField(field, row);
|
|
field.clear();
|
|
if (value == '\r') {
|
|
char next;
|
|
if (file.Peek(&next, 1) && next == '\n') {
|
|
file.Read(&value, 1);
|
|
break;
|
|
}
|
|
} else if (value == '\n') {
|
|
break;
|
|
}
|
|
} else {
|
|
field.push_back(value);
|
|
}
|
|
}
|
|
|
|
DecodeField(field, row);
|
|
return row;
|
|
}
|
|
|
|
// OFFSET: 0x005B7CD0
|
|
// FUNCTION: fn_03418
|
|
bool CCommonObject::LoadCsv(const char* name, std::vector<std::string> (*transform)(const std::vector<std::string>&),
|
|
void (*insert)(std::vector<const char*>&)) {
|
|
CFile file;
|
|
if (!this->OpenCsv(name, file)) {
|
|
return false;
|
|
}
|
|
|
|
while (!file.IsEOF()) {
|
|
std::vector<std::string> row = this->ReadCsvRow(file, ',');
|
|
if (transform) {
|
|
row = transform(row);
|
|
}
|
|
|
|
if (insert) {
|
|
std::vector<const char*> fields;
|
|
fields.reserve(row.size());
|
|
const int count = static_cast<int>(row.size());
|
|
for (int i = 0; i < count; ++i) {
|
|
fields.push_back(row[i].c_str());
|
|
}
|
|
insert(fields);
|
|
}
|
|
}
|
|
|
|
file.Close();
|
|
return true;
|
|
}
|
|
|
|
// OFFSET: 0x00B00360
|
|
// FUNCTION: fn_18368
|
|
int FillMessageBuckets(int* first, std::uintptr_t last, int* value) {
|
|
int result = 0;
|
|
int* dest = first;
|
|
unsigned int count = (last - reinterpret_cast<std::uintptr_t>(first) + 3) >> 2;
|
|
if (reinterpret_cast<std::uintptr_t>(first) > last) {
|
|
count = 0;
|
|
}
|
|
if (count >= 4) {
|
|
result = *value;
|
|
if (first > value || &first[count - 1] < value) {
|
|
unsigned int aligned = count & ~3u;
|
|
dest = &first[aligned];
|
|
for (int* p = first; p != dest; ++p) {
|
|
*p = result;
|
|
}
|
|
}
|
|
}
|
|
for (; dest != reinterpret_cast<int*>(last); ++dest) {
|
|
result = *value;
|
|
*dest = *value;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// OFFSET: 0x00B00130
|
|
// FUNCTION: fn_18367
|
|
void InsertMessage(int id, const char* text) {
|
|
g_messageStrings.emplace(id, text);
|
|
}
|
|
|
|
// OFFSET: 0x00B00F00
|
|
// FUNCTION: fn_18376
|
|
const char* LookupMessage(int id) {
|
|
// STATIC: 0x016D57A0
|
|
static char missing[20];
|
|
if (id >= 4698) {
|
|
_snprintf_s(missing, sizeof(missing), _TRUNCATE, "NO MSG : %d", id);
|
|
return missing;
|
|
}
|
|
|
|
auto found = g_messageStrings.find(id);
|
|
if (found == g_messageStrings.end()) {
|
|
return "NO MSG";
|
|
}
|
|
|
|
const std::string& text = found->second;
|
|
if (!g_inlineCharset || text[0] == '/' || text == "NO MSG") {
|
|
return text.c_str();
|
|
}
|
|
|
|
// STATIC: 0x016D57B4
|
|
// GLOBAL: gl_24383
|
|
static std::vector<std::string> cached(4698);
|
|
std::string& result = cached[id];
|
|
if (result.empty() || g_serviceType == 19) {
|
|
// STATIC: 0x016D57C8
|
|
// GLOBAL: gl_24384
|
|
static char buffer[260];
|
|
_snprintf_s(buffer, sizeof(buffer), _TRUNCATE, "%s%s", g_serverLanguage->Prefix(true), text.c_str());
|
|
result = buffer;
|
|
}
|
|
|
|
return result.c_str();
|
|
}
|
|
|
|
void InsertMessageRow(std::vector<const char*>& row);
|
|
|
|
bool ImportMessageCsv(std::string_view bytes) {
|
|
g_messageStrings.clear();
|
|
if (bytes.size() >= 2 && ((static_cast<unsigned char>(bytes[0]) == 0xFF && static_cast<unsigned char>(bytes[1]) == 0xFE) ||
|
|
(static_cast<unsigned char>(bytes[0]) == 0xFE && static_cast<unsigned char>(bytes[1]) == 0xFF))) {
|
|
return false;
|
|
}
|
|
|
|
if (bytes.substr(0, 3) == "\xEF\xBB\xBF") {
|
|
bytes.remove_prefix(3);
|
|
}
|
|
|
|
std::size_t cursor = 0;
|
|
|
|
while (cursor < bytes.size()) {
|
|
std::vector<std::string> row;
|
|
std::string field;
|
|
|
|
while (cursor < bytes.size()) {
|
|
char value = bytes[cursor++];
|
|
if (value == ',' || value == '\r' || value == '\n') {
|
|
DecodeField(field, row);
|
|
field.clear();
|
|
if (value == '\n') {
|
|
break;
|
|
}
|
|
|
|
if (value == '\r' && cursor < bytes.size() && bytes[cursor] == '\n') {
|
|
++cursor;
|
|
break;
|
|
}
|
|
} else {
|
|
field.push_back(value);
|
|
}
|
|
}
|
|
|
|
DecodeField(field, row);
|
|
row = TransformFields(row);
|
|
std::vector<const char*> fields;
|
|
fields.reserve(row.size());
|
|
for (const std::string& text : row) {
|
|
fields.push_back(text.c_str());
|
|
}
|
|
InsertMessageRow(fields);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// OFFSET: 0x00B00810
|
|
// FUNCTION: fn_18370
|
|
int FindMessageId(const char* name) {
|
|
if (!name || *name == '\0') {
|
|
return 4698;
|
|
}
|
|
|
|
for (int id = 0; id < 4699; ++id) {
|
|
if (messageNames[id] && std::strcmp(messageNames[id], name) == 0) {
|
|
return id;
|
|
}
|
|
}
|
|
|
|
return 4698;
|
|
}
|
|
|
|
// OFFSET: 0x00B00890
|
|
// FUNCTION: fn_18371
|
|
char* DecodeMessageRot(char* str) {
|
|
return str;
|
|
}
|
|
|
|
// OFFSET: 0x00B00910
|
|
// FUNCTION: fn_18372
|
|
void InsertMessageRow(std::vector<const char*>& row) {
|
|
const std::ptrdiff_t count = row.end() - row.begin();
|
|
if (count < 2) {
|
|
return;
|
|
}
|
|
|
|
int id = FindMessageId(row[0]);
|
|
if (id < 4698) {
|
|
g_messageStrings.emplace(id, row[count - 1]);
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x00B00E40
|
|
// FUNCTION: fn_18374
|
|
void ImportMessageTable() {
|
|
g_messageStrings.clear();
|
|
g_messageTableObject.LoadCsv("msgstringtable.csv", TransformFields, InsertMessageRow);
|
|
}
|
|
|
|
// OFFSET: 0x00B00EC0
|
|
// FUNCTION: fn_18375
|
|
bool IsObfuscatedMessage(int id) {
|
|
if (g_serviceType != 0) {
|
|
return false;
|
|
}
|
|
|
|
switch (id) {
|
|
case 2919549:
|
|
case 2919550:
|
|
case 2919551:
|
|
case 2919552:
|
|
case 2919554:
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// OFFSET: 0x00B011B0
|
|
// FUNCTION: fn_18377
|
|
const char* GetMessageString(int id) {
|
|
auto found = g_messageStrings.find(id);
|
|
if (found == g_messageStrings.end()) {
|
|
return "";
|
|
}
|
|
|
|
return found->second.c_str();
|
|
}
|