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
+1 -1
View File
@@ -58,6 +58,7 @@ add_custom_target(
set(SOURCES
${CMAKE_CURRENT_SOURCE_DIR}/src/Revision.cc
src/Account.cc
src/AFSArchive.cc
src/BattleParamsIndex.cc
src/BMLArchive.cc
@@ -97,7 +98,6 @@ set(SOURCES
src/ItemParameterTable.cc
src/Items.cc
src/LevelTable.cc
src/License.cc
src/Lobby.cc
src/Loggers.cc
src/Main.cc
+15 -4
View File
@@ -18,7 +18,7 @@ See TODO.md for a list of known issues and future work I've curated, or go to th
* [Client patch directories for PC and BB](#client-patch-directories)
* [How to connect](#how-to-connect)
* Features and configuration
* [User licenses](#user-licenses)
* [User accounts](#user-accounts)
* [Installing quests](#installing-quests)
* [Item tables and drop modes](#item-tables-and-drop-modes)
* [Cross-version play](#cross-version-play)
@@ -226,11 +226,22 @@ For GC clients, you'll have to use newserv's built-in DNS server or set up your
# Server feature configuration
## User licenses
## User accounts
By default, newserv does not require users to pre-register before playing; the server will instead automatically create an account the first time each player connects. These accounts have no special permissions. You can view, create, edit, and delete user accounts in the server's shell (run `help` in the shell to see how to do this).
If you're running the server not only for yourself, you may want to give your account elevated privileges. To do so, run `update-license SERIAL-NUMBER flags=root` (replacing SERIAL-NUMBER with your actual serial number). You can also use update-license to edit other parts of the license; for example, if you want your GC and BB characters to share an account so they will have the same Guild Card number and privilege flags, you can run `update-license SERIAL-NUMBER bb-username=USERNAME bb-password=PASSWORD`.
A license is a set of credentials that a player can use to log in. There are six types of licenses:
* *DC NTE licenses* consist of a 16-character serial number and 16-character access key.
* *DC licenses* consist of an 8-character hex serial number and an 8-character access key.
* *PC licenses* are the same format as DC licenses, but are used for PC v2.
* *GC licenses* consist of a 10-digit decimal serial number, a 12-character access key, and a password of up to 8 characters.
* *XB licenses* consist of a gamertag of up to 16 characters, a 16-character hex user ID, and a 16-character hex account ID.
* *BB licenses* consist of a username of up to 16 characters and a password of up to 16 characters.
Each account may have multiple licenses. To add a license to an account, use `add-license` in the shell.
On BB, character data is scoped to the license, but system and Guild Card data is scoped to the account. That is, an account with multiple BB licenses can have more than 4 characters (up to 4 per license), but they will all share the same team membership and Guild Card lists.
You may want to give your account elevated privileges. To do so, run `update-account ACCOUNT-ID flags=root` (replacing ACCOUNT-ID with your actual account-id). You can also use update-account to edit other parts of the account; see the help text for more information.
## Installing quests
@@ -433,7 +444,7 @@ Some commands only work on the game server and not on the proxy server. The chat
* `$where` (game server only): Shows your current floor number and coordinates. Mainly useful for debugging.
* Debugging commands
* `$debug` (game server only): Enable or disable debug. You need the DEBUG permission in your user license to use this command. Enabling debug does a few things:
* `$debug` (game server only): Enable or disable debug. You need the DEBUG flag in your user account to use this command. Enabling debug does a few things:
* You'll see in-game messages from the server when you take certain actions, like killing an enemy in BB.
* You'll see the rare seed value and floor variations when you join a game.
* You'll be placed into the highest available slot in lobbies and games instead of the lowest, unless you're joining a BB solo-mode game.
+1 -1
View File
@@ -14,7 +14,7 @@
## Episode 3
- Enforce tournament deck restrictions (e.g. rank checks, No Assist option) when populating COMs at tournament start time
- Make `reload licenses` not vulnerable to online players' licenses overwriting licenses on disk somehow
- Make `reload accounts` not vulnerable to online players' accounts overwriting accounts on disk somehow
- Implement ranks (based on total Meseta earned)
- Make an AR code that gets rid of the SAMPLE overlays on NTE
+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);
+13 -13
View File
@@ -274,20 +274,20 @@
// a full session log before submitting your report.
"HideDownloadCommands": true,
// If this option is disabled, the server only allows users who have licenses
// If this option is disabled, the server only allows users who have accounts
// on the server to connect. If this is enabled, all users will be allowed to
// connect even if they don't have licenses. When a user connects with an
// connect even if they don't have accounts. When a user connects with an
// unregistered license (serial number and access key combination, or username
// and password combination on BB), a new license is created for them.
// Licenses are addressed by serial numbers; on BB, the new license's serial
// and password combination on BB), a new account is created for them.
// Accounts are addressed by serial numbers; on BB, the new account's serial
// number is a hash of the username.
"AllowUnregisteredUsers": true,
// If this option is enabled and AllowUnregisteredUsers is enabled, the server
// will use temporary licenses for the prototype versions (DC NTE, DC 11/2000,
// GC NTE, and Ep3 NTE) instead of permanent licenses. In this case, you can
// still manually create permanent licenses for NTE players.
"UseTemporaryLicensesForPrototypes": true,
// will use temporary accounts for the prototype versions (DC NTE, DC 11/2000,
// GC NTE, and Ep3 NTE) instead of permanent accounts. In this case, you can
// still manually create permanent accounts for NTE players.
"UseTemporaryAccountsForPrototypes": true,
// If this option is enabled, PC NTE players will be allowed to connect. This
// is the only version of the game that does not have any way to identify the
@@ -298,7 +298,7 @@
// Whether to enable chat commands for all players. If this is true, all
// players will be able to use chat commands as normal; if this is false, only
// players with the ALWAYS_ENABLE_CHAT_COMMANDS license flag will be able to
// players with the ALWAYS_ENABLE_CHAT_COMMANDS account flag will be able to
// use chat commands.
"EnableChatCommands": true,
@@ -466,10 +466,10 @@
// If this is enabled, all players will have infinite Meseta, effectively
// making the jukebox and Pinz's Shop free. Otherwise, Meseta behaves as
// defined below. Meseta rewards are tied to a player's license (and therefore
// their serial number) and are stored server-side. Unlike other servers,
// newserv forbids overdrafting Meseta; if this option is disabled and a
// player spends Meseta they don't have, the player is disconnected.
// defined below. Meseta rewards are tied to a player's account and are
// stored server-side. Unlike other servers, newserv forbids overdrafting
// Meseta; if this option is disabled and a player spends Meseta they don't
// have, the player is disconnected.
"Episode3InfiniteMeseta": false,
// Meseta values for winning each tournament round. If a player defeats
// another player in round 1, for example, they will earn 400 Meseta; if they
+2 -2
View File
@@ -37,7 +37,7 @@
// Quests may be set to be unavailable until a preceding quest has been
// cleared or a team reward has been purchased. To enable this feature, set a
// value for AvailableIf in the quest's JSON file. (This is ignored if the
// player has the DISABLE_QUEST_REQUIREMENTS flag in their license.) This
// player has the DISABLE_QUEST_REQUIREMENTS flag in their account.) This
// field's value should be an expression that tests any of the following:
// F_XXXX: Quest flag specified in hex (e.g. F_014D)
// CC_EpX_Y: Whether or not Challenge stage X in Episode Y is complete
@@ -52,6 +52,6 @@
// On BB, quests may be disabled but still visible to the player. This
// expression controls when that should be the case. If AvailableIf evaluates
// to false, this is ignored. This field is also ignored if the player has
// the DISABLE_QUEST_REQUIREMENTS flag in their license.
// the DISABLE_QUEST_REQUIREMENTS flag in their account.
// "EnabledIf": "!F_0169",
}
+2 -2
View File
@@ -6,7 +6,7 @@
// replay runner uses virtual connections instead.
// 2. The IP stack simulator is disabled.
// 3. Unregistered users are allowed. This enables the tests to run on other
// machines, which won't have the same license file.
// machines, which won't have the same account files.
"ServerName": "Alexandria",
"CatchHandlerExceptions": false,
@@ -146,7 +146,7 @@
"HideDownloadCommands": true,
"AllowUnregisteredUsers": true,
"UseTemporaryLicensesForPrototypes": true,
"UseTemporaryAccountsForPrototypes": true,
"AllowPCNTE": true,
"AllowDCPCGames": true,
"AllowGCXBGames": true,