diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 6793df70..dde1c41e 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -260,27 +260,21 @@ static void proxy_command_auction(shared_ptr, static void server_command_patch(shared_ptr s, shared_ptr, shared_ptr c, const std::u16string& args) { string basename = encode_sjis(args); - auto send_call = [s, basename, wc = weak_ptr(c)]() { - auto c = wc.lock(); - if (!c) { - return; - } - try { + try { + prepare_client_for_patches(s, c, [s, wc = weak_ptr(c), basename]() { + auto c = wc.lock(); + if (!c) { + return; + } + // Note: We can't look this up outside of the closure because + // c->specific_version can change during prepare_client_for_patches auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at( string_printf("%s-%08" PRIX32, basename.c_str(), c->specific_version)); send_function_call(c, fn); - } catch (const out_of_range&) { - send_text_message(c, u"Invalid patch name"); - } - }; - - send_cache_patch_if_needed(s, c); - if (c->version() == GameVersion::GC && - c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { - send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect")); - c->on_version_detect_response = send_call; - } else { - send_call(); + c->function_call_response_queue.emplace_back(empty_function_call_response_handler); + }); + } catch (const out_of_range&) { + send_text_message(c, u"Invalid patch name"); } } @@ -307,26 +301,33 @@ static void proxy_command_patch(shared_ptr s, } }; - // This mirrors the implementation in send_cache_patch_if_needed + auto send_version_detect_or_send_call = [s, basename, &session, send_call]() { + if (session.version == GameVersion::GC && + session.newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + send_function_call( + session.client_channel, + session.newserv_client_config.cfg.flags, + s->function_code_index->name_to_function.at("VersionDetect")); + session.function_call_return_handler_queue.emplace_back(send_call); + } else { + send_call(session.newserv_client_config.cfg.specific_version, 0); + } + }; + + // This mirrors the implementation in prepare_client_for_patches if (!(session.newserv_client_config.cfg.flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { send_function_call( - session.client_channel, session.newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2634EC); - send_function_call( - session.client_channel, session.newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); - session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler); - session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler); - session.newserv_client_config.cfg.flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - } - - if (session.version == GameVersion::GC && - session.newserv_client_config.cfg.specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { - send_function_call( - session.client_channel, - session.newserv_client_config.cfg.flags, - s->function_code_index->name_to_function.at("VersionDetect")); - session.function_call_return_handler_queue.emplace_back(send_call); + session.client_channel, session.newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2734EC); + session.function_call_return_handler_queue.emplace_back([s, session_p = &session, send_version_detect_or_send_call](uint32_t, uint32_t) -> void { + send_function_call( + session_p->client_channel, session_p->newserv_client_config.cfg.flags, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); + session_p->function_call_return_handler_queue.emplace_back([session_p, send_version_detect_or_send_call](uint32_t, uint32_t) -> void { + session_p->newserv_client_config.cfg.flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; + send_version_detect_or_send_call(); + }); + }); } else { - send_call(session.newserv_client_config.cfg.specific_version, 0); + send_version_detect_or_send_call(); } } diff --git a/src/Client.hh b/src/Client.hh index e1d85896..d44619b6 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -156,7 +156,7 @@ struct Client { bool can_chat; std::string pending_bb_save_username; uint8_t pending_bb_save_player_index; - std::function on_version_detect_response; + std::deque> function_call_response_queue; // File loading state uint32_t dol_base_addr; diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index 4da02cea..e45f6671 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -72,6 +72,13 @@ string CompiledFunctionCode::generate_client_command_t( w.put_u8(0); } + footer.relocations_offset = w.size(); + + // Always write at least 4 bytes even if there are no relocations + if (this->relocation_deltas.empty()) { + w.put_u32(0); + } + if (override_relocations_offset) { footer.relocations_offset = override_relocations_offset; } else { diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index 79c81950..f353582b 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -1734,19 +1734,9 @@ static void on_10(shared_ptr s, shared_ptr c, if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { throw runtime_error("client does not support send_function_call"); } - send_cache_patch_if_needed(s, c); - if (c->version() == GameVersion::GC && - c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { - send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect")); - c->on_version_detect_response = [s, wc = weak_ptr(c)]() { - auto c = wc.lock(); - if (c) { - send_menu(c, s->function_code_index->patch_menu(c->specific_version)); - } - }; - } else { + prepare_client_for_patches(s, c, [s, c]() -> void { send_menu(c, s->function_code_index->patch_menu(c->specific_version)); - } + }); break; case MainMenuItemID::PROGRAMS: @@ -1756,8 +1746,9 @@ static void on_10(shared_ptr s, shared_ptr c, if (c->flags & Client::Flag::NO_SEND_FUNCTION_CALL) { throw runtime_error("client does not support send_function_call"); } - send_cache_patch_if_needed(s, c); - send_menu(c, s->dol_file_index->menu); + prepare_client_for_patches(s, c, [s, c]() -> void { + send_menu(c, s->dol_file_index->menu); + }); break; case MainMenuItemID::DISCONNECT: @@ -2071,6 +2062,7 @@ static void on_10(shared_ptr s, shared_ptr c, uint64_t key = (static_cast(item_id) << 32) | c->specific_version; send_function_call( c, s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key)); + c->function_call_response_queue.emplace_back(empty_function_call_response_handler); send_menu(c, s->function_code_index->patch_menu(c->specific_version)); } break; @@ -2287,22 +2279,13 @@ static void send_dol_file_chunk(shared_ptr s, shared_ptr c, static void on_B3(shared_ptr s, shared_ptr c, uint16_t, uint32_t flag, const string& data) { const auto& cmd = check_size_t(data); - if (flag == 0) { - return; - } - - auto called_fn = s->function_code_index->index_to_function.at(flag); - if (called_fn->name == "VersionDetect") { - // This is sent the first time the client chooses Patches from the main - // menu, so send the Patches menu when we get the response here - c->specific_version = cmd.return_value; - c->log.info("Version detected as %08" PRIX32, c->specific_version); - if (c->on_version_detect_response) { - c->on_version_detect_response(); - c->on_version_detect_response = nullptr; - } + if (!c->function_call_response_queue.empty()) { + auto& handler = c->function_call_response_queue.front(); + handler(cmd.return_value, cmd.checksum); + c->function_call_response_queue.pop_front(); } else if (c->loading_dol_file.get()) { + auto called_fn = s->function_code_index->index_to_function.at(flag); if (called_fn->name == "ReadMemoryWord") { c->dol_base_addr = (cmd.return_value - c->loading_dol_file->data.size()) & (~3); send_dol_file_chunk(s, c, c->dol_base_addr); @@ -2318,7 +2301,11 @@ static void on_B3(shared_ptr s, shared_ptr c, } else { send_dol_file_chunk(s, c, cmd.return_value); } + } else { + throw logic_error("unknown function called during DOL loading"); } + } else { + throw runtime_error("function call response queue is empty, and no program is being sent"); } } diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 3bc8e86a..6ff5e14b 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -311,14 +311,48 @@ void send_quest_buffer_overflow( send_command_t(c, 0xA7, 0x00, cmd); } -void send_cache_patch_if_needed(shared_ptr s, shared_ptr c) { +void empty_function_call_response_handler(uint32_t, uint32_t) {} + +void prepare_client_for_patches(shared_ptr s, shared_ptr c, std::function on_complete) { + auto send_version_detect = [s, wc = weak_ptr(c), on_complete]() -> void { + auto c = wc.lock(); + if (!c) { + return; + } + if (c->version() == GameVersion::GC && + c->specific_version == default_specific_version_for_version(GameVersion::GC, -1)) { + send_function_call(c, s->function_code_index->name_to_function.at("VersionDetect")); + c->function_call_response_queue.emplace_back([s, c, on_complete](uint32_t specific_version, uint32_t) -> void { + c->specific_version = specific_version; + c->log.info("Version detected as %08" PRIX32, c->specific_version); + on_complete(); + }); + } else { + on_complete(); + } + }; + if (!(c->flags & Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH)) { - send_function_call( - c, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2634EC); - send_function_call( - c, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); - c->flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; - send_update_client_config(c); + send_function_call(c, s->function_code_index->name_to_function.at("CacheClearFix-Phase1"), {}, "", 0, 0, 0x7F2734EC); + c->function_call_response_queue.emplace_back([s, wc = weak_ptr(c), send_version_detect](uint32_t, uint32_t) -> void { + auto c = wc.lock(); + if (!c) { + return; + } + send_function_call(c, s->function_code_index->name_to_function.at("CacheClearFix-Phase2")); + c->function_call_response_queue.emplace_back([s, wc = weak_ptr(c), send_version_detect](uint32_t, uint32_t) -> void { + auto c = wc.lock(); + if (!c) { + return; + } + c->log.info("Client cache behavior patched"); + c->flags |= Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; + send_update_client_config(c); + send_version_detect(); + }); + }); + } else { + send_version_detect(); } } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 7f413a07..087ce501 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -130,10 +130,12 @@ void send_server_init( uint8_t flags); void send_update_client_config(std::shared_ptr c); +void empty_function_call_response_handler(uint32_t, uint32_t); + void send_quest_buffer_overflow( std::shared_ptr s, std::shared_ptr c); -void send_cache_patch_if_needed(std::shared_ptr s, std::shared_ptr c); -uint32_t send_cache_patch_if_needed(std::shared_ptr s, Channel& c, uint32_t flags); +void prepare_client_for_patches( + std::shared_ptr s, std::shared_ptr c, std::function on_complete); void send_function_call( Channel& ch, uint64_t client_flags,