From ddb042148d15165ec2fe324c6548600d62720945 Mon Sep 17 00:00:00 2001 From: Eladash Date: Mon, 23 Aug 2021 16:21:49 +0300 Subject: [PATCH] Patches/LLVM: Implement Complex Patches Support --- Utilities/bin_patch.cpp | 83 ++++++++++++++++++++++++++++++++ Utilities/bin_patch.h | 10 ++++ rpcs3/Emu/Cell/PPUTranslator.cpp | 23 ++++++--- 3 files changed, 108 insertions(+), 8 deletions(-) diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index 0b87ac90ab..7a3e79e875 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -2,10 +2,12 @@ #include "File.h" #include "Config.h" #include "version.h" +#include "Emu/Memory/vm.h" #include "Emu/System.h" #include "util/types.hpp" #include "util/endian.hpp" +#include "util/asm.hpp" LOG_CHANNEL(patch_log, "PAT"); @@ -35,6 +37,7 @@ void fmt_class_string::format(std::string& out, u64 arg) switch (value) { case patch_type::invalid: return "invalid"; + case patch_type::alloc: return "alloc"; case patch_type::load: return "load"; case patch_type::byte: return "byte"; case patch_type::le16: return "le16"; @@ -512,10 +515,85 @@ void patch_engine::append_title_patches(const std::string& title_id) load(m_map, get_patches_path() + title_id + "_patch.yml"); } +void ppu_register_range(u32 addr, u32 size); + static std::basic_string apply_modification(const patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr) { std::basic_string applied; + for (const auto& p : patch.data_list) + { + if (p.type != patch_type::alloc) continue; + + // Do not allow null address or if dst is not a VM ptr + if (const u32 alloc_at = vm::try_get_addr(dst + (p.offset & -4096)).first; alloc_at >> 16) + { + const u32 alloc_size = utils::align(static_cast(p.value.long_value) + alloc_at % 4096, 4096); + + // Allocate map if needed, if allocated flags will indicate that bit 62 is set (unique identifier) + auto alloc_map = vm::reserve_map(vm::any, alloc_at & -0x10000, utils::align(alloc_size, 0x10000), vm::page_size_64k | vm::preallocated | vm::bf0_0x2 | (1ull << 62)); + + u64 flags = 0; + + switch (p.offset % patch_engine::mem_protection::mask) + { + case patch_engine::mem_protection::wx: flags |= vm::page_writable + vm::page_readable + vm::page_executable; break; + case patch_engine::mem_protection::ro: flags |= vm::page_readable; break; + case patch_engine::mem_protection::rx: flags |= vm::page_writable + vm::page_executable; break; + case patch_engine::mem_protection::rw: flags |= vm::page_writable + vm::page_readable; break; + default: ensure(false); + } + + if (alloc_map) + { + if (alloc_map->falloc(alloc_at, alloc_size)) + { + vm::page_protect(alloc_at, alloc_size, 0, flags, flags ^ (vm::page_writable + vm::page_readable + vm::page_executable)); + + if (flags & vm::page_executable) + { + ppu_register_range(alloc_at, alloc_size); + } + + applied.push_back(::narrow(&p - patch.data_list.data())); // Remember index in case of failure to allocate any memory + continue; + } + + // Revert if allocated map before failure + if (alloc_map->flags & (1ull << 62)) + { + vm::unmap(vm::any, alloc_map->addr); + } + } + } + + // Revert in case of failure + for (u32 index : applied) + { + const u32 addr = patch.data_list[index].offset & -4096; + + // Try different alignments until works + if (!vm::dealloc(addr)) + { + if (!vm::dealloc(addr & -0x10000)) + { + vm::dealloc(addr & -0x100000); + } + } + + if (auto alloc_map = vm::get(vm::any, addr); alloc_map->flags & (1ull << 62)) + { + vm::unmap(vm::any, alloc_map->addr); + } + } + + applied.clear(); + return applied; + } + + // Fixup values from before + std::fill(applied.begin(), applied.end(), u32{umax}); + for (const auto& p : patch.data_list) { u32 offset = p.offset; @@ -540,6 +618,11 @@ static std::basic_string apply_modification(const patch_engine::patch_info& // Invalid in this context continue; } + case patch_type::alloc: + { + // Applied before + continue; + } case patch_type::byte: { *ptr = static_cast(p.value.long_value); diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index 1a2efa4de3..d37652f9eb 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -26,6 +26,7 @@ enum class patch_type { invalid, load, + alloc, // Allocate memory at address (zeroized executable memory) byte, le16, le32, @@ -85,6 +86,15 @@ public: std::string version{}; }; + enum mem_protection : u8 + { + wx = 0, // Read + Write + Execute (default) + ro = 1, // Read + rx = 2, // Read + Execute + rw = 3, // Read + Write + mask = 3, + }; + using patch_map = std::unordered_map; patch_engine(); diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index 7e231308f0..87bf22b262 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -345,16 +345,23 @@ void PPUTranslator::CallFunction(u64 target, Value* indirect) if (!indirect) { - if ((!m_reloc && target < 0x10000) || target >= 0x100000000u - 0x10000) - { - Trap(); - return; - } + const u64 base = m_reloc ? m_reloc->addr : 0; + const u32 caddr = m_info.segs[0].addr; + const u32 cend = caddr + m_info.segs[0].size - 1; + const u64 _target = target + base; - callee = m_module->getOrInsertFunction(fmt::format("__0x%x", target), type); - cast(callee.getCallee())->setCallingConv(CallingConv::GHC); + if (_target >= caddr && _target <= cend) + { + callee = m_module->getOrInsertFunction(fmt::format("__0x%x", target), type); + cast(callee.getCallee())->setCallingConv(CallingConv::GHC); + } + else + { + indirect = m_reloc ? m_ir->CreateAdd(m_ir->getInt64(target), seg0) : m_ir->getInt64(target); + } } - else + + if (indirect) { m_ir->CreateStore(Trunc(indirect, GetType()), m_ir->CreateStructGEP(nullptr, m_thread, static_cast(&m_cia - m_locals)), true);