/* EQEMu: Everquest Server Emulator Copyright (C) 2001-2016 EQEMu Development Team (http://eqemulator.net) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY except by those people which sell it, which are required to give you total support for your newly bought product; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include "say_link.h" #include "emu_constants.h" #include "strings.h" #include "item_instance.h" #include "item_data.h" #include "../zone/zonedb.h" #include // static bucket global std::vector g_cached_saylinks = {}; bool EQ::saylink::DegenerateLinkBody(SayLinkBody_Struct &say_link_body_struct, const std::string &say_link_body) { memset(&say_link_body_struct, 0, sizeof(say_link_body_struct)); if (say_link_body.length() != EQ::constants::SAY_LINK_BODY_SIZE) { return false; } say_link_body_struct.action_id = (uint8) strtol(say_link_body.substr(0, 1).c_str(), nullptr, 16); say_link_body_struct.item_id = (uint32) strtol(say_link_body.substr(1, 5).c_str(), nullptr, 16); say_link_body_struct.augment_1 = (uint32) strtol(say_link_body.substr(6, 5).c_str(), nullptr, 16); say_link_body_struct.augment_2 = (uint32) strtol(say_link_body.substr(11, 5).c_str(), nullptr, 16); say_link_body_struct.augment_3 = (uint32) strtol(say_link_body.substr(16, 5).c_str(), nullptr, 16); say_link_body_struct.augment_4 = (uint32) strtol(say_link_body.substr(21, 5).c_str(), nullptr, 16); say_link_body_struct.augment_5 = (uint32) strtol(say_link_body.substr(26, 5).c_str(), nullptr, 16); say_link_body_struct.augment_6 = (uint32) strtol(say_link_body.substr(31, 5).c_str(), nullptr, 16); say_link_body_struct.is_evolving = (uint8) strtol(say_link_body.substr(36, 1).c_str(), nullptr, 16); say_link_body_struct.evolve_group = (uint32) strtol(say_link_body.substr(37, 4).c_str(), nullptr, 16); say_link_body_struct.evolve_level = (uint8) strtol(say_link_body.substr(41, 2).c_str(), nullptr, 16); say_link_body_struct.ornament_icon = (uint32) strtol(say_link_body.substr(43, 5).c_str(), nullptr, 16); say_link_body_struct.hash = (uint32) strtol(say_link_body.substr(48, 8).c_str(), nullptr, 16); return true; } bool EQ::saylink::GenerateLinkBody(std::string &say_link_body, const SayLinkBody_Struct &say_link_body_struct) { say_link_body = StringFormat( "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X", (0x0F & say_link_body_struct.action_id), (0x000FFFFF & say_link_body_struct.item_id), (0x000FFFFF & say_link_body_struct.augment_1), (0x000FFFFF & say_link_body_struct.augment_2), (0x000FFFFF & say_link_body_struct.augment_3), (0x000FFFFF & say_link_body_struct.augment_4), (0x000FFFFF & say_link_body_struct.augment_5), (0x000FFFFF & say_link_body_struct.augment_6), (0x0F & say_link_body_struct.is_evolving), (0x0000FFFF & say_link_body_struct.evolve_group), (0xFF & say_link_body_struct.evolve_level), (0x000FFFFF & say_link_body_struct.ornament_icon), (0xFFFFFFFF & say_link_body_struct.hash) ); if (say_link_body.length() != EQ::constants::SAY_LINK_BODY_SIZE) { return false; } return true; } EQ::SayLinkEngine::SayLinkEngine() { Reset(); } const std::string &EQ::SayLinkEngine::GenerateLink() { m_Link.clear(); m_LinkBody.clear(); m_LinkText.clear(); generate_body(); generate_text(); if ((m_LinkBody.length() == EQ::constants::SAY_LINK_BODY_SIZE) && (m_LinkText.length() > 0)) { m_Link.push_back(0x12); m_Link.append(m_LinkBody); m_Link.append(m_LinkText); m_Link.push_back(0x12); } if ((m_Link.length() == 0) || (m_Link.length() > (EQ::constants::SAY_LINK_MAXIMUM_SIZE))) { m_Error = true; m_Link = ""; LogError("SayLinkEngine::GenerateLink() failed to generate a useable say link"); LogError(">> LinkType: {}, Lengths: [link: {}({}), body: {}({}), text: {}({})]", m_LinkType, m_Link.length(), EQ::constants::SAY_LINK_MAXIMUM_SIZE, m_LinkBody.length(), EQ::constants::SAY_LINK_BODY_SIZE, m_LinkText.length(), EQ::constants::SAY_LINK_TEXT_SIZE ); LogError(">> LinkBody: {}", m_LinkBody.c_str()); LogError(">> LinkText: {}", m_LinkText.c_str()); } return m_Link; } void EQ::SayLinkEngine::Reset() { m_LinkType = saylink::SayLinkBlank; m_ItemData = nullptr; m_LootData = nullptr; m_ItemInst = nullptr; memset(&m_LinkBodyStruct, 0, sizeof(SayLinkBody_Struct)); memset(&m_LinkProxyStruct, 0, sizeof(SayLinkProxy_Struct)); m_TaskUse = false; m_Link.clear(); m_LinkBody.clear(); m_LinkText.clear(); m_Error = false; } void EQ::SayLinkEngine::generate_body() { /* Current server mask: EQClientRoF2 RoF2: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X" (56) RoF: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X" (55) SoF: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%05X" "%08X" (50) 6.2: "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%1X" "%08X" (45) */ memset(&m_LinkBodyStruct, 0, sizeof(SayLinkBody_Struct)); const EQ::ItemData *item_data = nullptr; switch (m_LinkType) { case saylink::SayLinkBlank: break; case saylink::SayLinkItemData: if (m_ItemData == nullptr) { break; } m_LinkBodyStruct.item_id = m_ItemData->ID; m_LinkBodyStruct.evolve_group = m_ItemData->LoreGroup; // this probably won't work for all items //m_LinkBodyStruct.evolve_level = m_ItemData->EvolvingLevel; // TODO: add hash call break; case saylink::SayLinkLootItem: if (m_LootData == nullptr) { break; } item_data = database.GetItem(m_LootData->item_id); if (item_data == nullptr) { break; } m_LinkBodyStruct.item_id = item_data->ID; m_LinkBodyStruct.augment_1 = m_LootData->aug_1; m_LinkBodyStruct.augment_2 = m_LootData->aug_2; m_LinkBodyStruct.augment_3 = m_LootData->aug_3; m_LinkBodyStruct.augment_4 = m_LootData->aug_4; m_LinkBodyStruct.augment_5 = m_LootData->aug_5; m_LinkBodyStruct.augment_6 = m_LootData->aug_6; m_LinkBodyStruct.evolve_group = item_data->LoreGroup; // see note above //m_LinkBodyStruct.evolve_level = item_data->EvolvingLevel; // TODO: add hash call break; case saylink::SayLinkItemInst: if (m_ItemInst == nullptr) { break; } if (m_ItemInst->GetItem() == nullptr) { break; } m_LinkBodyStruct.item_id = m_ItemInst->GetItem()->ID; m_LinkBodyStruct.augment_1 = m_ItemInst->GetAugmentItemID(0); m_LinkBodyStruct.augment_2 = m_ItemInst->GetAugmentItemID(1); m_LinkBodyStruct.augment_3 = m_ItemInst->GetAugmentItemID(2); m_LinkBodyStruct.augment_4 = m_ItemInst->GetAugmentItemID(3); m_LinkBodyStruct.augment_5 = m_ItemInst->GetAugmentItemID(4); m_LinkBodyStruct.augment_6 = m_ItemInst->GetAugmentItemID(5); m_LinkBodyStruct.is_evolving = (m_ItemInst->IsEvolving() ? 1 : 0); m_LinkBodyStruct.evolve_group = m_ItemInst->GetItem()->LoreGroup; // see note above m_LinkBodyStruct.evolve_level = m_ItemInst->GetEvolveLvl(); m_LinkBodyStruct.ornament_icon = m_ItemInst->GetOrnamentationIcon(); // TODO: add hash call break; default: break; } if (m_LinkProxyStruct.action_id) { m_LinkBodyStruct.action_id = m_LinkProxyStruct.action_id; } if (m_LinkProxyStruct.item_id) { m_LinkBodyStruct.item_id = m_LinkProxyStruct.item_id; } if (m_LinkProxyStruct.augment_1) { m_LinkBodyStruct.augment_1 = m_LinkProxyStruct.augment_1; } if (m_LinkProxyStruct.augment_2) { m_LinkBodyStruct.augment_2 = m_LinkProxyStruct.augment_2; } if (m_LinkProxyStruct.augment_3) { m_LinkBodyStruct.augment_3 = m_LinkProxyStruct.augment_3; } if (m_LinkProxyStruct.augment_4) { m_LinkBodyStruct.augment_4 = m_LinkProxyStruct.augment_4; } if (m_LinkProxyStruct.augment_5) { m_LinkBodyStruct.augment_5 = m_LinkProxyStruct.augment_5; } if (m_LinkProxyStruct.augment_6) { m_LinkBodyStruct.augment_6 = m_LinkProxyStruct.augment_6; } if (m_LinkProxyStruct.is_evolving) { m_LinkBodyStruct.is_evolving = m_LinkProxyStruct.is_evolving; } if (m_LinkProxyStruct.evolve_group) { m_LinkBodyStruct.evolve_group = m_LinkProxyStruct.evolve_group; } if (m_LinkProxyStruct.evolve_level) { m_LinkBodyStruct.evolve_level = m_LinkProxyStruct.evolve_level; } if (m_LinkProxyStruct.ornament_icon) { m_LinkBodyStruct.ornament_icon = m_LinkProxyStruct.ornament_icon; } if (m_LinkProxyStruct.hash) { m_LinkBodyStruct.hash = m_LinkProxyStruct.hash; } if (m_TaskUse) { m_LinkBodyStruct.hash = 0x14505DC2; } m_LinkBody = StringFormat( "%1X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%05X" "%1X" "%04X" "%02X" "%05X" "%08X", (0x0F & m_LinkBodyStruct.action_id), (0x000FFFFF & m_LinkBodyStruct.item_id), (0x000FFFFF & m_LinkBodyStruct.augment_1), (0x000FFFFF & m_LinkBodyStruct.augment_2), (0x000FFFFF & m_LinkBodyStruct.augment_3), (0x000FFFFF & m_LinkBodyStruct.augment_4), (0x000FFFFF & m_LinkBodyStruct.augment_5), (0x000FFFFF & m_LinkBodyStruct.augment_6), (0x0F & m_LinkBodyStruct.is_evolving), (0x0000FFFF & m_LinkBodyStruct.evolve_group), (0xFF & m_LinkBodyStruct.evolve_level), (0x000FFFFF & m_LinkBodyStruct.ornament_icon), (0xFFFFFFFF & m_LinkBodyStruct.hash) ); } void EQ::SayLinkEngine::generate_text() { if (m_LinkProxyStruct.text != nullptr) { m_LinkText = m_LinkProxyStruct.text; return; } const EQ::ItemData *item_data = nullptr; switch (m_LinkType) { case saylink::SayLinkBlank: break; case saylink::SayLinkItemData: if (m_ItemData == nullptr) { break; } m_LinkText = m_ItemData->Name; return; case saylink::SayLinkLootItem: if (m_LootData == nullptr) { break; } item_data = database.GetItem(m_LootData->item_id); if (item_data == nullptr) { break; } m_LinkText = item_data->Name; return; case saylink::SayLinkItemInst: if (m_ItemInst == nullptr) { break; } if (m_ItemInst->GetItem() == nullptr) { break; } m_LinkText = m_ItemInst->GetItem()->Name; return; default: break; } m_LinkText = "null"; } std::string EQ::SayLinkEngine::GenerateQuestSaylink(std::string saylink_text, bool silent, std::string link_name) { uint32 saylink_id = 0; SaylinkRepository::Saylink saylink = GetOrSaveSaylink(saylink_text); if (saylink.id > 0) { saylink_id = saylink.id; } /** * Generate the actual link */ EQ::SayLinkEngine linker; linker.SetProxyItemID(SAYLINK_ITEM_ID); if (silent) { linker.SetProxyAugment2ID(saylink_id); } else { linker.SetProxyAugment1ID(saylink_id); } linker.SetProxyText(link_name.c_str()); return linker.GenerateLink(); } std::string EQ::SayLinkEngine::InjectSaylinksIfNotExist(const char *message) { std::string new_message = message; int link_index = 0; int saylink_index = 0; std::vector links = {}; std::vector saylinks = {}; int saylink_length = 50; std::string saylink_separator = "\u0012"; std::string saylink_partial = "00000"; LogSaylinkDetail("new_message pre pass 1 [{}]", new_message); // first pass - strip existing saylinks by putting placeholder anchors on them for (auto &saylink: Strings::Split(new_message, saylink_separator)) { if (!saylink.empty() && saylink.length() > saylink_length && saylink.find(saylink_partial) != std::string::npos) { saylinks.emplace_back(saylink); LogSaylinkDetail("Found saylink [{}]", saylink); // replace with anchor Strings::FindReplace( new_message, fmt::format("{}", saylink), fmt::format("", saylink_index) ); saylink_index++; } } LogSaylinkDetail("new_message post pass 1 [{}]", new_message); LogSaylinkDetail("saylink separator count [{}]", std::count(new_message.begin(), new_message.end(), '\u0012')); // loop through brackets until none exist if (new_message.find('[') != std::string::npos) { for (auto &b: Strings::Split(new_message, "[")) { if (!b.empty() && b.find(']') != std::string::npos) { std::vector right_split = Strings::Split(b, "]"); if (!right_split.empty()) { std::string bracket_message = Strings::Trim(right_split[0]); // we shouldn't see a saylink fragment here, ignore this bracket if (bracket_message.find(saylink_partial) != std::string::npos) { continue; } // skip where multiple saylinks are within brackets if (bracket_message.find(saylink_separator) != std::string::npos && std::count(bracket_message.begin(), bracket_message.end(), '\u0012') > 1) { continue; } // if non empty bracket contents if (!bracket_message.empty()) { LogSaylinkDetail("Found bracket_message [{}]", bracket_message); // already a saylink // todo: improve this later if (!bracket_message.empty() && (bracket_message.length() > saylink_length || bracket_message.find(saylink_separator) != std::string::npos)) { links.emplace_back(bracket_message); } else { links.emplace_back( EQ::SayLinkEngine::GenerateQuestSaylink( bracket_message, false, bracket_message ) ); } // replace with anchor Strings::FindReplace( new_message, fmt::format("[{}]", bracket_message), fmt::format("", link_index) ); link_index++; } } } } } LogSaylinkDetail("new_message post pass 2 (post brackets) [{}]", new_message); // strip any current delimiters of saylinks Strings::FindReplace(new_message, saylink_separator, ""); // pop links onto anchors link_index = 0; for (auto &link: links) { // strip any current delimiters of saylinks Strings::FindReplace(link, saylink_separator, ""); Strings::FindReplace( new_message, fmt::format("", link_index), fmt::format("[\u0012{}\u0012]", link) ); link_index++; } LogSaylinkDetail("new_message post pass 3 (post prelink anchor pop) [{}]", new_message); // pop links onto anchors saylink_index = 0; for (auto &link: saylinks) { // strip any current delimiters of saylinks Strings::FindReplace(link, saylink_separator, ""); // check to see if we did a double anchor pass (existing saylink that was also inside brackets) // this means we found a saylink and we're checking to see if we're already encoded before double encoding if (new_message.find(fmt::format("\u0012\u0012", saylink_index)) != std::string::npos) { LogSaylinkDetail("Found encoded saylink at index [{}]", saylink_index); Strings::FindReplace( new_message, fmt::format("\u0012\u0012", saylink_index), fmt::format("\u0012{}\u0012", link) ); saylink_index++; continue; } Strings::FindReplace( new_message, fmt::format("", saylink_index), fmt::format("\u0012{}\u0012", link) ); saylink_index++; } LogSaylinkDetail("new_message post pass 4 (post saylink anchor pop) [{}]", new_message); return new_message; } void EQ::SayLinkEngine::LoadCachedSaylinks() { auto saylinks = SaylinkRepository::GetWhere(database, "phrase not like '%#%'"); LogSaylink("Loaded [{}] saylinks into cache", saylinks.size()); g_cached_saylinks = saylinks; } SaylinkRepository::Saylink EQ::SayLinkEngine::GetOrSaveSaylink(std::string saylink_text) { // return cached saylink if exist if (!g_cached_saylinks.empty()) { for (auto &s: g_cached_saylinks) { if (s.phrase == saylink_text) { return s; } } } auto saylinks = SaylinkRepository::GetWhere( database, fmt::format("phrase = '{}'", Strings::Escape(saylink_text)) ); // return if found from the database if (!saylinks.empty()) { return saylinks[0]; } // if not found in database - save if (saylinks.empty()) { auto new_saylink = SaylinkRepository::NewEntity(); new_saylink.phrase = saylink_text; // persist to database auto link = SaylinkRepository::InsertOne(database, new_saylink); if (link.id > 0) { g_cached_saylinks.emplace_back(link); return link; } } return {}; } std::string Saylink::Create(std::string saylink_text, bool silent, std::string link_name) { return EQ::SayLinkEngine::GenerateQuestSaylink(saylink_text, silent, link_name); }