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.
This commit is contained in:
Malcolm Jestadt 2020-01-07 04:10:23 -05:00 committed by Ivan
parent 7f07b79c04
commit ad8988afd3
3 changed files with 151 additions and 0 deletions

View File

@ -173,3 +173,78 @@ std::size_t patch_engine::apply(const std::string& name, u8* dst) const
return found->second.size(); 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<u8>(p.value);
break;
}
case patch_type::le16:
{
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
break;
}
case patch_type::le32:
case patch_type::lef32:
{
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
break;
}
case patch_type::le64:
case patch_type::lef64:
{
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
break;
}
case patch_type::be16:
{
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
break;
}
case patch_type::be32:
case patch_type::bef32:
{
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
break;
}
case patch_type::be64:
case patch_type::bef64:
{
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
break;
}
}
}
return (found->second.size() - rejected);
}

View File

@ -39,4 +39,6 @@ public:
// Apply patch (returns the number of entries applied) // Apply patch (returns the number of entries applied)
std::size_t apply(const std::string& name, u8* dst) const; 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;
}; };

View File

@ -1148,6 +1148,80 @@ void ppu_load_exec(const ppu_exec_object& elf)
// Initialize HLE modules // Initialize HLE modules
ppu_initialize_modules(link); 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<u8>(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<const uchar*>(&prog.p_vaddr), sizeof(prog.p_vaddr));
sha1_update(&sha2, reinterpret_cast<const uchar*>(&prog.p_memsz), sizeof(prog.p_memsz));
sha1_update(&sha2, reinterpret_cast<const uchar*>(&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<const char*>(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<patch_engine>()->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<patch_engine>()->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 // Static HLE patching
if (g_cfg.core.hook_functions) if (g_cfg.core.hook_functions)
{ {