From 65c9cd99cd9501833fe174ee2f0e51bb2a7cb0b6 Mon Sep 17 00:00:00 2001 From: Eladash Date: Mon, 6 Sep 2021 10:33:44 +0300 Subject: [PATCH] Patches/PPU: Implement HLE/LLE/With-TOC function call patches Example patches: [ jumpf, 0x12340, "cellGcmSys:cellGcmSetFlip"] // Places a call to cellGcmSetFlip at 0x12340 [ jumpf, 0x12340, "cellGcmSys:0xDC09357E"] // Same, using FNID [ jumpf, 0x12340, 0x2345678 ] # Function OPD based call eading OPD at 0x2345678 --- Utilities/bin_patch.cpp | 56 +++++++- Utilities/bin_patch.h | 1 + rpcs3/Emu/Cell/Modules/cellGcmSys.cpp | 2 +- rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp | 2 +- rpcs3/Emu/Cell/Modules/cellVdec.cpp | 2 +- rpcs3/Emu/Cell/PPUModule.cpp | 30 +++-- rpcs3/Emu/Cell/PPUModule.h | 12 +- rpcs3/Emu/Cell/PPUThread.cpp | 163 +++++++++++++++++++++-- rpcs3/Emu/Cell/PPUThread.h | 9 ++ 9 files changed, 246 insertions(+), 31 deletions(-) diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index eb2358d8ef..b060651267 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -9,6 +9,8 @@ #include "util/endian.hpp" #include "util/asm.hpp" +#include + LOG_CHANNEL(patch_log, "PAT"); template <> @@ -41,6 +43,7 @@ void fmt_class_string::format(std::string& out, u64 arg) case patch_type::code_alloc: return "calloc"; case patch_type::jump: return "jump"; case patch_type::jump_link: return "jumpl"; + case patch_type::jump_func: return "jumpf"; case patch_type::load: return "load"; case patch_type::byte: return "byte"; case patch_type::le16: return "le16"; @@ -464,6 +467,7 @@ bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie switch (p_data.type) { case patch_type::utf8: + case patch_type::jump_func: { break; } @@ -546,7 +550,8 @@ void patch_engine::append_title_patches(const std::string& title_id) } void ppu_register_range(u32 addr, u32 size); -bool ppu_form_branch_to_code(u32 entry, u32 target, bool link = false); +bool ppu_form_branch_to_code(u32 entry, u32 target, bool link = false, bool with_toc = false, std::string module_name = {}); +u32 ppu_generate_id(std::string_view name); void unmap_vm_area(std::shared_ptr& ptr) { @@ -744,6 +749,55 @@ static usz apply_modification(std::basic_string& applied, const patch_engin resval = out_branch & -4; break; } + case patch_type::jump_func: + { + const std::string& str = p.original_value; + + const u32 out_branch = vm::try_get_addr(dst + (offset & -4)).first; + const usz sep_pos = str.find_first_of(':'); + + // Must contain only a single ':' or none + // If ':' is found: Left string is the module name, right string is the function name + // If ':' is not found: The entire string is a direct address of the function's descriptor in hexadecimal + if (str.size() <= 2 || !sep_pos || sep_pos == str.size() - 1 || sep_pos != str.find_last_of(":")) + { + continue; + } + + const std::string_view func_name{std::string_view(str).substr(sep_pos + 1)}; + u32 id = 0; + + if (func_name.starts_with("0x"sv)) + { + // Raw hexadeciaml-formatted FNID (real function name cannot contain a digit at the start, derived from C/CPP which were used in PS3 development) + const auto result = std::from_chars(func_name.data() + 2, func_name.data() + func_name.size() - 2, id, 16); + + if (result.ec != std::errc() || str.data() + sep_pos != result.ptr) + { + continue; + } + } + else + { + if (sep_pos == umax) + { + continue; + } + + // Generate FNID using function name + id = ppu_generate_id(func_name); + } + + // Allow only if points to a PPU executable instruction + // FNID/OPD-address is placed at target + if (!ppu_form_branch_to_code(out_branch, id, true, true, std::string{str.data(), sep_pos != umax ? sep_pos : 0})) + { + continue; + } + + resval = out_branch & -4; + break; + } case patch_type::byte: { *ptr = static_cast(p.value.long_value); diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index b4b1680b8b..83eb77d83f 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -30,6 +30,7 @@ enum class patch_type code_alloc,// Allocate memory somewhere, saves branch to memory at specfied address (filled with PPU NOP and branch for returning) jump, // Install special 32-bit jump instruction (PPU only atm) jump_link, // jump + set link (PPU only atm) + jump_func, // jump to exported function (PPU only, forever) byte, le16, le32, diff --git a/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp b/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp index f51a1325fb..c8494eb2c0 100644 --- a/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp +++ b/rpcs3/Emu/Cell/Modules/cellGcmSys.cpp @@ -1570,5 +1570,5 @@ DECLARE(ppu_module_manager::cellGcmSys)("cellGcmSys", []() REG_FUNC(cellGcmSys, cellGcmGpadCaptureSnapshot); // Special - REG_FUNC(cellGcmSys, cellGcmCallback).flag(MFF_HIDDEN); + REG_HIDDEN_FUNC(cellGcmCallback); }); diff --git a/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp b/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp index e1e6e8cc06..a8bc4740da 100644 --- a/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp @@ -618,5 +618,5 @@ void cellSysutil_MsgDialog_init() REG_FUNC(cellSysutil, cellMsgDialogAbort); // Helper Function - REG_FUNC(cellSysutil, exit_game).flag(MFF_HIDDEN); + REG_HIDDEN_FUNC(exit_game); } diff --git a/rpcs3/Emu/Cell/Modules/cellVdec.cpp b/rpcs3/Emu/Cell/Modules/cellVdec.cpp index e03d05eb4c..1ef537007a 100644 --- a/rpcs3/Emu/Cell/Modules/cellVdec.cpp +++ b/rpcs3/Emu/Cell/Modules/cellVdec.cpp @@ -1250,5 +1250,5 @@ DECLARE(ppu_module_manager::cellVdec)("libvdec", []() REG_FUNC(libvdec, cellVdecSetFrameRateExt); // 0xcffc42a5 REG_FUNC(libvdec, cellVdecSetPts); // 0x3ce2e4f8 - REG_FUNC(libvdec, vdecEntry).flag(MFF_HIDDEN); + REG_HIDDEN_FUNC(vdecEntry); }); diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 634f9b1f1e..7347643ec6 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -37,21 +37,23 @@ extern void sys_initialize_tls(ppu_thread&, u64, u32, u32, u32); // HLE function name cache std::vector g_ppu_function_names; -extern u32 ppu_generate_id(const char* name) +extern u32 ppu_generate_id(std::string_view name) { // Symbol name suffix - const auto suffix = "\x67\x59\x65\x99\x04\x25\x04\x90\x56\x64\x27\x49\x94\x89\x74\x1A"; + constexpr auto suffix = "\x67\x59\x65\x99\x04\x25\x04\x90\x56\x64\x27\x49\x94\x89\x74\x1A"sv; sha1_context ctx; u8 output[20]; // Compute SHA-1 hash sha1_starts(&ctx); - sha1_update(&ctx, reinterpret_cast(name), std::strlen(name)); - sha1_update(&ctx, reinterpret_cast(suffix), std::strlen(suffix)); + sha1_update(&ctx, reinterpret_cast(name.data()), name.size()); + sha1_update(&ctx, reinterpret_cast(suffix.data()), suffix.size()); sha1_finish(&ctx, output); - return reinterpret_cast&>(output[0]); + le_t result = 0; + std::memcpy(&result, output, sizeof(result)); + return result; } ppu_static_module::ppu_static_module(const char* name) @@ -331,14 +333,11 @@ static void ppu_initialize_modules(ppu_linkage_info* link) g_ppu_function_names[function.second.index] = fmt::format("%s:%s", function.second.name, _module->name); } - if ((function.second.flags & MFF_HIDDEN) == 0) - { - auto& flink = linkage.functions[function.first]; + auto& flink = linkage.functions[function.first]; - flink.static_func = &function.second; - flink.export_addr = g_fxo->get().func_addr(function.second.index); - function.second.export_addr = &flink.export_addr; - } + flink.static_func = &function.second; + flink.export_addr = g_fxo->get().func_addr(function.second.index); + function.second.export_addr = &flink.export_addr; } for (auto& variable : _module->variables) @@ -528,7 +527,12 @@ struct ppu_prx_module_info be_t unk5; }; -bool ppu_form_branch_to_code(u32 entry, u32 target, bool link = false); +bool ppu_form_branch_to_code(u32 entry, u32 target); + +extern u32 ppu_get_exported_func_addr(u32 fnid, const std::string& module_name) +{ + return g_fxo->get().modules[module_name].functions[fnid].export_addr; +} // Load and register exports; return special exports found (nameless module) static auto ppu_load_exports(ppu_linkage_info* link, u32 exports_start, u32 exports_end) diff --git a/rpcs3/Emu/Cell/PPUModule.h b/rpcs3/Emu/Cell/PPUModule.h index 648937232a..02de8eed52 100644 --- a/rpcs3/Emu/Cell/PPUModule.h +++ b/rpcs3/Emu/Cell/PPUModule.h @@ -18,7 +18,7 @@ constexpr const char* ppu_select_name(const char* /*name*/, const char* orig_nam } // Generate FNID or VNID for given name -extern u32 ppu_generate_id(const char* name); +extern u32 ppu_generate_id(std::string_view name); // Overload for REG_FNID, REG_VNID macro constexpr u32 ppu_generate_id(u32 id) @@ -31,7 +31,7 @@ enum ppu_static_module_flags : u32 { MFF_FORCED_HLE = (1 << 0), // Always call HLE function MFF_PERFECT = (1 << 1), // Indicates complete implementation and LLE interchangeability - MFF_HIDDEN = (1 << 2), // Invisible function for internal use (TODO) + MFF_HIDDEN = (1 << 2), // Invisible variable for internal use (TODO) }; // HLE function information @@ -293,7 +293,9 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args) return func(ppu, args...); } -#define REG_FNID(_module, nid, func) ppu_module_manager::register_static_function<&func>(#_module, ppu_select_name(#func, nid), BIND_FUNC(func, ppu.cia = static_cast(ppu.lr) & ~3), ppu_generate_id(nid)) +#define BIND_FUNC_WITH_BLR(func) BIND_FUNC(func, ppu.cia = static_cast(ppu.lr) & ~3) + +#define REG_FNID(_module, nid, func) ppu_module_manager::register_static_function<&func>(#_module, ppu_select_name(#func, nid), BIND_FUNC_WITH_BLR(func), ppu_generate_id(nid)) #define REG_FUNC(_module, func) REG_FNID(_module, #func, func) @@ -301,6 +303,10 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args) #define REG_VAR(_module, var) REG_VNID(_module, #var, var) +#define REG_HIDDEN_FUNC(func) ppu_function_manager::register_function(BIND_FUNC_WITH_BLR(func)) + +#define REG_HIDDEN_FUNC_PURE(func) ppu_function_manager::register_function(func) + #define REINIT_FUNC(func) (ppu_module_manager::find_static_function<&func>().flags = 0, ppu_module_manager::find_static_function<&func>()) #define UNIMPLEMENTED_FUNC(_module) _module.todo("%s()", __func__) diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index d444d72a03..a1997c27ae 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -485,19 +485,96 @@ extern void ppu_register_function_at(u32 addr, u32 size, u64 ptr) return ppu_register_function_at(addr, size, reinterpret_cast(ptr)); } +u32 ppu_get_exported_func_addr(u32 fnid, const std::string& module_name); + +bool ppu_return_from_far_jump(ppu_thread& ppu) +{ + auto& calls_info = ppu.hle_func_calls_with_toc_info; + ensure(!calls_info.empty()); + + // Branch to next instruction after far jump call entry with restored R2 and LR + const auto restore_info = &calls_info.back(); + ppu.cia = restore_info->cia + 4; + ppu.lr = restore_info->saved_lr; + ppu.gpr[2] = restore_info->saved_r2; + + calls_info.pop_back(); + return false; +} + +static const bool s_init_return_far_jump_func = [] +{ + REG_HIDDEN_FUNC_PURE(ppu_return_from_far_jump); + return true; +}(); + struct ppu_far_jumps_t { - std::unordered_map> vals; + struct all_info_t + { + u32 target; + bool link; + bool with_toc; + std::string module_name; + }; + + std::unordered_map vals; mutable shared_mutex mutex; - std::pair get_target(u32 pc) const + // Get target address, 'ppu' is used in ppu_far_jump in order to modify registers + u32 get_target(const u32 pc, ppu_thread* ppu = nullptr) { reader_lock lock(mutex); if (auto it = vals.find(pc); it != vals.end()) { - return it->second; + all_info_t& all_info = it->second; + u32 target = all_info.target; + + bool link = all_info.link; + bool from_opd = all_info.with_toc; + + if (!all_info.module_name.empty()) + { + target = ppu_get_exported_func_addr(target, all_info.module_name); + } + + if (from_opd && !vm::check_addr(target)) + { + // Avoid reading unmapped memory under mutex + from_opd = false; + } + + if (from_opd) + { + auto& opd = vm::_ref(target); + target = opd.addr; + + // We modify LR to custom values here + link = false; + + if (ppu) + { + auto& calls_info = ppu->hle_func_calls_with_toc_info; + + // Save LR and R2 + // Set LR to the this ppu_return_from_far_jump branch for restoration of registers + // NOTE: In order to clean up this information all calls must return in order + auto& saved_info = calls_info.emplace_back(); + saved_info.cia = pc; + saved_info.saved_lr = std::exchange(ppu->lr, FIND_FUNC(ppu_return_from_far_jump)); + saved_info.saved_r2 = std::exchange(ppu->gpr[2], opd.rtoc); + } + + } + + if (link && ppu) + { + ppu->lr = pc + 4; + } + + return target; } return {}; @@ -507,39 +584,103 @@ struct ppu_far_jumps_t u32 ppu_get_far_jump(u32 pc) { g_fxo->init(); - return g_fxo->get().get_target(pc).first; + return g_fxo->get().get_target(pc); } static bool ppu_far_jump(ppu_thread& ppu) { - auto [cia, link] = g_fxo->get().get_target(ppu.cia); - if (link) ppu.lr = ppu.cia + 4; + const u32 cia = g_fxo->get().get_target(ppu.cia, &ppu); + + if (!vm::check_addr(cia, vm::page_executable)) + { + fmt::throw_exception("PPU far jump failed! (returned cia = 0x%08x)", cia); + } + ppu.cia = cia; return false; } -bool ppu_form_branch_to_code(u32 entry, u32 target, bool link) +bool ppu_form_branch_to_code(u32 entry, u32 target, bool link, bool with_toc, std::string module_name) { + // Force align entry and target entry &= -4; - target &= -4; - if (entry == target || !vm::check_addr(entry, vm::page_executable) || !vm::check_addr(target, vm::page_executable)) + // Exported functions are using target as FNID, must not be changed + if (module_name.empty()) + { + target &= -4; + + u32 cia_target = target; + + if (with_toc) + { + ppu_func_opd_t opd{}; + if (!vm::try_access(target, &opd, sizeof(opd), false)) + { + // Cannot access function descriptor + return false; + } + + // For now allow situations where OPD is changed later by patches or by the program itself + //cia_target = opd.addr; + + // So force a valid target (executable, yet not equal to entry) + cia_target = entry ^ 8; + } + + // Target CIA must be aligned, executable and not equal with + if (cia_target % 4 || entry == cia_target || !vm::check_addr(cia_target, vm::page_executable)) + { + return false; + } + } + + // Entry must be executable + if (!vm::check_addr(entry, vm::page_executable)) { return false; } + if (module_name.empty()) g_fxo->init(); + if (!module_name.empty()) + { + // Always use function descriptor for exported functions + with_toc = true; + } + + if (with_toc) + { + // Always link for calls with function descriptor + link = true; + } + // Register branch target in host memory, not guest memory auto& jumps = g_fxo->get(); std::lock_guard lock(jumps.mutex); - jumps.vals.insert_or_assign(entry, std::make_pair(target, link)); + jumps.vals.insert_or_assign(entry, std::type_identity_t{target, link, with_toc, std::move(module_name)}); ppu_register_function_at(entry, 4, &ppu_far_jump); return true; } +bool ppu_form_branch_to_code(u32 entry, u32 target, bool link, bool with_toc) +{ + return ppu_form_branch_to_code(entry, target, link, with_toc, std::string{}); +} + +bool ppu_form_branch_to_code(u32 entry, u32 target, bool link) +{ + return ppu_form_branch_to_code(entry, target, link, false); +} + +bool ppu_form_branch_to_code(u32 entry, u32 target) +{ + return ppu_form_branch_to_code(entry, target, false); +} + void ppu_remove_hle_instructions(u32 addr, u32 size) { g_fxo->init(); @@ -693,7 +834,7 @@ std::array op_branch_targets(u32 pc, ppu_opcode_t op) g_fxo->need(); - if (u32 target = g_fxo->get().get_target(pc).first) + if (u32 target = g_fxo->get().get_target(pc)) { res[0] = target; return res; diff --git a/rpcs3/Emu/Cell/PPUThread.h b/rpcs3/Emu/Cell/PPUThread.h index b5d9d574a8..74432179c2 100644 --- a/rpcs3/Emu/Cell/PPUThread.h +++ b/rpcs3/Emu/Cell/PPUThread.h @@ -307,6 +307,15 @@ public: static constexpr u32 call_history_max_size = 4096; + struct hle_func_call_with_toc_info_t + { + u32 cia; + u64 saved_lr; + u64 saved_r2; + }; + + std::vector hle_func_calls_with_toc_info; + // For named_thread ctor const struct thread_name_t {