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
This commit is contained in:
Eladash 2021-09-06 10:33:44 +03:00 committed by Ivan
parent b217e8384c
commit 65c9cd99cd
9 changed files with 246 additions and 31 deletions

View file

@ -9,6 +9,8 @@
#include "util/endian.hpp" #include "util/endian.hpp"
#include "util/asm.hpp" #include "util/asm.hpp"
#include <charconv>
LOG_CHANNEL(patch_log, "PAT"); LOG_CHANNEL(patch_log, "PAT");
template <> template <>
@ -41,6 +43,7 @@ void fmt_class_string<patch_type>::format(std::string& out, u64 arg)
case patch_type::code_alloc: return "calloc"; case patch_type::code_alloc: return "calloc";
case patch_type::jump: return "jump"; case patch_type::jump: return "jump";
case patch_type::jump_link: return "jumpl"; case patch_type::jump_link: return "jumpl";
case patch_type::jump_func: return "jumpf";
case patch_type::load: return "load"; case patch_type::load: return "load";
case patch_type::byte: return "byte"; case patch_type::byte: return "byte";
case patch_type::le16: return "le16"; 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) switch (p_data.type)
{ {
case patch_type::utf8: case patch_type::utf8:
case patch_type::jump_func:
{ {
break; break;
} }
@ -546,7 +550,8 @@ void patch_engine::append_title_patches(const std::string& title_id)
} }
void ppu_register_range(u32 addr, u32 size); 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<vm::block_t>& ptr) void unmap_vm_area(std::shared_ptr<vm::block_t>& ptr)
{ {
@ -744,6 +749,55 @@ static usz apply_modification(std::basic_string<u32>& applied, const patch_engin
resval = out_branch & -4; resval = out_branch & -4;
break; 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: case patch_type::byte:
{ {
*ptr = static_cast<u8>(p.value.long_value); *ptr = static_cast<u8>(p.value.long_value);

View file

@ -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) 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, // Install special 32-bit jump instruction (PPU only atm)
jump_link, // jump + set link (PPU only atm) jump_link, // jump + set link (PPU only atm)
jump_func, // jump to exported function (PPU only, forever)
byte, byte,
le16, le16,
le32, le32,

View file

@ -1570,5 +1570,5 @@ DECLARE(ppu_module_manager::cellGcmSys)("cellGcmSys", []()
REG_FUNC(cellGcmSys, cellGcmGpadCaptureSnapshot); REG_FUNC(cellGcmSys, cellGcmGpadCaptureSnapshot);
// Special // Special
REG_FUNC(cellGcmSys, cellGcmCallback).flag(MFF_HIDDEN); REG_HIDDEN_FUNC(cellGcmCallback);
}); });

View file

@ -618,5 +618,5 @@ void cellSysutil_MsgDialog_init()
REG_FUNC(cellSysutil, cellMsgDialogAbort); REG_FUNC(cellSysutil, cellMsgDialogAbort);
// Helper Function // Helper Function
REG_FUNC(cellSysutil, exit_game).flag(MFF_HIDDEN); REG_HIDDEN_FUNC(exit_game);
} }

View file

@ -1250,5 +1250,5 @@ DECLARE(ppu_module_manager::cellVdec)("libvdec", []()
REG_FUNC(libvdec, cellVdecSetFrameRateExt); // 0xcffc42a5 REG_FUNC(libvdec, cellVdecSetFrameRateExt); // 0xcffc42a5
REG_FUNC(libvdec, cellVdecSetPts); // 0x3ce2e4f8 REG_FUNC(libvdec, cellVdecSetPts); // 0x3ce2e4f8
REG_FUNC(libvdec, vdecEntry).flag(MFF_HIDDEN); REG_HIDDEN_FUNC(vdecEntry);
}); });

View file

@ -37,21 +37,23 @@ extern void sys_initialize_tls(ppu_thread&, u64, u32, u32, u32);
// HLE function name cache // HLE function name cache
std::vector<std::string> g_ppu_function_names; std::vector<std::string> g_ppu_function_names;
extern u32 ppu_generate_id(const char* name) extern u32 ppu_generate_id(std::string_view name)
{ {
// Symbol name suffix // 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; sha1_context ctx;
u8 output[20]; u8 output[20];
// Compute SHA-1 hash // Compute SHA-1 hash
sha1_starts(&ctx); sha1_starts(&ctx);
sha1_update(&ctx, reinterpret_cast<const u8*>(name), std::strlen(name)); sha1_update(&ctx, reinterpret_cast<const u8*>(name.data()), name.size());
sha1_update(&ctx, reinterpret_cast<const u8*>(suffix), std::strlen(suffix)); sha1_update(&ctx, reinterpret_cast<const u8*>(suffix.data()), suffix.size());
sha1_finish(&ctx, output); sha1_finish(&ctx, output);
return reinterpret_cast<le_t<u32>&>(output[0]); le_t<u32> result = 0;
std::memcpy(&result, output, sizeof(result));
return result;
} }
ppu_static_module::ppu_static_module(const char* name) ppu_static_module::ppu_static_module(const char* name)
@ -331,15 +333,12 @@ 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); 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.static_func = &function.second;
flink.export_addr = g_fxo->get<ppu_function_manager>().func_addr(function.second.index); flink.export_addr = g_fxo->get<ppu_function_manager>().func_addr(function.second.index);
function.second.export_addr = &flink.export_addr; function.second.export_addr = &flink.export_addr;
} }
}
for (auto& variable : _module->variables) for (auto& variable : _module->variables)
{ {
@ -528,7 +527,12 @@ struct ppu_prx_module_info
be_t<u32> unk5; be_t<u32> 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<ppu_linkage_info>().modules[module_name].functions[fnid].export_addr;
}
// Load and register exports; return special exports found (nameless module) // 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) static auto ppu_load_exports(ppu_linkage_info* link, u32 exports_start, u32 exports_end)

View file

@ -18,7 +18,7 @@ constexpr const char* ppu_select_name(const char* /*name*/, const char* orig_nam
} }
// Generate FNID or VNID for given name // 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 // Overload for REG_FNID, REG_VNID macro
constexpr u32 ppu_generate_id(u32 id) 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_FORCED_HLE = (1 << 0), // Always call HLE function
MFF_PERFECT = (1 << 1), // Indicates complete implementation and LLE interchangeability 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 // HLE function information
@ -293,7 +293,9 @@ inline RT ppu_execute(ppu_thread& ppu, Args... args)
return func(ppu, 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<u32>(ppu.lr) & ~3), ppu_generate_id(nid)) #define BIND_FUNC_WITH_BLR(func) BIND_FUNC(func, ppu.cia = static_cast<u32>(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) #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_VAR(_module, var) REG_VNID(_module, #var, var)
#define REG_HIDDEN_FUNC(func) ppu_function_manager::register_function<decltype(&func), &func>(BIND_FUNC_WITH_BLR(func))
#define REG_HIDDEN_FUNC_PURE(func) ppu_function_manager::register_function<decltype(&func), &func>(func)
#define REINIT_FUNC(func) (ppu_module_manager::find_static_function<&func>().flags = 0, ppu_module_manager::find_static_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__) #define UNIMPLEMENTED_FUNC(_module) _module.todo("%s()", __func__)

View file

@ -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<ppu_function_t>(ptr)); return ppu_register_function_at(addr, size, reinterpret_cast<ppu_function_t>(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 struct ppu_far_jumps_t
{ {
std::unordered_map<u32, std::pair<u32, bool>> vals; struct all_info_t
{
u32 target;
bool link;
bool with_toc;
std::string module_name;
};
std::unordered_map<u32, all_info_t> vals;
mutable shared_mutex mutex; mutable shared_mutex mutex;
std::pair<u32, bool> 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); reader_lock lock(mutex);
if (auto it = vals.find(pc); it != vals.end()) 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<sizeof(ppu_func_opd_t)>(target))
{
// Avoid reading unmapped memory under mutex
from_opd = false;
}
if (from_opd)
{
auto& opd = vm::_ref<ppu_func_opd_t>(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 {}; return {};
@ -507,39 +584,103 @@ struct ppu_far_jumps_t
u32 ppu_get_far_jump(u32 pc) u32 ppu_get_far_jump(u32 pc)
{ {
g_fxo->init<ppu_far_jumps_t>(); g_fxo->init<ppu_far_jumps_t>();
return g_fxo->get<const ppu_far_jumps_t>().get_target(pc).first; return g_fxo->get<ppu_far_jumps_t>().get_target(pc);
} }
static bool ppu_far_jump(ppu_thread& ppu) static bool ppu_far_jump(ppu_thread& ppu)
{ {
auto [cia, link] = g_fxo->get<const ppu_far_jumps_t>().get_target(ppu.cia); const u32 cia = g_fxo->get<ppu_far_jumps_t>().get_target(ppu.cia, &ppu);
if (link) ppu.lr = ppu.cia + 4;
if (!vm::check_addr(cia, vm::page_executable))
{
fmt::throw_exception("PPU far jump failed! (returned cia = 0x%08x)", cia);
}
ppu.cia = cia; ppu.cia = cia;
return false; 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; entry &= -4;
// Exported functions are using target as FNID, must not be changed
if (module_name.empty())
{
target &= -4; target &= -4;
if (entry == target || !vm::check_addr(entry, vm::page_executable) || !vm::check_addr(target, vm::page_executable)) 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; return false;
} }
if (module_name.empty())
g_fxo->init<ppu_far_jumps_t>(); g_fxo->init<ppu_far_jumps_t>();
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 // Register branch target in host memory, not guest memory
auto& jumps = g_fxo->get<ppu_far_jumps_t>(); auto& jumps = g_fxo->get<ppu_far_jumps_t>();
std::lock_guard lock(jumps.mutex); 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<typename ppu_far_jumps_t::all_info_t>{target, link, with_toc, std::move(module_name)});
ppu_register_function_at(entry, 4, &ppu_far_jump); ppu_register_function_at(entry, 4, &ppu_far_jump);
return true; 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) void ppu_remove_hle_instructions(u32 addr, u32 size)
{ {
g_fxo->init<ppu_far_jumps_t>(); g_fxo->init<ppu_far_jumps_t>();
@ -693,7 +834,7 @@ std::array<u32, 2> op_branch_targets(u32 pc, ppu_opcode_t op)
g_fxo->need<ppu_far_jumps_t>(); g_fxo->need<ppu_far_jumps_t>();
if (u32 target = g_fxo->get<const ppu_far_jumps_t>().get_target(pc).first) if (u32 target = g_fxo->get<ppu_far_jumps_t>().get_target(pc))
{ {
res[0] = target; res[0] = target;
return res; return res;

View file

@ -307,6 +307,15 @@ public:
static constexpr u32 call_history_max_size = 4096; 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_call_with_toc_info_t> hle_func_calls_with_toc_info;
// For named_thread ctor // For named_thread ctor
const struct thread_name_t const struct thread_name_t
{ {