diff --git a/common/ruletypes.h b/common/ruletypes.h index ad63226bd..b2de14baa 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -339,6 +339,7 @@ RULE_STRING(World, MOTD, "", "Server MOTD sent on login, change from empty to ha RULE_STRING(World, Rules, "", "Server Rules, change from empty to have this be used instead of variables table 'rules' value, lines are pipe (|) separated, example: A|B|C") RULE_BOOL(World, EnableAutoLogin, false, "Enables or disables auto login of characters, allowing people to log characters in directly from loginserver to ingame") RULE_BOOL(World, EnablePVPRegions, true, "Enables or disables PVP Regions automatically setting your PVP flag") +RULE_STRING(World, SupportedClients, "", "Comma-delimited list of clients to restrict to. Supported values are Titanium | SoF | SoD | UF | RoF | RoF2. Example: Titanium,RoF2") RULE_CATEGORY_END() RULE_CATEGORY(Zone) diff --git a/world/client.cpp b/world/client.cpp index bcb9100a3..a6f5cfb5f 100644 --- a/world/client.cpp +++ b/world/client.cpp @@ -526,9 +526,27 @@ bool Client::HandleSendLoginInfoPacket(const EQApplicationPacket *app) SendEnterWorld(cle->name()); SendPostEnterWorld(); if (!is_player_zoning) { - SendExpansionInfo(); - SendCharInfo(); - database.LoginIP(cle->AccountID(), long2ip(GetIP())); + const auto supported_clients = RuleS(World, SupportedClients); + bool skip_char_info = false; + if (!supported_clients.empty()) { + const std::string& name = EQ::versions::ClientVersionName(m_ClientVersion); + const auto& clients = Strings::Split(supported_clients, ","); + if (std::find(clients.begin(), clients.end(), name) == clients.end()) { + SendUnsupportedClientPacket( + fmt::format( + "Client Not In Supported List [{}]", + supported_clients + ) + ); + skip_char_info = true; + } + } + + if (!skip_char_info) { + SendExpansionInfo(); + SendCharInfo(); + database.LoginIP(cle->AccountID(), long2ip(GetIP())); + } } cle->SetIP(GetIP()); @@ -2453,3 +2471,34 @@ void Client::SendGuildTributeOptInToggle(const GuildTributeMemberToggle *in) QueuePacket(outapp); safe_delete(outapp); } + +void Client::SendUnsupportedClientPacket(const std::string& message) +{ + EQApplicationPacket packet(OP_SendCharInfo, sizeof(CharacterSelect_Struct) + sizeof(CharacterSelectEntry_Struct)); + + unsigned char* buff_ptr = packet.pBuffer; + auto cs = (CharacterSelect_Struct*) buff_ptr; + + cs->CharCount = 1; + cs->TotalChars = 1; + + buff_ptr += sizeof(CharacterSelect_Struct); + + auto e = (CharacterSelectEntry_Struct*) buff_ptr; + + strcpy(e->Name, message.c_str()); + + e->Race = Race::Human; + e->Class = Class::Warrior; + e->Level = 1; + e->ShroudClass = e->Class; + e->ShroudRace = e->Race; + e->Zone = std::numeric_limits::max(); + e->Instance = 0; + e->Gender = Gender::Male; + e->GoHome = 0; + e->Tutorial = 0; + e->Enabled = 0; + + QueuePacket(&packet); +} diff --git a/world/client.h b/world/client.h index b1f0afe8e..e88f8a94e 100644 --- a/world/client.h +++ b/world/client.h @@ -120,6 +120,7 @@ private: EQStreamInterface* eqs; bool CanTradeFVNoDropItem(); void RecordPossibleHack(const std::string& message); + void SendUnsupportedClientPacket(const std::string& message); }; bool CheckCharCreateInfoSoF(CharCreate_Struct *cc);