implement Episode 3 meseta

This commit is contained in:
Martin Michelsen
2023-09-24 16:25:38 -07:00
parent 8b544830a0
commit 058b040975
23 changed files with 561 additions and 459 deletions
+1 -1
View File
@@ -168,7 +168,7 @@ Tournaments work differently than they did on Sega's servers. Tournaments can be
These tournament semantics mean that there can be multiple matches in the same tournament in play simultaneously, and not all matches in a round must be complete before the next round can begin - only the matches preceding each individual match must be complete for that match to be playable.
Because newserv gives all players 1000000 meseta by default, there is no reward for winning a tournament. This may change in the future.
The Meseta rewards for winning tournament matches can be configured in config.json.
Episode 3 state and game data is stored in the system/ep3 directory. The files in there are:
* card-definitions.mnr: Compressed card definition list, sent to Episode 3 clients at connect time. Card stats and abilities can be changed by editing this file.
+18 -17
View File
@@ -37,11 +37,11 @@ private:
std::u16string user_msg;
};
static void check_privileges(shared_ptr<Client> c, uint64_t mask) {
static void check_license_flags(shared_ptr<Client> c, uint32_t mask) {
if (!c->license) {
throw precondition_failed(u"$C6You are not\nlogged in.");
}
if ((c->license->privileges & mask) != mask) {
if ((c->license->flags & mask) != mask) {
throw precondition_failed(u"$C6You do not have\npermission to\nrun this command.");
}
}
@@ -206,14 +206,14 @@ static void proxy_command_lobby_info(shared_ptr<ProxyServer::LinkedSession> ses,
}
static void server_command_ax(shared_ptr<Client> c, const std::u16string& args) {
check_privileges(c, Privilege::ANNOUNCE);
check_license_flags(c, License::Flag::ANNOUNCE);
string message = encode_sjis(args);
ax_messages_log.info("%s", message.c_str());
}
static void server_command_announce(shared_ptr<Client> c, const std::u16string& args) {
auto s = c->require_server_state();
check_privileges(c, Privilege::ANNOUNCE);
check_license_flags(c, License::Flag::ANNOUNCE);
send_text_message(s, args);
}
@@ -230,14 +230,14 @@ static void proxy_command_arrow(shared_ptr<ProxyServer::LinkedSession> ses, cons
}
static void server_command_debug(shared_ptr<Client> c, const std::u16string&) {
check_privileges(c, Privilege::DEBUG);
check_license_flags(c, License::Flag::DEBUG);
c->options.debug = !c->options.debug;
send_text_message_printf(c, "Debug %s",
c->options.debug ? "enabled" : "disabled");
}
static void server_command_auction(shared_ptr<Client> c, const std::u16string&) {
check_privileges(c, Privilege::DEBUG);
check_license_flags(c, License::Flag::DEBUG);
auto l = c->require_lobby();
if (l->is_game() && l->is_ep3()) {
G_InitiateCardAuction_GC_Ep3_6xB5x42 cmd;
@@ -330,7 +330,7 @@ static void proxy_command_patch(shared_ptr<ProxyServer::LinkedSession> ses, cons
}
static void server_command_persist(shared_ptr<Client> c, const std::u16string&) {
check_privileges(c, Privilege::DEBUG);
check_license_flags(c, License::Flag::DEBUG);
auto l = c->require_lobby();
if (l->flags & Lobby::Flag::DEFAULT) {
send_text_message(c, u"$C6Default lobbies\ncannot be marked\ntemporary");
@@ -464,7 +464,7 @@ static void server_command_cheat(shared_ptr<Client> c, const std::u16string&) {
static void server_command_lobby_event(shared_ptr<Client> c, const std::u16string& args) {
auto l = c->require_lobby();
check_is_game(l, false);
check_privileges(c, Privilege::CHANGE_EVENT);
check_license_flags(c, License::Flag::CHANGE_EVENT);
uint8_t new_event = event_for_name(args);
if (new_event == 0xFF) {
@@ -496,7 +496,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::u16string& args) {
check_privileges(c, Privilege::CHANGE_EVENT);
check_license_flags(c, License::Flag::CHANGE_EVENT);
uint8_t new_event = event_for_name(args);
if (new_event == 0xFF) {
@@ -844,7 +844,7 @@ static void server_command_convert_char_to_bb(shared_ptr<Client> c, const std::u
}
try {
s->license_manager->verify_bb(tokens[0].c_str(), tokens[1].c_str());
s->license_index->verify_bb(tokens[0].c_str(), tokens[1].c_str());
} catch (const exception& e) {
send_text_message_printf(c, "$C6Login failed: %s", e.what());
return;
@@ -876,7 +876,7 @@ static string name_for_client(shared_ptr<Client> c) {
static void server_command_silence(shared_ptr<Client> c, const std::u16string& args) {
auto s = c->require_server_state();
auto l = c->require_lobby();
check_privileges(c, Privilege::SILENCE_USER);
check_license_flags(c, License::Flag::SILENCE_USER);
auto target = s->find_client(&args);
if (!target->license) {
@@ -885,7 +885,7 @@ static void server_command_silence(shared_ptr<Client> c, const std::u16string& a
return;
}
if (target->license->privileges & Privilege::MODERATOR) {
if (target->license->flags & License::Flag::MODERATOR) {
send_text_message(c, u"$C6You do not have\nsufficient privileges.");
return;
}
@@ -899,7 +899,7 @@ static void server_command_silence(shared_ptr<Client> c, const std::u16string& a
static void server_command_kick(shared_ptr<Client> c, const std::u16string& args) {
auto s = c->require_server_state();
auto l = c->require_lobby();
check_privileges(c, Privilege::KICK_USER);
check_license_flags(c, License::Flag::KICK_USER);
auto target = s->find_client(&args);
if (!target->license) {
@@ -908,7 +908,7 @@ static void server_command_kick(shared_ptr<Client> c, const std::u16string& args
return;
}
if (target->license->privileges & Privilege::MODERATOR) {
if (target->license->flags & License::Flag::MODERATOR) {
send_text_message(c, u"$C6You do not have\nsufficient privileges.");
return;
}
@@ -922,7 +922,7 @@ static void server_command_kick(shared_ptr<Client> c, const std::u16string& args
static void server_command_ban(shared_ptr<Client> c, const std::u16string& args) {
auto s = c->require_server_state();
auto l = c->require_lobby();
check_privileges(c, Privilege::BAN_USER);
check_license_flags(c, License::Flag::BAN_USER);
u16string args_str(args);
size_t space_pos = args_str.find(L' ');
@@ -939,7 +939,7 @@ static void server_command_ban(shared_ptr<Client> c, const std::u16string& args)
return;
}
if (target->license->privileges & Privilege::BAN_USER) {
if (target->license->flags & License::Flag::BAN_USER) {
send_text_message(c, u"$C6You do not have\nsufficient privileges.");
return;
}
@@ -963,7 +963,8 @@ static void server_command_ban(shared_ptr<Client> c, const std::u16string& args)
usecs *= 60 * 60 * 24 * 365;
}
s->license_manager->ban_until(target->license->serial_number, now() + usecs);
target->license->ban_end_time = now() + usecs;
target->license->save();
send_message_box(target, u"$C6You were banned by a moderator.");
target->should_disconnect = true;
string target_name = name_for_client(target);
+2 -2
View File
@@ -147,11 +147,11 @@ QuestScriptVersion Client::quest_version() const {
}
}
void Client::set_license(shared_ptr<const License> l) {
void Client::set_license(shared_ptr<License> l) {
this->license = l;
this->game_data.guild_card_number = this->license->serial_number;
if (this->version() == GameVersion::BB) {
this->game_data.bb_username = this->license->username;
this->game_data.bb_username = this->license->bb_username;
}
}
+2 -2
View File
@@ -122,7 +122,7 @@ struct Client : public std::enable_shared_from_this<Client> {
PrefixedLogger log;
// License & account
std::shared_ptr<const License> license;
std::shared_ptr<License> license;
// Note: these fields are included in the client config. On GC, the client
// config can be up to 0x20 bytes; on BB it can be 0x28 bytes. We don't use
@@ -191,7 +191,7 @@ struct Client : public std::enable_shared_from_this<Client> {
}
QuestScriptVersion quest_version() const;
void set_license(std::shared_ptr<const License> l);
void set_license(std::shared_ptr<License> l);
std::shared_ptr<ServerState> require_server_state() const;
std::shared_ptr<Lobby> require_lobby() const;
+6 -6
View File
@@ -2209,8 +2209,8 @@ struct S_RankUpdate_GC_Ep3_B7 {
// without modifying it.
le_uint32_t rank = 0;
ptext<char, 0x0C> rank_text; // Encrypted (with encrypt_challenge_rank_text)
le_uint32_t meseta = 0;
le_uint32_t max_meseta = 0;
le_uint32_t current_meseta = 0;
le_uint32_t total_meseta_earned = 0;
le_uint32_t unlocked_jukebox_songs = 0xFFFFFFFF;
} __packed__;
@@ -2315,15 +2315,15 @@ struct S_UpdateMediaHeader_GC_Ep3_B9 {
// sent by client)
// 04 = unknown (C->S; request_token must match the last token sent by client)
struct C_Meseta_GC_Ep3_BA {
struct C_MesetaTransaction_GC_Ep3_BA {
le_uint32_t transaction_num = 0;
le_uint32_t value = 0;
le_uint32_t request_token = 0;
} __packed__;
struct S_Meseta_GC_Ep3_BA {
le_uint32_t remaining_meseta = 0;
le_uint32_t total_meseta_awarded = 0;
struct S_MesetaTransaction_GC_Ep3_BA {
le_uint32_t current_meseta = 0;
le_uint32_t total_meseta_earned = 0;
le_uint32_t request_token = 0; // Should match the token sent by the client
} __packed__;
+153 -157
View File
@@ -10,103 +10,172 @@
using namespace std;
License::License()
License::License(const JSON& json)
: serial_number(0),
privileges(0),
ban_end_time(0) {}
flags(0),
ban_end_time(0),
ep3_current_meseta(0),
ep3_total_meseta_earned(0) {
this->serial_number = json.get_int("SerialNumber");
this->access_key = json.get_string("AccessKey", "");
this->gc_password = json.get_string("GCPassword", "");
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->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0);
this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0);
}
JSON License::json() const {
return JSON::dict({
{"SerialNumber", this->serial_number},
{"AccessKey", this->access_key},
{"GCPassword", this->gc_password},
{"BBUsername", this->bb_username},
{"BBPassword", this->bb_password},
{"Flags", this->flags},
{"BanEndTime", this->ban_end_time},
{"Ep3CurrentMeseta", this->ep3_current_meseta},
{"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned},
});
}
void License::save() const {
if (!(this->flags & License::Flag::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->serial_number);
save_file(filename, json_data);
}
}
void License::delete_file() const {
string filename = string_printf("system/licenses/%010" PRIu32 ".json", this->serial_number);
remove(filename.c_str());
}
string License::str() const {
string ret = string_printf("License(serial_number=%" PRIu32, this->serial_number);
if (!this->username.empty()) {
ret += ", username=";
ret += this->username;
}
if (!this->bb_password.empty()) {
ret += ", bb-password=";
ret += this->bb_password;
}
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()) {
ret += ", access-key=";
ret += this->access_key;
tokens.emplace_back("access_key=" + this->access_key);
}
if (!this->gc_password.empty()) {
ret += ", gc-password=";
ret += this->gc_password;
tokens.emplace_back("gc_password=" + this->gc_password);
}
ret += string_printf(", privileges=%" PRIu32, this->privileges);
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) {
ret += string_printf(", banned-until=%" PRIu64, this->ban_end_time);
tokens.emplace_back(string_printf("ban_end_time=%016" PRIX64, this->ban_end_time));
}
return ret + ")";
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, ", ") + "]";
}
LicenseManager::LicenseManager()
: filename(""),
autosave(false) {}
struct BinaryLicense {
ptext<char, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
ptext<char, 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.
ptext<char, 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)
ptext<char, 0x0C> gc_password; // GC password
uint32_t privileges; // privilege level
uint64_t ban_end_time; // end time of ban (zero = not banned)
} __attribute__((packed));
LicenseManager::LicenseManager(const string& filename)
: filename(filename),
autosave(true) {
try {
auto licenses = load_vector_file<License>(this->filename);
for (const auto& read_license : licenses) {
shared_ptr<License> license(new License(read_license));
LicenseIndex::LicenseIndex() : autosave(true) {
if (!isdir("system/licenses")) {
mkdir("system/licenses", 0755);
}
// Before the temporary flag existed, licenses with root privileges would
// have the temporary flag set. To migrate these, explicitly unset the
// flag for all licenses loaded from the license file.
license->privileges &= ~Privilege::TEMPORARY;
uint32_t serial_number = license->serial_number;
this->bb_username_to_license.emplace(license->username, license);
this->serial_number_to_license.emplace(serial_number, license);
// 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;
license.gc_password = bin_license.gc_password;
license.bb_username = bin_license.username;
license.bb_password = bin_license.bb_password;
license.flags = bin_license.privileges & (~License::Flag::TEMPORARY);
license.ban_end_time = bin_license.ban_end_time;
license.ep3_current_meseta = 0;
license.ep3_total_meseta_earned = 0;
license.save();
}
}
} catch (const cannot_open_file&) {
license_log.warning("File %s does not exist; no licenses are registered",
this->filename.c_str());
::remove("system/licenses.nsi");
}
}
void LicenseManager::save() const {
if (this->filename.empty()) {
throw logic_error("license manager has no filename; cannot save");
}
auto f = fopen_unique(this->filename, "wb");
for (const auto& it : this->serial_number_to_license) {
if (it.second->privileges & Privilege::TEMPORARY) {
continue;
for (const auto& item : list_directory("system/licenses")) {
if (ends_with(item, ".json")) {
JSON json = JSON::parse(load_file("system/licenses/" + item));
shared_ptr<License> license(new License(json));
this->add(license);
}
fwritex(f.get(), it.second.get(), sizeof(License));
}
}
void LicenseManager::set_autosave(bool autosave) {
void LicenseIndex::set_autosave(bool autosave) {
this->autosave = autosave;
}
shared_ptr<const License> LicenseManager::verify_pc(uint32_t serial_number,
const string& access_key) const {
try {
auto& license = this->serial_number_to_license.at(serial_number);
if (!license->access_key.eq_n(access_key, 8)) {
throw incorrect_access_key();
}
size_t LicenseIndex::count() const {
return this->serial_number_to_license.size();
}
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license;
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<const License> LicenseManager::verify_gc(uint32_t serial_number,
const string& access_key) const {
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->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->bb_username.empty()) {
this->bb_username_to_license.erase(l->bb_username);
}
}
shared_ptr<License> LicenseIndex::verify_v1_v2(uint32_t serial_number, const string& access_key) const {
try {
auto& license = this->serial_number_to_license.at(serial_number);
if (!license->access_key.eq_n(access_key, 12)) {
if (license->access_key.compare(0, 8, access_key) != 0) {
throw incorrect_access_key();
}
if (license->ban_end_time && (license->ban_end_time >= now())) {
@@ -118,11 +187,25 @@ shared_ptr<const License> LicenseManager::verify_gc(uint32_t serial_number,
}
}
shared_ptr<const License> LicenseManager::verify_gc(uint32_t serial_number,
const string& access_key, const string& password) const {
shared_ptr<License> LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key) const {
try {
auto& license = this->serial_number_to_license.at(serial_number);
if (!license->access_key.eq_n(access_key, 12)) {
if (license->access_key != access_key) {
throw incorrect_access_key();
}
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
return license;
} catch (const out_of_range&) {
throw missing_license();
}
}
shared_ptr<License> LicenseIndex::verify_gc(uint32_t serial_number, const string& access_key, const string& password) const {
try {
auto& license = this->serial_number_to_license.at(serial_number);
if (license->access_key != access_key) {
throw incorrect_access_key();
}
if (license->gc_password != password) {
@@ -137,14 +220,12 @@ shared_ptr<const License> LicenseManager::verify_gc(uint32_t serial_number,
}
}
shared_ptr<const License> LicenseManager::verify_bb(const string& username,
const string& password) const {
shared_ptr<License> LicenseIndex::verify_bb(const string& username, const string& password) const {
try {
auto& license = this->bb_username_to_license.at(username);
if (license->bb_password != password) {
throw incorrect_password();
}
if (license->ban_end_time && (license->ban_end_time >= now())) {
throw invalid_argument("user is banned");
}
@@ -153,88 +234,3 @@ shared_ptr<const License> LicenseManager::verify_bb(const string& username,
throw missing_license();
}
}
size_t LicenseManager::count() const {
return this->serial_number_to_license.size();
}
void LicenseManager::ban_until(uint32_t serial_number, uint64_t end_time) {
this->serial_number_to_license.at(serial_number)->ban_end_time = end_time;
if (this->autosave) {
this->save();
}
}
shared_ptr<const License> LicenseManager::get(uint32_t serial_number) const {
try {
return this->serial_number_to_license.at(serial_number);
} catch (const out_of_range&) {
throw missing_license();
}
}
void LicenseManager::add(shared_ptr<License> l) {
this->serial_number_to_license[l->serial_number] = l;
if (!l->username.empty()) {
this->bb_username_to_license[l->username] = l;
}
if (this->autosave) {
this->save();
}
}
void LicenseManager::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->username.empty()) {
this->bb_username_to_license.erase(l->username);
}
if (this->autosave) {
this->save();
}
}
vector<License> LicenseManager::snapshot() const {
vector<License> ret;
for (auto it : this->serial_number_to_license) {
ret.emplace_back(*it.second);
}
return ret;
}
shared_ptr<License> LicenseManager::create_license_pc(
uint32_t serial_number, const string& access_key, bool temporary) {
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = access_key;
if (temporary) {
l->privileges |= Privilege::TEMPORARY;
}
return l;
}
shared_ptr<License> LicenseManager::create_license_gc(
uint32_t serial_number, const string& access_key, const string& password,
bool temporary) {
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = access_key;
l->gc_password = password;
if (temporary) {
l->privileges |= Privilege::TEMPORARY;
}
return l;
}
shared_ptr<License> LicenseManager::create_license_bb(
uint32_t serial_number, const string& username, const string& password,
bool temporary) {
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->username = username;
l->bb_password = password;
if (temporary) {
l->privileges |= Privilege::TEMPORARY;
}
return l;
}
+63 -66
View File
@@ -1,96 +1,93 @@
#pragma once
#include <memory>
#include <phosg/JSON.hh>
#include <string>
#include <unordered_map>
#include <vector>
#include "Text.hh"
enum Privilege {
KICK_USER = 0x00000001,
BAN_USER = 0x00000002,
SILENCE_USER = 0x00000004,
CHANGE_LOBBY_INFO = 0x00000008,
CHANGE_EVENT = 0x00000010,
ANNOUNCE = 0x00000020,
FREE_JOIN_GAMES = 0x00000040,
UNLOCK_GAMES = 0x00000080,
DEBUG = 0x01000000,
MODERATOR = 0x00000007,
ADMINISTRATOR = 0x0000003F,
ROOT = 0x7FFFFFFF,
TEMPORARY = 0x80000000,
};
class LicenseIndex;
struct License {
ptext<char, 0x14> username; // BB username (max. 16 chars; should technically be Unicode)
ptext<char, 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.
ptext<char, 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)
ptext<char, 0x0C> gc_password; // GC password
uint32_t privileges; // privilege level
uint64_t ban_end_time; // end time of ban (zero = not banned)
enum Flag : uint32_t {
// clang-format off
KICK_USER = 0x00000001,
BAN_USER = 0x00000002,
SILENCE_USER = 0x00000004,
CHANGE_LOBBY_INFO = 0x00000008,
CHANGE_EVENT = 0x00000010,
ANNOUNCE = 0x00000020,
FREE_JOIN_GAMES = 0x00000040,
UNLOCK_GAMES = 0x00000080,
DEBUG = 0x01000000,
MODERATOR = 0x00000007,
ADMINISTRATOR = 0x000000FF,
ROOT = 0x010000FF,
TEMPORARY = 0x80000000,
License();
std::string str() const;
} __attribute__((packed));
UNUSED_BITS = 0x7EFFFF00,
// clang-format on
};
class incorrect_password : public std::invalid_argument {
public:
incorrect_password() : invalid_argument("incorrect password") {}
};
uint32_t serial_number = 0;
std::string access_key;
std::string gc_password;
std::string bb_username;
std::string bb_password;
class incorrect_access_key : public std::invalid_argument {
public:
incorrect_access_key() : invalid_argument("incorrect access key") {}
};
uint32_t flags = 0;
uint64_t ban_end_time = 0; // 0 = not banned
class missing_license : public std::invalid_argument {
public:
missing_license() : invalid_argument("missing license") {}
};
uint32_t ep3_current_meseta = 0;
uint32_t ep3_total_meseta_earned = 0;
class LicenseManager {
public:
LicenseManager();
explicit LicenseManager(const std::string& filename);
~LicenseManager() = default;
License() = default;
explicit License(const JSON& json);
JSON json() const;
void save() const;
void delete_file() const;
std::string str() const;
};
class LicenseIndex {
public:
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();
~LicenseIndex() = default;
void set_autosave(bool autosave);
std::shared_ptr<const License> verify_pc(uint32_t serial_number,
const std::string& access_key) const;
std::shared_ptr<const License> verify_gc(uint32_t serial_number,
const std::string& access_key) const;
std::shared_ptr<const License> verify_gc(uint32_t serial_number,
const std::string& access_key, const std::string& password) const;
std::shared_ptr<const License> verify_bb(const std::string& username,
const std::string& password) const;
void ban_until(uint32_t serial_number, uint64_t seconds);
size_t count() const;
std::shared_ptr<License> get(uint32_t serial_number) const;
std::vector<std::shared_ptr<License>> all() const;
std::shared_ptr<const License> get(uint32_t serial_number) const;
void add(std::shared_ptr<License> l);
void remove(uint32_t serial_number);
std::vector<License> snapshot() const;
static std::shared_ptr<License> create_license_pc(
uint32_t serial_number, const std::string& access_key, bool temporary);
static std::shared_ptr<License> create_license_gc(
uint32_t serial_number, const std::string& access_key,
const std::string& password, bool temporary);
static std::shared_ptr<License> create_license_bb(
uint32_t serial_number, const std::string& username,
const std::string& password, bool temporary);
std::shared_ptr<License> verify_v1_v2(uint32_t serial_number, const std::string& access_key) const;
std::shared_ptr<License> verify_gc(uint32_t serial_number, const std::string& access_key) const;
std::shared_ptr<License> verify_gc(uint32_t serial_number, const std::string& access_key, const std::string& password) const;
std::shared_ptr<License> verify_bb(const std::string& username, const std::string& password) const;
protected:
std::string filename;
bool autosave;
std::unordered_map<std::string, std::shared_ptr<License>> bb_username_to_license;
-2
View File
@@ -12,7 +12,6 @@ PrefixedLogger config_log("[Config] ", LogLevel::USE_DEFAULT);
PrefixedLogger dns_server_log("[DNSServer] ", LogLevel::USE_DEFAULT);
PrefixedLogger function_compiler_log("[FunctionCompiler] ", LogLevel::USE_DEFAULT);
PrefixedLogger ip_stack_simulator_log("[IPStackSimulator] ", LogLevel::USE_DEFAULT);
PrefixedLogger license_log("[LicenseManager] ", LogLevel::USE_DEFAULT);
PrefixedLogger lobby_log("", LogLevel::USE_DEFAULT);
PrefixedLogger patch_index_log("[PatchFileIndex] ", LogLevel::USE_DEFAULT);
PrefixedLogger player_data_log("", LogLevel::USE_DEFAULT);
@@ -39,7 +38,6 @@ void set_log_levels_from_json(const JSON& json) {
set_log_level_from_json(dns_server_log, json, "DNSServer");
set_log_level_from_json(function_compiler_log, json, "FunctionCompiler");
set_log_level_from_json(ip_stack_simulator_log, json, "IPStackSimulator");
set_log_level_from_json(license_log, json, "LicenseManager");
set_log_level_from_json(lobby_log, json, "Lobbies");
set_log_level_from_json(patch_index_log, json, "PatchFileIndex");
set_log_level_from_json(player_data_log, json, "PlayerData");
-1
View File
@@ -11,7 +11,6 @@ extern PrefixedLogger config_log;
extern PrefixedLogger dns_server_log;
extern PrefixedLogger function_compiler_log;
extern PrefixedLogger ip_stack_simulator_log;
extern PrefixedLogger license_log;
extern PrefixedLogger lobby_log;
extern PrefixedLogger patch_index_log;
extern PrefixedLogger player_data_log;
+5 -5
View File
@@ -1176,8 +1176,8 @@ static HandlerResult S_G_B7(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t
if (ses->newserv_client_config.cfg.flags & Client::Flag::IS_EPISODE_3) {
if (ses->options.ep3_infinite_meseta) {
auto& cmd = check_size_t<S_RankUpdate_GC_Ep3_B7>(data);
if (cmd.meseta != 1000000) {
cmd.meseta = 1000000;
if (cmd.current_meseta != 1000000) {
cmd.current_meseta = 1000000;
return HandlerResult::Type::MODIFIED;
}
}
@@ -1272,9 +1272,9 @@ static HandlerResult S_B_EF(shared_ptr<ProxyServer::LinkedSession>, uint16_t, ui
static HandlerResult S_G_BA(shared_ptr<ProxyServer::LinkedSession> ses, uint16_t, uint32_t, string& data) {
if (ses->options.ep3_infinite_meseta) {
auto& cmd = check_size_t<S_Meseta_GC_Ep3_BA>(data);
if (cmd.remaining_meseta != 1000000) {
cmd.remaining_meseta = 1000000;
auto& cmd = check_size_t<S_MesetaTransaction_GC_Ep3_BA>(data);
if (cmd.current_meseta != 1000000) {
cmd.current_meseta = 1000000;
return HandlerResult::Type::MODIFIED;
}
}
+16 -13
View File
@@ -258,7 +258,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
auto s = server->state;
bool should_close_unlinked_session = false;
shared_ptr<const License> license;
shared_ptr<License> license;
uint32_t sub_version = 0;
uint8_t language = 1; // Default = English
string character_name;
@@ -272,7 +272,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
// anything else, disconnect
if (command == 0x93) {
const auto& cmd = check_size_t<C_LoginV1_DC_93>(data);
license = s->license_manager->verify_pc(
license = s->license_index->verify_v1_v2(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
language = cmd.language;
@@ -281,7 +281,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
client_config.cfg.flags |= Client::Flag::IS_DC_V1;
} else if (command == 0x9D) {
const auto& cmd = check_size_t<C_Login_DC_PC_GC_9D>(data, sizeof(C_LoginExtended_DC_GC_9D));
license = s->license_manager->verify_pc(
license = s->license_index->verify_v1_v2(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
language = cmd.language;
@@ -297,7 +297,7 @@ 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));
license = s->license_manager->verify_pc(
license = s->license_index->verify_v1_v2(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
language = cmd.language;
@@ -311,7 +311,7 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
throw runtime_error("command is not 9E");
}
const auto& cmd = check_size_t<C_Login_GC_9E>(data, sizeof(C_LoginExtended_GC_9E));
license = s->license_manager->verify_gc(
license = s->license_index->verify_gc(
stoul(cmd.serial_number, nullptr, 16), cmd.access_key);
sub_version = cmd.sub_version;
language = cmd.language;
@@ -329,15 +329,18 @@ void ProxyServer::UnlinkedSession::on_input(Channel& ch, uint16_t command, uint3
}
const auto& cmd = check_size_t<C_Login_BB_93>(data);
try {
license = s->license_manager->verify_bb(
license = s->license_index->verify_bb(
cmd.username, cmd.password);
} catch (const missing_license&) {
} catch (const LicenseIndex::missing_license&) {
if (!s->allow_unregistered_users) {
throw;
}
shared_ptr<License> l = LicenseManager::create_license_bb(
fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF;
l->bb_username = cmd.username;
l->bb_password = cmd.password;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
license = l;
}
login_command_bb = std::move(data);
@@ -484,7 +487,7 @@ ProxyServer::LinkedSession::LinkedSession(
shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
shared_ptr<const License> license,
shared_ptr<License> license,
const ClientConfigBB& newserv_client_config)
: LinkedSession(server, license->serial_number, local_port, version) {
this->license = license;
@@ -500,7 +503,7 @@ ProxyServer::LinkedSession::LinkedSession(
shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
std::shared_ptr<License> license,
const struct sockaddr_storage& next_destination)
: LinkedSession(server, license->serial_number, local_port, version) {
this->license = license;
@@ -821,7 +824,7 @@ shared_ptr<ProxyServer::LinkedSession> ProxyServer::get_session_by_name(
}
shared_ptr<ProxyServer::LinkedSession> ProxyServer::create_licensed_session(
shared_ptr<const License> l, uint16_t local_port, GameVersion version,
shared_ptr<License> l, uint16_t local_port, GameVersion version,
const ClientConfigBB& newserv_client_config) {
shared_ptr<LinkedSession> session(new LinkedSession(
this->shared_from_this(), local_port, version, l, newserv_client_config));
+4 -4
View File
@@ -38,7 +38,7 @@ public:
std::unique_ptr<struct event, void (*)(struct event*)> timeout_event;
std::shared_ptr<const License> license;
std::shared_ptr<License> license;
Channel client_channel;
Channel server_channel;
@@ -120,13 +120,13 @@ public:
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
std::shared_ptr<License> license,
const ClientConfigBB& newserv_client_config);
LinkedSession(
std::shared_ptr<ProxyServer> server,
uint16_t local_port,
GameVersion version,
std::shared_ptr<const License> license,
std::shared_ptr<License> license,
const struct sockaddr_storage& next_destination);
LinkedSession(
std::shared_ptr<ProxyServer> server,
@@ -171,7 +171,7 @@ public:
std::shared_ptr<LinkedSession> get_session();
std::shared_ptr<LinkedSession> get_session_by_name(const std::string& name);
std::shared_ptr<LinkedSession> create_licensed_session(
std::shared_ptr<const License> l,
std::shared_ptr<License> l,
uint16_t local_port,
GameVersion version,
const ClientConfigBB& newserv_client_config);
+155 -98
View File
@@ -121,7 +121,7 @@ static bool send_enable_send_function_call_if_applicable(shared_ptr<Client> c) {
auto s = c->require_server_state();
if (function_compiler_available() &&
(c->flags & Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL)) {
if (s->episode_3_send_function_call_enabled) {
if (s->ep3_send_function_call_enabled) {
send_quest_buffer_overflow(c);
} else {
c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL;
@@ -279,6 +279,10 @@ void on_login_complete(shared_ptr<Client> c) {
c->game_data.should_update_play_time = true;
}
if (c->flags & Client::Flag::IS_EPISODE_3) {
send_ep3_rank_update(c);
}
send_lobby_list(c);
send_get_player_info(c);
}
@@ -327,30 +331,32 @@ static void on_DB_V3(shared_ptr<Client> c, uint16_t, uint32_t, const string& dat
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
auto l = s->license_manager->verify_gc(serial_number, cmd.access_key,
cmd.password);
auto l = s->license_index->verify_gc(serial_number, cmd.access_key, cmd.password);
c->set_license(l);
send_command(c, 0x9A, 0x02);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_command(c, 0x9A, 0x03);
c->should_disconnect = true;
return;
} catch (const incorrect_password& e) {
} catch (const LicenseIndex::incorrect_password& e) {
send_command(c, 0x9A, 0x07);
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_command(c, 0x9A, 0x04);
c->should_disconnect = true;
return;
} else {
auto l = LicenseManager::create_license_gc(serial_number, cmd.access_key,
cmd.password, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
l->gc_password = cmd.password;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
send_command(c, 0x9A, 0x02);
}
@@ -367,23 +373,24 @@ static void on_88_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, const string&
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key);
c->set_license(l);
send_command(c, 0x88, 0x00);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
c->should_disconnect = true;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, u"Incorrect serial number");
c->should_disconnect = true;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
send_command(c, 0x88, 0x00);
}
@@ -400,25 +407,25 @@ static void on_8B_DCNTE(shared_ptr<Client> c, uint16_t, uint32_t, const string&
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
shared_ptr<License> l = s->license_index->verify_v1_v2(
serial_number, cmd.access_key);
c->set_license(l);
// send_command(c, 0x8B, 0x01);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
c->should_disconnect = true;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, u"Incorrect serial number");
c->should_disconnect = true;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
// send_command(c, 0x8B, 0x01);
}
}
@@ -445,23 +452,24 @@ static void on_90_DC(shared_ptr<Client> c, uint16_t, uint32_t, const string& dat
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key);
c->set_license(l);
send_command(c, 0x90, 0x02);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_command(c, 0x90, 0x03);
c->should_disconnect = true;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_command(c, 0x90, 0x03);
c->should_disconnect = true;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
send_command(c, 0x90, 0x01);
}
@@ -485,24 +493,25 @@ static void on_93_DC(shared_ptr<Client> c, uint16_t, uint32_t, const string& dat
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l = s->license_manager->verify_pc(
serial_number, cmd.access_key);
shared_ptr<License> l = s->license_index->verify_v1_v2(serial_number, cmd.access_key);
c->set_license(l);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_message_box(c, u"Incorrect access key");
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_message_box(c, u"Incorrect serial number");
c->should_disconnect = true;
return;
} else {
auto l = LicenseManager::create_license_pc(
serial_number, cmd.access_key, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
}
}
@@ -538,14 +547,14 @@ static void on_9A(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l;
shared_ptr<License> l;
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
l = s->license_index->verify_v1_v2(serial_number, cmd.access_key);
break;
case GameVersion::GC:
l = s->license_manager->verify_gc(serial_number, cmd.access_key);
l = s->license_index->verify_gc(serial_number, cmd.access_key);
break;
case GameVersion::XB:
throw runtime_error("xbox licenses are not implemented");
@@ -556,17 +565,17 @@ static void on_9A(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
c->set_license(l);
send_command(c, 0x9A, 0x02);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_command(c, 0x9A, 0x03);
c->should_disconnect = true;
return;
} catch (const incorrect_password& e) {
} catch (const LicenseIndex::incorrect_password& e) {
send_command(c, 0x9A, 0x07);
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
// On V3, the client should have sent a different command containing the
// password already, which should have created and added a temporary
// license. So, if no license exists at this point, disconnect the client
@@ -577,8 +586,11 @@ static void on_9A(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
c->should_disconnect = true;
return;
} else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
l = LicenseManager::create_license_pc(serial_number, cmd.access_key, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
send_command(c, 0x9A, 0x02);
} else {
@@ -595,14 +607,14 @@ static void on_9C(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16);
try {
shared_ptr<const License> l;
shared_ptr<License> l;
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = s->license_manager->verify_pc(serial_number, cmd.access_key);
l = s->license_index->verify_v1_v2(serial_number, cmd.access_key);
break;
case GameVersion::GC:
l = s->license_manager->verify_gc(serial_number, cmd.access_key,
l = s->license_index->verify_gc(serial_number, cmd.access_key,
cmd.password);
break;
case GameVersion::XB:
@@ -614,35 +626,25 @@ static void on_9C(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
c->set_license(l);
send_command(c, 0x9C, 0x01);
} catch (const incorrect_password& e) {
} catch (const LicenseIndex::incorrect_password& e) {
send_command(c, 0x9C, 0x00);
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
send_command(c, 0x9C, 0x00);
c->should_disconnect = true;
return;
} else {
shared_ptr<License> l;
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = LicenseManager::create_license_pc(serial_number, cmd.access_key,
true);
break;
case GameVersion::GC:
l = LicenseManager::create_license_gc(serial_number, cmd.access_key,
cmd.password, true);
break;
case GameVersion::XB:
throw runtime_error("xbox licenses are not implemented");
break;
default:
throw logic_error("unsupported versioned command");
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = cmd.access_key;
if (c->version() == GameVersion::GC) {
l->gc_password = cmd.password;
}
s->license_manager->add(l);
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
send_command(c, 0x9C, 0x01);
}
@@ -714,7 +716,7 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, const str
// the client to crash.
if (base_cmd->unused1 == 0x5F5CA297) {
c->flags &= ~(Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL | Client::Flag::NO_SEND_FUNCTION_CALL);
} else if (!s->episode_3_send_function_call_enabled &&
} else if (!s->ep3_send_function_call_enabled &&
(c->flags & Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL)) {
c->flags &= ~Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL;
c->flags |= Client::Flag::NO_SEND_FUNCTION_CALL;
@@ -722,14 +724,14 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, const str
uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16);
try {
shared_ptr<const License> l;
shared_ptr<License> l;
switch (c->version()) {
case GameVersion::DC:
case GameVersion::PC:
l = s->license_manager->verify_pc(serial_number, base_cmd->access_key);
l = s->license_index->verify_v1_v2(serial_number, base_cmd->access_key);
break;
case GameVersion::GC:
l = s->license_manager->verify_gc(serial_number, base_cmd->access_key);
l = s->license_index->verify_gc(serial_number, base_cmd->access_key);
break;
case GameVersion::XB:
throw runtime_error("xbox licenses are not implemented");
@@ -739,17 +741,17 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, const str
}
c->set_license(l);
} catch (const incorrect_access_key& e) {
} catch (const LicenseIndex::incorrect_access_key& e) {
send_command(c, 0x04, 0x03);
c->should_disconnect = true;
return;
} catch (const incorrect_password& e) {
} catch (const LicenseIndex::incorrect_password& e) {
send_command(c, 0x04, 0x06);
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
// On V3, the client should have sent a different command containing the
// password already, which should have created and added a temporary
// license. So, if no license exists at this point, disconnect the client
@@ -760,8 +762,11 @@ static void on_9D_9E(shared_ptr<Client> c, uint16_t command, uint32_t, const str
c->should_disconnect = true;
return;
} else if ((c->version() == GameVersion::DC) || (c->version() == GameVersion::PC)) {
l = LicenseManager::create_license_pc(serial_number, base_cmd->access_key, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = serial_number;
l->access_key = base_cmd->access_key;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
} else {
throw runtime_error("unsupported game version");
@@ -788,25 +793,28 @@ static void on_93_BB(shared_ptr<Client> c, uint16_t, uint32_t, const string& dat
c->flags |= flags_for_version(c->version(), -1);
try {
auto l = s->license_manager->verify_bb(cmd.username, cmd.password);
auto l = s->license_index->verify_bb(cmd.username, cmd.password);
c->set_license(l);
} catch (const incorrect_password& e) {
} catch (const LicenseIndex::incorrect_password& e) {
u16string message = u"Login failed: " + decode_sjis(e.what());
send_message_box(c, message.c_str());
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
u16string message = u"Login failed: " + decode_sjis(e.what());
send_message_box(c, message.c_str());
c->should_disconnect = true;
return;
} else {
shared_ptr<License> l = LicenseManager::create_license_bb(
fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF;
l->bb_username = cmd.username;
l->bb_password = cmd.password;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
}
}
@@ -892,11 +900,29 @@ static void on_B1(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
}
static void on_BA_Ep3(shared_ptr<Client> c, uint16_t command, uint32_t, const string& data) {
const auto& in_cmd = check_size_t<C_Meseta_GC_Ep3_BA>(data);
const auto& in_cmd = check_size_t<C_MesetaTransaction_GC_Ep3_BA>(data);
auto s = c->require_server_state();
auto l = c->lobby.lock();
bool is_lobby = l && !l->is_game();
uint32_t meseta = s->ep3_infinite_meseta ? 1000000 : 0;
S_Meseta_GC_Ep3_BA out_cmd = {meseta, meseta, in_cmd.request_token};
uint32_t current_meseta, total_meseta_earned;
if (s->ep3_infinite_meseta) {
current_meseta = 1000000;
total_meseta_earned = 1000000;
} else if (is_lobby && s->ep3_jukebox_is_free) {
current_meseta = c->license->ep3_current_meseta;
total_meseta_earned = c->license->ep3_total_meseta_earned;
} else {
if (c->license->ep3_current_meseta < in_cmd.value) {
throw runtime_error("meseta overdraft not allowed");
}
c->license->ep3_current_meseta -= in_cmd.value;
c->license->save();
current_meseta = c->license->ep3_current_meseta;
total_meseta_earned = c->license->ep3_total_meseta_earned;
}
S_MesetaTransaction_GC_Ep3_BA out_cmd = {current_meseta, total_meseta_earned, in_cmd.request_token};
send_command(c, command, 0x03, &out_cmd, sizeof(out_cmd));
}
@@ -1321,14 +1347,41 @@ static void on_CA_Ep3(shared_ptr<Client> c, uint16_t, uint32_t, const string& da
auto tourn = l->tournament_match->tournament.lock();
tourn->print_bracket(stderr);
shared_ptr<Episode3::Tournament::Team> winner_team;
shared_ptr<Episode3::Tournament::Team> loser_team;
if (winner_team_id == 0) {
l->tournament_match->set_winner_team(l->tournament_match->preceding_a->winner_team);
winner_team = l->tournament_match->preceding_a->winner_team;
loser_team = l->tournament_match->preceding_b->winner_team;
} else if (winner_team_id == 1) {
l->tournament_match->set_winner_team(l->tournament_match->preceding_b->winner_team);
winner_team = l->tournament_match->preceding_b->winner_team;
loser_team = l->tournament_match->preceding_a->winner_team;
} else {
throw logic_error("invalid winner team id");
}
send_ep3_tournament_match_result(l);
l->tournament_match->set_winner_team(winner_team);
uint32_t meseta_reward = 0;
auto& round_rewards = loser_team->has_any_human_players()
? s->ep3_defeat_player_meseta_rewards
: s->ep3_defeat_com_meseta_rewards;
meseta_reward = (l->tournament_match->round_num - 1 < round_rewards.size())
? round_rewards[l->tournament_match->round_num - 1]
: round_rewards.back();
if (l->tournament_match == tourn->get_final_match()) {
meseta_reward += s->ep3_final_round_meseta_bonus;
}
for (const auto& player : winner_team->players) {
if (player.is_human()) {
auto winner_c = player.client.lock();
if (winner_c) {
winner_c->license->ep3_current_meseta += meseta_reward;
winner_c->license->ep3_total_meseta_earned += meseta_reward;
winner_c->license->save();
send_ep3_rank_update(winner_c);
}
}
}
send_ep3_tournament_match_result(l, meseta_reward);
on_tournament_bracket_updated(s, tourn);
l->ep3_server->tournament_match_result_sent = true;
@@ -1863,7 +1916,7 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, const string& data)
break;
}
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES)) {
if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES)) {
if (!game->password.empty() && (password != game->password)) {
send_lobby_message_box(c, u"$C6Incorrect password.");
break;
@@ -2679,7 +2732,7 @@ static void on_00E3_BB(shared_ptr<Client> c, uint16_t, uint32_t, const string& d
ClientGameData temp_gd;
temp_gd.guild_card_number = c->license->serial_number;
temp_gd.bb_username = c->license->username;
temp_gd.bb_username = c->license->bb_username;
temp_gd.bb_player_index = cmd.player_index;
try {
@@ -3162,7 +3215,7 @@ shared_ptr<Lobby> create_game_generic(
throw runtime_error("invalid episode");
}
if (!(c->license->privileges & Privilege::FREE_JOIN_GAMES) &&
if (!(c->license->flags & License::Flag::FREE_JOIN_GAMES) &&
(min_level > c->game_data.player()->disp.stats.level)) {
// Note: We don't throw here because this is a situation players might
// actually encounter while playing the game normally
@@ -3773,25 +3826,29 @@ static void on_04_P(shared_ptr<Client> c, uint16_t, uint32_t, const string& data
auto s = c->require_server_state();
try {
auto l = s->license_manager->verify_bb(cmd.username, cmd.password);
auto l = s->license_index->verify_bb(cmd.username, cmd.password);
c->set_license(l);
} catch (const incorrect_password& e) {
} catch (const LicenseIndex::incorrect_password& e) {
u16string message = u"Login failed: " + decode_sjis(e.what());
send_message_box(c, message.c_str());
c->should_disconnect = true;
return;
} catch (const missing_license& e) {
} catch (const LicenseIndex::missing_license& e) {
if (!s->allow_unregistered_users) {
u16string message = u"Login failed: " + decode_sjis(e.what());
send_message_box(c, message.c_str());
c->should_disconnect = true;
return;
} else {
shared_ptr<License> l = LicenseManager::create_license_bb(
fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true);
s->license_manager->add(l);
shared_ptr<License> l(new License());
l->serial_number = fnv1a32(cmd.username) & 0x7FFFFFFF;
l->bb_username = cmd.username;
l->bb_password = cmd.password;
l->flags |= License::Flag::TEMPORARY;
s->license_index->add(l);
c->set_license(l);
}
}
+5 -7
View File
@@ -2227,8 +2227,9 @@ void send_ep3_media_update(
void send_ep3_rank_update(shared_ptr<Client> c) {
auto s = c->require_server_state();
uint32_t meseta = s->ep3_infinite_meseta ? 1000000 : 0;
S_RankUpdate_GC_Ep3_B7 cmd = {0, "\0\0\0\0\0\0\0\0\0\0\0", meseta, meseta, 0xFFFFFFFF};
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;
S_RankUpdate_GC_Ep3_B7 cmd = {0, "\0\0\0\0\0\0\0\0\0\0\0", current_meseta, total_meseta_earned, 0xFFFFFFFF};
send_command_t(c, 0xB7, 0x00, cmd);
}
@@ -2564,7 +2565,7 @@ void send_ep3_set_tournament_player_decks(shared_ptr<Client> c) {
// TODO: Handle disconnection during the match (the other team should win)
}
void send_ep3_tournament_match_result(shared_ptr<Lobby> l) {
void send_ep3_tournament_match_result(shared_ptr<Lobby> l, uint32_t meseta_reward) {
auto s = l->require_server_state();
auto& match = l->tournament_match;
auto tourn = match->tournament.lock();
@@ -2603,10 +2604,7 @@ void send_ep3_tournament_match_result(shared_ptr<Lobby> l) {
cmd.round_num = (match == tourn->get_final_match()) ? 6 : match->round_num;
cmd.num_players_per_team = match->preceding_a->winner_team->max_players;
cmd.winner_team_id = (match->preceding_b->winner_team == match->winner_team);
// TODO: This amount should vary depending on the match level / round number,
// but newserv doesn't currently implement meseta at all - we just always give
// the player 1000000 and never charge for anything.
cmd.meseta_amount = 100;
cmd.meseta_amount = meseta_reward;
cmd.meseta_reward_text = "You got %s meseta!";
if (!(s->ep3_behavior_flags & Episode3::BehaviorFlag::DISABLE_MASKING)) {
uint8_t mask_key = (random_object<uint32_t>() % 0xFF) + 1;
+1 -1
View File
@@ -331,7 +331,7 @@ void send_ep3_tournament_info(
std::shared_ptr<Client> c,
std::shared_ptr<const Episode3::Tournament> t);
void send_ep3_set_tournament_player_decks(std::shared_ptr<Client> c);
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l);
void send_ep3_tournament_match_result(std::shared_ptr<Lobby> l, uint32_t meseta_reward);
void send_ep3_tournament_details(
std::shared_ptr<Client> c,
+1 -1
View File
@@ -306,7 +306,7 @@ vector<shared_ptr<Client>> Server::get_clients_by_identifier(const string& ident
results.emplace_back(std::move(c));
continue;
}
if (c->license && c->license->username == ident) {
if (c->license && c->license->bb_username == ident) {
results.emplace_back(std::move(c));
continue;
}
+65 -55
View File
@@ -134,7 +134,7 @@ Server commands:\n\
gc-password=<password> (GC password)\n\
access-key=<access-key> (DC/GC/PC access key)\n\
serial=<serial-number> (decimal serial number; required for all licenses)\n\
privileges=<privilege-mask> (can be normal, mod, admin, root, or numeric)\n\
flags=<privilege-mask> (can be normal, mod, admin, root, or numeric)\n\
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\
@@ -310,7 +310,7 @@ Proxy session commands:\n\
if (token.size() >= 32) {
throw invalid_argument("username too long");
}
l->username = token.substr(12);
l->bb_username = token.substr(12);
} else if (starts_with(token, "bb-password=")) {
if (token.size() >= 32) {
@@ -333,18 +333,18 @@ Proxy session commands:\n\
} else if (starts_with(token, "serial=")) {
l->serial_number = stoul(token.substr(7));
} else if (starts_with(token, "privileges=")) {
} else if (starts_with(token, "flags=")) {
string mask = token.substr(11);
if (mask == "normal") {
l->privileges = 0;
l->flags = 0;
} else if (mask == "mod") {
l->privileges = Privilege::MODERATOR;
l->flags = License::Flag::MODERATOR;
} else if (mask == "admin") {
l->privileges = Privilege::ADMINISTRATOR;
l->flags = License::Flag::ADMINISTRATOR;
} else if (mask == "root") {
l->privileges = Privilege::ROOT;
l->flags = License::Flag::ROOT;
} else {
l->privileges = stoul(mask);
l->flags = stoul(mask);
}
} else {
@@ -356,7 +356,8 @@ Proxy session commands:\n\
throw invalid_argument("license does not contain serial number");
}
this->state->license_manager->add(l);
l->save();
this->state->license_index->add(l);
fprintf(stderr, "license added\n");
} else if (command_name == "update-license") {
@@ -366,71 +367,80 @@ Proxy session commands:\n\
}
uint32_t serial_number = stoul(tokens[0]);
tokens.erase(tokens.begin());
auto orig_l = this->state->license_manager->get(serial_number);
auto orig_l = this->state->license_index->get(serial_number);
shared_ptr<License> l(new License(*orig_l));
for (const string& token : tokens) {
if (starts_with(token, "bb-username=")) {
if (token.size() >= 32) {
throw invalid_argument("username too long");
}
l->username = token.substr(12);
this->state->license_index->remove(orig_l->serial_number);
try {
for (const string& token : tokens) {
if (starts_with(token, "bb-username=")) {
if (token.size() >= 32) {
throw invalid_argument("username too long");
}
l->bb_username = token.substr(12);
} else if (starts_with(token, "bb-password=")) {
if (token.size() >= 32) {
throw invalid_argument("bb-password too long");
}
l->bb_password = token.substr(12);
} else if (starts_with(token, "bb-password=")) {
if (token.size() >= 32) {
throw invalid_argument("bb-password too long");
}
l->bb_password = token.substr(12);
} else if (starts_with(token, "gc-password=")) {
if (token.size() > 20) {
throw invalid_argument("gc-password too long");
}
l->gc_password = token.substr(12);
} else if (starts_with(token, "gc-password=")) {
if (token.size() > 20) {
throw invalid_argument("gc-password too long");
}
l->gc_password = token.substr(12);
} else if (starts_with(token, "access-key=")) {
if (token.size() > 23) {
throw invalid_argument("access-key is too long");
}
l->access_key = token.substr(11);
} else if (starts_with(token, "access-key=")) {
if (token.size() > 23) {
throw invalid_argument("access-key is too long");
}
l->access_key = token.substr(11);
} else if (starts_with(token, "serial=")) {
l->serial_number = stoul(token.substr(7));
} else if (starts_with(token, "serial=")) {
l->serial_number = stoul(token.substr(7));
} else if (starts_with(token, "flags=")) {
string mask = token.substr(11);
if (mask == "normal") {
l->flags = 0;
} else if (mask == "mod") {
l->flags = License::Flag::MODERATOR;
} else if (mask == "admin") {
l->flags = License::Flag::ADMINISTRATOR;
} else if (mask == "root") {
l->flags = License::Flag::ROOT;
} else {
l->flags = stoul(mask);
}
} else if (starts_with(token, "privileges=")) {
string mask = token.substr(11);
if (mask == "normal") {
l->privileges = 0;
} else if (mask == "mod") {
l->privileges = Privilege::MODERATOR;
} else if (mask == "admin") {
l->privileges = Privilege::ADMINISTRATOR;
} else if (mask == "root") {
l->privileges = Privilege::ROOT;
} else {
l->privileges = stoul(mask);
throw invalid_argument("incorrect field: " + token);
}
} else {
throw invalid_argument("incorrect field: " + token);
}
if (!l->serial_number) {
throw invalid_argument("license does not contain serial number");
}
} catch (const exception&) {
this->state->license_index->add(orig_l);
throw;
}
if (!l->serial_number) {
throw invalid_argument("license does not contain serial number");
}
this->state->license_manager->add(l);
l->save();
this->state->license_index->add(l);
fprintf(stderr, "license updated\n");
} else if (command_name == "delete-license") {
uint32_t serial_number = stoul(command_args);
this->state->license_manager->remove(serial_number);
auto l = this->state->license_index->get(serial_number);
l->delete_file();
this->state->license_index->remove(l->serial_number);
fprintf(stderr, "license deleted\n");
} else if (command_name == "list-licenses") {
for (const auto& l : this->state->license_manager->snapshot()) {
string s = l.str();
for (const auto& l : this->state->license_index->all()) {
string s = l->str();
fprintf(stderr, "%s\n", s.c_str());
}
+24 -7
View File
@@ -26,9 +26,13 @@ ServerState::ServerState(const char* config_filename, bool is_replay)
allow_saving(true),
item_tracking_enabled(true),
drops_enabled(true),
episode_3_send_function_call_enabled(false),
ep3_send_function_call_enabled(false),
catch_handler_exceptions(true),
ep3_infinite_meseta(true),
ep3_infinite_meseta(false),
ep3_defeat_player_meseta_rewards({400, 500, 600, 700, 800}),
ep3_defeat_com_meseta_rewards({100, 200, 300, 400, 500}),
ep3_final_round_meseta_bonus(300),
ep3_jukebox_is_free(false),
ep3_behavior_flags(0),
run_shell_behavior(RunShellBehavior::DEFAULT),
cheat_mode_behavior(CheatModeBehavior::OFF_BY_DEFAULT),
@@ -534,13 +538,26 @@ void ServerState::parse_config(const JSON& json, bool is_reload) {
this->allow_unregistered_users = json.get_bool("AllowUnregisteredUsers", this->allow_unregistered_users);
this->item_tracking_enabled = json.get_bool("EnableItemTracking", this->item_tracking_enabled);
this->drops_enabled = json.get_bool("EnableDrops", this->drops_enabled);
this->episode_3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->episode_3_send_function_call_enabled);
this->ep3_send_function_call_enabled = json.get_bool("EnableEpisode3SendFunctionCall", this->ep3_send_function_call_enabled);
this->catch_handler_exceptions = json.get_bool("CatchHandlerExceptions", this->catch_handler_exceptions);
auto parse_int_list = +[](const JSON& json) -> vector<uint32_t> {
vector<uint32_t> ret;
for (const auto& item : json.as_list()) {
ret.emplace_back(item->as_int());
}
return ret;
};
this->ep3_infinite_meseta = json.get_bool("Episode3InfiniteMeseta", this->ep3_infinite_meseta);
this->proxy_allow_save_files = json.get_bool("ProxyAllowSaveFiles", this->proxy_allow_save_files);
this->proxy_enable_login_options = json.get_bool("ProxyEnableLoginOptions", this->proxy_enable_login_options);
this->ep3_defeat_player_meseta_rewards = parse_int_list(json.get("Episode3DefeatPlayerMeseta", JSON::list()));
this->ep3_defeat_com_meseta_rewards = parse_int_list(json.get("Episode3DefeatCOMMeseta", JSON::list()));
this->ep3_final_round_meseta_bonus = json.get_int("Episode3FinalRoundMesetaBonus", this->ep3_final_round_meseta_bonus);
this->ep3_jukebox_is_free = json.get_bool("Episode3JukeboxIsFree", this->ep3_jukebox_is_free);
this->ep3_behavior_flags = json.get_int("Episode3BehaviorFlags", this->ep3_behavior_flags);
this->ep3_card_auction_points = json.get_int("CardAuctionPoints", this->ep3_card_auction_points);
this->proxy_allow_save_files = json.get_bool("ProxyAllowSaveFiles", this->proxy_allow_save_files);
this->proxy_enable_login_options = json.get_bool("ProxyEnableLoginOptions", this->proxy_enable_login_options);
try {
const auto& i = json.at("CardAuctionSize");
@@ -767,9 +784,9 @@ void ServerState::load_bb_private_keys() {
void ServerState::load_licenses() {
config_log.info("Loading license list");
this->license_manager.reset(new LicenseManager("system/licenses.nsi"));
this->license_index.reset(new LicenseIndex());
if (this->is_replay) {
this->license_manager->set_autosave(false);
this->license_index->set_autosave(false);
}
}
+6 -2
View File
@@ -59,9 +59,13 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
bool allow_saving;
bool item_tracking_enabled;
bool drops_enabled;
bool episode_3_send_function_call_enabled;
bool ep3_send_function_call_enabled;
bool catch_handler_exceptions;
bool ep3_infinite_meseta;
std::vector<uint32_t> ep3_defeat_player_meseta_rewards;
std::vector<uint32_t> ep3_defeat_com_meseta_rewards;
uint32_t ep3_final_round_meseta_bonus;
bool ep3_jukebox_is_free;
uint32_t ep3_behavior_flags;
RunShellBehavior run_shell_behavior;
CheatModeBehavior cheat_mode_behavior;
@@ -110,7 +114,7 @@ struct ServerState : public std::enable_shared_from_this<ServerState> {
};
std::vector<Ep3LobbyBannerEntry> ep3_lobby_banners;
std::shared_ptr<LicenseManager> license_manager;
std::shared_ptr<LicenseIndex> license_index;
std::shared_ptr<const Menu> information_menu_v2;
std::shared_ptr<const Menu> information_menu_v3;
+17 -8
View File
@@ -193,8 +193,6 @@
// network level within the simulator. This log is fairly verbose at the
// info level, so by default we suppress those messages.
"IPStackSimulator": "WARNING",
// License manager messages describe the creation of new license files.
"LicenseManager": "INFO",
// Lobby messages describe creation and deletion of lobbies and games, as
// well as item tracking events within games. On Episode 3, debug messages
// during battles go to this stream as well; use "DEBUG" here to see them.
@@ -285,12 +283,23 @@
// they are at the newserv main menu. If set, this value must be an integer.
// "Episode3MenuSong": 0,
// Episode 3 Meseta behavior. If enabled (which is the default), all players
// have infinite Meseta, which effectively makes jukebox songs and Pinz's Shop
// free. If disabled, all players have no Meseta, which makes these features
// inaccessible. Proper Meseta behavior will be implemented at some point in
// the future.
"Episode3InfiniteMeseta": true,
// 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.
"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
// then defeat a COM in round 2, they will earn 200 more Meseta; if they
// defeat another player in round 3, they will earn an additional 600.
"Episode3DefeatPlayerMeseta": [400, 500, 600, 700, 800],
"Episode3DefeatCOMMeseta": [100, 200, 300, 400, 500],
// Winning the final round is worth this much extra Meseta.
"Episode3FinalRoundMesetaBonus": 300,
// If this option is enabled, the jukebox in Episode 3 lobbies does not deduct
// any Meseta when a song is played. The player must still have at least 100
// Meseta to play a song, however.
"Episode3JukeboxIsFree": false,
// Episode 3 battle behavior flags. When set to zero, battles behave as they
// did on the original Sega servers. Combinations of behaviors can be enabled
+4 -1
View File
@@ -62,7 +62,7 @@ I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=04 f
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 40 42 0F 00 40 42 0F 00 FF FF FF FF | @B @B
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=95 flag=00)
0000 | 95 00 04 00 |
I 16332 2023-09-17 10:14:34 - [Commands] Sending to C-1 (version=GC command=D5 flag=00)
@@ -2709,6 +2709,9 @@ I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=04 f
0010 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=83 flag=14)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
I 16332 2023-09-17 10:14:37 - [Commands] Sending to C-2 (version=GC command=83 flag=14)
0000 | 83 14 F4 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3
0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3
0020 | 03 00 00 00 00 00 00 00 33 00 00 33 04 00 00 00 | 3 3
@@ -62,7 +62,7 @@ I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=04 f
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 40 42 0F 00 40 42 0F 00 FF FF FF FF | @B @B
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=95 flag=00)
0000 | 95 00 04 00 |
I 17097 2023-09-19 21:52:47 - [Commands] Sending to C-1 (version=GC command=D5 flag=00)
@@ -2708,6 +2708,9 @@ I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 f
0000 | 04 00 2C 00 00 00 01 00 11 11 11 11 39 98 AC 82 | , 9
0010 | 0E 89 2A 49 0A 46 02 00 30 45 53 33 00 00 00 00 | *I F 0ES3
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=04 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
I 17097 2023-09-19 21:52:50 - [Commands] Sending to C-2 (version=GC command=83 flag=14)
0000 | 83 14 F4 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3
0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3
@@ -5866,7 +5869,7 @@ I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=04 f
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=B7 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 40 42 0F 00 40 42 0F 00 FF FF FF FF | @B @B
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=95 flag=00)
0000 | 95 00 04 00 |
I 17097 2023-09-19 21:53:53 - [Commands] Sending to C-3 (version=GC command=D5 flag=00)
@@ -8512,6 +8515,9 @@ I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 f
0000 | 04 00 2C 00 00 00 01 00 22 22 22 22 39 98 AC 82 | , 9
0010 | 0E 89 2A 49 0A 4C 02 00 00 00 00 33 00 00 00 00 | *I L 3
0020 | 00 00 FF FF FF FF FF FF FF FF FF FF |
I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=04 flag=00)
0000 | B7 00 20 00 00 00 00 00 00 00 00 00 00 00 00 00 |
0010 | 00 00 00 00 00 00 00 00 00 00 00 00 FF FF FF FF |
I 17097 2023-09-19 21:53:54 - [Commands] Sending to C-4 (version=GC command=83 flag=14)
0000 | 83 14 F4 00 33 00 00 33 01 00 00 00 00 00 00 00 | 3 3
0010 | 33 00 00 33 02 00 00 00 00 00 00 00 33 00 00 33 | 3 3 3 3
+5 -1
View File
@@ -18,6 +18,11 @@
"EnableItemTracking": true,
"Episode3BehaviorFlags": 0xFA,
"Episode3InfiniteMeseta": false,
"Episode3DefeatPlayerMeseta": [400, 500, 600, 700, 800],
"Episode3DefeatCOMMeseta": [100, 200, 300, 400, 500],
"Episode3FinalRoundMesetaBonus": 300,
"PortConfiguration": {
"gc-jp10": [9000, "gc", "login_server"],
"gc-jp11": [9001, "gc", "login_server"],
@@ -67,7 +72,6 @@
"DNSServer": "INFO",
"FunctionCompiler": "INFO",
"IPStackSimulator": "INFO",
"LicenseManager": "INFO",
"Lobbies": "INFO",
"PlayerData": "INFO",
"ProxyServer": "INFO",