From 058b040975ebc007a4c679b05d6bbc3afcbf52c4 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sun, 24 Sep 2023 16:25:38 -0700 Subject: [PATCH] implement Episode 3 meseta --- README.md | 2 +- src/ChatCommands.cc | 35 +- src/Client.cc | 4 +- src/Client.hh | 4 +- src/CommandFormats.hh | 12 +- src/License.cc | 310 +++++++++--------- src/License.hh | 129 ++++---- src/Loggers.cc | 2 - src/Loggers.hh | 1 - src/ProxyCommands.cc | 10 +- src/ProxyServer.cc | 29 +- src/ProxyServer.hh | 8 +- src/ReceiveCommands.cc | 253 ++++++++------ src/SendCommands.cc | 12 +- src/SendCommands.hh | 2 +- src/Server.cc | 2 +- src/ServerShell.cc | 120 +++---- src/ServerState.cc | 31 +- src/ServerState.hh | 8 +- system/config.example.json | 25 +- tests/GC-Episode3Battle.test.txt | 5 +- tests/GC-Episode3BattleWithSpectator.test.txt | 10 +- tests/config.json | 6 +- 23 files changed, 561 insertions(+), 459 deletions(-) diff --git a/README.md b/README.md index 715497a8..7d26d3ae 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index e941a739..4ff1f35e 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -37,11 +37,11 @@ private: std::u16string user_msg; }; -static void check_privileges(shared_ptr c, uint64_t mask) { +static void check_license_flags(shared_ptr 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 ses, } static void server_command_ax(shared_ptr 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 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 ses, cons } static void server_command_debug(shared_ptr 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 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 ses, cons } static void server_command_persist(shared_ptr 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 c, const std::u16string&) { static void server_command_lobby_event(shared_ptr 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 ses } static void server_command_lobby_event_all(shared_ptr 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 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 c) { static void server_command_silence(shared_ptr 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 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 c, const std::u16string& a static void server_command_kick(shared_ptr 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 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 c, const std::u16string& args static void server_command_ban(shared_ptr 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 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 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); diff --git a/src/Client.cc b/src/Client.cc index df3841b4..0d1bf87b 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -147,11 +147,11 @@ QuestScriptVersion Client::quest_version() const { } } -void Client::set_license(shared_ptr l) { +void Client::set_license(shared_ptr 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; } } diff --git a/src/Client.hh b/src/Client.hh index b5bc4cfa..2547a39e 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -122,7 +122,7 @@ struct Client : public std::enable_shared_from_this { PrefixedLogger log; // License & account - std::shared_ptr license; + std::shared_ptr 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 { } QuestScriptVersion quest_version() const; - void set_license(std::shared_ptr l); + void set_license(std::shared_ptr l); std::shared_ptr require_server_state() const; std::shared_ptr require_lobby() const; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index 8115bfbc..ba5b9327 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -2209,8 +2209,8 @@ struct S_RankUpdate_GC_Ep3_B7 { // without modifying it. le_uint32_t rank = 0; ptext 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__; diff --git a/src/License.cc b/src/License.cc index fda5953d..2e45fb9f 100644 --- a/src/License.cc +++ b/src/License.cc @@ -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 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 username; // BB username (max. 16 chars; should technically be Unicode) + ptext 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 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 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(this->filename); - for (const auto& read_license : licenses) { - shared_ptr 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("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(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 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 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 LicenseManager::verify_gc(uint32_t serial_number, - const string& access_key) const { +vector> LicenseIndex::all() const { + vector> 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 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 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 LicenseManager::verify_gc(uint32_t serial_number, } } -shared_ptr LicenseManager::verify_gc(uint32_t serial_number, - const string& access_key, const string& password) const { +shared_ptr 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 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 LicenseManager::verify_gc(uint32_t serial_number, } } -shared_ptr LicenseManager::verify_bb(const string& username, - const string& password) const { +shared_ptr 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 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 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 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 LicenseManager::snapshot() const { - vector ret; - for (auto it : this->serial_number_to_license) { - ret.emplace_back(*it.second); - } - return ret; -} - -shared_ptr LicenseManager::create_license_pc( - uint32_t serial_number, const string& access_key, bool temporary) { - shared_ptr l(new License()); - l->serial_number = serial_number; - l->access_key = access_key; - if (temporary) { - l->privileges |= Privilege::TEMPORARY; - } - return l; -} - -shared_ptr LicenseManager::create_license_gc( - uint32_t serial_number, const string& access_key, const string& password, - bool temporary) { - shared_ptr 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 LicenseManager::create_license_bb( - uint32_t serial_number, const string& username, const string& password, - bool temporary) { - shared_ptr l(new License()); - l->serial_number = serial_number; - l->username = username; - l->bb_password = password; - if (temporary) { - l->privileges |= Privilege::TEMPORARY; - } - return l; -} diff --git a/src/License.hh b/src/License.hh index b7ca72c4..0a1e7e18 100644 --- a/src/License.hh +++ b/src/License.hh @@ -1,96 +1,93 @@ #pragma once #include +#include #include #include #include #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 username; // BB username (max. 16 chars; should technically be Unicode) - ptext 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 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 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 verify_pc(uint32_t serial_number, - const std::string& access_key) const; - std::shared_ptr verify_gc(uint32_t serial_number, - const std::string& access_key) const; - std::shared_ptr verify_gc(uint32_t serial_number, - const std::string& access_key, const std::string& password) const; - std::shared_ptr 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 get(uint32_t serial_number) const; + std::vector> all() const; - std::shared_ptr get(uint32_t serial_number) const; void add(std::shared_ptr l); void remove(uint32_t serial_number); - std::vector snapshot() const; - static std::shared_ptr create_license_pc( - uint32_t serial_number, const std::string& access_key, bool temporary); - static std::shared_ptr create_license_gc( - uint32_t serial_number, const std::string& access_key, - const std::string& password, bool temporary); - static std::shared_ptr create_license_bb( - uint32_t serial_number, const std::string& username, - const std::string& password, bool temporary); + std::shared_ptr verify_v1_v2(uint32_t serial_number, const std::string& access_key) const; + std::shared_ptr verify_gc(uint32_t serial_number, const std::string& access_key) const; + std::shared_ptr verify_gc(uint32_t serial_number, const std::string& access_key, const std::string& password) const; + std::shared_ptr verify_bb(const std::string& username, const std::string& password) const; protected: - std::string filename; bool autosave; std::unordered_map> bb_username_to_license; diff --git a/src/Loggers.cc b/src/Loggers.cc index 292ecf40..f4f8fb61 100644 --- a/src/Loggers.cc +++ b/src/Loggers.cc @@ -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"); diff --git a/src/Loggers.hh b/src/Loggers.hh index e6553490..825542f5 100644 --- a/src/Loggers.hh +++ b/src/Loggers.hh @@ -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; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 45714871..87988249 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -1176,8 +1176,8 @@ static HandlerResult S_G_B7(shared_ptr 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(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, uint16_t, ui static HandlerResult S_G_BA(shared_ptr ses, uint16_t, uint32_t, string& data) { if (ses->options.ep3_infinite_meseta) { - auto& cmd = check_size_t(data); - if (cmd.remaining_meseta != 1000000) { - cmd.remaining_meseta = 1000000; + auto& cmd = check_size_t(data); + if (cmd.current_meseta != 1000000) { + cmd.current_meseta = 1000000; return HandlerResult::Type::MODIFIED; } } diff --git a/src/ProxyServer.cc b/src/ProxyServer.cc index 954749c7..e6103ffb 100644 --- a/src/ProxyServer.cc +++ b/src/ProxyServer.cc @@ -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 license; + shared_ptr 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(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(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(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(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(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 l = LicenseManager::create_license_bb( - fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true); - s->license_manager->add(l); + shared_ptr 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 server, uint16_t local_port, GameVersion version, - shared_ptr license, + shared_ptr 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 server, uint16_t local_port, GameVersion version, - std::shared_ptr license, + std::shared_ptr 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::get_session_by_name( } shared_ptr ProxyServer::create_licensed_session( - shared_ptr l, uint16_t local_port, GameVersion version, + shared_ptr l, uint16_t local_port, GameVersion version, const ClientConfigBB& newserv_client_config) { shared_ptr session(new LinkedSession( this->shared_from_this(), local_port, version, l, newserv_client_config)); diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 8a8ef8ba..eea22437 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -38,7 +38,7 @@ public: std::unique_ptr timeout_event; - std::shared_ptr license; + std::shared_ptr license; Channel client_channel; Channel server_channel; @@ -120,13 +120,13 @@ public: std::shared_ptr server, uint16_t local_port, GameVersion version, - std::shared_ptr license, + std::shared_ptr license, const ClientConfigBB& newserv_client_config); LinkedSession( std::shared_ptr server, uint16_t local_port, GameVersion version, - std::shared_ptr license, + std::shared_ptr license, const struct sockaddr_storage& next_destination); LinkedSession( std::shared_ptr server, @@ -171,7 +171,7 @@ public: std::shared_ptr get_session(); std::shared_ptr get_session_by_name(const std::string& name); std::shared_ptr create_licensed_session( - std::shared_ptr l, + std::shared_ptr l, uint16_t local_port, GameVersion version, const ClientConfigBB& newserv_client_config); diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 7dd75268..26dc5935 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -121,7 +121,7 @@ static bool send_enable_send_function_call_if_applicable(shared_ptr 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 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 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 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 c, uint16_t, uint32_t, const string& uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - shared_ptr l = s->license_manager->verify_pc( - serial_number, cmd.access_key); + shared_ptr 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 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 c, uint16_t, uint32_t, const string& uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - shared_ptr l = s->license_manager->verify_pc( + shared_ptr 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 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 c, uint16_t, uint32_t, const string& dat uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - shared_ptr l = s->license_manager->verify_pc( - serial_number, cmd.access_key); + shared_ptr 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 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 c, uint16_t, uint32_t, const string& dat uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - shared_ptr l = s->license_manager->verify_pc( - serial_number, cmd.access_key); + shared_ptr 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 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 c, uint16_t, uint32_t, const string& data) uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - shared_ptr l; + shared_ptr 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 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 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 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 c, uint16_t, uint32_t, const string& data) uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { - shared_ptr l; + shared_ptr 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 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 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 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 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 c, uint16_t command, uint32_t, const str uint32_t serial_number = stoul(base_cmd->serial_number, nullptr, 16); try { - shared_ptr l; + shared_ptr 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 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 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 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 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 l = LicenseManager::create_license_bb( - fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true); - s->license_manager->add(l); + shared_ptr 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 c, uint16_t, uint32_t, const string& data) } static void on_BA_Ep3(shared_ptr c, uint16_t command, uint32_t, const string& data) { - const auto& in_cmd = check_size_t(data); + const auto& in_cmd = check_size_t(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 c, uint16_t, uint32_t, const string& da auto tourn = l->tournament_match->tournament.lock(); tourn->print_bracket(stderr); + shared_ptr winner_team; + shared_ptr 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 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 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 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 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 l = LicenseManager::create_license_bb( - fnv1a32(cmd.username) & 0x7FFFFFFF, cmd.username, cmd.password, true); - s->license_manager->add(l); + + shared_ptr 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); } } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 964659b3..ce77389e 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -2227,8 +2227,9 @@ void send_ep3_media_update( void send_ep3_rank_update(shared_ptr 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 c) { // TODO: Handle disconnection during the match (the other team should win) } -void send_ep3_tournament_match_result(shared_ptr l) { +void send_ep3_tournament_match_result(shared_ptr 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 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() % 0xFF) + 1; diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 05ba5643..b113648f 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -331,7 +331,7 @@ void send_ep3_tournament_info( std::shared_ptr c, std::shared_ptr t); void send_ep3_set_tournament_player_decks(std::shared_ptr c); -void send_ep3_tournament_match_result(std::shared_ptr l); +void send_ep3_tournament_match_result(std::shared_ptr l, uint32_t meseta_reward); void send_ep3_tournament_details( std::shared_ptr c, diff --git a/src/Server.cc b/src/Server.cc index 8995c056..7a8672d1 100644 --- a/src/Server.cc +++ b/src/Server.cc @@ -306,7 +306,7 @@ vector> 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; } diff --git a/src/ServerShell.cc b/src/ServerShell.cc index 95d34b7e..69260377 100644 --- a/src/ServerShell.cc +++ b/src/ServerShell.cc @@ -134,7 +134,7 @@ Server commands:\n\ gc-password= (GC password)\n\ access-key= (DC/GC/PC access key)\n\ serial= (decimal serial number; required for all licenses)\n\ - privileges= (can be normal, mod, admin, root, or numeric)\n\ + flags= (can be normal, mod, admin, root, or numeric)\n\ update-license SERIAL-NUMBER PARAMETERS...\n\ Update an existing license. specifies which license to\n\ update. The options in 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 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()); } diff --git a/src/ServerState.cc b/src/ServerState.cc index 4fa4c9f8..de977b31 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -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 { + vector 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); } } diff --git a/src/ServerState.hh b/src/ServerState.hh index bef8bfc0..5b1c53b0 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -59,9 +59,13 @@ struct ServerState : public std::enable_shared_from_this { 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 ep3_defeat_player_meseta_rewards; + std::vector 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 { }; std::vector ep3_lobby_banners; - std::shared_ptr license_manager; + std::shared_ptr license_index; std::shared_ptr information_menu_v2; std::shared_ptr information_menu_v3; diff --git a/system/config.example.json b/system/config.example.json index ef528a61..acde970f 100644 --- a/system/config.example.json +++ b/system/config.example.json @@ -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 diff --git a/tests/GC-Episode3Battle.test.txt b/tests/GC-Episode3Battle.test.txt index 5bc7c6ab..d8076eee 100644 --- a/tests/GC-Episode3Battle.test.txt +++ b/tests/GC-Episode3Battle.test.txt @@ -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 diff --git a/tests/GC-Episode3BattleWithSpectator.test.txt b/tests/GC-Episode3BattleWithSpectator.test.txt index fd7ce3b3..16ec76f3 100644 --- a/tests/GC-Episode3BattleWithSpectator.test.txt +++ b/tests/GC-Episode3BattleWithSpectator.test.txt @@ -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 diff --git a/tests/config.json b/tests/config.json index 9776aa47..53d08ae4 100644 --- a/tests/config.json +++ b/tests/config.json @@ -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",