From 0429638cf09b779a0e248f57cb6779ead4ca11ae Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Wed, 13 Dec 2023 15:20:07 -0800 Subject: [PATCH] add extended patch metadata --- src/FunctionCompiler.cc | 35 ++++++++++++++++++++++++-------- src/FunctionCompiler.hh | 9 ++++---- src/ReceiveCommands.cc | 7 +++---- system/ppc/AllCards.3SE0.patch.s | 4 +++- system/ppc/Editors.3SE0.patch.s | 4 +++- system/ppc/ReadMemoryWord.s | 2 +- system/ppc/RunDOL.s | 2 +- system/ppc/VIPCard.3SE0.patch.s | 3 +++ system/ppc/VersionDetect.s | 2 +- system/ppc/WriteMemory.s | 18 ++++++++++------ 10 files changed, 59 insertions(+), 27 deletions(-) diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index 7a775266..74d26ae5 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -129,7 +129,7 @@ shared_ptr compile_function_code( #else auto ret = make_shared(); ret->arch = arch; - ret->name = name; + ret->short_name = name; ret->index = 0; ret->hide_from_patches_menu = false; @@ -137,6 +137,22 @@ shared_ptr compile_function_code( auto assembled = PPC32Emulator::assemble(text, {directory}); ret->code = std::move(assembled.code); ret->label_offsets = std::move(assembled.label_offsets); + for (const auto& it : assembled.metadata_keys) { + if (it.first == "hide_from_patches_menu") { + ret->hide_from_patches_menu = true; + } else if (it.first == "index") { + if (it.second.size() != 1) { + throw runtime_error("invalid index value in .meta directive"); + } + ret->index = it.second[0]; + } else if (it.first == "name") { + ret->long_name = it.second; + } else if (it.first == "description") { + ret->description = it.second; + } else { + throw runtime_error("unknown metadata key: " + it.first); + } + } } else if (arch == CompiledFunctionCode::Architecture::X86) { throw runtime_error("x86 assembler is not implemented"); } @@ -188,7 +204,7 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { // Check for specific_version token uint32_t specific_version = 0; - string patch_name = name; + string short_name = name; if (is_patch && (filename.size() >= 13) && (filename[filename.size() - 13] == '.') && @@ -197,14 +213,13 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { (filename[filename.size() - 10] == 'E' || filename[filename.size() - 10] == 'J' || filename[filename.size() - 10] == 'P') && (isdigit(filename[filename.size() - 9]) || filename[filename.size() - 9] == 'T')) { 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); + short_name = filename.substr(0, filename.size() - 13); } try { string path = directory + "/" + filename; string text = load_file(path); - auto code = compile_function_code( - CompiledFunctionCode::Architecture::POWERPC, directory, name, text); + auto code = compile_function_code(CompiledFunctionCode::Architecture::POWERPC, directory, name, text); if (code->index != 0) { if (!this->index_to_function.emplace(code->index, code).second) { throw runtime_error(string_printf( @@ -212,14 +227,14 @@ FunctionCodeIndex::FunctionCodeIndex(const string& directory) { } } code->specific_version = specific_version; - code->patch_name = patch_name; + code->short_name = short_name; this->name_to_function.emplace(name, code); if (is_patch) { code->menu_item_id = next_menu_item_id++; 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_printf("%s-%08" PRIX32, short_name.c_str(), specific_version), code); } string index_prefix = code->index ? string_printf("%02X => ", code->index) : ""; @@ -241,7 +256,11 @@ shared_ptr FunctionCodeIndex::patch_menu(uint32_t specific_version) 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->patch_name, "", MenuItem::Flag::REQUIRES_SEND_FUNCTION_CALL); + 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; diff --git a/src/FunctionCompiler.hh b/src/FunctionCompiler.hh index db825e88..85571b34 100644 --- a/src/FunctionCompiler.hh +++ b/src/FunctionCompiler.hh @@ -27,9 +27,10 @@ struct CompiledFunctionCode { std::vector relocation_deltas; 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) + std::string short_name; // Based on filename + std::string long_name; // From .meta name directive + std::string description; // From .meta description directive + uint8_t index; // 0 = unused (not registered in index_to_function) uint32_t menu_item_id; bool hide_from_patches_menu; uint32_t specific_version; @@ -60,7 +61,7 @@ struct FunctionCodeIndex { explicit FunctionCodeIndex(const std::string& directory); std::unordered_map> name_to_function; - std::unordered_map> index_to_function; + std::unordered_map> index_to_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; diff --git a/src/ReceiveCommands.cc b/src/ReceiveCommands.cc index abc90346..e34b361e 100644 --- a/src/ReceiveCommands.cc +++ b/src/ReceiveCommands.cc @@ -2634,14 +2634,13 @@ static void on_B3(shared_ptr c, uint16_t, uint32_t flag, string& data) { 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") { + if (called_fn->short_name == "ReadMemoryWord") { c->dol_base_addr = (cmd.return_value - c->loading_dol_file->data.size()) & (~3); send_dol_file_chunk(c, c->dol_base_addr); - } else if (called_fn->name == "WriteMemory") { + } else if (called_fn->short_name == "WriteMemory") { if (cmd.return_value >= c->dol_base_addr + c->loading_dol_file->data.size()) { auto fn = s->function_code_index->name_to_function.at("RunDOL"); - unordered_map label_writes( - {{"dol_base_ptr", c->dol_base_addr}}); + unordered_map label_writes({{"dol_base_ptr", c->dol_base_addr}}); send_function_call(c, fn, label_writes); // The client will stop running PSO after this, so disconnect them c->should_disconnect = true; diff --git a/system/ppc/AllCards.3SE0.patch.s b/system/ppc/AllCards.3SE0.patch.s index 1bca6dca..9cd54b79 100644 --- a/system/ppc/AllCards.3SE0.patch.s +++ b/system/ppc/AllCards.3SE0.patch.s @@ -7,7 +7,9 @@ # option is disabled, the Patches menu won't appear for the client. If this # patch is run on a different client version, it will do nothing. -hide_from_patches_menu: +.meta hide_from_patches_menu +.meta name="Get all cards" +.meta description="This patch gives you\nthe maximum number\nof each card." entry_ptr: reloc0: diff --git a/system/ppc/Editors.3SE0.patch.s b/system/ppc/Editors.3SE0.patch.s index 838cd4f8..ad9eac8d 100644 --- a/system/ppc/Editors.3SE0.patch.s +++ b/system/ppc/Editors.3SE0.patch.s @@ -12,7 +12,9 @@ # $patch command, since the client will likely crash if the player is not in a # game or lobby when the patch runs. -hide_from_patches_menu: +.meta hide_from_patches_menu +.meta name="Editors" +.meta description="Enables the various\ndebug menus" entry_ptr: reloc0: diff --git a/system/ppc/ReadMemoryWord.s b/system/ppc/ReadMemoryWord.s index d56eacda..44063cca 100644 --- a/system/ppc/ReadMemoryWord.s +++ b/system/ppc/ReadMemoryWord.s @@ -1,7 +1,7 @@ # This function is required for loading DOLs. If it's not present, newserv can't # serve DOL files to GameCube clients. -newserv_index_E0: +.meta index=E0 entry_ptr: reloc0: diff --git a/system/ppc/RunDOL.s b/system/ppc/RunDOL.s index 3265478b..bc16717c 100644 --- a/system/ppc/RunDOL.s +++ b/system/ppc/RunDOL.s @@ -1,7 +1,7 @@ # This function is required for loading DOLs. If it's not present, newserv can't # serve DOL files to GameCube clients. -newserv_index_E2: +.meta index=E2 entry_ptr: reloc0: diff --git a/system/ppc/VIPCard.3SE0.patch.s b/system/ppc/VIPCard.3SE0.patch.s index 87d57c79..9b81cab0 100644 --- a/system/ppc/VIPCard.3SE0.patch.s +++ b/system/ppc/VIPCard.3SE0.patch.s @@ -5,6 +5,9 @@ # option is disabled, the Patches menu won't appear for the client. If this # patch is run on a different client version, it will do nothing. +.meta name="Get VIP card" +.meta description="Gives you a VIP card" + entry_ptr: reloc0: .offsetof start diff --git a/system/ppc/VersionDetect.s b/system/ppc/VersionDetect.s index cf6afed9..986c1dd2 100644 --- a/system/ppc/VersionDetect.s +++ b/system/ppc/VersionDetect.s @@ -11,7 +11,7 @@ # of the above information. This value is called specific_version in the places # where it's used by the server. -newserv_index_E3: +.meta index=E3 entry_ptr: reloc0: diff --git a/system/ppc/WriteMemory.s b/system/ppc/WriteMemory.s index 54cea72f..f9a69ed1 100644 --- a/system/ppc/WriteMemory.s +++ b/system/ppc/WriteMemory.s @@ -43,13 +43,19 @@ # 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 +# These directives tell newserv what to show to the player in the Patches menu. +# Neither of them is required; if the name is omitted, the filename is used +# instead. +.meta name="Write memory" +.meta description="Writes data to any location in memory" + +# The .meta index directive 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 the WriteMemory function can also be used for other purposes. -newserv_index_E1: +# something when the B3 response is received. 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 the WriteMemory function can also be used +# for other purposes. +.meta index=E1 # The entry_ptr label is required for all functions. It should point to a # .offsetof directive that itself points to the actual entrypoint.