allow multiple licenses per account

This commit is contained in:
Martin Michelsen
2024-04-12 18:35:48 -07:00
parent 40d5c6ee64
commit 34751f99e9
35 changed files with 2351 additions and 1723 deletions
+989
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+9 -9
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+17 -16
View File
@@ -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);
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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);