From 50f3ebca5e3e7ac7f9e0cd6443ace8430f310c58 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Thu, 1 Feb 2024 21:28:35 -0800 Subject: [PATCH] add support for shared serial mechanics --- src/ChatCommands.cc | 50 +++++++++++++++---------- src/Client.cc | 4 +- src/CommandFormats.hh | 4 +- src/License.cc | 77 ++++++++++++++++++++++++++++++--------- src/License.hh | 46 ++++++++++++++++++++--- src/Lobby.cc | 2 +- src/ProxyServer.cc | 18 ++++++--- src/ReceiveCommands.cc | 29 +++++++-------- src/ReceiveSubcommands.cc | 6 +-- src/SendCommands.cc | 2 +- src/ServerShell.cc | 30 +++++++++++---- 11 files changed, 188 insertions(+), 80 deletions(-) diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 74ab9be1..4d6da846 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -38,11 +38,11 @@ private: std::string user_msg; }; -static void check_license_flags(shared_ptr c, uint32_t mask) { +static void check_license_flag(shared_ptr c, License::Flag flag) { if (!c->license) { throw precondition_failed("$C6You are not\nlogged in."); } - if ((c->license->flags & mask) != mask) { + if (!c->license->check_flag(flag)) { throw precondition_failed("$C6You do not have\npermission to\nrun this command."); } } @@ -66,19 +66,20 @@ static void check_is_ep3(shared_ptr c, bool is_ep3) { } static void check_cheats_enabled(shared_ptr l, shared_ptr c) { - if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && !(c->license->flags & License::Flag::CHEAT_ANYWHERE)) { + if (!l->check_flag(Lobby::Flag::CHEATS_ENABLED) && !c->license->check_flag(License::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->flags & License::Flag::CHEAT_ANYWHERE)) { + if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) && !c->license->check_flag(License::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->flags & License::Flag::CHEAT_ANYWHERE))) { + if ((s->cheat_mode_behavior == ServerState::BehaviorSwitch::OFF) && + (!ses->license || !ses->license->check_flag(License::Flag::CHEAT_ANYWHERE))) { throw precondition_failed("$C6Cheats are disabled\non this proxy."); } } @@ -257,13 +258,13 @@ static void proxy_command_lobby_info(shared_ptr ses, } static void server_command_ax(shared_ptr c, const std::string& args) { - check_license_flags(c, License::Flag::ANNOUNCE); + check_license_flag(c, License::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_flags(c, License::Flag::ANNOUNCE); + check_license_flag(c, License::Flag::ANNOUNCE); send_text_message(s, args); } @@ -280,7 +281,7 @@ static void proxy_command_arrow(shared_ptr ses, cons } static void server_command_debug(shared_ptr c, const std::string&) { - check_license_flags(c, License::Flag::DEBUG); + check_license_flag(c, License::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")); } @@ -543,7 +544,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_flags(c, License::Flag::DEBUG); + check_license_flag(c, License::Flag::DEBUG); auto l = c->require_lobby(); if (l->is_game() && l->is_ep3()) { G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; @@ -746,7 +747,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->flags & License::Flag::CHEAT_ANYWHERE)) { + if (!c->license->check_flag(License::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; @@ -759,7 +760,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_flags(c, License::Flag::CHANGE_EVENT); + check_license_flag(c, License::Flag::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { @@ -788,7 +789,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_flags(c, License::Flag::CHANGE_EVENT); + check_license_flag(c, License::Flag::CHANGE_EVENT); uint8_t new_event = event_for_name(args); if (new_event == 0xFF) { @@ -1043,7 +1044,7 @@ 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->flags & License::Flag::CHEAT_ANYWHERE)); + bool cheats_allowed = (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || c->license->check_flag(License::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) { @@ -1084,7 +1085,8 @@ static void server_command_edit(shared_ptr c, const std::string& args) { throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO."); } - bool cheats_allowed = ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || (c->license->flags & License::Flag::CHEAT_ANYWHERE)); + bool cheats_allowed = ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || + c->license->check_flag(License::Flag::CHEAT_ANYWHERE)); string encoded_args = tolower(args); vector tokens = split(encoded_args, ' '); @@ -1261,6 +1263,10 @@ 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"); + return; + } server_command_bbchar_savechar(c, args, false); } @@ -1269,6 +1275,10 @@ 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"); + return; + } auto l = c->require_lobby(); check_is_game(l, false); @@ -1314,7 +1324,7 @@ 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_flags(c, License::Flag::SILENCE_USER); + check_license_flag(c, License::Flag::SILENCE_USER); auto target = s->find_client(&args); if (!target->license) { @@ -1323,7 +1333,7 @@ static void server_command_silence(shared_ptr c, const std::string& args return; } - if (target->license->flags & License::Flag::MODERATOR) { + if (target->license->check_flag(License::Flag::SILENCE_USER)) { send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } @@ -1337,7 +1347,7 @@ 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_flags(c, License::Flag::KICK_USER); + check_license_flag(c, License::Flag::KICK_USER); auto target = s->find_client(&args); if (!target->license) { @@ -1346,7 +1356,7 @@ static void server_command_kick(shared_ptr c, const std::string& args) { return; } - if (target->license->flags & License::Flag::MODERATOR) { + if (target->license->check_flag(License::Flag::KICK_USER)) { send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } @@ -1360,7 +1370,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_flags(c, License::Flag::BAN_USER); + check_license_flag(c, License::Flag::BAN_USER); size_t space_pos = args.find(' '); if (space_pos == string::npos) { @@ -1376,7 +1386,7 @@ static void server_command_ban(shared_ptr c, const std::string& args) { return; } - if (target->license->flags & License::Flag::BAN_USER) { + if (target->license->check_flag(License::Flag::BAN_USER)) { send_text_message(c, "$C6You do not have\nsufficient privileges."); return; } diff --git a/src/Client.cc b/src/Client.cc index 47312453..0c070331 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -335,7 +335,7 @@ shared_ptr Client::team() const { } bool Client::can_see_quest(shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const { - if (this->license && (this->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (this->license && this->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { return true; } if (!q->available_expression) { @@ -356,7 +356,7 @@ bool Client::can_see_quest(shared_ptr q, uint8_t event, uint8_t dif } bool Client::can_play_quest(shared_ptr q, uint8_t event, uint8_t difficulty, size_t num_players) const { - if (this->license && (this->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (this->license && this->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { return true; } if (!q->enabled_expression) { diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 0ffcbfdd..c42f3198 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -4650,7 +4650,7 @@ struct G_SyncEnemyState_6x6B_Entry_Decompressed { uint8_t blue_buff_level = 0; } __packed__; -// 6x6C: Sync object state (used while loading into game; same header format as 6E) +// 6x6C: Sync object state (used while loading into game) // Compressed format is the same as 6x6B. // Decompressed format is a list of these @@ -4659,7 +4659,7 @@ struct G_SyncObjectState_6x6C_Entry_Decompressed { le_uint16_t object_index = 0; } __packed__; -// 6x6D: Sync item state (used while loading into game; same header format as 6E) +// 6x6D: Sync item state (used while loading into game) // Internal name: RcvItemCondition // Compressed format is the same as 6x6B. diff --git a/src/License.cc b/src/License.cc index bb208d18..94bd20a0 100644 --- a/src/License.cc +++ b/src/License.cc @@ -3,6 +3,7 @@ #include #include +#include #include #include "License.hh" @@ -165,57 +166,76 @@ void LicenseIndex::remove(uint32_t serial_number) { } } -shared_ptr LicenseIndex::verify_v1_v2(uint32_t serial_number, const string& access_key) const { +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(); } - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } return license; } catch (const out_of_range&) { throw missing_license(); } } -shared_ptr LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key) const { +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(); } - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } return license; } catch (const out_of_range&) { throw missing_license(); } } -shared_ptr LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key, const string& password) const { +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(); } - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } return license; } catch (const out_of_range&) { throw missing_license(); @@ -228,15 +248,18 @@ shared_ptr LicenseIndex::verify_xb(const string& gamertag, uint64_t use } 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(); } - if (license->ban_end_time && (license->ban_end_time >= now())) { - throw invalid_argument("user is banned"); - } return license; } catch (const out_of_range&) { throw missing_license(); @@ -249,18 +272,38 @@ shared_ptr LicenseIndex::verify_bb(const string& username, const string } try { auto& license = this->bb_username_to_license.at(username); - if (license->bb_password != password) { - throw incorrect_password(); + 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) diff --git a/src/License.hh b/src/License.hh index de6a4ad0..58b2d963 100644 --- a/src/License.hh +++ b/src/License.hh @@ -12,22 +12,23 @@ class LicenseIndex; class License { public: - enum Flag : uint32_t { + enum class Flag : uint32_t { // clang-format off KICK_USER = 0x00000001, BAN_USER = 0x00000002, SILENCE_USER = 0x00000004, - CHANGE_LOBBY_INFO = 0x00000008, CHANGE_EVENT = 0x00000010, ANNOUNCE = 0x00000020, FREE_JOIN_GAMES = 0x00000040, - UNLOCK_GAMES = 0x00000080, DEBUG = 0x01000000, CHEAT_ANYWHERE = 0x02000000, DISABLE_QUEST_REQUIREMENTS = 0x04000000, 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 = 0x78FFFF00, // clang-format on @@ -60,6 +61,22 @@ public: 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; }; @@ -106,9 +123,19 @@ public: void add(std::shared_ptr l); void remove(uint32_t serial_number); - std::shared_ptr verify_v1_v2(uint32_t serial_number, const std::string& access_key) const; - std::shared_ptr verify_gc(uint32_t serial_number, const std::string& access_key) const; - std::shared_ptr verify_gc(uint32_t serial_number, const std::string& access_key, const std::string& password) 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; @@ -116,6 +143,13 @@ protected: std::unordered_map> bb_username_to_license; std::unordered_map> xb_gamertag_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 { diff --git a/src/Lobby.cc b/src/Lobby.cc index 9bbfed8b..0c6d470d 100644 --- a/src/Lobby.cc +++ b/src/Lobby.cc @@ -716,7 +716,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->flags & License::Flag::FREE_JOIN_GAMES)) { + if (!c->license->check_flag(License::Flag::FREE_JOIN_GAMES)) { if (password && !this->password.empty() && (*password != this->password)) { return JoinError::INCORRECT_PASSWORD; } diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 3667016e..c16631bf 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -278,7 +278,8 @@ 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()); + ses->license = s->license_index->verify_v1_v2( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -287,7 +288,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()); + ses->license = s->license_index->verify_v1_v2( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -297,11 +299,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(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->license = s->license_index->verify_gc_no_password( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); } 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()); + ses->license = s->license_index->verify_v1_v2( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); } ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; @@ -319,7 +323,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()); + ses->license = s->license_index->verify_v1_v2( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); @@ -332,7 +337,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(stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode()); + ses->license = s->license_index->verify_gc_no_password( + stoul(cmd.serial_number.decode(), nullptr, 16), cmd.access_key.decode(), cmd.name.decode()); ses->sub_version = cmd.sub_version; ses->channel.language = cmd.language; ses->character_name = cmd.name.decode(ses->channel.language); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 755d8a71..d180f58b 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -57,7 +57,7 @@ static shared_ptr proxy_options_menu_for_client(shared_ptrcheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || (c->license->flags & License::Flag::CHEAT_ANYWHERE)) { + if ((s->cheat_mode_behavior != ServerState::BehaviorSwitch::OFF) || c->license->check_flag(License::Flag::CHEAT_ANYWHERE)) { if (!is_ep3(c->version())) { add_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"); @@ -397,7 +397,7 @@ static void on_DB_V3(shared_ptr c, uint16_t, uint32_t, string& data) { uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - auto l = s->license_index->verify_gc(serial_number, cmd.access_key.decode(), cmd.password.decode()); + auto l = s->license_index->verify_gc_with_password(serial_number, cmd.access_key.decode(), cmd.password.decode(), ""); c->set_license(l); send_command(c, 0x9A, 0x02); @@ -448,7 +448,7 @@ static void on_88_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), ""); c->set_license(l); send_command(c, 0x88, 0x00); @@ -493,7 +493,7 @@ static void on_8B_DCNTE(shared_ptr c, uint16_t, uint32_t, string& data) uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), cmd.name.decode()); c->set_license(l); } catch (const LicenseIndex::no_username& e) { @@ -547,7 +547,7 @@ static void on_90_DC(shared_ptr c, uint16_t, uint32_t, string& data) { uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), ""); c->set_license(l); send_command(c, 0x90, 0x02); @@ -604,7 +604,7 @@ static void on_93_DC(shared_ptr c, uint16_t, uint32_t, string& data) { uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); try { - shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); + shared_ptr l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), cmd.name.decode()); c->set_license(l); } catch (const LicenseIndex::no_username& e) { @@ -700,7 +700,7 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { } } else { serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); - l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); + l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), ""); } break; } @@ -709,7 +709,7 @@ static void on_9A(shared_ptr c, uint16_t, uint32_t, string& data) { case Version::GC_EP3_NTE: case Version::GC_EP3: { serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); - l = s->license_index->verify_gc(serial_number, cmd.access_key.decode()); + l = s->license_index->verify_gc_no_password(serial_number, cmd.access_key.decode(), ""); break; } default: @@ -772,13 +772,13 @@ static void on_9C(shared_ptr c, uint16_t, uint32_t, string& data) { switch (c->version()) { case Version::DC_V2: case Version::PC_V2: - l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); + l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), ""); break; case Version::GC_NTE: case Version::GC_V3: case Version::GC_EP3_NTE: case Version::GC_EP3: - l = s->license_index->verify_gc(serial_number, cmd.access_key.decode(), cmd.password.decode()); + l = s->license_index->verify_gc_with_password(serial_number, cmd.access_key.decode(), cmd.password.decode(), ""); break; default: // TODO: PC_NTE can probably send 9C, but due to the way we've @@ -911,7 +911,7 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d } } 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()); + l = s->license_index->verify_v1_v2(serial_number, base_cmd->access_key.decode(), base_cmd->name.decode()); } break; case Version::GC_NTE: @@ -919,7 +919,7 @@ static void on_9D_9E(shared_ptr c, uint16_t command, uint32_t, string& d 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(serial_number, base_cmd->access_key.decode()); + l = s->license_index->verify_gc_no_password(serial_number, base_cmd->access_key.decode(), base_cmd->name.decode()); break; default: throw logic_error("unsupported versioned command"); @@ -2477,7 +2477,7 @@ static void on_10(shared_ptr c, uint16_t, uint32_t, string& data) { break; } } - if (!(c->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (!c->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { include_condition = l->quest_include_condition(); } } @@ -4015,8 +4015,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->flags & License::Flag::FREE_JOIN_GAMES) && - (min_level > p->disp.stats.level)) { + if (!c->license->check_flag(License::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 send_lobby_message_box(c, "Your level is too\nlow for this\ndifficulty"); diff --git a/src/ReceiveSubcommands.cc b/src/ReceiveSubcommands.cc index 19049f12..ec9fbd91 100644 --- a/src/ReceiveSubcommands.cc +++ b/src/ReceiveSubcommands.cc @@ -1201,7 +1201,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 (is_v1_or_v2(c->version()) && (cmd.client_id == c->lobby_client_id)) { - bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->flags & License::Flag::CHEAT_ANYWHERE); + bool player_cheats_enabled = l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::Flag::CHEAT_ANYWHERE)); if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) { send_remove_conditions(c); } @@ -1216,7 +1216,7 @@ static void on_hit_by_enemy(shared_ptr c, uint8_t command, uint8_t flag, 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->flags & License::Flag::CHEAT_ANYWHERE)); + (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::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); } @@ -1230,7 +1230,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->flags & License::Flag::CHEAT_ANYWHERE)); + (l->check_flag(Lobby::Flag::CHEATS_ENABLED) || (c->license->check_flag(License::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); } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 082515d2..57153f9b 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -1519,7 +1519,7 @@ void send_quest_categories_menu_t( QuestMenuType menu_type, Episode episode) { QuestIndex::IncludeCondition include_condition = nullptr; - if (!(c->license->flags & License::Flag::DISABLE_QUEST_REQUIREMENTS)) { + if (!c->license->check_flag(License::Flag::DISABLE_QUEST_REQUIREMENTS)) { auto l = c->lobby.lock(); include_condition = l ? l->quest_include_condition() : nullptr; } diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 0463120f..bbef6639 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -154,7 +154,23 @@ Server commands:\n\ gc-password= (GC password)\n\ access-key= (DC/GC/PC access key)\n\ serial= (decimal serial number; required for all licenses)\n\ - flags= (can be normal, mod, admin, root, or numeric)\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\ + 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\n\ 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\ @@ -327,11 +343,11 @@ Proxy session commands:\n\ if (mask == "normal") { l->flags = 0; } else if (mask == "mod") { - l->flags = License::Flag::MODERATOR; + l->replace_all_flags(License::Flag::MODERATOR); } else if (mask == "admin") { - l->flags = License::Flag::ADMINISTRATOR; + l->replace_all_flags(License::Flag::ADMINISTRATOR); } else if (mask == "root") { - l->flags = License::Flag::ROOT; + l->replace_all_flags(License::Flag::ROOT); } else { l->flags = stoul(mask, nullptr, 16); } @@ -385,11 +401,11 @@ Proxy session commands:\n\ if (mask == "normal") { l->flags = 0; } else if (mask == "mod") { - l->flags = License::Flag::MODERATOR; + l->replace_all_flags(License::Flag::MODERATOR); } else if (mask == "admin") { - l->flags = License::Flag::ADMINISTRATOR; + l->replace_all_flags(License::Flag::ADMINISTRATOR); } else if (mask == "root") { - l->flags = License::Flag::ROOT; + l->replace_all_flags(License::Flag::ROOT); } else { l->flags = stoul(mask, nullptr, 16); }