SPU: Embedded PRX/overlay image patching and dumping

This commit is contained in:
Eladash 2020-10-03 08:07:13 +03:00 committed by Ivan
parent d5f7e7b179
commit 983547af7e
3 changed files with 123 additions and 72 deletions

View file

@ -694,6 +694,112 @@ static auto ppu_load_imports(std::vector<ppu_reloc>& relocs, ppu_linkage_info* l
return result;
}
static void ppu_check_patch_spu_images(const ppu_segment& seg)
{
const std::string_view seg_view{vm::get_super_ptr<char>(seg.addr), seg.size};
for (std::size_t i = seg_view.find("\177ELF"); i < seg.size; i = seg_view.find("\177ELF", i + 4))
{
const auto elf_header = vm::get_super_ptr<u8>(seg.addr + i);
// Try to load SPU image
const spu_exec_object obj(fs::file(elf_header, seg.size - i));
if (obj != elf_error::ok)
{
// This address does not have an SPU elf
continue;
}
// Segment info dump
std::vector<std::string> names;
std::string dump;
std::size_t 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 == 0x1u /* LOAD */ && prog.p_filesz > 0u)
{
sha1_update(&sha2, (elf_header + prog.p_offset), prog.p_filesz);
}
else if (prog.p_type == 0x4u /* NOTE */ && prog.p_filesz > 0u)
{
std::unique_ptr<u8[]> buf(new u8[prog.p_filesz + 0x15]{0});
std::memcpy(buf.get(), (elf_header + prog.p_offset), prog.p_filesz);
sha1_update(&sha2, buf.get(), prog.p_filesz);
// We assume that the string SPUNAME exists 0x14 bytes into the NOTE segment
// If NOTE section is not big enough for a whole string it will get truncated
// If NOTE section is too small to contain a name it won't extract a name
names.emplace_back(reinterpret_cast<char*>(buf.get() + 0x14));
if (!names.back().empty())
{
fmt::append(dump, "\n\tSPUNAME: '%s'", names.back());
}
else
{
names.resize(names.size() - 1);
}
}
}
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];
}
if (g_cfg.core.spu_debug)
{
std::string final_name;
for (const auto& name : names)
{
final_name += vfs::escape(name, true);
final_name += '-'; // Each name proceeded by '-' and also the last one
}
fs::file dump_file(fs::get_cache_dir() + "/spu_progs/" + final_name + hash.substr(4), fs::rewrite);
obj.save(dump_file);
}
// 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);
}
}
ppu_loader.success("SPU executable hash: %s (<- %u)%s", hash, applied, dump);
}
}
std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::string& path)
{
// Create new PRX object
@ -966,6 +1072,12 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
applied += g_fxo->get<patch_engine>()->apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr);
}
// Embedded SPU elf patching
for (const auto& seg : prx->segs)
{
ppu_check_patch_spu_images(seg);
}
ppu_loader.success("PRX library hash: %s (<- %u)", hash, applied);
if (Emu.IsReady() && g_fxo->get<ppu_module>()->segs.empty())
@ -1133,77 +1245,9 @@ void ppu_load_exec(const ppu_exec_object& elf)
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 += 128)
for (const auto& seg : _main->segs)
{
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 == 0x1u /* LOAD */ && prog.p_filesz > 0u)
{
sha1_update(&sha2, (elf_header + prog.p_offset), prog.p_filesz);
}
else if (prog.p_type == 0x4u /* NOTE */ && prog.p_filesz > 0u)
{
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);
}
}
ppu_loader.success("SPU executable hash: %s (<- %u)%s", hash, applied, dump);
ppu_check_patch_spu_images(seg);
}
// Static HLE patching
@ -1699,6 +1743,12 @@ std::shared_ptr<lv2_overlay> ppu_load_overlay(const ppu_exec_object& elf, const
applied += g_fxo->get<patch_engine>()->apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr);
}
// Embedded SPU elf patching
for (const auto& seg : ovlm->segs)
{
ppu_check_patch_spu_images(seg);
}
ppu_loader.success("OVL executable hash: %s (<- %u)", hash, applied);
// Load other programs

View file

@ -298,6 +298,7 @@ void Emulator::Init()
make_path_verbose(fs::get_cache_dir() + "shaderlog/");
make_path_verbose(fs::get_config_dir() + "captures/");
make_path_verbose(fs::get_cache_dir() + "spu_progs/");
// Initialize patch engine
g_fxo->init<patch_engine>()->append_global_patches();

View file

@ -1,4 +1,4 @@
#pragma once
#pragma once
#include "../../Utilities/types.h"
#include "../../Utilities/File.h"
@ -293,7 +293,7 @@ public:
header.e_curver = 1;
header.e_os_abi = OS != elf_os::none ? OS : this->header.e_os_abi;
header.e_abi_ver = this->header.e_abi_ver;
header.e_type = Type != elf_type::none ? Type : this->header.e_type;
header.e_type = Type != elf_type::none ? Type : static_cast<elf_type>(this->header.e_type);
header.e_machine = Machine;
header.e_version = 1;
header.e_entry = this->header.e_entry;