From ad8988afd3612eaf43b4e9c77c4860be65bd3c1c Mon Sep 17 00:00:00 2001 From: Malcolm Jestadt Date: Tue, 7 Jan 2020 04:10:23 -0500 Subject: [PATCH] Embedded SPU elf patching - PS3 games include both PPU and SPU code in their PPU executables, so to make patching games that make use of the same SPU libraries easier, we add a system to find and patch them. - Patches for this system still use SPU LS (Local Storage) addresses despite the fact that we aren't loading anything into SPU LS at this time. The patches are checked against each segment and patched in place. --- Utilities/bin_patch.cpp | 75 ++++++++++++++++++++++++++++++++++++ Utilities/bin_patch.h | 2 + rpcs3/Emu/Cell/PPUModule.cpp | 74 +++++++++++++++++++++++++++++++++++ 3 files changed, 151 insertions(+) diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index b578f03aad..0e339fa748 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -173,3 +173,78 @@ std::size_t patch_engine::apply(const std::string& name, u8* dst) const return found->second.size(); } + +std::size_t patch_engine::apply_with_ls_check(const std::string& name, u8* dst, u32 filesz, u32 ls_addr) const +{ + u32 rejected = 0; + + const auto found = m_map.find(name); + + if (found == m_map.cend()) + { + return 0; + } + + // Apply modifications sequentially + for (const auto& p : found->second) + { + auto ptr = dst + (p.offset - ls_addr); + + if(p.offset < ls_addr || p.offset >= (ls_addr + filesz)) + { + // This patch is out of range for this segment + rejected++; + continue; + } + + switch (p.type) + { + case patch_type::load: + { + // Invalid in this context + break; + } + case patch_type::byte: + { + *ptr = static_cast(p.value); + break; + } + case patch_type::le16: + { + *reinterpret_cast*>(ptr) = static_cast(p.value); + break; + } + case patch_type::le32: + case patch_type::lef32: + { + *reinterpret_cast*>(ptr) = static_cast(p.value); + break; + } + case patch_type::le64: + case patch_type::lef64: + { + *reinterpret_cast*>(ptr) = static_cast(p.value); + break; + } + case patch_type::be16: + { + *reinterpret_cast*>(ptr) = static_cast(p.value); + break; + } + case patch_type::be32: + case patch_type::bef32: + { + *reinterpret_cast*>(ptr) = static_cast(p.value); + break; + } + case patch_type::be64: + case patch_type::bef64: + { + *reinterpret_cast*>(ptr) = static_cast(p.value); + break; + } + } + } + + return (found->second.size() - rejected); +} diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index 562db18ba0..8bff69ab5f 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -39,4 +39,6 @@ public: // Apply patch (returns the number of entries applied) std::size_t apply(const std::string& name, u8* dst) const; + // Apply patch with a check that the address exists in SPU local storage + std::size_t apply_with_ls_check(const std::string&name, u8*dst, u32 filesz, u32 ls_addr) const; }; diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 6cb6850a19..32d8ed84d4 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -1148,6 +1148,80 @@ void ppu_load_exec(const ppu_exec_object& elf) // Initialize HLE modules ppu_initialize_modules(link); + // Embedded SPU elf patching + for (u32 i = _main->segs[0].addr; i < (_main->segs[0].addr + _main->segs[0].size); i += 4) + { + uchar* elf_header = vm::_ptr(i); + const spu_exec_object obj(fs::file(vm::base(vm::cast(i, HERE)), (_main->segs[0].addr + _main->segs[0].size) - i)); + + if (obj != elf_error::ok) + { + // This address does not have an SPU elf + continue; + } + + // Segment info dump + std::string dump; + + applied = 0; + + // Executable hash + sha1_context sha2; + sha1_starts(&sha2); + u8 sha1_hash[20]; + + for (const auto& prog : obj.progs) + { + // Only hash the data, we are not loading it + sha1_update(&sha2, reinterpret_cast(&prog.p_vaddr), sizeof(prog.p_vaddr)); + sha1_update(&sha2, reinterpret_cast(&prog.p_memsz), sizeof(prog.p_memsz)); + sha1_update(&sha2, reinterpret_cast(&prog.p_filesz), sizeof(prog.p_filesz)); + + fmt::append(dump, "\n\tSegment: p_type=0x%x, p_vaddr=0x%llx, p_filesz=0x%llx, p_memsz=0x%llx, p_offset=0x%llx", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz, prog.p_offset); + + if (prog.p_type == 0x1 /* LOAD */ && prog.p_filesz > 0) + { + sha1_update(&sha2, (elf_header + prog.p_offset), prog.p_filesz); + } + + else if (prog.p_type == 0x4 /* NOTE */ && prog.p_filesz > 0) + { + sha1_update(&sha2, (elf_header + prog.p_offset), prog.p_filesz); + + // We assume that the string SPUNAME exists 0x14 bytes into the NOTE segment + const auto spu_name = reinterpret_cast(elf_header + prog.p_offset + 0x14); + fmt::append(dump, "\n\tSPUNAME: '%s'", spu_name); + } + } + + sha1_finish(&sha2, sha1_hash); + + // Format patch name + std::string hash("SPU-0000000000000000000000000000000000000000"); + for (u32 i = 0; i < sizeof(sha1_hash); i++) + { + constexpr auto pal = "0123456789abcdef"; + hash[4 + i * 2] = pal[sha1_hash[i] >> 4]; + hash[5 + i * 2] = pal[sha1_hash[i] & 15]; + } + + // Try to patch each segment, will only succeed if the address exists in SPU local storage + for (const auto& prog : obj.progs) + { + // Apply the patch + applied += g_fxo->get()->apply_with_ls_check(hash, (elf_header + prog.p_offset), prog.p_filesz, prog.p_vaddr); + + if (!Emu.GetTitleID().empty()) + { + // Alternative patch + applied += g_fxo->get()->apply_with_ls_check(Emu.GetTitleID() + '-' + hash, (elf_header + prog.p_offset), prog.p_filesz, prog.p_vaddr); + } + } + + LOG_NOTICE(LOADER, "SPU executable hash: %s (<- %u)%s", hash, applied, dump); + + } + // Static HLE patching if (g_cfg.core.hook_functions) {