allow multiple licenses per account
This commit is contained in:
+989
@@ -0,0 +1,989 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Random.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#include "Account.hh"
|
||||
|
||||
using namespace std;
|
||||
|
||||
shared_ptr<DCNTELicense> DCNTELicense::from_json(const JSON& json) {
|
||||
auto ret = make_shared<DCNTELicense>();
|
||||
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> V1V2License::from_json(const JSON& json) {
|
||||
auto ret = make_shared<V1V2License>();
|
||||
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> GCLicense::from_json(const JSON& json) {
|
||||
auto ret = make_shared<GCLicense>();
|
||||
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> XBLicense::from_json(const JSON& json) {
|
||||
auto ret = make_shared<XBLicense>();
|
||||
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> BBLicense::from_json(const JSON& json) {
|
||||
auto ret = make_shared<BBLicense>();
|
||||
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<GCLicense>();
|
||||
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<V1V2License>();
|
||||
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<DCNTELicense>();
|
||||
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<XBLicense>();
|
||||
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<BBLicense>();
|
||||
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<uint32_t>(Flag::ROOT)) {
|
||||
flags_str = "ROOT";
|
||||
} else if (this->flags == static_cast<uint32_t>(Flag::ADMINISTRATOR)) {
|
||||
flags_str = "ADMINISTRATOR";
|
||||
} else if (this->flags == static_cast<uint32_t>(Flag::MODERATOR)) {
|
||||
flags_str = "MODERATOR";
|
||||
} else {
|
||||
if (this->flags & static_cast<uint32_t>(Flag::KICK_USER)) {
|
||||
flags_str += "KICK_USER,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::BAN_USER)) {
|
||||
flags_str += "BAN_USER,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::SILENCE_USER)) {
|
||||
flags_str += "SILENCE_USER,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::CHANGE_EVENT)) {
|
||||
flags_str += "CHANGE_EVENT,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::ANNOUNCE)) {
|
||||
flags_str += "ANNOUNCE,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::FREE_JOIN_GAMES)) {
|
||||
flags_str += "FREE_JOIN_GAMES,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::DEBUG)) {
|
||||
flags_str += "DEBUG,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::CHEAT_ANYWHERE)) {
|
||||
flags_str += "CHEAT_ANYWHERE,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(Flag::DISABLE_QUEST_REQUIREMENTS)) {
|
||||
flags_str += "ALWAYS_ENABLE_CHAT_COMMANDS,";
|
||||
}
|
||||
if (this->flags & static_cast<uint32_t>(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<Account> 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<Login> AccountIndex::from_dc_nte_credentials_locked(const string& serial_number, const string& access_key) {
|
||||
auto login = make_shared<Login>();
|
||||
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<Login> 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>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = fnv1a32(serial_number) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<DCNTELicense>();
|
||||
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<Login> AccountIndex::from_dc_credentials_locked(
|
||||
uint32_t serial_number, const string& access_key, const string& character_name) {
|
||||
auto login = make_shared<Login>();
|
||||
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<Login> 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>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = serial_number;
|
||||
auto lic = make_shared<V1V2License>();
|
||||
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<Login> 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<uint32_t>() & 0x7FFFFFFF;
|
||||
}
|
||||
auto login = make_shared<Login>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = guild_card_number;
|
||||
login->account->is_temporary = true;
|
||||
auto lic = make_shared<V1V2License>();
|
||||
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<Login> AccountIndex::from_pc_credentials_locked(
|
||||
uint32_t serial_number, const string& access_key, const string& character_name) {
|
||||
auto login = make_shared<Login>();
|
||||
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<Login> 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>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = serial_number;
|
||||
auto lic = make_shared<V1V2License>();
|
||||
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<Login> 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>();
|
||||
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<Login> 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>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = serial_number;
|
||||
auto lic = make_shared<GCLicense>();
|
||||
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<Login> AccountIndex::from_xb_credentials_locked(const string& gamertag, uint64_t user_id, uint64_t account_id) {
|
||||
auto login = make_shared<Login>();
|
||||
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<Login> 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>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = fnv1a32(gamertag) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<XBLicense>();
|
||||
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<Login> AccountIndex::from_bb_credentials_locked(const string& username, const string* password) {
|
||||
auto login = make_shared<Login>();
|
||||
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<Login> 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>();
|
||||
login->account_was_created = true;
|
||||
login->account = make_shared<Account>();
|
||||
login->account->account_id = fnv1a32(username) & 0x7FFFFFFF;
|
||||
auto lic = make_shared<BBLicense>();
|
||||
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<shared_ptr<Account>> AccountIndex::all() const {
|
||||
shared_lock g(this->lock);
|
||||
vector<shared_ptr<Account>> 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<Account> a) {
|
||||
unique_lock g(this->lock);
|
||||
this->add_locked(a);
|
||||
}
|
||||
|
||||
void AccountIndex::add_locked(shared_ptr<Account> 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> account, shared_ptr<DCNTELicense> 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> account, shared_ptr<V1V2License> 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> account, shared_ptr<V1V2License> 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> account, shared_ptr<GCLicense> 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> account, shared_ptr<XBLicense> 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> account, shared_ptr<BBLicense> 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> 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> 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> 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> 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> 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> 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<Account> AccountIndex::create_temporary_account_for_shared_account(
|
||||
shared_ptr<const Account> src_a, const string& variation_data) const {
|
||||
auto ret = make_shared<Account>(*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<Account>(json));
|
||||
} catch (const exception& e) {
|
||||
log_error("Failed to index account %s", item.c_str());
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+260
@@ -0,0 +1,260 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <shared_mutex>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "Text.hh"
|
||||
|
||||
class LicenseIndex;
|
||||
|
||||
struct DCNTELicense {
|
||||
std::string serial_number;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<DCNTELicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct V1V2License {
|
||||
uint32_t serial_number = 0;
|
||||
std::string access_key;
|
||||
|
||||
static std::shared_ptr<V1V2License> 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<GCLicense> 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<XBLicense> from_json(const JSON& json);
|
||||
JSON json() const;
|
||||
};
|
||||
|
||||
struct BBLicense {
|
||||
std::string username;
|
||||
std::string password;
|
||||
|
||||
static std::shared_ptr<BBLicense> 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<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> dc_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_licenses;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<GCLicense>> gc_licenses;
|
||||
std::unordered_map<std::string, std::shared_ptr<XBLicense>> xb_licenses;
|
||||
std::unordered_map<std::string, std::shared_ptr<BBLicense>> 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<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);
|
||||
}
|
||||
|
||||
void print(FILE* stream) const;
|
||||
};
|
||||
|
||||
struct Login {
|
||||
bool account_was_created = false;
|
||||
// This field will never be null
|
||||
std::shared_ptr<Account> account;
|
||||
// Exactly one of the following will be non-null, representing the license
|
||||
// that the client logged in with
|
||||
std::shared_ptr<DCNTELicense> dc_nte_license;
|
||||
std::shared_ptr<V1V2License> dc_license;
|
||||
std::shared_ptr<V1V2License> pc_license;
|
||||
std::shared_ptr<GCLicense> gc_license;
|
||||
std::shared_ptr<XBLicense> xb_license;
|
||||
std::shared_ptr<BBLicense> 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<Account> create_account(bool is_temporary) const;
|
||||
|
||||
size_t count() const;
|
||||
std::vector<std::shared_ptr<Account>> all() const;
|
||||
|
||||
void add(std::shared_ptr<Account> a);
|
||||
void remove(uint32_t serial_number);
|
||||
|
||||
void add_dc_nte_license(std::shared_ptr<Account> account, std::shared_ptr<DCNTELicense> license);
|
||||
void add_dc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
|
||||
void add_pc_license(std::shared_ptr<Account> account, std::shared_ptr<V1V2License> license);
|
||||
void add_gc_license(std::shared_ptr<Account> account, std::shared_ptr<GCLicense> license);
|
||||
void add_xb_license(std::shared_ptr<Account> account, std::shared_ptr<XBLicense> license);
|
||||
void add_bb_license(std::shared_ptr<Account> account, std::shared_ptr<BBLicense> license);
|
||||
void remove_dc_nte_license(std::shared_ptr<Account> account, const std::string& serial_number);
|
||||
void remove_dc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_pc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_gc_license(std::shared_ptr<Account> account, uint32_t serial_number);
|
||||
void remove_xb_license(std::shared_ptr<Account> account, const std::string& gamertag);
|
||||
void remove_bb_license(std::shared_ptr<Account> account, const std::string& username);
|
||||
|
||||
std::shared_ptr<Account> from_account_id(uint32_t account_id) const;
|
||||
std::shared_ptr<Login> from_dc_nte_credentials(
|
||||
const std::string& serial_number,
|
||||
const std::string& access_key,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_dc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_pc_nte_credentials(
|
||||
uint32_t guild_card_number,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_pc_credentials(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> 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<Login> from_xb_credentials(
|
||||
const std::string& gamertag,
|
||||
uint64_t user_id,
|
||||
uint64_t account_id,
|
||||
bool allow_create);
|
||||
std::shared_ptr<Login> from_bb_credentials(
|
||||
const std::string& username,
|
||||
const std::string* password,
|
||||
bool allow_create);
|
||||
|
||||
std::shared_ptr<Account> create_temporary_account_for_shared_account(
|
||||
std::shared_ptr<const Account> 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<uint32_t, std::shared_ptr<Account>> by_account_id;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_dc_nte_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_dc_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_pc_serial_number;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Account>> by_gc_serial_number;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_xb_gamertag;
|
||||
std::unordered_map<std::string, std::shared_ptr<Account>> by_bb_username;
|
||||
|
||||
void add_locked(std::shared_ptr<Account> a);
|
||||
|
||||
std::shared_ptr<Login> from_dc_nte_credentials_locked(
|
||||
const std::string& serial_number,
|
||||
const std::string& access_key);
|
||||
std::shared_ptr<Login> from_dc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> from_pc_credentials_locked(
|
||||
uint32_t serial_number,
|
||||
const std::string& access_key,
|
||||
const std::string& character_name);
|
||||
std::shared_ptr<Login> 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<Login> from_xb_credentials_locked(
|
||||
const std::string& gamertag,
|
||||
uint64_t user_id,
|
||||
uint64_t account_id);
|
||||
std::shared_ptr<Login> from_bb_credentials_locked(
|
||||
const std::string& username,
|
||||
const std::string* password);
|
||||
};
|
||||
+49
-45
@@ -38,11 +38,11 @@ private:
|
||||
std::string user_msg;
|
||||
};
|
||||
|
||||
static void check_license_flag(shared_ptr<Client> c, License::Flag flag) {
|
||||
if (!c->license) {
|
||||
static void check_account_flag(shared_ptr<Client> 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<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->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<ServerState> s, shared_ptr<Client> 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<ServerState> s, shared_ptr<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> ses,
|
||||
}
|
||||
|
||||
static void server_command_ax(shared_ptr<Client> 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<Client> 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<Client> 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<ProxyServer::LinkedSession> ses, cons
|
||||
}
|
||||
|
||||
static void server_command_debug(shared_ptr<Client> 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<Client> c, const std:
|
||||
}
|
||||
|
||||
static void server_command_auction(shared_ptr<Client> 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<Client> 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<Client> c, const std::string&) {
|
||||
static void server_command_lobby_event(shared_ptr<Client> 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<ProxyServer::LinkedSession> ses
|
||||
}
|
||||
|
||||
static void server_command_lobby_event_all(shared_ptr<Client> 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<ProxyServer::LinkedSession> 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<Client> c, const std::string& args) {
|
||||
@@ -1006,7 +1008,7 @@ static void server_command_saverec(shared_ptr<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<string> tokens = split(encoded_args, ' ');
|
||||
@@ -1401,7 +1404,6 @@ static void server_command_bbchar_savechar(shared_ptr<Client> c, const std::stri
|
||||
check_is_game(l, false);
|
||||
|
||||
auto pending_export = make_unique<Client::PendingCharacterExport>();
|
||||
pending_export->is_bb_conversion = is_bb_conversion;
|
||||
|
||||
if (is_bb_conversion) {
|
||||
vector<string> tokens = split(args, ' ');
|
||||
@@ -1418,7 +1420,9 @@ static void server_command_bbchar_savechar(shared_ptr<Client> 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<Client> 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<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");
|
||||
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<Client> 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<Client> 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<Client> 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<Client> c) {
|
||||
static void server_command_silence(shared_ptr<Client> 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<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 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<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 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<Client> 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<Client> 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);
|
||||
|
||||
+46
-63
@@ -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<License> 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<Lobby> Client::require_lobby() const {
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> 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<const TeamIndex::Team> 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<int8_t>(this->bb_character_index + 1));
|
||||
}
|
||||
|
||||
@@ -714,7 +697,7 @@ void Client::load_all_files() {
|
||||
this->guild_card_data = make_shared<PSOBBGuildCardFile>();
|
||||
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<PSOCommandHeaderBB>(f.get());
|
||||
if (header.size != 0x399C) {
|
||||
|
||||
+7
-10
@@ -5,13 +5,13 @@
|
||||
#include <memory>
|
||||
#include <stdexcept>
|
||||
|
||||
#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> license;
|
||||
std::shared_ptr<Login> 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<const License> license;
|
||||
std::shared_ptr<const Account> dest_account;
|
||||
ssize_t character_index = -1;
|
||||
bool is_bb_conversion = false;
|
||||
std::shared_ptr<const BBLicense> dest_bb_license; // Only used for $bbchar; null for $savechar
|
||||
};
|
||||
std::unique_ptr<PendingCharacterExport> pending_character_export;
|
||||
std::deque<std::function<void(uint32_t, uint32_t)>> function_call_response_queue;
|
||||
@@ -283,8 +281,7 @@ public:
|
||||
return this->channel.language;
|
||||
}
|
||||
|
||||
void set_license(std::shared_ptr<License> 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();
|
||||
|
||||
+10
-10
@@ -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<TextEncoding::ASCII, 0x11> 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<TextEncoding::ASCII, 0x30> 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<TextEncoding::ASCII, 0x20> unused;
|
||||
pstring<TextEncoding::ASCII, 0x10> serial_number; // On XB, this is the XBL gamertag
|
||||
pstring<TextEncoding::ASCII, 0x10> access_key; // On XB, this is the XBL user ID
|
||||
@@ -2749,11 +2749,11 @@ struct C_VerifyLicense_V3_DB {
|
||||
pstring<TextEncoding::ASCII, 0x30> serial_number2; // On XB, this is the XBL gamertag
|
||||
pstring<TextEncoding::ASCII, 0x30> access_key2; // On XB, this is the XBL user ID
|
||||
pstring<TextEncoding::ASCII, 0x30> 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<TextEncoding::ASCII, 0x10> unknown_a3; // Always blank?
|
||||
pstring<TextEncoding::ASCII, 0x10> unknown_a4; // == "?"
|
||||
@@ -2763,7 +2763,7 @@ struct C_VerifyLicense_BB_DB {
|
||||
pstring<TextEncoding::ASCII, 0x30> username;
|
||||
pstring<TextEncoding::ASCII, 0x30> password;
|
||||
pstring<TextEncoding::ASCII, 0x30> 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
|
||||
|
||||
+31
-31
@@ -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<Client> 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<const COMDeckDefinition> 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::Match> Tournament::get_final_match() const {
|
||||
return this->final_match;
|
||||
}
|
||||
|
||||
shared_ptr<Tournament::Team> Tournament::team_for_serial_number(
|
||||
uint32_t serial_number) const {
|
||||
if (!this->all_player_serial_numbers.count(serial_number)) {
|
||||
shared_ptr<Tournament::Team> 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<uint32_t>& Tournament::get_all_player_serial_numbers() const {
|
||||
return this->all_player_serial_numbers;
|
||||
const set<uint32_t>& 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<Tournament::Team> TournamentIndex::team_for_serial_number(uint32_t serial_number) const {
|
||||
shared_ptr<Tournament::Team> 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<Client> 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) {
|
||||
|
||||
@@ -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<const COMDeckDefinition> 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> 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<Client> c);
|
||||
explicit PlayerEntry(std::shared_ptr<const COMDeckDefinition> com_deck);
|
||||
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
std::shared_ptr<Client> 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<Team> get_winner_team() const;
|
||||
std::shared_ptr<Match> next_match_for_team(std::shared_ptr<Team> team) const;
|
||||
std::shared_ptr<Match> get_final_match() const;
|
||||
std::shared_ptr<Team> team_for_serial_number(uint32_t serial_number) const;
|
||||
const std::set<uint32_t>& get_all_player_serial_numbers() const;
|
||||
std::shared_ptr<Team> team_for_account_id(uint32_t account_id) const;
|
||||
const std::set<uint32_t>& get_all_player_account_ids() const;
|
||||
|
||||
void start();
|
||||
|
||||
@@ -178,7 +178,7 @@ private:
|
||||
State current_state;
|
||||
uint32_t menu_item_id;
|
||||
|
||||
std::set<uint32_t> all_player_serial_numbers;
|
||||
std::set<uint32_t> all_player_account_ids;
|
||||
std::unordered_set<std::shared_ptr<Match>> 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<Tournament::Team> team_for_serial_number(uint32_t serial_number) const;
|
||||
std::shared_ptr<Tournament::Team> team_for_account_id(uint32_t account_id) const;
|
||||
|
||||
void link_client(std::shared_ptr<Client> c);
|
||||
void link_all_clients(std::shared_ptr<ServerState> s);
|
||||
|
||||
+45
-16
@@ -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<const License> 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<const Account> 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<const Client> c, shared_ptr<const ItemNameIndex> item_name_index) {
|
||||
@@ -294,7 +323,7 @@ JSON HTTPServer::generate_game_client_json_st(shared_ptr<const Client> 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_ptr<const ProxyServer::Lin
|
||||
ret.emplace("DropMode", "proxy");
|
||||
break;
|
||||
}
|
||||
ret.emplace("License", ses->license ? 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())},
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ protected:
|
||||
|
||||
static JSON generate_quest_json_st(std::shared_ptr<const Quest> q);
|
||||
static JSON generate_client_config_json_st(const Client::Config& config);
|
||||
static JSON generate_license_json_st(std::shared_ptr<const License> l);
|
||||
static JSON generate_account_json_st(std::shared_ptr<const Account> a);
|
||||
static JSON generate_game_client_json_st(std::shared_ptr<const Client> c, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
static JSON generate_proxy_client_json_st(std::shared_ptr<const ProxyServer::LinkedSession> ses);
|
||||
static JSON generate_lobby_json_st(std::shared_ptr<const Lobby> l, std::shared_ptr<const ItemNameIndex> item_name_index);
|
||||
|
||||
-392
@@ -1,392 +0,0 @@
|
||||
#include <inttypes.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <phosg/Filesystem.hh>
|
||||
#include <phosg/Hash.hh>
|
||||
#include <phosg/Time.hh>
|
||||
|
||||
#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<string> 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<License> LicenseIndex::create_license() const {
|
||||
return make_shared<License>();
|
||||
}
|
||||
|
||||
shared_ptr<License> LicenseIndex::create_temporary_license() const {
|
||||
return make_shared<License>();
|
||||
}
|
||||
|
||||
size_t LicenseIndex::count() const {
|
||||
return this->serial_number_to_license.size();
|
||||
}
|
||||
|
||||
shared_ptr<License> 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<License> 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<shared_ptr<License>> LicenseIndex::all() const {
|
||||
vector<shared_ptr<License>> 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<License> 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<License> 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<License> 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<License> 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<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) {
|
||||
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<License> 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<License> 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<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() {
|
||||
struct BinaryLicense {
|
||||
pstring<TextEncoding::ASCII, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
|
||||
pstring<TextEncoding::ASCII, 0x14> 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<TextEncoding::ASCII, 0x10> 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<TextEncoding::ASCII, 0x0C> 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<BinaryLicense>("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<DiskLicense>(json);
|
||||
this->add(license);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<License> DiskLicenseIndex::create_license() const {
|
||||
return make_shared<DiskLicense>();
|
||||
}
|
||||
-165
@@ -1,165 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <phosg/JSON.hh>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<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;
|
||||
};
|
||||
|
||||
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<License> create_license() const;
|
||||
virtual std::shared_ptr<License> create_temporary_license() const;
|
||||
|
||||
size_t count() const;
|
||||
std::shared_ptr<License> get(uint32_t serial_number) const;
|
||||
std::shared_ptr<License> get_by_bb_username(const std::string& bb_username) const;
|
||||
std::vector<std::shared_ptr<License>> all() const;
|
||||
|
||||
void add(std::shared_ptr<License> l);
|
||||
void remove(uint32_t serial_number);
|
||||
|
||||
std::shared_ptr<License> verify_dc_nte(const std::string& serial_number, const std::string& access_key) const;
|
||||
std::shared_ptr<License> verify_v1_v2(
|
||||
uint32_t serial_number,
|
||||
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_bb(const std::string& username, const std::string& password) const;
|
||||
|
||||
protected:
|
||||
std::unordered_map<std::string, std::shared_ptr<License>> dc_nte_serial_number_to_license;
|
||||
std::unordered_map<std::string, std::shared_ptr<License>> xb_gamertag_to_license;
|
||||
std::unordered_map<std::string, std::shared_ptr<License>> bb_username_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 {
|
||||
public:
|
||||
DiskLicenseIndex();
|
||||
virtual ~DiskLicenseIndex() = default;
|
||||
|
||||
virtual std::shared_ptr<License> create_license() const;
|
||||
};
|
||||
+11
-8
@@ -566,6 +566,10 @@ bool Lobby::any_v1_clients_present() const {
|
||||
}
|
||||
|
||||
void Lobby::add_client(shared_ptr<Client> 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<Client> 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<Client> Lobby::find_client(const string* identifier, uint64_t serial_number) {
|
||||
shared_ptr<Client> 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<Client> 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<Client> c, bool consum
|
||||
}
|
||||
}
|
||||
|
||||
unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_serial_number() const {
|
||||
unordered_map<uint32_t, shared_ptr<Client>> Lobby::clients_by_account_id() const {
|
||||
unordered_map<uint32_t, shared_ptr<Client>> 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;
|
||||
|
||||
+2
-4
@@ -275,9 +275,7 @@ struct Lobby : public std::enable_shared_from_this<Lobby> {
|
||||
std::shared_ptr<Client> c,
|
||||
ssize_t required_client_id = -1);
|
||||
|
||||
std::shared_ptr<Client> find_client(
|
||||
const std::string* identifier = nullptr,
|
||||
uint64_t serial_number = 0);
|
||||
std::shared_ptr<Client> 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<Lobby> {
|
||||
|
||||
QuestIndex::IncludeCondition quest_include_condition() const;
|
||||
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_serial_number() const;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Client>> clients_by_account_id() const;
|
||||
|
||||
static void dispatch_on_idle_timeout(evutil_socket_t, short, void* ctx);
|
||||
|
||||
|
||||
+7
-7
@@ -160,19 +160,19 @@ void PatchServer::on_04(shared_ptr<Client> 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<Client> 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;
|
||||
|
||||
+2
-2
@@ -8,8 +8,8 @@
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#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<const LicenseIndex> license_index;
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<const PatchFileIndex> patch_file_index;
|
||||
std::shared_ptr<struct event_base> shared_base;
|
||||
};
|
||||
|
||||
+79
-58
@@ -136,13 +136,13 @@ static HandlerResult S_97(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
}
|
||||
|
||||
static HandlerResult C_G_9E(shared_ptr<ProxyServer::LinkedSession> 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<uint64_t>() & 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<ProxyServer::LinkedSession> ses, uint16_t
|
||||
}
|
||||
|
||||
static HandlerResult S_G_9A(shared_ptr<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> 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<S_ServerInitDefault_DC_PC_V3_02_17_91_9B>(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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
bool modified = false;
|
||||
if (ses->license) {
|
||||
if (ses->login) {
|
||||
auto& cmd = check_size_t<SC_TextHeader_01_06_11_B0_EE>(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<ProxyServer::LinkedSession> ses, uint1
|
||||
|
||||
template <typename CmdT>
|
||||
static HandlerResult S_41(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
if (ses->license) {
|
||||
if (ses->login) {
|
||||
auto& cmd = check_size_t<CmdT>(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<ProxyServer::LinkedSession> 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<S_GuildCardSearchResult_BB_41>;
|
||||
template <typename CmdT>
|
||||
static HandlerResult S_81(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
bool modified = false;
|
||||
if (ses->license) {
|
||||
if (ses->login) {
|
||||
auto& cmd = check_size_t<CmdT>(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<SC_SimpleMail_BB_81>;
|
||||
|
||||
static HandlerResult S_88(shared_ptr<ProxyServer::LinkedSession> 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<S_ArrowUpdateEntry_88>(
|
||||
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<ProxyServer::LinkedSession> ses, uint16_t
|
||||
template <typename CmdT>
|
||||
static HandlerResult S_C4(shared_ptr<ProxyServer::LinkedSession> 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<CmdT>(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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> 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<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
|
||||
static HandlerResult C_40(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
bool modified = false;
|
||||
if (ses->license) {
|
||||
if (ses->login) {
|
||||
auto& cmd = check_size_t<C_GuildCardSearch_40>(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<ProxyServer::LinkedSession> ses, uint16_t,
|
||||
template <typename CmdT>
|
||||
static HandlerResult C_81(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
|
||||
auto& cmd = check_size_t<CmdT>(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<ProxyServer::LinkedSession> ses, const string& dat
|
||||
|
||||
template <typename SendGuildCardCmdT>
|
||||
static HandlerResult C_6x(shared_ptr<ProxyServer::LinkedSession> 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<SendGuildCardCmdT>(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<void>(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, u
|
||||
}
|
||||
|
||||
static HandlerResult C_V123_A0_A1(shared_ptr<ProxyServer::LinkedSession> 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;
|
||||
|
||||
+46
-61
@@ -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<ListeningSocket>(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<LinkedSession>(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<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(), 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<C_LoginV1_DC_93>(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<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(), 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<C_Login_GC_9E>(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<C_LoginBase_BB_93>(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<LinkedSession> 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<LinkedSession>(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<LinkedSession>(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<LinkedSession>(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<LinkedSession>(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<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
shared_ptr<License> license,
|
||||
shared_ptr<Login> 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<struct sockaddr_in*>(&this->next_destination);
|
||||
@@ -560,10 +545,10 @@ ProxyServer::LinkedSession::LinkedSession(
|
||||
shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<License> license,
|
||||
std::shared_ptr<Login> 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<uint64_t, shared_ptr<ProxyServer::LinkedSession>>& ProxyServ
|
||||
return this->id_to_session;
|
||||
}
|
||||
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
|
||||
shared_ptr<License> l,
|
||||
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_logged_in_session(
|
||||
shared_ptr<Login> login,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
const Client::Config& config) {
|
||||
auto session = make_shared<LinkedSession>(this->shared_from_this(), local_port, version, l, config);
|
||||
auto session = make_shared<LinkedSession>(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;
|
||||
}
|
||||
|
||||
|
||||
+7
-7
@@ -37,7 +37,7 @@ public:
|
||||
|
||||
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
|
||||
|
||||
std::shared_ptr<License> license;
|
||||
std::shared_ptr<Login> login;
|
||||
|
||||
Channel client_channel;
|
||||
Channel server_channel;
|
||||
@@ -136,13 +136,13 @@ public:
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<License> license,
|
||||
std::shared_ptr<Login> login,
|
||||
const Client::Config& config);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
uint16_t local_port,
|
||||
Version version,
|
||||
std::shared_ptr<License> license,
|
||||
std::shared_ptr<Login> login,
|
||||
const struct sockaddr_storage& next_destination);
|
||||
LinkedSession(
|
||||
std::shared_ptr<ProxyServer> server,
|
||||
@@ -199,8 +199,8 @@ public:
|
||||
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name) const;
|
||||
const std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>>& all_sessions() const;
|
||||
|
||||
std::shared_ptr<LinkedSession> create_licensed_session(
|
||||
std::shared_ptr<License> l,
|
||||
std::shared_ptr<LinkedSession> create_logged_in_session(
|
||||
std::shared_ptr<Login> 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> license;
|
||||
std::shared_ptr<Login> login;
|
||||
uint32_t sub_version = 0;
|
||||
std::string character_name;
|
||||
Client::Config config;
|
||||
@@ -281,7 +281,7 @@ private:
|
||||
std::unordered_map<struct bufferevent*, std::shared_ptr<UnlinkedSession>> bev_to_unlinked_session;
|
||||
std::unordered_set<std::shared_ptr<UnlinkedSession>> unlinked_sessions_to_destroy;
|
||||
std::unordered_map<uint64_t, std::shared_ptr<LinkedSession>> 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();
|
||||
|
||||
+256
-499
File diff suppressed because it is too large
Load Diff
+17
-16
@@ -991,13 +991,13 @@ static void on_sync_joining_player_disp_and_inventory(
|
||||
case Version::DC_NTE:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DCNTE_6x70>(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<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DC112000_6x70>(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<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_DC_PC_6x70>(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<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_GC_6x70>(data, size),
|
||||
c->license->serial_number);
|
||||
c->login->account->account_id);
|
||||
break;
|
||||
case Version::XB_V3:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_XB_6x70>(data, size),
|
||||
c->license->serial_number);
|
||||
c->login->account->account_id);
|
||||
break;
|
||||
case Version::BB_V4:
|
||||
parsed = make_unique<Parsed6x70Data>(
|
||||
check_size_t<G_SyncPlayerDispAndInventory_BB_6x70>(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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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);
|
||||
|
||||
@@ -189,7 +189,7 @@ void ReplaySession::check_for_password(shared_ptr<const Event> ev) const {
|
||||
check_ak(cmd.access_key2.decode());
|
||||
}
|
||||
} else if (header.command == 0xDB) {
|
||||
const auto& cmd = check_size_t<C_VerifyLicense_V3_DB>(cmd_data, cmd_size);
|
||||
const auto& cmd = check_size_t<C_VerifyAccount_V3_DB>(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<const Event> ev) const {
|
||||
} else if (header.command == 0x9E) {
|
||||
check_pw(check_size_t<C_LoginExtended_BB_9E>(cmd_data, cmd_size).password.decode());
|
||||
} else if (header.command == 0xDB) {
|
||||
check_pw(check_size_t<C_VerifyLicense_BB_DB>(cmd_data, cmd_size).password.decode());
|
||||
check_pw(check_size_t<C_VerifyAccount_V3_DB>(cmd_data, cmd_size).password.decode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
+41
-41
@@ -277,7 +277,7 @@ void send_update_client_config(shared_ptr<Client> 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<Client> 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<Client> 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<Client> 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<Client> 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<Lobby> l, const string& text) {
|
||||
|
||||
void send_text_message(shared_ptr<ServerState> 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<Client> 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<Client> 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<Client> c, uint32_t from_guild_card_number, con
|
||||
|
||||
void send_simple_mail(shared_ptr<ServerState> 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<CommandHeaderT, Encoding> 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<Client> c, shared_ptr<Client> 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<Client> c, shared_ptr<Lobby> l, shared_ptr
|
||||
template <typename LobbyDataT>
|
||||
void populate_lobby_data_for_client(LobbyDataT& ret, shared_ptr<const Client> c, shared_ptr<const Client> 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<const Client> c,
|
||||
template <>
|
||||
void populate_lobby_data_for_client(PlayerLobbyDataXB& ret, shared_ptr<const Client> c, shared_ptr<const Client> 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<const Cli
|
||||
template <>
|
||||
void populate_lobby_data_for_client<PlayerLobbyDataBB>(PlayerLobbyDataBB& ret, shared_ptr<const Client> c, shared_ptr<const Client> 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<Client> c, shared_ptr<Lobby> 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<Client> c, shared_ptr<Lobby> 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<Lobby> 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<Client> 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<Lobby> 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<Client> c, shared_ptr<Lobby> 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<RulesT>::TeamEntry;
|
||||
auto describe_team = [&](TeamEntryT& team_entry, shared_ptr<const Episode3::Tournament::Team> team) -> void {
|
||||
team_entry.team_name.encode(team->name, c->language());
|
||||
@@ -3265,7 +3265,7 @@ void send_ep3_game_details_t(shared_ptr<Client> c, shared_ptr<Lobby> 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<Client> 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<Lobby> 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<Lobby> 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<Client> 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<size_t>(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<size_t>(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<Client> 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<Client> 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;
|
||||
}
|
||||
|
||||
+11
-7
@@ -281,14 +281,14 @@ shared_ptr<Client> Server::get_client() const {
|
||||
}
|
||||
|
||||
vector<shared_ptr<Client>> 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<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident
|
||||
vector<shared_ptr<Client>> 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;
|
||||
}
|
||||
|
||||
+305
-167
@@ -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. <parameters> is some subset of the following:\n\
|
||||
bb-username=<username> (BB username)\n\
|
||||
bb-password=<password> (BB password)\n\
|
||||
xb-gamertag=<gamertag> (Xbox gamertag)\n\
|
||||
xb-user-id=<user-id> (Xbox user ID)\n\
|
||||
xb-account-id=<account-id> (Xbox account ID)\n\
|
||||
gc-password=<password> (GC password)\n\
|
||||
dc-nte-serial-number=<serial-number> (DC NTE serial number)\n\
|
||||
dc-nte-access-key=<access-key> (DC NTE access key)\n\
|
||||
access-key=<access-key> (DC/GC/PC access key)\n\
|
||||
serial=<serial-number> (decimal serial number; required for all licenses)\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\
|
||||
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. <serial-number> specifies which license to\n\
|
||||
update. The options in <parameters> 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<uint32_t>(Account::Flag::KICK_USER);
|
||||
} else if (token_upper == "BAN_USER") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::BAN_USER);
|
||||
} else if (token_upper == "SILENCE_USER") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::SILENCE_USER);
|
||||
} else if (token_upper == "MODERATOR") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::MODERATOR);
|
||||
} else if (token_upper == "CHANGE_EVENT") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::CHANGE_EVENT);
|
||||
} else if (token_upper == "ANNOUNCE") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::ANNOUNCE);
|
||||
} else if (token_upper == "FREE_JOIN_GAMES") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::FREE_JOIN_GAMES);
|
||||
} else if (token_upper == "ADMINISTRATOR") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::ADMINISTRATOR);
|
||||
} else if (token_upper == "DEBUG") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::DEBUG);
|
||||
} else if (token_upper == "CHEAT_ANYWHERE") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::CHEAT_ANYWHERE);
|
||||
} else if (token_upper == "DISABLE_QUEST_REQUIREMENTS") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::DISABLE_QUEST_REQUIREMENTS);
|
||||
} else if (token_upper == "ALWAYS_ENABLE_CHAT_COMMANDS") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::ALWAYS_ENABLE_CHAT_COMMANDS);
|
||||
} else if (token_upper == "ROOT") {
|
||||
ret |= static_cast<uint32_t>(Account::Flag::ROOT);
|
||||
} else if (token_upper == "IS_SHARED_ACCOUNT") {
|
||||
ret |= static_cast<uint32_t>(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. <parameters> 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<Account>();
|
||||
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<DCNTELicense>();
|
||||
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<V1V2License>();
|
||||
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<V1V2License>();
|
||||
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<GCLicense>();
|
||||
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<XBLicense>();
|
||||
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<BBLicense>();
|
||||
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(
|
||||
|
||||
+12
-12
@@ -214,21 +214,21 @@ void ServerState::on_player_left_lobby(shared_ptr<Lobby> l, uint8_t leaving_clie
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<Client> ServerState::find_client(const string* identifier, uint64_t serial_number, shared_ptr<Lobby> l) {
|
||||
shared_ptr<Client> ServerState::find_client(const string* identifier, uint64_t account_id, shared_ptr<Lobby> 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<Client> 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<LicenseIndex> new_index = this->is_replay ? make_shared<LicenseIndex>() : make_shared<DiskLicenseIndex>();
|
||||
void ServerState::load_accounts(bool from_non_event_thread) {
|
||||
config_log.info("Indexing accounts");
|
||||
shared_ptr<AccountIndex> new_index = make_shared<AccountIndex>(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<PatchServer::Config> 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;
|
||||
}
|
||||
|
||||
+5
-5
@@ -11,6 +11,7 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#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<ServerState> {
|
||||
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<ServerState> {
|
||||
};
|
||||
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
|
||||
|
||||
std::shared_ptr<LicenseIndex> license_index;
|
||||
std::shared_ptr<AccountIndex> account_index;
|
||||
std::shared_ptr<TeamIndex> team_index;
|
||||
JSON team_reward_defs_json;
|
||||
|
||||
@@ -285,7 +285,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
|
||||
std::shared_ptr<Client> find_client(
|
||||
const std::string* identifier = nullptr,
|
||||
uint64_t serial_number = 0,
|
||||
uint64_t account_id = 0,
|
||||
std::shared_ptr<Lobby> l = nullptr);
|
||||
|
||||
uint32_t connect_address_for_client(std::shared_ptr<Client> c) const;
|
||||
@@ -353,7 +353,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
|
||||
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();
|
||||
|
||||
+54
-49
@@ -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<const TeamIndex::Team> TeamIndex::get_by_name(const string& name) con
|
||||
}
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::get_by_serial_number(uint32_t serial_number) const {
|
||||
shared_ptr<const TeamIndex::Team> 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<shared_ptr<const TeamIndex::Team>> TeamIndex::all() const {
|
||||
return ret;
|
||||
}
|
||||
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_serial_number, const string& master_name) {
|
||||
shared_ptr<const TeamIndex::Team> TeamIndex::create(const string& name, uint32_t master_account_id, const string& master_name) {
|
||||
auto team = make_shared<Team>(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 parray<le_uint16_t, 0x20 *
|
||||
team->save_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> 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> 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);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-13
@@ -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<uint32_t, Member> members;
|
||||
uint32_t reward_flags = 0;
|
||||
std::unordered_set<std::string> 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<uint8_t>(flag) & this->reward_flags);
|
||||
@@ -125,21 +125,21 @@ public:
|
||||
size_t count() const;
|
||||
std::shared_ptr<const Team> get_by_id(uint32_t team_id) const;
|
||||
std::shared_ptr<const Team> get_by_name(const std::string& name) const;
|
||||
std::shared_ptr<const Team> get_by_serial_number(uint32_t serial_number) const;
|
||||
std::shared_ptr<const Team> get_by_account_id(uint32_t account_id) const;
|
||||
std::vector<std::shared_ptr<const Team>> all() const;
|
||||
|
||||
std::shared_ptr<const Team> create(const std::string& name, uint32_t master_serial_number, const std::string& master_name);
|
||||
std::shared_ptr<const Team> 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<le_uint16_t, 0x20 * 0x20>& 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<uint32_t, std::shared_ptr<Team>> id_to_team;
|
||||
std::unordered_map<std::string, std::shared_ptr<Team>> name_to_team;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> serial_number_to_team;
|
||||
std::unordered_map<uint32_t, std::shared_ptr<Team>> account_id_to_team;
|
||||
std::vector<Reward> reward_defs;
|
||||
|
||||
void add_to_indexes(std::shared_ptr<Team> team);
|
||||
|
||||
Reference in New Issue
Block a user