From dbd6c59a0b5752b6436de57daba7124ea5c93a55 Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Fri, 26 May 2023 09:55:12 -0700 Subject: [PATCH] implement version-specific patches; clean up menu abstraction --- src/ChatCommands.cc | 76 +++- src/Client.cc | 10 +- src/Client.hh | 10 +- src/CommandFormats.hh | 3 +- src/FunctionCompiler.cc | 66 +++- src/FunctionCompiler.hh | 16 +- src/Menu.cc | 14 + src/Menu.hh | 37 +- src/ProxyCommands.cc | 38 +- src/ProxyServer.hh | 3 +- src/ReceiveCommands.cc | 339 +++++++++--------- src/SendCommands.cc | 35 +- src/SendCommands.hh | 5 +- src/ServerState.cc | 99 ++--- src/ServerState.hh | 17 +- src/Version.cc | 59 ++- src/Version.hh | 12 +- ...ards.patch.s => Ep3-AllCards.3SE0.patch.s} | 2 - ...itors.patch.s => Ep3-Editors.3SE0.patch.s} | 5 - ...PCard.patch.s => Ep3-VIPCard.3SE0.patch.s} | 5 - system/ppc/InitClearCaches.inc.s | 18 - system/ppc/ReadMemoryWord.s | 2 - system/ppc/RunDOL.s | 1 - system/ppc/VersionDetect.s | 30 ++ system/ppc/WriteMemory.s | 40 ++- 25 files changed, 548 insertions(+), 394 deletions(-) rename system/ppc/{Ep3-AllCards.patch.s => Ep3-AllCards.3SE0.patch.s} (98%) rename system/ppc/{Ep3-Editors.patch.s => Ep3-Editors.3SE0.patch.s} (97%) rename system/ppc/{Ep3-VIPCard.patch.s => Ep3-VIPCard.3SE0.patch.s} (69%) delete mode 100644 system/ppc/InitClearCaches.inc.s create mode 100644 system/ppc/VersionDetect.s diff --git a/src/ChatCommands.cc b/src/ChatCommands.cc index 7599e4cd..6793df70 100644 --- a/src/ChatCommands.cc +++ b/src/ChatCommands.cc @@ -259,26 +259,74 @@ static void proxy_command_auction(shared_ptr, static void server_command_patch(shared_ptr s, shared_ptr, shared_ptr c, const std::u16string& args) { - std::shared_ptr fn; - try { - fn = s->function_code_index->name_to_function.at(encode_sjis(args)); - send_function_call(c, fn); - } catch (const out_of_range&) { - send_text_message(c, u"Invalid patch name"); + string basename = encode_sjis(args); + auto send_call = [s, basename, wc = weak_ptr(c)]() { + auto c = wc.lock(); + if (!c) { + return; + } + try { + 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(); } } +static void empty_patch_return_handler(uint32_t, uint32_t) {} + static void proxy_command_patch(shared_ptr s, ProxyServer::LinkedSession& session, const std::u16string& args) { - std::shared_ptr fn; - try { - fn = s->function_code_index->name_to_function.at(encode_sjis(args)); + + string basename = encode_sjis(args); + auto send_call = [s, basename, &session](uint32_t specific_version, uint32_t) { + try { + if (session.newserv_client_config.cfg.specific_version != specific_version) { + session.newserv_client_config.cfg.specific_version = specific_version; + session.log.info("Version detected as %08" PRIX32, session.newserv_client_config.cfg.specific_version); + } + auto fn = s->function_code_index->name_and_specific_version_to_patch_function.at( + string_printf("%s-%08" PRIX32, basename.c_str(), session.newserv_client_config.cfg.specific_version)); + send_function_call( + session.client_channel, session.newserv_client_config.cfg.flags, fn); + // Don't forward the patch response to the server + session.function_call_return_handler_queue.emplace_back(empty_patch_return_handler); + } catch (const out_of_range&) { + send_text_message(session.client_channel, u"Invalid patch name"); + } + }; + + // This mirrors the implementation in send_cache_patch_if_needed + 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, fn); - // Don't forward the patch response to the server - session.should_forward_function_call_return_queue.emplace_back(false); - } catch (const out_of_range&) { - send_text_message(session.client_channel, u"Invalid patch name"); + 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); + } else { + send_call(session.newserv_client_config.cfg.specific_version, 0); } } diff --git a/src/Client.cc b/src/Client.cc index 38ee682b..1f880ee3 100644 --- a/src/Client.cc +++ b/src/Client.cc @@ -33,6 +33,7 @@ ClientOptions::ClientOptions() enable_chat_commands(true), enable_chat_filter(true), enable_player_notifications(false), + suppress_client_pings(false), suppress_remote_login(false), zero_remote_guild_card(false), ep3_infinite_meseta(false), @@ -45,9 +46,10 @@ Client::Client( GameVersion version, ServerBehavior server_behavior) : id(next_id++), - log("", client_log.min_level), + log(string_printf("[C-%" PRIX64 "] ", this->id), client_log.min_level), bb_game_state(0), flags(flags_for_version(version, -1)), + specific_version(default_specific_version_for_version(version, -1)), channel(bev, version, nullptr, nullptr, this, string_printf("C-%" PRIX64, this->id), TerminalFormat::FG_YELLOW, TerminalFormat::FG_GREEN), server_behavior(server_behavior), should_disconnect(false), @@ -81,6 +83,8 @@ Client::Client( struct timeval tv = usecs_to_timeval(60000000); // 1 minute event_add(this->save_game_data_event.get(), &tv); } + + this->log.info("Created"); } Client::~Client() { @@ -90,6 +94,8 @@ Client::~Client() { this->log.warning(" %s", it.first.c_str()); } } + + this->log.info("Deleted"); } void Client::set_license(shared_ptr l) { @@ -104,6 +110,7 @@ ClientConfig Client::export_config() const { ClientConfig cc; cc.magic = CLIENT_CONFIG_MAGIC; cc.flags = this->flags; + cc.specific_version = this->specific_version; cc.proxy_destination_address = this->proxy_destination_address; cc.proxy_destination_port = this->proxy_destination_port; cc.unused.clear(0xFF); @@ -124,6 +131,7 @@ void Client::import_config(const ClientConfig& cc) { throw invalid_argument("invalid client config"); } this->flags = cc.flags; + this->specific_version = cc.specific_version; this->proxy_destination_address = cc.proxy_destination_address; this->proxy_destination_port = cc.proxy_destination_port; } diff --git a/src/Client.hh b/src/Client.hh index 65796c49..fa550cd7 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -35,6 +35,7 @@ struct ClientOptions { bool enable_chat_commands; bool enable_chat_filter; bool enable_player_notifications; + bool suppress_client_pings; bool suppress_remote_login; bool zero_remote_guild_card; bool ep3_infinite_meseta; @@ -75,9 +76,9 @@ struct Client { ENCRYPTED_SEND_FUNCTION_CALL = 0x00000800, // Client supports send_function_call but does not actually run the code SEND_FUNCTION_CALL_CHECKSUM_ONLY = 0x00001000, - // Client supports send_function_call but doesn't clear its caches properly - // before calling the function (so we need to patch it) - SEND_FUNCTION_CALL_NEEDS_CACHE_PATCH = 0x00020000, + // Client supports send_function_call and clears its caches properly before + // calling the function (so we don't need to patch it) + SEND_FUNCTION_CALL_NO_CACHE_PATCH = 0x00020000, // Client is vulnerable to a buffer overflow that we can use to enable // send_function_call USE_OVERFLOW_FOR_SEND_FUNCTION_CALL = 0x00008000, @@ -111,6 +112,7 @@ struct Client { // all of that space. uint8_t bb_game_state; uint32_t flags; + uint32_t specific_version; // Network Channel channel; @@ -141,6 +143,7 @@ struct Client { uint16_t card_battle_table_seat_number; uint16_t card_battle_table_seat_state; std::weak_ptr ep3_tournament_team; + std::shared_ptr last_menu_sent; // Miscellaneous (used by chat commands) uint32_t next_exp_value; // next EXP value to give @@ -148,6 +151,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; // File loading state uint32_t dol_base_addr; diff --git a/src/CommandFormats.hh b/src/CommandFormats.hh index d8c6880a..3daf968f 100644 --- a/src/CommandFormats.hh +++ b/src/CommandFormats.hh @@ -108,9 +108,10 @@ enum ClientStateBB : uint8_t { struct ClientConfig { uint64_t magic; uint32_t flags; + uint32_t specific_version; uint32_t proxy_destination_address; uint16_t proxy_destination_port; - parray unused; + parray unused; } __packed__; struct ClientConfigBB { diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index f71f60c6..0d52ccc8 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -176,6 +176,20 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { bool is_patch = ends_with(filename, ".patch.s"); string name = filename.substr(0, filename.size() - (is_patch ? 8 : 2)); + // Check for specific_version token + uint32_t specific_version = 0; + string patch_name = name; + if (is_patch && + (filename.size() >= 13) && + (filename[filename.size() - 13] == '.') && + isdigit(filename[filename.size() - 12]) && + (filename[filename.size() - 11] == 'O' || filename[filename.size() - 11] == 'S') && + (filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') && + isdigit(filename[filename.size() - 9])) { + specific_version = 0x33000000 | (filename[filename.size() - 11] << 16) | (filename[filename.size() - 10] << 8) | filename[filename.size() - 9]; + patch_name = filename.substr(0, filename.size() - 13); + } + try { string path = directory + "/" + filename; string text = load_file(path); @@ -187,15 +201,19 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { "duplicate function index: %08" PRIX32, code->index)); } } + code->specific_version = specific_version; + code->patch_name = patch_name; this->name_to_function.emplace(name, code); if (is_patch) { code->menu_item_id = next_menu_item_id++; - this->menu_item_id_to_patch_function.emplace(code->menu_item_id, code); - this->name_to_patch_function.emplace(name, code); + this->menu_item_id_and_specific_version_to_patch_function.emplace( + static_cast(code->menu_item_id) << 32 | specific_version, code); + this->name_and_specific_version_to_patch_function.emplace( + string_printf("%s-%08" PRIX32, patch_name.c_str(), specific_version), code); } string index_prefix = code->index ? string_printf("%02X => ", code->index) : ""; - string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "] ", code->menu_item_id) : ""; + string patch_prefix = is_patch ? string_printf("[%08" PRIX32 "/%08" PRIX32 "] ", code->menu_item_id, code->specific_version) : ""; function_compiler_log.info("Compiled function %s%s%s (%s)", index_prefix.c_str(), patch_prefix.c_str(), name.c_str(), name_for_architecture(code->arch)); @@ -205,19 +223,30 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { } } -vector FunctionCodeIndex::patch_menu() const { - vector ret; - ret.emplace_back(PatchesMenuItemID::GO_BACK, u"Go back", u"", 0); - for (const auto& it : this->name_to_patch_function) { +shared_ptr FunctionCodeIndex::patch_menu(uint32_t specific_version) const { + auto suffix = string_printf("-%08" PRIX32, specific_version); + + shared_ptr ret(new Menu(MenuID::PATCHES, u"Patches")); + ret->items.emplace_back(PatchesMenuItemID::GO_BACK, u"Go back", u"", 0); + for (const auto& it : this->name_and_specific_version_to_patch_function) { const auto& fn = it.second; - if (!fn->hide_from_patches_menu) { - ret.emplace_back(fn->menu_item_id, decode_sjis(fn->name), u"", + if (!fn->hide_from_patches_menu && ends_with(it.first, suffix)) { + ret->items.emplace_back(fn->menu_item_id, decode_sjis(fn->patch_name), u"", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); } } return ret; } +bool FunctionCodeIndex::patch_menu_empty(uint32_t specific_version) const { + for (const auto& it : this->menu_item_id_and_specific_version_to_patch_function) { + if ((it.first & 0xFF000000) == (specific_version & 0xFF000000)) { + return false; + } + } + return true; +} + DOLFileIndex::DOLFileIndex(const string& directory) { if (!function_compiler_available()) { function_compiler_log.info("Function compiler is not available"); @@ -228,6 +257,10 @@ DOLFileIndex::DOLFileIndex(const string& directory) { return; } + shared_ptr menu(new Menu(MenuID::PROGRAMS, u"Programs")); + this->menu = menu; + menu->items.emplace_back(ProgramsMenuItemID::GO_BACK, u"Go back", u"", 0); + uint32_t next_menu_item_id = 0; for (const auto& filename : list_directory(directory)) { if (!ends_with(filename, ".dol")) { @@ -245,6 +278,11 @@ DOLFileIndex::DOLFileIndex(const string& directory) { this->name_to_file.emplace(dol->name, dol); this->item_id_to_file.emplace_back(dol); + + string size_str = format_size(dol->data.size()); + string description = string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str()); + menu->items.emplace_back(dol->menu_item_id, decode_sjis(dol->name), + decode_sjis(description), MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); function_compiler_log.info("Loaded DOL file %s", filename.c_str()); } catch (const exception& e) { @@ -252,13 +290,3 @@ DOLFileIndex::DOLFileIndex(const string& directory) { } } } - -vector DOLFileIndex::menu() const { - vector ret; - ret.emplace_back(ProgramsMenuItemID::GO_BACK, u"Go back", u"", 0); - for (const auto& dol : this->item_id_to_file) { - ret.emplace_back(dol->menu_item_id, decode_sjis(dol->name), u"", - MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); - } - return ret; -} diff --git a/src/FunctionCompiler.hh b/src/FunctionCompiler.hh index b0b2c579..e6f5524f 100644 --- a/src/FunctionCompiler.hh +++ b/src/FunctionCompiler.hh @@ -28,9 +28,11 @@ struct CompiledFunctionCode { std::unordered_map label_offsets; uint32_t entrypoint_offset_offset; std::string name; + std::string patch_name; // Blank if not a patch uint32_t index; // 0 = unused (not registered in index_to_function) uint32_t menu_item_id; bool hide_from_patches_menu; + uint32_t specific_version; bool is_big_endian() const; @@ -59,14 +61,12 @@ struct FunctionCodeIndex { std::unordered_map> name_to_function; std::unordered_map> index_to_function; - std::unordered_map> menu_item_id_to_patch_function; + std::unordered_map> menu_item_id_and_specific_version_to_patch_function; + // Key here is e.g. "PATCHNAME-SPECIFICVERSION", with the latter in hex + std::map> name_and_specific_version_to_patch_function; - std::map> name_to_patch_function; - - std::vector patch_menu() const; - inline bool patch_menu_empty() const { - return this->name_to_patch_function.empty(); - } + std::shared_ptr patch_menu(uint32_t specific_version) const; + bool patch_menu_empty(uint32_t specific_version) const; }; struct DOLFileIndex { @@ -78,11 +78,11 @@ struct DOLFileIndex { std::vector> item_id_to_file; std::map> name_to_file; + std::shared_ptr menu; DOLFileIndex() = default; explicit DOLFileIndex(const std::string& directory); - std::vector menu() const; inline bool empty() const { return this->name_to_file.empty() && this->item_id_to_file.empty(); } diff --git a/src/Menu.cc b/src/Menu.cc index f548a740..5d1c6bb2 100644 --- a/src/Menu.cc +++ b/src/Menu.cc @@ -8,4 +8,18 @@ MenuItem::MenuItem( : item_id(item_id), name(name), description(description), + get_description(nullptr), flags(flags) {} + +MenuItem::MenuItem( + uint32_t item_id, const u16string& name, + std::function get_description, uint32_t flags) + : item_id(item_id), + name(name), + description(), + get_description(std::move(get_description)), + flags(flags) {} + +Menu::Menu(uint32_t menu_id, const std::u16string& name) + : menu_id(menu_id), + name(name) {} diff --git a/src/Menu.hh b/src/Menu.hh index b33bb757..3c7d7936 100644 --- a/src/Menu.hh +++ b/src/Menu.hh @@ -2,7 +2,9 @@ #include +#include #include +#include // Note: These aren't enums because neither enum nor enum class does what we // want. Specifically, we need GO_BACK to be valid in multiple enums (and enums @@ -59,17 +61,18 @@ constexpr uint32_t GO_BACK = 0xAAFFFFAA; constexpr uint32_t CHAT_COMMANDS = 0xAA0000AA; constexpr uint32_t CHAT_FILTER = 0xAA1111AA; constexpr uint32_t PLAYER_NOTIFICATIONS = 0xAA2222AA; -constexpr uint32_t INFINITE_HP = 0xAA3333AA; -constexpr uint32_t INFINITE_TP = 0xAA4444AA; -constexpr uint32_t SWITCH_ASSIST = 0xAA5555AA; -constexpr uint32_t BLOCK_EVENTS = 0xAA6666AA; -constexpr uint32_t BLOCK_PATCHES = 0xAA7777AA; -constexpr uint32_t SAVE_FILES = 0xAA8888AA; -constexpr uint32_t RED_NAME = 0xAA9999AA; -constexpr uint32_t BLANK_NAME = 0xAAAAAAAA; -constexpr uint32_t SUPPRESS_LOGIN = 0xAABBBBAA; -constexpr uint32_t SKIP_CARD = 0xAACCCCAA; -constexpr uint32_t EP3_INFINITE_MESETA = 0xAADDDDAA; +constexpr uint32_t BLOCK_PINGS = 0xAA3333AA; +constexpr uint32_t INFINITE_HP = 0xAA4444AA; +constexpr uint32_t INFINITE_TP = 0xAA5555AA; +constexpr uint32_t SWITCH_ASSIST = 0xAA6666AA; +constexpr uint32_t BLOCK_EVENTS = 0xAA7777AA; +constexpr uint32_t BLOCK_PATCHES = 0xAA8888AA; +constexpr uint32_t SAVE_FILES = 0xAA9999AA; +constexpr uint32_t RED_NAME = 0xAAAAAAAA; +constexpr uint32_t BLANK_NAME = 0xAABBBBAA; +constexpr uint32_t SUPPRESS_LOGIN = 0xAACCCCAA; +constexpr uint32_t SKIP_CARD = 0xAADDDDAA; +constexpr uint32_t EP3_INFINITE_MESETA = 0xAAEEEEAA; } // namespace ProxyOptionsMenuItemID struct MenuItem { @@ -96,8 +99,20 @@ struct MenuItem { uint32_t item_id; std::u16string name; std::u16string description; + std::function get_description; uint32_t flags; MenuItem(uint32_t item_id, const std::u16string& name, const std::u16string& description, uint32_t flags); + MenuItem(uint32_t item_id, const std::u16string& name, + std::function get_description, uint32_t flags); +}; + +struct Menu { + uint32_t menu_id; + std::u16string name; + std::vector items; + + Menu() = delete; + Menu(uint32_t menu_id, const std::u16string& name); }; diff --git a/src/ProxyCommands.cc b/src/ProxyCommands.cc index 24ac9e13..41243fdf 100644 --- a/src/ProxyCommands.cc +++ b/src/ProxyCommands.cc @@ -120,6 +120,23 @@ static HandlerResult C_05(shared_ptr, return HandlerResult::Type::FORWARD; } +static HandlerResult C_1D(shared_ptr, + ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { + return session.options.suppress_client_pings + ? HandlerResult::Type::SUPPRESS + : HandlerResult::Type::FORWARD; +} + +static HandlerResult S_1D(shared_ptr, + ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { + if (session.options.suppress_client_pings) { + session.server_channel.send(0x1D); + return HandlerResult::Type::SUPPRESS; + } else { + return HandlerResult::Type::FORWARD; + } +} + static HandlerResult S_97(shared_ptr, ProxyServer::LinkedSession& session, uint16_t, uint32_t flag, string&) { // If the client has already received a 97 command, block this one and @@ -706,20 +723,27 @@ static HandlerResult S_B2(shared_ptr, session.server_channel.send(0xB3, flag, &cmd, sizeof(cmd)); return HandlerResult::Type::SUPPRESS; } else { - session.should_forward_function_call_return_queue.emplace_back(true); + session.function_call_return_handler_queue.emplace_back(nullptr); return HandlerResult::Type::FORWARD; } } static HandlerResult C_B3(shared_ptr, - ProxyServer::LinkedSession& session, uint16_t, uint32_t, string&) { - if (session.should_forward_function_call_return_queue.empty()) { + ProxyServer::LinkedSession& session, uint16_t, uint32_t, string& data) { + auto cmd = check_size_t(data); + if (session.function_call_return_handler_queue.empty()) { session.log.warning("Received function call result with empty result queue"); return HandlerResult::Type::FORWARD; } - bool should_forward = session.should_forward_function_call_return_queue.front(); - session.should_forward_function_call_return_queue.pop_front(); - return should_forward ? HandlerResult::Type::FORWARD : HandlerResult::Type::SUPPRESS; + + auto handler = std::move(session.function_call_return_handler_queue.front()); + session.function_call_return_handler_queue.pop_front(); + if (handler != nullptr) { + handler(cmd.return_value, cmd.checksum); + return HandlerResult::Type::SUPPRESS; + } else { + return HandlerResult::Type::FORWARD; + } } static HandlerResult S_B_E7(shared_ptr, @@ -1718,7 +1742,7 @@ static on_command_t handlers[0x100][6][2] = { /* 1A */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_V3_1A_D5, nullptr}, {S_V3_1A_D5, nullptr}, {nullptr, nullptr}}, /* 1B */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, /* 1C */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {S_invalid, nullptr}}, -/* 1D */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, +/* 1D */ {{S_invalid, nullptr}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}, {S_1D, C_1D}}, /* 1E */ {{S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}, {S_invalid, nullptr}}, /* 1F */ {{S_invalid, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}, {nullptr, nullptr}}, // CMD S-PATCH C-PATCH S-DC C-DC S-PC C-PC S-GC C-GC S-XB C-XB S-BB C-BB diff --git a/src/ProxyServer.hh b/src/ProxyServer.hh index 637a6e0b..ed3fea58 100644 --- a/src/ProxyServer.hh +++ b/src/ProxyServer.hh @@ -68,7 +68,8 @@ public: int64_t remote_guild_card_number; parray remote_client_config_data; ClientConfigBB newserv_client_config; - std::deque should_forward_function_call_return_queue; + // A null handler in here means to forward the response to the remote server + std::deque> function_call_return_handler_queue; G_SwitchStateChanged_6x05 last_switch_enabled_command; PlayerInventoryItem next_drop_item; uint32_t next_item_id; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index c99cf208..20e3ad77 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -67,60 +67,58 @@ vector quest_download_menu({ MenuItem(static_cast(QuestCategory::DOWNLOAD), u"Download", u"$E$C6Quests to download\nto your Memory Card", 0), }); -static const unordered_map proxy_options_menu_descriptions({ - {ProxyOptionsMenuItemID::GO_BACK, u"Return to the\nproxy menu"}, - {ProxyOptionsMenuItemID::CHAT_COMMANDS, u"Enable chat\ncommands"}, - {ProxyOptionsMenuItemID::CHAT_FILTER, u"Enable escape\nsequences in\nchat messages\nand info board"}, - {ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, u"Show a message\nwhen other players\njoin or leave"}, - {ProxyOptionsMenuItemID::INFINITE_HP, u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"}, - {ProxyOptionsMenuItemID::INFINITE_TP, u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"}, - {ProxyOptionsMenuItemID::SWITCH_ASSIST, u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"}, - {ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, u"Fix Meseta value\nat 1,000,000"}, - {ProxyOptionsMenuItemID::BLOCK_EVENTS, u"Disable seasonal\nevents in the lobby\nand in games"}, - {ProxyOptionsMenuItemID::BLOCK_PATCHES, u"Disable patches sent\nby the remote server"}, - {ProxyOptionsMenuItemID::SAVE_FILES, u"Save local copies of\nfiles from the\nremote server\n(quests, etc.)"}, - {ProxyOptionsMenuItemID::RED_NAME, u"Set your name\ncolor to red"}, - {ProxyOptionsMenuItemID::BLANK_NAME, u"Suppress your\ncharacter name\nduring login"}, - {ProxyOptionsMenuItemID::SUPPRESS_LOGIN, u"Use an alternate\nlogin sequence"}, - {ProxyOptionsMenuItemID::SKIP_CARD, u"Use an alternate\nvalue for your initial\nGuild Card"}, -}); - -static vector proxy_options_menu_for_client( +static shared_ptr proxy_options_menu_for_client( shared_ptr s, shared_ptr c) { - vector ret; + shared_ptr ret(new Menu(MenuID::PROXY_OPTIONS, u"Proxy options")); // Note: The descriptions are instead in the map above, because this menu is // dynamically created every time it's sent to the client. This is just one // way in which the menu abstraction is currently insufficient (there is a // TODO about this in README.md). - ret.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", u"", 0); + ret->items.emplace_back(ProxyOptionsMenuItemID::GO_BACK, u"Go back", u"Return to the\nProxy Server menu", 0); - auto add_option = [&](uint32_t item_id, bool is_enabled, const char16_t* text) -> void { + auto add_option = [&](uint32_t item_id, bool is_enabled, const char16_t* text, const char16_t* description) -> void { u16string option = is_enabled ? u"* " : u"- "; option += text; - ret.emplace_back(item_id, option, u"", 0); + ret->items.emplace_back(item_id, option, description, 0); }; - add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands, u"Chat commands"); - add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter, u"Chat filter"); - add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, c->options.enable_player_notifications, u"Player notifs"); + add_option(ProxyOptionsMenuItemID::CHAT_COMMANDS, c->options.enable_chat_commands, + u"Chat commands", u"Enable chat\ncommands"); + add_option(ProxyOptionsMenuItemID::CHAT_FILTER, c->options.enable_chat_filter, + u"Chat filter", u"Enable escape\nsequences in\nchat messages\nand info board"); + add_option(ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS, c->options.enable_player_notifications, + u"Player notifs", u"Show a message\nwhen other players\njoin or leave"); + add_option(ProxyOptionsMenuItemID::BLOCK_PINGS, c->options.suppress_client_pings, + u"Block pings", u"Block ping commands\nsent by the client"); if (!(c->flags & Client::Flag::IS_EPISODE_3)) { - add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, u"Infinite HP"); - add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, u"Infinite TP"); - add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, u"Switch assist"); + add_option(ProxyOptionsMenuItemID::INFINITE_HP, c->options.infinite_hp, + u"Infinite HP", u"Enable automatic HP\nrestoration when\nyou are hit by an\nenemy or trap\n\nCannot revive you\nfrom one-hit kills"); + add_option(ProxyOptionsMenuItemID::INFINITE_TP, c->options.infinite_tp, + u"Infinite TP", u"Enable automatic TP\nrestoration when\nyou cast any\ntechnique"); + add_option(ProxyOptionsMenuItemID::SWITCH_ASSIST, c->options.switch_assist, + u"Switch assist", u"Automatically try\nto unlock 2-player\ndoors when you step\non both switches\nsequentially"); } else { // Note: Thie option's text is the maximum possible length for any menu item - add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, u"Infinite Meseta"); + add_option(ProxyOptionsMenuItemID::EP3_INFINITE_MESETA, c->options.ep3_infinite_meseta, + u"Infinite Meseta", u"Fix Meseta value\nat 1,000,000"); } - add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0), u"Block events"); - add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, (c->options.function_call_return_value >= 0), u"Block patches"); + add_option(ProxyOptionsMenuItemID::BLOCK_EVENTS, (c->options.override_lobby_event >= 0), + u"Block events", u"Disable seasonal\nevents in the lobby\nand in games"); + add_option(ProxyOptionsMenuItemID::BLOCK_PATCHES, (c->options.function_call_return_value >= 0), + u"Block patches", u"Disable patches sent\nby the remote server"); if (s->proxy_allow_save_files) { - add_option(ProxyOptionsMenuItemID::SAVE_FILES, c->options.save_files, u"Save files"); + add_option(ProxyOptionsMenuItemID::SAVE_FILES, c->options.save_files, + u"Save files", u"Save local copies of\nfiles from the\nremote server\n(quests, etc.)"); } if (s->proxy_enable_login_options) { - add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name, u"Red name"); - add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name, u"Blank name"); - add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login, u"Skip login"); - add_option(ProxyOptionsMenuItemID::SKIP_CARD, c->options.zero_remote_guild_card, u"Skip card"); + add_option(ProxyOptionsMenuItemID::RED_NAME, c->options.red_name, + u"Red name", u"Set your name\ncolor to red"); + add_option(ProxyOptionsMenuItemID::BLANK_NAME, c->options.blank_name, + u"Blank name", u"Suppress your\ncharacter name\nduring login"); + add_option(ProxyOptionsMenuItemID::SUPPRESS_LOGIN, c->options.suppress_remote_login, + u"Skip login", u"Use an alternate\nlogin sequence"); + add_option(ProxyOptionsMenuItemID::SKIP_CARD, c->options.zero_remote_guild_card, + u"Skip card", u"Use an alternate\nvalue for your initial\nGuild Card"); } return ret; @@ -150,8 +148,7 @@ static void send_client_to_proxy_server(shared_ptr s, shared_ptr s, shared_ptr c) { - send_menu(c, u"Proxy server", MenuID::PROXY_DESTINATIONS, - s->proxy_destinations_menu_for_version(c->version())); + send_menu(c, s->proxy_destinations_menu_for_version(c->version())); } static bool send_enable_send_function_call_if_applicable( @@ -203,7 +200,72 @@ void on_connect(std::shared_ptr s, std::shared_ptr c) { } static void send_main_menu(shared_ptr s, shared_ptr c) { - send_menu(c, s->name.c_str(), MenuID::MAIN, s->main_menu); + shared_ptr main_menu(new Menu(MenuID::MAIN, s->name)); + + main_menu->items.emplace_back( + MainMenuItemID::GO_TO_LOBBY, u"Go to lobby", + [s, wc = weak_ptr(c)]() -> u16string { + auto c = wc.lock(); + if (!c) { + return u""; + } + + size_t num_players = 0; + size_t num_games = 0; + size_t num_compatible_games = 0; + for (const auto& it : s->id_to_lobby) { + const auto& l = it.second; + if (l->is_game()) { + num_games++; + if (l->version == c->version() && + (!l->is_ep3() == !(c->flags & Client::Flag::IS_EPISODE_3))) { + num_compatible_games++; + } + } + for (const auto& c : l->clients) { + if (c) { + num_players++; + } + } + } + string info = string_printf( + "$C6%zu$C7 players online\n$C6%zu$C7 games\n$C6%zu$C7 compatible games", + num_players, num_games, num_compatible_games); + return decode_sjis(info); + }, + 0); + main_menu->items.emplace_back(MainMenuItemID::INFORMATION, u"Information", + u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES); + uint32_t proxy_destinations_menu_item_flags = + // DCNTE doesn't support multiple ship select menus without changing + // servers (via a 19 command) apparently :( + MenuItem::Flag::INVISIBLE_ON_DCNTE | + (s->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) | + (s->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) | + (s->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) | + (s->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) | + MenuItem::Flag::INVISIBLE_ON_BB; + main_menu->items.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server", + u"Connect to another\nserver", proxy_destinations_menu_item_flags); + main_menu->items.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests", + u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); + if (!s->is_replay) { + if (!s->function_code_index->patch_menu_empty(c->specific_version)) { + main_menu->items.emplace_back(MainMenuItemID::PATCHES, u"Patches", + u"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); + } + if (!s->dol_file_index->empty()) { + main_menu->items.emplace_back(MainMenuItemID::PROGRAMS, u"Programs", + u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED); + } + } + main_menu->items.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect", + u"Disconnect", 0); + main_menu->items.emplace_back(MainMenuItemID::CLEAR_LICENSE, u"Clear license", + u"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password", + MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); + + send_menu(c, main_menu); } void on_login_complete(shared_ptr s, shared_ptr c) { @@ -232,15 +294,6 @@ void on_login_complete(shared_ptr s, shared_ptr c) { send_change_event(c, s->pre_lobby_event); } - if (function_compiler_available() && (c->flags & Client::Flag::SEND_FUNCTION_CALL_NEEDS_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_NEEDS_CACHE_PATCH; - send_update_client_config(c); - } - if (s->welcome_message.empty() || (c->flags & Client::Flag::NO_D6) || !(c->flags & Client::Flag::AT_WELCOME_MESSAGE)) { @@ -293,6 +346,9 @@ static void set_console_client_flags( } } c->flags |= flags_for_version(c->version(), sub_version); + if (c->specific_version == default_specific_version_for_version(c->version(), -1)) { + c->specific_version = default_specific_version_for_version(c->version(), sub_version); + } } static void on_DB_V3(shared_ptr s, shared_ptr c, @@ -552,8 +608,7 @@ static void on_9A(shared_ptr s, shared_ptr c, static void on_9C(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { const auto& cmd = check_size_t(data); - - c->flags |= flags_for_version(c->version(), cmd.sub_version); + set_console_client_flags(c, cmd.sub_version); uint32_t serial_number = stoul(cmd.serial_number, nullptr, 16); try { @@ -1398,8 +1453,7 @@ static void on_D6_V3(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); if (c->flags & Client::Flag::IN_INFORMATION_MENU) { - send_menu(c, u"Information", MenuID::INFORMATION, - *s->information_menu_for_version(c->version())); + send_menu(c, s->information_menu_for_version(c->version())); } else if (c->flags & Client::Flag::AT_WELCOME_MESSAGE) { send_enable_send_function_call_if_applicable(s, c); c->flags &= ~Client::Flag::AT_WELCOME_MESSAGE; @@ -1413,95 +1467,22 @@ static void on_09(shared_ptr s, shared_ptr c, const auto& cmd = check_size_t(data); switch (cmd.menu_id) { - case MenuID::MAIN: - if (cmd.item_id == MainMenuItemID::GO_TO_LOBBY) { - size_t num_players = 0; - size_t num_games = 0; - size_t num_compatible_games = 0; - for (const auto& it : s->id_to_lobby) { - const auto& l = it.second; - if (l->is_game()) { - num_games++; - if (l->version == c->version() && - (!l->is_ep3() == !(c->flags & Client::Flag::IS_EPISODE_3))) { - num_compatible_games++; - } - } - for (const auto& c : l->clients) { - if (c) { - num_players++; - } - } - } - string info = string_printf( - "$C6%zu$C7 players online\n$C6%zu$C7 games\n$C6%zu$C7 compatible games", - num_players, num_games, num_compatible_games); - send_ship_info(c, decode_sjis(info)); - } else { - for (const auto& item : s->main_menu) { - if (item.item_id == cmd.item_id) { - send_ship_info(c, item.description); - } - } - } - break; - - case MenuID::INFORMATION: - if (cmd.item_id == InformationMenuItemID::GO_BACK) { - send_ship_info(c, u"Return to the\nmain menu."); - } else { - try { - // We use item_id + 1 here because "go back" is the first item - send_ship_info(c, s->information_menu_for_version(c->version())->at(cmd.item_id + 1).description.c_str()); - } catch (const out_of_range&) { - send_ship_info(c, u"$C4Missing information\nmenu item"); - } - } - break; - - case MenuID::PROXY_DESTINATIONS: - if (cmd.item_id == ProxyDestinationsMenuItemID::GO_BACK) { - send_ship_info(c, u"Return to the\nmain menu"); - } else if (cmd.item_id == ProxyDestinationsMenuItemID::OPTIONS) { - send_ship_info(c, u"Set proxy session\noptions"); - } else { - try { - const auto& menu = s->proxy_destinations_menu_for_version(c->version()); - // We use item_id + 2 here because "go back" and "options" are the - // first items - send_ship_info(c, menu.at(cmd.item_id + 2).description.c_str()); - } catch (const out_of_range&) { - send_ship_info(c, u"$C4Missing proxy\ndestination"); - } - } - break; - - case MenuID::PROXY_OPTIONS: - try { - const auto* description = proxy_options_menu_descriptions.at(cmd.item_id); - send_ship_info(c, description); - } catch (const out_of_range&) { - send_ship_info(c, u"$C4Missing proxy\noption"); - } - break; - case MenuID::QUEST_FILTER: // Don't send anything here. The quest filter menu already has short // descriptions included with the entries, which the client shows in the // usual location on the screen. break; - case MenuID::QUEST: { if (!s->quest_index) { send_quest_info(c, u"$C6Quests are not available.", !c->lobby_id); - break; + } else { + auto q = s->quest_index->get(c->version(), cmd.item_id); + if (!q) { + send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id); + } else { + send_quest_info(c, q->long_description.c_str(), !c->lobby_id); + } } - auto q = s->quest_index->get(c->version(), cmd.item_id); - if (!q) { - send_quest_info(c, u"$C4Quest does not\nexist.", !c->lobby_id); - break; - } - send_quest_info(c, q->long_description.c_str(), !c->lobby_id); break; } @@ -1576,27 +1557,6 @@ static void on_09(shared_ptr s, shared_ptr c, send_ship_info(c, decode_sjis(info)); } - break; - } - - case MenuID::PATCHES: - // TODO: Find a way to provide descriptions for patches. - break; - - case MenuID::PROGRAMS: { - if (cmd.item_id == ProgramsMenuItemID::GO_BACK) { - send_ship_info(c, u"Return to the\nmain menu."); - } else { - try { - auto dol = s->dol_file_index->item_id_to_file.at(cmd.item_id); - string size_str = format_size(dol->data.size()); - string info = string_printf("$C6%s$C7\n%s", dol->name.c_str(), size_str.c_str()); - send_ship_info(c, decode_sjis(info)); - } catch (const out_of_range&) { - send_ship_info(c, u"Incorrect program ID."); - } - } - break; } case MenuID::TOURNAMENTS_FOR_SPEC: @@ -1611,6 +1571,7 @@ static void on_09(shared_ptr s, shared_ptr c, } break; } + case MenuID::TOURNAMENT_ENTRIES: { if (!(c->flags & Client::Flag::IS_EPISODE_3)) { send_ship_info(c, u"Incorrect menu ID"); @@ -1650,7 +1611,21 @@ static void on_09(shared_ptr s, shared_ptr c, } default: - send_ship_info(c, u"Incorrect menu ID"); + if (!c->last_menu_sent || c->last_menu_sent->menu_id != cmd.menu_id) { + send_ship_info(c, u"Incorrect menu ID"); + } else { + for (const auto& item : c->last_menu_sent->items) { + if (item.item_id == cmd.item_id) { + if (item.get_description != nullptr) { + send_ship_info(c, item.get_description()); + } else { + send_ship_info(c, item.description); + } + return; + } + } + send_ship_info(c, u"$C4Incorrect menu\nitem ID"); + } break; } } @@ -1712,8 +1687,7 @@ static void on_10(shared_ptr s, shared_ptr c, } case MainMenuItemID::INFORMATION: - send_menu(c, u"Information", MenuID::INFORMATION, - *s->information_menu_for_version(c->version())); + send_menu(c, s->information_menu_for_version(c->version())); c->flags |= Client::Flag::IN_INFORMATION_MENU; break; @@ -1737,11 +1711,29 @@ static void on_10(shared_ptr s, shared_ptr c, break; case MainMenuItemID::PATCHES: - send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu()); + if (!function_compiler_available()) { + throw runtime_error("function compiler not available"); + } + 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 { + send_menu(c, s->function_code_index->patch_menu(c->specific_version)); + } break; case MainMenuItemID::PROGRAMS: - send_menu(c, u"Programs", MenuID::PROGRAMS, s->dol_file_index->menu()); + send_menu(c, s->dol_file_index->menu); break; case MainMenuItemID::DISCONNECT: @@ -1789,6 +1781,9 @@ static void on_10(shared_ptr s, shared_ptr c, case ProxyOptionsMenuItemID::PLAYER_NOTIFICATIONS: c->options.enable_player_notifications = !c->options.enable_player_notifications; goto resend_proxy_options_menu; + case ProxyOptionsMenuItemID::BLOCK_PINGS: + c->options.suppress_client_pings = !c->options.suppress_client_pings; + goto resend_proxy_options_menu; case ProxyOptionsMenuItemID::INFINITE_HP: c->options.infinite_hp = !c->options.infinite_hp; goto resend_proxy_options_menu; @@ -1830,8 +1825,7 @@ static void on_10(shared_ptr s, shared_ptr c, case ProxyOptionsMenuItemID::SKIP_CARD: c->options.zero_remote_guild_card = !c->options.zero_remote_guild_card; resend_proxy_options_menu: - send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS, - proxy_options_menu_for_client(s, c)); + send_menu(c, proxy_options_menu_for_client(s, c)); break; default: send_message_box(c, u"Incorrect menu item ID."); @@ -1845,8 +1839,7 @@ static void on_10(shared_ptr s, shared_ptr c, send_main_menu(s, c); } else if (item_id == ProxyDestinationsMenuItemID::OPTIONS) { - send_menu(c, s->name.c_str(), MenuID::PROXY_OPTIONS, - proxy_options_menu_for_client(s, c)); + send_menu(c, proxy_options_menu_for_client(s, c)); } else { const pair* dest = nullptr; @@ -2051,9 +2044,10 @@ static void on_10(shared_ptr s, shared_ptr c, throw runtime_error("client does not support send_function_call"); } + uint64_t key = (static_cast(item_id) << 32) | c->specific_version; send_function_call( - c, s->function_code_index->menu_item_id_to_patch_function.at(item_id)); - send_menu(c, u"Patches", MenuID::PATCHES, s->function_code_index->patch_menu()); + c, s->function_code_index->menu_item_id_and_specific_version_to_patch_function.at(key)); + send_menu(c, s->function_code_index->patch_menu(c->specific_version)); } break; @@ -2191,8 +2185,7 @@ static void on_08_E6(shared_ptr s, shared_ptr c, static void on_1F(shared_ptr s, shared_ptr c, uint16_t, uint32_t, const string& data) { check_size_v(data.size(), 0); - send_menu(c, u"Information", MenuID::INFORMATION, - *s->information_menu_for_version(c->version()), true); + send_menu(c, s->information_menu_for_version(c->version()), true); } static void on_A0(shared_ptr s, shared_ptr c, @@ -2272,7 +2265,17 @@ static void on_B3(shared_ptr s, shared_ptr c, } auto called_fn = s->function_code_index->index_to_function.at(flag); - if (c->loading_dol_file.get()) { + 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; + } + + } else if (c->loading_dol_file.get()) { 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); diff --git a/src/SendCommands.cc b/src/SendCommands.cc index 9eb483c0..ddf0cae9 100644 --- a/src/SendCommands.cc +++ b/src/SendCommands.cc @@ -310,6 +310,17 @@ void send_quest_buffer_overflow( send_command_t(c, 0xA7, 0x00, cmd); } +void send_cache_patch_if_needed(shared_ptr s, shared_ptr c) { + 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); + } +} + void send_function_call( shared_ptr c, shared_ptr code, @@ -1004,23 +1015,17 @@ void send_guild_card(shared_ptr c, shared_ptr source) { // menus template -void send_menu_t( - shared_ptr c, - const u16string& menu_name, - uint32_t menu_id, - const vector& items, - bool is_info_menu) { - +void send_menu_t(shared_ptr c, shared_ptr menu, bool is_info_menu) { vector entries; { auto& e = entries.emplace_back(); - e.menu_id = menu_id; + e.menu_id = menu->menu_id; e.item_id = 0xFFFFFFFF; e.flags = 0x0004; - e.text = menu_name; + e.text = menu->name; } - for (const auto& item : items) { + for (const auto& item : menu->items) { bool is_visible = true; switch (c->version()) { case GameVersion::DC: @@ -1059,7 +1064,7 @@ void send_menu_t( if (is_visible) { auto& e = entries.emplace_back(); - e.menu_id = menu_id; + e.menu_id = menu->menu_id; e.item_id = item.item_id; e.flags = (c->version() == GameVersion::BB) ? 0x0004 : 0x0F04; e.text = item.name; @@ -1067,16 +1072,16 @@ void send_menu_t( } send_command_vt(c, is_info_menu ? 0x1F : 0x07, entries.size() - 1, entries); + c->last_menu_sent = menu; } -void send_menu(shared_ptr c, const u16string& menu_name, - uint32_t menu_id, const vector& items, bool is_info_menu) { +void send_menu(shared_ptr c, shared_ptr menu, bool is_info_menu) { if (c->version() == GameVersion::PC || c->version() == GameVersion::PATCH || c->version() == GameVersion::BB) { - send_menu_t(c, menu_name, menu_id, items, is_info_menu); + send_menu_t(c, menu, is_info_menu); } else { - send_menu_t(c, menu_name, menu_id, items, is_info_menu); + send_menu_t(c, menu, is_info_menu); } } diff --git a/src/SendCommands.hh b/src/SendCommands.hh index 9271f2c3..7f413a07 100644 --- a/src/SendCommands.hh +++ b/src/SendCommands.hh @@ -132,6 +132,8 @@ void send_update_client_config(std::shared_ptr c); 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 send_function_call( Channel& ch, uint64_t client_flags, @@ -245,8 +247,7 @@ void send_guild_card( uint8_t section_id, uint8_t char_class); void send_guild_card(std::shared_ptr c, std::shared_ptr source); -void send_menu(std::shared_ptr c, const std::u16string& menu_name, - uint32_t menu_id, const std::vector& items, bool is_info_menu = false); +void send_menu(std::shared_ptr c, std::shared_ptr menu, bool is_info_menu = false); void send_game_menu( std::shared_ptr c, std::shared_ptr s, diff --git a/src/ServerState.cc b/src/ServerState.cc index f4628176..c0ba1c89 100644 --- a/src/ServerState.cc +++ b/src/ServerState.cc @@ -309,7 +309,7 @@ uint32_t ServerState::connect_address_for_client(std::shared_ptr c) { } } -shared_ptr> ServerState::information_menu_for_version(GameVersion version) { +std::shared_ptr ServerState::information_menu_for_version(GameVersion version) { if ((version == GameVersion::DC) || (version == GameVersion::PC)) { return this->information_menu_v2; } else if ((version == GameVersion::GC) || (version == GameVersion::XB)) { @@ -318,7 +318,7 @@ shared_ptr> ServerState::information_menu_for_version(Gam throw out_of_range("no information menu exists for this version"); } -const vector& ServerState::proxy_destinations_menu_for_version(GameVersion version) { +shared_ptr ServerState::proxy_destinations_menu_for_version(GameVersion version) { switch (version) { case GameVersion::DC: return this->proxy_destinations_menu_dc; @@ -386,25 +386,21 @@ void ServerState::create_menus(shared_ptr config_json) { config_log.info("Creating menus"); const auto& d = config_json->as_dict(); - this->main_menu.clear(); - this->proxy_destinations_menu_dc.clear(); - this->proxy_destinations_menu_pc.clear(); - this->proxy_destinations_menu_gc.clear(); - this->proxy_destinations_menu_xb.clear(); - - shared_ptr> information_menu_v2(new vector()); - shared_ptr> information_menu_v3(new vector()); + shared_ptr information_menu_v2(new Menu(MenuID::INFORMATION, u"Information")); + shared_ptr information_menu_v3(new Menu(MenuID::INFORMATION, u"Information")); shared_ptr> information_contents(new vector()); - information_menu_v3->emplace_back(InformationMenuItemID::GO_BACK, u"Go back", + information_menu_v2->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back", + u"Return to the\nmain menu", 0); + information_menu_v3->items.emplace_back(InformationMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); { uint32_t item_id = 0; for (const auto& item : d.at("InformationMenuContents")->as_list()) { auto& v = item->as_list(); - information_menu_v2->emplace_back(item_id, decode_sjis(v.at(0)->as_string()), + information_menu_v2->items.emplace_back(item_id, decode_sjis(v.at(0)->as_string()), decode_sjis(v.at(1)->as_string()), 0); - information_menu_v3->emplace_back(item_id, decode_sjis(v.at(0)->as_string()), + information_menu_v3->items.emplace_back(item_id, decode_sjis(v.at(0)->as_string()), decode_sjis(v.at(1)->as_string()), MenuItem::Flag::REQUIRES_MESSAGE_BOXES); information_contents->emplace_back(decode_sjis(v.at(2)->as_string())); item_id++; @@ -414,53 +410,43 @@ void ServerState::create_menus(shared_ptr config_json) { this->information_menu_v3 = information_menu_v3; this->information_contents = information_contents; - auto generate_proxy_destinations_menu = [&]( - vector& ret_menu, - vector>& ret_pds, - const char* key) { + auto generate_proxy_destinations_menu = [&](vector>& ret_pds, const char* key) -> shared_ptr { + shared_ptr ret(new Menu(MenuID::PROXY_DESTINATIONS, u"Proxy server")); + ret_pds.clear(); + try { map> sorted_jsons; for (const auto& it : d.at(key)->as_dict()) { sorted_jsons.emplace(it.first, it.second); } - ret_menu.clear(); - ret_pds.clear(); - - ret_menu.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back", + ret->items.emplace_back(ProxyDestinationsMenuItemID::GO_BACK, u"Go back", u"Return to the\nmain menu", 0); - ret_menu.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options", - u"Set proxy options", 0); + ret->items.emplace_back(ProxyDestinationsMenuItemID::OPTIONS, u"Options", + u"Set proxy session\noptions", 0); uint32_t item_id = 0; for (const auto& item : sorted_jsons) { const string& netloc_str = item.second->as_string(); const string& description = "$C7Remote server:\n$C6" + netloc_str; - ret_menu.emplace_back(item_id, decode_sjis(item.first), + ret->items.emplace_back(item_id, decode_sjis(item.first), decode_sjis(description), 0); ret_pds.emplace_back(parse_netloc(netloc_str)); item_id++; } } catch (const out_of_range&) { } + return ret; }; - generate_proxy_destinations_menu( - this->proxy_destinations_menu_dc, - this->proxy_destinations_dc, - "ProxyDestinations-DC"); - generate_proxy_destinations_menu( - this->proxy_destinations_menu_pc, - this->proxy_destinations_pc, - "ProxyDestinations-PC"); - generate_proxy_destinations_menu( - this->proxy_destinations_menu_gc, - this->proxy_destinations_gc, - "ProxyDestinations-GC"); - generate_proxy_destinations_menu( - this->proxy_destinations_menu_xb, - this->proxy_destinations_xb, - "ProxyDestinations-XB"); + this->proxy_destinations_menu_dc = generate_proxy_destinations_menu( + this->proxy_destinations_dc, "ProxyDestinations-DC"); + this->proxy_destinations_menu_pc = generate_proxy_destinations_menu( + this->proxy_destinations_pc, "ProxyDestinations-PC"); + this->proxy_destinations_menu_gc = generate_proxy_destinations_menu( + this->proxy_destinations_gc, "ProxyDestinations-GC"); + this->proxy_destinations_menu_xb = generate_proxy_destinations_menu( + this->proxy_destinations_xb, "ProxyDestinations-XB"); try { const string& netloc_str = d.at("ProxyDestination-Patch")->as_string(); @@ -489,39 +475,6 @@ void ServerState::create_menus(shared_ptr config_json) { this->proxy_destination_bb.second = 0; } - this->main_menu.emplace_back(MainMenuItemID::GO_TO_LOBBY, u"Go to lobby", - u"Join the lobby", 0); - this->main_menu.emplace_back(MainMenuItemID::INFORMATION, u"Information", - u"View server\ninformation", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::REQUIRES_MESSAGE_BOXES); - uint32_t proxy_destinations_menu_item_flags = - // DCNTE doesn't support multiple ship select menus without changing - // servers (via a 19 command) apparently :( - MenuItem::Flag::INVISIBLE_ON_DCNTE | - (this->proxy_destinations_dc.empty() ? MenuItem::Flag::INVISIBLE_ON_DC : 0) | - (this->proxy_destinations_pc.empty() ? MenuItem::Flag::INVISIBLE_ON_PC : 0) | - (this->proxy_destinations_gc.empty() ? MenuItem::Flag::INVISIBLE_ON_GC : 0) | - (this->proxy_destinations_xb.empty() ? MenuItem::Flag::INVISIBLE_ON_XB : 0) | - MenuItem::Flag::INVISIBLE_ON_BB; - this->main_menu.emplace_back(MainMenuItemID::PROXY_DESTINATIONS, u"Proxy server", - u"Connect to another\nserver", proxy_destinations_menu_item_flags); - this->main_menu.emplace_back(MainMenuItemID::DOWNLOAD_QUESTS, u"Download quests", - u"Download quests", MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); - if (!this->is_replay) { - if (!this->function_code_index->patch_menu_empty()) { - this->main_menu.emplace_back(MainMenuItemID::PATCHES, u"Patches", - u"Change game\nbehaviors", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); - } - if (!this->dol_file_index->empty()) { - this->main_menu.emplace_back(MainMenuItemID::PROGRAMS, u"Programs", - u"Run GameCube\nprograms", MenuItem::Flag::GC_ONLY | MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL | MenuItem::Flag::REQUIRES_SAVE_DISABLED); - } - } - this->main_menu.emplace_back(MainMenuItemID::DISCONNECT, u"Disconnect", - u"Disconnect", 0); - this->main_menu.emplace_back(MainMenuItemID::CLEAR_LICENSE, u"Clear license", - u"Disconnect with an\ninvalid license error\nso you can enter a\ndifferent serial\nnumber, access key,\nor password", - MenuItem::Flag::INVISIBLE_ON_DCNTE | MenuItem::Flag::INVISIBLE_ON_BB); - try { this->welcome_message = decode_sjis(d.at("WelcomeMessage")->as_string()); } catch (const out_of_range&) { diff --git a/src/ServerState.hh b/src/ServerState.hh index cebd3db4..1b5a548d 100644 --- a/src/ServerState.hh +++ b/src/ServerState.hh @@ -84,14 +84,13 @@ struct ServerState { std::shared_ptr license_manager; - std::vector main_menu; - std::shared_ptr> information_menu_v2; - std::shared_ptr> information_menu_v3; + std::shared_ptr information_menu_v2; + std::shared_ptr information_menu_v3; std::shared_ptr> information_contents; - std::vector proxy_destinations_menu_dc; - std::vector proxy_destinations_menu_pc; - std::vector proxy_destinations_menu_gc; - std::vector proxy_destinations_menu_xb; + std::shared_ptr proxy_destinations_menu_dc; + std::shared_ptr proxy_destinations_menu_pc; + std::shared_ptr proxy_destinations_menu_gc; + std::shared_ptr proxy_destinations_menu_xb; std::vector> proxy_destinations_dc; std::vector> proxy_destinations_pc; std::vector> proxy_destinations_gc; @@ -146,8 +145,8 @@ struct ServerState { uint32_t connect_address_for_client(std::shared_ptr c); - std::shared_ptr> information_menu_for_version(GameVersion version); - const std::vector& proxy_destinations_menu_for_version(GameVersion version); + std::shared_ptr information_menu_for_version(GameVersion version); + std::shared_ptr proxy_destinations_menu_for_version(GameVersion version); const std::vector>& proxy_destinations_for_version(GameVersion version); void set_port_configuration( diff --git a/src/Version.cc b/src/Version.cc index ef804d9e..eec25333 100644 --- a/src/Version.cc +++ b/src/Version.cc @@ -20,19 +20,23 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { case -1: // Initial check (before sub_version recognition) switch (version) { case GameVersion::DC: - return Client::Flag::NO_D6; + return Client::Flag::NO_D6 | + Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case GameVersion::GC: - case GameVersion::XB: return 0; + case GameVersion::XB: + return Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case GameVersion::PC: return Client::Flag::NO_D6 | - Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY; + Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY | + Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case GameVersion::PATCH: return Client::Flag::NO_D6 | Client::Flag::NO_SEND_FUNCTION_CALL; case GameVersion::BB: return Client::Flag::NO_D6 | - Client::Flag::SAVE_ENABLED; + Client::Flag::SAVE_ENABLED | + Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; } break; @@ -46,18 +50,18 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { Client::Flag::NO_SEND_FUNCTION_CALL; case 0x26: // DCv2 US - return Client::Flag::NO_D6; + return Client::Flag::NO_D6 | + Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case 0x29: // PC return Client::Flag::NO_D6 | - Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY; + Client::Flag::SEND_FUNCTION_CALL_CHECKSUM_ONLY | + Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00 case 0x34: // GC Ep1&2 JP v1.03 - return ((version == GameVersion::GC) && function_compiler_available()) - ? Client::Flag::SEND_FUNCTION_CALL_NEEDS_CACHE_PATCH - : 0; + return 0; case 0x32: // GC Ep1&2 EU 50Hz case 0x33: // GC Ep1&2 EU 60Hz return Client::Flag::NO_D6_AFTER_LOBBY; @@ -80,7 +84,8 @@ uint32_t flags_for_version(GameVersion version, int64_t sub_version) { case 0x41: // GC Ep3 US return Client::Flag::NO_D6_AFTER_LOBBY | Client::Flag::IS_EPISODE_3 | - Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL; + Client::Flag::USE_OVERFLOW_FOR_SEND_FUNCTION_CALL | + Client::Flag::SEND_FUNCTION_CALL_NO_CACHE_PATCH; case 0x43: // GC Ep3 EU return Client::Flag::NO_D6_AFTER_LOBBY | Client::Flag::IS_EPISODE_3 | @@ -167,3 +172,37 @@ ServerBehavior server_behavior_for_name(const char* name) { throw invalid_argument("incorrect server behavior name"); } } + +uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version) { + uint32_t base_specific_version = (static_cast(version) + '0') << 24; + if (version == GameVersion::GC) { + // For versions that don't support send_function_call by default, we need + // to set the specific_version based on sub_version. Fortunately, all + // versions that share sub_version values also support send_function_call, + // so for those versions we get the specific_version later by sending the + // VersionDetect call. + switch (sub_version) { + case 0x36: // GC Ep1&2 US v1.02 (Plus) + return 0x334F4532; // 3OE2 + case 0x39: // GC Ep1&2 JP v1.05 (Plus) + return 0x334F4A35; // 3OJ5 + case 0x41: // GC Ep3 US + return 0x33534530; // 3SE0 + case 0x43: // GC Ep3 EU + return 0x33535030; // 3SP0 + case -1: // Initial check (before sub_version recognition) + case 0x30: // GC Ep1&2 JP v1.02, at least one version of PSO XB + case 0x31: // GC Ep1&2 US v1.00, GC US v1.01, GC EU v1.00, GC JP v1.00 + case 0x32: // GC Ep1&2 EU 50Hz + case 0x33: // GC Ep1&2 EU 60Hz + case 0x34: // GC Ep1&2 JP v1.03 + case 0x35: // GC Ep1&2 JP v1.04 (Plus) + case 0x40: // GC Ep3 trial + case 0x42: // GC Ep3 JP + default: + return base_specific_version; + } + } else { + return base_specific_version; + } +} diff --git a/src/Version.hh b/src/Version.hh index 2a6da44e..58efca71 100644 --- a/src/Version.hh +++ b/src/Version.hh @@ -7,11 +7,11 @@ enum class GameVersion { PATCH = 0, - DC, - PC, - GC, - XB, - BB, + DC = 1, + PC = 2, + GC = 3, + XB = 4, + BB = 5, }; enum class ServerBehavior { @@ -34,3 +34,5 @@ GameVersion version_for_name(const char* name); const char* name_for_server_behavior(ServerBehavior behavior); ServerBehavior server_behavior_for_name(const char* name); + +uint32_t default_specific_version_for_version(GameVersion version, int64_t sub_version); diff --git a/system/ppc/Ep3-AllCards.patch.s b/system/ppc/Ep3-AllCards.3SE0.patch.s similarity index 98% rename from system/ppc/Ep3-AllCards.patch.s rename to system/ppc/Ep3-AllCards.3SE0.patch.s index c4f06d89..1bca6dca 100644 --- a/system/ppc/Ep3-AllCards.patch.s +++ b/system/ppc/Ep3-AllCards.3SE0.patch.s @@ -14,8 +14,6 @@ reloc0: .offsetof start start: - .include InitClearCaches - .include Episode3USAOnly stwu [r1 - 0x20], r1 diff --git a/system/ppc/Ep3-Editors.patch.s b/system/ppc/Ep3-Editors.3SE0.patch.s similarity index 97% rename from system/ppc/Ep3-Editors.patch.s rename to system/ppc/Ep3-Editors.3SE0.patch.s index 745c13d9..9139ab45 100644 --- a/system/ppc/Ep3-Editors.patch.s +++ b/system/ppc/Ep3-Editors.3SE0.patch.s @@ -19,11 +19,6 @@ reloc0: .offsetof start start: - # Note: We don't actually need this for Episode 3, since all Episode 3 - # versions correctly clear the caches before running code from a B2 command. - # But we leave it in to be consistent with patches for Episodes 1&2. - .include InitClearCaches - .include Episode3USAOnly stwu [r1 - 0x20], r1 diff --git a/system/ppc/Ep3-VIPCard.patch.s b/system/ppc/Ep3-VIPCard.3SE0.patch.s similarity index 69% rename from system/ppc/Ep3-VIPCard.patch.s rename to system/ppc/Ep3-VIPCard.3SE0.patch.s index f3ad5d1a..87d57c79 100644 --- a/system/ppc/Ep3-VIPCard.patch.s +++ b/system/ppc/Ep3-VIPCard.3SE0.patch.s @@ -10,11 +10,6 @@ reloc0: .offsetof start start: - # Note: We don't actually need this for Episode 3, since all Episode 3 - # versions correctly clear the caches before running code from a B2 command. - # But we leave it in to be consistent with patches for Episodes 1&2. - .include InitClearCaches - .include Episode3USAOnly # Call seq_var_set(7000) - this gives the local player a VIP card diff --git a/system/ppc/InitClearCaches.inc.s b/system/ppc/InitClearCaches.inc.s deleted file mode 100644 index b50524a1..00000000 --- a/system/ppc/InitClearCaches.inc.s +++ /dev/null @@ -1,18 +0,0 @@ -# This macro clears the data and instruction caches at the beginning of each -# function. This is necessary because apparently some versions of PSO don't do -# this correctly by themselves. - -# This macro expects to be run immediately at the entrypoint (usually the start -# label) for all functions. It returns the original return address in r12, and -# the address of the start label in r11. - mflr r12 # r12 = address to return to - mfctr r3 # r3 = address of start label (this code is called via bctrl) - addi r4, r3, 0x7C00 # r4 = end of relevant region -InitClearCaches__next_cache_block: - dcbst r0, r3 - sync - icbi r0, r3 - addi r3, r3, 0x20 - cmpl r3, r4 - blt InitClearCaches__next_cache_block - isync diff --git a/system/ppc/ReadMemoryWord.s b/system/ppc/ReadMemoryWord.s index ee7eef24..aaed8109 100644 --- a/system/ppc/ReadMemoryWord.s +++ b/system/ppc/ReadMemoryWord.s @@ -8,8 +8,6 @@ reloc0: .offsetof start start: - .include InitClearCaches - bl read address: .zero diff --git a/system/ppc/RunDOL.s b/system/ppc/RunDOL.s index b2770993..19282483 100644 --- a/system/ppc/RunDOL.s +++ b/system/ppc/RunDOL.s @@ -8,7 +8,6 @@ reloc0: .offsetof start start: - .include InitClearCaches disable_interrupts: mfmsr r3 diff --git a/system/ppc/VersionDetect.s b/system/ppc/VersionDetect.s new file mode 100644 index 00000000..ed5e58ef --- /dev/null +++ b/system/ppc/VersionDetect.s @@ -0,0 +1,30 @@ +# This function returns the game version, with values more specific than can be +# detected by the sub_version field in various login commands. + +# The returned value has the format 03GGRRVV, where: +# G = game (Ox4F (O) = Episodes 1&2, 0x53 (S) = Episode 3) +# R = region (0x45 (E), 0x4A (J), 0x50 (P)) +# V = minor version (0 = 1.00, 1 = 1.01, 2 = 1.02, etc.) + +newserv_index_E3: + +entry_ptr: +reloc0: + .offsetof start + +start: + lis r3, 0x8000 + lwz r4, [r3] + lbz r5, [r3 + 7] + li r3, -1 + + rlwinm r0, r4, 16, 16, 31 + cmplwi r0, 0x4750 + bnelr + + lis r3, 0x3300 + rlwimi r3, r4, 8, 8, 23 + rlwimi r3, r5, 0, 24, 31 + ori r3, r3, 0x0030 + + blr diff --git a/system/ppc/WriteMemory.s b/system/ppc/WriteMemory.s index 00c2c12f..8aa09ca5 100644 --- a/system/ppc/WriteMemory.s +++ b/system/ppc/WriteMemory.s @@ -2,11 +2,26 @@ # serve DOL files to GameCube clients. # This is also the file I've chosen to document how to write code for newserv's -# functions subsystem. The code implemented in this file writes a -# variable-length block of data to a specified address in the client's memory. -# Note that WriteMemory is a general function that uses many of the subsystem's -# features. If you're writing a patch (not a general function), you cannot use -# the suffix or label_offsets features that are described here. +# functions subsystem. There are three kinds of functions: includes, patches, +# and general functions. This file, WriteMemory, is a general function. It +# writes a variable-length block of data to a specified address in the client's +# memory. + +# Includes are snippets of code that are intended to be used as part of other +# functions and patches. These files' names end with .inc.s. These can be used +# with the .include directive; there is an example of this in the code below. + +# Patches are functions that are available to run upon client request. They can +# be made available in the Patches menu or via the $patch command. In general, +# patches should be named like PATCHNAME.VXLS.patch.s, where V, X, L, and S +# denote which specific game version the patch is for. Specifically: +# V should be 3 for PSO GameCube +# X should be O for Episodes 1 & 2, and S for Episode 3 +# L should be E, J, or P for USA, Japanese, or Europe +# S should be 0, 1, 2, etc. for the disc version (0 = v1.00, 1 = v1.01, etc.) +# If a label named hide_from_patches_menu is present anywhere in the code, the +# patch is only usable via the $patch command and does not appear in the Patches +# menu. # For example, to use this function to write the bytes 38 00 00 05 to the # address 8010521C, send_function_call could be called like this: @@ -20,13 +35,16 @@ # label_writes, // Variables to pass in to the function's code # suffix); // Data to append after the code (not all functions use this) # The meanings of label_writes and suffix are described in the comments below. +# Note that there is no way to specify label_writes or suffix for patches +# requested by the client, so those features should only be used in general +# functions. # A label newserv_index_XX tells newserv what value to use in the flag field # when sending the B2 command. This is needed if the server needs to do # something when the B3 response is received. For GameCube functions, if # specified, the index must be in the range 01-FF. The DOL loading # functionality, which this function is a part of, uses indexes E0, E1, and E2, -# but this function can also be used for other purposes. +# but the WriteMemory function can also be used for other purposes. newserv_index_E1: # The entry_ptr label is required for all functions. It should point to a @@ -41,14 +59,6 @@ reloc0: .offsetof start start: - # A .include directive essentially pastes in the code from the referenced - # file. Here, we use the code from the file InitClearCaches.inc.s. - # PSO GC doesn't properly clear the data and instruction caches when it - # executes functions, so we use this include in all functions to do so. Since - # all functions do this, this makes it safe to use more than one function in - # each client's session. - .include InitClearCaches - bl get_block_ptr mr r6, r3 # r6 = address of dest_addr label @@ -67,6 +77,8 @@ copy_block__again: # Flush the data cache and clear the instruction cache at the written region lwz r3, [r6] # r3 = dest ptr lwz r4, [r6 + 4] # r4 = size + # A .include directive essentially pastes in the code from the referenced + # file. Here, we use the code from the file FlushCachedCode.inc.s. .include FlushCachedCode # Return the address after the last byte written. The value returned in r3