add support for shared serial mechanics

This commit is contained in:
Martin Michelsen
2024-02-01 21:28:35 -08:00
parent ef89699d59
commit 50f3ebca5e
11 changed files with 188 additions and 80 deletions
+30 -20
View File
@@ -38,11 +38,11 @@ private:
std::string user_msg; std::string user_msg;
}; };
static void check_license_flags(shared_ptr<Client> c, uint32_t mask) { static void check_license_flag(shared_ptr<Client> c, License::Flag flag) {
if (!c->license) { if (!c->license) {
throw precondition_failed("$C6You are not\nlogged in."); 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."); throw precondition_failed("$C6You do not have\npermission to\nrun this command.");
} }
} }
@@ -66,19 +66,20 @@ static void check_is_ep3(shared_ptr<Client> c, bool is_ep3) {
} }
static void check_cheats_enabled(shared_ptr<Lobby> l, shared_ptr<Client> c) { static void check_cheats_enabled(shared_ptr<Lobby> l, shared_ptr<Client> 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."); throw precondition_failed("$C6This command can\nonly be used in\ncheat mode.");
} }
} }
static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<Client> c) { static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<Client> 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."); throw precondition_failed("$C6Cheats are disabled\non this server.");
} }
} }
static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<ProxyServer::LinkedSession> ses) { static void check_cheats_allowed(shared_ptr<ServerState> s, shared_ptr<ProxyServer::LinkedSession> 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."); throw precondition_failed("$C6Cheats are disabled\non this proxy.");
} }
} }
@@ -257,13 +258,13 @@ static void proxy_command_lobby_info(shared_ptr<ProxyServer::LinkedSession> ses,
} }
static void server_command_ax(shared_ptr<Client> c, const std::string& args) { static void server_command_ax(shared_ptr<Client> 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()); ax_messages_log.info("%s", args.c_str());
} }
static void server_command_announce(shared_ptr<Client> c, const std::string& args) { static void server_command_announce(shared_ptr<Client> c, const std::string& args) {
auto s = c->require_server_state(); 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); send_text_message(s, args);
} }
@@ -280,7 +281,7 @@ static void proxy_command_arrow(shared_ptr<ProxyServer::LinkedSession> ses, cons
} }
static void server_command_debug(shared_ptr<Client> c, const std::string&) { static void server_command_debug(shared_ptr<Client> 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); 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")); 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<Client> c, const std:
} }
static void server_command_auction(shared_ptr<Client> c, const std::string&) { static void server_command_auction(shared_ptr<Client> c, const std::string&) {
check_license_flags(c, License::Flag::DEBUG); check_license_flag(c, License::Flag::DEBUG);
auto l = c->require_lobby(); auto l = c->require_lobby();
if (l->is_game() && l->is_ep3()) { if (l->is_game() && l->is_ep3()) {
G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd; G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd;
@@ -746,7 +747,7 @@ static void server_command_cheat(shared_ptr<Client> c, const std::string&) {
l->toggle_flag(Lobby::Flag::CHEATS_ENABLED); l->toggle_flag(Lobby::Flag::CHEATS_ENABLED);
send_text_message_printf(l, "Cheat mode %s", l->check_flag(Lobby::Flag::CHEATS_ENABLED) ? "enabled" : "disabled"); 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); 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) { if (l->min_level < default_min_level) {
l->min_level = default_min_level; l->min_level = default_min_level;
@@ -759,7 +760,7 @@ static void server_command_cheat(shared_ptr<Client> c, const std::string&) {
static void server_command_lobby_event(shared_ptr<Client> c, const std::string& args) { static void server_command_lobby_event(shared_ptr<Client> c, const std::string& args) {
auto l = c->require_lobby(); auto l = c->require_lobby();
check_is_game(l, false); 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); uint8_t new_event = event_for_name(args);
if (new_event == 0xFF) { if (new_event == 0xFF) {
@@ -788,7 +789,7 @@ static void proxy_command_lobby_event(shared_ptr<ProxyServer::LinkedSession> ses
} }
static void server_command_lobby_event_all(shared_ptr<Client> c, const std::string& args) { static void server_command_lobby_event_all(shared_ptr<Client> 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); uint8_t new_event = event_for_name(args);
if (new_event == 0xFF) { if (new_event == 0xFF) {
@@ -1043,7 +1044,7 @@ static void server_command_min_level(shared_ptr<Client> c, const std::string& ar
size_t new_min_level = stoull(args) - 1; size_t new_min_level = stoull(args) - 1;
auto s = c->require_server_state(); 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) { if (!cheats_allowed) {
size_t default_min_level = s->default_min_level_for_game(l->base_version, l->episode, l->difficulty); 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) { if (new_min_level < default_min_level) {
@@ -1084,7 +1085,8 @@ static void server_command_edit(shared_ptr<Client> c, const std::string& args) {
throw precondition_failed("$C6This command cannot\nbe used for your\nversion of PSO."); 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); string encoded_args = tolower(args);
vector<string> tokens = split(encoded_args, ' '); vector<string> tokens = split(encoded_args, ' ');
@@ -1261,6 +1263,10 @@ static void server_command_bbchar(shared_ptr<Client> c, const std::string& args)
} }
static void server_command_savechar(shared_ptr<Client> c, const std::string& args) { static void server_command_savechar(shared_ptr<Client> 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); server_command_bbchar_savechar(c, args, false);
} }
@@ -1269,6 +1275,10 @@ static void server_command_loadchar(shared_ptr<Client> c, const std::string& arg
send_text_message(c, "$C7This command can only\nbe used on v1 or v2"); send_text_message(c, "$C7This command can only\nbe used on v1 or v2");
return; 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(); auto l = c->require_lobby();
check_is_game(l, false); check_is_game(l, false);
@@ -1314,7 +1324,7 @@ static string name_for_client(shared_ptr<Client> c) {
static void server_command_silence(shared_ptr<Client> c, const std::string& args) { static void server_command_silence(shared_ptr<Client> c, const std::string& args) {
auto s = c->require_server_state(); auto s = c->require_server_state();
auto l = c->require_lobby(); 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); auto target = s->find_client(&args);
if (!target->license) { if (!target->license) {
@@ -1323,7 +1333,7 @@ static void server_command_silence(shared_ptr<Client> c, const std::string& args
return; 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."); send_text_message(c, "$C6You do not have\nsufficient privileges.");
return; return;
} }
@@ -1337,7 +1347,7 @@ static void server_command_silence(shared_ptr<Client> c, const std::string& args
static void server_command_kick(shared_ptr<Client> c, const std::string& args) { static void server_command_kick(shared_ptr<Client> c, const std::string& args) {
auto s = c->require_server_state(); auto s = c->require_server_state();
auto l = c->require_lobby(); 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); auto target = s->find_client(&args);
if (!target->license) { if (!target->license) {
@@ -1346,7 +1356,7 @@ static void server_command_kick(shared_ptr<Client> c, const std::string& args) {
return; 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."); send_text_message(c, "$C6You do not have\nsufficient privileges.");
return; return;
} }
@@ -1360,7 +1370,7 @@ static void server_command_kick(shared_ptr<Client> c, const std::string& args) {
static void server_command_ban(shared_ptr<Client> c, const std::string& args) { static void server_command_ban(shared_ptr<Client> c, const std::string& args) {
auto s = c->require_server_state(); auto s = c->require_server_state();
auto l = c->require_lobby(); 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(' '); size_t space_pos = args.find(' ');
if (space_pos == string::npos) { if (space_pos == string::npos) {
@@ -1376,7 +1386,7 @@ static void server_command_ban(shared_ptr<Client> c, const std::string& args) {
return; 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."); send_text_message(c, "$C6You do not have\nsufficient privileges.");
return; return;
} }
+2 -2
View File
@@ -335,7 +335,7 @@ shared_ptr<const TeamIndex::Team> Client::team() const {
} }
bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players) const { bool Client::can_see_quest(shared_ptr<const Quest> 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; return true;
} }
if (!q->available_expression) { if (!q->available_expression) {
@@ -356,7 +356,7 @@ bool Client::can_see_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t dif
} }
bool Client::can_play_quest(shared_ptr<const Quest> q, uint8_t event, uint8_t difficulty, size_t num_players) const { bool Client::can_play_quest(shared_ptr<const Quest> 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; return true;
} }
if (!q->enabled_expression) { if (!q->enabled_expression) {
+2 -2
View File
@@ -4650,7 +4650,7 @@ struct G_SyncEnemyState_6x6B_Entry_Decompressed {
uint8_t blue_buff_level = 0; uint8_t blue_buff_level = 0;
} __packed__; } __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. // Compressed format is the same as 6x6B.
// Decompressed format is a list of these // Decompressed format is a list of these
@@ -4659,7 +4659,7 @@ struct G_SyncObjectState_6x6C_Entry_Decompressed {
le_uint16_t object_index = 0; le_uint16_t object_index = 0;
} __packed__; } __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 // Internal name: RcvItemCondition
// Compressed format is the same as 6x6B. // Compressed format is the same as 6x6B.
+60 -17
View File
@@ -3,6 +3,7 @@
#include <string.h> #include <string.h>
#include <phosg/Filesystem.hh> #include <phosg/Filesystem.hh>
#include <phosg/Hash.hh>
#include <phosg/Time.hh> #include <phosg/Time.hh>
#include "License.hh" #include "License.hh"
@@ -165,57 +166,76 @@ void LicenseIndex::remove(uint32_t serial_number) {
} }
} }
shared_ptr<License> LicenseIndex::verify_v1_v2(uint32_t serial_number, const string& access_key) const { shared_ptr<License> LicenseIndex::verify_v1_v2(
uint32_t serial_number,
const string& access_key,
const string& character_name) const {
if (serial_number == 0) { if (serial_number == 0) {
throw no_username(); throw no_username();
} }
try { try {
auto& license = this->serial_number_to_license.at(serial_number); 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) { if (license->access_key.compare(0, 8, access_key) != 0) {
throw incorrect_access_key(); throw incorrect_access_key();
} }
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license; return license;
} catch (const out_of_range&) { } catch (const out_of_range&) {
throw missing_license(); throw missing_license();
} }
} }
shared_ptr<License> LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key) const { shared_ptr<License> LicenseIndex::verify_gc_no_password(
uint32_t serial_number,
const string& access_key,
const string& character_name) const {
if (serial_number == 0) { if (serial_number == 0) {
throw no_username(); throw no_username();
} }
try { try {
auto& license = this->serial_number_to_license.at(serial_number); 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) { if (license->access_key != access_key) {
throw incorrect_access_key(); throw incorrect_access_key();
} }
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license; return license;
} catch (const out_of_range&) { } catch (const out_of_range&) {
throw missing_license(); throw missing_license();
} }
} }
shared_ptr<License> LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key, const string& password) const { shared_ptr<License> 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) { if (serial_number == 0) {
throw no_username(); throw no_username();
} }
try { try {
auto& license = this->serial_number_to_license.at(serial_number); 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) { if (license->access_key != access_key) {
throw incorrect_access_key(); throw incorrect_access_key();
} }
if (license->gc_password != password) { if (license->gc_password != password) {
throw incorrect_password(); throw incorrect_password();
} }
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license; return license;
} catch (const out_of_range&) { } catch (const out_of_range&) {
throw missing_license(); throw missing_license();
@@ -228,15 +248,18 @@ shared_ptr<License> LicenseIndex::verify_xb(const string& gamertag, uint64_t use
} }
try { try {
auto& license = this->xb_gamertag_to_license.at(gamertag); 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)) { if (license->xb_user_id && (license->xb_user_id != user_id)) {
throw incorrect_access_key(); throw incorrect_access_key();
} }
if (license->xb_account_id && (license->xb_account_id != account_id)) { if (license->xb_account_id && (license->xb_account_id != account_id)) {
throw incorrect_access_key(); throw incorrect_access_key();
} }
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license; return license;
} catch (const out_of_range&) { } catch (const out_of_range&) {
throw missing_license(); throw missing_license();
@@ -249,18 +272,38 @@ shared_ptr<License> LicenseIndex::verify_bb(const string& username, const string
} }
try { try {
auto& license = this->bb_username_to_license.at(username); auto& license = this->bb_username_to_license.at(username);
if (license->bb_password != password) { if (license->check_flag(License::Flag::IS_SHARED_SERIAL)) {
throw incorrect_password(); throw missing_license(); // BB users cannot use shared serials
} }
if (license->ban_end_time && (license->ban_end_time >= now())) { if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned"); throw invalid_argument("user is banned");
} }
if (license->bb_password != password) {
throw incorrect_password();
}
return license; return license;
} catch (const out_of_range&) { } catch (const out_of_range&) {
throw missing_license(); throw missing_license();
} }
} }
shared_ptr<License> 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() { DiskLicenseIndex::DiskLicenseIndex() {
struct BinaryLicense { struct BinaryLicense {
pstring<TextEncoding::ASCII, 0x14> username; // BB username (max. 16 chars; should technically be Unicode) pstring<TextEncoding::ASCII, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
+40 -6
View File
@@ -12,22 +12,23 @@ class LicenseIndex;
class License { class License {
public: public:
enum Flag : uint32_t { enum class Flag : uint32_t {
// clang-format off // clang-format off
KICK_USER = 0x00000001, KICK_USER = 0x00000001,
BAN_USER = 0x00000002, BAN_USER = 0x00000002,
SILENCE_USER = 0x00000004, SILENCE_USER = 0x00000004,
CHANGE_LOBBY_INFO = 0x00000008,
CHANGE_EVENT = 0x00000010, CHANGE_EVENT = 0x00000010,
ANNOUNCE = 0x00000020, ANNOUNCE = 0x00000020,
FREE_JOIN_GAMES = 0x00000040, FREE_JOIN_GAMES = 0x00000040,
UNLOCK_GAMES = 0x00000080,
DEBUG = 0x01000000, DEBUG = 0x01000000,
CHEAT_ANYWHERE = 0x02000000, CHEAT_ANYWHERE = 0x02000000,
DISABLE_QUEST_REQUIREMENTS = 0x04000000, DISABLE_QUEST_REQUIREMENTS = 0x04000000,
MODERATOR = 0x00000007, MODERATOR = 0x00000007,
ADMINISTRATOR = 0x000000FF, ADMINISTRATOR = 0x000000FF,
ROOT = 0x7FFFFFFF, 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, UNUSED_BITS = 0x78FFFF00,
// clang-format on // clang-format on
@@ -60,6 +61,22 @@ public:
virtual void save() const; virtual void save() const;
virtual void delete_file() const; virtual void delete_file() const;
[[nodiscard]] inline bool check_flag(Flag flag) const {
return !!(this->flags & static_cast<uint32_t>(flag));
}
inline void set_flag(Flag flag) {
this->flags |= static_cast<uint32_t>(flag);
}
inline void clear_flag(Flag flag) {
this->flags &= (~static_cast<uint32_t>(flag));
}
inline void toggle_flag(Flag flag) {
this->flags ^= static_cast<uint32_t>(flag);
}
inline void replace_all_flags(Flag mask) {
this->flags = static_cast<uint32_t>(mask);
}
std::string str() const; std::string str() const;
}; };
@@ -106,9 +123,19 @@ public:
void add(std::shared_ptr<License> l); void add(std::shared_ptr<License> l);
void remove(uint32_t serial_number); void remove(uint32_t serial_number);
std::shared_ptr<License> verify_v1_v2(uint32_t serial_number, const std::string& access_key) const; std::shared_ptr<License> verify_v1_v2(
std::shared_ptr<License> verify_gc(uint32_t serial_number, const std::string& access_key) const; uint32_t serial_number,
std::shared_ptr<License> verify_gc(uint32_t serial_number, const std::string& access_key, const std::string& password) const; const std::string& access_key,
const std::string& character_name) const;
std::shared_ptr<License> verify_gc_no_password(
uint32_t serial_number,
const std::string& access_key,
const std::string& character_name) const;
std::shared_ptr<License> 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<License> verify_xb(const std::string& gamertag, uint64_t user_id, uint64_t account_id) const; std::shared_ptr<License> verify_xb(const std::string& gamertag, uint64_t user_id, uint64_t account_id) const;
std::shared_ptr<License> verify_bb(const std::string& username, const std::string& password) const; std::shared_ptr<License> verify_bb(const std::string& username, const std::string& password) const;
@@ -116,6 +143,13 @@ protected:
std::unordered_map<std::string, std::shared_ptr<License>> bb_username_to_license; std::unordered_map<std::string, std::shared_ptr<License>> bb_username_to_license;
std::unordered_map<std::string, std::shared_ptr<License>> xb_gamertag_to_license; std::unordered_map<std::string, std::shared_ptr<License>> xb_gamertag_to_license;
std::unordered_map<uint32_t, std::shared_ptr<License>> serial_number_to_license; std::unordered_map<uint32_t, std::shared_ptr<License>> serial_number_to_license;
std::shared_ptr<License> 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 { class DiskLicenseIndex : public LicenseIndex {
+1 -1
View File
@@ -716,7 +716,7 @@ Lobby::JoinError Lobby::join_error_for_client(std::shared_ptr<Client> c, const s
if (this->mode == GameMode::SOLO) { if (this->mode == GameMode::SOLO) {
return JoinError::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)) { if (password && !this->password.empty() && (*password != this->password)) {
return JoinError::INCORRECT_PASSWORD; return JoinError::INCORRECT_PASSWORD;
} }
+12 -6
View File
@@ -278,7 +278,8 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
ses->channel.version = Version::DC_NTE; ses->channel.version = Version::DC_NTE;
ses->log.info("Version changed to DC_NTE"); ses->log.info("Version changed to DC_NTE");
const auto& cmd = check_size_t<C_Login_DCNTE_8B>(data, sizeof(C_LoginExtended_DCNTE_8B)); const auto& cmd = check_size_t<C_Login_DCNTE_8B>(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->sub_version = cmd.sub_version;
ses->channel.language = cmd.language; ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.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->channel.version = Version::DC_V1;
ses->log.info("Version changed to DC_V1"); ses->log.info("Version changed to DC_V1");
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data); const auto& cmd = check_size_t<C_LoginV1_DC_93>(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->sub_version = cmd.sub_version;
ses->channel.language = cmd.language; ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.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) { if (cmd.sub_version >= 0x30) {
ses->log.info("Version changed to GC_NTE"); ses->log.info("Version changed to GC_NTE");
ses->channel.version = Version::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 } else { // DC V2
ses->log.info("Version changed to DC_V2"); ses->log.info("Version changed to DC_V2");
ses->channel.version = Version::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->sub_version = cmd.sub_version;
ses->channel.language = cmd.language; 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"); throw runtime_error("command is not 9D");
} }
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_PC_9D)); const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(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->sub_version = cmd.sub_version;
ses->channel.language = cmd.language; ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.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 // We should only get a 9E while the session is unlinked
if (command == 0x9E) { if (command == 0x9E) {
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E)); const auto& cmd = check_size_t<C_Login_GC_9E>(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->sub_version = cmd.sub_version;
ses->channel.language = cmd.language; ses->channel.language = cmd.language;
ses->character_name = cmd.name.decode(ses->channel.language); ses->character_name = cmd.name.decode(ses->channel.language);
+14 -15
View File
@@ -57,7 +57,7 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
"Block patches", "Disable patches sent\nby the remote server"); "Block patches", "Disable patches sent\nby the remote server");
add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, Client::Flag::SWITCH_ASSIST_ENABLED, add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, Client::Flag::SWITCH_ASSIST_ENABLED,
"Switch assist", "Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); "Switch assist", "Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially");
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)) {
if (!is_ep3(c->version())) { if (!is_ep3(c->version())) {
add_option(ProxyOptionsMenuItemID::INFINITE_HP, Client::Flag::INFINITE_HP_ENABLED, 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"); "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<Client> c, uint16_t, uint32_t, string& data) {
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16);
try { 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); c->set_license(l);
send_command(c, 0x9A, 0x02); send_command(c, 0x9A, 0x02);
@@ -448,7 +448,7 @@ static void on_88_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16);
try { try {
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), "");
c->set_license(l); c->set_license(l);
send_command(c, 0x88, 0x00); send_command(c, 0x88, 0x00);
@@ -493,7 +493,7 @@ static void on_8B_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, string& data)
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16);
try { try {
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), cmd.name.decode());
c->set_license(l); c->set_license(l);
} catch (const LicenseIndex::no_username& e) { } catch (const LicenseIndex::no_username& e) {
@@ -547,7 +547,7 @@ static void on_90_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16);
try { try {
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), "");
c->set_license(l); c->set_license(l);
send_command(c, 0x90, 0x02); send_command(c, 0x90, 0x02);
@@ -604,7 +604,7 @@ static void on_93_DC(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); uint32_t serial_number = stoul(cmd.serial_number.decode(), nullptr, 16);
try { try {
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode()); shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key.decode(), cmd.name.decode());
c->set_license(l); c->set_license(l);
} catch (const LicenseIndex::no_username& e) { } catch (const LicenseIndex::no_username& e) {
@@ -700,7 +700,7 @@ static void on_9A(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
} }
} else { } else {
serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); 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; break;
} }
@@ -709,7 +709,7 @@ static void on_9A(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: { case Version::GC_EP3: {
serial_number = stoul(cmd.serial_number.decode(), nullptr, 16); 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; break;
} }
default: default:
@@ -772,13 +772,13 @@ static void on_9C(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
switch (c->version()) { switch (c->version()) {
case Version::DC_V2: case Version::DC_V2:
case Version::PC_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; break;
case Version::GC_NTE: case Version::GC_NTE:
case Version::GC_V3: case Version::GC_V3:
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: 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; break;
default: default:
// TODO: PC_NTE can probably send 9C, but due to the way we've // 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<Client> c, uint16_t command, uint32_t, string& d
} }
} else { } else {
serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); 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; break;
case Version::GC_NTE: case Version::GC_NTE:
@@ -919,7 +919,7 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, string& d
case Version::GC_EP3_NTE: case Version::GC_EP3_NTE:
case Version::GC_EP3: case Version::GC_EP3:
serial_number = stoul(base_cmd->serial_number.decode(), nullptr, 16); 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; break;
default: default:
throw logic_error("unsupported versioned command"); throw logic_error("unsupported versioned command");
@@ -2477,7 +2477,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
break; 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(); include_condition = l->quest_include_condition();
} }
} }
@@ -4015,8 +4015,7 @@ shared_ptr<Lobby> create_game_generic(
size_t min_level = s->default_min_level_for_game(c->version(), episode, difficulty); size_t min_level = s->default_min_level_for_game(c->version(), episode, difficulty);
auto p = c->character(); auto p = c->character();
if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES) && if (!c->license->check_flag(License::Flag::FREE_JOIN_GAMES) && (min_level > p->disp.stats.level)) {
(min_level > p->disp.stats.level)) {
// Note: We don't throw here because this is a situation players might // Note: We don't throw here because this is a situation players might
// actually encounter while playing the game normally // actually encounter while playing the game normally
send_lobby_message_box(c, "Your level is too\nlow for this\ndifficulty"); send_lobby_message_box(c, "Your level is too\nlow for this\ndifficulty");
+3 -3
View File
@@ -1201,7 +1201,7 @@ static void on_received_condition(shared_ptr<Client> c, uint8_t command, uint8_t
if (l->is_game()) { if (l->is_game()) {
forward_subcommand(c, command, flag, data, size); forward_subcommand(c, command, flag, data, size);
if (is_v1_or_v2(c->version()) && (cmd.client_id == c->lobby_client_id)) { 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)) { if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
send_remove_conditions(c); send_remove_conditions(c);
} }
@@ -1216,7 +1216,7 @@ static void on_hit_by_enemy(shared_ptr<Client> c, uint8_t command, uint8_t flag,
if (l->is_game() && (cmd.client_id == c->lobby_client_id)) { if (l->is_game() && (cmd.client_id == c->lobby_client_id)) {
forward_subcommand(c, command, flag, data, size); forward_subcommand(c, command, flag, data, size);
bool player_cheats_enabled = !is_v1(c->version()) && 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)) { if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_HP_ENABLED)) {
send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550); send_player_stats_change(c, PlayerStatsChange::ADD_HP, 2550);
} }
@@ -1230,7 +1230,7 @@ static void on_cast_technique_finished(shared_ptr<Client> c, uint8_t command, ui
if (l->is_game() && (cmd.header.client_id == c->lobby_client_id)) { if (l->is_game() && (cmd.header.client_id == c->lobby_client_id)) {
forward_subcommand(c, command, flag, data, size); forward_subcommand(c, command, flag, data, size);
bool player_cheats_enabled = !is_v1(c->version()) && 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)) { if (player_cheats_enabled && c->config.check_flag(Client::Flag::INFINITE_TP_ENABLED)) {
send_player_stats_change(c, PlayerStatsChange::ADD_TP, 255); send_player_stats_change(c, PlayerStatsChange::ADD_TP, 255);
} }
+1 -1
View File
@@ -1519,7 +1519,7 @@ void send_quest_categories_menu_t(
QuestMenuType menu_type, QuestMenuType menu_type,
Episode episode) { Episode episode) {
QuestIndex::IncludeCondition include_condition = nullptr; 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(); auto l = c->lobby.lock();
include_condition = l ? l->quest_include_condition() : nullptr; include_condition = l ? l->quest_include_condition() : nullptr;
} }
+23 -7
View File
@@ -154,7 +154,23 @@ Server commands:\n\
gc-password=<password> (GC password)\n\ gc-password=<password> (GC password)\n\
access-key=<access-key> (DC/GC/PC access key)\n\ access-key=<access-key> (DC/GC/PC access key)\n\
serial=<serial-number> (decimal serial number; required for all licenses)\n\ serial=<serial-number> (decimal serial number; required for all licenses)\n\
flags=<privilege-mask> (can be normal, mod, admin, root, or numeric)\n\ flags=<privilege-mask> (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-license SERIAL-NUMBER PARAMETERS...\n\
Update an existing license. <serial-number> specifies which license to\n\ Update an existing license. <serial-number> specifies which license to\n\
update. The options in <parameters> are the same as for the add-license\n\ update. The options in <parameters> are the same as for the add-license\n\
@@ -327,11 +343,11 @@ Proxy session commands:\n\
if (mask == "normal") { if (mask == "normal") {
l->flags = 0; l->flags = 0;
} else if (mask == "mod") { } else if (mask == "mod") {
l->flags = License::Flag::MODERATOR; l->replace_all_flags(License::Flag::MODERATOR);
} else if (mask == "admin") { } else if (mask == "admin") {
l->flags = License::Flag::ADMINISTRATOR; l->replace_all_flags(License::Flag::ADMINISTRATOR);
} else if (mask == "root") { } else if (mask == "root") {
l->flags = License::Flag::ROOT; l->replace_all_flags(License::Flag::ROOT);
} else { } else {
l->flags = stoul(mask, nullptr, 16); l->flags = stoul(mask, nullptr, 16);
} }
@@ -385,11 +401,11 @@ Proxy session commands:\n\
if (mask == "normal") { if (mask == "normal") {
l->flags = 0; l->flags = 0;
} else if (mask == "mod") { } else if (mask == "mod") {
l->flags = License::Flag::MODERATOR; l->replace_all_flags(License::Flag::MODERATOR);
} else if (mask == "admin") { } else if (mask == "admin") {
l->flags = License::Flag::ADMINISTRATOR; l->replace_all_flags(License::Flag::ADMINISTRATOR);
} else if (mask == "root") { } else if (mask == "root") {
l->flags = License::Flag::ROOT; l->replace_all_flags(License::Flag::ROOT);
} else { } else {
l->flags = stoul(mask, nullptr, 16); l->flags = stoul(mask, nullptr, 16);
} }