Allow account concurrency across client sources

This commit is contained in:
2026-06-10 15:27:17 -04:00
parent 98e338046c
commit 79fa456365
5 changed files with 104 additions and 6 deletions
+56 -6
View File
@@ -21,6 +21,43 @@ const uint64_t CLIENT_CONFIG_MAGIC = 0x8399AC32;
static std::atomic<uint64_t> 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<uint64_t>(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> 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)) {
+44
View File
@@ -1,4 +1,42 @@
#include "GameServer.hh"
#include <stdexcept>
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<uint64_t>(account_id) << 8) | account_client_source_for_version(v);
}
#include <ctype.h>
#include <errno.h>
@@ -201,5 +239,11 @@ asio::awaitable<void> GameServer::destroy_client(std::shared_ptr<Client> 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);
}
}
}
+1
View File
@@ -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);
+2
View File
@@ -130,6 +130,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
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<uint16_t, NUM_VERSIONS> 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<ServerState> {
int32_t ep3_menu_song = -1;
std::unordered_map<uint32_t, std::shared_ptr<Client>> client_for_account;
std::unordered_map<uint64_t, std::shared_ptr<Client>> client_for_account_source;
std::map<std::string, uint32_t> all_addresses;
uint32_t local_address = 0;
+1
View File
@@ -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