mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
PPU LLVM: Implement SELF precompilation
Do not use PS3 memory for precompilation.
This commit is contained in:
parent
7062ead4fd
commit
554b27a82a
@ -805,7 +805,7 @@ void unmap_vm_area(std::shared_ptr<vm::block_t>& ptr)
|
||||
}
|
||||
|
||||
// Returns old 'applied' size
|
||||
static usz apply_modification(std::basic_string<u32>& applied, patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr)
|
||||
static usz apply_modification(std::basic_string<u32>& applied, patch_engine::patch_info& patch, std::function<u8*(u32)> mem_translate, u32 filesz, u32 min_addr)
|
||||
{
|
||||
const usz old_applied_size = applied.size();
|
||||
|
||||
@ -846,8 +846,8 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
|
||||
{
|
||||
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)
|
||||
// Do not allow null address or if resultant ptr is not a VM ptr
|
||||
if (const u32 alloc_at = vm::try_get_addr(mem_translate(p.offset & -4096)).first; alloc_at >> 16)
|
||||
{
|
||||
const u32 alloc_size = utils::align(static_cast<u32>(p.value.long_value) + alloc_at % 4096, 4096);
|
||||
|
||||
@ -934,7 +934,13 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
|
||||
|
||||
offset -= min_addr;
|
||||
|
||||
auto ptr = dst + offset;
|
||||
auto ptr = mem_translate(offset);
|
||||
|
||||
if (!ptr)
|
||||
{
|
||||
// Memory translation failed
|
||||
continue;
|
||||
}
|
||||
|
||||
if (relocate_instructions_at)
|
||||
{
|
||||
@ -960,7 +966,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
|
||||
}
|
||||
case patch_type::code_alloc:
|
||||
{
|
||||
const u32 out_branch = vm::try_get_addr(dst + (offset & -4)).first;
|
||||
const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4)).first;
|
||||
|
||||
// Allow only if points to a PPU executable instruction
|
||||
if (out_branch < 0x10000 || out_branch >= 0x4000'0000 || !vm::check_addr<4>(out_branch, vm::page_executable))
|
||||
@ -1044,7 +1050,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
|
||||
case patch_type::jump:
|
||||
case patch_type::jump_link:
|
||||
{
|
||||
const u32 out_branch = vm::try_get_addr(dst + (offset & -4)).first;
|
||||
const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4)).first;
|
||||
const u32 dest = static_cast<u32>(p.value.long_value);
|
||||
|
||||
// Allow only if points to a PPU executable instruction
|
||||
@ -1060,7 +1066,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
|
||||
{
|
||||
const std::string& str = p.original_value;
|
||||
|
||||
const u32 out_branch = vm::try_get_addr(dst + (offset & -4)).first;
|
||||
const u32 out_branch = vm::try_get_addr(mem_translate(offset & -4)).first;
|
||||
const usz sep_pos = str.find_first_of(':');
|
||||
|
||||
// Must contain only a single ':' or none
|
||||
@ -1251,7 +1257,7 @@ static usz apply_modification(std::basic_string<u32>& applied, patch_engine::pat
|
||||
return old_applied_size;
|
||||
}
|
||||
|
||||
std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32 filesz, u32 min_addr)
|
||||
std::basic_string<u32> patch_engine::apply(const std::string& name, std::function<u8*(u32)> mem_translate, u32 filesz, u32 min_addr)
|
||||
{
|
||||
if (!m_map.contains(name))
|
||||
{
|
||||
@ -1392,7 +1398,7 @@ std::basic_string<u32> patch_engine::apply(const std::string& name, u8* dst, u32
|
||||
{
|
||||
if (patch)
|
||||
{
|
||||
const usz old_size = apply_modification(applied_total, *patch, dst, filesz, min_addr);
|
||||
const usz old_size = apply_modification(applied_total, *patch, mem_translate, filesz, min_addr);
|
||||
|
||||
if (applied_total.size() != old_size)
|
||||
{
|
||||
|
@ -3,6 +3,7 @@
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <functional>
|
||||
|
||||
#include "util/types.hpp"
|
||||
#include "util/yaml.hpp"
|
||||
@ -212,7 +213,7 @@ public:
|
||||
void append_title_patches(const std::string& title_id);
|
||||
|
||||
// Apply patch (returns the number of entries applied)
|
||||
std::basic_string<u32> apply(const std::string& name, u8* dst, u32 filesz = -1, u32 min_addr = 0);
|
||||
std::basic_string<u32> apply(const std::string& name, std::function<u8*(u32)> mem_translate, u32 filesz = -1, u32 min_addr = 0);
|
||||
|
||||
// Deallocate memory used by patches
|
||||
void unload(const std::string& name);
|
||||
|
@ -78,7 +78,7 @@ void ppu_module::validate(u32 reloc)
|
||||
|
||||
if (size && size != funcs[index].size)
|
||||
{
|
||||
if (size + 4 != funcs[index].size || vm::read32(addr + size) != ppu_instructions::NOP())
|
||||
if (size + 4 != funcs[index].size || *ensure(get_ptr<u32>(addr + size)) != ppu_instructions::NOP())
|
||||
{
|
||||
ppu_validator.error("%s.yml : function size mismatch at 0x%x(size=0x%x) (0x%x, 0x%x)", path, found, funcs[index].size, addr, size);
|
||||
}
|
||||
@ -112,9 +112,9 @@ void ppu_module::validate(u32 reloc)
|
||||
}
|
||||
}
|
||||
|
||||
static u32 ppu_test(const vm::cptr<u32> ptr, vm::cptr<void> fend, ppu_pattern_array pat)
|
||||
static u32 ppu_test(const be_t<u32>* ptr, const void* fend, ppu_pattern_array pat)
|
||||
{
|
||||
vm::cptr<u32> cur = ptr;
|
||||
const be_t<u32>* cur = ptr;
|
||||
|
||||
for (auto& p : pat)
|
||||
{
|
||||
@ -141,10 +141,10 @@ static u32 ppu_test(const vm::cptr<u32> ptr, vm::cptr<void> fend, ppu_pattern_ar
|
||||
cur++;
|
||||
}
|
||||
|
||||
return cur.addr() - ptr.addr();
|
||||
return (cur - ptr) * sizeof(*ptr);
|
||||
}
|
||||
|
||||
static u32 ppu_test(vm::cptr<u32> ptr, vm::cptr<void> fend, ppu_pattern_matrix pats)
|
||||
static u32 ppu_test(const be_t<u32>* ptr, const void* fend, ppu_pattern_matrix pats)
|
||||
{
|
||||
for (auto pat : pats)
|
||||
{
|
||||
@ -585,6 +585,14 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
return func;
|
||||
};
|
||||
|
||||
static const auto advance = [](auto& _ptr, auto& ptr, u32 count)
|
||||
{
|
||||
const auto old_ptr = ptr;
|
||||
_ptr += count;
|
||||
ptr += count;
|
||||
return old_ptr;
|
||||
};
|
||||
|
||||
// Register new TOC and find basic set of functions
|
||||
auto add_toc = [&](u32 toc)
|
||||
{
|
||||
@ -596,16 +604,24 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
// Grope for OPD section (TODO: optimization, better constraints)
|
||||
for (const auto& seg : segs)
|
||||
{
|
||||
if (!seg.addr) continue;
|
||||
if (seg.size < 8) continue;
|
||||
|
||||
for (vm::cptr<u32> ptr = vm::cast(seg.addr); ptr.addr() < seg.addr + seg.size; ptr++)
|
||||
const vm::cptr<void> seg_end = vm::cast(seg.addr + seg.size - 8);
|
||||
vm::cptr<u32> _ptr = vm::cast(seg.addr);
|
||||
auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
for (; _ptr <= seg_end;)
|
||||
{
|
||||
if (ptr[0] >= start && ptr[0] < end && ptr[0] % 4 == 0 && ptr[1] == toc)
|
||||
if (ptr[1] == toc && ptr[0] >= start && ptr[0] < end && ptr[0] % 4 == 0)
|
||||
{
|
||||
// New function
|
||||
ppu_log.trace("OPD*: [0x%x] 0x%x (TOC=0x%x)", ptr, ptr[0], ptr[1]);
|
||||
add_func(*ptr, addr_heap.count(ptr.addr()) ? toc : 0, 0);
|
||||
ptr++;
|
||||
ppu_log.trace("OPD*: [0x%x] 0x%x (TOC=0x%x)", _ptr, ptr[0], ptr[1]);
|
||||
add_func(*ptr, addr_heap.count(_ptr.addr()) ? toc : 0, 0);
|
||||
advance(_ptr, ptr, 2);
|
||||
}
|
||||
else
|
||||
{
|
||||
advance(_ptr, ptr, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -621,9 +637,13 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
// Find references indiscriminately
|
||||
for (const auto& seg : segs)
|
||||
{
|
||||
if (!seg.addr) continue;
|
||||
if (seg.size < 4) continue;
|
||||
|
||||
for (vm::cptr<u32> ptr = vm::cast(seg.addr); ptr.addr() < seg.addr + seg.size; ptr++)
|
||||
vm::cptr<u32> _ptr = vm::cast(seg.addr);
|
||||
const vm::cptr<void> seg_end = vm::cast(seg.addr + seg.size - 4);
|
||||
auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
for (vm::cptr<u32> _ptr = vm::cast(seg.addr); _ptr <= seg_end; advance(_ptr, ptr, 1))
|
||||
{
|
||||
const u32 value = *ptr;
|
||||
|
||||
@ -651,15 +671,17 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
vm::cptr<void> sec_end = vm::cast(sec.addr + sec.size);
|
||||
|
||||
// Probe
|
||||
for (vm::cptr<u32> ptr = vm::cast(sec.addr); ptr < sec_end; ptr += 2)
|
||||
for (vm::cptr<u32> _ptr = vm::cast(sec.addr); _ptr < sec_end; _ptr += 2)
|
||||
{
|
||||
if (ptr + 6 <= sec_end && !ptr[0] && !ptr[2] && ptr[1] == ptr[4] && ptr[3] == ptr[5])
|
||||
auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
if (_ptr + 6 <= sec_end && !ptr[0] && !ptr[2] && ptr[1] == ptr[4] && ptr[3] == ptr[5])
|
||||
{
|
||||
// Special OPD format case (some homebrews)
|
||||
ptr += 4;
|
||||
advance(_ptr, ptr, 4);
|
||||
}
|
||||
|
||||
if (ptr + 2 > sec_end)
|
||||
if (_ptr + 2 > sec_end)
|
||||
{
|
||||
sec_end.set(0);
|
||||
break;
|
||||
@ -673,7 +695,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
//const u32 _toc_end = _toc + 0x8000;
|
||||
|
||||
// TODO: improve TOC constraints
|
||||
if (_toc % 4 || !vm::check_addr(_toc) || _toc >= 0x40000000 || (_toc >= start && _toc < end))
|
||||
if (_toc % 4 || !get_ptr<u32>(_toc) || _toc >= 0x40000000 || (_toc >= start && _toc < end))
|
||||
{
|
||||
sec_end.set(0);
|
||||
break;
|
||||
@ -689,18 +711,20 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
if (sec_end) ppu_log.notice("Reading OPD section at 0x%x...", sec.addr);
|
||||
|
||||
// Mine
|
||||
for (vm::cptr<u32> ptr = vm::cast(sec.addr); ptr < sec_end; ptr += 2)
|
||||
for (vm::cptr<u32> _ptr = vm::cast(sec.addr); _ptr < sec_end; _ptr += 2)
|
||||
{
|
||||
auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
// Special case: see "Probe"
|
||||
if (!ptr[0]) ptr += 4;
|
||||
if (!ptr[0]) advance(_ptr, ptr, 4);
|
||||
|
||||
// Add function and TOC
|
||||
const u32 addr = ptr[0];
|
||||
const u32 toc = ptr[1];
|
||||
ppu_log.trace("OPD: [0x%x] 0x%x (TOC=0x%x)", ptr, addr, toc);
|
||||
ppu_log.trace("OPD: [0x%x] 0x%x (TOC=0x%x)", _ptr, addr, toc);
|
||||
|
||||
TOCs.emplace(toc);
|
||||
auto& func = add_func(addr, addr_heap.count(ptr.addr()) ? toc : 0, 0);
|
||||
auto& func = add_func(addr, addr_heap.count(_ptr.addr()) ? toc : 0, 0);
|
||||
func.attr += ppu_attr::known_addr;
|
||||
known_functions.emplace(addr);
|
||||
}
|
||||
@ -709,7 +733,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
// Register TOC from entry point
|
||||
if (entry && !lib_toc)
|
||||
{
|
||||
lib_toc = vm::read32(entry) ? vm::read32(entry + 4) : vm::read32(entry + 20);
|
||||
lib_toc = *ensure(get_ptr<u32>(entry)) ? *ensure(get_ptr<u32>(entry + 4)) : *ensure(get_ptr<u32>(entry + 20));
|
||||
}
|
||||
|
||||
// Secondary attempt
|
||||
@ -733,23 +757,25 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
vm::cptr<void> sec_end = vm::cast(sec.addr + sec.size);
|
||||
|
||||
// Probe
|
||||
for (vm::cptr<u32> ptr = vm::cast(sec.addr); ptr < sec_end;)
|
||||
for (vm::cptr<u32> _ptr = vm::cast(sec.addr); _ptr < sec_end;)
|
||||
{
|
||||
if (!ptr.aligned() || ptr.addr() < sec.addr || ptr >= sec_end)
|
||||
if (!_ptr.aligned() || _ptr.addr() < sec.addr || _ptr >= sec_end)
|
||||
{
|
||||
sec_end.set(0);
|
||||
break;
|
||||
}
|
||||
|
||||
const auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
const u32 size = ptr[0] + 4;
|
||||
|
||||
if (size == 4 && ptr + 1 == sec_end)
|
||||
if (size == 4 && _ptr + 1 == sec_end)
|
||||
{
|
||||
// Null terminator
|
||||
break;
|
||||
}
|
||||
|
||||
if (size % 4 || size < 0x10 || ptr + size / 4 > sec_end)
|
||||
if (size % 4 || size < 0x10 || _ptr + size / 4 > sec_end)
|
||||
{
|
||||
sec_end.set(0);
|
||||
break;
|
||||
@ -757,7 +783,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
|
||||
if (ptr[1])
|
||||
{
|
||||
const u32 cie_off = ptr.addr() - ptr[1] + 4;
|
||||
const u32 cie_off = _ptr.addr() - ptr[1] + 4;
|
||||
|
||||
if (cie_off % 4 || cie_off < sec.addr || cie_off >= sec_end.addr())
|
||||
{
|
||||
@ -766,14 +792,16 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
}
|
||||
}
|
||||
|
||||
ptr = vm::cast(ptr.addr() + size);
|
||||
_ptr = vm::cast(_ptr.addr() + size);
|
||||
}
|
||||
|
||||
if (sec_end && sec.size > 4) ppu_log.notice("Reading .eh_frame section at 0x%x...", sec.addr);
|
||||
|
||||
// Mine
|
||||
for (vm::cptr<u32> ptr = vm::cast(sec.addr); ptr < sec_end; ptr = vm::cast(ptr.addr() + ptr[0] + 4))
|
||||
for (vm::cptr<u32> _ptr = vm::cast(sec.addr); _ptr < sec_end; _ptr = vm::cast(_ptr.addr() + *get_ptr<u32>(_ptr) + 4))
|
||||
{
|
||||
const auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
if (ptr[0] == 0u)
|
||||
{
|
||||
// Null terminator
|
||||
@ -788,7 +816,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
else
|
||||
{
|
||||
// Get associated CIE (currently unused)
|
||||
const vm::cptr<u32> cie = vm::cast(ptr.addr() - ptr[1] + 4);
|
||||
const vm::cptr<u32> cie = vm::cast(_ptr.addr() - ptr[1] + 4);
|
||||
|
||||
u32 addr = 0;
|
||||
u32 size = 0;
|
||||
@ -817,7 +845,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
// TODO: absolute/relative offset (approximation)
|
||||
if (addr > 0xc0000000)
|
||||
{
|
||||
addr += ptr.addr() + 8;
|
||||
addr += _ptr.addr() + 8;
|
||||
}
|
||||
|
||||
ppu_log.trace(".eh_frame: [0x%x] FDE 0x%x (cie=*0x%x, addr=0x%x, size=0x%x)", ptr, ptr[0], cie, addr, size);
|
||||
@ -875,15 +903,16 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
if (func.blocks.empty())
|
||||
{
|
||||
// Special function analysis
|
||||
const vm::cptr<u32> ptr = vm::cast(func.addr);
|
||||
const vm::cptr<u32> _ptr = vm::cast(func.addr);
|
||||
const vm::cptr<void> fend = vm::cast(end);
|
||||
const auto ptr = get_ptr<u32>(_ptr);
|
||||
|
||||
using namespace ppu_instructions;
|
||||
|
||||
if (ptr + 1 <= fend && (ptr[0] & 0xfc000001) == B({}, {}))
|
||||
if (_ptr + 1 <= fend && (ptr[0] & 0xfc000001) == B({}, {}))
|
||||
{
|
||||
// Simple trampoline
|
||||
const u32 target = (ptr[0] & 0x2 ? 0 : ptr.addr()) + ppu_opcode_t{ptr[0]}.bt24;
|
||||
const u32 target = (ptr[0] & 0x2 ? 0 : _ptr.addr()) + ppu_opcode_t{ptr[0]}.bt24;
|
||||
|
||||
if (target == func.addr)
|
||||
{
|
||||
@ -913,7 +942,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr + 0x4 <= fend &&
|
||||
if (_ptr + 0x4 <= fend &&
|
||||
(ptr[0] & 0xffff0000) == LIS(r11, 0) &&
|
||||
(ptr[1] & 0xffff0000) == ADDI(r11, r11, 0) &&
|
||||
ptr[2] == MTCTR(r11) &&
|
||||
@ -941,7 +970,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr + 0x7 <= fend &&
|
||||
if (_ptr + 0x7 <= fend &&
|
||||
ptr[0] == STD(r2, r1, 0x28) &&
|
||||
(ptr[1] & 0xffff0000) == ADDIS(r12, r2, {}) &&
|
||||
(ptr[2] & 0xffff0000) == LWZ(r11, r12, {}) &&
|
||||
@ -957,9 +986,10 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
func.attr += ppu_attr::known_size;
|
||||
|
||||
// Look for another imports to fill gaps (hack)
|
||||
auto p2 = ptr + 7;
|
||||
auto _p2 = _ptr + 7;
|
||||
auto p2 = get_ptr<u32>(_p2);
|
||||
|
||||
while (p2 + 0x7 <= fend &&
|
||||
while (_p2 + 0x7 <= fend &&
|
||||
p2[0] == STD(r2, r1, 0x28) &&
|
||||
(p2[1] & 0xffff0000) == ADDIS(r12, r2, {}) &&
|
||||
(p2[2] & 0xffff0000) == LWZ(r11, r12, {}) &&
|
||||
@ -968,18 +998,18 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
p2[5] == MTCTR(r11) &&
|
||||
p2[6] == BCTR())
|
||||
{
|
||||
auto& next = add_func(p2.addr(), -1, func.addr);
|
||||
auto& next = add_func(_p2.addr(), -1, func.addr);
|
||||
next.size = 0x1C;
|
||||
next.blocks.emplace(next.addr, next.size);
|
||||
next.attr += ppu_attr::known_addr;
|
||||
next.attr += ppu_attr::known_size;
|
||||
p2 += 7;
|
||||
advance(_p2, p2, 7);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr + 0x7 <= fend &&
|
||||
if (_ptr + 0x7 <= fend &&
|
||||
ptr[0] == STD(r2, r1, 0x28) &&
|
||||
(ptr[1] & 0xffff0000) == ADDIS(r2, r2, {}) &&
|
||||
(ptr[2] & 0xffff0000) == ADDI(r2, r2, {}) &&
|
||||
@ -1029,7 +1059,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr + 4 <= fend &&
|
||||
if (_ptr + 4 <= fend &&
|
||||
ptr[0] == STD(r2, r1, 0x28) &&
|
||||
(ptr[1] & 0xffff0000) == ADDIS(r2, r2, {}) &&
|
||||
(ptr[2] & 0xffff0000) == ADDI(r2, r2, {}) &&
|
||||
@ -1037,7 +1067,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
{
|
||||
// Trampoline with TOC
|
||||
const u32 toc_add = (ptr[1] << 16) + s16(ptr[2]);
|
||||
const u32 target = (ptr[3] & 0x2 ? 0 : (ptr + 3).addr()) + ppu_opcode_t{ptr[3]}.bt24;
|
||||
const u32 target = (ptr[3] & 0x2 ? 0 : (_ptr + 3).addr()) + ppu_opcode_t{ptr[3]}.bt24;
|
||||
|
||||
if (target >= start && target < end)
|
||||
{
|
||||
@ -1076,7 +1106,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
}
|
||||
}
|
||||
|
||||
if (ptr + 8 <= fend &&
|
||||
if (_ptr + 8 <= fend &&
|
||||
(ptr[0] & 0xffff0000) == LI(r12, 0) &&
|
||||
(ptr[1] & 0xffff0000) == ORIS(r12, r12, 0) &&
|
||||
(ptr[2] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
@ -1095,9 +1125,10 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
func.attr += ppu_attr::known_size;
|
||||
|
||||
// Look for another imports to fill gaps (hack)
|
||||
auto p2 = ptr + 8;
|
||||
auto _p2 = _ptr + 8;
|
||||
auto p2 = get_ptr<u32>(_p2);
|
||||
|
||||
while (p2 + 8 <= fend &&
|
||||
while (_p2 + 8 <= fend &&
|
||||
(p2[0] & 0xffff0000) == LI(r12, 0) &&
|
||||
(p2[1] & 0xffff0000) == ORIS(r12, r12, 0) &&
|
||||
(p2[2] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
@ -1107,19 +1138,19 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
p2[6] == MTCTR(r0) &&
|
||||
p2[7] == BCTR())
|
||||
{
|
||||
auto& next = add_func(p2.addr(), -1, func.addr);
|
||||
auto& next = add_func(_p2.addr(), -1, func.addr);
|
||||
next.size = 0x20;
|
||||
next.blocks.emplace(next.addr, next.size);
|
||||
next.attr += ppu_attr::known_addr;
|
||||
next.attr += ppu_attr::known_size;
|
||||
p2 += 8;
|
||||
advance(_p2, p2, 8);
|
||||
known_functions.emplace(next.addr);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ptr + 3 <= fend &&
|
||||
if (_ptr + 3 <= fend &&
|
||||
ptr[0] == 0x7c0004acu &&
|
||||
ptr[1] == 0x00000000u &&
|
||||
ptr[2] == BLR())
|
||||
@ -1131,7 +1162,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
continue;
|
||||
}
|
||||
|
||||
if (const u32 len = ppu_test(ptr, fend, ppu_patterns::abort))
|
||||
if (const u32 len = ppu_test(ptr, get_ptr<void>(fend), ppu_patterns::abort))
|
||||
{
|
||||
// Function "abort"
|
||||
ppu_log.notice("Function [0x%x]: 'abort'", func.addr);
|
||||
@ -1199,10 +1230,13 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
{
|
||||
auto& block = block_queue[j].get();
|
||||
|
||||
for (vm::cptr<u32> _ptr = vm::cast(block.first); _ptr.addr() < func_end;)
|
||||
vm::cptr<u32> _ptr = vm::cast(block.first);
|
||||
auto ptr = ensure(get_ptr<u32>(_ptr));
|
||||
|
||||
for (; _ptr.addr() < func_end;)
|
||||
{
|
||||
const u32 iaddr = _ptr.addr();
|
||||
const ppu_opcode_t op{*_ptr++};
|
||||
const ppu_opcode_t op{*advance(_ptr, ptr, 1)};
|
||||
const ppu_itype::type type = s_ppu_itype.decode(op.opcode);
|
||||
|
||||
if (type == ppu_itype::UNK)
|
||||
@ -1276,9 +1310,9 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
const u32 jt_addr = _ptr.addr();
|
||||
const u32 jt_end = func_end;
|
||||
|
||||
for (; _ptr.addr() < jt_end; _ptr++)
|
||||
for (; _ptr.addr() < jt_end; advance(_ptr, ptr, 1))
|
||||
{
|
||||
const u32 addr = jt_addr + *_ptr;
|
||||
const u32 addr = jt_addr + *ptr;
|
||||
|
||||
if (addr == jt_addr)
|
||||
{
|
||||
@ -1331,7 +1365,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
block.second = _ptr.addr() - block.first;
|
||||
break;
|
||||
}
|
||||
else if (type == ppu_itype::STDU && func.attr & ppu_attr::no_size && (op.opcode == *_ptr || *_ptr == ppu_instructions::BLR()))
|
||||
else if (type == ppu_itype::STDU && func.attr & ppu_attr::no_size && (op.opcode == *ptr || *ptr == ppu_instructions::BLR()))
|
||||
{
|
||||
// Hack
|
||||
ppu_log.success("[0x%x] Instruction repetition: 0x%08x", iaddr, op.opcode);
|
||||
@ -1391,7 +1425,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
for (vm::cptr<u32> _ptr = vm::cast(block.first); _ptr.addr() < block.first + block.second;)
|
||||
{
|
||||
const u32 iaddr = _ptr.addr();
|
||||
const ppu_opcode_t op{*_ptr++};
|
||||
const ppu_opcode_t op{*ensure(get_ptr<u32>(_ptr++))};
|
||||
const ppu_itype::type type = s_ppu_itype.decode(op.opcode);
|
||||
|
||||
if (type == ppu_itype::B || type == ppu_itype::BC)
|
||||
@ -1465,7 +1499,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
for (vm::cptr<u32> _ptr = vm::cast(start); _ptr.addr() < next;)
|
||||
{
|
||||
const u32 addr = _ptr.addr();
|
||||
const ppu_opcode_t op{*_ptr++};
|
||||
const ppu_opcode_t op{*ensure(get_ptr<u32>(_ptr++))};
|
||||
const ppu_itype::type type = s_ppu_itype.decode(op.opcode);
|
||||
|
||||
if (type == ppu_itype::UNK)
|
||||
@ -1607,7 +1641,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
continue;
|
||||
}
|
||||
|
||||
const u32 target = vm::_ref<u32>(rel.addr);
|
||||
const u32 target = *ensure(get_ptr<u32>(rel.addr));
|
||||
|
||||
if (target % 4 || target < start || target >= end)
|
||||
{
|
||||
@ -1684,7 +1718,7 @@ bool ppu_module::analyse(u32 lib_toc, u32 entry, const u32 sec_end, const std::b
|
||||
|
||||
for (; i_pos < lim; i_pos += 4)
|
||||
{
|
||||
const u32 opc = vm::_ref<u32>(i_pos);
|
||||
const u32 opc = *ensure(get_ptr<u32>(i_pos));
|
||||
|
||||
switch (auto type = s_ppu_itype.decode(opc))
|
||||
{
|
||||
|
@ -3,8 +3,10 @@
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <deque>
|
||||
#include "util/types.hpp"
|
||||
#include "util/endian.hpp"
|
||||
#include "util/to_endian.hpp"
|
||||
|
||||
#include "Utilities/bit_set.h"
|
||||
#include "PPUOpcodes.h"
|
||||
@ -65,6 +67,7 @@ struct ppu_segment
|
||||
u32 type;
|
||||
u32 flags;
|
||||
u32 filesz;
|
||||
void* ptr{};
|
||||
};
|
||||
|
||||
// PPU Module Information
|
||||
@ -89,6 +92,8 @@ struct ppu_module
|
||||
std::vector<ppu_segment> segs{};
|
||||
std::vector<ppu_segment> secs{};
|
||||
std::vector<ppu_function> funcs{};
|
||||
std::deque<std::shared_ptr<void>> allocations;
|
||||
std::map<u32, u32> addr_to_seg_index;
|
||||
|
||||
// Copy info without functions
|
||||
void copy_part(const ppu_module& info)
|
||||
@ -99,10 +104,44 @@ struct ppu_module
|
||||
relocs = info.relocs;
|
||||
segs = info.segs;
|
||||
secs = info.secs;
|
||||
allocations = info.allocations;
|
||||
addr_to_seg_index = info.addr_to_seg_index;
|
||||
}
|
||||
|
||||
bool analyse(u32 lib_toc, u32 entry, u32 end, const std::basic_string<u32>& applied, std::function<bool()> check_aborted = {});
|
||||
void validate(u32 reloc);
|
||||
|
||||
template <typename T>
|
||||
to_be_t<T>* get_ptr(u32 addr) const
|
||||
{
|
||||
auto it = addr_to_seg_index.upper_bound(addr);
|
||||
|
||||
if (it == addr_to_seg_index.begin())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
it--;
|
||||
|
||||
const auto& seg = segs[it->second];
|
||||
const u32 seg_size = seg.size;
|
||||
const u32 seg_addr = seg.addr;
|
||||
|
||||
constexpr usz size_element = std::is_void_v<T> ? 0 : sizeof(std::conditional_t<std::is_void_v<T>, char, T>);
|
||||
|
||||
if (seg_size >= std::max<usz>(size_element, 1) && addr <= seg_addr + seg_size - size_element)
|
||||
{
|
||||
return reinterpret_cast<to_be_t<T>*>(static_cast<u8*>(seg.ptr) + (addr - seg_addr));
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
template <typename T, typename U> requires requires (const U& obj) { +obj.addr() * 0; }
|
||||
to_be_t<T>* get_ptr(U&& addr) const
|
||||
{
|
||||
return get_ptr<T>(addr.addr());
|
||||
}
|
||||
};
|
||||
|
||||
struct main_ppu_module : public ppu_module
|
||||
|
@ -754,7 +754,7 @@ static auto ppu_load_exports(ppu_linkage_info* link, u32 exports_start, u32 expo
|
||||
|
||||
if (lib.num_tlsvar)
|
||||
{
|
||||
ppu_loader.fatal("Unexpected num_tlsvar (%u)!", lib.num_tlsvar);
|
||||
ppu_loader.error("Unexpected num_tlsvar (%u)!", lib.num_tlsvar);
|
||||
}
|
||||
|
||||
const bool should_load = ppu_register_library_lock(module_name, true);
|
||||
@ -889,7 +889,7 @@ static auto ppu_load_imports(std::vector<ppu_reloc>& relocs, ppu_linkage_info* l
|
||||
|
||||
if (lib.num_tlsvar)
|
||||
{
|
||||
ppu_loader.fatal("Unexpected num_tlsvar (%u)!", lib.num_tlsvar);
|
||||
ppu_loader.error("Unexpected num_tlsvar (%u)!", lib.num_tlsvar);
|
||||
}
|
||||
|
||||
// Static module
|
||||
@ -1056,13 +1056,18 @@ void init_ppu_functions(utils::serial* ar, bool full = false)
|
||||
}
|
||||
}
|
||||
|
||||
static void ppu_check_patch_spu_images(const ppu_segment& seg)
|
||||
static void ppu_check_patch_spu_images(const ppu_module& mod, const ppu_segment& seg)
|
||||
{
|
||||
const std::string_view seg_view{vm::get_super_ptr<char>(seg.addr), seg.size};
|
||||
if (!seg.size)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string_view seg_view{ensure(mod.get_ptr<char>(seg.addr)), seg.size};
|
||||
|
||||
for (usz 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);
|
||||
const auto elf_header = ensure(mod.get_ptr<u8>(seg.addr + i));
|
||||
|
||||
// Try to load SPU image
|
||||
const spu_exec_object obj(fs::file(elf_header, seg.size - i));
|
||||
@ -1103,7 +1108,7 @@ static void ppu_check_patch_spu_images(const ppu_segment& seg)
|
||||
sha1_update(&sha2, (elf_header + prog.p_offset), prog.p_filesz);
|
||||
|
||||
// We assume that the string SPUNAME exists 0x14 bytes into the NOTE segment
|
||||
name = reinterpret_cast<char*>(elf_header + prog.p_offset + 0x14);
|
||||
name = ensure(mod.get_ptr<const char>(seg.addr + i + prog.p_offset + 0x14));
|
||||
|
||||
if (!name.empty())
|
||||
{
|
||||
@ -1137,12 +1142,12 @@ static void ppu_check_patch_spu_images(const ppu_segment& seg)
|
||||
for (const auto& prog : obj.progs)
|
||||
{
|
||||
// Apply the patch
|
||||
applied += g_fxo->get<patch_engine>().apply(hash, (elf_header + prog.p_offset), prog.p_filesz, prog.p_vaddr);
|
||||
applied += g_fxo->get<patch_engine>().apply(hash, [&](u32 addr) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr);
|
||||
|
||||
if (!Emu.GetTitleID().empty())
|
||||
{
|
||||
// Alternative patch
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, (elf_header + prog.p_offset), prog.p_filesz, prog.p_vaddr);
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return addr + elf_header + prog.p_offset; }, prog.p_filesz, prog.p_vaddr);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1248,7 +1253,7 @@ const char* get_prx_name_by_cia(u32 addr)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::string& path, s64 file_offset, utils::serial* ar)
|
||||
std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, bool virtual_load, const std::string& path, s64 file_offset, utils::serial* ar)
|
||||
{
|
||||
if (elf != elf_error::ok)
|
||||
{
|
||||
@ -1256,7 +1261,7 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
}
|
||||
|
||||
// Create new PRX object
|
||||
const auto prx = !ar ? idm::make_ptr<lv2_obj, lv2_prx>() : std::make_shared<lv2_prx>();
|
||||
const auto prx = !ar && !virtual_load ? idm::make_ptr<lv2_obj, lv2_prx>() : std::make_shared<lv2_prx>();
|
||||
|
||||
// Access linkage information object
|
||||
auto& link = g_fxo->get<ppu_linkage_info>();
|
||||
@ -1271,6 +1276,9 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
u32 end = 0;
|
||||
u32 toc = 0;
|
||||
|
||||
// 0x100000: Workaround for analyser glitches
|
||||
u32 allocating_address = 0x100000;
|
||||
|
||||
for (const auto& prog : elf.progs)
|
||||
{
|
||||
ppu_loader.notice("** Segment: p_type=0x%x, p_vaddr=0x%llx, p_filesz=0x%llx, p_memsz=0x%llx, flags=0x%x", prog.p_type, prog.p_vaddr, prog.p_filesz, prog.p_memsz, prog.p_flags);
|
||||
@ -1295,15 +1303,42 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
|
||||
// Alloc segment memory
|
||||
// Or use saved address
|
||||
const u32 addr = !ar ? vm::alloc(mem_size, vm::main) : ar->operator u32();
|
||||
u32 addr = 0;
|
||||
|
||||
if (!vm::check_addr(addr))
|
||||
if (virtual_load)
|
||||
{
|
||||
addr = std::exchange(allocating_address, allocating_address + utils::align<u32>(mem_size, 0x10000));
|
||||
}
|
||||
else
|
||||
{
|
||||
addr = (!ar ? vm::alloc(mem_size, vm::main) : ar->operator u32());
|
||||
}
|
||||
|
||||
_seg.ptr = vm::base(addr);
|
||||
|
||||
if (virtual_load)
|
||||
{
|
||||
// Leave additional room for the analyser so it can safely access beyond limit a bit
|
||||
// Because with VM the address sapce is not really a limit so any u32 address is valid there, here it is UB to create pointer that goes beyond the boundaries
|
||||
// TODO: Use make_shared_for_overwrite when all compilers support it
|
||||
const usz alloc_size = utils::align<usz>(mem_size, 0x10000) + 4096;
|
||||
prx->allocations.push_back(std::shared_ptr<u8[]>(new u8[alloc_size]));
|
||||
_seg.ptr = prx->allocations.back().get();
|
||||
std::memset(static_cast<u8*>(_seg.ptr) + prog.bin.size(), 0, alloc_size - 4096 - prog.bin.size());
|
||||
}
|
||||
else if (!vm::check_addr(addr))
|
||||
{
|
||||
fmt::throw_exception("vm::alloc() failed (size=0x%x)", mem_size);
|
||||
}
|
||||
|
||||
_seg.addr = addr;
|
||||
_seg.size = mem_size;
|
||||
_seg.filesz = file_size;
|
||||
|
||||
prx->addr_to_seg_index.emplace(addr, prx->segs.size() - 1);
|
||||
|
||||
// Copy segment data
|
||||
if (!ar) std::memcpy(vm::base(addr), prog.bin.data(), file_size);
|
||||
if (!ar) std::memcpy(ensure(prx->get_ptr<void>(addr)), prog.bin.data(), file_size);
|
||||
ppu_loader.warning("**** Loaded to 0x%x...0x%x (size=0x%x)", addr, addr + mem_size - 1, mem_size);
|
||||
|
||||
// Hash segment
|
||||
@ -1312,7 +1347,7 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
sha1_update(&sha, prog.bin.data(), prog.bin.size());
|
||||
|
||||
// Initialize executable code if necessary
|
||||
if (prog.p_flags & 0x1)
|
||||
if (prog.p_flags & 0x1 && !virtual_load)
|
||||
{
|
||||
if (ar)
|
||||
{
|
||||
@ -1322,10 +1357,6 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
|
||||
ppu_register_range(addr, mem_size);
|
||||
}
|
||||
|
||||
_seg.addr = addr;
|
||||
_seg.size = mem_size;
|
||||
_seg.filesz = file_size;
|
||||
}
|
||||
|
||||
break;
|
||||
@ -1421,63 +1452,63 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
{
|
||||
case 1: // R_PPC64_ADDR32
|
||||
{
|
||||
const u32 value = vm::_ref<u32>(raddr) = static_cast<u32>(rdata);
|
||||
const u32 value = *ensure(prx->get_ptr<u32>(raddr)) = static_cast<u32>(rdata);
|
||||
ppu_loader.trace("**** RELOCATION(1): 0x%x <- 0x%08x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 4: //R_PPC64_ADDR16_LO
|
||||
{
|
||||
const u16 value = vm::_ref<u16>(raddr) = static_cast<u16>(rdata);
|
||||
const u16 value = *ensure(prx->get_ptr<u16>(raddr)) = static_cast<u16>(rdata);
|
||||
ppu_loader.trace("**** RELOCATION(4): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 5: //R_PPC64_ADDR16_HI
|
||||
{
|
||||
const u16 value = vm::_ref<u16>(raddr) = static_cast<u16>(rdata >> 16);
|
||||
const u16 value = *ensure(prx->get_ptr<u16>(raddr)) = static_cast<u16>(rdata >> 16);
|
||||
ppu_loader.trace("**** RELOCATION(5): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 6: //R_PPC64_ADDR16_HA
|
||||
{
|
||||
const u16 value = vm::_ref<u16>(raddr) = static_cast<u16>(rdata >> 16) + (rdata & 0x8000 ? 1 : 0);
|
||||
const u16 value = *ensure(prx->get_ptr<u16>(raddr)) = static_cast<u16>(rdata >> 16) + (rdata & 0x8000 ? 1 : 0);
|
||||
ppu_loader.trace("**** RELOCATION(6): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 10: //R_PPC64_REL24
|
||||
{
|
||||
const u32 value = vm::_ref<ppu_bf_t<be_t<u32>, 6, 24>>(raddr) = static_cast<u32>(rdata - raddr) >> 2;
|
||||
const u32 value = *ensure(prx->get_ptr<ppu_bf_t<be_t<u32>, 6, 24>>(raddr)) = static_cast<u32>(rdata - raddr) >> 2;
|
||||
ppu_loader.warning("**** RELOCATION(10): 0x%x <- 0x%06x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 11: //R_PPC64_REL14
|
||||
{
|
||||
const u32 value = vm::_ref<ppu_bf_t<be_t<u32>, 16, 14>>(raddr) = static_cast<u32>(rdata - raddr) >> 2;
|
||||
const u32 value = *ensure(prx->get_ptr<ppu_bf_t<be_t<u32>, 16, 14>>(raddr)) = static_cast<u32>(rdata - raddr) >> 2;
|
||||
ppu_loader.warning("**** RELOCATION(11): 0x%x <- 0x%06x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 38: //R_PPC64_ADDR64
|
||||
{
|
||||
const u64 value = vm::_ref<u64>(raddr) = rdata;
|
||||
const u64 value = *ensure(prx->get_ptr<u64>(raddr)) = rdata;
|
||||
ppu_loader.trace("**** RELOCATION(38): 0x%x <- 0x%016llx (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 44: //R_PPC64_REL64
|
||||
{
|
||||
const u64 value = vm::_ref<u64>(raddr) = rdata - raddr;
|
||||
const u64 value = *ensure(prx->get_ptr<u64>(raddr)) = rdata - raddr;
|
||||
ppu_loader.trace("**** RELOCATION(44): 0x%x <- 0x%016llx (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
|
||||
case 57: //R_PPC64_ADDR16_LO_DS
|
||||
{
|
||||
const u16 value = vm::_ref<ppu_bf_t<be_t<u16>, 0, 14>>(raddr) = static_cast<u16>(rdata) >> 2;
|
||||
const u16 value = *ensure(prx->get_ptr<ppu_bf_t<be_t<u16>, 0, 14>>(raddr)) = static_cast<u16>(rdata) >> 2;
|
||||
ppu_loader.trace("**** RELOCATION(57): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
|
||||
break;
|
||||
}
|
||||
@ -1512,7 +1543,7 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
};
|
||||
|
||||
// Access library information (TODO)
|
||||
const vm::cptr<ppu_prx_library_info> lib_info = vm::cast(prx->segs[0].addr + elf.progs[0].p_paddr - elf.progs[0].p_offset);
|
||||
const auto lib_info = ensure(prx->get_ptr<const ppu_prx_library_info>(prx->segs[0].addr + elf.progs[0].p_paddr - elf.progs[0].p_offset));
|
||||
const std::string lib_name = lib_info->name;
|
||||
|
||||
strcpy_trunc(prx->module_info_name, lib_name);
|
||||
@ -1523,7 +1554,7 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
prx->exports_start = lib_info->exports_start;
|
||||
prx->exports_end = lib_info->exports_end;
|
||||
|
||||
for (usz start = prx->exports_start, size = 0;; size++, start += vm::read8(start) ? vm::read8(start) : sizeof(ppu_prx_module_info))
|
||||
for (usz start = prx->exports_start, size = 0;; size++)
|
||||
{
|
||||
if (start >= prx->exports_end)
|
||||
{
|
||||
@ -1531,18 +1562,25 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
prx->m_external_loaded_flags.resize(size);
|
||||
break;
|
||||
}
|
||||
|
||||
const u8 increment = *ensure(prx->get_ptr<u8>(start));
|
||||
start += increment ? increment : sizeof(ppu_prx_module_info);
|
||||
}
|
||||
|
||||
ppu_loader.warning("Library %s (rtoc=0x%x):", lib_name, lib_info->toc);
|
||||
|
||||
prx->specials = ppu_load_exports(&link, prx->exports_start, prx->exports_end, true);
|
||||
prx->imports = ppu_load_imports(prx->relocs, &link, lib_info->imports_start, lib_info->imports_end);
|
||||
if (!virtual_load)
|
||||
{
|
||||
prx->specials = ppu_load_exports(&link, prx->exports_start, prx->exports_end, true);
|
||||
prx->imports = ppu_load_imports(prx->relocs, &link, lib_info->imports_start, lib_info->imports_end);
|
||||
}
|
||||
|
||||
std::stable_sort(prx->relocs.begin(), prx->relocs.end());
|
||||
toc = lib_info->toc;
|
||||
}
|
||||
else
|
||||
{
|
||||
ppu_loader.fatal("Library %s: PRX library info not found");
|
||||
ppu_loader.error("Library %s: PRX library info not found");
|
||||
}
|
||||
|
||||
prx->start.set(prx->specials[0xbc9a0086]);
|
||||
@ -1579,12 +1617,12 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
const std::string hash_seg = fmt::format("%s-%u", hash, i);
|
||||
|
||||
// Apply the patch
|
||||
auto _applied = g_fxo->get<patch_engine>().apply(hash_seg, vm::get_super_ptr(seg.addr), seg.size);
|
||||
auto _applied = g_fxo->get<patch_engine>().apply(hash_seg, [&](u32 addr) { return prx->get_ptr<u8>(addr + seg.addr); }, seg.size);
|
||||
|
||||
if (!Emu.GetTitleID().empty())
|
||||
{
|
||||
// Alternative patch
|
||||
_applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash_seg, vm::get_super_ptr(seg.addr), seg.size);
|
||||
_applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash_seg, [&](u32 addr) { return prx->get_ptr<u8>(addr + seg.addr); }, seg.size);
|
||||
}
|
||||
|
||||
// Rebase patch offsets
|
||||
@ -1605,12 +1643,15 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
// Embedded SPU elf patching
|
||||
for (const auto& seg : prx->segs)
|
||||
{
|
||||
ppu_check_patch_spu_images(seg);
|
||||
ppu_check_patch_spu_images(*prx, seg);
|
||||
}
|
||||
|
||||
prx->analyse(toc, 0, end, applied);
|
||||
|
||||
try_spawn_ppu_if_exclusive_program(*prx);
|
||||
if (!ar && !virtual_load)
|
||||
{
|
||||
try_spawn_ppu_if_exclusive_program(*prx);
|
||||
}
|
||||
|
||||
return prx;
|
||||
}
|
||||
@ -1667,7 +1708,10 @@ void ppu_unload_prx(const lv2_prx& prx)
|
||||
{
|
||||
if (!seg.size) continue;
|
||||
|
||||
vm::dealloc(seg.addr, vm::main);
|
||||
if (seg.ptr == vm::base(seg.addr))
|
||||
{
|
||||
vm::dealloc(seg.addr, vm::main);
|
||||
}
|
||||
|
||||
const std::string hash_seg = fmt::format("%s-%u", hash, &seg - prx.segs.data());
|
||||
|
||||
@ -1682,7 +1726,7 @@ void ppu_unload_prx(const lv2_prx& prx)
|
||||
}
|
||||
}
|
||||
|
||||
bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, utils::serial* ar)
|
||||
bool ppu_load_exec(const ppu_exec_object& elf, bool virtual_load, const std::string& elf_path, utils::serial* ar)
|
||||
{
|
||||
if (elf != elf_error::ok)
|
||||
{
|
||||
@ -1753,6 +1797,12 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
|
||||
} error_handler{_main};
|
||||
|
||||
if (virtual_load)
|
||||
{
|
||||
// No need for cleanup
|
||||
error_handler.errored = false;
|
||||
}
|
||||
|
||||
// Allocate memory at fixed positions
|
||||
for (const auto& prog : elf.progs)
|
||||
{
|
||||
@ -1774,13 +1824,25 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
{
|
||||
if (prog.bin.size() > size || prog.bin.size() != prog.p_filesz)
|
||||
{
|
||||
ppu_loader.fatal("ppu_load_exec(): Invalid binary size (0x%llx, memsz=0x%x)", prog.bin.size(), size);
|
||||
ppu_loader.error("ppu_load_exec(): Invalid binary size (0x%llx, memsz=0x%x)", prog.bin.size(), size);
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool already_loaded = ar && vm::check_addr(addr, vm::page_readable, size);
|
||||
|
||||
if (already_loaded)
|
||||
_seg.ptr = vm::base(addr);
|
||||
|
||||
if (virtual_load)
|
||||
{
|
||||
// Leave additional room for the analyser so it can safely access beyond limit a bit
|
||||
// Because with VM the address sapce is not really a limit so any u32 address is valid there, here it is UB to create pointer that goes beyond the boundaries
|
||||
// TODO: Use make_shared_for_overwrite when all compilers support it
|
||||
const usz alloc_size = utils::align<usz>(size, 0x10000) + 4096;
|
||||
_main.allocations.push_back(std::shared_ptr<u8[]>(new u8[alloc_size]));
|
||||
_seg.ptr = _main.allocations.back().get();
|
||||
std::memset(static_cast<u8*>(_seg.ptr) + prog.bin.size(), 0, alloc_size - 4096 - prog.bin.size());
|
||||
}
|
||||
else if (already_loaded)
|
||||
{
|
||||
}
|
||||
else if (!vm::falloc(addr, size, vm::main))
|
||||
@ -1789,15 +1851,19 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
|
||||
if (!vm::falloc(addr, size))
|
||||
{
|
||||
ppu_loader.fatal("ppu_load_exec(): vm::falloc() failed (addr=0x%x, memsz=0x%x)", addr, size);
|
||||
ppu_loader.error("ppu_load_exec(): vm::falloc() failed (addr=0x%x, memsz=0x%x)", addr, size);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Store only LOAD segments (TODO)
|
||||
_main.segs.emplace_back(_seg);
|
||||
_main.addr_to_seg_index.emplace(addr, _main.segs.size() - 1);
|
||||
|
||||
// Copy segment data, hash it
|
||||
if (!already_loaded)
|
||||
{
|
||||
std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size());
|
||||
std::memcpy(_main.get_ptr<void>(addr), prog.bin.data(), prog.bin.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -1812,7 +1878,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
sha1_update(&sha, prog.bin.data(), prog.bin.size());
|
||||
|
||||
// Initialize executable code if necessary
|
||||
if (prog.p_flags & 0x1)
|
||||
if (prog.p_flags & 0x1 && !virtual_load)
|
||||
{
|
||||
if (already_loaded && ar)
|
||||
{
|
||||
@ -1822,9 +1888,6 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
|
||||
ppu_register_range(addr, size);
|
||||
}
|
||||
|
||||
// Store only LOAD segments (TODO)
|
||||
_main.segs.emplace_back(_seg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1868,12 +1931,12 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
Emu.SetExecutableHash(hash);
|
||||
|
||||
// Apply the patch
|
||||
auto applied = g_fxo->get<patch_engine>().apply(!ar ? hash : std::string{}, vm::g_base_addr);
|
||||
auto applied = g_fxo->get<patch_engine>().apply(!ar ? hash : std::string{}, [&](u32 addr) { return _main.get_ptr<u8>(addr); });
|
||||
|
||||
if (!ar && !Emu.GetTitleID().empty())
|
||||
{
|
||||
// Alternative patch
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr);
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return _main.get_ptr<u8>(addr); });
|
||||
}
|
||||
|
||||
if (applied.empty())
|
||||
@ -1891,11 +1954,11 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
// Embedded SPU elf patching
|
||||
for (const auto& seg : _main.segs)
|
||||
{
|
||||
ppu_check_patch_spu_images(seg);
|
||||
ppu_check_patch_spu_images(_main, seg);
|
||||
}
|
||||
|
||||
// Static HLE patching
|
||||
if (g_cfg.core.hook_functions)
|
||||
if (g_cfg.core.hook_functions && !virtual_load)
|
||||
{
|
||||
auto shle = g_fxo->init<statichle_handler>(0);
|
||||
|
||||
@ -1943,7 +2006,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
|
||||
if ((prog.p_vaddr | prog.p_filesz | prog.p_memsz) > u32{umax})
|
||||
{
|
||||
ppu_loader.fatal("ppu_load_exec(): TLS segment is invalid!");
|
||||
ppu_loader.error("ppu_load_exec(): TLS segment is invalid!");
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -1970,7 +2033,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
//be_t<u32> crash_dump_param_addr;
|
||||
};
|
||||
|
||||
const auto& info = vm::_ref<process_param_t>(vm::cast(prog.p_vaddr));
|
||||
const auto& info = *ensure(_main.get_ptr<process_param_t>(vm::cast(prog.p_vaddr)));
|
||||
|
||||
if (info.size < sizeof(process_param_t))
|
||||
{
|
||||
@ -2025,7 +2088,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
be_t<u32> unk2;
|
||||
};
|
||||
|
||||
const auto& proc_prx_param = vm::_ref<const ppu_proc_prx_param_t>(vm::cast(prog.p_vaddr));
|
||||
const auto& proc_prx_param = *ensure(_main.get_ptr<const ppu_proc_prx_param_t>(vm::cast(prog.p_vaddr)));
|
||||
|
||||
ppu_loader.notice("* libent_start = *0x%x", proc_prx_param.libent_start);
|
||||
ppu_loader.notice("* libstub_start = *0x%x", proc_prx_param.libstub_start);
|
||||
@ -2034,12 +2097,16 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
|
||||
if (proc_prx_param.magic != 0x1b434cecu)
|
||||
{
|
||||
ppu_loader.fatal("ppu_load_exec(): Bad magic! (0x%x)", proc_prx_param.magic);
|
||||
ppu_loader.error("ppu_load_exec(): Bad magic! (0x%x)", proc_prx_param.magic);
|
||||
return false;
|
||||
}
|
||||
|
||||
ppu_load_exports(&link, proc_prx_param.libent_start, proc_prx_param.libent_end);
|
||||
ppu_load_imports(_main.relocs, &link, proc_prx_param.libstub_start, proc_prx_param.libstub_end);
|
||||
if (!virtual_load)
|
||||
{
|
||||
ppu_load_exports(&link, proc_prx_param.libent_start, proc_prx_param.libent_end);
|
||||
ppu_load_imports(_main.relocs, &link, proc_prx_param.libstub_start, proc_prx_param.libstub_end);
|
||||
}
|
||||
|
||||
std::stable_sort(_main.relocs.begin(), _main.relocs.end());
|
||||
}
|
||||
break;
|
||||
@ -2106,7 +2173,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
load_libs.emplace("libsysmodule.sprx");
|
||||
}
|
||||
|
||||
if (ar || Emu.IsVsh())
|
||||
if (ar || Emu.IsVsh() || virtual_load)
|
||||
{
|
||||
// Cannot be used with vsh.self or savestates (they self-manage itself)
|
||||
load_libs.clear();
|
||||
@ -2149,8 +2216,15 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
g_fxo->init<lv2_memory_container>(mem_size);
|
||||
}
|
||||
|
||||
void init_fxo_for_exec(utils::serial* ar, bool full);
|
||||
init_fxo_for_exec(ar, false);
|
||||
if (!virtual_load)
|
||||
{
|
||||
void init_fxo_for_exec(utils::serial* ar, bool full);
|
||||
init_fxo_for_exec(ar, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
Emu.ConfigurePPUCache();
|
||||
}
|
||||
|
||||
liblv2_begin = 0;
|
||||
liblv2_end = 0;
|
||||
@ -2165,13 +2239,13 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
{
|
||||
ppu_loader.warning("Loading library: %s", name);
|
||||
|
||||
auto prx = ppu_load_prx(obj, lle_dir + name, 0, nullptr);
|
||||
auto prx = ppu_load_prx(obj, false, lle_dir + name, 0, nullptr);
|
||||
prx->state = PRX_STATE_STARTED;
|
||||
prx->load_exports();
|
||||
|
||||
if (prx->funcs.empty())
|
||||
{
|
||||
ppu_loader.fatal("Module %s has no functions!", name);
|
||||
ppu_loader.error("Module %s has no functions!", name);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -2196,7 +2270,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
}
|
||||
}
|
||||
|
||||
if (ar)
|
||||
if (ar || virtual_load)
|
||||
{
|
||||
error_handler.errored = false;
|
||||
return true;
|
||||
@ -2332,7 +2406,7 @@ bool ppu_load_exec(const ppu_exec_object& elf, const std::string& elf_path, util
|
||||
return true;
|
||||
}
|
||||
|
||||
std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object& elf, const std::string& path, s64 file_offset, utils::serial* ar)
|
||||
std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object& elf, bool virtual_load, const std::string& path, s64 file_offset, utils::serial* ar)
|
||||
{
|
||||
if (elf != elf_error::ok)
|
||||
{
|
||||
@ -2396,11 +2470,23 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
|
||||
const bool already_loaded = !!ar; // Unimplemented optimization for savestates
|
||||
|
||||
if (already_loaded)
|
||||
_seg.ptr = vm::base(addr);
|
||||
|
||||
if (virtual_load)
|
||||
{
|
||||
// Leave additional room for the analyser so it can safely access beyond limit a bit
|
||||
// Because with VM the address sapce is not really a limit so any u32 address is valid there, here it is UB to create pointer that goes beyond the boundaries
|
||||
// TODO: Use make_shared_for_overwrite when all compilers support it
|
||||
const usz alloc_size = utils::align<usz>(size, 0x10000) + 4096;
|
||||
ovlm->allocations.push_back(std::shared_ptr<u8[]>(new u8[alloc_size]));
|
||||
_seg.ptr = ovlm->allocations.back().get();
|
||||
std::memset(static_cast<u8*>(_seg.ptr) + prog.bin.size(), 0, alloc_size - 4096 - prog.bin.size());
|
||||
}
|
||||
else if (already_loaded)
|
||||
{
|
||||
if (!vm::check_addr(addr, vm::page_readable, size))
|
||||
{
|
||||
ppu_loader.fatal("ppu_load_overlay(): Archived PPU overlay memory has not been found! (addr=0x%x, memsz=0x%x)", addr, size);
|
||||
ppu_loader.error("ppu_load_overlay(): Archived PPU overlay memory has not been found! (addr=0x%x, memsz=0x%x)", addr, size);
|
||||
return {nullptr, CELL_EABORT};
|
||||
}
|
||||
}
|
||||
@ -2418,14 +2504,18 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
return {nullptr, CELL_EBUSY};
|
||||
}
|
||||
|
||||
// Store only LOAD segments (TODO)
|
||||
ovlm->segs.emplace_back(_seg);
|
||||
ovlm->addr_to_seg_index.emplace(addr, ovlm->segs.size() - 1);
|
||||
|
||||
// Copy segment data, hash it
|
||||
if (!already_loaded) std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size());
|
||||
if (!already_loaded) std::memcpy(ensure(ovlm->get_ptr<void>(addr)), prog.bin.data(), prog.bin.size());
|
||||
sha1_update(&sha, reinterpret_cast<const uchar*>(&prog.p_vaddr), sizeof(prog.p_vaddr));
|
||||
sha1_update(&sha, reinterpret_cast<const uchar*>(&prog.p_memsz), sizeof(prog.p_memsz));
|
||||
sha1_update(&sha, prog.bin.data(), prog.bin.size());
|
||||
|
||||
// Initialize executable code if necessary
|
||||
if (prog.p_flags & 0x1)
|
||||
if (prog.p_flags & 0x1 && !virtual_load)
|
||||
{
|
||||
if (ar)
|
||||
{
|
||||
@ -2435,9 +2525,6 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
|
||||
ppu_register_range(addr, size);
|
||||
}
|
||||
|
||||
// Store only LOAD segments (TODO)
|
||||
ovlm->segs.emplace_back(_seg);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2479,18 +2566,18 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
}
|
||||
|
||||
// Apply the patch
|
||||
auto applied = g_fxo->get<patch_engine>().apply(hash, vm::g_base_addr);
|
||||
auto applied = g_fxo->get<patch_engine>().apply(hash, [ovlm](u32 addr) { return ovlm->get_ptr<u8>(addr); });
|
||||
|
||||
if (!Emu.GetTitleID().empty())
|
||||
{
|
||||
// Alternative patch
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr);
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [ovlm](u32 addr) { return ovlm->get_ptr<u8>(addr); });
|
||||
}
|
||||
|
||||
// Embedded SPU elf patching
|
||||
for (const auto& seg : ovlm->segs)
|
||||
{
|
||||
ppu_check_patch_spu_images(seg);
|
||||
ppu_check_patch_spu_images(*ovlm, seg);
|
||||
}
|
||||
|
||||
if (applied.empty())
|
||||
@ -2523,7 +2610,7 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
//and a lot of zeros.
|
||||
};
|
||||
|
||||
const auto& info = vm::_ref<process_param_t>(vm::cast(prog.p_vaddr));
|
||||
const auto& info = *ensure(ovlm->get_ptr<process_param_t>(vm::cast(prog.p_vaddr)));
|
||||
|
||||
if (info.size < sizeof(process_param_t))
|
||||
{
|
||||
@ -2561,7 +2648,7 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
be_t<u32> unk2;
|
||||
};
|
||||
|
||||
const auto& proc_prx_param = vm::_ref<const ppu_proc_prx_param_t>(vm::cast(prog.p_vaddr));
|
||||
const auto& proc_prx_param = *ensure(ovlm->get_ptr<const ppu_proc_prx_param_t>(vm::cast(prog.p_vaddr)));
|
||||
|
||||
ppu_loader.notice("* libent_start = *0x%x", proc_prx_param.libent_start);
|
||||
ppu_loader.notice("* libstub_start = *0x%x", proc_prx_param.libstub_start);
|
||||
@ -2573,8 +2660,11 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
fmt::throw_exception("Bad magic! (0x%x)", proc_prx_param.magic);
|
||||
}
|
||||
|
||||
ppu_load_exports(&link, proc_prx_param.libent_start, proc_prx_param.libent_end);
|
||||
ppu_load_imports(ovlm->relocs, &link, proc_prx_param.libstub_start, proc_prx_param.libstub_end);
|
||||
if (!virtual_load)
|
||||
{
|
||||
ppu_load_exports(&link, proc_prx_param.libent_start, proc_prx_param.libent_end);
|
||||
ppu_load_imports(ovlm->relocs, &link, proc_prx_param.libstub_start, proc_prx_param.libstub_end);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -2593,7 +2683,7 @@ std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_ex
|
||||
// Validate analyser results (not required)
|
||||
ovlm->validate(0);
|
||||
|
||||
if (!ar)
|
||||
if (!ar && !virtual_load)
|
||||
{
|
||||
idm::import_existing<lv2_obj, lv2_overlay>(ovlm);
|
||||
try_spawn_ppu_if_exclusive_program(*ovlm);
|
||||
@ -2641,7 +2731,7 @@ bool ppu_load_rel_exec(const ppu_rel_object& elf)
|
||||
|
||||
if (!addr)
|
||||
{
|
||||
ppu_loader.fatal("ppu_load_rel_exec(): vm::alloc() failed (memsz=0x%x)", memsize);
|
||||
ppu_loader.error("ppu_load_rel_exec(): vm::alloc() failed (memsz=0x%x)", memsize);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -161,9 +161,10 @@ extern void ppu_initialize();
|
||||
extern void ppu_finalize(const ppu_module& info);
|
||||
extern bool ppu_initialize(const ppu_module& info, bool = false);
|
||||
static void ppu_initialize2(class jit_compiler& jit, const ppu_module& module_part, const std::string& cache_path, const std::string& obj_name);
|
||||
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset, utils::serial* = nullptr);
|
||||
extern bool ppu_load_exec(const ppu_exec_object&, bool virtual_load, const std::string&, utils::serial* = nullptr);
|
||||
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, bool virtual_load, const std::string& path, s64 file_offset, utils::serial* = nullptr);
|
||||
extern void ppu_unload_prx(const lv2_prx&);
|
||||
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, const std::string&, s64 file_offset, utils::serial* = nullptr);
|
||||
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, bool virtual_load, const std::string&, s64 file_offset, utils::serial* = nullptr);
|
||||
extern void ppu_execute_syscall(ppu_thread& ppu, u64 code);
|
||||
static void ppu_break(ppu_thread&, ppu_opcode_t, be_t<u32>*, ppu_intrp_func*);
|
||||
|
||||
@ -3140,7 +3141,8 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
||||
|
||||
atomic_t<usz> fnext = 0;
|
||||
|
||||
shared_mutex sprx_mtx, ovl_mtx;
|
||||
lf_queue<std::string> possible_exec_file_paths;
|
||||
shared_mutex ovl_mtx;
|
||||
|
||||
named_thread_group workers("SPRX Worker ", std::min<u32>(utils::get_thread_count(), ::size32(file_queue)), [&]
|
||||
{
|
||||
@ -3192,17 +3194,11 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
||||
|
||||
if (ppu_prx_object obj = src; (prx_err = obj, obj == elf_error::ok))
|
||||
{
|
||||
std::unique_lock lock(sprx_mtx);
|
||||
|
||||
if (auto prx = ppu_load_prx(obj, path, offset))
|
||||
if (auto prx = ppu_load_prx(obj, true, path, offset))
|
||||
{
|
||||
lock.unlock();
|
||||
obj.clear(), src.close(); // Clear decrypted file and elf object memory
|
||||
ppu_initialize(*prx);
|
||||
idm::remove<lv2_obj, lv2_prx>(idm::last_id());
|
||||
lock.lock();
|
||||
ppu_unload_prx(*prx);
|
||||
lock.unlock();
|
||||
ppu_finalize(*prx);
|
||||
continue;
|
||||
}
|
||||
@ -3215,10 +3211,7 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
||||
{
|
||||
while (ovl_err == elf_error::ok)
|
||||
{
|
||||
// Only one thread compiles OVL atm, other can compile PRX cuncurrently
|
||||
std::unique_lock lock(ovl_mtx);
|
||||
|
||||
auto [ovlm, error] = ppu_load_overlay(obj, path, offset);
|
||||
auto [ovlm, error] = ppu_load_overlay(obj, true, path, offset);
|
||||
|
||||
if (error)
|
||||
{
|
||||
@ -3229,15 +3222,13 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
||||
|
||||
obj.clear(), src.close(); // Clear decrypted file and elf object memory
|
||||
|
||||
ppu_initialize(*ovlm);
|
||||
|
||||
for (auto& seg : ovlm->segs)
|
||||
{
|
||||
vm::dealloc(seg.addr);
|
||||
// Does not really require this lock, this is done for performance reasons.
|
||||
// Seems like too many created threads is hard for Windows to manage efficiently with many CPU threads.
|
||||
std::lock_guard lock(ovl_mtx);
|
||||
ppu_initialize(*ovlm);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
idm::remove<lv2_obj, lv2_overlay>(idm::last_id());
|
||||
ppu_finalize(*ovlm);
|
||||
break;
|
||||
}
|
||||
@ -3248,7 +3239,8 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
||||
}
|
||||
}
|
||||
|
||||
ppu_log.notice("Failed to precompile '%s' (prx: %s, ovl: %s)", path, prx_err, ovl_err);
|
||||
ppu_log.notice("Failed to precompile '%s' (prx: %s, ovl: %s): Attempting tratment as executable file", path, prx_err, ovl_err);
|
||||
possible_exec_file_paths.push(path);
|
||||
continue;
|
||||
}
|
||||
});
|
||||
@ -3256,6 +3248,97 @@ extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_
|
||||
// Join every thread
|
||||
workers.join();
|
||||
|
||||
named_thread exec_worker("PPU Exec Worker", [&]
|
||||
{
|
||||
if (!possible_exec_file_paths)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
pthread_jit_write_protect_np(false);
|
||||
#endif
|
||||
// Set low priority
|
||||
thread_ctrl::scoped_priority low_prio(-1);
|
||||
|
||||
auto slice = possible_exec_file_paths.pop_all();
|
||||
|
||||
auto main_module = std::move(g_fxo->get<main_ppu_module>());
|
||||
|
||||
for (; slice; slice.pop_front(), g_progr_fdone++)
|
||||
{
|
||||
g_progr_ftotal++;
|
||||
|
||||
if (Emu.IsStopped())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string path = *slice;
|
||||
|
||||
ppu_log.notice("Trying to load as executable: %s", path);
|
||||
|
||||
// Load MSELF, SPRX or SELF
|
||||
fs::file src{path};
|
||||
|
||||
if (!src)
|
||||
{
|
||||
ppu_log.error("Failed to open '%s' (%s)", path, fs::g_tls_error);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Some files may fail to decrypt due to the lack of klic
|
||||
src = decrypt_self(std::move(src));
|
||||
|
||||
if (!src)
|
||||
{
|
||||
ppu_log.notice("Failed to decrypt '%s'", path);
|
||||
continue;
|
||||
}
|
||||
|
||||
elf_error exec_err{};
|
||||
|
||||
if (ppu_exec_object obj = src; (exec_err = obj, obj == elf_error::ok))
|
||||
{
|
||||
while (exec_err == elf_error::ok)
|
||||
{
|
||||
if (!ppu_load_exec(obj, true, path))
|
||||
{
|
||||
// Abort
|
||||
exec_err = elf_error::header_type;
|
||||
break;
|
||||
}
|
||||
|
||||
main_ppu_module& _main = g_fxo->get<main_ppu_module>();
|
||||
|
||||
if (!_main.analyse(0, _main.elf_entry, _main.seg0_code_end, _main.applied_pathes, [](){ return Emu.IsStopped(); }))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
obj.clear(), src.close(); // Clear decrypted file and elf object memory
|
||||
|
||||
ppu_initialize(_main);
|
||||
ppu_finalize(_main);
|
||||
_main = {};
|
||||
break;
|
||||
}
|
||||
|
||||
if (exec_err == elf_error::ok)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
ppu_log.notice("Failed to precompile '%s' as executable (%s)", path, exec_err);
|
||||
continue;
|
||||
}
|
||||
|
||||
g_fxo->get<main_ppu_module>() = std::move(main_module);
|
||||
});
|
||||
|
||||
exec_worker();
|
||||
|
||||
// Revert changes
|
||||
|
||||
if (!had_ovl)
|
||||
@ -3360,7 +3443,7 @@ extern void ppu_initialize()
|
||||
{
|
||||
const std::string eseibrd = mount_point + "/vsh/module/eseibrd.sprx";
|
||||
|
||||
if (auto prx = ppu_load_prx(ppu_prx_object{decrypt_self(fs::file{eseibrd})}, eseibrd, 0))
|
||||
if (auto prx = ppu_load_prx(ppu_prx_object{decrypt_self(fs::file{eseibrd})}, true, eseibrd, 0))
|
||||
{
|
||||
// Check if cache exists for this infinitesimally small prx
|
||||
dev_flash_located = ppu_initialize(*prx, true);
|
||||
@ -3563,9 +3646,11 @@ bool ppu_initialize(const ppu_module& info, bool check_only)
|
||||
continue;
|
||||
}
|
||||
|
||||
for (u32 i = addr; i < addr + size; i += 4)
|
||||
auto i_ptr = ensure(info.get_ptr<u32>(addr));
|
||||
|
||||
for (u32 i = addr; i < addr + size; i += 4, i_ptr++)
|
||||
{
|
||||
if (g_ppu_itype.decode(vm::read32(i)) == ppu_itype::MFVSCR)
|
||||
if (g_ppu_itype.decode(*i_ptr) == ppu_itype::MFVSCR)
|
||||
{
|
||||
ppu_log.warning("MFVSCR found");
|
||||
has_mfvscr = true;
|
||||
@ -3708,7 +3793,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only)
|
||||
if (roff > addr)
|
||||
{
|
||||
// Hash from addr to the beginning of the relocation
|
||||
sha1_update(&ctx, vm::_ptr<const u8>(addr), roff - addr);
|
||||
sha1_update(&ctx, ensure(info.get_ptr<const u8>(addr)), roff - addr);
|
||||
}
|
||||
|
||||
// Hash relocation type instead
|
||||
@ -3721,9 +3806,11 @@ bool ppu_initialize(const ppu_module& info, bool check_only)
|
||||
|
||||
if (has_dcbz == 1)
|
||||
{
|
||||
for (u32 i = addr, end = block.second + block.first - 1; i <= end; i += 4)
|
||||
auto i_ptr = ensure(info.get_ptr<u32>(addr));
|
||||
|
||||
for (u32 i = addr, end = block.second + block.first - 1; i <= end; i += 4, i_ptr++)
|
||||
{
|
||||
if (g_ppu_itype.decode(vm::read32(i)) == ppu_itype::DCBZ)
|
||||
if (g_ppu_itype.decode(*i_ptr) == ppu_itype::DCBZ)
|
||||
{
|
||||
has_dcbz = 2;
|
||||
break;
|
||||
@ -3732,7 +3819,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only)
|
||||
}
|
||||
|
||||
// Hash from addr to the end of the block
|
||||
sha1_update(&ctx, vm::_ptr<const u8>(addr), block.second - (addr - block.first));
|
||||
sha1_update(&ctx, ensure(info.get_ptr<const u8>(addr)), block.second - (addr - block.first));
|
||||
}
|
||||
|
||||
if (reloc)
|
||||
@ -3742,9 +3829,11 @@ bool ppu_initialize(const ppu_module& info, bool check_only)
|
||||
|
||||
if (has_dcbz == 1)
|
||||
{
|
||||
for (u32 i = func.addr, end = func.addr + func.size - 1; i <= end; i += 4)
|
||||
auto i_ptr = ensure(info.get_ptr<u32>(func.addr));
|
||||
|
||||
for (u32 i = func.addr, end = func.addr + func.size - 1; i <= end; i += 4, i_ptr++)
|
||||
{
|
||||
if (g_ppu_itype.decode(vm::read32(i)) == ppu_itype::DCBZ)
|
||||
if (g_ppu_itype.decode(*i_ptr) == ppu_itype::DCBZ)
|
||||
{
|
||||
has_dcbz = 2;
|
||||
break;
|
||||
@ -3752,7 +3841,7 @@ bool ppu_initialize(const ppu_module& info, bool check_only)
|
||||
}
|
||||
}
|
||||
|
||||
sha1_update(&ctx, vm::_ptr<const u8>(func.addr), func.size);
|
||||
sha1_update(&ctx, ensure(info.get_ptr<const u8>(func.addr)), func.size);
|
||||
}
|
||||
|
||||
if (false)
|
||||
|
@ -150,7 +150,7 @@ Function* PPUTranslator::Translate(const ppu_function& info)
|
||||
|
||||
for (u32 addr = m_addr; addr < m_addr + info.size; addr += 4)
|
||||
{
|
||||
const u32 op = vm::read32(vm::cast(addr + base));
|
||||
const u32 op = *ensure(m_info.get_ptr<u32>(addr + base));
|
||||
|
||||
switch (g_ppu_itype.decode(op))
|
||||
{
|
||||
@ -214,7 +214,7 @@ Function* PPUTranslator::Translate(const ppu_function& info)
|
||||
const auto block = std::make_pair(info.addr, info.size);
|
||||
{
|
||||
// Optimize BLR (prefetch LR)
|
||||
if (vm::read32(vm::cast(block.first + block.second - 4)) == ppu_instructions::BLR())
|
||||
if (*ensure(m_info.get_ptr<u32>(block.first + block.second - 4)) == ppu_instructions::BLR())
|
||||
{
|
||||
RegLoad(m_lr);
|
||||
}
|
||||
@ -239,7 +239,7 @@ Function* PPUTranslator::Translate(const ppu_function& info)
|
||||
m_rel = nullptr;
|
||||
}
|
||||
|
||||
const u32 op = vm::read32(vm::cast(m_addr + base));
|
||||
const u32 op = *ensure(m_info.get_ptr<u32>(m_addr + base));
|
||||
|
||||
(this->*(s_ppu_decoder.decode(op)))({op});
|
||||
|
||||
|
@ -13,7 +13,7 @@
|
||||
#include "sys_overlay.h"
|
||||
#include "sys_fs.h"
|
||||
|
||||
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset, utils::serial* ar = nullptr);
|
||||
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, bool virtual_load, const std::string& path, s64 file_offset, utils::serial* ar = nullptr);
|
||||
|
||||
extern bool ppu_initialize(const ppu_module&, bool = false);
|
||||
extern void ppu_finalize(const ppu_module&);
|
||||
@ -43,7 +43,7 @@ static error_code overlay_load_module(vm::ptr<u32> ovlmid, const std::string& vp
|
||||
return {CELL_ENOEXEC, obj.operator elf_error()};
|
||||
}
|
||||
|
||||
const auto [ovlm, error] = ppu_load_overlay(obj, vfs::get(vpath), file_offset);
|
||||
const auto [ovlm, error] = ppu_load_overlay(obj, false, vfs::get(vpath), file_offset);
|
||||
|
||||
obj.clear();
|
||||
|
||||
@ -77,7 +77,7 @@ std::shared_ptr<void> lv2_overlay::load(utils::serial& ar)
|
||||
{
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
|
||||
file = make_file_view(std::move(file), offset);
|
||||
ovlm = ppu_load_overlay(ppu_exec_object{ decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic)) }, path, 0, &ar).first;
|
||||
ovlm = ppu_load_overlay(ppu_exec_object{ decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic)) }, false, path, 0, &ar).first;
|
||||
ensure(ovlm);
|
||||
}
|
||||
else
|
||||
|
@ -17,7 +17,7 @@
|
||||
#include "sys_memory.h"
|
||||
#include <span>
|
||||
|
||||
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, const std::string&, s64, utils::serial* = nullptr);
|
||||
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, bool virtual_load, const std::string&, s64, utils::serial* = nullptr);
|
||||
extern void ppu_unload_prx(const lv2_prx& prx);
|
||||
extern bool ppu_initialize(const ppu_module&, bool = false);
|
||||
extern void ppu_finalize(const ppu_module&);
|
||||
@ -270,7 +270,7 @@ static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr<s
|
||||
return CELL_PRX_ERROR_ILLEGAL_LIBRARY;
|
||||
}
|
||||
|
||||
const auto prx = ppu_load_prx(obj, path, file_offset);
|
||||
const auto prx = ppu_load_prx(obj, false, path, file_offset);
|
||||
|
||||
obj.clear();
|
||||
|
||||
@ -324,7 +324,7 @@ std::shared_ptr<void> lv2_prx::load(utils::serial& ar)
|
||||
{
|
||||
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
|
||||
file = make_file_view(std::move(file), offset);
|
||||
prx = ppu_load_prx(ppu_prx_object{ decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic)) }, path, 0, &ar);
|
||||
prx = ppu_load_prx(ppu_prx_object{ decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic)) }, false, path, 0, &ar);
|
||||
prx->m_loaded_flags = std::move(loaded_flags);
|
||||
prx->m_external_loaded_flags = std::move(external_flags);
|
||||
|
||||
|
@ -185,12 +185,12 @@ void sys_spu_image::deploy(u8* loc, std::span<const sys_spu_segment> segs, bool
|
||||
}
|
||||
|
||||
// Apply the patch
|
||||
auto applied = g_fxo->get<patch_engine>().apply(hash, loc);
|
||||
auto applied = g_fxo->get<patch_engine>().apply(hash, [loc](u32 addr) { return loc + addr; });
|
||||
|
||||
if (!Emu.GetTitleID().empty())
|
||||
{
|
||||
// Alternative patch
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, loc);
|
||||
applied += g_fxo->get<patch_engine>().apply(Emu.GetTitleID() + '-' + hash, [loc](u32 addr) { return loc + addr; });
|
||||
}
|
||||
|
||||
(is_verbose ? spu_log.notice : sys_spu.trace)("Loaded SPU image: %s (<- %u)%s", hash, applied.size(), dump);
|
||||
|
@ -70,15 +70,15 @@ std::string g_cfg_defaults;
|
||||
|
||||
atomic_t<u64> g_watchdog_hold_ctr{0};
|
||||
|
||||
extern bool ppu_load_exec(const ppu_exec_object&, const std::string&, utils::serial* = nullptr);
|
||||
extern bool ppu_load_exec(const ppu_exec_object&, bool virtual_load, const std::string&, utils::serial* = nullptr);
|
||||
extern void spu_load_exec(const spu_exec_object&);
|
||||
extern void spu_load_rel_exec(const spu_rel_object&);
|
||||
extern void ppu_precompile(std::vector<std::string>& dir_queue, std::vector<ppu_module*>* loaded_prx);
|
||||
extern bool ppu_initialize(const ppu_module&, bool = false);
|
||||
extern void ppu_finalize(const ppu_module&);
|
||||
extern void ppu_unload_prx(const lv2_prx&);
|
||||
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0, utils::serial* = nullptr);
|
||||
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 = 0, utils::serial* = nullptr);
|
||||
extern std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, bool virtual_load, const std::string&, s64 = 0, utils::serial* = nullptr);
|
||||
extern std::pair<std::shared_ptr<lv2_overlay>, CellError> ppu_load_overlay(const ppu_exec_object&, bool virtual_load, const std::string& path, s64 = 0, utils::serial* = nullptr);
|
||||
extern bool ppu_load_rel_exec(const ppu_rel_object&);
|
||||
extern bool is_savestate_version_compatible(const std::vector<std::pair<u16, u16>>& data, bool is_boot_check);
|
||||
extern std::vector<std::pair<u16, u16>> read_used_savestate_versions();
|
||||
@ -1338,6 +1338,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
std::vector<std::string> dir_queue;
|
||||
dir_queue.emplace_back(m_path + '/');
|
||||
|
||||
init_fxo_for_exec(nullptr, true);
|
||||
|
||||
{
|
||||
if (m_title_id.empty())
|
||||
{
|
||||
@ -1384,7 +1386,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
|
||||
const ppu_exec_object obj = src;
|
||||
|
||||
if (obj == elf_error::ok && ppu_load_exec(obj, path))
|
||||
if (obj == elf_error::ok && ppu_load_exec(obj, true, path))
|
||||
{
|
||||
g_fxo->get<main_ppu_module>().path = path;
|
||||
}
|
||||
@ -1393,22 +1395,6 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
sys_log.error("Failed to load binary '%s' (%s)", path, obj.get_error());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Workaround for analyser glitches
|
||||
ensure(vm::falloc(0x10000, 0xf0000, vm::main));
|
||||
}
|
||||
}
|
||||
|
||||
if (auto& _main = g_fxo->get<main_ppu_module>(); _main.path.empty())
|
||||
{
|
||||
init_fxo_for_exec(nullptr, true);
|
||||
}
|
||||
|
||||
if (auto main_ppu = idm::get<named_thread<ppu_thread>>(ppu_thread::id_base))
|
||||
{
|
||||
// Created by ppu_load_exec, unwanted
|
||||
main_ppu->state += cpu_flag::exit;
|
||||
}
|
||||
|
||||
g_fxo->init<named_thread>("SPRX Loader"sv, [this, dir_queue]() mutable
|
||||
@ -1961,11 +1947,11 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
|
||||
g_fxo->init<main_ppu_module>();
|
||||
|
||||
if (ppu_load_exec(ppu_exec, m_path, DeserialManager()))
|
||||
if (ppu_load_exec(ppu_exec, false, m_path, DeserialManager()))
|
||||
{
|
||||
}
|
||||
// Overlay (OVL) executable (only load it)
|
||||
else if (vm::map(0x3000'0000, 0x1000'0000, 0x200); !ppu_load_overlay(ppu_exec, m_path).first)
|
||||
else if (vm::map(0x3000'0000, 0x1000'0000, 0x200); !ppu_load_overlay(ppu_exec, false, m_path).first)
|
||||
{
|
||||
ppu_exec.set_error(elf_error::header_type);
|
||||
}
|
||||
@ -1989,7 +1975,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
||||
// PPU PRX
|
||||
GetCallbacks().on_ready();
|
||||
g_fxo->init(false);
|
||||
ppu_load_prx(ppu_prx, m_path);
|
||||
ppu_load_prx(ppu_prx, false, m_path);
|
||||
Pause(true);
|
||||
}
|
||||
else if (spu_exec.open(elf_file) == elf_error::ok)
|
||||
|
Loading…
Reference in New Issue
Block a user