add support for auto-patching

This commit is contained in:
Martin Michelsen
2024-04-12 22:09:26 -07:00
parent 0b2272bfa7
commit c98d1081a3
8 changed files with 183 additions and 29 deletions
+14
View File
@@ -228,6 +228,13 @@ Account::Account(const JSON& json)
this->ep3_current_meseta = json.get_int("Ep3CurrentMeseta", 0);
this->ep3_total_meseta_earned = json.get_int("Ep3TotalMesetaEarned", 0);
this->bb_team_id = json.get_int("BBTeamID", 0);
try {
for (const auto& it : json.get_list("AutoPatchesEnabled")) {
this->auto_patches_enabled.emplace(it->as_string());
}
} catch (const out_of_range&) {
}
}
JSON Account::json() const {
@@ -255,6 +262,12 @@ JSON Account::json() const {
for (const auto& it : this->bb_licenses) {
bb_json.emplace_back(it.second->json());
}
JSON auto_patches_json = JSON::list();
for (const auto& it : this->auto_patches_enabled) {
auto_patches_json.emplace_back(it);
}
return JSON::dict({
{"FormatVersion", 1},
{"AccountID", this->account_id},
@@ -271,6 +284,7 @@ JSON Account::json() const {
{"Ep3CurrentMeseta", this->ep3_current_meseta},
{"Ep3TotalMesetaEarned", this->ep3_total_meseta_earned},
{"BBTeamID", this->bb_team_id},
{"AutoPatchesEnabled", std::move(auto_patches_json)},
});
}
+2
View File
@@ -90,6 +90,8 @@ struct Account {
uint32_t bb_team_id = 0;
bool is_temporary = false; // If true, isn't saved to disk
std::unordered_set<std::string> auto_patches_enabled;
std::unordered_map<std::string, std::shared_ptr<DCNTELicense>> dc_nte_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> dc_licenses;
std::unordered_map<uint32_t, std::shared_ptr<V1V2License>> pc_licenses;
+1
View File
@@ -61,6 +61,7 @@ public:
HAS_EP3_MEDIA_UPDATES = 0x0000000010000000,
USE_OVERRIDE_RANDOM_SEED = 0x0000000020000000,
HAS_GUILD_CARD_NUMBER = 0x0000000040000000,
HAS_AUTO_PATCHES = 0x0000004000000000,
AT_BANK_COUNTER = 0x0000000080000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ITEM_STATE = 0x0001000000000000, // Server-side only
SHOULD_SEND_ARTIFICIAL_ENEMY_AND_SET_STATE = 0x0040000000000000, // Server-side only
+26 -6
View File
@@ -325,13 +325,33 @@ shared_ptr<const Menu> FunctionCodeIndex::patch_menu(uint32_t specific_version)
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
for (const auto& it : this->name_and_specific_version_to_patch_function) {
const auto& fn = it.second;
if (!fn->hide_from_patches_menu && ends_with(it.first, suffix)) {
ret->items.emplace_back(
fn->menu_item_id,
fn->long_name.empty() ? fn->short_name : fn->long_name,
fn->description,
MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
if (fn->hide_from_patches_menu || !ends_with(it.first, suffix)) {
continue;
}
ret->items.emplace_back(
fn->menu_item_id,
fn->long_name.empty() ? fn->short_name : fn->long_name,
fn->description,
MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
return ret;
}
shared_ptr<const Menu> FunctionCodeIndex::patch_switches_menu(
uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const {
auto suffix = string_printf("-%08" PRIX32, specific_version);
auto ret = make_shared<Menu>(MenuID::PATCH_SWITCHES, "Patch switches");
ret->items.emplace_back(PatchesMenuItemID::GO_BACK, "Go back", "Return to the\nmain menu", 0);
for (const auto& it : this->name_and_specific_version_to_patch_function) {
const auto& fn = it.second;
if (fn->hide_from_patches_menu || !ends_with(it.first, suffix)) {
continue;
}
string name;
name.push_back(auto_patches_enabled.count(fn->short_name) ? '*' : '-');
name += fn->long_name.empty() ? fn->short_name : fn->long_name;
ret->items.emplace_back(fn->menu_item_id, name, fn->description, MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
return ret;
}
+2
View File
@@ -6,6 +6,7 @@
#include <memory>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "Menu.hh"
@@ -69,6 +70,7 @@ struct FunctionCodeIndex {
std::map<std::string, std::shared_ptr<CompiledFunctionCode>> name_and_specific_version_to_patch_function;
std::shared_ptr<const Menu> patch_menu(uint32_t specific_version) const;
std::shared_ptr<const Menu> patch_switches_menu(uint32_t specific_version, const std::unordered_set<std::string>& auto_patches_enabled) const;
bool patch_menu_empty(uint32_t specific_version) const;
std::shared_ptr<const CompiledFunctionCode> get_patch(const std::string& name, uint32_t specific_version) const;
+5
View File
@@ -291,6 +291,10 @@ JSON HTTPServer::generate_account_json_st(shared_ptr<const Account> a) {
for (const auto& it : a->bb_licenses) {
bb_licenses_json.emplace_back(it.first);
}
auto auto_patches_json = JSON::list();
for (const auto& it : a->auto_patches_enabled) {
auto_patches_json.emplace_back(it);
}
return JSON::dict({
{"AccountID", a->account_id},
{"Flags", a->flags},
@@ -307,6 +311,7 @@ JSON HTTPServer::generate_account_json_st(shared_ptr<const Account> a) {
{"GCLicenses", std::move(gc_licenses_json)},
{"XBLicenses", std::move(xb_licenses_json)},
{"BBLicenses", std::move(bb_licenses_json)},
{"AutoPatchesEnabled", std::move(auto_patches_json)},
});
};
+2
View File
@@ -24,6 +24,7 @@ constexpr uint32_t QUEST_CATEGORIES = 0x66010166;
constexpr uint32_t PROXY_DESTINATIONS = 0x77000077;
constexpr uint32_t PROGRAMS = 0x88000088;
constexpr uint32_t PATCHES = 0x99000099;
constexpr uint32_t PATCH_SWITCHES = 0x99010199;
constexpr uint32_t PROXY_OPTIONS = 0xAA0000AA;
constexpr uint32_t TOURNAMENTS = 0xBB0000BB;
constexpr uint32_t TOURNAMENTS_FOR_SPEC = 0xBB1111BB;
@@ -36,6 +37,7 @@ constexpr uint32_t INFORMATION = 0x11333311;
constexpr uint32_t DOWNLOAD_QUESTS = 0x11444411;
constexpr uint32_t PROXY_DESTINATIONS = 0x11555511;
constexpr uint32_t PATCHES = 0x11666611;
constexpr uint32_t PATCH_SWITCHES = 0x11676711;
constexpr uint32_t PROGRAMS = 0x11777711;
constexpr uint32_t DISCONNECT = 0x11888811;
constexpr uint32_t CLEAR_LICENSE = 0x11999911;
+131 -23
View File
@@ -118,6 +118,63 @@ static shared_ptr<const Menu> proxy_options_menu_for_client(shared_ptr<const Cli
return ret;
}
void send_first_pre_lobby_commands(shared_ptr<Client> c, std::function<void()> on_complete) {
// TODO: This function is bad. Ideally we would use coroutines and clean up
// all these terrible callbacks.
if (function_compiler_available() &&
!c->config.check_flag(Client::Flag::HAS_AUTO_PATCHES) &&
!c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) {
prepare_client_for_patches(c, [wc = weak_ptr<Client>(c), on_complete = std::move(on_complete)]() -> void {
auto c = wc.lock();
if (!c) {
return;
}
auto s = c->require_server_state();
size_t num_patches_sent = 0;
for (const auto& patch_name : c->login->account->auto_patches_enabled) {
try {
send_function_call(c, s->function_code_index->get_patch(patch_name, c->config.specific_version));
num_patches_sent++;
} catch (const out_of_range&) {
c->log.warning("Client has auto patch %s enabled, but it is not available for specific_version %08" PRIX32,
patch_name.c_str(), c->config.specific_version);
}
}
// We can't just blast all the commands at the client, since the sequence
// ends with a reconnect command, which changes the encryption state! We
// have to wait until all the patch responses have been received before
// sending any command that the client will respond to. It's OK to send
// the 04 immediately before the 19 (since the client does not respond to
// 04), but it is not OK to send the B2s without waiting.
if (num_patches_sent == 0) {
c->config.set_flag(Client::Flag::HAS_AUTO_PATCHES);
send_update_client_config(c, false);
on_complete();
} else {
while (c->function_call_response_queue.size() < num_patches_sent - 1) {
c->function_call_response_queue.emplace_back(empty_function_call_response_handler);
}
c->function_call_response_queue.emplace_back([wc, on_complete = std::move(on_complete)](uint32_t, uint32_t) {
auto c = wc.lock();
if (!c) {
return;
}
c->config.set_flag(Client::Flag::HAS_AUTO_PATCHES);
send_update_client_config(c, false);
on_complete();
});
}
});
} else {
on_complete();
}
}
void send_client_to_login_server(shared_ptr<Client> c) {
string port_name = login_port_name_for_version(c->version());
auto s = c->require_server_state();
@@ -125,35 +182,49 @@ void send_client_to_login_server(shared_ptr<Client> c) {
}
void send_client_to_lobby_server(shared_ptr<Client> c) {
auto s = c->require_server_state();
string port_name = lobby_port_name_for_version(c->version());
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
send_first_pre_lobby_commands(c, [wc = weak_ptr(c)]() {
auto c = wc.lock();
if (!c) {
return;
}
auto s = c->require_server_state();
string port_name = lobby_port_name_for_version(c->version());
send_reconnect(c, s->connect_address_for_client(c),
s->name_to_port_config.at(port_name)->port);
});
}
void send_client_to_proxy_server(shared_ptr<Client> c) {
auto s = c->require_server_state();
send_first_pre_lobby_commands(c, [wc = weak_ptr(c)]() {
auto c = wc.lock();
if (!c) {
return;
}
string port_name = proxy_port_name_for_version(c->version());
uint16_t local_port = s->name_to_port_config.at(port_name)->port;
auto s = c->require_server_state();
s->proxy_server->delete_session(c->login->account->account_id);
auto ses = s->proxy_server->create_logged_in_session(c->login, local_port, c->version(), c->config);
if (!c->can_use_chat_commands()) {
ses->config.clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED);
}
if (!s->proxy_allow_save_files) {
ses->config.clear_flag(Client::Flag::PROXY_SAVE_FILES);
}
if (!s->proxy_enable_login_options) {
ses->config.clear_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN);
ses->config.clear_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD);
}
if (ses->config.check_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD)) {
ses->remote_guild_card_number = 0;
}
string port_name = proxy_port_name_for_version(c->version());
uint16_t local_port = s->name_to_port_config.at(port_name)->port;
send_reconnect(c, s->connect_address_for_client(c), local_port);
s->proxy_server->delete_session(c->login->account->account_id);
auto ses = s->proxy_server->create_logged_in_session(c->login, local_port, c->version(), c->config);
if (!c->can_use_chat_commands()) {
ses->config.clear_flag(Client::Flag::PROXY_CHAT_COMMANDS_ENABLED);
}
if (!s->proxy_allow_save_files) {
ses->config.clear_flag(Client::Flag::PROXY_SAVE_FILES);
}
if (!s->proxy_enable_login_options) {
ses->config.clear_flag(Client::Flag::PROXY_SUPPRESS_REMOTE_LOGIN);
ses->config.clear_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD);
}
if (ses->config.check_flag(Client::Flag::PROXY_ZERO_REMOTE_GUILD_CARD)) {
ses->remote_guild_card_number = 0;
}
send_reconnect(c, s->connect_address_for_client(c), local_port);
});
}
static void send_proxy_destinations_menu(shared_ptr<Client> c) {
@@ -267,6 +338,8 @@ static void send_main_menu(shared_ptr<Client> c) {
if (!s->function_code_index->patch_menu_empty(c->config.specific_version)) {
main_menu->items.emplace_back(MainMenuItemID::PATCHES, "Patches",
"Change game\nbehaviors", MenuItem::Flag::INVISIBLE_ON_DC | MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
main_menu->items.emplace_back(MainMenuItemID::PATCH_SWITCHES, "Patch switches",
"Automatically\napply patches every\ntime you connect", MenuItem::Flag::INVISIBLE_ON_DC | MenuItem::Flag::INVISIBLE_ON_PC | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL);
}
if (!s->dol_file_index->empty()) {
main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, "Programs",
@@ -2041,6 +2114,21 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
});
break;
case MainMenuItemID::PATCH_SWITCHES:
if (!function_compiler_available()) {
throw runtime_error("function compiler not available");
}
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) {
throw runtime_error("client does not support send_function_call");
}
// We have to prepare the client for patches here, even though we
// don't send them from this mennu, because we need to know the
// client's specific_version before sending the menu.
prepare_client_for_patches(c, [c]() -> void {
send_menu(c, c->require_server_state()->function_code_index->patch_switches_menu(c->config.specific_version, c->login->account->auto_patches_enabled));
});
break;
case MainMenuItemID::PROGRAMS:
if (!function_compiler_available()) {
throw runtime_error("function compiler not available");
@@ -2407,6 +2495,26 @@ static void on_10(shared_ptr<Client> c, uint16_t, uint32_t, string& data) {
}
break;
case MenuID::PATCH_SWITCHES:
if (item_id == PatchesMenuItemID::GO_BACK) {
send_main_menu(c);
} else {
if (c->config.check_flag(Client::Flag::NO_SEND_FUNCTION_CALL)) {
throw runtime_error("client does not support send_function_call");
}
auto s = c->require_server_state();
uint64_t key = (static_cast<uint64_t>(item_id) << 32) | c->config.specific_version;
auto fn = s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key);
if (!c->login->account->auto_patches_enabled.emplace(fn->short_name).second) {
c->login->account->auto_patches_enabled.erase(fn->short_name);
}
c->login->account->save();
send_menu(c, s->function_code_index->patch_switches_menu(c->config.specific_version, c->login->account->auto_patches_enabled));
}
break;
case MenuID::PROGRAMS:
if (item_id == ProgramsMenuItemID::GO_BACK) {
send_main_menu(c);