#include "data_manager.h" #include "game.h" #include "client_card.h" namespace ygo { namespace{ unsigned char scriptBuffer[0x100000]{}; } DataManager dataManager; static const char SELECT_STMT[] = "SELECT datas.id, datas.ot, datas.alias, datas.setcode, datas.type, datas.atk, datas.def, datas.level, datas.race, datas.attribute, datas.category," " texts.name, texts.desc, texts.str1, texts.str2, texts.str3, texts.str4, texts.str5, texts.str6, texts.str7, texts.str8," " texts.str9, texts.str10, texts.str11, texts.str12, texts.str13, texts.str14, texts.str15, texts.str16 FROM datas INNER JOIN texts ON datas.id = texts.id"; static constexpr int DATAS_COUNT = 11; static constexpr int CARD_ARTWORK_VERSIONS_OFFSET = 20; static inline bool is_alternative(uint32_t code, uint32_t alias) { return alias && (alias < code + CARD_ARTWORK_VERSIONS_OFFSET) && (code < alias + CARD_ARTWORK_VERSIONS_OFFSET); } DataManager::DataManager() : _datas(32768), _strings(32768) { extra_setcode = { {8512558u, {0x8f, 0x54, 0x59, 0x82, 0x13a}}, {55088578u, {0x8f, 0x54, 0x59, 0x82, 0x13a}}, }; } bool DataManager::ReadDB(sqlite3* pDB) { sqlite3_stmt* pStmt = nullptr; int texts_offset = DATAS_COUNT; if (sqlite3_prepare_v2(pDB, SELECT_STMT, -1, &pStmt, nullptr) != SQLITE_OK) return Error(pDB, pStmt); wchar_t strBuffer[4096]; for (int step = sqlite3_step(pStmt); step != SQLITE_DONE; step = sqlite3_step(pStmt)) { if (step != SQLITE_ROW) return Error(pDB, pStmt); uint32_t code = static_cast(sqlite3_column_int64(pStmt, 0)); auto& cd = _datas[code]; cd.code = code; cd.ot = sqlite3_column_int(pStmt, 1); cd.alias = sqlite3_column_int(pStmt, 2); uint64_t setcode = static_cast(sqlite3_column_int64(pStmt, 3)); write_setcode(cd.setcode, setcode); cd.type = static_cast(sqlite3_column_int64(pStmt, 4)); cd.attack = sqlite3_column_int(pStmt, 5); cd.defense = sqlite3_column_int(pStmt, 6); if (cd.type & TYPE_LINK) { cd.link_marker = cd.defense; cd.defense = 0; } else cd.link_marker = 0; uint32_t level = static_cast(sqlite3_column_int64(pStmt, 7)); cd.level = level & 0xff; cd.lscale = (level >> 24) & 0xff; cd.rscale = (level >> 16) & 0xff; cd.race = static_cast(sqlite3_column_int64(pStmt, 8)); cd.attribute = static_cast(sqlite3_column_int64(pStmt, 9)); cd.category = static_cast(sqlite3_column_int64(pStmt, 10)); // rule_code if (cd.code == 5405695) { cd.rule_code = cd.alias; cd.alias = 0; } else if (cd.alias && !(cd.type & TYPE_TOKEN) && !is_alternative(cd.code, cd.alias)) { cd.rule_code = cd.alias; cd.alias = 0; } auto& cs = _strings[code]; if (const char* text = (const char*)sqlite3_column_text(pStmt, texts_offset + 0)) { BufferIO::DecodeUTF8(text, strBuffer); cs.name = strBuffer; } if (const char* text = (const char*)sqlite3_column_text(pStmt, texts_offset + 1)) { BufferIO::DecodeUTF8(text, strBuffer); cs.text = strBuffer; } for (int i = 0; i < DESC_COUNT; ++i) { if (const char* text = (const char*)sqlite3_column_text(pStmt, (texts_offset + 2) + i)) { BufferIO::DecodeUTF8(text, strBuffer); cs.desc[i] = strBuffer; } } } sqlite3_finalize(pStmt); for (auto& entry : _datas) { auto& cd = entry.second; if (cd.rule_code || !cd.alias || (cd.type & TYPE_TOKEN)) continue; auto it = _datas.find(cd.alias); if (it == _datas.end()) continue; cd.rule_code = it->second.rule_code; } for (const auto& entry : extra_setcode) { const auto& code = entry.first; const auto& list = entry.second; if (list.size() > SIZE_SETCODE || list.empty()) continue; auto it = _datas.find(code); if (it == _datas.end()) continue; std::memcpy(it->second.setcode, list.data(), list.size() * sizeof(uint16_t)); } return true; } bool DataManager::LoadDB(const char* file) { auto reader = IrrFileSystem->createAndOpenFile(file); if (reader == nullptr) { mysnprintf(errmsg, "File does not exist or failed to unzip: %s", file); return false; } sqlite3* db_handle = nullptr; if (sqlite3_open(":memory:", &db_handle) != SQLITE_OK) { Error(db_handle, nullptr); sqlite3_close(db_handle); reader->drop(); return false; } sqlite3_int64 sz = reader->getSize(); unsigned char* buffer = (unsigned char*)sqlite3_malloc64(sz); if (!buffer) { Error(db_handle, nullptr); sqlite3_close(db_handle); reader->drop(); return false; } reader->read(buffer, sz); reader->drop(); // force rollback-journal mode by setting header bytes 18 and 19 to 0x01 if (sz >= 20 && buffer[18] == 0x02) { buffer[18] = 0x01; buffer[19] = 0x01; } int rc = sqlite3_deserialize( db_handle, nullptr, buffer, sz, sz, SQLITE_DESERIALIZE_FREEONCLOSE | SQLITE_DESERIALIZE_READONLY ); if (rc != SQLITE_OK) { Error(db_handle, nullptr); sqlite3_close(db_handle); return false; } bool ret = ReadDB(db_handle); sqlite3_close(db_handle); return ret; } bool DataManager::LoadStrings(const char* file) { FILE* fp = myfopen(file, "r"); if(!fp) return false; char linebuf[TEXT_LINE_SIZE]{}; while(std::fgets(linebuf, sizeof linebuf, fp)) { ReadStringConfLine(linebuf); } std::fclose(fp); return true; } bool DataManager::LoadStrings(irr::io::IReadFile* reader) { char ch{}; std::string linebuf; while (reader->read(&ch, 1)) { if (ch == '\0') break; linebuf.push_back(ch); if (ch == '\n' || linebuf.size() >= TEXT_LINE_SIZE - 1) { ReadStringConfLine(linebuf.data()); linebuf.clear(); } } reader->drop(); return true; } void DataManager::ReadStringConfLine(const char* linebuf) { if(linebuf[0] != '!') return; char strbuf[TEXT_LINE_SIZE]{}; int value{}; wchar_t strBuffer[4096]{}; if (std::sscanf(linebuf, "!%63s", strbuf) != 1) return; if(!std::strcmp(strbuf, "system")) { if (std::sscanf(&linebuf[7], "%d %240[^\n]", &value, strbuf) != 2) return; BufferIO::DecodeUTF8(strbuf, strBuffer); _sysStrings[value] = strBuffer; } else if(!std::strcmp(strbuf, "victory")) { if (std::sscanf(&linebuf[8], "%x %240[^\n]", &value, strbuf) != 2) return; BufferIO::DecodeUTF8(strbuf, strBuffer); _victoryStrings[value] = strBuffer; } else if(!std::strcmp(strbuf, "counter")) { if (std::sscanf(&linebuf[8], "%x %240[^\n]", &value, strbuf) != 2) return; BufferIO::DecodeUTF8(strbuf, strBuffer); _counterStrings[value] = strBuffer; } else if(!std::strcmp(strbuf, "setname")) { //using tab for comment if (std::sscanf(&linebuf[8], "%x %240[^\t\n]", &value, strbuf) != 2) return; BufferIO::DecodeUTF8(strbuf, strBuffer); _setnameStrings[value] = strBuffer; } } bool DataManager::Error(sqlite3* pDB, sqlite3_stmt* pStmt) { if (const char* msg = sqlite3_errmsg(pDB)) mysnprintf(errmsg, "sqlite3_errmsg: %s", msg); else errmsg[0] = '\0'; sqlite3_finalize(pStmt); return false; } code_pointer DataManager::GetCodePointer(uint32_t code) const { return _datas.find(code); } string_pointer DataManager::GetStringPointer(uint32_t code) const { return _strings.find(code); } bool DataManager::GetData(uint32_t code, CardData* pData) const { auto cdit = _datas.find(code); if(cdit == _datas.end()) return false; if (pData) { std::memcpy(pData, &cdit->second, sizeof(CardData)); } return true; } bool DataManager::GetString(uint32_t code, CardString* pStr) const { auto csit = _strings.find(code); if(csit == _strings.end()) { pStr->name = unknown_string; pStr->text = unknown_string; return false; } *pStr = csit->second; return true; } const wchar_t* DataManager::GetName(uint32_t code) const { auto csit = _strings.find(code); if(csit == _strings.end()) return unknown_string; if(!csit->second.name.empty()) return csit->second.name.c_str(); return unknown_string; } const wchar_t* DataManager::GetText(uint32_t code) const { auto csit = _strings.find(code); if(csit == _strings.end()) return unknown_string; if(!csit->second.text.empty()) return csit->second.text.c_str(); return unknown_string; } const wchar_t* DataManager::GetDesc(uint32_t strCode) const { if (strCode < (MIN_CARD_ID << 4)) return GetSysString(strCode); unsigned int code = (strCode >> 4) & 0x0fffffff; unsigned int offset = strCode & 0xf; auto csit = _strings.find(code); if(csit == _strings.end()) return unknown_string; if(!csit->second.desc[offset].empty()) return csit->second.desc[offset].c_str(); return unknown_string; } const wchar_t* DataManager::GetMapString(const wstring_map& table, uint32_t code) const { auto csit = table.find(code); if (csit == table.end()) return unknown_string; return csit->second.c_str(); } const wchar_t* DataManager::GetSysString(uint32_t code) const { return GetMapString(_sysStrings, code); } const wchar_t* DataManager::GetVictoryString(uint32_t code) const { return GetMapString(_victoryStrings, code); } const wchar_t* DataManager::GetCounterName(uint32_t code) const { return GetMapString(_counterStrings, code); } const wchar_t* DataManager::GetSetName(uint32_t code) const { return GetMapString(_setnameStrings, code); } std::vector DataManager::GetSetCodes(std::wstring setname) const { std::vector matchingCodes; for(auto csit = _setnameStrings.begin(); csit != _setnameStrings.end(); ++csit) { const std::wstring& setnameString = csit->second; size_t start = 0; while(start < setnameString.size()) { // handle "setname|another setname" auto pos = setnameString.find(L'|', start); std::wstring token; if(pos == std::wstring::npos) token = setnameString.substr(start); else token = setnameString.substr(start, pos - start); if(setname.size() < 2) { // exact match for short set names to avoid too many results if(token == setname) { matchingCodes.push_back(csit->first); break; } } else { if(token.find(setname) != std::wstring::npos) { matchingCodes.push_back(csit->first); break; } } if(pos == std::wstring::npos) break; start = pos + 1; } } return matchingCodes; } std::wstring DataManager::GetNumString(int num, bool bracket) const { if(!bracket) return std::to_wstring(num); std::wstring numBuffer{ L"(" }; numBuffer.append(std::to_wstring(num)); numBuffer.push_back(L')'); return numBuffer; } const wchar_t* DataManager::FormatLocation(int location, int sequence) const { if(location == LOCATION_SZONE) { if(sequence < 5) return GetSysString(1003); else if(sequence == 5) return GetSysString(1008); else return GetSysString(1009); } int string_id = 0; for (int i = 0; i < 10; ++i) { if ((0x1U << i) == location) { string_id = STRING_ID_LOCATION + i; break; } } if (string_id) return GetSysString(string_id); else return unknown_string; } const wchar_t* DataManager::FormatLocation(ClientCard* card) const { if (!card) return unknown_string; return FormatLocation(card->location, card->sequence); } std::wstring DataManager::FormatAttribute(unsigned int attribute) const { std::wstring buffer; for (int i = 0; i < ATTRIBUTES_COUNT; ++i) { if (attribute & (0x1U << i)) { if (!buffer.empty()) buffer.push_back(L'|'); buffer.append(GetSysString(STRING_ID_ATTRIBUTE + i)); } } if (buffer.empty()) buffer = unknown_string; return buffer; } std::wstring DataManager::FormatRace(unsigned int race) const { std::wstring buffer; for(int i = 0; i < RACES_COUNT; ++i) { if(race & (0x1U << i)) { if (!buffer.empty()) buffer.push_back(L'|'); buffer.append(GetSysString(STRING_ID_RACE + i)); } } if (buffer.empty()) buffer = unknown_string; return buffer; } std::wstring DataManager::FormatType(unsigned int type) const { std::wstring buffer; for (int i = 0; i < TYPES_COUNT; ++i) { if (type & (0x1U << i)) { if (!buffer.empty()) buffer.push_back(L'|'); buffer.append(GetSysString(STRING_ID_TYPE + i)); } } if (buffer.empty()) buffer = unknown_string; return buffer; } std::wstring DataManager::FormatSetName(const uint16_t setcode[]) const { std::wstring buffer; for(int i = 0; i < 10; ++i) { if (!setcode[i]) break; const wchar_t* setname = GetSetName(setcode[i]); if (!buffer.empty()) buffer.push_back(L'|'); buffer.append(setname); } if (buffer.empty()) buffer = unknown_string; return buffer; } std::wstring DataManager::FormatLinkMarker(unsigned int link_marker) const { std::wstring buffer; if (link_marker & LINK_MARKER_TOP_LEFT) buffer.append(L"[\u2196]"); if (link_marker & LINK_MARKER_TOP) buffer.append(L"[\u2191]"); if (link_marker & LINK_MARKER_TOP_RIGHT) buffer.append(L"[\u2197]"); if (link_marker & LINK_MARKER_LEFT) buffer.append(L"[\u2190]"); if (link_marker & LINK_MARKER_RIGHT) buffer.append(L"[\u2192]"); if (link_marker & LINK_MARKER_BOTTOM_LEFT) buffer.append(L"[\u2199]"); if (link_marker & LINK_MARKER_BOTTOM) buffer.append(L"[\u2193]"); if (link_marker & LINK_MARKER_BOTTOM_RIGHT) buffer.append(L"[\u2198]"); return buffer; } wchar_t DataManager::NormalizeChar(wchar_t c) { // Convert Alphabet characters to uppercase to ignore case. if (c >= 0x0061 && c <= 0x007A) { return c - 0x0020; } // Normalize accented characters (Latin-1 Supplement). if ((c >= 0x00C0 && c <= 0x00C5) || (c >= 0x00E0 && c <= 0x00E5)) { return L'A'; } if (c == 0x00C7 || c == 0x00E7) { return L'C'; } if ((c >= 0x00C8 && c <= 0x00CB) || (c >= 0x00E8 && c <= 0x00EB)) { return L'E'; } if ((c >= 0x00CC && c <= 0x00CF) || (c >= 0x00EC && c <= 0x00EF)) { return L'I'; } if (c == 0x00D1 || c == 0x00F1) { return L'N'; } if ((c >= 0x00D2 && c <= 0x00D6) || (c >= 0x00F2 && c <= 0x00F6)) { return L'O'; } if ((c >= 0x00D9 && c <= 0x00DC) || (c >= 0x00F9 && c <= 0x00FC)) { return L'U'; } if (c == 0x00DD || c == 0x00FD || c == 0x00FF) { return L'Y'; } return c; } void DataManager::NormalizeString(const wchar_t* src, wchar_t* dst, size_t dst_size) { size_t i = 0; for(; src[i] && i < dst_size - 1; ++i) { dst[i] = NormalizeChar(src[i]); } dst[i] = 0; } bool DataManager::CardNameContains(const wchar_t* haystack, const wchar_t* needle) { if(!needle[0]) { return true; } if(!haystack) { return false; } wchar_t normalized_haystack[TEXT_LINE_SIZE]{}; wchar_t normalized_needle[TEXT_LINE_SIZE]{}; NormalizeString(haystack, normalized_haystack, TEXT_LINE_SIZE); NormalizeString(needle, normalized_needle, TEXT_LINE_SIZE); return std::wcsstr(normalized_haystack, normalized_needle) != nullptr; } uint32_t DataManager::CardReader(uint32_t code, card_data* pData) { if (!dataManager.GetData(code, pData)) pData->clear(); return 0; } unsigned char* DataManager::ScriptReaderEx(const char* script_path, int* slen) { // default script name: ./script/c%d.lua if (std::strncmp(script_path, "./script", 8) != 0) // not a card script file return ReadScriptFromFile(script_path, slen); const char* script_name = script_path + 2; char expansions_path[1024]{}; mysnprintf(expansions_path, "./expansions/%s", script_name); if (mainGame->gameConf.prefer_expansion_script) { // debug script with raw file in expansions if (ReadScriptFromFile(expansions_path, slen)) return scriptBuffer; if (ReadScriptFromIrrFS(script_name, slen)) return scriptBuffer; if (ReadScriptFromFile(script_path, slen)) return scriptBuffer; } else { if (ReadScriptFromIrrFS(script_name, slen)) return scriptBuffer; if (ReadScriptFromFile(script_path, slen)) return scriptBuffer; if (ReadScriptFromFile(expansions_path, slen)) return scriptBuffer; } return nullptr; } unsigned char* DataManager::ReadScriptFromIrrFS(const char* script_name, int* slen) { auto reader = dataManager.IrrFileSystem->createAndOpenFile(script_name); if (!reader) return nullptr; int size = reader->read(scriptBuffer, sizeof scriptBuffer); reader->drop(); if (size >= (int)sizeof scriptBuffer) return nullptr; *slen = size; return scriptBuffer; } unsigned char* DataManager::ReadScriptFromFile(const char* script_name, int* slen) { FILE* fp = myfopen(script_name, "rb"); if (!fp) return nullptr; size_t len = std::fread(scriptBuffer, 1, sizeof scriptBuffer, fp); std::fclose(fp); if (len >= sizeof scriptBuffer) return nullptr; *slen = (int)len; return scriptBuffer; } bool DataManager::deck_sort_lv(const CardDataC* p1, const CardDataC* p2) { if ((p1->type & 0x7) != (p2->type & 0x7)) return (p1->type & 0x7) < (p2->type & 0x7); if ((p1->type & 0x7) == 1) { auto type1 = (p1->type & 0x48020c0) ? (p1->type & 0x48020c1) : (p1->type & 0x31); auto type2 = (p2->type & 0x48020c0) ? (p2->type & 0x48020c1) : (p2->type & 0x31); if (type1 != type2) return type1 < type2; if (p1->level != p2->level) return p1->level > p2->level; if (p1->attack != p2->attack) return p1->attack > p2->attack; if (p1->defense != p2->defense) return p1->defense > p2->defense; return p1->code < p2->code; } if ((p1->type & 0xfffffff8) != (p2->type & 0xfffffff8)) return (p1->type & 0xfffffff8) < (p2->type & 0xfffffff8); return p1->code < p2->code; } bool DataManager::deck_sort_atk(const CardDataC* p1, const CardDataC* p2) { if ((p1->type & 0x7) != (p2->type & 0x7)) return (p1->type & 0x7) < (p2->type & 0x7); if ((p1->type & 0x7) == 1) { if (p1->attack != p2->attack) return p1->attack > p2->attack; if (p1->defense != p2->defense) return p1->defense > p2->defense; if (p1->level != p2->level) return p1->level > p2->level; auto type1 = (p1->type & 0x48020c0) ? (p1->type & 0x48020c1) : (p1->type & 0x31); auto type2 = (p2->type & 0x48020c0) ? (p2->type & 0x48020c1) : (p2->type & 0x31); if (type1 != type2) return type1 < type2; return p1->code < p2->code; } if ((p1->type & 0xfffffff8) != (p2->type & 0xfffffff8)) return (p1->type & 0xfffffff8) < (p2->type & 0xfffffff8); return p1->code < p2->code; } bool DataManager::deck_sort_def(const CardDataC* p1, const CardDataC* p2) { if ((p1->type & 0x7) != (p2->type & 0x7)) return (p1->type & 0x7) < (p2->type & 0x7); if ((p1->type & 0x7) == 1) { if (p1->defense != p2->defense) return p1->defense > p2->defense; if (p1->attack != p2->attack) return p1->attack > p2->attack; if (p1->level != p2->level) return p1->level > p2->level; auto type1 = (p1->type & 0x48020c0) ? (p1->type & 0x48020c1) : (p1->type & 0x31); auto type2 = (p2->type & 0x48020c0) ? (p2->type & 0x48020c1) : (p2->type & 0x31); if (type1 != type2) return type1 < type2; return p1->code < p2->code; } if ((p1->type & 0xfffffff8) != (p2->type & 0xfffffff8)) return (p1->type & 0xfffffff8) < (p2->type & 0xfffffff8); return p1->code < p2->code; } bool DataManager::deck_sort_name(const CardDataC* p1, const CardDataC* p2) { const wchar_t* name1 = dataManager.GetName(p1->code); const wchar_t* name2 = dataManager.GetName(p2->code); int res = std::wcscmp(name1, name2); if (res != 0) return res < 0; return p1->code < p2->code; } }