diff --git a/CMakeLists.txt b/CMakeLists.txt index 61c811f8..95f94120 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ add_custom_target( set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/Revision.cc + src/Account.cc src/AFSArchive.cc src/BattleParamsIndex.cc src/BMLArchive.cc @@ -97,7 +98,6 @@ set(SOURCES src/ItemParameterTable.cc src/Items.cc src/LevelTable.cc - src/License.cc src/Lobby.cc src/Loggers.cc src/Main.cc diff --git a/README.md b/README.md index 8546b7a0..238d98f0 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ See TODO.md for a list of known issues and future work I've curated, or go to th * [Client patch directories for PC and BB](#client-patch-directories) * [How to connect](#how-to-connect) * Features and configuration - * [User licenses](#user-licenses) + * [User accounts](#user-accounts) * [Installing quests](#installing-quests) * [Item tables and drop modes](#item-tables-and-drop-modes) * [Cross-version play](#cross-version-play) @@ -226,11 +226,22 @@ For GC clients, you'll have to use newserv's built-in DNS server or set up your # Server feature configuration -## User licenses +## User accounts By default, newserv does not require users to pre-register before playing; the server will instead automatically create an account the first time each player connects. These accounts have no special permissions. You can view, create, edit, and delete user accounts in the server's shell (run `help` in the shell to see how to do this). -If you're running the server not only for yourself, you may want to give your account elevated privileges. To do so, run `update-license SERIAL-NUMBER flags=root` (replacing SERIAL-NUMBER with your actual serial number). You can also use update-license to edit other parts of the license; for example, if you want your GC and BB characters to share an account so they will have the same Guild Card number and privilege flags, you can run `update-license SERIAL-NUMBER bb-username=USERNAME bb-password=PASSWORD`. +A license is a set of credentials that a player can use to log in. There are six types of licenses: +* *DC NTE licenses* consist of a 16-character serial number and 16-character access key. +* *DC licenses* consist of an 8-character hex serial number and an 8-character access key. +* *PC licenses* are the same format as DC licenses, but are used for PC v2. +* *GC licenses* consist of a 10-digit decimal serial number, a 12-character access key, and a password of up to 8 characters. +* *XB licenses* consist of a gamertag of up to 16 characters, a 16-character hex user ID, and a 16-character hex account ID. +* *BB licenses* consist of a username of up to 16 characters and a password of up to 16 characters. +Each account may have multiple licenses. To add a license to an account, use `add-license` in the shell. + +On BB, character data is scoped to the license, but system and Guild Card data is scoped to the account. That is, an account with multiple BB licenses can have more than 4 characters (up to 4 per license), but they will all share the same team membership and Guild Card lists. + +You may want to give your account elevated privileges. To do so, run `update-account ACCOUNT-ID flags=root` (replacing ACCOUNT-ID with your actual account-id). You can also use update-account to edit other parts of the account; see the help text for more information. ## Installing quests @@ -433,7 +444,7 @@ Some commands only work on the game server and not on the proxy server. The chat * `$where` (game server only): Shows your current floor number and coordinates. Mainly useful for debugging. * Debugging commands - * `$debug` (game server only): Enable or disable debug. You need the DEBUG permission in your user license to use this command. Enabling debug does a few things: + * `$debug` (game server only): Enable or disable debug. You need the DEBUG flag in your user account to use this command. Enabling debug does a few things: * You'll see in-game messages from the server when you take certain actions, like killing an enemy in BB. * You'll see the rare seed value and floor variations when you join a game. * You'll be placed into the highest available slot in lobbies and games instead of the lowest, unless you're joining a BB solo-mode game. diff --git a/TODO.md b/TODO.md index 391cddeb..79e7f188 100644 --- a/TODO.md +++ b/TODO.md @@ -14,7 +14,7 @@ ## Episode 3 - Enforce tournament deck restrictions (e.g. rank checks, No Assist option) when populating COMs at tournament start time -- Make `reload licenses` not vulnerable to online players' licenses overwriting licenses on disk somehow +- Make `reload accounts` not vulnerable to online players' accounts overwriting accounts on disk somehow - Implement ranks (based on total Meseta earned) - Make an AR code that gets rid of the SAMPLE overlays on NTE diff --git a/src/Account.cc b/src/Account.cc new file mode 100644 index 00000000..800915f3 --- /dev/null +++ b/src/Account.cc @@ -0,0 +1,989 @@ +#include +#include +#include + +#include +#include +#include +#include + +#include "Account.hh" + +using namespace std; + +shared_ptr DCNTELicense::from_json(const JSON& json) { + auto ret = make_shared(); + ret->serial_number = json.get_string("SerialNumber"); + ret->access_key = json.get_string("AccessKey"); + if (ret->serial_number.size() > 16) { + throw runtime_error("serial number is too long"); + } + if (ret->serial_number.empty()) { + throw runtime_error("serial number is too short"); + } + if (ret->access_key.size() > 16) { + throw runtime_error("access key is too long"); + } + if (ret->access_key.empty()) { + throw runtime_error("access key is too short"); + } + return ret; +} + +JSON DCNTELicense::json() const { + return JSON::dict({ + {"SerialNumber", this->serial_number}, + {"AccessKey", this->access_key}, + }); +} + +shared_ptr V1V2License::from_json(const JSON& json) { + auto ret = make_shared(); + ret->serial_number = json.get_int("SerialNumber"); + ret->access_key = json.get_string("AccessKey"); + if (ret->serial_number == 0) { + throw runtime_error("serial number is zero"); + } + if (ret->access_key.size() != 8) { + throw runtime_error("access key length is incorrect"); + } + return ret; +} + +JSON V1V2License::json() const { + return JSON::dict({ + {"SerialNumber", this->serial_number}, + {"AccessKey", this->access_key}, + }); +} + +shared_ptr GCLicense::from_json(const JSON& json) { + auto ret = make_shared(); + ret->serial_number = json.get_int("SerialNumber"); + ret->access_key = json.get_string("AccessKey"); + ret->password = json.get_string("Password"); + if (ret->serial_number == 0) { + throw runtime_error("serial number is zero"); + } + if (ret->access_key.size() != 12) { + throw runtime_error("access key length is incorrect"); + } + if (ret->password.empty()) { + throw runtime_error("password is too short"); + } + return ret; +} + +JSON GCLicense::json() const { + return JSON::dict({ + {"SerialNumber", this->serial_number}, + {"AccessKey", this->access_key}, + {"Password", this->password}, + }); +} + +shared_ptr XBLicense::from_json(const JSON& json) { + auto ret = make_shared(); + ret->gamertag = json.get_string("GamerTag"); + ret->user_id = json.get_int("UserID"); + ret->account_id = json.get_int("AccountID"); + if (ret->gamertag.empty()) { + throw runtime_error("gamertag is too short"); + } + if (ret->user_id == 0) { + throw runtime_error("user ID is zero"); + } + if (ret->account_id == 0) { + throw runtime_error("account ID is zero"); + } + return ret; +} + +JSON XBLicense::json() const { + return JSON::dict({ + {"GamerTag", this->gamertag}, + {"UserID", this->user_id}, + {"AccountID", this->account_id}, + }); +} + +shared_ptr BBLicense::from_json(const JSON& json) { + auto ret = make_shared(); + ret->username = json.get_string("UserName"); + ret->password = json.get_string("Password"); + if (ret->username.size() > 16) { + throw runtime_error("username is too long"); + } + if (ret->username.empty()) { + throw runtime_error("username is too short"); + } + if (ret->password.size() > 16) { + throw runtime_error("password is too long"); + } + if (ret->password.empty()) { + throw runtime_error("password is too short"); + } + return ret; +} + +JSON BBLicense::json() const { + return JSON::dict({ + {"UserName", this->username}, + {"Password", this->password}, + }); +} + +Account::Account(const JSON& json) + : account_id(0), + flags(0), + ban_end_time(0), + ep3_current_meseta(0), + ep3_total_meseta_earned(0), + bb_team_id(0) { + uint64_t format_version = 0; + try { + format_version = json.get_int("FormatVersion"); + } catch (const out_of_range&) { + } + + if (format_version == 0) { + // Original format - no account ID + this->account_id = json.get_int("SerialNumber"); + string access_key = json.get_string("AccessKey", ""); + string dc_nte_serial_number = json.get_string("DCNTESerialNumber", ""); + string dc_nte_access_key = json.get_string("DCNTEAccessKey", ""); + string gc_password = json.get_string("GCPassword", ""); + string xb_gamertag = json.get_string("XBGamerTag", ""); + uint64_t xb_user_id = json.get_int("XBUserID", 0); + uint64_t xb_account_id = json.get_int("XBAccountID", 0); + string bb_username = json.get_string("BBUsername", ""); + string bb_password = json.get_string("BBPassword", ""); + if (access_key.size() == 12) { + if (!gc_password.empty()) { + auto lic = make_shared(); + lic->serial_number = this->account_id; + lic->access_key = access_key; + lic->password = gc_password; + this->gc_licenses.emplace(lic->serial_number, lic); + } + } else if (access_key.size() >= 8) { + auto lic = make_shared(); + lic->serial_number = this->account_id; + lic->access_key = access_key.substr(0, 8); + this->dc_licenses.emplace(lic->serial_number, lic); + this->pc_licenses.emplace(lic->serial_number, lic); + } + if (!dc_nte_serial_number.empty() && !dc_nte_access_key.empty()) { + auto lic = make_shared(); + lic->serial_number = dc_nte_serial_number; + lic->access_key = dc_nte_access_key; + this->dc_nte_licenses.emplace(lic->serial_number, lic); + } + if (!xb_gamertag.empty() && xb_user_id && xb_account_id) { + auto lic = make_shared(); + lic->gamertag = xb_gamertag; + lic->user_id = xb_user_id; + lic->account_id = xb_account_id; + this->xb_licenses.emplace(lic->gamertag, lic); + } + if (!bb_username.empty() && !bb_password.empty()) { + auto lic = make_shared(); + lic->username = bb_username; + lic->password = bb_password; + this->bb_licenses.emplace(lic->username, lic); + } + } else { + // Second-gen format - with account ID; multiple credentials per version + this->account_id = json.get_int("AccountID"); + for (const auto& it : json.get_list("DCNTELicenses")) { + auto lic = DCNTELicense::from_json(*it); + this->dc_nte_licenses.emplace(lic->serial_number, lic); + } + for (const auto& it : json.get_list("DCLicenses")) { + auto lic = V1V2License::from_json(*it); + this->dc_licenses.emplace(lic->serial_number, lic); + } + for (const auto& it : json.get_list("PCLicenses")) { + auto lic = V1V2License::from_json(*it); + this->pc_licenses.emplace(lic->serial_number, lic); + } + for (const auto& it : json.get_list("GCLicenses")) { + auto lic = GCLicense::from_json(*it); + this->gc_licenses.emplace(lic->serial_number, lic); + } + for (const auto& it : json.get_list("XBLicenses")) { + auto lic = XBLicense::from_json(*it); + this->xb_licenses.emplace(lic->gamertag, lic); + } + for (const auto& it : json.get_list("BBLicenses")) { + auto lic = BBLicense::from_json(*it); + this->bb_licenses.emplace(lic->username, lic); + } + } + + this->flags = json.get_int("Flags", 0); + this->ban_end_time = json.get_int("BanEndTime", 0); + this->last_player_name = json.get_string("LastPlayerName", ""); + this->auto_reply_message = json.get_string("AutoReplyMessage", ""); + this->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0); + this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0); + this->bb_team_id = json.get_int("BBTeamID", 0); +} + +JSON Account::json() const { + JSON dc_nte_json = JSON::list(); + for (const auto& it : this->dc_nte_licenses) { + dc_nte_json.emplace_back(it.second->json()); + } + JSON dc_json = JSON::list(); + for (const auto& it : this->dc_licenses) { + dc_json.emplace_back(it.second->json()); + } + JSON pc_json = JSON::list(); + for (const auto& it : this->pc_licenses) { + pc_json.emplace_back(it.second->json()); + } + JSON gc_json = JSON::list(); + for (const auto& it : this->gc_licenses) { + gc_json.emplace_back(it.second->json()); + } + JSON xb_json = JSON::list(); + for (const auto& it : this->xb_licenses) { + xb_json.emplace_back(it.second->json()); + } + JSON bb_json = JSON::list(); + for (const auto& it : this->bb_licenses) { + bb_json.emplace_back(it.second->json()); + } + return JSON::dict({ + {"FormatVersion", 1}, + {"AccountID", this->account_id}, + {"DCNTELicenses", std::move(dc_nte_json)}, + {"DCLicenses", std::move(dc_json)}, + {"PCLicenses", std::move(pc_json)}, + {"GCLicenses", std::move(gc_json)}, + {"XBLicenses", std::move(xb_json)}, + {"BBLicenses", std::move(bb_json)}, + {"Flags", this->flags}, + {"BanEndTime", this->ban_end_time}, + {"LastPlayerName", this->last_player_name}, + {"AutoReplyMessage", this->auto_reply_message}, + {"Ep3CurrentMeseta", this->ep3_current_meseta}, + {"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned}, + {"BBTeamID", this->bb_team_id}, + }); +} + +void Account::print(FILE* stream) const { + fprintf(stream, "Account: %010" PRIu32 "/%08" PRIX32 "\n", this->account_id, this->account_id); + + if (this->flags) { + string flags_str = ""; + if (this->flags == static_cast(Flag::ROOT)) { + flags_str = "ROOT"; + } else if (this->flags == static_cast(Flag::ADMINISTRATOR)) { + flags_str = "ADMINISTRATOR"; + } else if (this->flags == static_cast(Flag::MODERATOR)) { + flags_str = "MODERATOR"; + } else { + if (this->flags & static_cast(Flag::KICK_USER)) { + flags_str += "KICK_USER,"; + } + if (this->flags & static_cast(Flag::BAN_USER)) { + flags_str += "BAN_USER,"; + } + if (this->flags & static_cast(Flag::SILENCE_USER)) { + flags_str += "SILENCE_USER,"; + } + if (this->flags & static_cast(Flag::CHANGE_EVENT)) { + flags_str += "CHANGE_EVENT,"; + } + if (this->flags & static_cast(Flag::ANNOUNCE)) { + flags_str += "ANNOUNCE,"; + } + if (this->flags & static_cast(Flag::FREE_JOIN_GAMES)) { + flags_str += "FREE_JOIN_GAMES,"; + } + if (this->flags & static_cast(Flag::DEBUG)) { + flags_str += "DEBUG,"; + } + if (this->flags & static_cast(Flag::CHEAT_ANYWHERE)) { + flags_str += "CHEAT_ANYWHERE,"; + } + if (this->flags & static_cast(Flag::DISABLE_QUEST_REQUIREMENTS)) { + flags_str += "ALWAYS_ENABLE_CHAT_COMMANDS,"; + } + if (this->flags & static_cast(Flag::IS_SHARED_ACCOUNT)) { + flags_str += "IS_SHARED_ACCOUNT,"; + } + } + if (flags_str.empty()) { + flags_str = "none"; + } else if (ends_with(flags_str, ",")) { + flags_str.pop_back(); + } + fprintf(stream, " Flags: %08" PRIX32 " (%s)\n", this->flags, flags_str.c_str()); + } + + if (this->ban_end_time) { + string time_str = format_time(this->ban_end_time); + fprintf(stream, " Banned until: %" PRIu64 " (%s)\n", this->ban_end_time, time_str.c_str()); + } + if (this->ep3_current_meseta || this->ep3_total_meseta_earned) { + fprintf(stream, " Episode 3 meseta: %" PRIu32 " (total earned: %" PRIu32 ")\n", this->ep3_current_meseta, this->ep3_total_meseta_earned); + } + if (!this->last_player_name.empty()) { + fprintf(stream, " Last player name: \"%s\"\n", this->last_player_name.c_str()); + } + if (!this->auto_reply_message.empty()) { + fprintf(stream, " Auto reply message: \"%s\"\n", this->auto_reply_message.c_str()); + } + if (this->bb_team_id) { + fprintf(stream, " BB team ID: %08" PRIX32 "\n", this->bb_team_id); + } + if (this->is_temporary) { + fprintf(stream, " Is temporary license: true\n"); + } + + for (const auto& it : this->dc_nte_licenses) { + fprintf(stream, " DC NTE license: serial_number=%s access_key=%s\n", + it.second->serial_number.c_str(), it.second->access_key.c_str()); + } + for (const auto& it : this->dc_licenses) { + fprintf(stream, " DC license: serial_number=%" PRIX32 " access_key=%s\n", + it.second->serial_number, it.second->access_key.c_str()); + } + for (const auto& it : this->pc_licenses) { + fprintf(stream, " PC license: serial_number=%" PRIX32 " access_key=%s\n", + it.second->serial_number, it.second->access_key.c_str()); + } + for (const auto& it : this->gc_licenses) { + fprintf(stream, " GC license: serial_number=%010" PRIu32 " access_key=%s password=%s\n", + it.second->serial_number, it.second->access_key.c_str(), it.second->password.c_str()); + } + for (const auto& it : this->xb_licenses) { + fprintf(stream, " XB license: gamertag=%s user_id=%016" PRIX64 " account_id=%016" PRIX64 "\n", + it.second->gamertag.c_str(), it.second->user_id, it.second->account_id); + } + for (const auto& it : this->bb_licenses) { + fprintf(stream, " BB license: username=%s password=%s\n", + it.second->username.c_str(), it.second->password.c_str()); + } +} + +void Account::save() const { + if (!this->is_temporary) { + auto json = this->json(); + string json_data = json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS); + string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->account_id); + save_file(filename, json_data); + } +} + +void Account::delete_file() const { + string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->account_id); + remove(filename.c_str()); +} + +size_t AccountIndex::count() const { + shared_lock g(this->lock); + return this->by_account_id.size(); +} + +shared_ptr AccountIndex::from_account_id(uint32_t account_id) const { + try { + shared_lock g(this->lock); + return this->by_account_id.at(account_id); + } catch (const out_of_range&) { + throw missing_account(); + } +} + +shared_ptr AccountIndex::from_dc_nte_credentials_locked(const string& serial_number, const string& access_key) { + auto login = make_shared(); + login->account = this->by_dc_nte_serial_number.at(serial_number); + login->dc_nte_license = login->account->dc_nte_licenses.at(serial_number); + if (login->dc_nte_license->access_key != access_key) { + throw incorrect_access_key(); + } + if (login->account->ban_end_time && (login->account->ban_end_time >= now())) { + throw invalid_argument("user is banned"); + } + return login; +} + +shared_ptr AccountIndex::from_dc_nte_credentials( + const string& serial_number, const string& access_key, bool allow_create) { + if (serial_number.empty()) { + throw no_username(); + } + + try { + shared_lock g(this->lock); + return this->from_dc_nte_credentials_locked(serial_number, access_key); + } catch (const out_of_range&) { + } + + unique_lock g(this->lock); + try { + return this->from_dc_nte_credentials_locked(serial_number, access_key); + } catch (const out_of_range&) { + } + + if (allow_create) { + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = fnv1a32(serial_number) & 0x7FFFFFFF; + auto lic = make_shared(); + lic->serial_number = serial_number; + lic->access_key = access_key; + login->account->dc_nte_licenses.emplace(lic->serial_number, lic); + login->dc_nte_license = lic; + this->add_locked(login->account); + return login; + } else { + throw missing_account(); + } +} + +shared_ptr AccountIndex::from_dc_credentials_locked( + uint32_t serial_number, const string& access_key, const string& character_name) { + auto login = make_shared(); + login->account = this->by_dc_serial_number.at(serial_number); + login->dc_license = login->account->dc_licenses.at(serial_number); + bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT); + if (!is_shared && (login->dc_license->access_key != access_key)) { + throw incorrect_access_key(); + } + if (login->account->ban_end_time && (login->account->ban_end_time >= now())) { + throw invalid_argument("user is banned"); + } + if (is_shared) { + login->account = this->create_temporary_account_for_shared_account(login->account, access_key + ":" + character_name); + } + return login; +} + +shared_ptr AccountIndex::from_dc_credentials( + uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) { + if (serial_number == 0) { + throw no_username(); + } + + try { + shared_lock g(this->lock); + return this->from_dc_credentials_locked(serial_number, access_key, character_name); + } catch (const out_of_range&) { + } + + unique_lock g(this->lock); + try { + return this->from_dc_credentials_locked(serial_number, access_key, character_name); + } catch (const out_of_range&) { + } + + if (allow_create) { + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = serial_number; + auto lic = make_shared(); + lic->serial_number = serial_number; + lic->access_key = access_key; + login->account->dc_licenses.emplace(lic->serial_number, lic); + login->dc_license = lic; + this->add_locked(login->account); + return login; + } else { + throw missing_account(); + } +} + +shared_ptr AccountIndex::from_pc_nte_credentials(uint32_t guild_card_number, bool allow_create) { + if (!allow_create) { + throw missing_account(); + } + if (guild_card_number == 0xFFFFFFFF) { + guild_card_number = random_object() & 0x7FFFFFFF; + } + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = guild_card_number; + login->account->is_temporary = true; + auto lic = make_shared(); + lic->serial_number = guild_card_number; + login->account->pc_licenses.emplace(lic->serial_number, lic); + login->pc_license = lic; + this->add(login->account); + return login; +} + +shared_ptr AccountIndex::from_pc_credentials_locked( + uint32_t serial_number, const string& access_key, const string& character_name) { + auto login = make_shared(); + login->account = this->by_pc_serial_number.at(serial_number); + login->pc_license = login->account->pc_licenses.at(serial_number); + bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT); + if (!is_shared && (login->pc_license->access_key != access_key)) { + throw incorrect_access_key(); + } + if (login->account->ban_end_time && (login->account->ban_end_time >= now())) { + throw invalid_argument("user is banned"); + } + if (is_shared) { + login->account = this->create_temporary_account_for_shared_account(login->account, access_key + ":" + character_name); + } + return login; +} + +shared_ptr AccountIndex::from_pc_credentials( + uint32_t serial_number, const string& access_key, const string& character_name, bool allow_create) { + if (serial_number == 0) { + throw no_username(); + } + + try { + shared_lock g(this->lock); + return this->from_pc_credentials_locked(serial_number, access_key, character_name); + } catch (const out_of_range&) { + } + + unique_lock g(this->lock); + try { + return this->from_pc_credentials_locked(serial_number, access_key, character_name); + } catch (const out_of_range&) { + } + + if (allow_create) { + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = serial_number; + auto lic = make_shared(); + lic->serial_number = serial_number; + lic->access_key = access_key; + login->account->pc_licenses.emplace(lic->serial_number, lic); + login->pc_license = lic; + this->add_locked(login->account); + return login; + } else { + throw missing_account(); + } +} + +shared_ptr AccountIndex::from_gc_credentials_locked( + uint32_t serial_number, const string& access_key, const string* password, const string& character_name) { + auto login = make_shared(); + login->account = this->by_gc_serial_number.at(serial_number); + login->gc_license = login->account->gc_licenses.at(serial_number); + bool is_shared = login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT); + if (!is_shared && (login->gc_license->access_key != access_key)) { + throw incorrect_access_key(); + } + if (password && (login->gc_license->password != *password)) { + throw incorrect_password(); + } + if (login->account->ban_end_time && (login->account->ban_end_time >= now())) { + throw invalid_argument("user is banned"); + } + if (is_shared) { + login->account = this->create_temporary_account_for_shared_account(login->account, access_key + ":" + character_name); + } + return login; +} + +shared_ptr AccountIndex::from_gc_credentials( + uint32_t serial_number, const string& access_key, const string* password, const string& character_name, bool allow_create) { + if (serial_number == 0) { + throw no_username(); + } + + try { + shared_lock g(this->lock); + return this->from_gc_credentials_locked(serial_number, access_key, password, character_name); + } catch (const out_of_range&) { + } + + unique_lock g(this->lock); + try { + return this->from_gc_credentials_locked(serial_number, access_key, password, character_name); + } catch (const out_of_range&) { + } + + if (allow_create && password) { + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = serial_number; + auto lic = make_shared(); + lic->serial_number = serial_number; + lic->access_key = access_key; + lic->password = *password; + login->account->gc_licenses.emplace(lic->serial_number, lic); + login->gc_license = lic; + this->add_locked(login->account); + return login; + } else { + throw missing_account(); + } +} + +shared_ptr AccountIndex::from_xb_credentials_locked(const string& gamertag, uint64_t user_id, uint64_t account_id) { + auto login = make_shared(); + login->account = this->by_xb_gamertag.at(gamertag); + login->xb_license = login->account->xb_licenses.at(gamertag); + if ((login->xb_license->user_id && (login->xb_license->user_id != user_id)) || + (login->xb_license->account_id && (login->xb_license->account_id != account_id))) { + throw incorrect_access_key(); + } + if (login->account->ban_end_time && (login->account->ban_end_time >= now())) { + throw invalid_argument("user is banned"); + } + return login; +} + +shared_ptr AccountIndex::from_xb_credentials( + const string& gamertag, uint64_t user_id, uint64_t account_id, bool allow_create) { + if (user_id == 0 || account_id == 0) { + throw incorrect_access_key(); + } + + try { + shared_lock g(this->lock); + return this->from_xb_credentials_locked(gamertag, user_id, account_id); + } catch (const out_of_range&) { + } + + unique_lock g(this->lock); + try { + return this->from_xb_credentials_locked(gamertag, user_id, account_id); + } catch (const out_of_range&) { + } + + if (allow_create) { + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = fnv1a32(gamertag) & 0x7FFFFFFF; + auto lic = make_shared(); + lic->gamertag = gamertag; + lic->user_id = user_id; + lic->account_id = account_id; + login->account->xb_licenses.emplace(lic->gamertag, lic); + login->xb_license = lic; + this->add_locked(login->account); + return login; + } else { + throw missing_account(); + } +} + +shared_ptr AccountIndex::from_bb_credentials_locked(const string& username, const string* password) { + auto login = make_shared(); + login->account = this->by_bb_username.at(username); + login->bb_license = login->account->bb_licenses.at(username); + if (password && (login->bb_license->password != *password)) { + throw incorrect_password(); + } + if (login->account->ban_end_time && (login->account->ban_end_time >= now())) { + throw invalid_argument("user is banned"); + } + return login; +} + +shared_ptr AccountIndex::from_bb_credentials(const string& username, const string* password, bool allow_create) { + if (username.empty() || (password && password->empty())) { + throw no_username(); + } + + try { + shared_lock g(this->lock); + return this->from_bb_credentials_locked(username, password); + } catch (const out_of_range&) { + } + + unique_lock g(this->lock); + try { + return this->from_bb_credentials_locked(username, password); + } catch (const out_of_range&) { + } + + if (allow_create && password) { + auto login = make_shared(); + login->account_was_created = true; + login->account = make_shared(); + login->account->account_id = fnv1a32(username) & 0x7FFFFFFF; + auto lic = make_shared(); + lic->username = username; + lic->password = *password; + login->account->bb_licenses.emplace(lic->username, lic); + login->bb_license = lic; + this->add_locked(login->account); + return login; + } else { + throw missing_account(); + } +} + +vector> AccountIndex::all() const { + shared_lock g(this->lock); + vector> ret; + ret.reserve(this->by_account_id.size()); + for (const auto& it : this->by_account_id) { + ret.emplace_back(it.second); + } + return ret; +} + +void AccountIndex::add(shared_ptr a) { + unique_lock g(this->lock); + this->add_locked(a); +} + +void AccountIndex::add_locked(shared_ptr a) { + if (this->force_all_temporary) { + a->is_temporary = true; + } + + for (const auto& it : a->dc_nte_licenses) { + if (this->by_dc_nte_serial_number.count(it.second->serial_number)) { + throw runtime_error("account already exists with this DC NTE serial number"); + } + } + for (const auto& it : a->dc_licenses) { + if (this->by_dc_serial_number.count(it.second->serial_number)) { + throw runtime_error("account already exists with this DC serial number"); + } + } + for (const auto& it : a->pc_licenses) { + if (this->by_pc_serial_number.count(it.second->serial_number)) { + throw runtime_error("account already exists with this PC NTE serial number"); + } + } + for (const auto& it : a->gc_licenses) { + if (this->by_gc_serial_number.count(it.second->serial_number)) { + throw runtime_error("account already exists with this GC serial number"); + } + } + for (const auto& it : a->xb_licenses) { + if (this->by_xb_gamertag.count(it.second->gamertag)) { + throw runtime_error("account already exists with this XB gamertag"); + } + } + for (const auto& it : a->bb_licenses) { + if (this->by_bb_username.count(it.second->username)) { + throw runtime_error("account already exists with this BB username"); + } + } + + while (this->by_account_id.count(a->account_id) || !a->account_id || (a->account_id == 0xFFFFFFFF)) { + a->account_id = (a->account_id + 1) & 0x7FFFFFFF; + } + + this->by_account_id[a->account_id] = a; + for (const auto& it : a->dc_nte_licenses) { + this->by_dc_nte_serial_number[it.second->serial_number] = a; + } + for (const auto& it : a->dc_licenses) { + this->by_dc_serial_number[it.second->serial_number] = a; + } + for (const auto& it : a->pc_licenses) { + this->by_pc_serial_number[it.second->serial_number] = a; + } + for (const auto& it : a->gc_licenses) { + this->by_gc_serial_number[it.second->serial_number] = a; + } + for (const auto& it : a->xb_licenses) { + this->by_xb_gamertag[it.second->gamertag] = a; + } + for (const auto& it : a->bb_licenses) { + this->by_bb_username[it.second->username] = a; + } +} + +void AccountIndex::remove(uint32_t account_id) { + unique_lock g(this->lock); + auto acc_it = this->by_account_id.find(account_id); + if (acc_it == this->by_account_id.end()) { + throw out_of_range("account does not exist"); + } + auto a = std::move(acc_it->second); + this->by_account_id.erase(acc_it); + + for (const auto& it : a->dc_nte_licenses) { + this->by_dc_nte_serial_number.erase(it.second->serial_number); + } + for (const auto& it : a->dc_licenses) { + this->by_dc_serial_number.erase(it.second->serial_number); + } + for (const auto& it : a->pc_licenses) { + this->by_pc_serial_number.erase(it.second->serial_number); + } + for (const auto& it : a->gc_licenses) { + this->by_gc_serial_number.erase(it.second->serial_number); + } + for (const auto& it : a->xb_licenses) { + this->by_xb_gamertag.erase(it.second->gamertag); + } + for (const auto& it : a->bb_licenses) { + this->by_bb_username.erase(it.second->username); + } +} + +void AccountIndex::add_dc_nte_license(shared_ptr account, shared_ptr license) { + if (!this->by_dc_nte_serial_number.emplace(license->serial_number, account).second) { + throw runtime_error("serial number already registered"); + } + if (!account->dc_nte_licenses.emplace(license->serial_number, license).second) { + this->by_dc_nte_serial_number.erase(license->serial_number); + throw logic_error("serial number registered in account but not in account index"); + } +} + +void AccountIndex::add_dc_license(shared_ptr account, shared_ptr license) { + if (!this->by_dc_serial_number.emplace(license->serial_number, account).second) { + throw runtime_error("serial number already registered"); + } + if (!account->dc_licenses.emplace(license->serial_number, license).second) { + this->by_dc_serial_number.erase(license->serial_number); + throw logic_error("serial number registered in account but not in account index"); + } +} + +void AccountIndex::add_pc_license(shared_ptr account, shared_ptr license) { + if (!this->by_pc_serial_number.emplace(license->serial_number, account).second) { + throw runtime_error("serial number already registered"); + } + if (!account->pc_licenses.emplace(license->serial_number, license).second) { + this->by_pc_serial_number.erase(license->serial_number); + throw logic_error("serial number registered in account but not in account index"); + } +} + +void AccountIndex::add_gc_license(shared_ptr account, shared_ptr license) { + if (!this->by_gc_serial_number.emplace(license->serial_number, account).second) { + throw runtime_error("serial number already registered"); + } + if (!account->gc_licenses.emplace(license->serial_number, license).second) { + this->by_gc_serial_number.erase(license->serial_number); + throw logic_error("serial number registered in account but not in account index"); + } +} + +void AccountIndex::add_xb_license(shared_ptr account, shared_ptr license) { + if (!this->by_xb_gamertag.emplace(license->gamertag, account).second) { + throw runtime_error("gamertag already registered"); + } + if (!account->xb_licenses.emplace(license->gamertag, license).second) { + this->by_xb_gamertag.erase(license->gamertag); + throw logic_error("gamertag registered in account but not in account index"); + } +} + +void AccountIndex::add_bb_license(shared_ptr account, shared_ptr license) { + if (!this->by_bb_username.emplace(license->username, account).second) { + throw runtime_error("username already registered"); + } + if (!account->bb_licenses.emplace(license->username, license).second) { + this->by_bb_username.erase(license->username); + throw logic_error("username registered in account but not in account index"); + } +} + +void AccountIndex::remove_dc_nte_license(shared_ptr account, const string& serial_number) { + auto it = account->dc_nte_licenses.find(serial_number); + if (it == account->dc_nte_licenses.end()) { + throw runtime_error("license not registered to account"); + } + if (!this->by_dc_nte_serial_number.erase(it->second->serial_number)) { + throw runtime_error("license registered in account but not in account index"); + } + account->dc_nte_licenses.erase(it); +} + +void AccountIndex::remove_dc_license(shared_ptr account, uint32_t serial_number) { + auto it = account->dc_licenses.find(serial_number); + if (it == account->dc_licenses.end()) { + throw runtime_error("license not registered to account"); + } + if (!this->by_dc_serial_number.erase(it->second->serial_number)) { + throw runtime_error("license registered in account but not in account index"); + } + account->dc_licenses.erase(it); +} + +void AccountIndex::remove_pc_license(shared_ptr account, uint32_t serial_number) { + auto it = account->pc_licenses.find(serial_number); + if (it == account->pc_licenses.end()) { + throw runtime_error("license not registered to account"); + } + if (!this->by_pc_serial_number.erase(it->second->serial_number)) { + throw runtime_error("license registered in account but not in account index"); + } + account->pc_licenses.erase(it); +} + +void AccountIndex::remove_gc_license(shared_ptr account, uint32_t serial_number) { + auto it = account->gc_licenses.find(serial_number); + if (it == account->gc_licenses.end()) { + throw runtime_error("license not registered to account"); + } + if (!this->by_gc_serial_number.erase(it->second->serial_number)) { + throw runtime_error("license registered in account but not in account index"); + } + account->gc_licenses.erase(it); +} + +void AccountIndex::remove_xb_license(shared_ptr account, const string& gamertag) { + auto it = account->xb_licenses.find(gamertag); + if (it == account->xb_licenses.end()) { + throw runtime_error("license not registered to account"); + } + if (!this->by_xb_gamertag.erase(it->second->gamertag)) { + throw runtime_error("license registered in account but not in account index"); + } + account->xb_licenses.erase(it); +} + +void AccountIndex::remove_bb_license(shared_ptr account, const string& username) { + auto it = account->bb_licenses.find(username); + if (it == account->bb_licenses.end()) { + throw runtime_error("license not registered to account"); + } + if (!this->by_bb_username.erase(it->second->username)) { + throw runtime_error("license registered in account but not in account index"); + } + account->bb_licenses.erase(it); +} + +shared_ptr AccountIndex::create_temporary_account_for_shared_account( + shared_ptr src_a, const string& variation_data) const { + auto ret = make_shared(*src_a); + ret->is_temporary = true; + ret->account_id = fnv1a32(&src_a->account_id, sizeof(src_a->account_id)); + ret->account_id = fnv1a32(variation_data, ret->account_id); + return ret; +} + +AccountIndex::AccountIndex(bool force_all_temporary) + : force_all_temporary(force_all_temporary) { + if (!this->force_all_temporary) { + if (!isdir("system/licenses")) { + mkdir("system/licenses", 0755); + } else { + for (const auto& item : list_directory("system/licenses")) { + if (ends_with(item, ".json")) { + try { + JSON json = JSON::parse(load_file("system/licenses/" + item)); + this->add(make_shared(json)); + } catch (const exception& e) { + log_error("Failed to index account %s", item.c_str()); + throw; + } + } + } + } + } +} diff --git a/src/Account.hh b/src/Account.hh new file mode 100644 index 00000000..9488ec67 --- /dev/null +++ b/src/Account.hh @@ -0,0 +1,260 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "Text.hh" + +class LicenseIndex; + +struct DCNTELicense { + std::string serial_number; + std::string access_key; + + static std::shared_ptr from_json(const JSON& json); + JSON json() const; +}; + +struct V1V2License { + uint32_t serial_number = 0; + std::string access_key; + + static std::shared_ptr from_json(const JSON& json); + JSON json() const; +}; + +struct GCLicense { + uint32_t serial_number = 0; + std::string access_key; + std::string password; + + static std::shared_ptr from_json(const JSON& json); + JSON json() const; +}; + +struct XBLicense { + std::string gamertag; + uint64_t user_id = 0; + uint64_t account_id = 0; + + static std::shared_ptr from_json(const JSON& json); + JSON json() const; +}; + +struct BBLicense { + std::string username; + std::string password; + + static std::shared_ptr from_json(const JSON& json); + JSON json() const; +}; + +struct Account { + enum class Flag : uint32_t { + // clang-format off + KICK_USER = 0x00000001, + BAN_USER = 0x00000002, + SILENCE_USER = 0x00000004, + CHANGE_EVENT = 0x00000010, + ANNOUNCE = 0x00000020, + FREE_JOIN_GAMES = 0x00000040, + DEBUG = 0x01000000, + CHEAT_ANYWHERE = 0x02000000, + DISABLE_QUEST_REQUIREMENTS = 0x04000000, + ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000, + MODERATOR = 0x00000007, + ADMINISTRATOR = 0x000000FF, + ROOT = 0x7FFFFFFF, + IS_SHARED_ACCOUNT = 0x80000000, + // NOTE: When adding or changing license flags, don't forget to change the + // documentation in the shell's help text. + UNUSED_BITS = 0x70FFFF00, + // clang-format on + }; + + // account_id is also the account's guild card number + uint32_t account_id = 0; + + uint32_t flags = 0; + uint64_t ban_end_time = 0; // 0 = not banned + std::string last_player_name; + std::string auto_reply_message; + + uint32_t ep3_current_meseta = 0; + uint32_t ep3_total_meseta_earned = 0; + + uint32_t bb_team_id = 0; + bool is_temporary = false; // If true, isn't saved to disk + + std::unordered_map> dc_nte_licenses; + std::unordered_map> dc_licenses; + std::unordered_map> pc_licenses; + std::unordered_map> gc_licenses; + std::unordered_map> xb_licenses; + std::unordered_map> bb_licenses; + + Account() = default; + explicit Account(const JSON& json); + virtual ~Account() = default; + + JSON json() const; + virtual void save() const; + virtual void delete_file() const; + + [[nodiscard]] inline bool check_flag(Flag flag) const { + return !!(this->flags & static_cast(flag)); + } + inline void set_flag(Flag flag) { + this->flags |= static_cast(flag); + } + inline void clear_flag(Flag flag) { + this->flags &= (~static_cast(flag)); + } + inline void toggle_flag(Flag flag) { + this->flags ^= static_cast(flag); + } + inline void replace_all_flags(Flag mask) { + this->flags = static_cast(mask); + } + + void print(FILE* stream) const; +}; + +struct Login { + bool account_was_created = false; + // This field will never be null + std::shared_ptr account; + // Exactly one of the following will be non-null, representing the license + // that the client logged in with + std::shared_ptr dc_nte_license; + std::shared_ptr dc_license; + std::shared_ptr pc_license; + std::shared_ptr gc_license; + std::shared_ptr xb_license; + std::shared_ptr bb_license; +}; + +class AccountIndex { +public: + class no_username : public std::invalid_argument { + public: + no_username() : invalid_argument("serial number is zero or username is missing") {} + }; + class incorrect_password : public std::invalid_argument { + public: + incorrect_password() : invalid_argument("incorrect password") {} + }; + class incorrect_access_key : public std::invalid_argument { + public: + incorrect_access_key() : invalid_argument("incorrect access key") {} + }; + class missing_account : public std::invalid_argument { + public: + missing_account() : invalid_argument("missing account") {} + }; + + explicit AccountIndex(bool force_all_temporary); + virtual ~AccountIndex() = default; + + std::shared_ptr create_account(bool is_temporary) const; + + size_t count() const; + std::vector> all() const; + + void add(std::shared_ptr a); + void remove(uint32_t serial_number); + + void add_dc_nte_license(std::shared_ptr account, std::shared_ptr license); + void add_dc_license(std::shared_ptr account, std::shared_ptr license); + void add_pc_license(std::shared_ptr account, std::shared_ptr license); + void add_gc_license(std::shared_ptr account, std::shared_ptr license); + void add_xb_license(std::shared_ptr account, std::shared_ptr license); + void add_bb_license(std::shared_ptr account, std::shared_ptr license); + void remove_dc_nte_license(std::shared_ptr account, const std::string& serial_number); + void remove_dc_license(std::shared_ptr account, uint32_t serial_number); + void remove_pc_license(std::shared_ptr account, uint32_t serial_number); + void remove_gc_license(std::shared_ptr account, uint32_t serial_number); + void remove_xb_license(std::shared_ptr account, const std::string& gamertag); + void remove_bb_license(std::shared_ptr account, const std::string& username); + + std::shared_ptr from_account_id(uint32_t account_id) const; + std::shared_ptr from_dc_nte_credentials( + const std::string& serial_number, + const std::string& access_key, + bool allow_create); + std::shared_ptr from_dc_credentials( + uint32_t serial_number, + const std::string& access_key, + const std::string& character_name, + bool allow_create); + std::shared_ptr from_pc_nte_credentials( + uint32_t guild_card_number, + bool allow_create); + std::shared_ptr from_pc_credentials( + uint32_t serial_number, + const std::string& access_key, + const std::string& character_name, + bool allow_create); + std::shared_ptr from_gc_credentials( + uint32_t serial_number, + const std::string& access_key, + const std::string* password, + const std::string& character_name, + bool allow_create); + std::shared_ptr from_xb_credentials( + const std::string& gamertag, + uint64_t user_id, + uint64_t account_id, + bool allow_create); + std::shared_ptr from_bb_credentials( + const std::string& username, + const std::string* password, + bool allow_create); + + std::shared_ptr create_temporary_account_for_shared_account( + std::shared_ptr src_a, const std::string& variation_data) const; + +protected: + bool force_all_temporary; + + // This class must be thread-safe because it's used by both the patch server + // and game server threads + mutable std::shared_mutex lock; + std::unordered_map> by_account_id; + std::unordered_map> by_dc_nte_serial_number; + std::unordered_map> by_dc_serial_number; + std::unordered_map> by_pc_serial_number; + std::unordered_map> by_gc_serial_number; + std::unordered_map> by_xb_gamertag; + std::unordered_map> by_bb_username; + + void add_locked(std::shared_ptr a); + + std::shared_ptr from_dc_nte_credentials_locked( + const std::string& serial_number, + const std::string& access_key); + std::shared_ptr from_dc_credentials_locked( + uint32_t serial_number, + const std::string& access_key, + const std::string& character_name); + std::shared_ptr from_pc_credentials_locked( + uint32_t serial_number, + const std::string& access_key, + const std::string& character_name); + std::shared_ptr from_gc_credentials_locked( + uint32_t serial_number, + const std::string& access_key, + const std::string* password, + const std::string& character_name); + std::shared_ptr from_xb_credentials_locked( + const std::string& gamertag, + uint64_t user_id, + uint64_t account_id); + std::shared_ptr from_bb_credentials_locked( + const std::string& username, + const std::string* password); +}; diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 9a60fe82..0ecec9bb 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -38,11 +38,11 @@ private: std::string user_msg; }; -static void check_license_flag(shared_ptr c, License::Flag flag) { - if (!c->license) { +static void check_account_flag(shared_ptr c, Account::Flag flag) { + if (!c->login) { throw precondition_failed("$C6You are not\nlogged in."); } - if (!c->license->check_flag(flag)) { + if (!c->login->account->check_flag(flag)) { throw precondition_failed("$C6You do not have\npermission to\nrun this command."); } } @@ -72,20 +72,22 @@ static void check_debug_enabled(shared_ptr c) { } static void check_cheats_enabled(shared_ptr l, shared_ptr c) { - if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && !c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) { + if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && + !c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) { throw precondition_failed("$C6This command can\nonly be used in\ncheat mode."); } } static void check_cheats_allowed(shared_ptr s, shared_ptr c) { - if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) && !c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) { + if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) && + !c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) { throw precondition_failed("$C6Cheats are disabled\non this server."); } } static void check_cheats_allowed(shared_ptr s, shared_ptr ses) { if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) && - (!ses->license || !ses->license->check_flag(License::Flag::CHEAT_ANYWHERE))) { + (!ses->login || !ses->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))) { throw precondition_failed("$C6Cheats are disabled\non this proxy."); } } @@ -261,19 +263,19 @@ static void proxy_command_lobby_info(shared_ptr ses, } static void server_command_ax(shared_ptr c, const std::string& args) { - check_license_flag(c, License::Flag::ANNOUNCE); + check_account_flag(c, Account::Flag::ANNOUNCE); ax_messages_log.info("%s", args.c_str()); } static void server_command_announce(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); - check_license_flag(c, License::Flag::ANNOUNCE); + check_account_flag(c, Account::Flag::ANNOUNCE); send_text_message(s, args); } static void server_command_announce_mail(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); - check_license_flag(c, License::Flag::ANNOUNCE); + check_account_flag(c, Account::Flag::ANNOUNCE); send_simple_mail(s, 0, s->name, args); } @@ -290,7 +292,7 @@ static void proxy_command_arrow(shared_ptr ses, cons } static void server_command_debug(shared_ptr c, const std::string&) { - check_license_flag(c, License::Flag::DEBUG); + check_account_flag(c, Account::Flag::DEBUG); c->config.toggle_flag(Client::Flag::DEBUG_ENABLED); send_text_message_printf(c, "Debug %s", (c->config.check_flag(Client::Flag::DEBUG_ENABLED) ? "enabled" : "disabled")); } @@ -678,7 +680,7 @@ static void server_command_show_material_counts(shared_ptr c, const std: } static void server_command_auction(shared_ptr c, const std::string&) { - check_license_flag(c, License::Flag::DEBUG); + check_account_flag(c, Account::Flag::DEBUG); auto l = c->require_lobby(); if (l->is_game() && l->is_ep3()) { G_InitiateCardAuction_Ep3_6xB5x42 cmd; @@ -898,7 +900,7 @@ static void server_command_cheat(shared_ptr c, const std::string&) { l->toggle_flag(Lobby::Flag::CHEATS_ENABLED); send_text_message_printf(l, "Cheat mode %s", l->check_flag(Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); - if (!c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) { + if (!c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) { size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty); if (l->min_level < default_min_level) { l->min_level = default_min_level; @@ -911,7 +913,7 @@ static void server_command_cheat(shared_ptr c, const std::string&) { static void server_command_lobby_event(shared_ptr c, const std::string& args) { auto l = c->require_lobby(); check_is_game(l, false); - check_license_flag(c, License::Flag::CHANGE_EVENT); + check_account_flag(c, Account::Flag::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { @@ -940,7 +942,7 @@ static void proxy_command_lobby_event(shared_ptr ses } static void server_command_lobby_event_all(shared_ptr c, const std::string& args) { - check_license_flag(c, License::Flag::CHANGE_EVENT); + check_account_flag(c, Account::Flag::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { @@ -992,13 +994,13 @@ static void proxy_command_lobby_type(shared_ptr ses, ses->config.override_lobby_number = new_type; } -static string file_path_for_recording(const std::string& args, uint32_t serial_number) { +static string file_path_for_recording(const std::string& args, uint32_t account_id) { for (char ch : args) { if (ch <= 0x20 || ch > 0x7E || ch == '/') { throw runtime_error("invalid recording name"); } } - return string_printf("system/ep3/battle-records/%010" PRIu32 "_%s.mzrd", serial_number, args.c_str()); + return string_printf("system/ep3/battle-records/%010" PRIu32 "_%s.mzrd", account_id, args.c_str()); } static void server_command_saverec(shared_ptr c, const std::string& args) { @@ -1006,7 +1008,7 @@ static void server_command_saverec(shared_ptr c, const std::string& args send_text_message(c, "$C4No finished\nrecording is\npresent"); return; } - string file_path = file_path_for_recording(args, c->license->serial_number); + string file_path = file_path_for_recording(args, c->login->account->account_id); string data = c->ep3_prev_battle_record->serialize(); save_file(file_path, data); send_text_message(c, "$C7Recording saved"); @@ -1023,7 +1025,7 @@ static void server_command_playrec(shared_ptr c, const std::string& args if (l->is_game() && l->battle_player) { l->battle_player->start(); } else if (!l->is_game()) { - string file_path = file_path_for_recording(args, c->license->serial_number); + string file_path = file_path_for_recording(args, c->login->account->account_id); auto s = c->require_server_state(); string filename = args; @@ -1060,11 +1062,11 @@ static void server_command_meseta(shared_ptr c, const std::string& args) check_debug_enabled(c); uint32_t amount = stoul(args, nullptr, 0); - c->license->ep3_current_meseta += amount; - c->license->ep3_total_meseta_earned += amount; - c->license->save(); + c->login->account->ep3_current_meseta += amount; + c->login->account->ep3_total_meseta_earned += amount; + c->login->account->save(); send_ep3_rank_update(c); - send_text_message_printf(c, "You now have\n$C6%" PRIu32 "$C7 Meseta", c->license->ep3_current_meseta); + send_text_message_printf(c, "You now have\n$C6%" PRIu32 "$C7 Meseta", c->login->account->ep3_current_meseta); } //////////////////////////////////////////////////////////////////////////////// @@ -1215,7 +1217,8 @@ static void server_command_min_level(shared_ptr c, const std::string& ar size_t new_min_level = stoull(args) - 1; auto s = c->require_server_state(); - bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || c->license->check_flag(License::Flag::CHEAT_ANYWHERE)); + bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || + c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)); if (!cheats_allowed) { size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty); if (new_min_level < default_min_level) { @@ -1257,7 +1260,7 @@ static void server_command_edit(shared_ptr c, const std::string& args) { } bool cheats_allowed = ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || - c->license->check_flag(License::Flag::CHEAT_ANYWHERE)); + c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)); string encoded_args = tolower(args); vector tokens = split(encoded_args, ' '); @@ -1401,7 +1404,6 @@ static void server_command_bbchar_savechar(shared_ptr c, const std::stri check_is_game(l, false); auto pending_export = make_unique(); - pending_export->is_bb_conversion = is_bb_conversion; if (is_bb_conversion) { vector tokens = split(args, ' '); @@ -1418,7 +1420,9 @@ static void server_command_bbchar_savechar(shared_ptr c, const std::stri } try { - c->pending_character_export->license = s->license_index->verify_bb(tokens[0].c_str(), tokens[1].c_str()); + auto dest_login = s->account_index->from_bb_credentials(tokens[0], &tokens[1], false); + pending_export->dest_account = dest_login->account; + pending_export->dest_bb_license = dest_login->bb_license; } catch (const exception& e) { send_text_message_printf(c, "$C6Login failed: %s", e.what()); return; @@ -1430,7 +1434,7 @@ static void server_command_bbchar_savechar(shared_ptr c, const std::stri send_text_message(c, "$C6Player index must\nbe in range 1-4"); return; } - pending_export->license = c->license; + pending_export->dest_account = c->login->account; } c->pending_character_export = std::move(pending_export); @@ -1445,8 +1449,8 @@ static void server_command_bbchar(shared_ptr c, const std::string& args) } static void server_command_savechar(shared_ptr c, const std::string& args) { - if (c->license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - send_text_message(c, "$C7This command cannot\nbe used on a shared\nserial number"); + if (c->login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT)) { + send_text_message(c, "$C7This command cannot\nbe used on a shared\naccount"); return; } server_command_bbchar_savechar(c, args, false); @@ -1457,8 +1461,8 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg send_text_message(c, "$C7This command can only\nbe used on v1 or v2"); return; } - if (c->license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - send_text_message(c, "$C7This command cannot\nbe used on a shared\nserial number"); + if (c->login->account->check_flag(Account::Flag::IS_SHARED_ACCOUNT)) { + send_text_message(c, "$C7This command cannot\nbe used on a shared\naccount"); return; } auto l = c->require_lobby(); @@ -1469,7 +1473,7 @@ static void server_command_loadchar(shared_ptr c, const std::string& arg send_text_message(c, "$C6Player index must\nbe in range 1-4"); return; } - c->load_backup_character(c->license->serial_number, index); + c->load_backup_character(c->login->account->account_id, index); auto s = c->require_server_state(); send_player_leave_notification(l, c->lobby_client_id); @@ -1496,8 +1500,8 @@ static string name_for_client(shared_ptr c) { return escape_player_name(player->disp.name.decode(player->inventory.language)); } - if (c->license.get()) { - return string_printf("SN:%" PRIu32, c->license->serial_number); + if (c->login) { + return string_printf("SN:%" PRIu32, c->login->account->account_id); } return "Player"; @@ -1506,16 +1510,16 @@ static string name_for_client(shared_ptr c) { static void server_command_silence(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); - check_license_flag(c, License::Flag::SILENCE_USER); + check_account_flag(c, Account::Flag::SILENCE_USER); auto target = s->find_client(&args); - if (!target->license) { + if (!target->login) { // this should be impossible, but I'll bet it's not actually send_text_message(c, "$C6Client not logged in"); return; } - if (target->license->check_flag(License::Flag::SILENCE_USER)) { + if (target->login->account->check_flag(Account::Flag::SILENCE_USER)) { send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } @@ -1529,16 +1533,16 @@ static void server_command_silence(shared_ptr c, const std::string& args static void server_command_kick(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); - check_license_flag(c, License::Flag::KICK_USER); + check_account_flag(c, Account::Flag::KICK_USER); auto target = s->find_client(&args); - if (!target->license) { + if (!target->login) { // This should be impossible, but I'll bet it's not actually send_text_message(c, "$C6Client not logged in"); return; } - if (target->license->check_flag(License::Flag::KICK_USER)) { + if (target->login->account->check_flag(Account::Flag::KICK_USER)) { send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } @@ -1552,7 +1556,7 @@ static void server_command_kick(shared_ptr c, const std::string& args) { static void server_command_ban(shared_ptr c, const std::string& args) { auto s = c->require_server_state(); auto l = c->require_lobby(); - check_license_flag(c, License::Flag::BAN_USER); + check_account_flag(c, Account::Flag::BAN_USER); size_t space_pos = args.find(' '); if (space_pos == string::npos) { @@ -1562,13 +1566,13 @@ static void server_command_ban(shared_ptr c, const std::string& args) { string identifier = args.substr(space_pos + 1); auto target = s->find_client(&identifier); - if (!target->license) { + if (!target->login) { // This should be impossible, but I'll bet it's not actually send_text_message(c, "$C6Client not logged in"); return; } - if (target->license->check_flag(License::Flag::BAN_USER)) { + if (target->login->account->check_flag(Account::Flag::BAN_USER)) { send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } @@ -1592,8 +1596,8 @@ static void server_command_ban(shared_ptr c, const std::string& args) { usecs *= 60 * 60 * 24 * 365; } - target->license->ban_end_time = now() + usecs; - target->license->save(); + target->login->account->ban_end_time = now() + usecs; + target->login->account->save(); send_message_box(target, "$C6You were banned by a moderator."); target->should_disconnect = true; string target_name = name_for_client(target); diff --git a/src/Client.cc b/src/Client.cc index 9a2cce95..8213e018 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -262,33 +262,16 @@ void Client::reschedule_ping_and_timeout_events() { event_add(this->idle_timeout_event.get(), &idle_tv); } -void Client::set_license(shared_ptr l) { - if (this->version() == Version::BB_V4) { - // Make sure bb_username is filename-safe - for (char ch : l->bb_username) { - if (!isalnum(ch) && (ch != '-') && (ch != '_') && (ch != '@')) { - throw runtime_error("invalid characters in username"); - } - } - } - this->license = l; -} - -void Client::convert_license_to_temporary_if_nte() { - // If the session is a prototype version and the license was created and we - // should use a temporary license instead, delete the permanent license and - // replace it with a temporary license. +void Client::convert_account_to_temporary_if_nte() { + // If the session is a prototype version and the account was created and we + // should use a temporary account instead, delete the permanent account and + // replace it with a temporary account. auto s = this->require_server_state(); - if (s->use_temp_licenses_for_prototypes && - this->config.check_flag(Client::Flag::LICENSE_WAS_CREATED) && - is_any_nte(this->version())) { - this->log.info("Client is a prototype version and the license was created during this session; converting permanent license to temporary license"); - s->license_index->remove(this->license->serial_number); - auto new_l = s->license_index->create_temporary_license(); - this->license->delete_file(); - *new_l = std::move(*this->license); - this->set_license(new_l); - this->config.clear_flag(Client::Flag::LICENSE_WAS_CREATED); + if (s->use_temp_accounts_for_prototypes && this->login->account_was_created && is_any_nte(this->version())) { + this->log.info("Client is a prototype version and the account was created during this session; converting permanent account to temporary account"); + this->login->account->is_temporary = true; + this->login->account->delete_file(); + this->login->account_was_created = false; } } @@ -309,29 +292,29 @@ shared_ptr Client::require_lobby() const { } shared_ptr Client::team() const { - if (!this->license) { - throw logic_error("Client::team called on client with no license"); + if (!this->login) { + throw logic_error("Client::team called on client with no account"); } - if (this->license->bb_team_id == 0) { + if (this->login->account->bb_team_id == 0) { return nullptr; } auto p = this->character(false); auto s = this->require_server_state(); - auto team = s->team_index->get_by_id(this->license->bb_team_id); + auto team = s->team_index->get_by_id(this->login->account->bb_team_id); if (!team) { - this->log.info("License contains a team ID, but the team does not exist; clearing team ID from license"); - this->license->bb_team_id = 0; - this->license->save(); + this->log.info("Account contains a team ID, but the team does not exist; clearing team ID from account"); + this->login->account->bb_team_id = 0; + this->login->account->save(); return nullptr; } - auto member_it = team->members.find(this->license->serial_number); + auto member_it = team->members.find(this->login->account->account_id); if (member_it == team->members.end()) { - this->log.info("License contains a team ID, but the team does not contain this member; clearing team ID from license"); - this->license->bb_team_id = 0; - this->license->save(); + this->log.info("Account contains a team ID, but the team does not contain this member; clearing team ID from account"); + this->login->account->bb_team_id = 0; + this->login->account->save(); return nullptr; } @@ -342,7 +325,7 @@ shared_ptr Client::team() const { string name = p->disp.name.decode(this->language()); if (m.name != name) { this->log.info("Updating player name in team config"); - s->team_index->update_member_name(this->license->serial_number, name); + s->team_index->update_member_name(this->login->account->account_id, name); } } @@ -356,7 +339,7 @@ bool Client::evaluate_quest_availability_expression( uint8_t difficulty, size_t num_players, bool v1_present) const { - if (this->license && this->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (this->login && this->login->account->check_flag(Account::Flag::DISABLE_QUEST_REQUIREMENTS)) { return true; } if (!expr) { @@ -403,10 +386,10 @@ bool Client::can_play_quest( } bool Client::can_use_chat_commands() const { - if (!this->license) { + if (!this->login) { return false; } - if (this->license->check_flag(License::Flag::ALWAYS_ENABLE_CHAT_COMMANDS)) { + if (this->login->account->check_flag(Account::Flag::ALWAYS_ENABLE_CHAT_COMMANDS)) { return true; } return this->require_server_state()->enable_chat_commands; @@ -622,10 +605,10 @@ string Client::system_filename() const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have system data"); } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } - return string_printf("system/players/system_%s.psosys", this->license->bb_username.c_str()); + return string_printf("system/players/system_%s.psosys", this->login->bb_license->username.c_str()); } string Client::character_filename(const std::string& bb_username, int8_t index) { @@ -638,55 +621,55 @@ string Client::character_filename(const std::string& bb_username, int8_t index) return string_printf("system/players/player_%s_%hhd.psochar", bb_username.c_str(), index); } -string Client::backup_character_filename(uint32_t serial_number, size_t index) { - return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", serial_number, index); +string Client::backup_character_filename(uint32_t account_id, size_t index) { + return string_printf("system/players/backup_player_%" PRIu32 "_%zu.psochar", account_id, index); } string Client::character_filename(int8_t index) const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have character data"); } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } - return this->character_filename(this->license->bb_username, (index < 0) ? this->bb_character_index : index); + return this->character_filename(this->login->bb_license->username, (index < 0) ? this->bb_character_index : index); } string Client::guild_card_filename() const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have character data"); } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } - return string_printf("system/players/guild_cards_%s.psocard", this->license->bb_username.c_str()); + return string_printf("system/players/guild_cards_%s.psocard", this->login->bb_license->username.c_str()); } string Client::shared_bank_filename() const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have character data"); } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } - return string_printf("system/players/shared_bank_%s.psobank", this->license->bb_username.c_str()); + return string_printf("system/players/shared_bank_%s.psobank", this->login->bb_license->username.c_str()); } string Client::legacy_account_filename() const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have character data"); } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } - return string_printf("system/players/account_%s.nsa", this->license->bb_username.c_str()); + return string_printf("system/players/account_%s.nsa", this->login->bb_license->username.c_str()); } string Client::legacy_player_filename() const { if (this->version() != Version::BB_V4) { throw logic_error("non-BB players do not have character data"); } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("client is not logged in"); } if (this->bb_character_index < 0) { @@ -694,7 +677,7 @@ string Client::legacy_player_filename() const { } return string_printf( "system/players/player_%s_%hhd.nsc", - this->license->bb_username.c_str(), + this->login->bb_license->username.c_str(), static_cast(this->bb_character_index + 1)); } @@ -714,7 +697,7 @@ void Client::load_all_files() { this->guild_card_data = make_shared(); return; } - if (!this->license) { + if (!this->login || !this->login->bb_license) { throw logic_error("cannot load BB player data until client is logged in"); } @@ -835,7 +818,7 @@ void Client::load_all_files() { this->character_data->quest_flags = nsc_data.quest_flags; this->character_data->death_count = nsc_data.death_count; this->character_data->bank = nsc_data.bank; - this->character_data->guild_card.guild_card_number = this->license->serial_number; + this->character_data->guild_card.guild_card_number = this->login->account->account_id; this->character_data->guild_card.name = nsc_data.disp.name; this->character_data->guild_card.description = nsc_data.guild_card_description; this->character_data->guild_card.present = 1; @@ -869,8 +852,8 @@ void Client::load_all_files() { if (this->character_data) { // Clear legacy play_time field this->character_data->disp.name.clear_after_bytes(0x18); - this->license->auto_reply_message = this->character_data->auto_reply.decode(); - this->license->save(); + this->login->account->auto_reply_message = this->character_data->auto_reply.decode(); + this->login->account->save(); this->last_play_time_update = now(); } } @@ -917,10 +900,10 @@ void Client::save_character_file( fwritex(f.get(), *character); fwritex(f.get(), *system); // TODO: Technically, we should write the actual team membership struct to the - // file here, but that would cause Client to depend on License, which + // file here, but that would cause Client to depend on Account, which // it currently does not. This data doesn't matter at all for correctness // within newserv, since it ignores this data entirely and instead generates - // the membership struct from the team ID in the License and the team's state. + // the membership struct from the team ID in the Account and the team's state. // So, writing correct data here would mostly be for compatibility with other // PSO servers. But if the other server is newserv, then this data would be // used anyway, and if it's not, then it would presumably have a different set @@ -960,8 +943,8 @@ void Client::save_guild_card_file() const { player_data_log.info("Saved Guild Card file %s", filename.c_str()); } -void Client::load_backup_character(uint32_t serial_number, size_t index) { - string filename = this->backup_character_filename(serial_number, index); +void Client::load_backup_character(uint32_t account_id, size_t index) { + string filename = this->backup_character_filename(account_id, index); auto f = fopen_unique(filename, "rb"); auto header = freadx(f.get()); if (header.size != 0x399C) { diff --git a/src/Client.hh b/src/Client.hh index ac1f24d5..516d6275 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -5,13 +5,13 @@ #include #include +#include "Account.hh" #include "Channel.hh" #include "CommandFormats.hh" #include "Episode3/BattleRecord.hh" #include "Episode3/Tournament.hh" #include "FileContentsCache.hh" #include "FunctionCompiler.hh" -#include "License.hh" #include "PSOEncryption.hh" #include "PSOProtocol.hh" #include "PatchFileIndex.hh" @@ -38,7 +38,6 @@ public: // Version-related flags CHECKED_FOR_DC_V1_PROTOTYPE = 0x0000000000000002, - LICENSE_WAS_CREATED = 0x0000000000000004, // Server-side only NO_D6_AFTER_LOBBY = 0x0000000000000100, NO_D6 = 0x0000000000000200, FORCE_ENGLISH_LANGUAGE_BB = 0x0000000000000400, @@ -181,8 +180,7 @@ public: uint64_t id; PrefixedLogger log; - // License & account - std::shared_ptr license; + std::shared_ptr login; // Network Channel channel; @@ -254,9 +252,9 @@ public: RecentSwitchFlags recent_switch_flags; // used for switch assist bool can_chat; struct PendingCharacterExport { - std::shared_ptr license; + std::shared_ptr dest_account; ssize_t character_index = -1; - bool is_bb_conversion = false; + std::shared_ptr dest_bb_license; // Only used for $bbchar; null for $savechar }; std::unique_ptr pending_character_export; std::deque> function_call_response_queue; @@ -283,8 +281,7 @@ public: return this->channel.language; } - void set_license(std::shared_ptr l); - void convert_license_to_temporary_if_nte(); + void convert_account_to_temporary_if_nte(); void sync_config(); @@ -355,7 +352,7 @@ public: std::string system_filename() const; static std::string character_filename(const std::string& bb_username, int8_t index); - static std::string backup_character_filename(uint32_t serial_number, size_t index); + static std::string backup_character_filename(uint32_t account_id, size_t index); std::string character_filename(int8_t index = -1) const; std::string guild_card_filename() const; std::string shared_bank_filename() const; @@ -373,7 +370,7 @@ public: void save_character_file(); void save_guild_card_file() const; - void load_backup_character(uint32_t serial_number, size_t index); + void load_backup_character(uint32_t account_id, size_t index); void save_and_unload_character(); PlayerBank& current_bank(); diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index d7c4d587..5d7871ac 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -770,7 +770,7 @@ struct C_WriteFileConfirmation_V3_BB_13_A7 { // NTE will respond with an 8B. Other non-V3 clients will respond with a 9A or // 9D. -// 18 (S->C): License verification result (PC/V3) +// 18 (S->C): Account verification result (PC/V3) // Behaves exactly the same as 9A (S->C). No arguments except header.flag. // 19 (S->C): Reconnect to different address @@ -1515,7 +1515,7 @@ struct C_LobbySelection_84 { // 86: Invalid command // 87: Invalid command -// 88 (C->S): License check (DC NTE only) +// 88 (C->S): Account check (DC NTE only) // The server should respond with an 88 command. struct C_Login_DCNTE_88 { @@ -1523,7 +1523,7 @@ struct C_Login_DCNTE_88 { pstring access_key; } __packed_ws__(C_Login_DCNTE_88, 0x22); -// 88 (S->C): License check result (DC NTE only) +// 88 (S->C): Account check result (DC NTE only) // No arguments except header.flag. // If header.flag is zero, client will respond with an 8A command. Otherwise, it // will respond with an 8B command. This is the same behavior as for the 18 @@ -1643,7 +1643,7 @@ struct C_LoginV1_DC_PC_V3_90 { // the receive handler. } __packed_ws__(C_LoginV1_DC_PC_V3_90, 0x22); -// 90 (S->C): License verification result (V3) +// 90 (S->C): Account verification result (V3) // Behaves exactly the same as 9A (S->C). No arguments except header.flag. // 91 (S->C): Start encryption at login server (legacy; non-BB only) @@ -1819,7 +1819,7 @@ struct C_Login_DC_PC_V3_9A { pstring email_address; } __packed_ws__(C_Login_DC_PC_V3_9A, 0xDC); -// 9A (S->C): License verification result +// 9A (S->C): Account verification result // Internal name: RcvPsoRegistCheckV2 // The result code is sent in the header.flag field. Result codes: // 00 = license ok (don't save to memory card; client responds with 9D/9E) @@ -1955,7 +1955,7 @@ struct C_LoginExtended_XB_9E : C_Login_XB_9E { struct C_LoginExtended_BB_9E { /* 0000 */ le_uint32_t player_tag = 0x00010000; - /* 0004 */ le_uint32_t guild_card_number = 0; // == serial_number when on newserv + /* 0004 */ le_uint32_t guild_card_number = 0; // == account_id when on newserv /* 0008 */ le_uint32_t sub_version = 0; /* 000C */ le_uint32_t unknown_a1 = 0; /* 0010 */ le_uint32_t unknown_a2 = 0; @@ -2740,7 +2740,7 @@ check_struct_size(S_InfoBoardEntry_BB_D8, 0x178); // DB (C->S): Verify license (V3/BB) // Server should respond with a 9A command. -struct C_VerifyLicense_V3_DB { +struct C_VerifyAccount_V3_DB { pstring unused; pstring serial_number; // On XB, this is the XBL gamertag pstring access_key; // On XB, this is the XBL user ID @@ -2749,11 +2749,11 @@ struct C_VerifyLicense_V3_DB { pstring serial_number2; // On XB, this is the XBL gamertag pstring access_key2; // On XB, this is the XBL user ID pstring password; // On XB, this contains "xbox-pso" -} __packed_ws__(C_VerifyLicense_V3_DB, 0xDC); +} __packed_ws__(C_VerifyAccount_V3_DB, 0xDC); // Note: This login pathway generally isn't used on BB (and isn't supported at // all during the data server phase). All current servers use 03/93 instead. -struct C_VerifyLicense_BB_DB { +struct C_VerifyAccount_BB_DB { // Note: These four fields are likely the same as those used in BB's 9E pstring unknown_a3; // Always blank? pstring unknown_a4; // == "?" @@ -2763,7 +2763,7 @@ struct C_VerifyLicense_BB_DB { pstring username; pstring password; pstring game_tag; // "psopc2" -} __packed_ws__(C_VerifyLicense_BB_DB, 0xD4); +} __packed_ws__(C_VerifyAccount_BB_DB, 0xD4); // DC: Set battle in progress flag (Episode 3) // No arguments except header.flag when sent by the client. When header.flag is diff --git a/src/Episode3/Tournament.cc b/src/Episode3/Tournament.cc index 6279c9f2..3489b820 100644 --- a/src/Episode3/Tournament.cc +++ b/src/Episode3/Tournament.cc @@ -9,18 +9,18 @@ using namespace std; namespace Episode3 { -Tournament::PlayerEntry::PlayerEntry(uint32_t serial_number, const string& player_name) - : serial_number(serial_number), +Tournament::PlayerEntry::PlayerEntry(uint32_t account_id, const string& player_name) + : account_id(account_id), player_name(player_name) {} Tournament::PlayerEntry::PlayerEntry(shared_ptr c) - : serial_number(c->license->serial_number), + : account_id(c->login->account->account_id), client(c), player_name(c->character()->disp.name.decode(c->language())) {} Tournament::PlayerEntry::PlayerEntry( shared_ptr com_deck) - : serial_number(0), + : account_id(0), com_deck(com_deck) {} bool Tournament::PlayerEntry::is_com() const { @@ -28,7 +28,7 @@ bool Tournament::PlayerEntry::is_com() const { } bool Tournament::PlayerEntry::is_human() const { - return (this->serial_number != 0); + return (this->account_id != 0); } Tournament::Team::Team( @@ -56,9 +56,9 @@ string Tournament::Team::str() const { for (const auto& player : this->players) { if (player.is_human()) { if (player.player_name.empty()) { - ret += string_printf(" %08" PRIX32, player.serial_number); + ret += string_printf(" %08" PRIX32, player.account_id); } else { - ret += string_printf(" %08" PRIX32 " (%s)", player.serial_number, player.player_name.c_str()); + ret += string_printf(" %08" PRIX32 " (%s)", player.account_id, player.player_name.c_str()); } } } @@ -81,12 +81,12 @@ void Tournament::Team::register_player( if (!tournament) { throw runtime_error("tournament has been deleted"); } - if (!tournament->all_player_serial_numbers.emplace(c->license->serial_number).second) { + if (!tournament->all_player_account_ids.emplace(c->login->account->account_id).second) { throw runtime_error("player already registered in same tournament"); } for (const auto& player : this->players) { - if (player.is_human() && (player.serial_number == c->license->serial_number)) { + if (player.is_human() && (player.account_id == c->login->account->account_id)) { throw logic_error("player already registered in team but not in tournament"); } } @@ -99,11 +99,11 @@ void Tournament::Team::register_player( } } -bool Tournament::Team::unregister_player(uint32_t serial_number) { +bool Tournament::Team::unregister_player(uint32_t account_id) { size_t index; for (index = 0; index < this->players.size(); index++) { if (this->players[index].is_human() && - (this->players[index].serial_number == serial_number)) { + (this->players[index].account_id == account_id)) { break; } } @@ -143,7 +143,7 @@ bool Tournament::Team::unregister_player(uint32_t serial_number) { // If the tournament has not started yet, just remove the player from the // team } else { - if (!tournament->all_player_serial_numbers.erase(serial_number)) { + if (!tournament->all_player_account_ids.erase(account_id)) { throw logic_error("player removed from team but not from tournament"); } } @@ -371,13 +371,13 @@ void Tournament::init() { team_index_to_rounds_cleared.emplace_back(team_json->get_int("num_rounds_cleared")); for (const auto& player_json : team_json->get_list("player_specs")) { if (player_json->is_list()) { - uint32_t serial_number = player_json->at(0).as_int(); - team->players.emplace_back(serial_number, player_json->at(1).as_string()); - this->all_player_serial_numbers.emplace(serial_number); + uint32_t account_id = player_json->at(0).as_int(); + team->players.emplace_back(account_id, player_json->at(1).as_string()); + this->all_player_account_ids.emplace(account_id); } else if (player_json->is_int()) { - uint32_t serial_number = player_json->as_int(); - team->players.emplace_back(serial_number); - this->all_player_serial_numbers.emplace(serial_number); + uint32_t account_id = player_json->as_int(); + team->players.emplace_back(account_id); + this->all_player_account_ids.emplace(account_id); } else if (player_json->is_string()) { team->players.emplace_back(this->com_deck_index->deck_for_name(player_json->as_string())); } else { @@ -511,9 +511,9 @@ JSON Tournament::json() const { for (const auto& player : team->players) { if (player.is_human()) { if (!player.player_name.empty()) { - players_list.emplace_back(JSON::list({player.serial_number, player.player_name})); + players_list.emplace_back(JSON::list({player.account_id, player.player_name})); } else { - players_list.emplace_back(player.serial_number); + players_list.emplace_back(player.account_id); } } else { players_list.emplace_back(player.com_deck->deck_name); @@ -571,25 +571,25 @@ shared_ptr Tournament::get_final_match() const { return this->final_match; } -shared_ptr Tournament::team_for_serial_number( - uint32_t serial_number) const { - if (!this->all_player_serial_numbers.count(serial_number)) { +shared_ptr Tournament::team_for_account_id( + uint32_t account_id) const { + if (!this->all_player_account_ids.count(account_id)) { return nullptr; } for (auto team : this->teams) { for (const auto& player : team->players) { - if (player.serial_number == serial_number) { + if (player.account_id == account_id) { return team->is_active ? team : nullptr; } } } - throw logic_error("serial number registered in tournament but not in any team"); + throw logic_error("account ID registered in tournament but not in any team"); } -const set& Tournament::get_all_player_serial_numbers() const { - return this->all_player_serial_numbers; +const set& Tournament::get_all_player_account_ids() const { + return this->all_player_account_ids; } void Tournament::start() { @@ -896,10 +896,10 @@ bool TournamentIndex::delete_tournament(const string& name) { return true; } -shared_ptr TournamentIndex::team_for_serial_number(uint32_t serial_number) const { +shared_ptr TournamentIndex::team_for_account_id(uint32_t account_id) const { for (const auto& it : this->name_to_tournament) { const auto& tourn = it.second; - auto team = tourn->team_for_serial_number(serial_number); + auto team = tourn->team_for_account_id(account_id); if (team) { return team; } @@ -912,11 +912,11 @@ void TournamentIndex::link_client(shared_ptr c) { return; } - auto team = this->team_for_serial_number(c->license->serial_number); + auto team = this->team_for_account_id(c->login->account->account_id); auto tourn = team ? team->tournament.lock() : nullptr; if (team && team->is_active && tourn) { for (auto& player : team->players) { - if (player.serial_number == c->license->serial_number) { + if (player.account_id == c->login->account->account_id) { c->ep3_tournament_team = team; player.client = c; if (c->version() == Version::GC_EP3) { diff --git a/src/Episode3/Tournament.hh b/src/Episode3/Tournament.hh index 239a3354..8fb62423 100644 --- a/src/Episode3/Tournament.hh +++ b/src/Episode3/Tournament.hh @@ -33,16 +33,16 @@ public: }; struct PlayerEntry { - // Invariant: (serial_number == 0) != (com_deck == nullptr) + // Invariant: (account_id == 0) != (com_deck == nullptr) // (that is, exactly one of the following must be valid) - uint32_t serial_number; + uint32_t account_id; std::shared_ptr com_deck; - // client is valid if serial_number is nonzero and the client is connected + // client is valid if account_id is nonzero and the client is connected std::weak_ptr client; std::string player_name; // Not used for COM decks - explicit PlayerEntry(uint32_t serial_number, const std::string& player_name = ""); + explicit PlayerEntry(uint32_t account_id, const std::string& player_name = ""); explicit PlayerEntry(std::shared_ptr c); explicit PlayerEntry(std::shared_ptr com_deck); @@ -73,7 +73,7 @@ public: std::shared_ptr c, const std::string& team_name, const std::string& password); - bool unregister_player(uint32_t serial_number); + bool unregister_player(uint32_t account_id); bool has_any_human_players() const; size_t num_human_players() const; @@ -152,8 +152,8 @@ public: std::shared_ptr get_winner_team() const; std::shared_ptr next_match_for_team(std::shared_ptr team) const; std::shared_ptr get_final_match() const; - std::shared_ptr team_for_serial_number(uint32_t serial_number) const; - const std::set& get_all_player_serial_numbers() const; + std::shared_ptr team_for_account_id(uint32_t account_id) const; + const std::set& get_all_player_account_ids() const; void start(); @@ -178,7 +178,7 @@ private: State current_state; uint32_t menu_item_id; - std::set all_player_serial_numbers; + std::set all_player_account_ids; std::unordered_set> pending_matches; // This vector contains all teams in the original starting order of the @@ -231,7 +231,7 @@ public: uint8_t flags); bool delete_tournament(const std::string& name); - std::shared_ptr team_for_serial_number(uint32_t serial_number) const; + std::shared_ptr team_for_account_id(uint32_t account_id) const; void link_client(std::shared_ptr c); void link_all_clients(std::shared_ptr s); diff --git a/src/HTTPServer.cc b/src/HTTPServer.cc index ffcb722f..7305e7dc 100644 --- a/src/HTTPServer.cc +++ b/src/HTTPServer.cc @@ -266,19 +266,48 @@ JSON HTTPServer::generate_client_config_json_st(const Client::Config& config) { return ret; } -JSON HTTPServer::generate_license_json_st(shared_ptr l) { - auto ret = JSON::dict({ - {"SerialNumber", l->serial_number}, - {"Flags", l->flags}, - {"Ep3CurrentMeseta", l->ep3_current_meseta}, - {"Ep3TotalMesetaEarned", l->ep3_total_meseta_earned}, - {"BBTeamID", l->bb_team_id}, +JSON HTTPServer::generate_account_json_st(shared_ptr a) { + auto dc_nte_licenses_json = JSON::list(); + for (const auto& it : a->dc_nte_licenses) { + dc_nte_licenses_json.emplace_back(it.first); + } + auto dc_licenses_json = JSON::list(); + for (const auto& it : a->dc_licenses) { + dc_licenses_json.emplace_back(it.first); + } + auto pc_licenses_json = JSON::list(); + for (const auto& it : a->pc_licenses) { + pc_licenses_json.emplace_back(it.first); + } + auto gc_licenses_json = JSON::list(); + for (const auto& it : a->gc_licenses) { + gc_licenses_json.emplace_back(it.first); + } + auto xb_licenses_json = JSON::list(); + for (const auto& it : a->xb_licenses) { + xb_licenses_json.emplace_back(it.first); + } + auto bb_licenses_json = JSON::list(); + for (const auto& it : a->bb_licenses) { + bb_licenses_json.emplace_back(it.first); + } + return JSON::dict({ + {"AccountID", a->account_id}, + {"Flags", a->flags}, + {"BanEndTime", a->ban_end_time ? a->ban_end_time : JSON(nullptr)}, + {"Ep3CurrentMeseta", a->ep3_current_meseta}, + {"Ep3TotalMesetaEarned", a->ep3_total_meseta_earned}, + {"BBTeamID", a->bb_team_id}, + {"LastPlayerName", a->last_player_name}, + {"AutoReplyMessage", a->auto_reply_message}, + {"IsTemporary", a->is_temporary}, + {"DCNTELicenses", std::move(dc_nte_licenses_json)}, + {"DCLicenses", std::move(dc_licenses_json)}, + {"PCLicenses", std::move(pc_licenses_json)}, + {"GCLicenses", std::move(gc_licenses_json)}, + {"XBLicenses", std::move(xb_licenses_json)}, + {"BBLicenses", std::move(bb_licenses_json)}, }); - ret.emplace("BanEndTime", l->ban_end_time ? l->ban_end_time : JSON(nullptr)); - ret.emplace("XBGamertag", !l->xb_gamertag.empty() ? l->xb_gamertag : JSON(nullptr)); - ret.emplace("XBUserID", l->xb_user_id ? l->xb_user_id : JSON(nullptr)); - ret.emplace("BBUsername", !l->bb_username.empty() ? l->bb_username : JSON(nullptr)); - return ret; }; JSON HTTPServer::generate_game_client_json_st(shared_ptr c, shared_ptr item_name_index) { @@ -294,7 +323,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr c, shared {"LocationFloor", c->floor}, {"CanChat", c->can_chat}, }); - ret.emplace("license", c->license ? HTTPServer::generate_license_json_st(c->license) : JSON(nullptr)); + ret.emplace("Account", c->login ? HTTPServer::generate_account_json_st(c->login->account) : JSON(nullptr)); auto l = c->lobby.lock(); if (l) { ret.emplace("LobbyID", l->lobby_id); @@ -498,7 +527,7 @@ JSON HTTPServer::generate_proxy_client_json_st(shared_ptrlicense ? HTTPServer::generate_license_json_st(ses->license) : JSON(nullptr)); + ret.emplace("Account", ses->login ? HTTPServer::generate_account_json_st(ses->login->account) : JSON(nullptr)); return ret; } @@ -766,7 +795,7 @@ JSON HTTPServer::generate_summary_json() const { auto l = c->lobby.lock(); clients_json.emplace_back(JSON::dict({ {"ID", c->id}, - {"SerialNumber", c->license ? c->license->serial_number : JSON(nullptr)}, + {"AccountID", c->login ? c->login->account->account_id : JSON(nullptr)}, {"Name", p ? p->disp.name.decode(it.second->language()) : JSON(nullptr)}, {"Version", name_for_enum(it.second->version())}, {"Language", name_for_language_code(it.second->language())}, @@ -780,7 +809,7 @@ JSON HTTPServer::generate_summary_json() const { auto proxy_clients_json = JSON::list(); for (const auto& it : this->state->proxy_server->all_sessions()) { proxy_clients_json.emplace_back(JSON::dict({ - {"SerialNumber", it.second->license ? it.second->license->serial_number : JSON(nullptr)}, + {"AccountID", it.second->login ? it.second->login->account->account_id : JSON(nullptr)}, {"Name", it.second->character_name}, {"Version", name_for_enum(it.second->version())}, {"Language", name_for_language_code(it.second->language())}, diff --git a/src/HTTPServer.hh b/src/HTTPServer.hh index f7c9c410..e2cd56cf 100644 --- a/src/HTTPServer.hh +++ b/src/HTTPServer.hh @@ -58,7 +58,7 @@ protected: static JSON generate_quest_json_st(std::shared_ptr q); static JSON generate_client_config_json_st(const Client::Config& config); - static JSON generate_license_json_st(std::shared_ptr l); + static JSON generate_account_json_st(std::shared_ptr a); static JSON generate_game_client_json_st(std::shared_ptr c, std::shared_ptr item_name_index); static JSON generate_proxy_client_json_st(std::shared_ptr ses); static JSON generate_lobby_json_st(std::shared_ptr l, std::shared_ptr item_name_index); diff --git a/src/License.cc b/src/License.cc deleted file mode 100644 index c90b2013..00000000 --- a/src/License.cc +++ /dev/null @@ -1,392 +0,0 @@ -#include -#include -#include - -#include -#include -#include - -#include "License.hh" - -using namespace std; - -License::License(const JSON& json) - : serial_number(0), - flags(0), - ban_end_time(0), - ep3_current_meseta(0), - ep3_total_meseta_earned(0), - bb_team_id(0) { - this->serial_number = json.get_int("SerialNumber"); - this->access_key = json.get_string("AccessKey", ""); - this->dc_nte_serial_number = json.get_string("DCNTESerialNumber", ""); - this->dc_nte_access_key = json.get_string("DCNTEAccessKey", ""); - this->gc_password = json.get_string("GCPassword", ""); - this->xb_gamertag = json.get_string("XBGamerTag", ""); - this->xb_user_id = json.get_int("XBUserID", 0); - this->xb_account_id = json.get_int("XBAccountID", 0); - this->bb_username = json.get_string("BBUsername", ""); - this->bb_password = json.get_string("BBPassword", ""); - this->flags = json.get_int("Flags", 0); - this->ban_end_time = json.get_int("BanEndTime", 0); - this->last_player_name = json.get_string("LastPlayerName", ""); - this->auto_reply_message = json.get_string("AutoReplyMessage", ""); - this->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0); - this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0); - this->bb_team_id = json.get_int("BBTeamID", 0); -} - -JSON License::json() const { - return JSON::dict({ - {"SerialNumber", this->serial_number}, - {"AccessKey", this->access_key}, - {"DCNTESerialNumber", this->dc_nte_serial_number}, - {"DCNTEAccessKey", this->dc_nte_access_key}, - {"GCPassword", this->gc_password}, - {"XBGamerTag", this->xb_gamertag}, - {"XBUserID", this->xb_user_id}, - {"XBAccountID", this->xb_account_id}, - {"BBUsername", this->bb_username}, - {"BBPassword", this->bb_password}, - {"Flags", this->flags}, - {"BanEndTime", this->ban_end_time}, - {"LastPlayerName", this->last_player_name}, - {"AutoReplyMessage", this->auto_reply_message}, - {"Ep3CurrentMeseta", this->ep3_current_meseta}, - {"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned}, - {"BBTeamID", this->bb_team_id}, - }); -} - -void License::save() const {} -void License::delete_file() const {} - -string License::str() const { - vector tokens; - tokens.emplace_back(string_printf("serial_number=%010" PRIu32 "/%08" PRIX32, this->serial_number, this->serial_number)); - if (!this->access_key.empty()) { - tokens.emplace_back("access_key=" + this->access_key); - } - if (!this->dc_nte_serial_number.empty()) { - tokens.emplace_back("dc_nte_serial_number=" + this->dc_nte_serial_number); - } - if (!this->dc_nte_access_key.empty()) { - tokens.emplace_back("dc_nte_access_key=" + this->dc_nte_access_key); - } - if (!this->gc_password.empty()) { - tokens.emplace_back("gc_password=" + this->gc_password); - } - if (!this->xb_gamertag.empty()) { - tokens.emplace_back("xb_gamertag=" + this->xb_gamertag); - } - if (this->xb_user_id != 0) { - tokens.emplace_back(string_printf("xb_user_id=%016" PRIX64, this->xb_user_id)); - } - if (this->xb_account_id != 0) { - tokens.emplace_back(string_printf("xb_account_id=%016" PRIX64, this->xb_account_id)); - } - if (!this->bb_username.empty()) { - tokens.emplace_back("bb_username=" + this->bb_username); - } - if (!this->bb_password.empty()) { - tokens.emplace_back("bb_password=" + this->bb_password); - } - tokens.emplace_back(string_printf("flags=%08" PRIX32, this->flags)); - if (this->ban_end_time) { - tokens.emplace_back(string_printf("ban_end_time=%016" PRIX64, this->ban_end_time)); - } - if (this->ep3_current_meseta) { - tokens.emplace_back(string_printf("ep3_current_meseta=%" PRIu32, this->ep3_current_meseta)); - } - if (this->ep3_total_meseta_earned) { - tokens.emplace_back(string_printf("ep3_total_meseta_earned=%" PRIu32, this->ep3_total_meseta_earned)); - } - return "[License: " + join(tokens, ", ") + "]"; -} - -DiskLicense::DiskLicense(const JSON& json) : License(json) {} - -void DiskLicense::save() const { - auto json = this->json(); - string json_data = json.serialize(JSON::SerializeOption::FORMAT | JSON::SerializeOption::HEX_INTEGERS); - string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->serial_number); - save_file(filename, json_data); -} - -void DiskLicense::delete_file() const { - string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->serial_number); - remove(filename.c_str()); -} - -shared_ptr LicenseIndex::create_license() const { - return make_shared(); -} - -shared_ptr LicenseIndex::create_temporary_license() const { - return make_shared(); -} - -size_t LicenseIndex::count() const { - return this->serial_number_to_license.size(); -} - -shared_ptr LicenseIndex::get(uint32_t serial_number) const { - try { - return this->serial_number_to_license.at(serial_number); - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::get_by_bb_username(const string& bb_username) const { - try { - return this->bb_username_to_license.at(bb_username); - } catch (const out_of_range&) { - throw missing_license(); - } -} - -vector> LicenseIndex::all() const { - vector> ret; - ret.reserve(this->serial_number_to_license.size()); - for (const auto& it : this->serial_number_to_license) { - ret.emplace_back(it.second); - } - return ret; -} - -void LicenseIndex::add(shared_ptr l) { - this->serial_number_to_license[l->serial_number] = l; - if (!l->dc_nte_serial_number.empty()) { - this->dc_nte_serial_number_to_license[l->dc_nte_serial_number] = l; - } - if (!l->xb_gamertag.empty()) { - this->xb_gamertag_to_license[l->xb_gamertag] = l; - } - if (!l->bb_username.empty()) { - this->bb_username_to_license[l->bb_username] = l; - } -} - -void LicenseIndex::remove(uint32_t serial_number) { - auto l = this->serial_number_to_license.at(serial_number); - this->serial_number_to_license.erase(l->serial_number); - if (!l->dc_nte_serial_number.empty()) { - this->dc_nte_serial_number_to_license.erase(l->dc_nte_serial_number); - } - if (!l->xb_gamertag.empty()) { - this->xb_gamertag_to_license.erase(l->xb_gamertag); - } - if (!l->bb_username.empty()) { - this->bb_username_to_license.erase(l->bb_username); - } -} - -shared_ptr LicenseIndex::verify_dc_nte(const string& serial_number, const string& access_key) const { - if (serial_number.empty()) { - throw no_username(); - } - try { - auto& license = this->dc_nte_serial_number_to_license.at(serial_number); - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } - if (license->dc_nte_access_key != access_key) { - throw incorrect_access_key(); - } - return license; - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::verify_v1_v2( - uint32_t serial_number, - const string& access_key, - const string& character_name) const { - if (serial_number == 0) { - throw no_username(); - } - try { - auto& license = this->serial_number_to_license.at(serial_number); - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } - if (license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - return this->create_temporary_license_for_shared_license(license->flags, serial_number, access_key, "", character_name); - } - if (license->access_key.compare(0, 8, access_key) != 0) { - throw incorrect_access_key(); - } - return license; - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::verify_gc_no_password( - uint32_t serial_number, - const string& access_key, - const string& character_name) const { - if (serial_number == 0) { - throw no_username(); - } - try { - auto& license = this->serial_number_to_license.at(serial_number); - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } - if (license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - return this->create_temporary_license_for_shared_license(license->flags, serial_number, access_key, "", character_name); - } - if (license->access_key != access_key) { - throw incorrect_access_key(); - } - return license; - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::verify_gc_with_password( - uint32_t serial_number, - const string& access_key, - const string& password, - const string& character_name) const { - if (serial_number == 0) { - throw no_username(); - } - try { - auto& license = this->serial_number_to_license.at(serial_number); - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } - if (license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - return this->create_temporary_license_for_shared_license(license->flags, serial_number, access_key, password, character_name); - } - if (license->access_key != access_key) { - throw incorrect_access_key(); - } - if (license->gc_password != password) { - throw incorrect_password(); - } - return license; - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::verify_xb(const string& gamertag, uint64_t user_id, uint64_t account_id) const { - if (user_id == 0 || account_id == 0) { - throw incorrect_access_key(); - } - try { - auto& license = this->xb_gamertag_to_license.at(gamertag); - if (license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - throw missing_license(); // XB users cannot use shared serials - } - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } - if (license->xb_user_id && (license->xb_user_id != user_id)) { - throw incorrect_access_key(); - } - if (license->xb_account_id && (license->xb_account_id != account_id)) { - throw incorrect_access_key(); - } - return license; - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::verify_bb(const string& username, const string& password) const { - if (username.empty() || password.empty()) { - throw no_username(); - } - try { - auto& license = this->bb_username_to_license.at(username); - if (license->check_flag(License::Flag::IS_SHARED_SERIAL)) { - throw missing_license(); // BB users cannot use shared serials - } - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } - if (license->bb_password != password) { - throw incorrect_password(); - } - return license; - } catch (const out_of_range&) { - throw missing_license(); - } -} - -shared_ptr LicenseIndex::create_temporary_license_for_shared_license( - uint32_t base_flags, - uint32_t serial_number, - const string& access_key, - const string& password, - const string& character_name) const { - uint32_t temp_serial_number = fnv1a32(&serial_number, sizeof(serial_number)); - temp_serial_number = fnv1a32(access_key, temp_serial_number); - temp_serial_number = fnv1a32(password, temp_serial_number); - temp_serial_number = fnv1a32(character_name, temp_serial_number); - auto ret = this->create_temporary_license(); - ret->serial_number = temp_serial_number & 0x7FFFFFFF; - ret->flags = base_flags; - ret->set_flag(License::Flag::IS_SHARED_SERIAL); - return ret; -} - -DiskLicenseIndex::DiskLicenseIndex() { - struct BinaryLicense { - pstring username; // BB username (max. 16 chars; should technically be Unicode) - pstring bb_password; // BB password (max. 16 chars) - uint32_t serial_number; // PC/GC serial number. MUST BE PRESENT FOR BB LICENSES TOO; this is also the player's guild card number. - pstring access_key; // PC/GC access key. (to log in using PC on a GC license, just enter the first 8 characters of the GC access key) - pstring gc_password; // GC password - uint32_t privileges; // privilege level - uint64_t ban_end_time; // end time of ban (zero = not banned) - } __packed_ws__(BinaryLicense, 0x54); - - if (!isdir("system/licenses")) { - mkdir("system/licenses", 0755); - } - - // Convert binary licenses to JSON licenses and save them - if (isfile("system/licenses.nsi")) { - auto bin_licenses = load_vector_file("system/licenses.nsi"); - for (const auto& bin_license : bin_licenses) { - // Only add licenses from the binary file if there isn't a JSON version of - // the same license - try { - this->get(bin_license.serial_number); - } catch (const missing_license&) { - License license; - license.serial_number = bin_license.serial_number; - license.access_key = bin_license.access_key.decode(); - license.gc_password = bin_license.gc_password.decode(); - license.bb_username = bin_license.username.decode(); - license.bb_password = bin_license.bb_password.decode(); - license.flags = bin_license.privileges; - license.ban_end_time = bin_license.ban_end_time; - license.ep3_current_meseta = 0; - license.ep3_total_meseta_earned = 0; - license.save(); - } - } - ::remove("system/licenses.nsi"); - } - - for (const auto& item : list_directory("system/licenses")) { - if (ends_with(item, ".json")) { - JSON json = JSON::parse(load_file("system/licenses/" + item)); - auto license = make_shared(json); - this->add(license); - } - } -} - -shared_ptr DiskLicenseIndex::create_license() const { - return make_shared(); -} diff --git a/src/License.hh b/src/License.hh deleted file mode 100644 index 6470fe16..00000000 --- a/src/License.hh +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -#include "Text.hh" - -class LicenseIndex; - -class License { -public: - enum class Flag : uint32_t { - // clang-format off - KICK_USER = 0x00000001, - BAN_USER = 0x00000002, - SILENCE_USER = 0x00000004, - CHANGE_EVENT = 0x00000010, - ANNOUNCE = 0x00000020, - FREE_JOIN_GAMES = 0x00000040, - DEBUG = 0x01000000, - CHEAT_ANYWHERE = 0x02000000, - DISABLE_QUEST_REQUIREMENTS = 0x04000000, - ALWAYS_ENABLE_CHAT_COMMANDS = 0x08000000, - MODERATOR = 0x00000007, - ADMINISTRATOR = 0x000000FF, - ROOT = 0x7FFFFFFF, - IS_SHARED_SERIAL = 0x80000000, - // NOTE: When adding or changing license flags, don't forget to change the - // documentation in the shell's help text. - UNUSED_BITS = 0x70FFFF00, - // clang-format on - }; - - uint32_t serial_number = 0; - std::string dc_nte_serial_number; - std::string dc_nte_access_key; - std::string access_key; - std::string gc_password; - std::string xb_gamertag; - uint64_t xb_user_id = 0; - uint64_t xb_account_id = 0; - std::string bb_username; - std::string bb_password; - - uint32_t flags = 0; - uint64_t ban_end_time = 0; // 0 = not banned - std::string last_player_name; - std::string auto_reply_message; - - uint32_t ep3_current_meseta = 0; - uint32_t ep3_total_meseta_earned = 0; - - uint32_t bb_team_id = 0; - - License() = default; - explicit License(const JSON& json); - virtual ~License() = default; - - JSON json() const; - virtual void save() const; - virtual void delete_file() const; - - [[nodiscard]] inline bool check_flag(Flag flag) const { - return !!(this->flags & static_cast(flag)); - } - inline void set_flag(Flag flag) { - this->flags |= static_cast(flag); - } - inline void clear_flag(Flag flag) { - this->flags &= (~static_cast(flag)); - } - inline void toggle_flag(Flag flag) { - this->flags ^= static_cast(flag); - } - inline void replace_all_flags(Flag mask) { - this->flags = static_cast(mask); - } - - std::string str() const; -}; - -class DiskLicense : public License { -public: - DiskLicense() = default; - explicit DiskLicense(const JSON& json); - virtual ~DiskLicense() = default; - - virtual void save() const; - virtual void delete_file() const; -}; - -class LicenseIndex { -public: - class no_username : public std::invalid_argument { - public: - no_username() : invalid_argument("serial number is zero or username is missing") {} - }; - class incorrect_password : public std::invalid_argument { - public: - incorrect_password() : invalid_argument("incorrect password") {} - }; - class incorrect_access_key : public std::invalid_argument { - public: - incorrect_access_key() : invalid_argument("incorrect access key") {} - }; - class missing_license : public std::invalid_argument { - public: - missing_license() : invalid_argument("missing license") {} - }; - - LicenseIndex() = default; - virtual ~LicenseIndex() = default; - - virtual std::shared_ptr create_license() const; - virtual std::shared_ptr create_temporary_license() const; - - size_t count() const; - std::shared_ptr get(uint32_t serial_number) const; - std::shared_ptr get_by_bb_username(const std::string& bb_username) const; - std::vector> all() const; - - void add(std::shared_ptr l); - void remove(uint32_t serial_number); - - std::shared_ptr verify_dc_nte(const std::string& serial_number, const std::string& access_key) const; - std::shared_ptr verify_v1_v2( - uint32_t serial_number, - const std::string& access_key, - const std::string& character_name) const; - std::shared_ptr verify_gc_no_password( - uint32_t serial_number, - const std::string& access_key, - const std::string& character_name) const; - std::shared_ptr verify_gc_with_password( - uint32_t serial_number, - const std::string& access_key, - const std::string& password, - const std::string& character_name) const; - std::shared_ptr verify_xb(const std::string& gamertag, uint64_t user_id, uint64_t account_id) const; - std::shared_ptr verify_bb(const std::string& username, const std::string& password) const; - -protected: - std::unordered_map> dc_nte_serial_number_to_license; - std::unordered_map> xb_gamertag_to_license; - std::unordered_map> bb_username_to_license; - std::unordered_map> serial_number_to_license; - - std::shared_ptr create_temporary_license_for_shared_license( - uint32_t base_flags, - uint32_t serial_number, - const std::string& access_key, - const std::string& password, - const std::string& character_name) const; -}; - -class DiskLicenseIndex : public LicenseIndex { -public: - DiskLicenseIndex(); - virtual ~DiskLicenseIndex() = default; - - virtual std::shared_ptr create_license() const; -}; diff --git a/src/Lobby.cc b/src/Lobby.cc index b096c108..13606585 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -566,6 +566,10 @@ bool Lobby::any_v1_clients_present() const { } void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { + if (!c->login) { + throw runtime_error("client is not logged in"); + } + ssize_t index; ssize_t min_client_id = this->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM) ? 4 : 0; @@ -645,7 +649,7 @@ void Lobby::add_client(shared_ptr c, ssize_t required_client_id) { auto p = c->character(); PlayerLobbyDataDCGC lobby_data; lobby_data.player_tag = 0x00010000; - lobby_data.guild_card_number = c->license->serial_number; + lobby_data.guild_card_number = c->login->account->account_id; lobby_data.name.encode(p->disp.name.decode(c->language()), c->language()); this->battle_record->add_player( lobby_data, @@ -767,14 +771,13 @@ void Lobby::move_client_to_lobby( dest_lobby->add_client(c, required_client_id); } -shared_ptr Lobby::find_client(const string* identifier, uint64_t serial_number) { +shared_ptr Lobby::find_client(const string* identifier, uint64_t account_id) { for (size_t x = 0; x < this->max_clients; x++) { auto lc = this->clients[x]; if (!lc) { continue; } - if (serial_number && lc->license && - (lc->license->serial_number == serial_number)) { + if (account_id && lc->login && (lc->login->account->account_id == account_id)) { return lc; } if (identifier && (lc->character()->disp.name.eq(*identifier, lc->language()))) { @@ -802,7 +805,7 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr c, const s if (this->mode == GameMode::SOLO) { return JoinError::SOLO; } - if (!c->license->check_flag(License::Flag::FREE_JOIN_GAMES)) { + if (!c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES)) { if (password && !this->password.empty() && (*password != this->password)) { return JoinError::INCORRECT_PASSWORD; } @@ -915,11 +918,11 @@ void Lobby::assign_inventory_and_bank_item_ids(shared_ptr c, bool consum } } -unordered_map> Lobby::clients_by_serial_number() const { +unordered_map> Lobby::clients_by_account_id() const { unordered_map> ret; for (auto c : this->clients) { - if (c) { - ret.emplace(c->license->serial_number, c); + if (c && c->login) { + ret.emplace(c->login->account->account_id, c); } } return ret; diff --git a/src/Lobby.hh b/src/Lobby.hh index 9c3427d0..ce3c6d0a 100644 --- a/src/Lobby.hh +++ b/src/Lobby.hh @@ -275,9 +275,7 @@ struct Lobby : public std::enable_shared_from_this { std::shared_ptr c, ssize_t required_client_id = -1); - std::shared_ptr find_client( - const std::string* identifier = nullptr, - uint64_t serial_number = 0); + std::shared_ptr find_client(const std::string* identifier = nullptr, uint64_t account_id = 0); enum class JoinError { ALLOWED = 0, @@ -307,7 +305,7 @@ struct Lobby : public std::enable_shared_from_this { QuestIndex::IncludeCondition quest_include_condition() const; - std::unordered_map> clients_by_serial_number() const; + std::unordered_map> clients_by_account_id() const; static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx); diff --git a/src/PatchServer.cc b/src/PatchServer.cc index a82509f8..dbeb1887 100644 --- a/src/PatchServer.cc +++ b/src/PatchServer.cc @@ -160,19 +160,19 @@ void PatchServer::on_04(shared_ptr c, string& data) { string password = cmd.password.decode(); // There are 3 cases here: - // - No login information at all: just proceed without checking license - // - Username only: check that license exists if allow_unregistered_users is off + // - No login information at all: just proceed without checking credentials + // - Username: check that account exists if allow_unregistered_users is off // - Username and password: call verify_bb if (!username.empty() && !password.empty()) { try { - this->config->license_index->verify_bb(username, password); + this->config->account_index->from_bb_credentials(username, &password, false); - } catch (const LicenseIndex::incorrect_password& e) { + } catch (const AccountIndex::incorrect_password& e) { this->send_message_box(c, string_printf("Login failed: %s", e.what())); this->disconnect_client(c); return; - } catch (const LicenseIndex::missing_license& e) { + } catch (const AccountIndex::missing_account& e) { if (!this->config->allow_unregistered_users) { this->send_message_box(c, string_printf("Login failed: %s", e.what())); this->disconnect_client(c); @@ -182,8 +182,8 @@ void PatchServer::on_04(shared_ptr c, string& data) { } else if (!username.empty() && !this->config->allow_unregistered_users) { try { - this->config->license_index->get_by_bb_username(username); - } catch (const LicenseIndex::missing_license& e) { + this->config->account_index->from_bb_credentials(username, nullptr, false); + } catch (const AccountIndex::missing_account& e) { this->send_message_box(c, string_printf("Login failed: %s", e.what())); this->disconnect_client(c); return; diff --git a/src/PatchServer.hh b/src/PatchServer.hh index 24653be4..144b94a3 100644 --- a/src/PatchServer.hh +++ b/src/PatchServer.hh @@ -8,8 +8,8 @@ #include #include +#include "Account.hh" #include "Channel.hh" -#include "License.hh" #include "PatchFileIndex.hh" #include "Version.hh" @@ -20,7 +20,7 @@ public: bool hide_data_from_logs; uint64_t idle_timeout_usecs; std::string message; - std::shared_ptr license_index; + std::shared_ptr account_index; std::shared_ptr patch_file_index; std::shared_ptr shared_base; }; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index ef750f87..c2494cf3 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -136,13 +136,13 @@ static HandlerResult S_97(shared_ptr ses, uint16_t, } static HandlerResult C_G_9E(shared_ptr ses, uint16_t, uint32_t, string&) { - if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) { + if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN) && ses->login) { le_uint64_t checksum = random_object() & 0x0000FFFFFFFFFFFF; ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); S_UpdateClientConfig_V3_04 cmd; cmd.player_tag = 0x00010000; - cmd.guild_card_number = ses->license->serial_number; + cmd.guild_card_number = ses->login->account->account_id; cmd.client_config.clear(0xFF); ses->client_channel.send(0x04, 0x00, &cmd, sizeof(cmd)); @@ -154,7 +154,7 @@ static HandlerResult C_G_9E(shared_ptr ses, uint16_t } static HandlerResult S_G_9A(shared_ptr ses, uint16_t, uint32_t, string&) { - if (!ses->license || ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN)) { + if (ses->config.check_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN) || !ses->login || !ses->login->gc_license) { return HandlerResult::Type::FORWARD; } @@ -171,8 +171,8 @@ static HandlerResult S_G_9A(shared_ptr ses, uint16_t cmd.sub_version = ses->sub_version; cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0; cmd.language = ses->language(); - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->login->account->account_id)); + cmd.access_key.encode(ses->login->gc_license->access_key); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { @@ -204,8 +204,8 @@ static HandlerResult S_V123P_02_17( // after_message than newserv does, so don't require it const auto& cmd = check_size_t(data, 0xFFFF); - if (!ses->license) { - ses->log.info("No license in linked session"); + if (!ses->login) { + ses->log.info("Linked session is not logged in"); // We have to forward the command BEFORE setting up encryption, so the // client will be able to understand what we sent. @@ -226,7 +226,7 @@ static HandlerResult S_V123P_02_17( return HandlerResult::Type::SUPPRESS; } - ses->log.info("Existing license in linked session"); + ses->log.info("Linked session is logged in"); // This isn't forwarded to the client, so don't recreate the client's crypts if (uses_v3_encryption(ses->version())) { @@ -253,10 +253,13 @@ static HandlerResult S_V123P_02_17( case Version::DC_V1_11_2000_PROTOTYPE: case Version::DC_V1: + if (!ses->login->dc_license) { + throw runtime_error("DC license missing from login"); + } if (command == 0x17) { C_LoginV1_DC_PC_V3_90 cmd; - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->login->account->account_id)); + cmd.access_key.encode(ses->login->dc_license->access_key); cmd.access_key.clear_after_bytes(8); ses->server_channel.send(0x90, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -274,8 +277,8 @@ static HandlerResult S_V123P_02_17( cmd.sub_version = ses->sub_version; cmd.is_extended = 0; cmd.language = ses->language(); - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->login->account->account_id)); + cmd.access_key.encode(ses->login->dc_license->access_key); cmd.access_key.clear_after_bytes(8); cmd.hardware_id.encode(ses->hardware_id); cmd.name.encode(ses->character_name); @@ -288,6 +291,18 @@ static HandlerResult S_V123P_02_17( case Version::PC_NTE: case Version::PC_V2: case Version::GC_NTE: + const string* access_key; + if (ses->version() == Version::DC_V2) { + access_key = ses->login->dc_license ? &ses->login->dc_license->access_key : nullptr; + } else if (ses->version() != Version::GC_NTE) { + access_key = ses->login->pc_license ? &ses->login->pc_license->access_key : nullptr; + } else { + access_key = ses->login->gc_license ? &ses->login->gc_license->access_key : nullptr; + } + if (!access_key) { + throw runtime_error("incorrect login type"); + } + if (command == 0x17) { C_Login_DC_PC_V3_9A cmd; if (ses->remote_guild_card_number < 0) { @@ -298,8 +313,8 @@ static HandlerResult S_V123P_02_17( cmd.guild_card_number = ses->remote_guild_card_number; } cmd.sub_version = ses->sub_version; - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->login->account->account_id)); + cmd.access_key.encode(*access_key); if (ses->version() != Version::GC_NTE) { cmd.access_key.clear_after_bytes(8); } @@ -307,7 +322,7 @@ static HandlerResult S_V123P_02_17( cmd.access_key2 = cmd.access_key; // TODO: We probably should set email_address, but we currently don't // keep that value anywhere in the session object, nor is it saved in - // the License object. + // the Account object. ses->server_channel.send(0x9A, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } else { @@ -324,8 +339,8 @@ static HandlerResult S_V123P_02_17( cmd.sub_version = ses->sub_version; cmd.is_extended = 0; cmd.language = ses->language(); - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->login->account->account_id)); + cmd.access_key.encode(*access_key); if (ses->version() != Version::GC_NTE) { cmd.access_key.clear_after_bytes(8); } @@ -344,14 +359,17 @@ static HandlerResult S_V123P_02_17( case Version::GC_V3: case Version::GC_EP3_NTE: case Version::GC_EP3: + if (!ses->login->gc_license) { + throw runtime_error("GC license missing from login"); + } if (command == 0x17) { - C_VerifyLicense_V3_DB cmd; - cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->license->serial_number)); - cmd.access_key.encode(ses->license->access_key); + C_VerifyAccount_V3_DB cmd; + cmd.serial_number.encode(string_printf("%08" PRIX32 "", ses->login->account->account_id)); + cmd.access_key.encode(ses->login->gc_license->access_key); cmd.sub_version = ses->sub_version; cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; - cmd.password.encode(ses->license->gc_password); + cmd.password.encode(ses->login->gc_license->password); ses->server_channel.send(0xDB, 0x00, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; @@ -400,6 +418,9 @@ static HandlerResult S_V123P_02_17( throw logic_error("GC init command not handled"); case Version::XB_V3: { + if (!ses->login->xb_license) { + throw runtime_error("XB license missing from login"); + } C_LoginExtended_XB_9E cmd; if (ses->remote_guild_card_number < 0) { cmd.player_tag = 0xFFFF0000; @@ -413,8 +434,8 @@ static HandlerResult S_V123P_02_17( cmd.sub_version = ses->sub_version; cmd.is_extended = (ses->remote_guild_card_number < 0) ? 1 : 0; cmd.language = ses->language(); - cmd.serial_number.encode(ses->license->xb_gamertag); - cmd.access_key.encode(string_printf("%016" PRIX64, ses->license->xb_user_id)); + cmd.serial_number.encode(ses->login->xb_license->gamertag); + cmd.access_key.encode(string_printf("%016" PRIX64, ses->login->xb_license->user_id)); cmd.serial_number2 = cmd.serial_number; cmd.access_key2 = cmd.access_key; if (ses->config.check_flag(Client::Flag::PROXY_BLANK_NAME_ENABLED)) { @@ -424,8 +445,8 @@ static HandlerResult S_V123P_02_17( } cmd.netloc = ses->xb_netloc; cmd.unknown_a1a = ses->xb_9E_unknown_a1a; - cmd.xb_user_id_high = (ses->license->xb_user_id >> 32) & 0xFFFFFFFF; - cmd.xb_user_id_low = ses->license->xb_user_id & 0xFFFFFFFF; + cmd.xb_user_id_high = (ses->login->xb_license->user_id >> 32) & 0xFFFFFFFF; + cmd.xb_user_id_low = ses->login->xb_license->user_id & 0xFFFFFFFF; ses->server_channel.send(0x9E, 0x01, &cmd, sizeof(C_LoginExtended_XB_9E)); return HandlerResult::Type::SUPPRESS; } @@ -508,8 +529,8 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 offsetof(S_UpdateClientConfig_V3_04, client_config), sizeof(S_UpdateClientConfig_V3_04)); - // If this is a licensed session, hide the guild card number assigned by the - // remote server so the client doesn't see it change. If this is an unlicensed + // If this is a logged-in session, hide the guild card number assigned by the + // remote server so the client doesn't see it change. If this is a logged-out // session, then the client never received a guild card number from newserv // anyway, so we can let the client see the number from the remote server. bool had_guild_card_number = (ses->remote_guild_card_number >= 0); @@ -522,8 +543,8 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 ses->remote_guild_card_number); send_ship_info(ses->client_channel, message); } - if (ses->license) { - cmd.guild_card_number = ses->license->serial_number; + if (ses->login) { + cmd.guild_card_number = ses->login->account->account_id; } // It seems the client ignores the length of the 04 command, and always copies @@ -549,15 +570,15 @@ static HandlerResult S_V123_04(shared_ptr ses, uint1 ses->server_channel.send(0x96, 0x00, &checksum, sizeof(checksum)); } - return ses->license ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; + return ses->login ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; } static HandlerResult S_V123_06(shared_ptr ses, uint16_t, uint32_t, string& data) { bool modified = false; - if (ses->license) { + if (ses->login) { auto& cmd = check_size_t(data, 0xFFFF); if (cmd.guild_card_number == ses->remote_guild_card_number) { - cmd.guild_card_number = ses->license->serial_number; + cmd.guild_card_number = ses->login->account->account_id; modified = true; } } @@ -579,7 +600,7 @@ static HandlerResult S_V123_06(shared_ptr ses, uint1 template static HandlerResult S_41(shared_ptr ses, uint16_t, uint32_t, string& data) { - if (ses->license) { + if (ses->login) { auto& cmd = check_size_t(data); if ((cmd.searcher_guild_card_number == ses->remote_guild_card_number) && (cmd.result_guild_card_number == ses->remote_guild_card_number) && @@ -593,11 +614,11 @@ static HandlerResult S_41(shared_ptr ses, uint16_t, } else { bool modified = false; if (cmd.searcher_guild_card_number == ses->remote_guild_card_number) { - cmd.searcher_guild_card_number = ses->license->serial_number; + cmd.searcher_guild_card_number = ses->login->account->account_id; modified = true; } if (cmd.result_guild_card_number == ses->remote_guild_card_number) { - cmd.result_guild_card_number = ses->license->serial_number; + cmd.result_guild_card_number = ses->login->account->account_id; modified = true; } return modified ? HandlerResult::Type::MODIFIED : HandlerResult::Type::FORWARD; @@ -614,14 +635,14 @@ constexpr on_command_t S_B_41 = &S_41; template static HandlerResult S_81(shared_ptr ses, uint16_t, uint32_t, string& data) { bool modified = false; - if (ses->license) { + if (ses->login) { auto& cmd = check_size_t(data); if (cmd.from_guild_card_number == ses->remote_guild_card_number) { - cmd.from_guild_card_number = ses->license->serial_number; + cmd.from_guild_card_number = ses->login->account->account_id; modified = true; } if (cmd.to_guild_card_number == ses->remote_guild_card_number) { - cmd.to_guild_card_number = ses->license->serial_number; + cmd.to_guild_card_number = ses->login->account->account_id; modified = true; } } @@ -634,13 +655,13 @@ constexpr on_command_t S_B_81 = &S_81; static HandlerResult S_88(shared_ptr ses, uint16_t, uint32_t flag, string& data) { bool modified = false; - if (ses->license) { + if (ses->login) { size_t expected_size = sizeof(S_ArrowUpdateEntry_88) * flag; auto* entries = &check_size_t( data, expected_size, expected_size); for (size_t x = 0; x < flag; x++) { if (entries[x].guild_card_number == ses->remote_guild_card_number) { - entries[x].guild_card_number = ses->license->serial_number; + entries[x].guild_card_number = ses->login->account->account_id; modified = true; } } @@ -806,14 +827,14 @@ static HandlerResult S_B_E7(shared_ptr ses, uint16_t template static HandlerResult S_C4(shared_ptr ses, uint16_t, uint32_t flag, string& data) { bool modified = false; - if (ses->license) { + if (ses->login) { size_t expected_size = sizeof(CmdT) * flag; // Some servers (e.g. Schtserv) send extra data on the end of this command; // the client ignores it so we can ignore it too auto* entries = &check_size_t(data, expected_size, 0xFFFF); for (size_t x = 0; x < flag; x++) { if (entries[x].guild_card_number == ses->remote_guild_card_number) { - entries[x].guild_card_number = ses->license->serial_number; + entries[x].guild_card_number = ses->login->account->account_id; modified = true; } } @@ -830,7 +851,7 @@ static HandlerResult S_G_E4(shared_ptr ses, uint16_t bool modified = false; for (size_t x = 0; x < 4; x++) { if (cmd.entries[x].guild_card_number == ses->remote_guild_card_number) { - cmd.entries[x].guild_card_number = ses->license->serial_number; + cmd.entries[x].guild_card_number = ses->login->account->account_id; modified = true; } } @@ -1521,8 +1542,8 @@ static HandlerResult S_65_67_68_EB(shared_ptr ses, u ses->log.warning("Ignoring invalid player index %zu at position %zu", index, x); } else { string name = escape_player_name(entry.disp.visual.name.decode(entry.inventory.language)); - if (ses->license && (entry.lobby_data.guild_card_number == ses->remote_guild_card_number)) { - entry.lobby_data.guild_card_number = ses->license->serial_number; + if (ses->login && (entry.lobby_data.guild_card_number == ses->remote_guild_card_number)) { + entry.lobby_data.guild_card_number = ses->login->account->account_id; num_replacements++; modified = true; } else if (ses->config.check_flag(Client::Flag::PROXY_PLAYER_NOTIFICATIONS_ENABLED) && @@ -1675,7 +1696,7 @@ static HandlerResult S_64(shared_ptr ses, uint16_t, update_leader_id(ses, cmd->leader_id); for (size_t x = 0; x < flag; x++) { if (cmd->lobby_data[x].guild_card_number == ses->remote_guild_card_number) { - cmd->lobby_data[x].guild_card_number = ses->license->serial_number; + cmd->lobby_data[x].guild_card_number = ses->login->account->account_id; modified = true; } auto& p = ses->lobby_players[x]; @@ -1740,11 +1761,11 @@ static HandlerResult S_E8(shared_ptr ses, uint16_t, auto& spec_entry = cmd.entries[x]; if (player_entry.lobby_data.guild_card_number == ses->remote_guild_card_number) { - player_entry.lobby_data.guild_card_number = ses->license->serial_number; + player_entry.lobby_data.guild_card_number = ses->login->account->account_id; modified = true; } if (spec_entry.guild_card_number == ses->remote_guild_card_number) { - spec_entry.guild_card_number = ses->license->serial_number; + spec_entry.guild_card_number = ses->login->account->account_id; modified = true; } @@ -1879,13 +1900,13 @@ static HandlerResult C_06(shared_ptr ses, uint16_t, static HandlerResult C_40(shared_ptr ses, uint16_t, uint32_t, string& data) { bool modified = false; - if (ses->license) { + if (ses->login) { auto& cmd = check_size_t(data); - if (cmd.searcher_guild_card_number == ses->license->serial_number) { + if (cmd.searcher_guild_card_number == ses->login->account->account_id) { cmd.searcher_guild_card_number = ses->remote_guild_card_number; modified = true; } - if (cmd.target_guild_card_number == ses->license->serial_number) { + if (cmd.target_guild_card_number == ses->login->account->account_id) { cmd.target_guild_card_number = ses->remote_guild_card_number; modified = true; } @@ -1896,11 +1917,11 @@ static HandlerResult C_40(shared_ptr ses, uint16_t, template static HandlerResult C_81(shared_ptr ses, uint16_t, uint32_t, string& data) { auto& cmd = check_size_t(data); - if (ses->license) { - if (cmd.from_guild_card_number == ses->license->serial_number) { + if (ses->login) { + if (cmd.from_guild_card_number == ses->login->account->account_id) { cmd.from_guild_card_number = ses->remote_guild_card_number; } - if (cmd.to_guild_card_number == ses->license->serial_number) { + if (cmd.to_guild_card_number == ses->login->account->account_id) { cmd.to_guild_card_number = ses->remote_guild_card_number; } } @@ -1922,12 +1943,12 @@ void C_6x_movement(shared_ptr ses, const string& dat template static HandlerResult C_6x(shared_ptr ses, uint16_t command, uint32_t flag, string& data) { - if (ses->license && !data.empty()) { + if (ses->login && !data.empty()) { // On BB, the 6x06 command is blank - the server generates the actual Guild // Card contents and sends it to the target client. if ((data[0] == 0x06) && !is_v4(ses->version())) { auto& cmd = check_size_t(data); - if (cmd.guild_card.guild_card_number == ses->license->serial_number) { + if (cmd.guild_card.guild_card_number == ses->login->account->account_id) { cmd.guild_card.guild_card_number = ses->remote_guild_card_number; } } @@ -2006,11 +2027,11 @@ HandlerResult C_6x(shared_ptr ses, uint16_t, u } static HandlerResult C_V123_A0_A1(shared_ptr ses, uint16_t, uint32_t, string&) { - if (!ses->license) { + if (!ses->login) { return HandlerResult::Type::FORWARD; } - // For licensed sessions, send them back to newserv's main menu instead of + // For logged-in sessions, send them back to newserv's main menu instead of // going to the remote server's ship/block select menu ses->send_to_game_server(); ses->disconnect_action = ProxyServer::LinkedSession::DisconnectAction::CLOSE_IMMEDIATELY; diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index dc497023..6d59e4cb 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -41,7 +41,7 @@ ProxyServer::ProxyServer( : base(base), destroy_sessions_ev(event_new(this->base.get(), -1, EV_TIMEOUT, &ProxyServer::dispatch_destroy_sessions, this), event_free), state(state), - next_unlicensed_session_id(0xFF00000000000001) {} + next_logged_out_session_id(0xFF00000000000001) {} void ProxyServer::listen(const std::string& addr, uint16_t port, Version version, const struct sockaddr_storage* default_destination) { auto socket_obj = make_shared(this, addr, port, version, default_destination); @@ -140,14 +140,14 @@ void ProxyServer::on_client_connect( // client, create a linked session immediately and connect to the remote // server. This creates a direct session. if (default_destination && is_patch(version)) { - uint64_t session_id = this->next_unlicensed_session_id++; - if (this->next_unlicensed_session_id == 0) { - this->next_unlicensed_session_id = 0xFF00000000000001; + uint64_t session_id = this->next_logged_out_session_id++; + if (this->next_logged_out_session_id == 0) { + this->next_logged_out_session_id = 0xFF00000000000001; } auto emplace_ret = this->id_to_session.emplace(session_id, make_shared(this->shared_from_this(), session_id, listen_port, version, *default_destination)); if (!emplace_ret.second) { - throw logic_error("linked session already exists for unlicensed client"); + throw logic_error("linked session already exists for logged-out client"); } auto ses = emplace_ret.first->second; ses->log.info("Opened linked session"); @@ -278,8 +278,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->channel.version = Version::DC_NTE; ses->log.info("Version changed to DC_NTE"); const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_DCNTE_8B)); - ses->license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); + ses->login = s->account_index->from_dc_nte_credentials(cmd.serial_number.decode(), cmd.access_key.decode(), false); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -288,8 +287,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 ses->channel.version = Version::DC_V1; ses->log.info("Version changed to DC_V1"); const auto& cmd = check_size_t(data); - ses->license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); + ses->login = s->account_index->from_dc_credentials( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode(), false); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -299,13 +298,13 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 if (cmd.sub_version >= 0x30) { ses->log.info("Version changed to GC_NTE"); ses->channel.version = Version::GC_NTE; - ses->license = s->license_index->verify_gc_no_password( - stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); + ses->login = s->account_index->from_gc_credentials( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), nullptr, cmd.name.decode(), false); } else { // DC V2 ses->log.info("Version changed to DC_V2"); ses->channel.version = Version::DC_V2; - ses->license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); + ses->login = s->account_index->from_dc_credentials( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode(), false); } ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; @@ -323,8 +322,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 throw runtime_error("command is not 9D"); } const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_PC_9D)); - ses->license = s->license_index->verify_v1_v2( - stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); + ses->login = s->account_index->from_pc_credentials( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode(), false); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -337,8 +336,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 // We should only get a 9E while the session is unlinked if (command == 0x9E) { const auto& cmd = check_size_t(data, sizeof(C_LoginExtended_GC_9E)); - ses->license = s->license_index->verify_gc_no_password( - stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); + ses->login = s->account_index->from_gc_credentials( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), nullptr, cmd.name.decode(), false); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -359,7 +358,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 string xb_gamertag = cmd.serial_number.decode(); uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16); uint64_t xb_account_id = cmd.netloc.account_id; - ses->license = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id); + ses->login = s->account_index->from_xb_credentials(xb_gamertag, xb_user_id, xb_account_id, false); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -382,22 +381,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 throw runtime_error("command is not 93"); } const auto& cmd = check_size_t(data, 0xFFFF); - try { - ses->license = s->license_index->verify_bb(cmd.username.decode(), cmd.password.decode()); - } catch (const LicenseIndex::missing_license&) { - if (!s->allow_unregistered_users) { - throw; - } - auto l = s->license_index->create_license(); - l->serial_number = fnv1a32(cmd.username.decode()) & 0x7FFFFFFF; - l->bb_username = cmd.username.decode(); - l->bb_password = cmd.password.decode(); - s->license_index->add(l); - l->save(); - ses->license = l; - string l_str = l->str(); - ses->log.info("Created license %s", l_str.c_str()); - } + string password = cmd.password.decode(); + ses->login = s->account_index->from_bb_credentials(cmd.username.decode(), &password, s->allow_unregistered_users); ses->login_command_bb = std::move(data); break; } @@ -415,36 +400,36 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3 // afterward. struct bufferevent* session_key = ch.bev.get(); - // If license is non-null, then the client has a password and can be connected + // If login is present, then the client has credentials and can be connected // to the remote lobby server. - if (ses->license.get()) { + if (ses->login) { // At this point, we will always close the unlinked session, even if it // doesn't get converted/merged to a linked session should_close_unlinked_session = true; - // Look up the linked session for this license (if any) + // Look up the linked session for this account (if any) shared_ptr linked_ses; try { - linked_ses = server->id_to_session.at(ses->license->serial_number); + linked_ses = server->id_to_session.at(ses->login->account->account_id); linked_ses->log.info("Resuming linked session from unlinked session"); } catch (const out_of_range&) { - // If there's no open session for this license, then there must be a valid + // If there's no open session for this account, then there must be a valid // destination somewhere - either in the client config or in the unlinked // session if (ses->config.proxy_destination_address != 0) { - linked_ses = make_shared(server, ses->local_port, ses->version(), ses->license, ses->config); - linked_ses->log.info("Opened licensed session for unlinked session based on client config"); + linked_ses = make_shared(server, ses->local_port, ses->version(), ses->login, ses->config); + linked_ses->log.info("Opened logged-in session for unlinked session based on client config"); } else if (ses->next_destination.ss_family == AF_INET) { - linked_ses = make_shared(server, ses->local_port, ses->version(), ses->license, ses->next_destination); - linked_ses->log.info("Opened licensed session for unlinked session based on unlinked default destination"); + linked_ses = make_shared(server, ses->local_port, ses->version(), ses->login, ses->next_destination); + linked_ses->log.info("Opened logged-in session for unlinked session based on unlinked default destination"); } else { ses->log.error("Cannot open linked session: no valid destination in client config or unlinked session"); } } if (linked_ses.get()) { - server->id_to_session.emplace(ses->license->serial_number, linked_ses); + server->id_to_session.emplace(ses->login->account->account_id, linked_ses); // Resume the linked session using the unlinked session try { if (ses->version() == Version::BB_V4) { @@ -496,7 +481,7 @@ ProxyServer::LinkedSession::LinkedSession( id(id), log(string_printf("[ProxyServer:LinkedSession:%08" PRIX64 "] ", this->id), proxy_server_log.min_level), timeout_event(event_new(server->base.get(), -1, EV_TIMEOUT, &LinkedSession::dispatch_on_timeout, this), event_free), - license(nullptr), + login(nullptr), client_channel( version, 1, @@ -544,10 +529,10 @@ ProxyServer::LinkedSession::LinkedSession( shared_ptr server, uint16_t local_port, Version version, - shared_ptr license, + shared_ptr login, const Client::Config& config) - : LinkedSession(server, license->serial_number, local_port, version) { - this->license = license; + : LinkedSession(server, login->account->account_id, local_port, version) { + this->login = login; this->config = config; memset(&this->next_destination, 0, sizeof(this->next_destination)); struct sockaddr_in* dest_sin = reinterpret_cast(&this->next_destination); @@ -560,10 +545,10 @@ ProxyServer::LinkedSession::LinkedSession( shared_ptr server, uint16_t local_port, Version version, - std::shared_ptr license, + std::shared_ptr login, const struct sockaddr_storage& next_destination) - : LinkedSession(server, license->serial_number, local_port, version) { - this->license = license; + : LinkedSession(server, login->account->account_id, local_port, version) { + this->login = login; this->next_destination = next_destination; } @@ -631,7 +616,7 @@ void ProxyServer::LinkedSession::resume_inner( throw runtime_error("client connection is already open for this session"); } if (this->next_destination.ss_family != AF_INET) { - throw logic_error("attempted to resume an unlicensed linked session without destination set"); + throw logic_error("attempted to resume an logged-out linked session without destination set"); } this->client_channel.replace_with( @@ -795,9 +780,9 @@ void ProxyServer::LinkedSession::set_drop_mode(DropMode new_mode) { } void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) { - // If there is no license, do nothing - we can't return to the game server - // from unlicensed sessions - if (!this->license) { + // If there is no account, do nothing - we can't return to the game server + // from logged-out sessions + if (!this->login) { this->disconnect(); return; } @@ -831,7 +816,7 @@ void ProxyServer::LinkedSession::send_to_game_server(const char* error_message) if (is_v3(this->version())) { S_UpdateClientConfig_V3_04 update_client_config_cmd; update_client_config_cmd.player_tag = 0x00010000; - update_client_config_cmd.guild_card_number = this->license->serial_number; + update_client_config_cmd.guild_card_number = this->login->account->account_id; this->config.serialize_into(update_client_config_cmd.client_config); this->client_channel.send(0x04, 0x00, &update_client_config_cmd, sizeof(update_client_config_cmd)); } @@ -938,17 +923,17 @@ const unordered_map>& ProxyServ return this->id_to_session; } -shared_ptr ProxyServer::create_licensed_session( - shared_ptr l, +shared_ptr ProxyServer::create_logged_in_session( + shared_ptr login, uint16_t local_port, Version version, const Client::Config& config) { - auto session = make_shared(this->shared_from_this(), local_port, version, l, config); + auto session = make_shared(this->shared_from_this(), local_port, version, login, config); auto emplace_ret = this->id_to_session.emplace(session->id, session); if (!emplace_ret.second) { - throw runtime_error("session already exists for this license"); + throw runtime_error("session already exists for this account"); } - session->log.info("Opening licensed session"); + session->log.info("Opening logged-in session"); return emplace_ret.first->second; } diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 59cda07f..f20c70ac 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -37,7 +37,7 @@ public: std::unique_ptr timeout_event; - std::shared_ptr license; + std::shared_ptr login; Channel client_channel; Channel server_channel; @@ -136,13 +136,13 @@ public: std::shared_ptr server, uint16_t local_port, Version version, - std::shared_ptr license, + std::shared_ptr login, const Client::Config& config); LinkedSession( std::shared_ptr server, uint16_t local_port, Version version, - std::shared_ptr license, + std::shared_ptr login, const struct sockaddr_storage& next_destination); LinkedSession( std::shared_ptr server, @@ -199,8 +199,8 @@ public: std::shared_ptr get_session_by_name(const std::string& name) const; const std::unordered_map>& all_sessions() const; - std::shared_ptr create_licensed_session( - std::shared_ptr l, + std::shared_ptr create_logged_in_session( + std::shared_ptr login, uint16_t local_port, Version version, const Client::Config& config); @@ -250,7 +250,7 @@ private: // just local variables inside on_input because XB requires two commands to // get started (9E and 9F), so we need to store this state somewhere between // those commands. - std::shared_ptr license; + std::shared_ptr login; uint32_t sub_version = 0; std::string character_name; Client::Config config; @@ -281,7 +281,7 @@ private: std::unordered_map> bev_to_unlinked_session; std::unordered_set> unlinked_sessions_to_destroy; std::unordered_map> id_to_session; - uint64_t next_unlicensed_session_id; + uint64_t next_logged_out_session_id; static void dispatch_destroy_sessions(evutil_socket_t, short, void* ctx); void destroy_sessions(); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 67f97bfb..598c9592 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -31,6 +31,9 @@ const char* QUEST_BARRIER_DISCONNECT_HOOK_NAME = "quest_barrier"; const char* ADD_NEXT_CLIENT_DISCONNECT_HOOK_NAME = "add_next_game_client"; static shared_ptr proxy_options_menu_for_client(shared_ptr c) { + if (!c->login) { + throw logic_error("client is not logged in"); + } auto s = c->require_server_state(); auto ret = make_shared(MenuID::PROXY_OPTIONS, "Proxy options"); @@ -78,7 +81,8 @@ static shared_ptr proxy_options_menu_for_client(shared_ptrcheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) { + if ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || + c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)) { if (!is_ep3(c->version())) { add_flag_option(ProxyOptionsMenuItemID::INFINITE_HP, Client::Flag::INFINITE_HP_ENABLED, "Infinite HP", "Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); @@ -133,8 +137,8 @@ void send_client_to_proxy_server(shared_ptr c) { string port_name = proxy_port_name_for_version(c->version()); uint16_t local_port = s->name_to_port_config.at(port_name)->port; - s->proxy_server->delete_session(c->license->serial_number); - auto ses = s->proxy_server->create_licensed_session(c->license, local_port, c->version(), c->config); + s->proxy_server->delete_session(c->login->account->account_id); + auto ses = s->proxy_server->create_logged_in_session(c->login, local_port, c->version(), c->config); if (!c->can_use_chat_commands()) { ses->config.clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED); } @@ -279,7 +283,7 @@ static void send_main_menu(shared_ptr c) { } void on_login_complete(shared_ptr c) { - c->convert_license_to_temporary_if_nte(); + c->convert_account_to_temporary_if_nte(); // On BB, this function is called when the data server phase is done (and we // should send the ship select menu), so we don't need to check for it here. @@ -420,55 +424,32 @@ static void set_console_client_flags(shared_ptr c, uint32_t sub_version) } static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, string& data) { - const auto& cmd = check_size_t(data); + const auto& cmd = check_size_t(data); auto s = c->require_server_state(); if (c->channel.crypt_in->type() == PSOEncryption::Type::V2) { - throw runtime_error("GC trial edition client sent V3 verify license command"); + throw runtime_error("GC trial edition client sent V3 verify account command"); } set_console_client_flags(c, cmd.sub_version); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - auto l = s->license_index->verify_gc_with_password(serial_number, cmd.access_key.decode(), cmd.password.decode(), ""); - c->set_license(l); + auto password = cmd.password.decode(); + c->login = s->account_index->from_gc_credentials( + serial_number, cmd.access_key.decode(), &password, "", s->allow_unregistered_users); send_command(c, 0x9A, 0x02); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_command(c, 0x9A, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { send_command(c, 0x9A, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_password& e) { + } catch (const AccountIndex::incorrect_password& e) { send_command(c, 0x9A, 0x01); - return; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_command(c, 0x9A, 0x04); - c->should_disconnect = true; - return; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->serial_number = serial_number; - l->access_key = cmd.access_key.decode(); - l->gc_password = cmd.password.decode(); - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - send_command(c, 0x9A, 0x02); - } + } catch (const AccountIndex::missing_account& e) { + send_command(c, 0x9A, 0x04); } + + c->should_disconnect = !c->login; } static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -480,39 +461,19 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) c->log.info("Game version changed to DC_NTE"); try { - shared_ptr l = s->license_index->verify_dc_nte(cmd.serial_number.decode(), cmd.access_key.decode()); - c->set_license(l); + c->login = s->account_index->from_dc_nte_credentials( + cmd.serial_number.decode(), cmd.access_key.decode(), s->allow_unregistered_users); send_command(c, 0x88, 0x00); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { send_message_box(c, "Incorrect access key"); - c->should_disconnect = true; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->dc_nte_serial_number = cmd.serial_number.decode(); - l->dc_nte_access_key = cmd.access_key.decode(); - l->serial_number = fnv1a32(l->dc_nte_serial_number) & 0x7FFFFFFF; - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - send_command(c, 0x88, 0x00); - } + } catch (const AccountIndex::missing_account& e) { + send_message_box(c, "Incorrect serial number"); } + + c->should_disconnect = !c->login; } static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -525,46 +486,24 @@ static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) c->log.info("Game version changed to DC_NTE"); try { - shared_ptr l = s->license_index->verify_dc_nte(cmd.serial_number.decode(), cmd.access_key.decode()); - c->set_license(l); - - } catch (const LicenseIndex::no_username& e) { + c->login = s->account_index->from_dc_nte_credentials(cmd.serial_number.decode(), cmd.access_key.decode(), s->allow_unregistered_users); + } catch (const AccountIndex::no_username& e) { send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { send_message_box(c, "Incorrect access key"); + } catch (const AccountIndex::missing_account& e) { + send_message_box(c, "Incorrect serial number"); + } + + if (!c->login) { c->should_disconnect = true; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->dc_nte_serial_number = cmd.serial_number.decode(); - l->dc_nte_access_key = cmd.access_key.decode(); - l->serial_number = fnv1a32(l->dc_nte_serial_number) & 0x7FFFFFFF; - s->license_index->add(l); - if (!s->is_replay) { - l->save(); + } else { + if (cmd.is_extended) { + const auto& ext_cmd = check_size_t(data); + if (ext_cmd.extension.lobby_refs[0].menu_id == MenuID::LOBBY) { + c->preferred_lobby_id = ext_cmd.extension.lobby_refs[0].item_id; } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); } - } - - if (cmd.is_extended) { - const auto& ext_cmd = check_size_t(data); - if (ext_cmd.extension.lobby_refs[0].menu_id == MenuID::LOBBY) { - c->preferred_lobby_id = ext_cmd.extension.lobby_refs[0].item_id; - } - } - - if (!c->should_disconnect) { send_update_client_config(c, true); on_login_complete(c); } @@ -582,54 +521,22 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, string& data) { string access_key_str = cmd.access_key.decode(); uint32_t serial_number = 0; try { - shared_ptr l; if (serial_number_str.size() > 8 || access_key_str.size() > 8) { - l = s->license_index->verify_dc_nte(serial_number_str, access_key_str); + c->login = s->account_index->from_dc_nte_credentials(serial_number_str, access_key_str, s->allow_unregistered_users); } else { serial_number = stoull(serial_number_str, nullptr, 16); - l = s->license_index->verify_v1_v2(serial_number, access_key_str, ""); + c->login = s->account_index->from_dc_credentials(serial_number, access_key_str, "", s->allow_unregistered_users); } - c->set_license(l); - send_command(c, 0x90, 0x02); + send_command(c, 0x90, 0x01); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_command(c, 0x90, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { + send_command(c, 0x90, 0x03); + } catch (const AccountIndex::missing_account& e) { send_command(c, 0x90, 0x03); - c->should_disconnect = true; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_command(c, 0x90, 0x03); - c->should_disconnect = true; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - if (serial_number_str.size() > 8 || access_key_str.size() > 8) { - l->dc_nte_serial_number = serial_number_str; - l->dc_nte_access_key = access_key_str; - l->serial_number = fnv1a32(l->dc_nte_serial_number) & 0x7FFFFFFF; - } else if (serial_number != 0) { - l->serial_number = serial_number; - l->access_key = cmd.access_key.decode(); - } else { - send_command(c, 0x90, 0x03); - c->should_disconnect = true; - return; - } - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - send_command(c, 0x90, 0x01); - } } + c->should_disconnect = !c->login; } static void on_92_DC(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -657,53 +564,24 @@ static void on_93_DC(shared_ptr c, uint16_t, uint32_t, string& data) { string access_key_str = cmd.access_key.decode(); uint32_t serial_number = 0; try { - shared_ptr l; if (serial_number_str.size() > 8 || access_key_str.size() > 8) { - l = s->license_index->verify_dc_nte(serial_number_str, access_key_str); + c->login = s->account_index->from_dc_nte_credentials(serial_number_str, access_key_str, s->allow_unregistered_users); } else { serial_number = stoull(serial_number_str, nullptr, 16); - l = s->license_index->verify_v1_v2(serial_number, access_key_str, cmd.name.decode()); + c->login = s->account_index->from_dc_credentials( + serial_number, access_key_str, cmd.name.decode(), s->allow_unregistered_users); } - c->set_license(l); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { send_message_box(c, "Incorrect access key"); + } catch (const AccountIndex::missing_account& e) { + send_message_box(c, "Incorrect serial number"); + } + if (!c->login) { c->should_disconnect = true; return; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - return; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - if (serial_number_str.size() > 8 || access_key_str.size() > 8) { - l->dc_nte_serial_number = serial_number_str; - l->dc_nte_access_key = access_key_str; - l->serial_number = fnv1a32(l->dc_nte_serial_number) & 0x7FFFFFFF; - } else if (serial_number != 0) { - l->serial_number = serial_number; - l->access_key = cmd.access_key.decode(); - } else { - send_command(c, 0x90, 0x03); - c->should_disconnect = true; - return; - } - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - } } if (cmd.is_extended) { @@ -737,15 +615,17 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { set_console_client_flags(c, cmd.sub_version); - uint32_t serial_number = 0; try { - shared_ptr l; switch (c->version()) { - case Version::DC_V2: + case Version::DC_V2: { + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); + c->login = s->account_index->from_dc_credentials( + serial_number, cmd.access_key.decode(), "", s->allow_unregistered_users); + break; + } case Version::PC_NTE: case Version::PC_V2: { - if ((c->version() != Version::DC_V2) && - (cmd.sub_version == 0x29) && + if ((cmd.sub_version == 0x29) && cmd.v1_serial_number.empty() && cmd.v1_access_key.empty() && cmd.serial_number.empty() && @@ -755,21 +635,12 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { cmd.email_address.empty()) { c->channel.version = Version::PC_NTE; c->log.info("Changed client version to PC_NTE"); - if (!s->allow_unregistered_users || !s->allow_pc_nte) { - throw LicenseIndex::no_username(); - } else { - serial_number = cmd.guild_card_number; - while ((serial_number == 0xFFFFFFFF) || s->license_index->get(serial_number)) { - serial_number = random_object() & 0x7FFFFFFF; - } - auto l = s->license_index->create_temporary_license(); - l->serial_number = serial_number; - string l_str = l->str(); - c->log.info("Created temporary license for PC NTE client %s", l_str.c_str()); - } + c->login = s->account_index->from_pc_nte_credentials( + cmd.guild_card_number, s->allow_unregistered_users && s->allow_pc_nte); } else { - serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); - l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), ""); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); + c->login = s->account_index->from_pc_credentials( + serial_number, cmd.access_key.decode(), "", s->allow_unregistered_users); } break; } @@ -777,55 +648,30 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { case Version::GC_V3: case Version::GC_EP3_NTE: case Version::GC_EP3: { - serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); - l = s->license_index->verify_gc_no_password(serial_number, cmd.access_key.decode(), ""); + uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); + // On V3, the client should have sent a DB command containing the + // password already, which should have created an account if needed. So + // if no account exists at this point, disconnect the client even if + // unregistered users are allowed. + c->login = s->account_index->from_gc_credentials(serial_number, cmd.access_key.decode(), nullptr, "", false); break; } default: throw runtime_error("unsupported versioned command"); } - c->set_license(l); send_command(c, 0x9A, 0x02); - } catch (const LicenseIndex::no_username& e) { + + } catch (const AccountIndex::no_username& e) { send_command(c, 0x9A, 0x03); - c->should_disconnect = true; - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { send_command(c, 0x9A, 0x03); - c->should_disconnect = true; - } catch (const LicenseIndex::incorrect_password& e) { + } catch (const AccountIndex::incorrect_password& e) { send_command(c, 0x9A, 0x01); - c->should_disconnect = true; - } catch (const LicenseIndex::missing_license& e) { - // On V3, the client should have sent a different command containing the - // password already, which should have created and added a license. So, if - // no license exists at this point, disconnect the client even if - // unregistered clients are allowed. - shared_ptr l; - if (is_v3(c->version())) { - send_command(c, 0x9A, 0x04); - c->should_disconnect = true; - - } else if (!s->allow_unregistered_users || (serial_number == 0)) { - send_command(c, 0x9A, 0x03); - c->should_disconnect = true; - - } else if (is_v1_or_v2(c->version())) { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->serial_number = serial_number; - l->access_key = cmd.access_key.decode(); - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - send_command(c, 0x9A, 0x02); - } else { - throw runtime_error("unsupported game version"); - } + } catch (const AccountIndex::missing_account& e) { + send_command(c, 0x9A, 0x03); } + + c->should_disconnect = !c->login; } static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -837,59 +683,36 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l; switch (c->version()) { case Version::DC_V2: + c->login = s->account_index->from_dc_credentials(serial_number, cmd.access_key.decode(), "", false); + break; case Version::PC_V2: - l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), ""); + c->login = s->account_index->from_pc_credentials(serial_number, cmd.access_key.decode(), "", false); break; case Version::GC_NTE: case Version::GC_V3: case Version::GC_EP3_NTE: - case Version::GC_EP3: - l = s->license_index->verify_gc_with_password(serial_number, cmd.access_key.decode(), cmd.password.decode(), ""); + case Version::GC_EP3: { + string password = cmd.password.decode(); + c->login = s->account_index->from_gc_credentials(serial_number, cmd.access_key.decode(), &password, "", false); break; + } default: // TODO: PC_NTE can probably send 9C, but due to the way we've // implemented PC_NTE's login sequence, it never should send 9C. throw logic_error("unsupported versioned command"); } - c->set_license(l); send_command(c, 0x9C, 0x01); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_message_box(c, "Incorrect serial number"); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_password& e) { + } catch (const AccountIndex::incorrect_password& e) { + send_command(c, 0x9C, 0x00); + } catch (const AccountIndex::missing_account& e) { send_command(c, 0x9C, 0x00); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_command(c, 0x9C, 0x00); - c->should_disconnect = true; - return; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->serial_number = serial_number; - l->access_key = cmd.access_key.decode(); - if (is_gc(c->version())) { - l->gc_password = cmd.password.decode(); - } - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - send_command(c, 0x9C, 0x01); - } } + c->should_disconnect = !c->login; } static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& data) { @@ -949,15 +772,17 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d c->config.set_flag(Client::Flag::NO_SEND_FUNCTION_CALL); } - uint32_t serial_number = 0; try { - shared_ptr l; switch (c->version()) { - case Version::DC_V2: + case Version::DC_V2: { + uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); + c->login = s->account_index->from_dc_credentials( + serial_number, base_cmd->access_key.decode(), base_cmd->name.decode(), s->allow_unregistered_users); + break; + } case Version::PC_NTE: case Version::PC_V2: - if ((c->version() != Version::DC_V2) && - (base_cmd->sub_version == 0x29) && + if ((base_cmd->sub_version == 0x29) && base_cmd->v1_serial_number.empty() && base_cmd->v1_access_key.empty() && base_cmd->serial_number.empty() && @@ -966,76 +791,45 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d base_cmd->access_key2.empty()) { c->channel.version = Version::PC_NTE; c->log.info("Changed client version to PC_NTE"); - if (!s->allow_unregistered_users || !s->allow_pc_nte) { - throw LicenseIndex::no_username(); - } else { - serial_number = base_cmd->guild_card_number; - while ((serial_number == 0xFFFFFFFF) || s->license_index->get(serial_number)) { - serial_number = random_object() & 0x7FFFFFFF; - } - auto l = s->license_index->create_temporary_license(); - l->serial_number = serial_number; - string l_str = l->str(); - c->log.info("Created temporary license for PC NTE client %s", l_str.c_str()); - } + c->login = s->account_index->from_pc_nte_credentials( + base_cmd->guild_card_number, s->allow_unregistered_users && s->allow_pc_nte); } else { - serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); - l = s->license_index->verify_v1_v2(serial_number, base_cmd->access_key.decode(), base_cmd->name.decode()); + uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); + c->login = s->account_index->from_pc_credentials( + serial_number, base_cmd->access_key.decode(), base_cmd->name.decode(), s->allow_unregistered_users); } break; case Version::GC_NTE: case Version::GC_V3: case Version::GC_EP3_NTE: - case Version::GC_EP3: - serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); - l = s->license_index->verify_gc_no_password(serial_number, base_cmd->access_key.decode(), base_cmd->name.decode()); + case Version::GC_EP3: { + uint32_t serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); + // GC clients should have sent a DB command first which would have + // created the account if needed + c->login = s->account_index->from_gc_credentials( + serial_number, base_cmd->access_key.decode(), nullptr, base_cmd->name.decode(), false); break; + } default: throw logic_error("unsupported versioned command"); } - c->set_license(l); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_command(c, 0x04, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { send_command(c, 0x04, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_password& e) { + } catch (const AccountIndex::incorrect_password& e) { send_command(c, 0x04, 0x06); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users || (serial_number == 0)) { - send_command(c, 0x04, 0x04); - c->should_disconnect = true; - return; - - } else if (is_v1_or_v2(c->version()) || is_v3(c->version())) { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->serial_number = serial_number; - l->access_key = base_cmd->access_key.decode(); - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - - } else { - throw runtime_error("unsupported game version"); - } + } catch (const AccountIndex::missing_account& e) { + send_command(c, 0x04, 0x04); } - send_update_client_config(c, true); - on_login_complete(c); + if (!c->login) { + c->should_disconnect = true; + } else { + send_update_client_config(c, true); + on_login_complete(c); + } } static void on_9E_XB(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -1060,54 +854,39 @@ static void on_9E_XB(shared_ptr c, uint16_t, uint32_t, string& data) { uint64_t xb_user_id = stoull(cmd.access_key.decode(), nullptr, 16); uint64_t xb_account_id = cmd.netloc.account_id; try { - shared_ptr l = s->license_index->verify_xb(xb_gamertag, xb_user_id, xb_account_id); + c->login = s->account_index->from_xb_credentials(xb_gamertag, xb_user_id, xb_account_id, s->allow_unregistered_users); bool should_save = false; - if (l->xb_user_id == 0) { - l->xb_user_id = xb_user_id; - c->log.info("Set license XB user ID to %016" PRIX64, l->xb_user_id); + if (c->login->xb_license->user_id == 0) { + c->login->xb_license->user_id = xb_user_id; + c->log.info("Set license XB user ID to %016" PRIX64, c->login->xb_license->user_id); should_save = true; } - if (l->xb_account_id == 0) { - l->xb_account_id = xb_account_id; - c->log.info("Set license XB account ID to %016" PRIX64, l->xb_account_id); + if (c->login->xb_license->account_id == 0) { + c->login->xb_license->account_id = xb_account_id; + c->log.info("Set license XB account ID to %016" PRIX64, c->login->xb_license->account_id); should_save = true; } if (should_save && !s->is_replay) { - l->save(); + c->login->account->save(); } - c->set_license(l); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_command(c, 0x04, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_access_key& e) { + } catch (const AccountIndex::incorrect_access_key& e) { + send_command(c, 0x04, 0x03); + } catch (const AccountIndex::missing_account& e) { send_command(c, 0x04, 0x03); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::missing_license& e) { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->serial_number = fnv1a32(xb_gamertag) & 0x7FFFFFFF; - l->xb_gamertag = xb_gamertag; - l->xb_user_id = xb_user_id; - l->xb_account_id = xb_account_id; - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); } - // The 9E command doesn't include the client config, so we need to request it - // separately with a 9F command. The 9F handler will call on_login_complete. - // Note that we can't send this command immediately after the 02/17 command; - // if we do, the client doesn't decrypt it properly and won't respond. - send_command(c, 0x9F, 0x00); + if (!c->login) { + c->should_disconnect = true; + } else { + // The 9E command doesn't include the client config, so we need to request it + // separately with a 9F command. The 9F handler will call on_login_complete. + // Note that we can't send this command immediately after the 02/17 command; + // if we do, the client doesn't decrypt it properly and won't respond. + send_command(c, 0x9F, 0x00); + } } static void scramble_bb_security_data(parray& data, uint8_t which, bool reverse) { @@ -1161,38 +940,18 @@ static void on_93_BB(shared_ptr c, uint16_t, uint32_t, string& data) { string password = base_cmd.password.decode(); try { - auto l = s->license_index->verify_bb(username, password); - c->set_license(l); + c->login = s->account_index->from_bb_credentials(username, &password, s->allow_unregistered_users); - } catch (const LicenseIndex::no_username& e) { + } catch (const AccountIndex::no_username& e) { send_message_box(c, "Username is missing"); - c->should_disconnect = true; - return; - - } catch (const LicenseIndex::incorrect_password& e) { + } catch (const AccountIndex::incorrect_password& e) { send_message_box(c, "Incorrect login password"); + } catch (const AccountIndex::missing_account& e) { + send_message_box(c, "You are not registered on this server"); + } + if (!c->login) { c->should_disconnect = true; return; - - } catch (const LicenseIndex::missing_license& e) { - if (!s->allow_unregistered_users) { - send_message_box(c, "You are not registered on this server"); - c->should_disconnect = true; - return; - } else { - c->config.set_flag(Client::Flag::LICENSE_WAS_CREATED); - auto l = s->license_index->create_license(); - l->serial_number = fnv1a32(username) & 0x7FFFFFFF; - l->bb_username = username; - l->bb_password = password; - s->license_index->add(l); - if (!s->is_replay) { - l->save(); - } - c->set_license(l); - string l_str = l->str(); - c->log.info("Created license %s", l_str.c_str()); - } } if (base_cmd.guild_card_number != 0) { @@ -1307,18 +1066,18 @@ static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, string& current_meseta = 1000000; total_meseta_earned = 1000000; } else if (is_lobby && s->ep3_jukebox_is_free) { - current_meseta = c->license->ep3_current_meseta; - total_meseta_earned = c->license->ep3_total_meseta_earned; + current_meseta = c->login->account->ep3_current_meseta; + total_meseta_earned = c->login->account->ep3_total_meseta_earned; } else { - if (c->license->ep3_current_meseta < in_cmd.value) { + if (c->login->account->ep3_current_meseta < in_cmd.value) { throw runtime_error("meseta overdraft not allowed"); } - c->license->ep3_current_meseta -= in_cmd.value; + c->login->account->ep3_current_meseta -= in_cmd.value; if (!s->is_replay) { - c->license->save(); + c->login->account->save(); } - current_meseta = c->license->ep3_current_meseta; - total_meseta_earned = c->license->ep3_total_meseta_earned; + current_meseta = c->login->account->ep3_current_meseta; + total_meseta_earned = c->login->account->ep3_total_meseta_earned; } S_MesetaTransaction_Ep3_BA out_cmd = {current_meseta, total_meseta_earned, in_cmd.request_token}; @@ -1452,7 +1211,7 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr l, int16_t ta // present and rearrange their client IDs to match their team positions unordered_map> game_clients; if (tourn_match) { - unordered_map required_serial_numbers; + unordered_map required_account_ids; auto add_team_players = [&](shared_ptr team, size_t base_index) -> void { size_t z = 0; for (const auto& player : team->players) { @@ -1460,7 +1219,7 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr l, int16_t ta throw logic_error("more than 2 players on team"); } if (player.is_human()) { - required_serial_numbers.emplace(base_index + z, player.serial_number); + required_account_ids.emplace(base_index + z, player.account_id); } z++; } @@ -1468,17 +1227,17 @@ static bool start_ep3_battle_table_game_if_ready(shared_ptr l, int16_t ta add_team_players(tourn_match->preceding_a->winner_team, 0); add_team_players(tourn_match->preceding_b->winner_team, 2); - for (const auto& it : required_serial_numbers) { + for (const auto& it : required_account_ids) { size_t client_id = it.first; - uint32_t serial_number = it.second; + uint32_t account_id = it.second; for (const auto& it : table_clients) { - if (it.second->license->serial_number == serial_number) { + if (it.second->login->account->account_id == account_id) { game_clients.emplace(client_id, it.second); } } } - if (game_clients.size() != required_serial_numbers.size()) { + if (game_clients.size() != required_account_ids.size()) { // Not all tournament match participants are present, so we can't start // the tournament match. (But they can still use the battle table) tourn_match.reset(); @@ -1711,7 +1470,7 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { PlayerLobbyDataDCGC lobby_data; lobby_data.name.encode(existing_p->disp.name.decode(existing_c->language()), c->language()); lobby_data.player_tag = 0x00010000; - lobby_data.guild_card_number = existing_c->license->serial_number; + lobby_data.guild_card_number = existing_c->login->account->account_id; l->battle_record->add_player( lobby_data, existing_p->inventory, @@ -1767,10 +1526,10 @@ static void on_CA_Ep3(shared_ptr c, uint16_t, uint32_t, string& data) { if (player.is_human()) { auto winner_c = player.client.lock(); if (winner_c) { - winner_c->license->ep3_current_meseta += meseta_reward; - winner_c->license->ep3_total_meseta_earned += meseta_reward; + winner_c->login->account->ep3_current_meseta += meseta_reward; + winner_c->login->account->ep3_total_meseta_earned += meseta_reward; if (!s->is_replay) { - winner_c->license->save(); + winner_c->login->account->save(); } send_ep3_rank_update(winner_c); } @@ -1809,7 +1568,7 @@ static void on_E2_Ep3(shared_ptr c, uint16_t, uint32_t flag, string&) { if (tourn) { if (tourn->get_state() != Episode3::Tournament::State::COMPLETE) { auto s = c->require_server_state(); - team->unregister_player(c->license->serial_number); + team->unregister_player(c->login->account->account_id); on_tournament_bracket_updated(s, tourn); } c->ep3_tournament_team.reset(); @@ -2007,10 +1766,10 @@ static void on_09(shared_ptr c, uint16_t, uint32_t, string& data) { for (const auto& player : team->players) { if (player.is_human()) { if (player.player_name.empty()) { - message += string_printf("\n $C6%08" PRIX32 "$C7", player.serial_number); + message += string_printf("\n $C6%08" PRIX32 "$C7", player.account_id); } else { string player_name = escape_player_name(player.player_name); - message += string_printf("\n $C6%s$C7 (%08" PRIX32 ")", player_name.c_str(), player.serial_number); + message += string_printf("\n $C6%s$C7 (%08" PRIX32 ")", player_name.c_str(), player.account_id); } } else { string player_name = escape_player_name(player.com_deck->player_name); @@ -2557,7 +2316,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { shared_ptr l = c->lobby.lock(); Episode episode = l ? l->episode : Episode::NONE; QuestIndex::IncludeCondition include_condition = nullptr; - if (l && !c->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (l && !c->login->account->check_flag(Account::Flag::DISABLE_QUEST_REQUIREMENTS)) { include_condition = l->quest_include_condition(); } @@ -2692,7 +2451,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { } if (team_name.empty()) { team_name = c->character()->disp.name.decode(c->language()); - team_name += string_printf("/%" PRIX32, c->license->serial_number); + team_name += string_printf("/%" PRIX32, c->login->account->account_id); } auto s = c->require_server_state(); uint16_t tourn_num = item_id >> 16; @@ -3030,7 +2789,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri c->v1_v2_last_reported_disp = make_unique(cmd.disp); player->inventory = cmd.inventory; player->disp = cmd.disp.to_bb(player->inventory.language, player->inventory.language); - c->license->last_player_name = player->disp.name.decode(player->inventory.language); + c->login->account->last_player_name = player->disp.name.decode(player->inventory.language); break; } case Version::DC_V2: { @@ -3041,7 +2800,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri player->battle_records = cmd.records.battle; player->challenge_records = cmd.records.challenge; player->choice_search_config = cmd.choice_search_config; - c->license->last_player_name = player->disp.name.decode(player->inventory.language); + c->login->account->last_player_name = player->disp.name.decode(player->inventory.language); break; } case Version::PC_NTE: @@ -3065,12 +2824,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } catch (const runtime_error& e) { c->log.warning("Failed to decode auto-reply message: %s", e.what()); } - c->license->auto_reply_message = auto_reply; + c->login->account->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); - c->license->auto_reply_message.clear(); + c->login->account->auto_reply_message.clear(); } - c->license->last_player_name = player->disp.name.decode(player->inventory.language); + c->login->account->last_player_name = player->disp.name.decode(player->inventory.language); break; } case Version::GC_NTE: { @@ -3093,12 +2852,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } catch (const runtime_error& e) { c->log.warning("Failed to decode auto-reply message: %s", e.what()); } - c->license->auto_reply_message = auto_reply; + c->login->account->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); - c->license->auto_reply_message.clear(); + c->login->account->auto_reply_message.clear(); } - c->license->last_player_name = player->disp.name.decode(player->inventory.language); + c->login->account->last_player_name = player->disp.name.decode(player->inventory.language); break; } @@ -3125,7 +2884,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri if (c->config.specific_version == 0x33000000) { c->config.specific_version = 0x33534A54; // 3SJT } - c->convert_license_to_temporary_if_nte(); + c->convert_account_to_temporary_if_nte(); } cmd = &check_size_t(data, 0xFFFF); } @@ -3169,12 +2928,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } catch (const runtime_error& e) { c->log.warning("Failed to decode auto-reply message: %s", e.what()); } - c->license->auto_reply_message = auto_reply; + c->login->account->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); - c->license->auto_reply_message.clear(); + c->login->account->auto_reply_message.clear(); } - c->license->last_player_name = player->disp.name.decode(player->inventory.language); + c->login->account->last_player_name = player->disp.name.decode(player->inventory.language); break; } case Version::BB_V4: { @@ -3197,12 +2956,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } catch (const runtime_error& e) { c->log.warning("Failed to decode auto-reply message: %s", e.what()); } - c->license->auto_reply_message = auto_reply; + c->login->account->auto_reply_message = auto_reply; } else { player->auto_reply.clear(); - c->license->auto_reply_message.clear(); + c->login->account->auto_reply_message.clear(); } - c->license->last_player_name = player->disp.name.decode(player->inventory.language); + c->login->account->last_player_name = player->disp.name.decode(player->inventory.language); break; } default: @@ -3210,7 +2969,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } player->inventory.decode_from_client(c->version()); c->channel.language = player->inventory.language; - c->license->save(); + c->login->account->save(); string name_str = player->disp.name.decode(c->language()); c->channel.name = string_printf("C-%" PRIX64 " (%s)", c->id, name_str.c_str()); @@ -3229,14 +2988,12 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri c->pending_character_export.reset(); string filename; - if (pending_export->is_bb_conversion) { + if (pending_export->dest_bb_license) { filename = Client::character_filename( - pending_export->license->bb_username, - pending_export->character_index); + pending_export->dest_bb_license->username, pending_export->character_index); } else { filename = Client::backup_character_filename( - pending_export->license->serial_number, - pending_export->character_index); + pending_export->dest_account->account_id, pending_export->character_index); } if (s->player_files_manager->get_character(filename)) { @@ -3244,7 +3001,7 @@ static void on_61_98(shared_ptr c, uint16_t command, uint32_t flag, stri } else { auto bb_player = PSOBBCharacterFile::create_from_config( - pending_export->license->serial_number, + pending_export->dest_account->account_id, c->language(), player->disp.visual, player->disp.name.decode(c->language()), @@ -3300,13 +3057,13 @@ static void on_30(shared_ptr c, uint16_t, uint32_t, string& data) { c->pending_character_export.reset(); string filename; - if (pending_export->is_bb_conversion) { + if (pending_export->dest_bb_license) { filename = Client::character_filename( - pending_export->license->bb_username, + pending_export->dest_bb_license->username, pending_export->character_index); } else { filename = Client::backup_character_filename( - pending_export->license->serial_number, + pending_export->dest_account->account_id, pending_export->character_index); } @@ -3410,7 +3167,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { bool should_hide_contents = (!(l->check_flag(Lobby::Flag::IS_SPECTATOR_TEAM))) && (private_flags & (1 << x)); const string& effective_text = should_hide_contents ? whisper_text : text; try { - send_chat_message(l->clients[x], c->license->serial_number, from_name, effective_text, private_flags); + send_chat_message(l->clients[x], c->login->account->account_id, from_name, effective_text, private_flags); } catch (const runtime_error& e) { l->clients[x]->log.warning("Failed to encode chat message: %s", e.what()); } @@ -3420,7 +3177,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { for (size_t x = 0; x < watcher_l->max_clients; x++) { if (watcher_l->clients[x]) { try { - send_chat_message(watcher_l->clients[x], c->license->serial_number, from_name, text, private_flags); + send_chat_message(watcher_l->clients[x], c->login->account->account_id, from_name, text, private_flags); } catch (const runtime_error& e) { watcher_l->clients[x]->log.warning("Failed to encode chat message: %s", e.what()); } @@ -3437,7 +3194,7 @@ static void on_06(shared_ptr c, uint16_t, uint32_t, string& data) { p->disp.name.decode(c->language()), text, private_flags); - l->battle_record->add_chat_message(c->license->serial_number, std::move(prepared_message)); + l->battle_record->add_chat_message(c->login->account->account_id, std::move(prepared_message)); } catch (const runtime_error& e) { l->log.warning("Failed to encode chat message for battle record: %s", e.what()); } @@ -3459,7 +3216,7 @@ static void on_E3_BB(shared_ptr c, uint16_t, uint32_t, string& data) { send_approve_player_choice_bb(c); } else { - if (!c->license) { + if (!c->login) { c->should_disconnect = true; return; } @@ -3648,7 +3405,7 @@ static void on_EC_BB(shared_ptr, uint16_t, uint32_t, string& data) { static void on_E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { const auto& cmd = check_size_t(data); - if (!c->license) { + if (!c->login) { send_message_box(c, "$C6You are not logged in."); return; } @@ -3671,7 +3428,7 @@ static void on_E5_BB(shared_ptr c, uint16_t, uint32_t, string& data) { } else { try { auto s = c->require_server_state(); - c->create_character_file(c->license->serial_number, c->language(), cmd.preview, s->level_table); + c->create_character_file(c->login->account->account_id, c->language(), cmd.preview, s->level_table); } catch (const exception& e) { send_message_box(c, string_printf("$C6New character could not be created:\n%s", e.what())); return; @@ -3868,7 +3625,7 @@ static void on_40(shared_ptr c, uint16_t, uint32_t, string& data) { try { auto s = c->require_server_state(); auto result = s->find_client(nullptr, cmd.target_guild_card_number); - if (!result->blocked_senders.count(c->license->serial_number)) { + if (!result->blocked_senders.count(c->login->account->account_id)) { auto result_lobby = result->lobby.lock(); if (result_lobby) { send_card_search_result(c, result, result_lobby); @@ -3893,7 +3650,7 @@ static void on_choice_search_t(shared_ptr c, const ChoiceSearchConfig& c vector results; for (const auto& l : s->all_lobbies()) { for (const auto& lc : l->clients) { - if (!lc || lc->character()->choice_search_config.disabled) { + if (!lc || !lc->login || lc->character()->choice_search_config.disabled) { continue; } @@ -3916,7 +3673,7 @@ static void on_choice_search_t(shared_ptr c, const ChoiceSearchConfig& c if (is_match) { auto lp = lc->character(); auto& result = results.emplace_back(); - result.guild_card_number = lc->license->serial_number; + result.guild_card_number = lc->login->account->account_id; result.name.encode(lp->disp.name.decode(lc->language()), c->language()); string info_string = string_printf("%s Lv%zu\n%s\n", name_for_char_class(lp->disp.visual.char_class), @@ -4031,38 +3788,38 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, string& data) { } catch (const out_of_range&) { } - if (!target) { + if (!target || !target->login) { // TODO: We should store pending messages for accounts somewhere, and send // them when the player signs on again. if (!c->blocked_senders.count(to_guild_card_number)) { try { - auto target_license = s->license_index->get(to_guild_card_number); - if (!target_license->auto_reply_message.empty()) { + auto target_account = s->account_index->from_account_id(to_guild_card_number); + if (!target_account->auto_reply_message.empty()) { send_simple_mail( c, - target_license->serial_number, - target_license->last_player_name, - target_license->auto_reply_message); + target_account->account_id, + target_account->last_player_name, + target_account->auto_reply_message); } - } catch (const LicenseIndex::missing_license&) { + } catch (const AccountIndex::missing_account&) { } } send_text_message(c, "$C6Player is offline"); } else { // If the sender is blocked, don't forward the mail - if (target->blocked_senders.count(c->license->serial_number)) { + if (target->blocked_senders.count(c->login->account->account_id)) { return; } // If the target has auto-reply enabled, send the autoreply. Note that we also // forward the message in this case. - if (!c->blocked_senders.count(target->license->serial_number)) { + if (!c->blocked_senders.count(target->login->account->account_id)) { auto target_p = target->character(); if (!target_p->auto_reply.empty()) { send_simple_mail( c, - target->license->serial_number, + target->login->account->account_id, target_p->disp.name.decode(target_p->inventory.language), target_p->auto_reply.decode(target_p->inventory.language)); } @@ -4071,7 +3828,7 @@ static void on_81(shared_ptr c, uint16_t, uint32_t, string& data) { // Forward the message send_simple_mail( target, - c->license->serial_number, + c->login->account->account_id, c->character()->disp.name.decode(c->language()), message); } @@ -4110,15 +3867,15 @@ void on_C7(shared_ptr c, uint16_t, uint32_t, string& data) { c->log.warning("Failed to decode auto-reply message: %s", e.what()); return; } - c->license->auto_reply_message = message; - c->license->save(); + c->login->account->auto_reply_message = message; + c->login->account->save(); } static void on_C8(shared_ptr c, uint16_t, uint32_t, string& data) { check_size_v(data.size(), 0); c->character(true, false)->auto_reply.clear(); - c->license->auto_reply_message.clear(); - c->license->save(); + c->login->account->auto_reply_message.clear(); + c->login->account->save(); } static void on_C6(shared_ptr c, uint16_t, uint32_t, string& data) { @@ -4165,7 +3922,7 @@ shared_ptr create_game_generic( size_t min_level = s->default_min_level_for_game(c->version(), episode, difficulty); auto p = c->character(); - if (!c->license->check_flag(License::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) { + if (!c->login->account->check_flag(Account::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) { // Note: We don't throw here because this is a situation players might // actually encounter while playing the game normally string msg = string_printf("You must be level %zu\nor above to play\nthis difficulty.", static_cast(min_level + 1)); @@ -4975,14 +4732,14 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri string team_name = cmd.name.decode(c->language()); if (s->team_index->get_by_name(team_name)) { send_command(c, 0x02EA, 0x00000002); - } else if (c->license->bb_team_id != 0) { + } else if (c->login->account->bb_team_id != 0) { // TODO: What's the right error code to use here? send_command(c, 0x02EA, 0x00000001); } else { string player_name = c->character()->disp.name.decode(c->language()); - auto team = s->team_index->create(team_name, c->license->serial_number, player_name); - c->license->bb_team_id = team->team_id; - c->license->save(); + auto team = s->team_index->create(team_name, c->login->account->account_id, player_name); + c->login->account->bb_team_id = team->team_id; + c->login->account->save(); send_command(c, 0x02EA, 0x00000000); send_update_team_metadata_for_client(c); @@ -4993,7 +4750,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } case 0x03EA: { // Add team member auto team = c->team(); - if (team && team->members.at(c->license->serial_number).privilege_level() >= 0x30) { + if (team && team->members.at(c->login->account->account_id).privilege_level() >= 0x30) { const auto& cmd = check_size_t(data); auto s = c->require_server_state(); shared_ptr added_c; @@ -5003,7 +4760,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri send_command(c, 0x04EA, 0x00000006); } - if (added_c) { + if (added_c && added_c->login) { auto added_c_team = added_c->team(); if (added_c_team) { send_command(c, 0x04EA, 0x00000001); @@ -5015,11 +4772,11 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri send_command(added_c, 0x04EA, 0x00000005); } else { - added_c->license->bb_team_id = team->team_id; - added_c->license->save(); + added_c->login->account->bb_team_id = team->team_id; + added_c->login->account->save(); s->team_index->add_member( team->team_id, - added_c->license->serial_number, + added_c->login->account->account_id, added_c->character()->disp.name.decode(added_c->language())); send_update_team_metadata_for_client(added_c); @@ -5035,13 +4792,13 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri auto team = c->team(); if (team) { const auto& cmd = check_size_t(data); - bool is_removing_self = (cmd.guild_card_number == c->license->serial_number); + bool is_removing_self = (cmd.guild_card_number == c->login->account->account_id); if (is_removing_self || - (team->members.at(c->license->serial_number).privilege_level() >= 0x30)) { + (team->members.at(c->login->account->account_id).privilege_level() >= 0x30)) { s->team_index->remove_member(cmd.guild_card_number); - auto removed_license = s->license_index->get(cmd.guild_card_number); - removed_license->bb_team_id = 0; - removed_license->save(); + auto removed_account = s->account_index->from_account_id(cmd.guild_card_number); + removed_account->bb_team_id = 0; + removed_account->save(); send_command(c, 0x06EA, 0x00000000); shared_ptr removed_c; @@ -5072,7 +4829,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri if (ends_with(data, required_end)) { for (const auto& it : team->members) { try { - auto target_c = s->find_client(nullptr, it.second.serial_number); + auto target_c = s->find_client(nullptr, it.second.account_id); send_command(target_c, 0x07EA, 0x00000000, data); } catch (const out_of_range&) { } @@ -5097,12 +4854,12 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } case 0x0FEA: { // Set team flag auto team = c->team(); - if (team && team->members.at(c->license->serial_number).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { + if (team && team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { const auto& cmd = check_size_t(data); s->team_index->set_flag_data(team->team_id, cmd.flag_data); for (const auto& it : team->members) { try { - auto member_c = s->find_client(nullptr, it.second.serial_number); + auto member_c = s->find_client(nullptr, it.second.account_id); send_update_team_metadata_for_client(member_c); } catch (const out_of_range&) { } @@ -5112,13 +4869,13 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } case 0x10EA: { // Disband team auto team = c->team(); - if (team && team->members.at(c->license->serial_number).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { + if (team && team->members.at(c->login->account->account_id).check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { s->team_index->disband(team->team_id); send_command(c, 0x10EA, 0x00000000); for (const auto& it : team->members) { try { - auto member_c = s->find_client(nullptr, it.second.serial_number); + auto member_c = s->find_client(nullptr, it.second.account_id); send_update_team_metadata_for_client(member_c); send_team_membership_info(member_c); } catch (const out_of_range&) { @@ -5131,7 +4888,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri auto team = c->team(); if (team) { auto& cmd = check_size_t(data); - if (cmd.guild_card_number == c->license->serial_number) { + if (cmd.guild_card_number == c->login->account->account_id) { throw runtime_error("this command cannot be used to modify your own permissions"); } @@ -5141,7 +4898,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri bool send_master_transfer_updates = false; switch (flag) { case 0x00: // Demote member - if (s->team_index->demote_leader(c->license->serial_number, cmd.guild_card_number)) { + if (s->team_index->demote_leader(c->login->account->account_id, cmd.guild_card_number)) { send_command(c, 0x11EA, 0x00000000); send_updates_for_other_m = true; } else { @@ -5149,7 +4906,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } break; case 0x30: // Promote member - if (s->team_index->promote_leader(c->license->serial_number, cmd.guild_card_number)) { + if (s->team_index->promote_leader(c->login->account->account_id, cmd.guild_card_number)) { send_command(c, 0x11EA, 0x00000000); send_updates_for_other_m = true; } else { @@ -5157,7 +4914,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri } break; case 0x40: // Transfer master - s->team_index->change_master(c->license->serial_number, cmd.guild_card_number); + s->team_index->change_master(c->login->account->account_id, cmd.guild_card_number); send_command(c, 0x11EA, 0x00000000); send_updates_for_this_m = true; send_updates_for_other_m = true; @@ -5170,7 +4927,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri if (send_master_transfer_updates) { for (const auto& it : team->members) { try { - auto other_c = s->find_client(nullptr, it.second.serial_number); + auto other_c = s->find_client(nullptr, it.second.account_id); send_update_lobby_data_bb(other_c); } catch (const out_of_range&) { } @@ -5224,7 +4981,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri if (reward.reward_flag != TeamIndex::Team::RewardFlag::NONE) { for (const auto& it : team->members) { try { - auto member_c = s->find_client(nullptr, it.second.serial_number); + auto member_c = s->find_client(nullptr, it.second.account_id); send_update_team_reward_flags(member_c); } catch (const out_of_range&) { } @@ -5253,7 +5010,7 @@ static void on_EA_BB(shared_ptr c, uint16_t command, uint32_t flag, stri send_command(c, 0x1FEA, 0x00000000); for (const auto& it : team->members) { try { - auto member_c = s->find_client(nullptr, it.second.serial_number); + auto member_c = s->find_client(nullptr, it.second.account_id); send_update_team_metadata_for_client(c); send_team_membership_info(c); } catch (const out_of_range&) { @@ -5560,7 +5317,7 @@ static on_command_t handlers[0x100][NUM_VERSIONS - 2] = { // clang-format on }; -static void check_unlicensed_command(Version version, uint8_t command) { +static void check_logged_out_command(Version version, uint8_t command) { switch (version) { case Version::DC_NTE: case Version::DC_V1_11_2000_PROTOTYPE: @@ -5569,7 +5326,7 @@ static void check_unlicensed_command(Version version, uint8_t command) { // newserv doesn't actually know that DC clients are DC until it receives // an appropriate login command (93, 9A, or 9D), but those commands also // log the client in, so this case should never be executed. - throw logic_error("cannot check unlicensed command for DC client"); + throw logic_error("cannot check logged-out command for DC client"); case Version::PC_NTE: case Version::PC_V2: if (command != 0x9A && command != 0x9C && command != 0x9D) { @@ -5612,11 +5369,11 @@ void on_command(shared_ptr c, uint16_t command, uint32_t flag, string& d c->reschedule_ping_and_timeout_events(); // Most of the command handlers assume the client is registered, logged in, - // and not banned (and therefore that c->license is not null), so the client - // is allowed to access normal functionality. This check prevents clients from + // and not banned (and therefore that c->login is not null), so the client is + // allowed to access normal functionality. This check prevents clients from // sneakily sending commands to access functionality without logging in. - if (!c->license.get()) { - check_unlicensed_command(c->version(), command); + if (!c->login) { + check_logged_out_command(c->version(), command); } auto fn = handlers[command & 0xFF][static_cast(c->version()) - 2]; diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 54763265..4cd48524 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -991,13 +991,13 @@ static void on_sync_joining_player_disp_and_inventory( case Version::DC_NTE: parsed = make_unique( check_size_t(data, size), - c->license->serial_number); + c->login->account->account_id); parsed->clear_dc_protos_unused_item_fields(); break; case Version::DC_V1_11_2000_PROTOTYPE: parsed = make_unique( check_size_t(data, size), - c->license->serial_number, + c->login->account->account_id, c->language()); parsed->clear_dc_protos_unused_item_fields(); break; @@ -1007,7 +1007,7 @@ static void on_sync_joining_player_disp_and_inventory( case Version::PC_V2: parsed = make_unique( check_size_t(data, size), - c->license->serial_number); + c->login->account->account_id); if (c_v == Version::DC_V1) { parsed->clear_v1_unused_item_fields(); } @@ -1018,17 +1018,17 @@ static void on_sync_joining_player_disp_and_inventory( case Version::GC_EP3: parsed = make_unique( check_size_t(data, size), - c->license->serial_number); + c->login->account->account_id); break; case Version::XB_V3: parsed = make_unique( check_size_t(data, size), - c->license->serial_number); + c->login->account->account_id); break; case Version::BB_V4: parsed = make_unique( check_size_t(data, size), - c->license->serial_number); + c->login->account->account_id); break; default: throw logic_error("6x70 command from unknown game version"); @@ -1414,7 +1414,8 @@ static void on_player_revivable(shared_ptr c, uint8_t command, uint8_t f forward_subcommand(c, command, flag, data, size); // Revive if infinite HP is enabled - bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE)); + bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || + (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { G_UseMedicalCenter_6x31 v2_cmd = {0x31, 0x01, c->lobby_client_id}; G_RevivePlayer_V3_BB_6xA1 v3_cmd = {0xA1, 0x01, c->lobby_client_id}; @@ -1444,7 +1445,7 @@ void on_player_revived(shared_ptr c, uint8_t command, uint8_t flag, void if (l->is_game()) { forward_subcommand(c, command, flag, data, size); bool player_cheats_enabled = !is_v1(c->version()) && - (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE))); + (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550); } @@ -1458,7 +1459,7 @@ static void on_received_condition(shared_ptr c, uint8_t command, uint8_t if (l->is_game()) { forward_subcommand(c, command, flag, data, size); if (cmd.client_id == c->lobby_client_id) { - bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE)); + bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE)); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { send_remove_conditions(c); } @@ -1474,7 +1475,7 @@ static void on_change_hp(shared_ptr c, uint8_t command, uint8_t flag, vo if (l->is_game() && (cmd.client_id == c->lobby_client_id)) { forward_subcommand(c, command, flag, data, size); bool player_cheats_enabled = !is_v1(c->version()) && - (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE))); + (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550); } @@ -1488,7 +1489,7 @@ static void on_cast_technique_finished(shared_ptr c, uint8_t command, ui if (l->is_game() && (cmd.header.client_id == c->lobby_client_id)) { forward_subcommand(c, command, flag, data, size); bool player_cheats_enabled = !is_v1(c->version()) && - (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE))); + (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->login->account->check_flag(Account::Flag::CHEAT_ANYWHERE))); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) { send_player_stats_change(c, PlayerStatsChange::ADD_TP, 255); } @@ -2021,7 +2022,7 @@ static void on_pick_up_item_generic( string bb_message = string_printf("$C6%s$C7 has found %s", p_name.c_str(), desc.c_str()); if (should_send_global_notif) { for (auto& it : s->channel_to_client) { - if (it.second->license && + if (it.second->login && !is_patch(it.second->version()) && !is_ep3(it.second->version()) && it.second->lobby.lock()) { @@ -3166,11 +3167,11 @@ static void send_max_level_notification_if_needed(shared_ptr c) { string name = p->disp.name.decode(c->language()); size_t level_for_str = max_level + 1; string message = string_printf("$CG%s$C6\nGC: %" PRIu32 "\nhas reached Level $CG%zu", - name.c_str(), c->license->serial_number, level_for_str); + name.c_str(), c->login->account->account_id, level_for_str); string bb_message = string_printf("$CG%s$C6 (GC: %" PRIu32 ") has reached Level $CG%zu", - name.c_str(), c->license->serial_number, level_for_str); + name.c_str(), c->login->account->account_id, level_for_str); for (auto& it : s->channel_to_client) { - if ((it.second != c) && it.second->license && !is_patch(it.second->version()) && it.second->lobby.lock()) { + if ((it.second != c) && it.second->login && !is_patch(it.second->version()) && it.second->lobby.lock()) { send_text_or_scrolling_message(it.second, message, bb_message); } } @@ -3508,7 +3509,7 @@ void on_exchange_item_for_team_points_bb(shared_ptr c, uint8_t command, auto item = p->remove_item(cmd.item_id, cmd.amount, *s->item_stack_limits(c->version())); size_t points = s->item_parameter_table(Version::BB_V4)->get_item_team_points(item); - s->team_index->add_member_points(c->license->serial_number, points); + s->team_index->add_member_points(c->login->account->account_id, points); if (l->log.should_log(LogLevel::INFO)) { auto name = s->describe_item(c->version(), item, false); diff --git a/src/ReplaySession.cc b/src/ReplaySession.cc index 7a1466fc..27e8afa7 100644 --- a/src/ReplaySession.cc +++ b/src/ReplaySession.cc @@ -189,7 +189,7 @@ void ReplaySession::check_for_password(shared_ptr ev) const { check_ak(cmd.access_key2.decode()); } } else if (header.command == 0xDB) { - const auto& cmd = check_size_t(cmd_data, cmd_size); + const auto& cmd = check_size_t(cmd_data, cmd_size); check_ak(cmd.access_key.decode()); check_ak(cmd.access_key2.decode()); check_pw(cmd.password.decode()); @@ -208,7 +208,7 @@ void ReplaySession::check_for_password(shared_ptr ev) const { } else if (header.command == 0x9E) { check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } else if (header.command == 0xDB) { - check_pw(check_size_t(cmd_data, cmd_size).password.decode()); + check_pw(check_size_t(cmd_data, cmd_size).password.decode()); } break; } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 8a1bb91c..be5882ea 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -277,7 +277,7 @@ void send_update_client_config(shared_ptr c, bool always_send) { c->config.set_flag(Client::Flag::HAS_GUILD_CARD_NUMBER); S_UpdateClientConfig_DC_PC_04 cmd; cmd.player_tag = 0x00010000; - cmd.guild_card_number = c->license->serial_number; + cmd.guild_card_number = c->login->account->account_id; send_command_t(c, 0x04, 0x00, cmd); } break; @@ -290,7 +290,7 @@ void send_update_client_config(shared_ptr c, bool always_send) { c->config.set_flag(Client::Flag::HAS_GUILD_CARD_NUMBER); S_UpdateClientConfig_V3_04 cmd; cmd.player_tag = 0x00010000; - cmd.guild_card_number = c->license->serial_number; + cmd.guild_card_number = c->login->account->account_id; c->config.serialize_into(cmd.client_config); send_command_t(c, 0x04, 0x00, cmd); break; @@ -556,7 +556,7 @@ void send_client_init_bb(shared_ptr c, uint32_t error_code) { S_ClientInit_BB_00E6 cmd; cmd.error_code = error_code; cmd.player_tag = 0x00010000; - cmd.guild_card_number = c->license->serial_number; + cmd.guild_card_number = c->login->account->account_id; cmd.security_token = team ? team->team_id : 0; c->config.serialize_into(cmd.client_config); cmd.can_create_team = 1; @@ -570,7 +570,7 @@ void send_system_file_bb(shared_ptr c) { PSOBBFullSystemFile cmd; cmd.base = *c->system_file(); if (team) { - cmd.team_membership = team->membership_for_member(c->license->serial_number); + cmd.team_membership = team->membership_for_member(c->login->account->account_id); } send_command_t(c, 0x00E2, 0x00000000, cmd); } @@ -709,7 +709,7 @@ void send_complete_player_bb(shared_ptr c) { cmd.char_file = *p; cmd.system_file.base = *sys; if (team) { - cmd.system_file.team_membership = team->membership_for_member(c->license->serial_number); + cmd.system_file.team_membership = team->membership_for_member(c->login->account->account_id); } send_command_t(c, 0x00E7, 0x00000000, cmd); } @@ -864,7 +864,7 @@ void send_text_message(shared_ptr l, const string& text) { void send_text_message(shared_ptr s, const string& text) { for (auto& it : s->channel_to_client) { - if (it.second->license && !is_patch(it.second->version())) { + if (it.second->login && !is_patch(it.second->version())) { send_text_message(it.second, text); } } @@ -1002,7 +1002,7 @@ void send_simple_mail_t(shared_ptr c, uint32_t from_guild_card_number, c cmd.player_tag = from_guild_card_number ? 0x00010000 : 0; cmd.from_guild_card_number = from_guild_card_number; cmd.from_name.encode(from_name, c->language()); - cmd.to_guild_card_number = c->license->serial_number; + cmd.to_guild_card_number = c->login->account->account_id; cmd.text.encode(text, c->language()); send_command_t(c, 0x81, 0x00, cmd); } @@ -1012,7 +1012,7 @@ void send_simple_mail_bb(shared_ptr c, uint32_t from_guild_card_number, cmd.player_tag = from_guild_card_number ? 0x00010000 : 0; cmd.from_guild_card_number = from_guild_card_number; cmd.from_name.encode(from_name, c->language()); - cmd.to_guild_card_number = c->license->serial_number; + cmd.to_guild_card_number = c->login->account->account_id; cmd.received_date.encode(format_time(now()), c->language()); cmd.text.encode(text, c->language()); send_command_t(c, 0x81, 0x00, cmd); @@ -1045,7 +1045,7 @@ void send_simple_mail(shared_ptr c, uint32_t from_guild_card_number, con void send_simple_mail(shared_ptr s, uint32_t from_guild_card_number, const string& from_name, const string& text) { for (const auto& it : s->channel_to_client) { - if (it.second->license && !is_patch(it.second->version())) { + if (it.second->login && !is_patch(it.second->version())) { send_simple_mail(it.second, from_guild_card_number, from_name, text); } } @@ -1127,8 +1127,8 @@ void send_card_search_result_t( S_GuildCardSearchResultT cmd; cmd.player_tag = 0x00010000; - cmd.searcher_guild_card_number = c->license->serial_number; - cmd.result_guild_card_number = result->license->serial_number; + cmd.searcher_guild_card_number = c->login->account->account_id; + cmd.result_guild_card_number = result->login->account->account_id; cmd.reconnect_command_header.size = sizeof(cmd.reconnect_command_header) + sizeof(cmd.reconnect_command); cmd.reconnect_command_header.command = 0x19; cmd.reconnect_command_header.flag = 0x00; @@ -1301,20 +1301,20 @@ void send_guild_card( } void send_guild_card(shared_ptr c, shared_ptr source) { - if (!source->license) { - throw runtime_error("source player does not have a license"); + if (!source->login) { + throw runtime_error("source player does not have an account"); } auto source_p = source->character(true, false); auto source_team = source->team(); - uint64_t xb_user_id = source->license->xb_user_id - ? source->license->xb_user_id - : (0xAE00000000000000ULL | source->license->serial_number); + uint64_t xb_user_id = (source->login->xb_license && source->login->xb_license->user_id) + ? source->login->xb_license->user_id + : (0xAE00000000000000ULL | source->login->account->account_id); send_guild_card( c->channel, - source->license->serial_number, + source->login->account->account_id, xb_user_id, source_p->disp.name.decode(source->language()), source_team ? source_team->name : "", @@ -1569,7 +1569,7 @@ void send_quest_categories_menu_t( QuestMenuType menu_type, Episode episode) { QuestIndex::IncludeCondition include_condition = nullptr; - if (!c->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (!c->login->account->check_flag(Account::Flag::DISABLE_QUEST_REQUIREMENTS)) { auto l = c->lobby.lock(); include_condition = l ? l->quest_include_condition() : nullptr; } @@ -1701,7 +1701,7 @@ void send_player_records_t(shared_ptr c, shared_ptr l, shared_ptr template void populate_lobby_data_for_client(LobbyDataT& ret, shared_ptr c, shared_ptr viewer_c) { ret.player_tag = 0x00010000; - ret.guild_card_number = c->license->serial_number; + ret.guild_card_number = c->login->account->account_id; ret.client_id = c->lobby_client_id; string name = c->character()->disp.name.decode(c->language()); ret.name.encode(name, viewer_c->language()); @@ -1710,11 +1710,11 @@ void populate_lobby_data_for_client(LobbyDataT& ret, shared_ptr c, template <> void populate_lobby_data_for_client(PlayerLobbyDataXB& ret, shared_ptr c, shared_ptr viewer_c) { ret.player_tag = 0x00010000; - ret.guild_card_number = c->license->serial_number; + ret.guild_card_number = c->login->account->account_id; if (c->xb_netloc) { ret.netloc = *c->xb_netloc; } else { - ret.netloc.account_id = 0xAE00000000000000 | c->license->serial_number; + ret.netloc.account_id = 0xAE00000000000000 | c->login->account->account_id; } ret.client_id = c->lobby_client_id; string name = c->character()->disp.name.decode(c->language()); @@ -1724,11 +1724,11 @@ void populate_lobby_data_for_client(PlayerLobbyDataXB& ret, shared_ptr void populate_lobby_data_for_client(PlayerLobbyDataBB& ret, shared_ptr c, shared_ptr viewer_c) { ret.player_tag = 0x00010000; - ret.guild_card_number = c->license->serial_number; + ret.guild_card_number = c->login->account->account_id; ret.client_id = c->lobby_client_id; auto team = c->team(); if (team) { - ret.team_master_guild_card_number = team->master_serial_number; + ret.team_master_guild_card_number = team->master_account_id; ret.team_id = team->team_id; } else { ret.team_master_guild_card_number = 0; @@ -1780,7 +1780,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) auto& e = cmd.entries[z]; e.player_tag = 0x00010000; - e.guild_card_number = wc->license->serial_number; + e.guild_card_number = wc->login->account->account_id; e.name.encode(wc_p->disp.name.decode(wc_p->inventory.language), c->language()); e.present = 1; e.level = wc->ep3_config @@ -1848,7 +1848,7 @@ static void send_join_spectator_team(shared_ptr c, shared_ptr l) cmd_p.disp.enforce_lobby_join_limits_for_version(c->version()); cmd_e.player_tag = 0x00010000; - cmd_e.guild_card_number = other_c->license->serial_number; + cmd_e.guild_card_number = other_c->login->account->account_id; cmd_e.name = cmd_p.lobby_data.name; cmd_e.present = 1; cmd_e.level = other_c->ep3_config @@ -2416,7 +2416,7 @@ void send_arrow_update(shared_ptr l) { } auto& e = entries.emplace_back(); e.player_tag = 0x00010000; - e.guild_card_number = lc->license->serial_number; + e.guild_card_number = lc->login->account->account_id; e.arrow_color = lc->lobby_arrow_color; } @@ -3031,8 +3031,8 @@ void send_ep3_media_update( void send_ep3_rank_update(shared_ptr c) { auto s = c->require_server_state(); - uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_current_meseta; - uint32_t total_meseta_earned = s->ep3_infinite_meseta ? 1000000 : c->license->ep3_total_meseta_earned; + uint32_t current_meseta = s->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_current_meseta; + uint32_t total_meseta_earned = s->ep3_infinite_meseta ? 1000000 : c->login->account->ep3_total_meseta_earned; S_RankUpdate_Ep3_B7 cmd = {0, {}, current_meseta, total_meseta_earned, 0xFFFFFFFF}; send_command_t(c, 0xB7, 0x00, cmd); } @@ -3056,7 +3056,7 @@ void send_ep3_card_battle_table_state(shared_ptr l, uint16_t table_number auto& e = is_nte ? cmd_nte.entries[c->card_battle_table_seat_number] : cmd_final.entries[c->card_battle_table_seat_number]; if (e.state == 0) { e.state = c->card_battle_table_seat_state; - e.guild_card_number = c->license->serial_number; + e.guild_card_number = c->login->account->account_id; auto& clients = is_nte ? clients_nte : clients_final; clients.emplace(c); } @@ -3256,7 +3256,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { cmd.players_per_team = (tourn->get_flags() & Episode3::Tournament::Flag::IS_2V2) ? 2 : 1; if (primary_lobby) { - auto serial_number_to_client = primary_lobby->clients_by_serial_number(); + auto account_id_to_client = primary_lobby->clients_by_account_id(); using TeamEntryT = typename S_TournamentGameDetailsBaseT_Ep3_E3::TeamEntry; auto describe_team = [&](TeamEntryT& team_entry, shared_ptr team) -> void { team_entry.team_name.encode(team->name, c->language()); @@ -3265,7 +3265,7 @@ void send_ep3_game_details_t(shared_ptr c, shared_ptr l) { const auto& player = team->players[z]; if (player.is_human()) { try { - auto other_c = serial_number_to_client.at(player.serial_number); + auto other_c = account_id_to_client.at(player.account_id); entry.name.encode(other_c->character()->disp.name.decode(other_c->language()), c->language()); entry.description.encode(ep3_description_for_client(other_c), c->language()); } catch (const out_of_range&) { @@ -3387,7 +3387,7 @@ void send_ep3_set_tournament_player_decks_t(shared_ptr c) { if (player.is_human()) { entry.type = 1; // Human entry.player_name.encode(player.player_name, c->language()); - if (player.serial_number == c->license->serial_number) { + if (player.account_id == c->login->account->account_id) { cmd.player_slot = base_index + z; } } else { @@ -3426,7 +3426,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar throw logic_error("cannot send tournament result without valid winner team"); } - auto serial_number_to_client = l->clients_by_serial_number(); + auto account_id_to_client = l->clients_by_account_id(); for (const auto& lc : l->clients) { if (!lc) { @@ -3437,7 +3437,7 @@ void send_ep3_tournament_match_result(shared_ptr l, uint32_t meseta_rewar const auto& player = team->players[z]; if (player.is_human()) { try { - auto pc = serial_number_to_client.at(player.serial_number); + auto pc = account_id_to_client.at(player.account_id); entry.player_names[z].encode(pc->character()->disp.name.decode(pc->language()), lc->language()); } catch (const out_of_range&) { entry.player_names[z].encode(player.player_name, lc->language()); @@ -3895,9 +3895,9 @@ void send_team_membership_info(shared_ptr c) { auto team = c->team(); S_TeamMembershipInformation_BB_12EA cmd; if (team) { - cmd.guild_card_number = c->license->serial_number; + cmd.guild_card_number = c->login->account->account_id; cmd.team_id = team->team_id; - cmd.privilege_level = team->members.at(c->license->serial_number).privilege_level(); + cmd.privilege_level = team->members.at(c->login->account->account_id).privilege_level(); cmd.team_member_count = min(team->members.size(), 100); cmd.team_name.encode(team->name); } @@ -3908,12 +3908,12 @@ static S_TeamInfoForPlayer_BB_13EA_15EA_Entry team_metadata_for_client(shared_pt auto team = c->team(); S_TeamInfoForPlayer_BB_13EA_15EA_Entry cmd; cmd.lobby_client_id = c->lobby_client_id; - cmd.guild_card_number2 = c->license->serial_number; + cmd.guild_card_number2 = c->login->account->account_id; cmd.player_name = c->character()->disp.name; if (team) { - cmd.guild_card_number = c->license->serial_number; + cmd.guild_card_number = c->login->account->account_id; cmd.team_id = team->team_id; - cmd.privilege_level = team->members.at(c->license->serial_number).privilege_level(); + cmd.privilege_level = team->members.at(c->login->account->account_id).privilege_level(); cmd.team_member_count = min(team->members.size(), 100); cmd.team_name.encode(team->name); if (team->flag_data) { @@ -3971,7 +3971,7 @@ void send_team_member_list(shared_ptr c) { auto& e = entries.emplace_back(); e.rank = z + 1; e.privilege_level = m->privilege_level(); - e.guild_card_number = m->serial_number; + e.guild_card_number = m->account_id; e.name.encode(m->name, c->language()); } @@ -4006,7 +4006,7 @@ void send_intra_team_ranking(shared_ptr c) { auto& e = entries.emplace_back(); e.rank = z + 1; e.privilege_level = m->privilege_level(); - e.guild_card_number = m->serial_number; + e.guild_card_number = m->account_id; e.player_name.encode(m->name); e.points = m->points; } diff --git a/src/Server.cc b/src/Server.cc index 8f59385d..2d7400d3 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -281,14 +281,14 @@ shared_ptr Server::get_client() const { } vector> Server::get_clients_by_identifier(const string& ident) const { - int64_t serial_number_hex = -1; - int64_t serial_number_dec = -1; + int64_t account_id_hex = -1; + int64_t account_id_dec = -1; try { - serial_number_dec = stoul(ident, nullptr, 10); + account_id_dec = stoul(ident, nullptr, 10); } catch (const invalid_argument&) { } try { - serial_number_hex = stoul(ident, nullptr, 16); + account_id_hex = stoul(ident, nullptr, 16); } catch (const invalid_argument&) { } @@ -297,15 +297,19 @@ vector> Server::get_clients_by_identifier(const string& ident vector> results; for (const auto& it : this->state->channel_to_client) { auto c = it.second; - if (c->license && c->license->serial_number == serial_number_dec) { + if (c->login && c->login->account->account_id == account_id_hex) { results.emplace_back(std::move(c)); continue; } - if (c->license && c->license->serial_number == serial_number_hex) { + if (c->login && c->login->account->account_id == account_id_dec) { results.emplace_back(std::move(c)); continue; } - if (c->license && c->license->bb_username == ident) { + if (c->login && c->login->xb_license && c->login->xb_license->gamertag == ident) { + results.emplace_back(std::move(c)); + continue; + } + if (c->login && c->login->bb_license && c->login->bb_license->username == ident) { results.emplace_back(std::move(c)); continue; } diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 76880202..04b00676 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -178,10 +178,10 @@ CommandDefinition c_on( Run a command on a specific game server client or proxy server session.\n\ Without this prefix, commands that affect a single client or session will\n\ work only if there's exactly one connected client or open session. SESSION\n\ - may be a client ID (e.g. C-3), a player name, a license serial number, or\n\ - a BB account username. For proxy commands, SESSION should be the session\n\ - ID, which generally is the same as the player\'s serial number and appears\n\ - after \"LinkedSession:\" in the log output.", + may be a client ID (e.g. C-3), a player name, an account ID, an Xbox\n\ + gamertag, or a BB account username. For proxy commands, SESSION should be\n\ + the session ID, which generally is the same as the player\'s account ID\n\ + and appears after \"LinkedSession:\" in the log output.", false, +[](CommandArgs& args) { size_t session_name_end = skip_non_whitespace(args.args, 0); @@ -197,6 +197,7 @@ CommandDefinition c_on( CommandDefinition c_reload( "reload", "reload ITEM [ITEM...]\n\ Reload various parts of the server configuration. The items are:\n\ + accounts - reindex user accounts\n\ battle-params - reload the BB enemy stats files\n\ bb-keys - reload BB private keys\n\ config - reload most fields from config.json\n\ @@ -209,7 +210,6 @@ CommandDefinition c_reload( item-definitions - reload item definitions files\n\ item-name-index - regenerate item name list\n\ level-table - reload the level-up tables\n\ - licenses - reindex user licenses\n\ patch-files - reindex the PC and BB patch directories\n\ quests - reindex all quests (including Episode3 download quests)\n\ set-tables - reload set data tables\n\ @@ -228,8 +228,8 @@ CommandDefinition c_reload( for (const auto& type : types) { if (type == "bb-keys") { args.s->load_bb_private_keys(true); - } else if (type == "licenses") { - args.s->load_licenses(true); + } else if (type == "accounts") { + args.s->load_accounts(true); } else if (type == "patch-files") { args.s->load_patch_indexes(true); } else if (type == "ep3-cards") { @@ -278,188 +278,326 @@ CommandDefinition c_reload( } }); -CommandDefinition c_add_license( - "add-license", "add-license PARAMETERS...\n\ - Add a license to the server. is some subset of the following:\n\ - bb-username= (BB username)\n\ - bb-password= (BB password)\n\ - xb-gamertag= (Xbox gamertag)\n\ - xb-user-id= (Xbox user ID)\n\ - xb-account-id= (Xbox account ID)\n\ - gc-password= (GC password)\n\ - dc-nte-serial-number= (DC NTE serial number)\n\ - dc-nte-access-key= (DC NTE access key)\n\ - access-key= (DC/GC/PC access key)\n\ - serial= (decimal serial number; required for all licenses)\n\ - flags= (see below)\n\ - If flags is specified in hex, the meanings of bits are:\n\ - 00000001 = Can kick other users offline\n\ - 00000002 = Can ban other users\n\ - 00000004 = Can silence other users\n\ - 00000010 = Can change lobby events\n\ - 00000020 = Can make server-wide announcements\n\ - 00000040 = Ignores game join restrictions (e.g. level/quest requirements)\n\ - 01000000 = Can use debugging commands\n\ - 02000000 = Can use cheat commands even if cheat mode is disabled\n\ - 04000000 = Can play any quest without progression/flags restrictions\n\ - 08000000 = Can use chat commands even if disabled in config.json\n\ - 80000000 = License is a shared serial (disables Access Key and password\n\ - checks; players will get Guild Cards based on their player names)\n\ - There are also shorthands for some general privilege levels:\n\ - flags=moderator = 00000007\n\ - flags=admin = 000000FF\n\ - flags=root = 7FFFFFFF", +CommandDefinition c_list_accounts( + "list-accounts", "list-accounts\n\ + List all accounts registered on the server.", true, +[](CommandArgs& args) { - auto l = args.s->license_index->create_license(); - - for (const string& token : split(args.args, ' ')) { - if (starts_with(token, "bb-username=")) { - l->bb_username = token.substr(12); - } else if (starts_with(token, "bb-password=")) { - l->bb_password = token.substr(12); - } else if (starts_with(token, "xb-gamertag=")) { - l->xb_gamertag = token.substr(12); - } else if (starts_with(token, "xb-user-id=")) { - l->xb_user_id = stoull(token.substr(11), nullptr, 16); - } else if (starts_with(token, "xb-account-id=")) { - l->xb_account_id = stoull(token.substr(14), nullptr, 16); - } else if (starts_with(token, "gc-password=")) { - l->gc_password = token.substr(12); - } else if (starts_with(token, "dc-nte-serial-number=")) { - l->dc_nte_serial_number = token.substr(21); - } else if (starts_with(token, "dc-nte-access-key=")) { - l->dc_nte_access_key = token.substr(18); - } else if (starts_with(token, "access-key=")) { - l->access_key = token.substr(11); - } else if (starts_with(token, "serial=")) { - l->serial_number = stoul(token.substr(7), nullptr, 0); - - } else if (starts_with(token, "flags=")) { - string mask = token.substr(6); - if (mask == "normal") { - l->flags = 0; - } else if (mask == "mod") { - l->replace_all_flags(License::Flag::MODERATOR); - } else if (mask == "admin") { - l->replace_all_flags(License::Flag::ADMINISTRATOR); - } else if (mask == "root") { - l->replace_all_flags(License::Flag::ROOT); - } else { - l->flags = stoul(mask, nullptr, 16); - } - - } else { - throw invalid_argument("incorrect field: " + token); + auto accounts = args.s->account_index->all(); + if (accounts.empty()) { + fprintf(stderr, "No accounts registered\n"); + } else { + for (const auto& a : accounts) { + a->print(stderr); } } - - if (!l->serial_number) { - throw invalid_argument("license does not contain serial number"); - } - - l->save(); - args.s->license_index->add(l); - fprintf(stderr, "License added\n"); }); -CommandDefinition c_update_license( - "update-license", "update-license SERIAL-NUMBER PARAMETERS...\n\ - Update an existing license. specifies which license to\n\ - update. The options in are the same as for the add-license\n\ - command.", +uint32_t parse_account_flags(const string& flags_str) { + try { + size_t end_pos = 0; + uint32_t ret = stoul(flags_str, &end_pos, 16); + if (end_pos == flags_str.size()) { + return ret; + } + } catch (const exception&) { + } + + uint32_t ret = 0; + auto tokens = split(flags_str, ','); + for (const auto& token : tokens) { + string token_upper = toupper(token); + if (token_upper == "NONE") { + // Nothing to do + } else if (token_upper == "KICK_USER") { + ret |= static_cast(Account::Flag::KICK_USER); + } else if (token_upper == "BAN_USER") { + ret |= static_cast(Account::Flag::BAN_USER); + } else if (token_upper == "SILENCE_USER") { + ret |= static_cast(Account::Flag::SILENCE_USER); + } else if (token_upper == "MODERATOR") { + ret |= static_cast(Account::Flag::MODERATOR); + } else if (token_upper == "CHANGE_EVENT") { + ret |= static_cast(Account::Flag::CHANGE_EVENT); + } else if (token_upper == "ANNOUNCE") { + ret |= static_cast(Account::Flag::ANNOUNCE); + } else if (token_upper == "FREE_JOIN_GAMES") { + ret |= static_cast(Account::Flag::FREE_JOIN_GAMES); + } else if (token_upper == "ADMINISTRATOR") { + ret |= static_cast(Account::Flag::ADMINISTRATOR); + } else if (token_upper == "DEBUG") { + ret |= static_cast(Account::Flag::DEBUG); + } else if (token_upper == "CHEAT_ANYWHERE") { + ret |= static_cast(Account::Flag::CHEAT_ANYWHERE); + } else if (token_upper == "DISABLE_QUEST_REQUIREMENTS") { + ret |= static_cast(Account::Flag::DISABLE_QUEST_REQUIREMENTS); + } else if (token_upper == "ALWAYS_ENABLE_CHAT_COMMANDS") { + ret |= static_cast(Account::Flag::ALWAYS_ENABLE_CHAT_COMMANDS); + } else if (token_upper == "ROOT") { + ret |= static_cast(Account::Flag::ROOT); + } else if (token_upper == "IS_SHARED_ACCOUNT") { + ret |= static_cast(Account::Flag::IS_SHARED_ACCOUNT); + } else { + throw runtime_error("invalid flag name: " + token_upper); + } + } + return ret; +} + +CommandDefinition c_add_account( + "add-account", "add-account [PARAMETERS...]\n\ + Add an account to the server. is some subset of:\n\ + id=ACCOUNT-ID: preferred account ID in hex (optional)\n\ + flags=FLAGS: behaviors and permissions for the account (see below)\n\ + ep3-current-meseta=MESETA: Episode 3 Meseta value\n\ + ep3-total-meseta=MESETA: Episode 3 total Meseta ever earned\n\ + temporary: marks the account as temporary; it is not saved to disk and\n\ + therefore will be deleted when the server shuts down\n\ + If given, FLAGS is a comma-separated list of zero or more the following:\n\ + NONE: Placeholder if no other flags are specified\n\ + KICK_USER: Can kick other users offline\n\ + BAN_USER: Can ban other users\n\ + SILENCE_USER: Can silence other users\n\ + MODERATOR: Alias for all of the above flags\n\ + CHANGE_EVENT: Can change lobby events\n\ + ANNOUNCE: Can make server-wide announcements\n\ + FREE_JOIN_GAMES: Ignores game restrictions (level/quest requirements)\n\ + ADMINISTRATOR: Alias for all of the above flags (including MODERATOR)\n\ + DEBUG: Can use debugging commands\n\ + CHEAT_ANYWHERE: Can use cheat commands even if cheat mode is disabled\n\ + DISABLE_QUEST_REQUIREMENTS: Can play any quest without progression\n\ + restrictions\n\ + ALWAYS_ENABLE_CHAT_COMMANDS: Can use chat commands even if they are\n\ + disabled in config.json\n\ + ROOT: Alias for all of the above flags (including ADMINISTRATOR)\n\ + IS_SHARED_ACCOUNT: Account is a shared serial (disables Access Key and\n\ + password checks; players will get Guild Cards based on their player\n\ + names)", + true, + +[](CommandArgs& args) { + auto account = make_shared(); + for (const string& token : split(args.args, ' ')) { + if (starts_with(token, "id=")) { + account->account_id = stoul(token.substr(3), nullptr, 16); + } else if (starts_with(token, "ep3-current-meseta=")) { + account->ep3_current_meseta = stoul(token.substr(19), nullptr, 0); + } else if (starts_with(token, "ep3-total-meseta=")) { + account->ep3_total_meseta_earned = stoul(token.substr(17), nullptr, 0); + } else if (token == "temporary") { + account->is_temporary = true; + } else if (starts_with(token, "flags=")) { + account->flags = parse_account_flags(token.substr(6)); + } else { + throw invalid_argument("invalid account field: " + token); + } + } + args.s->account_index->add(account); + account->save(); + fprintf(stderr, "Account %08" PRIX32 " added\n", account->account_id); + }); +CommandDefinition c_update_account( + "update-account", "update-account ACCOUNT-ID PARAMETERS...\n\ + Update an existing license. ACCOUNT-ID (8 hex digits) specifies which\n\ + account to update. The options are similar to the add-account command:\n\ + flags=FLAGS: behaviors and permissions for the account (same as with\n\ + add-account)\n\ + ep3-current-meseta=MESETA: Episode 3 Meseta value\n\ + ep3-total-meseta=MESETA: Episode 3 total Meseta ever earned\n\ + temporary: marks the account as temporary; it is not saved to disk and\n\ + therefore will be deleted when the server shuts down\n\ + permanent: if the account was temporary, makes it non-temporary", true, +[](CommandArgs& args) { auto tokens = split(args.args, ' '); if (tokens.size() < 2) { throw runtime_error("not enough arguments"); } - uint32_t serial_number = stoul(tokens[0]); + auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16)); tokens.erase(tokens.begin()); - auto orig_l = args.s->license_index->get(serial_number); - auto l = args.s->license_index->create_license(); - *l = *orig_l; - args.s->license_index->remove(orig_l->serial_number); - try { - for (const string& token : tokens) { - if (starts_with(token, "bb-username=")) { - l->bb_username = token.substr(12); - } else if (starts_with(token, "bb-password=")) { - l->bb_password = token.substr(12); - } else if (starts_with(token, "xb-gamertag=")) { - l->xb_gamertag = token.substr(12); - } else if (starts_with(token, "xb-user-id=")) { - l->xb_user_id = stoull(token.substr(11), nullptr, 16); - } else if (starts_with(token, "xb-account-id=")) { - l->xb_account_id = stoull(token.substr(14), nullptr, 16); - } else if (starts_with(token, "gc-password=")) { - l->gc_password = token.substr(12); - } else if (starts_with(token, "dc-nte-serial-number=")) { - l->dc_nte_serial_number = token.substr(21); - } else if (starts_with(token, "dc-nte-access-key=")) { - l->dc_nte_access_key = token.substr(18); - } else if (starts_with(token, "access-key=")) { - l->access_key = token.substr(11); - } else if (starts_with(token, "serial=")) { - l->serial_number = stoul(token.substr(7), nullptr, 0); - - } else if (starts_with(token, "flags=")) { - string mask = token.substr(6); - if (mask == "normal") { - l->flags = 0; - } else if (mask == "mod") { - l->replace_all_flags(License::Flag::MODERATOR); - } else if (mask == "admin") { - l->replace_all_flags(License::Flag::ADMINISTRATOR); - } else if (mask == "root") { - l->replace_all_flags(License::Flag::ROOT); - } else { - l->flags = stoul(mask, nullptr, 16); - } - - } else { - throw invalid_argument("incorrect field: " + token); - } + // Do all the parsing first, then the updates afterward, so we won't + // partially update the account if parsing a later option fails + int64_t new_ep3_current_meseta = -1; + int64_t new_ep3_total_meseta = -1; + int64_t new_flags = -1; + uint8_t new_is_temporary = 0xFF; + for (const string& token : tokens) { + if (starts_with(token, "ep3-current-meseta=")) { + new_ep3_current_meseta = stoul(token.substr(19), nullptr, 0); + } else if (starts_with(token, "ep3-total-meseta=")) { + new_ep3_total_meseta = stoul(token.substr(17), nullptr, 0); + } else if (token == "temporary") { + new_is_temporary = 1; + } else if (token == "permanent") { + new_is_temporary = 0; + } else if (starts_with(token, "flags=")) { + new_flags = parse_account_flags(token.substr(6)); + } else { + throw invalid_argument("invalid account field: " + token); } - - if (!l->serial_number) { - throw invalid_argument("license does not contain serial number"); - } - } catch (const exception&) { - args.s->license_index->add(orig_l); - throw; } - l->save(); - args.s->license_index->add(l); - fprintf(stderr, "License updated\n"); + if (new_ep3_current_meseta > 0) { + account->ep3_current_meseta = new_ep3_current_meseta; + } + if (new_ep3_total_meseta > 0) { + account->ep3_total_meseta_earned = new_ep3_total_meseta; + } + if (new_flags > 0) { + account->flags = new_flags; + } + if (new_is_temporary != 0xFF) { + account->is_temporary = new_is_temporary; + } + + account->save(); + fprintf(stderr, "Account %08" PRIX32 " updated\n", account->account_id); + }); +CommandDefinition c_delete_account( + "delete-account", "delete-account ACCOUNT-ID\n\ + Delete an account from the server. If a player is online with the deleted\n\ + account, they will not be automatically disconnected.", + true, + +[](CommandArgs& args) { + auto account = args.s->account_index->from_account_id(stoul(args.args, nullptr, 16)); + args.s->account_index->remove(account->account_id); + account->is_temporary = true; + account->delete_file(); + fprintf(stderr, "Account deleted\n"); + }); + +CommandDefinition c_add_license( + "add-license", "add-license ACCOUNT-ID TYPE CREDENTIALS...\n\ + Add a license to an account. Each account may have multiple licenses of\n\ + each type. The types are:\n\ + DC-NTE: CREDENTIALS is serial number and access key (16 characters each)\n\ + DC: CREDENTIALS is serial number and access key (8 characters each)\n\ + PC: CREDENTIALS is serial number and access key (8 characters each)\n\ + GC: CREDENTIALS is serial number (10 digits), access key (12 digits), and\n\ + password (up to 8 characters)\n\ + XB: CREDENTIALS is gamertag (up to 16 characters), user ID (16 hex\n\ + digits), and account ID (16 hex digits)\n\ + BB: CREDENTIALS is username and password (up to 16 characters each)\n\ + Examples (adding licenses to account 385A92C4):\n\ + add-license 385A92C4 DC 107862F9 d38XTu2p\n\ + add-license 385A92C4 GC 0418572923 282949185033 hunter2\n\ + add-license 385A92C4 BB user1 trustno1", + true, + +[](CommandArgs& args) { + auto tokens = split(args.args, ' '); + if (tokens.size() < 3) { + throw runtime_error("not enough arguments"); + } + + auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16)); + + string type_str = toupper(tokens[1]); + if (type_str == "DC-NTE") { + if (tokens.size() != 4) { + throw runtime_error("incorrect number of parameters"); + } + auto license = make_shared(); + license->serial_number = std::move(tokens[2]); + license->access_key = std::move(tokens[3]); + args.s->account_index->add_dc_nte_license(account, license); + + } else if (type_str == "DC") { + if (tokens.size() != 4) { + throw runtime_error("incorrect number of parameters"); + } + auto license = make_shared(); + license->serial_number = stoul(tokens[2], nullptr, 16); + license->access_key = std::move(tokens[3]); + args.s->account_index->add_dc_license(account, license); + + } else if (type_str == "PC") { + if (tokens.size() != 4) { + throw runtime_error("incorrect number of parameters"); + } + auto license = make_shared(); + license->serial_number = stoul(tokens[2], nullptr, 16); + license->access_key = std::move(tokens[3]); + args.s->account_index->add_pc_license(account, license); + + } else if (type_str == "GC") { + if (tokens.size() != 5) { + throw runtime_error("incorrect number of parameters"); + } + auto license = make_shared(); + license->serial_number = stoul(tokens[2], nullptr, 10); + license->access_key = std::move(tokens[3]); + license->password = std::move(tokens[4]); + args.s->account_index->add_gc_license(account, license); + + } else if (type_str == "XB") { + if (tokens.size() != 5) { + throw runtime_error("incorrect number of parameters"); + } + auto license = make_shared(); + license->gamertag = std::move(tokens[2]); + license->user_id = stoull(tokens[3], nullptr, 16); + license->account_id = stoull(tokens[4], nullptr, 16); + args.s->account_index->add_xb_license(account, license); + + } else if (type_str == "BB") { + if (tokens.size() != 4) { + throw runtime_error("incorrect number of parameters"); + } + auto license = make_shared(); + license->username = std::move(tokens[2]); + license->password = std::move(tokens[3]); + args.s->account_index->add_bb_license(account, license); + + } else { + throw runtime_error("invalid license type"); + } + + account->save(); + fprintf(stderr, "Account %08" PRIX32 " updated\n", account->account_id); }); CommandDefinition c_delete_license( - "delete-license", "delete-license SERIAL-NUMBER\n\ - Delete a license from the server.", + "delete-license", "delete-license ACCOUNT-ID TYPE PRIMARY-CREDENTIAL\n\ + Delete a license from an account. ACCOUNT-ID and TYPE have the same\n\ + meanings as for add-license. PRIMARY-CREDENTIAL is the first credential\n\ + for the license type; specifically:\n\ + DC-NTE: PRIMARY-CREDENTIAL is the serial number\n\ + DC: PRIMARY-CREDENTIAL is the serial number (hex)\n\ + PC: PRIMARY-CREDENTIAL is the serial number (hex)\n\ + GC: PRIMARY-CREDENTIAL is the serial number (decimal)\n\ + XB: PRIMARY-CREDENTIAL is the gamertag\n\ + BB: PRIMARY-CREDENTIAL is the username\n\ + Examples (deleting licenses from account 385A92C4):\n\ + delete-license 385A92C4 DC 107862F9\n\ + delete-license 385A92C4 GC 0418572923\n\ + delete-license 385A92C4 BB user1", true, +[](CommandArgs& args) { - uint32_t serial_number = stoul(args.args); - auto l = args.s->license_index->get(serial_number); - l->delete_file(); - args.s->license_index->remove(l->serial_number); - fprintf(stderr, "License deleted\n"); - }); -CommandDefinition c_list_licenses( - "list-licenses", "list-licenses\n\ - List all licenses registered on the server.", - true, - +[](CommandArgs& args) { - auto licenses = args.s->license_index->all(); - if (licenses.empty()) { - fprintf(stderr, "No licenses registered\n"); - } else { - for (const auto& l : licenses) { - string s = l->str(); - fprintf(stderr, "%s\n", s.c_str()); - } + auto tokens = split(args.args, ' '); + if (tokens.size() != 3) { + throw runtime_error("incorrect argument count"); } + + auto account = args.s->account_index->from_account_id(stoul(tokens[0], nullptr, 16)); + + string type_str = toupper(tokens[1]); + if (type_str == "DC-NTE") { + args.s->account_index->remove_dc_nte_license(account, tokens[2]); + } else if (type_str == "DC") { + args.s->account_index->remove_dc_license(account, stoul(tokens[2], nullptr, 16)); + } else if (type_str == "PC") { + args.s->account_index->remove_pc_license(account, stoul(tokens[2], nullptr, 16)); + } else if (type_str == "GC") { + args.s->account_index->remove_gc_license(account, stoul(tokens[2], nullptr, 0)); + } else if (type_str == "XB") { + args.s->account_index->remove_xb_license(account, tokens[2]); + } else if (type_str == "BB") { + args.s->account_index->remove_bb_license(account, tokens[2]); + } else { + throw runtime_error("invalid license type"); + } + + account->save(); + fprintf(stderr, "Account %08" PRIX32 " updated\n", account->account_id); }); CommandDefinition c_announce( diff --git a/src/ServerState.cc b/src/ServerState.cc index 4f5b72db..fbc8f835 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -214,21 +214,21 @@ void ServerState::on_player_left_lobby(shared_ptr l, uint8_t leaving_clie } } -shared_ptr ServerState::find_client(const string* identifier, uint64_t serial_number, shared_ptr l) { +shared_ptr ServerState::find_client(const string* identifier, uint64_t account_id, shared_ptr l) { // WARNING: There are multiple callsites where we assume this function never // returns a client that isn't in any lobby. If this behavior changes, we will // need to audit all callsites to ensure correctness. - if ((serial_number == 0) && identifier) { + if ((account_id == 0) && identifier) { try { - serial_number = stoull(*identifier, nullptr, 0); + account_id = stoull(*identifier, nullptr, 0); } catch (const exception&) { } } if (l) { try { - return l->find_client(identifier, serial_number); + return l->find_client(identifier, account_id); } catch (const out_of_range&) { } } @@ -238,7 +238,7 @@ shared_ptr ServerState::find_client(const string* identifier, uint64_t s continue; // don't bother looking again } try { - return other_l->find_client(identifier, serial_number); + return other_l->find_client(identifier, account_id); } catch (const out_of_range&) { } } @@ -689,7 +689,7 @@ void ServerState::load_config_early() { this->ip_stack_debug = this->config_json->get_bool("IPStackDebug", false); this->allow_unregistered_users = this->config_json->get_bool("AllowUnregisteredUsers", false); this->allow_pc_nte = this->config_json->get_bool("AllowPCNTE", false); - this->use_temp_licenses_for_prototypes = this->config_json->get_bool("UseTemporaryLicensesForPrototypes", true); + 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); this->allowed_drop_modes_v1_v2_battle = this->config_json->get_int("AllowedDropModesV1V2Battle", 0x07); @@ -1265,12 +1265,12 @@ void ServerState::load_bb_private_keys(bool from_non_event_thread) { this->forward_or_call(from_non_event_thread, std::move(set)); } -void ServerState::load_licenses(bool from_non_event_thread) { - config_log.info("Indexing licenses"); - shared_ptr new_index = this->is_replay ? make_shared() : make_shared(); +void ServerState::load_accounts(bool from_non_event_thread) { + config_log.info("Indexing accounts"); + shared_ptr new_index = make_shared(this->is_replay); auto set = [s = this->shared_from_this(), new_index = std::move(new_index)]() { - s->license_index = std::move(new_index); + s->account_index = std::move(new_index); }; this->forward_or_call(from_non_event_thread, std::move(set)); } @@ -1806,7 +1806,7 @@ void ServerState::load_all() { this->collect_network_addresses(); this->load_config_early(); this->load_bb_private_keys(false); - this->load_licenses(false); + this->load_accounts(false); this->clear_map_file_caches(); this->load_patch_indexes(false); this->load_ep3_cards(false); @@ -1842,7 +1842,7 @@ shared_ptr ServerState::generate_patch_server_config(bool i ret->hide_data_from_logs = this->hide_download_commands; ret->idle_timeout_usecs = this->patch_client_idle_timeout_usecs; ret->message = is_bb ? this->bb_patch_server_message : this->pc_patch_server_message; - ret->license_index = this->license_index; + ret->account_index = this->account_index; ret->patch_file_index = is_bb ? this->bb_patch_file_index : this->pc_patch_file_index; return ret; } diff --git a/src/ServerState.hh b/src/ServerState.hh index 06e04d5d..3f33bf03 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -11,6 +11,7 @@ #include #include +#include "Account.hh" #include "Client.hh" #include "CommonItemSet.hh" #include "Episode3/DataIndexes.hh" @@ -21,7 +22,6 @@ #include "ItemNameIndex.hh" #include "ItemParameterTable.hh" #include "LevelTable.hh" -#include "License.hh" #include "Lobby.hh" #include "Menu.hh" #include "PatchServer.hh" @@ -87,7 +87,7 @@ struct ServerState : public std::enable_shared_from_this { bool ip_stack_debug = false; bool allow_unregistered_users = false; bool allow_pc_nte = false; - bool use_temp_licenses_for_prototypes = true; + bool use_temp_accounts_for_prototypes = true; bool allow_dc_pc_games = true; bool allow_gc_xb_games = true; bool enable_chat_commands = true; @@ -214,7 +214,7 @@ struct ServerState : public std::enable_shared_from_this { }; std::vector ep3_lobby_banners; - std::shared_ptr license_index; + std::shared_ptr account_index; std::shared_ptr team_index; JSON team_reward_defs_json; @@ -285,7 +285,7 @@ struct ServerState : public std::enable_shared_from_this { std::shared_ptr find_client( const std::string* identifier = nullptr, - uint64_t serial_number = 0, + uint64_t account_id = 0, std::shared_ptr l = nullptr); uint32_t connect_address_for_client(std::shared_ptr c) const; @@ -353,7 +353,7 @@ struct ServerState : public std::enable_shared_from_this { void load_config_early(); void load_config_late(); void load_bb_private_keys(bool from_non_event_thread); - void load_licenses(bool from_non_event_thread); + void load_accounts(bool from_non_event_thread); void load_teams(bool from_non_event_thread); void load_patch_indexes(bool from_non_event_thread); void clear_map_file_caches(); diff --git a/src/TeamIndex.cc b/src/TeamIndex.cc index 115cfef9..b0903ac2 100644 --- a/src/TeamIndex.cc +++ b/src/TeamIndex.cc @@ -13,14 +13,19 @@ using namespace std; TeamIndex::Team::Member::Member(const JSON& json) - : serial_number(json.get_int("SerialNumber")), - flags(json.get_int("Flags", 0)), + : flags(json.get_int("Flags", 0)), points(json.get_int("Points", 0)), - name(json.get_string("Name", "")) {} + name(json.get_string("Name", "")) { + try { + this->account_id = json.get_int("AccountID"); + } catch (const out_of_range&) { + this->account_id = json.get_int("SerialNumber"); + } +} JSON TeamIndex::Team::Member::json() const { return JSON::dict({ - {"SerialNumber", this->serial_number}, + {"AccountID", this->account_id}, {"Flags", this->flags}, {"Points", this->points}, {"Name", this->name}, @@ -57,11 +62,11 @@ void TeamIndex::Team::load_config() { for (const auto& member_it : json.get_list("Members")) { Member m(*member_it); this->points += m.points; - uint32_t serial_number = m.serial_number; + uint32_t account_id = m.account_id; if (m.check_flag(Member::Flag::IS_MASTER)) { - this->master_serial_number = serial_number; + this->master_account_id = account_id; } - this->members.emplace(serial_number, std::move(m)); + this->members.emplace(account_id, std::move(m)); } try { for (const auto& it : json.get_list("RewardKeys")) { @@ -124,11 +129,11 @@ void TeamIndex::Team::delete_files() const { remove(flag_filename.c_str()); } -PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t serial_number) const { - const auto& m = this->members.at(serial_number); +PSOBBTeamMembership TeamIndex::Team::membership_for_member(uint32_t account_id) const { + const auto& m = this->members.at(account_id); PSOBBTeamMembership ret; - ret.team_master_guild_card_number = this->master_serial_number; + ret.team_master_guild_card_number = this->master_account_id; ret.team_id = this->team_id; ret.unknown_a5 = 0; ret.unknown_a6 = 0; @@ -279,9 +284,9 @@ shared_ptr TeamIndex::get_by_name(const string& name) con } } -shared_ptr TeamIndex::get_by_serial_number(uint32_t serial_number) const { +shared_ptr TeamIndex::get_by_account_id(uint32_t account_id) const { try { - return this->serial_number_to_team.at(serial_number); + return this->account_id_to_team.at(account_id); } catch (const out_of_range&) { return nullptr; } @@ -295,17 +300,17 @@ vector> TeamIndex::all() const { return ret; } -shared_ptr TeamIndex::create(const string& name, uint32_t master_serial_number, const string& master_name) { +shared_ptr TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) { auto team = make_shared(this->next_team_id++); save_file(this->directory + "/base.json", JSON::dict({{"NextTeamID", this->next_team_id}}).serialize()); Team::Member m; - m.serial_number = master_serial_number; + m.account_id = master_account_id; m.flags = 0; m.points = 0; m.name = master_name; m.set_flag(Team::Member::Flag::IS_MASTER); - team->members.emplace(master_serial_number, std::move(m)); + team->members.emplace(master_account_id, std::move(m)); team->name = name; team->save_config(); @@ -329,30 +334,30 @@ void TeamIndex::rename(uint32_t team_id, const std::string& new_team_name) { team->save_config(); } -void TeamIndex::add_member(uint32_t team_id, uint32_t serial_number, const string& name) { +void TeamIndex::add_member(uint32_t team_id, uint32_t account_id, const string& name) { auto team = this->id_to_team.at(team_id); - if (!this->serial_number_to_team.emplace(serial_number, team).second) { + if (!this->account_id_to_team.emplace(account_id, team).second) { throw runtime_error("user is already in a different team"); } Team::Member m; - m.serial_number = serial_number; + m.account_id = account_id; m.flags = 0; m.points = 0; m.name = name; - team->members.emplace(serial_number, std::move(m)); + team->members.emplace(account_id, std::move(m)); team->save_config(); } -void TeamIndex::remove_member(uint32_t serial_number) { - auto team_it = this->serial_number_to_team.find(serial_number); - if (team_it == this->serial_number_to_team.end()) { +void TeamIndex::remove_member(uint32_t account_id) { + auto team_it = this->account_id_to_team.find(account_id); + if (team_it == this->account_id_to_team.end()) { throw runtime_error("client is not in any team"); } auto team = std::move(team_it->second); - this->serial_number_to_team.erase(team_it); - team->members.erase(serial_number); + this->account_id_to_team.erase(team_it); + team->members.erase(account_id); if (team->members.empty()) { this->disband(team->team_id); } else { @@ -360,16 +365,16 @@ void TeamIndex::remove_member(uint32_t serial_number) { } } -void TeamIndex::update_member_name(uint32_t serial_number, const std::string& name) { - auto team = this->serial_number_to_team.at(serial_number); - auto& m = team->members.at(serial_number); +void TeamIndex::update_member_name(uint32_t account_id, const std::string& name) { + auto team = this->account_id_to_team.at(account_id); + auto& m = team->members.at(account_id); m.name = name; team->save_config(); } -void TeamIndex::add_member_points(uint32_t serial_number, uint32_t points) { - auto team = this->serial_number_to_team.at(serial_number); - auto& m = team->members.at(serial_number); +void TeamIndex::add_member_points(uint32_t account_id, uint32_t points) { + auto team = this->account_id_to_team.at(account_id); + auto& m = team->members.at(account_id); m.points += points; team->points += points; team->save_config(); @@ -381,13 +386,13 @@ void TeamIndex::set_flag_data(uint32_t team_id, const parraysave_flag(); } -bool TeamIndex::promote_leader(uint32_t master_serial_number, uint32_t leader_serial_number) { - auto team = this->serial_number_to_team.at(master_serial_number); - auto& master_m = team->members.at(master_serial_number); +bool TeamIndex::promote_leader(uint32_t master_account_id, uint32_t leader_account_id) { + auto team = this->account_id_to_team.at(master_account_id); + auto& master_m = team->members.at(master_account_id); if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { - throw runtime_error("incorrect master serial number"); + throw runtime_error("incorrect master account ID"); } - auto& other_m = team->members.at(leader_serial_number); + auto& other_m = team->members.at(leader_account_id); if (other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER) || !team->can_promote_leader()) { return false; @@ -397,13 +402,13 @@ bool TeamIndex::promote_leader(uint32_t master_serial_number, uint32_t leader_se return true; } -bool TeamIndex::demote_leader(uint32_t master_serial_number, uint32_t leader_serial_number) { - auto team = this->serial_number_to_team.at(master_serial_number); - auto& master_m = team->members.at(master_serial_number); +bool TeamIndex::demote_leader(uint32_t master_account_id, uint32_t leader_account_id) { + auto team = this->account_id_to_team.at(master_account_id); + auto& master_m = team->members.at(master_account_id); if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { - throw runtime_error("incorrect master serial number"); + throw runtime_error("incorrect master account ID"); } - auto& other_m = team->members.at(leader_serial_number); + auto& other_m = team->members.at(leader_account_id); if (!other_m.check_flag(TeamIndex::Team::Member::Flag::IS_LEADER)) { return false; @@ -413,19 +418,19 @@ bool TeamIndex::demote_leader(uint32_t master_serial_number, uint32_t leader_ser return true; } -void TeamIndex::change_master(uint32_t master_serial_number, uint32_t new_master_serial_number) { - auto team = this->serial_number_to_team.at(master_serial_number); - auto& master_m = team->members.at(master_serial_number); +void TeamIndex::change_master(uint32_t master_account_id, uint32_t new_master_account_id) { + auto team = this->account_id_to_team.at(master_account_id); + auto& master_m = team->members.at(master_account_id); if (!master_m.check_flag(TeamIndex::Team::Member::Flag::IS_MASTER)) { - throw runtime_error("incorrect master serial number"); + throw runtime_error("incorrect master account ID"); } - auto& new_master_m = team->members.at(new_master_serial_number); + auto& new_master_m = team->members.at(new_master_account_id); master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_MASTER); master_m.set_flag(TeamIndex::Team::Member::Flag::IS_LEADER); new_master_m.clear_flag(TeamIndex::Team::Member::Flag::IS_LEADER); new_master_m.set_flag(TeamIndex::Team::Member::Flag::IS_MASTER); - team->master_serial_number = new_master_serial_number; + team->master_account_id = new_master_account_id; team->save_config(); } @@ -451,9 +456,9 @@ void TeamIndex::add_to_indexes(shared_ptr team) { throw runtime_error("team name is already in use"); } for (const auto& it : team->members) { - if (!this->serial_number_to_team.emplace(it.second.serial_number, team).second) { + if (!this->account_id_to_team.emplace(it.second.account_id, team).second) { static_game_data_log.warning("Serial number %08" PRIX32 " (%010" PRIu32 ") exists in multiple teams", - it.second.serial_number, it.second.serial_number); + it.second.account_id, it.second.account_id); } } } @@ -462,6 +467,6 @@ void TeamIndex::remove_from_indexes(shared_ptr team) { this->id_to_team.erase(team->team_id); this->name_to_team.erase(team->name); for (const auto& it : team->members) { - this->serial_number_to_team.erase(it.second.serial_number); + this->account_id_to_team.erase(it.second.account_id); } } diff --git a/src/TeamIndex.hh b/src/TeamIndex.hh index ac7303f8..d15837b1 100644 --- a/src/TeamIndex.hh +++ b/src/TeamIndex.hh @@ -22,7 +22,7 @@ public: IS_MASTER = 0x01, IS_LEADER = 0x02, }; - uint32_t serial_number = 0; + uint32_t account_id = 0; uint8_t flags = 0; uint64_t points = 0; std::string name; @@ -60,7 +60,7 @@ public: uint32_t points = 0; uint32_t spent_points = 0; std::string name; - uint32_t master_serial_number = 0; + uint32_t master_account_id = 0; std::unordered_map members; uint32_t reward_flags = 0; std::unordered_set reward_keys; @@ -79,7 +79,7 @@ public: void save_flag() const; void delete_files() const; - PSOBBTeamMembership membership_for_member(uint32_t serial_number) const; + PSOBBTeamMembership membership_for_member(uint32_t account_id) const; [[nodiscard]] inline bool check_reward_flag(RewardFlag flag) const { return !!(static_cast(flag) & this->reward_flags); @@ -125,21 +125,21 @@ public: size_t count() const; std::shared_ptr get_by_id(uint32_t team_id) const; std::shared_ptr get_by_name(const std::string& name) const; - std::shared_ptr get_by_serial_number(uint32_t serial_number) const; + std::shared_ptr get_by_account_id(uint32_t account_id) const; std::vector> all() const; - std::shared_ptr create(const std::string& name, uint32_t master_serial_number, const std::string& master_name); + std::shared_ptr create(const std::string& name, uint32_t master_account_id, const std::string& master_name); void disband(uint32_t team_id); void rename(uint32_t team_id, const std::string& new_name); - void add_member(uint32_t team_id, uint32_t serial_number, const std::string& name); - void remove_member(uint32_t serial_number); - void update_member_name(uint32_t serial_number, const std::string& name); - void add_member_points(uint32_t serial_number, uint32_t points); + void add_member(uint32_t team_id, uint32_t account_id, const std::string& name); + void remove_member(uint32_t account_id); + void update_member_name(uint32_t account_id, const std::string& name); + void add_member_points(uint32_t account_id, uint32_t points); void set_flag_data(uint32_t team_id, const parray& flag_data); - bool promote_leader(uint32_t master_serial_number, uint32_t leader_serial_number); - bool demote_leader(uint32_t master_serial_number, uint32_t leader_serial_number); - void change_master(uint32_t master_serial_number, uint32_t new_master_serial_number); + bool promote_leader(uint32_t master_account_id, uint32_t leader_account_id); + bool demote_leader(uint32_t master_account_id, uint32_t leader_account_id); + void change_master(uint32_t master_account_id, uint32_t new_master_account_id); void buy_reward(uint32_t team_id, const std::string& key, uint32_t points, Team::RewardFlag reward_flag); protected: @@ -147,7 +147,7 @@ protected: uint32_t next_team_id; std::unordered_map> id_to_team; std::unordered_map> name_to_team; - std::unordered_map> serial_number_to_team; + std::unordered_map> account_id_to_team; std::vector reward_defs; void add_to_indexes(std::shared_ptr team); diff --git a/system/config.example.json b/system/config.example.json index 80501603..f04c65e5 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -274,20 +274,20 @@ // a full session log before submitting your report. "HideDownloadCommands": true, - // If this option is disabled, the server only allows users who have licenses + // If this option is disabled, the server only allows users who have accounts // on the server to connect. If this is enabled, all users will be allowed to - // connect even if they don't have licenses. When a user connects with an + // connect even if they don't have accounts. When a user connects with an // unregistered license (serial number and access key combination, or username - // and password combination on BB), a new license is created for them. - // Licenses are addressed by serial numbers; on BB, the new license's serial + // and password combination on BB), a new account is created for them. + // Accounts are addressed by serial numbers; on BB, the new account's serial // number is a hash of the username. "AllowUnregisteredUsers": true, // If this option is enabled and AllowUnregisteredUsers is enabled, the server - // will use temporary licenses for the prototype versions (DC NTE, DC 11/2000, - // GC NTE, and Ep3 NTE) instead of permanent licenses. In this case, you can - // still manually create permanent licenses for NTE players. - "UseTemporaryLicensesForPrototypes": true, + // will use temporary accounts for the prototype versions (DC NTE, DC 11/2000, + // GC NTE, and Ep3 NTE) instead of permanent accounts. In this case, you can + // still manually create permanent accounts for NTE players. + "UseTemporaryAccountsForPrototypes": true, // If this option is enabled, PC NTE players will be allowed to connect. This // is the only version of the game that does not have any way to identify the @@ -298,7 +298,7 @@ // 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 license flag will be able to + // players with the ALWAYS_ENABLE_CHAT_COMMANDS account flag will be able to // use chat commands. "EnableChatCommands": true, @@ -466,10 +466,10 @@ // If this is enabled, all players will have infinite Meseta, effectively // making the jukebox and Pinz's Shop free. Otherwise, Meseta behaves as - // defined below. Meseta rewards are tied to a player's license (and therefore - // their serial number) and are stored server-side. Unlike other servers, - // newserv forbids overdrafting Meseta; if this option is disabled and a - // player spends Meseta they don't have, the player is disconnected. + // defined below. Meseta rewards are tied to a player's account and are + // stored server-side. Unlike other servers, newserv forbids overdrafting + // Meseta; if this option is disabled and a player spends Meseta they don't + // have, the player is disconnected. "Episode3InfiniteMeseta": false, // Meseta values for winning each tournament round. If a player defeats // another player in round 1, for example, they will earn 400 Meseta; if they diff --git a/system/quests/battle/b88001.json b/system/quests/battle/b88001.json index 8f7dc917..2756ed6f 100644 --- a/system/quests/battle/b88001.json +++ b/system/quests/battle/b88001.json @@ -37,7 +37,7 @@ // Quests may be set to be unavailable until a preceding quest has been // cleared or a team reward has been purchased. To enable this feature, set a // value for AvailableIf in the quest's JSON file. (This is ignored if the - // player has the DISABLE_QUEST_REQUIREMENTS flag in their license.) This + // player has the DISABLE_QUEST_REQUIREMENTS flag in their account.) This // field's value should be an expression that tests any of the following: // F_XXXX: Quest flag specified in hex (e.g. F_014D) // CC_EpX_Y: Whether or not Challenge stage X in Episode Y is complete @@ -52,6 +52,6 @@ // On BB, quests may be disabled but still visible to the player. This // expression controls when that should be the case. If AvailableIf evaluates // to false, this is ignored. This field is also ignored if the player has - // the DISABLE_QUEST_REQUIREMENTS flag in their license. + // the DISABLE_QUEST_REQUIREMENTS flag in their account. // "EnabledIf": "!F_0169", } diff --git a/tests/config.json b/tests/config.json index eb2c9b45..ae8c4b33 100644 --- a/tests/config.json +++ b/tests/config.json @@ -6,7 +6,7 @@ // replay runner uses virtual connections instead. // 2. The IP stack simulator is disabled. // 3. Unregistered users are allowed. This enables the tests to run on other - // machines, which won't have the same license file. + // machines, which won't have the same account files. "ServerName": "Alexandria", "CatchHandlerExceptions": false, @@ -146,7 +146,7 @@ "HideDownloadCommands": true, "AllowUnregisteredUsers": true, - "UseTemporaryLicensesForPrototypes": true, + "UseTemporaryAccountsForPrototypes": true, "AllowPCNTE": true, "AllowDCPCGames": true, "AllowGCXBGames": true,