diff --git a/changelog.txt b/changelog.txt index 12a9fc76a..360f61082 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,12 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 03/04/2018 == +Uleat: Updated UCS versioning + - SoF and higher clients have a new opcode identified (update your *.conf files) + - Rework of previous ucs connectivity code + - Unrelated: Zone::weatherSend() now takes an optional parameter for singular updates (as in client entering zone) + -- prior to this, every client already in-zone received a weather update packet whenever a new client zoned in + == 02/18/2018 == Uleat: Bug reporting fix and overhaul. - Fixed bug reporting for SoD+ clients diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 89e0126a8..5078cca68 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -393,6 +393,7 @@ N(OP_PVPLeaderBoardReply), N(OP_PVPLeaderBoardRequest), N(OP_PVPStats), N(OP_QueryResponseThing), +N(OP_QueryUCSServerStatus), N(OP_RaidInvite), N(OP_RaidJoin), N(OP_RaidUpdate), diff --git a/common/emu_versions.h b/common/emu_versions.h index 9ab5becde..a5f3d0183 100644 --- a/common/emu_versions.h +++ b/common/emu_versions.h @@ -121,6 +121,20 @@ namespace EQEmu ClientVersion ConvertOfflinePCMobVersionToClientVersion(MobVersion mob_version); MobVersion ConvertClientVersionToOfflinePCMobVersion(ClientVersion client_version); + + enum UCSVersion : char { + ucsUnknown = '\0', + ucsClient62Chat = 'A', + ucsClient62Mail = 'a', + ucsTitaniumChat = 'B', + ucsTitaniumMail = 'b', + ucsSoFCombined = 'C', + ucsSoDCombined = 'D', + ucsUFCombined = 'E', + ucsRoFCombined = 'F', + ucsRoF2Combined = 'G' + }; + } /*versions*/ } /*EQEmu*/ diff --git a/common/ruletypes.h b/common/ruletypes.h index f6c9f569b..d80996184 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -614,9 +614,6 @@ RULE_INT(Chat, IntervalDurationMS, 60000) RULE_INT(Chat, KarmaUpdateIntervalMS, 1200000) RULE_INT(Chat, KarmaGlobalChatLimit, 72) //amount of karma you need to be able to talk in ooc/auction/chat below the level limit RULE_INT(Chat, GlobalChatLevelLimit, 8) //level limit you need to of reached to talk in ooc/auction/chat if your karma is too low. -RULE_INT(Chat, ExpireClientVersionRequests, 3) // time in seconds to keep current cv requests active -RULE_INT(Chat, ExpireClientVersionReplies, 30) // time in seconds to keep current cv replies active -RULE_INT(Chat, UCSBroadcastServerReadyDelay, 60) // time in seconds to delay broadcast `server ready` after start-up RULE_CATEGORY_END() RULE_CATEGORY(Merchant) diff --git a/common/servertalk.h b/common/servertalk.h index 82f39319f..e1d019fc4 100644 --- a/common/servertalk.h +++ b/common/servertalk.h @@ -190,9 +190,8 @@ #define ServerOP_ReloadLogs 0x4010 #define ServerOP_ReloadPerlExportSettings 0x4011 #define ServerOP_CZSetEntityVariableByClientName 0x4012 -#define ServerOP_UCSClientVersionRequest 0x4013 -#define ServerOP_UCSClientVersionReply 0x4014 -#define ServerOP_UCSBroadcastServerReady 0x4015 +#define ServerOP_UCSServerStatusRequest 0x4013 +#define ServerOP_UCSServerStatusReply 0x4014 /* Query Server OP Codes */ #define ServerOP_QSPlayerLogTrades 0x5010 #define ServerOP_QSPlayerLogHandins 0x5011 @@ -1281,18 +1280,15 @@ struct ServerRequestTellQueue_Struct { char name[64]; }; -struct UCSClientVersionRequest_Struct { - uint32 character_id; -}; - -struct UCSClientVersionReply_Struct { - uint32 character_id; - EQEmu::versions::ClientVersion client_version; -}; - -struct UCSBroadcastServerReady_Struct { - char chat_prefix[128]; - char mail_prefix[128]; +struct UCSServerStatus_Struct { + uint8 available; // non-zero=true, 0=false + union { + struct { + uint16 port; + uint16 unused; + }; + uint32 timestamp; + }; }; #pragma pack() diff --git a/common/shareddb.cpp b/common/shareddb.cpp index 4a82c32da..a4c6e54d1 100644 --- a/common/shareddb.cpp +++ b/common/shareddb.cpp @@ -127,6 +127,24 @@ void SharedDatabase::SetMailKey(int CharID, int IPAddress, int MailKey) } +std::string SharedDatabase::GetMailKey(int CharID, bool key_only) +{ + std::string query = StringFormat("SELECT `mailkey` FROM `character_data` WHERE `id`='%i' LIMIT 1", CharID); + auto results = QueryDatabase(query); + if (!results.Success()) { + Log(Logs::Detail, Logs::MySQLError, "Error retrieving mailkey from database: %s", results.ErrorMessage().c_str()); + return std::string(); + } + + auto row = results.begin(); + std::string mail_key = row[0]; + + if (mail_key.length() > 8 && key_only) + return mail_key.substr(8); + else + return mail_key; +} + bool SharedDatabase::SaveCursor(uint32 char_id, std::list::const_iterator &start, std::list::const_iterator &end) { // Delete cursor items diff --git a/common/shareddb.h b/common/shareddb.h index 171c2b8c6..bbd8dc9a0 100644 --- a/common/shareddb.h +++ b/common/shareddb.h @@ -73,6 +73,7 @@ class SharedDatabase : public Database bool GetCommandSettings(std::map>> &command_settings); uint32 GetTotalTimeEntitledOnAccount(uint32 AccountID); void SetMailKey(int CharID, int IPAddress, int MailKey); + std::string GetMailKey(int CharID, bool key_only = false); /* Character InventoryProfile diff --git a/ucs/clientlist.cpp b/ucs/clientlist.cpp index e5076a150..130914952 100644 --- a/ucs/clientlist.cpp +++ b/ucs/clientlist.cpp @@ -512,7 +512,6 @@ Client::Client(std::shared_ptr eqs) { AccountGrabUpdateTimer = new Timer(60000); //check every minute GlobalChatLimiterTimer = new Timer(RuleI(Chat, IntervalDurationMS)); - RawConnectionType = '\0'; TypeOfConnection = ConnectionTypeUnknown; ClientVersion_ = EQEmu::versions::ClientVersion::Unknown; @@ -645,10 +644,6 @@ void Clientlist::Process() database.GetAccountStatus((*it)); - // give world packet a chance to arrive and be processed - if ((*it)->GetCharID()) - ClientVersionRequestQueue[(*it)->GetCharID()] = (Timer::GetCurrentTime() + (RuleI(Chat, ExpireClientVersionRequests) * 1000)); - if ((*it)->GetConnectionType() == ConnectionTypeCombined) (*it)->SendFriends(); @@ -688,34 +683,8 @@ void Clientlist::Process() continue; } - // initiate request if we don't already have a reply from 'world enter' (ucs crash recovery protocol) - if ((*it)->GetClientVersion() == EQEmu::versions::ClientVersion::Unknown) { - if (!CheckForClientVersionReply((*it))) - RequestClientVersion((*it)->GetCharID()); - } - ++it; } - - // delete expired replies - auto repiter = ClientVersionReplyQueue.begin(); - while (repiter != ClientVersionReplyQueue.end()) { - if ((*repiter).second.second <= Timer::GetCurrentTime()) { - repiter = ClientVersionReplyQueue.erase(repiter); - continue; - } - ++repiter; - } - - // delete expired requests - auto reqiter = ClientVersionRequestQueue.begin(); - while (reqiter != ClientVersionRequestQueue.end()) { - if ((*reqiter).second <= Timer::GetCurrentTime()) { - reqiter = ClientVersionRequestQueue.erase(reqiter); - continue; - } - ++reqiter; - } } void Clientlist::ProcessOPMailCommand(Client *c, std::string CommandString) @@ -873,55 +842,6 @@ void Clientlist::ProcessOPMailCommand(Client *c, std::string CommandString) } } -void Clientlist::RequestClientVersion(uint32 character_id) { - if (!character_id) - return; - - if (ClientVersionRequestQueue.find(character_id) != ClientVersionRequestQueue.end()) { - if (ClientVersionRequestQueue[character_id] > Timer::GetCurrentTime()) - return; - } - - if (LogSys.log_settings[Logs::UCS_Server].is_category_enabled) { - Log(Logs::Detail, Logs::UCS_Server, "Requesting ClientVersion reply for character id: %u", - character_id); - } - ClientVersionRequestIDs.push_back(character_id); - ClientVersionRequestQueue[character_id] = (Timer::GetCurrentTime() + (RuleI(Chat, ExpireClientVersionRequests) * 1000)); -} - -bool Clientlist::QueueClientVersionReply(uint32 character_id, EQEmu::versions::ClientVersion client_version) { - if (!character_id) - return true; - if (client_version < EQEmu::versions::ClientVersion::Titanium || client_version > EQEmu::versions::ClientVersion::RoF2) - return false; - - if (LogSys.log_settings[Logs::UCS_Server].is_category_enabled) { - Log(Logs::Detail, Logs::UCS_Server, "Queueing ClientVersion %u reply for character id: %u", - static_cast(client_version), character_id); - } - ClientVersionReplyQueue[character_id] = cvt_pair(client_version, (Timer::GetCurrentTime() + (RuleI(Chat, ExpireClientVersionReplies) * 1000))); - ClientVersionRequestQueue.erase(character_id); - - return true; -} - -bool Clientlist::CheckForClientVersionReply(Client* c) { - if (!c) - return true; - if (ClientVersionReplyQueue.find(c->GetCharID()) == ClientVersionReplyQueue.end()) - return false; - - if (LogSys.log_settings[Logs::UCS_Server].is_category_enabled) { - Log(Logs::General, Logs::UCS_Server, "Registering ClientVersion %s for stream %s:%u", - EQEmu::versions::ClientVersionName(ClientVersionReplyQueue[c->GetCharID()].first), c->ClientStream->GetRemoteAddr().c_str(), c->ClientStream->GetRemotePort()); - } - c->SetClientVersion(ClientVersionReplyQueue[c->GetCharID()].first); - ClientVersionReplyQueue.erase(c->GetCharID()); - - return true; -} - void Clientlist::CloseAllConnections() { @@ -2214,39 +2134,64 @@ void Client::AccountUpdate() void Client::SetConnectionType(char c) { - RawConnectionType = c; - switch (c) { - case 'S': - { - TypeOfConnection = ConnectionTypeCombined; - Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (SoF/SoD)"); - break; - } - case 'U': - { - TypeOfConnection = ConnectionTypeCombined; - UnderfootOrLater = true; - Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (Underfoot+)"); - break; - } - case 'M': - { - TypeOfConnection = ConnectionTypeMail; - Log(Logs::Detail, Logs::UCS_Server, "Connection type is Mail (6.2 or Titanium client)"); - break; - } - case 'C': + case EQEmu::versions::ucsTitaniumChat: { TypeOfConnection = ConnectionTypeChat; - Log(Logs::Detail, Logs::UCS_Server, "Connection type is Chat (6.2 or Titanium client)"); + ClientVersion_ = EQEmu::versions::ClientVersion::Titanium; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Chat (Titanium)"); + break; + } + case EQEmu::versions::ucsTitaniumMail: + { + TypeOfConnection = ConnectionTypeMail; + ClientVersion_ = EQEmu::versions::ClientVersion::Titanium; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Mail (Titanium)"); + break; + } + case EQEmu::versions::ucsSoFCombined: + { + TypeOfConnection = ConnectionTypeCombined; + ClientVersion_ = EQEmu::versions::ClientVersion::SoF; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (SoF)"); + break; + } + case EQEmu::versions::ucsSoDCombined: + { + TypeOfConnection = ConnectionTypeCombined; + ClientVersion_ = EQEmu::versions::ClientVersion::SoD; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (SoD)"); + break; + } + case EQEmu::versions::ucsUFCombined: + { + TypeOfConnection = ConnectionTypeCombined; + ClientVersion_ = EQEmu::versions::ClientVersion::UF; + UnderfootOrLater = true; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (Underfoot)"); + break; + } + case EQEmu::versions::ucsRoFCombined: + { + TypeOfConnection = ConnectionTypeCombined; + ClientVersion_ = EQEmu::versions::ClientVersion::RoF; + UnderfootOrLater = true; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (RoF)"); + break; + } + case EQEmu::versions::ucsRoF2Combined: + { + TypeOfConnection = ConnectionTypeCombined; + ClientVersion_ = EQEmu::versions::ClientVersion::RoF2; + UnderfootOrLater = true; + Log(Logs::Detail, Logs::UCS_Server, "Connection type is Combined (RoF2)"); break; } default: { - RawConnectionType = '\0'; TypeOfConnection = ConnectionTypeUnknown; + ClientVersion_ = EQEmu::versions::ClientVersion::Unknown; Log(Logs::Detail, Logs::UCS_Server, "Connection type is unknown."); } } diff --git a/ucs/clientlist.h b/ucs/clientlist.h index f84bdc8a2..6021c5c0e 100644 --- a/ucs/clientlist.h +++ b/ucs/clientlist.h @@ -140,10 +140,8 @@ public: int GetMailBoxNumber() { return CurrentMailBox; } int GetMailBoxNumber(std::string CharacterName); - char GetRawConnectionType() { return RawConnectionType; } void SetConnectionType(char c); ConnectionType GetConnectionType() { return TypeOfConnection; } - void SetClientVersion(EQEmu::versions::ClientVersion client_version) { ClientVersion_ = client_version; } EQEmu::versions::ClientVersion GetClientVersion() { return ClientVersion_; } inline bool IsMailConnection() { return (TypeOfConnection == ConnectionTypeMail) || (TypeOfConnection == ConnectionTypeCombined); } @@ -173,7 +171,6 @@ private: int AttemptedMessages; bool ForceDisconnect; - char RawConnectionType; ConnectionType TypeOfConnection; EQEmu::versions::ClientVersion ClientVersion_; bool UnderfootOrLater; @@ -190,22 +187,11 @@ public: Client *IsCharacterOnline(std::string CharacterName); void ProcessOPMailCommand(Client *c, std::string CommandString); - std::list ClientVersionRequestIDs; - - void RequestClientVersion(uint32 character_id); - bool QueueClientVersionReply(uint32 character_id, EQEmu::versions::ClientVersion client_version); - bool CheckForClientVersionReply(Client* c); - private: - typedef std::pair cvt_pair; - EQ::Net::EQStreamManager *chatsf; std::list ClientChatConnections; - std::map ClientVersionRequestQueue; - std::map ClientVersionReplyQueue; - OpcodeManager *ChatOpMgr; }; diff --git a/ucs/ucs.cpp b/ucs/ucs.cpp index b59effd03..b9e7f41b4 100644 --- a/ucs/ucs.cpp +++ b/ucs/ucs.cpp @@ -140,19 +140,12 @@ int main() { worldserver = new WorldServer; - worldserver->ActivateBroadcastServerReadyTimer(); - while(RunLoops) { - // this triggers clients to 'reconnect' to ucs after server crash - if (worldserver->HasBroadcastServerReadyTimer()) - worldserver->ProcessBroadcastServerReady(); Timer::SetCurrentTime(); g_Clientlist->Process(); - worldserver->ProcessClientVersionRequests(g_Clientlist->ClientVersionRequestIDs); - if(ChannelListProcessTimer.Check()) ChannelList->Process(); diff --git a/ucs/worldserver.cpp b/ucs/worldserver.cpp index 48ec03dca..b3df5d99d 100644 --- a/ucs/worldserver.cpp +++ b/ucs/worldserver.cpp @@ -50,8 +50,6 @@ WorldServer::WorldServer() { m_connection.reset(new EQ::Net::ServertalkClient(Config->WorldIP, Config->WorldTCPPort, false, "UCS", Config->SharedKey)); m_connection->OnMessage(std::bind(&WorldServer::ProcessMessage, this, std::placeholders::_1, std::placeholders::_2)); - - m_bsr_timer = nullptr; } WorldServer::~WorldServer() @@ -140,59 +138,7 @@ void WorldServer::ProcessMessage(uint16 opcode, EQ::Net::Packet &p) std::string()); break; } - - case ServerOP_UCSClientVersionReply: - { - UCSClientVersionReply_Struct* cvr = (UCSClientVersionReply_Struct*)pack->pBuffer; - g_Clientlist->QueueClientVersionReply(cvr->character_id, cvr->client_version); - break; } - } -} - -void WorldServer::ProcessClientVersionRequests(std::list& id_list) { - UCSClientVersionRequest_Struct cvr; - EQ::Net::DynamicPacket dp_cvr; - for (auto iter : id_list) { - cvr.character_id = iter; - dp_cvr.PutData(0, &cvr, sizeof(cvr)); - m_connection->Send(ServerOP_UCSClientVersionRequest, dp_cvr); - } - id_list.clear(); -} - -void WorldServer::ProcessBroadcastServerReady() { - if (m_bsr_timer && (*m_bsr_timer) <= Timer::GetCurrentTime()) { - UCSBroadcastServerReady_Struct bsr; - memset(&bsr, 0, sizeof(UCSBroadcastServerReady_Struct)); - - sprintf(bsr.chat_prefix, "%s,%i,%s.", - Config->ChatHost.c_str(), - Config->ChatPort, - Config->ShortName.c_str() - ); - sprintf(bsr.mail_prefix, "%s,%i,%s.", - Config->ChatHost.c_str(), - Config->MailPort, - Config->ShortName.c_str() - ); - - EQ::Net::DynamicPacket dp_bsr; - dp_bsr.PutData(0, &bsr, sizeof(UCSBroadcastServerReady_Struct)); - m_connection->Send(ServerOP_UCSBroadcastServerReady, dp_bsr); - - safe_delete(m_bsr_timer); - } -} - -void WorldServer::ActivateBroadcastServerReadyTimer() { - safe_delete(m_bsr_timer); - m_bsr_timer = new uint32; - - // clients do not drop their connection to ucs immediately... - // it can take upwards of 60 seconds to process the drop - // and clients will not re-connect to ucs until that occurs - *m_bsr_timer = (Timer::GetCurrentTime() + (RuleI(Chat, UCSBroadcastServerReadyDelay) * 1000)); } void Client45ToServerSayLink(std::string& serverSayLink, const std::string& clientSayLink) { diff --git a/ucs/worldserver.h b/ucs/worldserver.h index 66ed0ec32..aad68e085 100644 --- a/ucs/worldserver.h +++ b/ucs/worldserver.h @@ -29,14 +29,7 @@ public: ~WorldServer(); void ProcessMessage(uint16 opcode, EQ::Net::Packet &); - void ProcessClientVersionRequests(std::list& id_list); - - void ProcessBroadcastServerReady(); - bool HasBroadcastServerReadyTimer() { return (m_bsr_timer != nullptr); } - void ActivateBroadcastServerReadyTimer(); - private: - uint32* m_bsr_timer; std::unique_ptr m_connection; }; diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 074d5751d..b348ea1ad 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -165,6 +165,7 @@ OP_GMNameChange=0x3077 # Was 0x4434 OP_GMLastName=0x4dd7 # Was 0x3077 # Misc Opcodes +OP_QueryUCSServerStatus=0x6964 OP_InspectRequest=0x23f1 OP_InspectAnswer=0x5794 OP_InspectMessageUpdate=0x3064 diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index dd58b3c11..9bd507bc1 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -164,6 +164,7 @@ OP_GMNameChange=0x035f OP_GMLastName=0x46ce # Misc Opcodes +OP_QueryUCSServerStatus=0x398f OP_InspectRequest=0x57bc OP_InspectAnswer=0x71ac OP_InspectMessageUpdate=0x4d25 diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 5913670ae..ab8267a8a 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -165,6 +165,7 @@ OP_GMKill=0x6685 # C OP_GMNameChange=0x565d # C OP_GMLastName=0x3563 # C +OP_QueryUCSServerStatus=0x4036 OP_InspectAnswer=0x4938 # C OP_Action2=0x7e4d # C OP_Damage? OP_BeginCast=0x0d5a # C diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index a96b4208a..c08edbca3 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -168,6 +168,7 @@ OP_GMKill=0x799c # C OP_GMNameChange=0x0f48 # C OP_GMLastName=0x7bfb # C +OP_QueryUCSServerStatus=0x4481 OP_InspectAnswer=0x0c2b # C OP_BeginCast=0x0d5a # C OP_ColoredText=0x71bf # C diff --git a/world/client.cpp b/world/client.cpp index d904e1d2d..d3b74ae3f 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -84,6 +84,7 @@ extern ClientList client_list; extern EQEmu::Random emu_random; extern uint32 numclients; extern volatile bool RunLoops; +extern volatile bool UCSServerAvailable_; Client::Client(EQStreamInterface* ieqs) : autobootup_timeout(RuleI(World, ZoneAutobootTimeoutMS)), @@ -890,53 +891,84 @@ bool Client::HandleEnterWorldPacket(const EQApplicationPacket *app) { } QueuePacket(outapp); safe_delete(outapp); - + + // set mailkey - used for duration of character session int MailKey = emu_random.Int(1, INT_MAX); database.SetMailKey(charid, GetIP(), MailKey); + if (UCSServerAvailable_) { + const WorldConfig *Config = WorldConfig::get(); + std::string buffer; - char ConnectionType; + EQEmu::versions::UCSVersion ConnectionType = EQEmu::versions::ucsUnknown; - if (m_ClientVersionBit & EQEmu::versions::bit_UFAndLater) - ConnectionType = 'U'; - else if (m_ClientVersionBit & EQEmu::versions::bit_SoFAndLater) - ConnectionType = 'S'; - else - ConnectionType = 'C'; + // chat server packet + switch (GetClientVersion()) { + case EQEmu::versions::ClientVersion::Titanium: + ConnectionType = EQEmu::versions::ucsTitaniumChat; + break; + case EQEmu::versions::ClientVersion::SoF: + ConnectionType = EQEmu::versions::ucsSoFCombined; + break; + case EQEmu::versions::ClientVersion::SoD: + ConnectionType = EQEmu::versions::ucsSoDCombined; + break; + case EQEmu::versions::ClientVersion::UF: + ConnectionType = EQEmu::versions::ucsUFCombined; + break; + case EQEmu::versions::ClientVersion::RoF: + ConnectionType = EQEmu::versions::ucsRoFCombined; + break; + case EQEmu::versions::ClientVersion::RoF2: + ConnectionType = EQEmu::versions::ucsRoF2Combined; + break; + default: + ConnectionType = EQEmu::versions::ucsUnknown; + break; + } - auto outapp2 = new EQApplicationPacket(OP_SetChatServer); - char buffer[112]; + buffer = StringFormat("%s,%i,%s.%s,%c%08X", + Config->ChatHost.c_str(), + Config->ChatPort, + Config->ShortName.c_str(), + GetCharName(), + ConnectionType, + MailKey + ); - const WorldConfig *Config = WorldConfig::get(); + outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1)); + memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); + outapp->pBuffer[buffer.length()] = '\0'; - sprintf(buffer,"%s,%i,%s.%s,%c%08X", - Config->ChatHost.c_str(), - Config->ChatPort, - Config->ShortName.c_str(), - this->GetCharName(), ConnectionType, MailKey - ); - outapp2->size=strlen(buffer)+1; - outapp2->pBuffer = new uchar[outapp2->size]; - memcpy(outapp2->pBuffer,buffer,outapp2->size); - QueuePacket(outapp2); - safe_delete(outapp2); + QueuePacket(outapp); + safe_delete(outapp); - outapp2 = new EQApplicationPacket(OP_SetChatServer2); + // mail server packet + switch (GetClientVersion()) { + case EQEmu::versions::ClientVersion::Titanium: + ConnectionType = EQEmu::versions::ucsTitaniumMail; + break; + default: + // retain value from previous switch + break; + } - if (m_ClientVersionBit & EQEmu::versions::bit_TitaniumAndEarlier) - ConnectionType = 'M'; + buffer = StringFormat("%s,%i,%s.%s,%c%08X", + Config->MailHost.c_str(), + Config->MailPort, + Config->ShortName.c_str(), + GetCharName(), + ConnectionType, + MailKey + ); - sprintf(buffer,"%s,%i,%s.%s,%c%08X", - Config->MailHost.c_str(), - Config->MailPort, - Config->ShortName.c_str(), - this->GetCharName(), ConnectionType, MailKey - ); - outapp2->size=strlen(buffer)+1; - outapp2->pBuffer = new uchar[outapp2->size]; - memcpy(outapp2->pBuffer,buffer,outapp2->size); - QueuePacket(outapp2); - safe_delete(outapp2); + outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1)); + memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); + outapp->pBuffer[buffer.length()] = '\0'; + + QueuePacket(outapp); + safe_delete(outapp); + } EnterWorld(); @@ -1215,14 +1247,6 @@ void Client::EnterWorld(bool TryBootup) { wtz->response = 0; zone_server->SendPacket(pack); delete pack; - - UCSClientVersionReply_Struct cvr; - cvr.character_id = GetCharID(); - cvr.client_version = GetClientVersion(); - EQ::Net::DynamicPacket dp_cvr; - dp_cvr.PutData(0, &cvr, sizeof(cvr)); - zone_server->HandleMessage(ServerOP_UCSClientVersionReply, dp_cvr); - } else { // if they havent seen character select screen, we can assume this is a zone // to zone movement, which should be preauthorized before they leave the previous zone diff --git a/world/net.cpp b/world/net.cpp index 033f470f7..c9a3f134c 100644 --- a/world/net.cpp +++ b/world/net.cpp @@ -462,6 +462,8 @@ int main(int argc, char** argv) { connection->Handle()->RemoteIP(), connection->Handle()->RemotePort(), connection->GetUUID()); UCSLink.SetConnection(connection); + + zoneserver_list.UpdateUCSServerAvailable(); }); server_connection->OnConnectionRemoved("UCS", [](std::shared_ptr connection) { @@ -469,6 +471,8 @@ int main(int argc, char** argv) { connection->GetUUID()); UCSLink.SetConnection(nullptr); + + zoneserver_list.UpdateUCSServerAvailable(false); }); server_connection->OnConnectionIdentified("WebInterface", [](std::shared_ptr connection) { diff --git a/world/ucs.cpp b/world/ucs.cpp index 76cba7e4b..b745005d4 100644 --- a/world/ucs.cpp +++ b/world/ucs.cpp @@ -2,14 +2,11 @@ #include "../common/eqemu_logsys.h" #include "ucs.h" #include "world_config.h" -#include "zonelist.h" #include "../common/misc_functions.h" #include "../common/md5.h" #include "../common/packet_dump.h" -extern ZSList zoneserver_list; - UCSConnection::UCSConnection() { Stream = 0; @@ -52,12 +49,6 @@ void UCSConnection::ProcessPacket(uint16 opcode, EQ::Net::Packet &p) Log(Logs::Detail, Logs::UCS_Server, "Got authentication from UCS when they are already authenticated."); break; } - case ServerOP_UCSBroadcastServerReady: - case ServerOP_UCSClientVersionRequest: - { - zoneserver_list.SendPacket(pack); - break; - } default: { Log(Logs::Detail, Logs::UCS_Server, "Unknown ServerOPcode from UCS 0x%04x, size %d", opcode, pack->size); diff --git a/world/zonelist.cpp b/world/zonelist.cpp index a1f0a60ff..d86f727f6 100644 --- a/world/zonelist.cpp +++ b/world/zonelist.cpp @@ -32,6 +32,7 @@ extern uint32 numzones; extern bool holdzones; extern EQEmu::Random emu_random; extern WebInterfaceList web_interface; +volatile bool UCSServerAvailable_ = false; void CatchSignal(int sig_num); ZSList::ZSList() @@ -669,6 +670,16 @@ void ZSList::GetZoneIDList(std::vector &zones) { } } +void ZSList::UpdateUCSServerAvailable(bool ucss_available) { + UCSServerAvailable_ = ucss_available; + auto outapp = new ServerPacket(ServerOP_UCSServerStatusReply, sizeof(UCSServerStatus_Struct)); + auto ucsss = (UCSServerStatus_Struct*)outapp->pBuffer; + ucsss->available = (ucss_available ? 1 : 0); + ucsss->timestamp = Timer::GetCurrentTime(); + SendPacket(outapp); + safe_delete(outapp); +} + void ZSList::WorldShutDown(uint32 time, uint32 interval) { if (time > 0) { diff --git a/world/zonelist.h b/world/zonelist.h index e152e5046..9911b87e0 100644 --- a/world/zonelist.h +++ b/world/zonelist.h @@ -54,7 +54,8 @@ public: Timer* reminder; void NextGroupIDs(uint32 &start, uint32 &end); void SendLSZones(); - uint16 GetAvailableZonePort(); + uint16 GetAvailableZonePort(); + void UpdateUCSServerAvailable(bool ucss_available = true); int GetZoneCount(); void GetZoneIDList(std::vector &zones); diff --git a/world/zoneserver.cpp b/world/zoneserver.cpp index 061ee6b72..e685b280e 100644 --- a/world/zoneserver.cpp +++ b/world/zoneserver.cpp @@ -41,6 +41,7 @@ extern GroupLFPList LFPGroupList; extern ZSList zoneserver_list; extern LoginServerList loginserverlist; extern volatile bool RunLoops; +extern volatile bool UCSServerAvailable_; extern AdventureManager adventure_manager; extern UCSConnection UCSLink; extern QueryServConnection QSLink; @@ -1262,12 +1263,29 @@ void ZoneServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) { break; } - case ServerOP_UCSClientVersionReply: case ServerOP_UCSMailMessage: { UCSLink.SendPacket(pack); break; } + + case ServerOP_UCSServerStatusRequest: + { + auto ucsss = (UCSServerStatus_Struct*)pack->pBuffer; + auto zs = zoneserver_list.FindByPort(ucsss->port); + if (!zs) + break; + + auto outapp = new ServerPacket(ServerOP_UCSServerStatusReply, sizeof(UCSServerStatus_Struct)); + ucsss = (UCSServerStatus_Struct*)outapp->pBuffer; + ucsss->available = (UCSServerAvailable_ ? 1 : 0); + ucsss->timestamp = Timer::GetCurrentTime(); + zs->SendPacket(outapp); + safe_delete(outapp); + + break; + } + case ServerOP_QSSendQuery: case ServerOP_QueryServGeneric: case ServerOP_Speech: diff --git a/zone/client_packet.cpp b/zone/client_packet.cpp index 843a2c282..fe8af2a98 100644 --- a/zone/client_packet.cpp +++ b/zone/client_packet.cpp @@ -316,6 +316,7 @@ void MapOpcodes() ConnectedOpcodes[OP_PurchaseLeadershipAA] = &Client::Handle_OP_PurchaseLeadershipAA; ConnectedOpcodes[OP_PVPLeaderBoardDetailsRequest] = &Client::Handle_OP_PVPLeaderBoardDetailsRequest; ConnectedOpcodes[OP_PVPLeaderBoardRequest] = &Client::Handle_OP_PVPLeaderBoardRequest; + ConnectedOpcodes[OP_QueryUCSServerStatus] = &Client::Handle_OP_QueryUCSServerStatus; ConnectedOpcodes[OP_RaidInvite] = &Client::Handle_OP_RaidCommand; ConnectedOpcodes[OP_RandomReq] = &Client::Handle_OP_RandomReq; ConnectedOpcodes[OP_ReadBook] = &Client::Handle_OP_ReadBook; @@ -792,7 +793,7 @@ void Client::CompleteConnect() } if (zone) - zone->weatherSend(); + zone->weatherSend(this); TotalKarma = database.GetKarma(AccountID()); SendDisciplineTimers(); @@ -11005,6 +11006,84 @@ void Client::Handle_OP_PVPLeaderBoardRequest(const EQApplicationPacket *app) safe_delete(outapp); } +void Client::Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app) +{ + if (zone->IsUCSServerAvailable()) { + EQApplicationPacket* outapp = nullptr; + std::string buffer; + + std::string MailKey = database.GetMailKey(CharacterID(), true); + EQEmu::versions::UCSVersion ConnectionType = EQEmu::versions::ucsUnknown; + + // chat server packet + switch (ClientVersion()) { + case EQEmu::versions::ClientVersion::Titanium: + ConnectionType = EQEmu::versions::ucsTitaniumChat; + break; + case EQEmu::versions::ClientVersion::SoF: + ConnectionType = EQEmu::versions::ucsSoFCombined; + break; + case EQEmu::versions::ClientVersion::SoD: + ConnectionType = EQEmu::versions::ucsSoDCombined; + break; + case EQEmu::versions::ClientVersion::UF: + ConnectionType = EQEmu::versions::ucsUFCombined; + break; + case EQEmu::versions::ClientVersion::RoF: + ConnectionType = EQEmu::versions::ucsRoFCombined; + break; + case EQEmu::versions::ClientVersion::RoF2: + ConnectionType = EQEmu::versions::ucsRoF2Combined; + break; + default: + ConnectionType = EQEmu::versions::ucsUnknown; + break; + } + + buffer = StringFormat("%s,%i,%s.%s,%c%s", + Config->ChatHost.c_str(), + Config->ChatPort, + Config->ShortName.c_str(), + GetName(), + ConnectionType, + MailKey.c_str() + ); + + outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1)); + memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); + outapp->pBuffer[buffer.length()] = '\0'; + + QueuePacket(outapp); + safe_delete(outapp); + + // mail server packet + switch (ClientVersion()) { + case EQEmu::versions::ClientVersion::Titanium: + ConnectionType = EQEmu::versions::ucsTitaniumMail; + break; + default: + // retain value from previous switch + break; + } + + buffer = StringFormat("%s,%i,%s.%s,%c%s", + Config->MailHost.c_str(), + Config->MailPort, + Config->ShortName.c_str(), + GetName(), + ConnectionType, + MailKey.c_str() + ); + + outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1)); + memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); + outapp->pBuffer[buffer.length()] = '\0'; + + QueuePacket(outapp); + safe_delete(outapp); + } +} + void Client::Handle_OP_RaidCommand(const EQApplicationPacket *app) { if (app->size < sizeof(RaidGeneral_Struct)) { diff --git a/zone/client_packet.h b/zone/client_packet.h index 1e7981ed9..4f9902599 100644 --- a/zone/client_packet.h +++ b/zone/client_packet.h @@ -228,6 +228,7 @@ void Handle_OP_PurchaseLeadershipAA(const EQApplicationPacket *app); void Handle_OP_PVPLeaderBoardDetailsRequest(const EQApplicationPacket *app); void Handle_OP_PVPLeaderBoardRequest(const EQApplicationPacket *app); + void Handle_OP_QueryUCSServerStatus(const EQApplicationPacket *app); void Handle_OP_RaidCommand(const EQApplicationPacket *app); void Handle_OP_RandomReq(const EQApplicationPacket *app); void Handle_OP_ReadBook(const EQApplicationPacket *app); diff --git a/zone/worldserver.cpp b/zone/worldserver.cpp index 09e5aa223..053bebacf 100644 --- a/zone/worldserver.cpp +++ b/zone/worldserver.cpp @@ -1814,69 +1814,10 @@ void WorldServer::HandleMessage(uint16 opcode, const EQ::Net::Packet &p) break; } - case ServerOP_UCSBroadcastServerReady: + case ServerOP_UCSServerStatusReply: { - UCSBroadcastServerReady_Struct* bsr = (UCSBroadcastServerReady_Struct*)pack->pBuffer; - EQApplicationPacket* outapp = nullptr; - std::string buffer; - - for (auto liter : entity_list.GetClientList()) { - auto c = liter.second; - if (!c) - continue; - - int MailKey = zone->random.Int(1, INT_MAX); - - database.SetMailKey(c->CharacterID(), c->GetIP(), MailKey); - - char ConnectionType; - - // chat server packet - if (c->ClientVersionBit() & EQEmu::versions::bit_UFAndLater) - ConnectionType = 'U'; - else if (c->ClientVersionBit() & EQEmu::versions::bit_SoFAndLater) - ConnectionType = 'S'; - else - ConnectionType = 'C'; - - buffer = bsr->chat_prefix; - buffer.append(StringFormat("%s,%c%08X", c->GetName(), ConnectionType, MailKey)); - - outapp = new EQApplicationPacket(OP_SetChatServer, (buffer.length() + 1)); - memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); - outapp->pBuffer[buffer.length()] = '\0'; - - c->QueuePacket(outapp); - safe_delete(outapp); - - // mail server packet - if (c->ClientVersionBit() & EQEmu::versions::bit_TitaniumAndEarlier) - ConnectionType = 'M'; - - buffer = bsr->mail_prefix; - buffer.append(StringFormat("%s,%c%08X", c->GetName(), ConnectionType, MailKey)); - - outapp = new EQApplicationPacket(OP_SetChatServer2, (buffer.length() + 1)); - memcpy(outapp->pBuffer, buffer.c_str(), buffer.length()); - outapp->pBuffer[buffer.length()] = '\0'; - - c->QueuePacket(outapp); - safe_delete(outapp); - } - break; - } - case ServerOP_UCSClientVersionRequest: - { - UCSClientVersionRequest_Struct* cvreq = (UCSClientVersionRequest_Struct*)pack->pBuffer; - Client* c = entity_list.GetClientByCharID(cvreq->character_id); - if (c) { - UCSClientVersionReply_Struct cvrep; - cvrep.character_id = c->CharacterID(); - cvrep.client_version = c->ClientVersion(); - EQ::Net::DynamicPacket dp_cvrep; - dp_cvrep.PutData(0, &cvrep, sizeof(cvrep)); - worldserver.m_connection->Send(ServerOP_UCSClientVersionReply, dp_cvrep); - } + auto ucsss = (UCSServerStatus_Struct*)pack->pBuffer; + zone->SetUCSServerAvailable((ucsss->available != 0), ucsss->timestamp); break; } case ServerOP_CZSetEntityVariableByNPCTypeID: diff --git a/zone/zone.cpp b/zone/zone.cpp index fce562c94..90d59e17e 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -146,6 +146,8 @@ bool Zone::Bootup(uint32 iZoneID, uint32 iInstanceID, bool iStaticZone) { UpdateWindowTitle(); zone->GetTimeSync(); + zone->RequestUCSServerStatus(); + /* Set Logging */ LogSys.StartFileLogs(StringFormat("%s_version_%u_inst_id_%u_port_%u", zone->GetShortName(), zone->GetInstanceVersion(), zone->GetInstanceID(), ZoneConfig::get()->ZonePort)); @@ -847,6 +849,9 @@ Zone::Zone(uint32 in_zoneid, uint32 in_instanceid, const char* in_short_name) GuildBanks = new GuildBankManager; else GuildBanks = nullptr; + + m_ucss_available = false; + m_last_ucss_update = 0; } Zone::~Zone() { @@ -1863,14 +1868,17 @@ bool ZoneDatabase::GetDecayTimes(npcDecayTimes_Struct* npcCorpseDecayTimes) { return true; } -void Zone::weatherSend() +void Zone::weatherSend(Client* client) { auto outapp = new EQApplicationPacket(OP_Weather, 8); if(zone_weather>0) outapp->pBuffer[0] = zone_weather-1; if(zone_weather>0) outapp->pBuffer[4] = zone->weather_intensity; - entity_list.QueueClients(0, outapp); + if (client) + client->QueuePacket(outapp); + else + entity_list.QueueClients(0, outapp); safe_delete(outapp); } @@ -2336,3 +2344,22 @@ void Zone::UpdateHotzone() is_hotzone = atoi(row[0]) == 0 ? false: true; } +void Zone::RequestUCSServerStatus() { + auto outapp = new ServerPacket(ServerOP_UCSServerStatusRequest, sizeof(UCSServerStatus_Struct)); + auto ucsss = (UCSServerStatus_Struct*)outapp->pBuffer; + ucsss->available = 0; + ucsss->port = Config->ZonePort; + ucsss->unused = 0; + worldserver.SendPacket(outapp); + safe_delete(outapp); +} + +void Zone::SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp) { + if (m_last_ucss_update == update_timestamp && m_ucss_available != ucss_available) { + m_ucss_available = false; + RequestUCSServerStatus(); + return; + } + if (m_last_ucss_update < update_timestamp) + m_ucss_available = ucss_available; +} diff --git a/zone/zone.h b/zone/zone.h index 158d414a4..bbd070305 100644 --- a/zone/zone.h +++ b/zone/zone.h @@ -224,7 +224,7 @@ public: void SetDate(uint16 year, uint8 month, uint8 day, uint8 hour, uint8 minute); void SetTime(uint8 hour, uint8 minute, bool update_world = true); - void weatherSend(); + void weatherSend(Client* client = nullptr); bool CanBind() const { return(can_bind); } bool IsCity() const { return(is_city); } bool CanDoCombat() const { return(can_combat); } @@ -275,6 +275,10 @@ public: inline void ShowZoneGlobalLoot(Client *to) { m_global_loot.ShowZoneGlobalLoot(to); } inline void ShowNPCGlobalLoot(Client *to, NPC *who) { m_global_loot.ShowNPCGlobalLoot(to, who); } + void RequestUCSServerStatus(); + void SetUCSServerAvailable(bool ucss_available, uint32 update_timestamp); + bool IsUCSServerAvailable() { return m_ucss_available; } + // random object that provides random values for the zone EQEmu::Random random; @@ -355,6 +359,9 @@ private: Timer hotzone_timer; GlobalLootManager m_global_loot; + + bool m_ucss_available; + uint32 m_last_ucss_update; }; #endif diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index d5db09a53..cef24a649 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1623,6 +1623,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla if (account_id <= 0) return false; + std::string mail_key = database.GetMailKey(character_id); + clock_t t = std::clock(); /* Function timer start */ std::string query = StringFormat( "REPLACE INTO `character_data` (" @@ -1719,7 +1721,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla " e_aa_effects, " " e_percent_to_aa, " " e_expended_aa_spent, " - " e_last_invsnapshot " + " e_last_invsnapshot, " + " mailkey " ") " "VALUES (" "%u," // id " id, " @@ -1815,7 +1818,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla "%u," // e_aa_effects "%u," // e_percent_to_aa "%u," // e_expended_aa_spent - "%u" // e_last_invsnapshot + "%u," // e_last_invsnapshot + "'%s'" // mailkey mail_key ")", character_id, // " id, " account_id, // " account_id, " @@ -1910,7 +1914,8 @@ bool ZoneDatabase::SaveCharacterData(uint32 character_id, uint32 account_id, Pla m_epp->aa_effects, m_epp->perAA, m_epp->expended_aa, - m_epp->last_invsnapshot_time + m_epp->last_invsnapshot_time, + mail_key.c_str() ); auto results = database.QueryDatabase(query); Log(Logs::General, Logs::None, "ZoneDatabase::SaveCharacterData %i, done... Took %f seconds", character_id, ((float)(std::clock() - t)) / CLOCKS_PER_SEC);