diff --git a/src/Client.cc b/src/Client.cc index fad4c7c7..177c7468 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -21,6 +21,43 @@ const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32; static std::atomic next_client_id(1); + +static uint8_t account_client_source_for_version(Version v) { + switch (v) { + case Version::DC_NTE: + case Version::DC_11_2000: + case Version::DC_V1: + case Version::DC_V2: + return 1; + + case Version::PC_PATCH: + case Version::PC_NTE: + case Version::PC_V2: + return 2; + + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_NTE: + case Version::GC_EP3: + return 3; + + case Version::XB_V3: + return 4; + + case Version::BB_PATCH: + case Version::BB_V4: + return 5; + + default: + throw std::logic_error("invalid game version for account client source"); + } +} + +static uint64_t account_client_source_key(uint32_t account_id, Version v) { + return (static_cast(account_id) << 8) | account_client_source_for_version(v); +} + + void Client::set_flags_for_version(Version version, int64_t sub_version) { this->set_flag(Flag::PROXY_CHAT_COMMANDS_ENABLED); @@ -459,14 +496,27 @@ void Client::set_login(std::shared_ptr login) { auto s = this->require_server_state(); if (!s->allow_same_account_concurrent_logins) { - auto it = s->client_for_account.find(login->account->account_id); - if ((it != s->client_for_account.end()) && (it->second.get() != this)) { - if (it->second->channel) { - it->second->channel->disconnect(); + if (s->allow_same_account_concurrent_logins_across_client_sources) { + uint64_t source_key = account_client_source_key(login->account->account_id, this->version()); + auto it = s->client_for_account_source.find(source_key); + if ((it != s->client_for_account_source.end()) && (it->second.get() != this)) { + if (it->second->channel) { + it->second->channel->disconnect(); + } + s->client_for_account_source.erase(it); } - s->client_for_account.erase(it); + s->client_for_account_source.emplace(source_key, this->shared_from_this()); + + } else { + auto it = s->client_for_account.find(login->account->account_id); + if ((it != s->client_for_account.end()) && (it->second.get() != this)) { + if (it->second->channel) { + it->second->channel->disconnect(); + } + s->client_for_account.erase(it); + } + s->client_for_account.emplace(this->login->account->account_id, this->shared_from_this()); } - s->client_for_account.emplace(this->login->account->account_id, this->shared_from_this()); } if (this->log.should_log(phosg::LogLevel::L_INFO)) { diff --git a/src/GameServer.cc b/src/GameServer.cc index 5ee3eda5..9c7ef494 100644 --- a/src/GameServer.cc +++ b/src/GameServer.cc @@ -1,4 +1,42 @@ #include "GameServer.hh" +#include + + +static uint8_t account_client_source_for_version(Version v) { + switch (v) { + case Version::DC_NTE: + case Version::DC_11_2000: + case Version::DC_V1: + case Version::DC_V2: + return 1; + + case Version::PC_PATCH: + case Version::PC_NTE: + case Version::PC_V2: + return 2; + + case Version::GC_NTE: + case Version::GC_V3: + case Version::GC_EP3_NTE: + case Version::GC_EP3: + return 3; + + case Version::XB_V3: + return 4; + + case Version::BB_PATCH: + case Version::BB_V4: + return 5; + + default: + throw std::logic_error("invalid game version for account client source"); + } +} + +static uint64_t account_client_source_key(uint32_t account_id, Version v) { + return (static_cast(account_id) << 8) | account_client_source_for_version(v); +} + #include #include @@ -201,5 +239,11 @@ asio::awaitable GameServer::destroy_client(std::shared_ptr c) { if ((it != this->state->client_for_account.end()) && (it->second == c)) { this->state->client_for_account.erase(it); } + + uint64_t source_key = account_client_source_key(c->login->account->account_id, c->version()); + auto source_it = this->state->client_for_account_source.find(source_key); + if ((source_it != this->state->client_for_account_source.end()) && (source_it->second == c)) { + this->state->client_for_account_source.erase(source_it); + } } } diff --git a/src/ServerState.cc b/src/ServerState.cc index 0384ede7..78cf9bf5 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -891,6 +891,7 @@ void ServerState::load_config_early() { this->allow_unregistered_users = this->config_json->get_bool("AllowUnregisteredUsers", false); this->allow_pc_nte = this->config_json->get_bool("AllowPCNTE", false); this->allow_same_account_concurrent_logins = this->config_json->get_bool("AllowSameAccountConcurrentLogins", false); + this->allow_same_account_concurrent_logins_across_client_sources = this->config_json->get_bool("AllowSameAccountConcurrentLoginsAcrossClientSources", false); this->use_temp_accounts_for_prototypes = this->config_json->get_bool("UseTemporaryAccountsForPrototypes", true); this->notify_server_for_max_level_achieved = this->config_json->get_bool("NotifyServerForMaxLevelAchieved", false); this->allowed_drop_modes_v1_v2_normal = this->config_json->get_int("AllowedDropModesV1V2Normal", 0x1F); diff --git a/src/ServerState.hh b/src/ServerState.hh index 4b5023b0..6c1e606d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -130,6 +130,7 @@ struct ServerState : public std::enable_shared_from_this { bool allow_pc_nte = false; bool use_temp_accounts_for_prototypes = true; bool allow_same_account_concurrent_logins = true; + bool allow_same_account_concurrent_logins_across_client_sources = false; std::array compatibility_groups = {}; bool enable_chat_commands = true; char chat_command_sentinel = '\0'; // 0 = default (@ on 11/2000; $ on all other versions) @@ -319,6 +320,7 @@ struct ServerState : public std::enable_shared_from_this { int32_t ep3_menu_song = -1; std::unordered_map> client_for_account; + std::unordered_map> client_for_account_source; std::map all_addresses; uint32_t local_address = 0; diff --git a/system/config.example.json b/system/config.example.json index 17184310..0ac09f4f 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -230,6 +230,7 @@ // only one login is allowed per account at a time; when a new session is opened, any existing sessions on the same // account are disconnected. "AllowSameAccountConcurrentLogins": true, + "AllowSameAccountConcurrentLoginsAcrossClientSources": false, // Whether to enable chat commands for all players. If this is true, all players will be able to use chat commands as // normal; if this is false, only players with the ALWAYS_ENABLE_CHAT_COMMANDS account flag will be able to use chat