add support for auto-patching
This commit is contained in:
@@ -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)},
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)},
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -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
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user