From 554b27a82a43b9c2bcfeb98288e458aaef15821d Mon Sep 17 00:00:00 2001 From: Eladash Date: Sun, 25 Jun 2023 15:53:42 +0300 Subject: [PATCH] PPU LLVM: Implement SELF precompilation Do not use PS3 memory for precompilation. --- Utilities/bin_patch.cpp | 24 +-- Utilities/bin_patch.h | 3 +- rpcs3/Emu/Cell/PPUAnalyser.cpp | 154 +++++++++++------- rpcs3/Emu/Cell/PPUAnalyser.h | 39 +++++ rpcs3/Emu/Cell/PPUModule.cpp | 248 ++++++++++++++++++++--------- rpcs3/Emu/Cell/PPUThread.cpp | 151 ++++++++++++++---- rpcs3/Emu/Cell/PPUTranslator.cpp | 6 +- rpcs3/Emu/Cell/lv2/sys_overlay.cpp | 6 +- rpcs3/Emu/Cell/lv2/sys_prx.cpp | 6 +- rpcs3/Emu/Cell/lv2/sys_spu.cpp | 4 +- rpcs3/Emu/System.cpp | 32 ++-- 11 files changed, 459 insertions(+), 214 deletions(-) diff --git a/Utilities/bin_patch.cpp b/Utilities/bin_patch.cpp index ab54d53137..2ac98d81e5 100644 --- a/Utilities/bin_patch.cpp +++ b/Utilities/bin_patch.cpp @@ -805,7 +805,7 @@ void unmap_vm_area(std::shared_ptr& ptr) } // Returns old 'applied' size -static usz apply_modification(std::basic_string& applied, patch_engine::patch_info& patch, u8* dst, u32 filesz, u32 min_addr) +static usz apply_modification(std::basic_string& applied, patch_engine::patch_info& patch, std::function 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& 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(p.value.long_value) + alloc_at % 4096, 4096); @@ -934,7 +934,13 @@ static usz apply_modification(std::basic_string& 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& 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& 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(p.value.long_value); // Allow only if points to a PPU executable instruction @@ -1060,7 +1066,7 @@ static usz apply_modification(std::basic_string& 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& applied, patch_engine::pat return old_applied_size; } -std::basic_string patch_engine::apply(const std::string& name, u8* dst, u32 filesz, u32 min_addr) +std::basic_string patch_engine::apply(const std::string& name, std::function mem_translate, u32 filesz, u32 min_addr) { if (!m_map.contains(name)) { @@ -1392,7 +1398,7 @@ std::basic_string 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) { diff --git a/Utilities/bin_patch.h b/Utilities/bin_patch.h index d628884921..ff46f59e72 100644 --- a/Utilities/bin_patch.h +++ b/Utilities/bin_patch.h @@ -3,6 +3,7 @@ #include #include #include +#include #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 apply(const std::string& name, u8* dst, u32 filesz = -1, u32 min_addr = 0); + std::basic_string apply(const std::string& name, std::function mem_translate, u32 filesz = -1, u32 min_addr = 0); // Deallocate memory used by patches void unload(const std::string& name); diff --git a/rpcs3/Emu/Cell/PPUAnalyser.cpp b/rpcs3/Emu/Cell/PPUAnalyser.cpp index b62359d3dc..4efe2a5291 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.cpp +++ b/rpcs3/Emu/Cell/PPUAnalyser.cpp @@ -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(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 ptr, vm::cptr fend, ppu_pattern_array pat) +static u32 ppu_test(const be_t* ptr, const void* fend, ppu_pattern_array pat) { - vm::cptr cur = ptr; + const be_t* cur = ptr; for (auto& p : pat) { @@ -141,10 +141,10 @@ static u32 ppu_test(const vm::cptr ptr, vm::cptr fend, ppu_pattern_ar cur++; } - return cur.addr() - ptr.addr(); + return (cur - ptr) * sizeof(*ptr); } -static u32 ppu_test(vm::cptr ptr, vm::cptr fend, ppu_pattern_matrix pats) +static u32 ppu_test(const be_t* 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 ptr = vm::cast(seg.addr); ptr.addr() < seg.addr + seg.size; ptr++) + const vm::cptr seg_end = vm::cast(seg.addr + seg.size - 8); + vm::cptr _ptr = vm::cast(seg.addr); + auto ptr = get_ptr(_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 ptr = vm::cast(seg.addr); ptr.addr() < seg.addr + seg.size; ptr++) + vm::cptr _ptr = vm::cast(seg.addr); + const vm::cptr seg_end = vm::cast(seg.addr + seg.size - 4); + auto ptr = get_ptr(_ptr); + + for (vm::cptr _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 sec_end = vm::cast(sec.addr + sec.size); // Probe - for (vm::cptr ptr = vm::cast(sec.addr); ptr < sec_end; ptr += 2) + for (vm::cptr _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(_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(_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 ptr = vm::cast(sec.addr); ptr < sec_end; ptr += 2) + for (vm::cptr _ptr = vm::cast(sec.addr); _ptr < sec_end; _ptr += 2) { + auto ptr = get_ptr(_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(entry)) ? *ensure(get_ptr(entry + 4)) : *ensure(get_ptr(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 sec_end = vm::cast(sec.addr + sec.size); // Probe - for (vm::cptr ptr = vm::cast(sec.addr); ptr < sec_end;) + for (vm::cptr _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(_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 ptr = vm::cast(sec.addr); ptr < sec_end; ptr = vm::cast(ptr.addr() + ptr[0] + 4)) + for (vm::cptr _ptr = vm::cast(sec.addr); _ptr < sec_end; _ptr = vm::cast(_ptr.addr() + *get_ptr(_ptr) + 4)) { + const auto ptr = get_ptr(_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 cie = vm::cast(ptr.addr() - ptr[1] + 4); + const vm::cptr 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 ptr = vm::cast(func.addr); + const vm::cptr _ptr = vm::cast(func.addr); const vm::cptr fend = vm::cast(end); + const auto ptr = get_ptr(_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(_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(_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(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 _ptr = vm::cast(block.first); _ptr.addr() < func_end;) + vm::cptr _ptr = vm::cast(block.first); + auto ptr = ensure(get_ptr(_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 _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(_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 _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(_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(rel.addr); + const u32 target = *ensure(get_ptr(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(i_pos); + const u32 opc = *ensure(get_ptr(i_pos)); switch (auto type = s_ppu_itype.decode(opc)) { diff --git a/rpcs3/Emu/Cell/PPUAnalyser.h b/rpcs3/Emu/Cell/PPUAnalyser.h index 1f3cf84fda..81dc157a78 100644 --- a/rpcs3/Emu/Cell/PPUAnalyser.h +++ b/rpcs3/Emu/Cell/PPUAnalyser.h @@ -3,8 +3,10 @@ #include #include #include +#include #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 segs{}; std::vector secs{}; std::vector funcs{}; + std::deque> allocations; + std::map 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& applied, std::function check_aborted = {}); void validate(u32 reloc); + + template + to_be_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 ? 0 : sizeof(std::conditional_t, char, T>); + + if (seg_size >= std::max(size_element, 1) && addr <= seg_addr + seg_size - size_element) + { + return reinterpret_cast*>(static_cast(seg.ptr) + (addr - seg_addr)); + } + + return nullptr; + } + + template requires requires (const U& obj) { +obj.addr() * 0; } + to_be_t* get_ptr(U&& addr) const + { + return get_ptr(addr.addr()); + } }; struct main_ppu_module : public ppu_module diff --git a/rpcs3/Emu/Cell/PPUModule.cpp b/rpcs3/Emu/Cell/PPUModule.cpp index 3c5e99cc36..cce0c015fc 100644 --- a/rpcs3/Emu/Cell/PPUModule.cpp +++ b/rpcs3/Emu/Cell/PPUModule.cpp @@ -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& 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(seg.addr), seg.size}; + if (!seg.size) + { + return; + } + + const std::string_view seg_view{ensure(mod.get_ptr(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(seg.addr + i); + const auto elf_header = ensure(mod.get_ptr(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(elf_header + prog.p_offset + 0x14); + name = ensure(mod.get_ptr(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().apply(hash, (elf_header + prog.p_offset), prog.p_filesz, prog.p_vaddr); + applied += g_fxo->get().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().apply(Emu.GetTitleID() + '-' + hash, (elf_header + prog.p_offset), prog.p_filesz, prog.p_vaddr); + applied += g_fxo->get().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 ppu_load_prx(const ppu_prx_object& elf, const std::string& path, s64 file_offset, utils::serial* ar) +std::shared_ptr 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 ppu_load_prx(const ppu_prx_object& elf, const std::stri } // Create new PRX object - const auto prx = !ar ? idm::make_ptr() : std::make_shared(); + const auto prx = !ar && !virtual_load ? idm::make_ptr() : std::make_shared(); // Access linkage information object auto& link = g_fxo->get(); @@ -1271,6 +1276,9 @@ std::shared_ptr 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 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(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(mem_size, 0x10000) + 4096; + prx->allocations.push_back(std::shared_ptr(new u8[alloc_size])); + _seg.ptr = prx->allocations.back().get(); + std::memset(static_cast(_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(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 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 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 ppu_load_prx(const ppu_prx_object& elf, const std::stri { case 1: // R_PPC64_ADDR32 { - const u32 value = vm::_ref(raddr) = static_cast(rdata); + const u32 value = *ensure(prx->get_ptr(raddr)) = static_cast(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(raddr) = static_cast(rdata); + const u16 value = *ensure(prx->get_ptr(raddr)) = static_cast(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(raddr) = static_cast(rdata >> 16); + const u16 value = *ensure(prx->get_ptr(raddr)) = static_cast(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(raddr) = static_cast(rdata >> 16) + (rdata & 0x8000 ? 1 : 0); + const u16 value = *ensure(prx->get_ptr(raddr)) = static_cast(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, 6, 24>>(raddr) = static_cast(rdata - raddr) >> 2; + const u32 value = *ensure(prx->get_ptr, 6, 24>>(raddr)) = static_cast(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, 16, 14>>(raddr) = static_cast(rdata - raddr) >> 2; + const u32 value = *ensure(prx->get_ptr, 16, 14>>(raddr)) = static_cast(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(raddr) = rdata; + const u64 value = *ensure(prx->get_ptr(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(raddr) = rdata - raddr; + const u64 value = *ensure(prx->get_ptr(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, 0, 14>>(raddr) = static_cast(rdata) >> 2; + const u16 value = *ensure(prx->get_ptr, 0, 14>>(raddr)) = static_cast(rdata) >> 2; ppu_loader.trace("**** RELOCATION(57): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata); break; } @@ -1512,7 +1543,7 @@ std::shared_ptr ppu_load_prx(const ppu_prx_object& elf, const std::stri }; // Access library information (TODO) - const vm::cptr 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(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 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 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(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 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().apply(hash_seg, vm::get_super_ptr(seg.addr), seg.size); + auto _applied = g_fxo->get().apply(hash_seg, [&](u32 addr) { return prx->get_ptr(addr + seg.addr); }, seg.size); if (!Emu.GetTitleID().empty()) { // Alternative patch - _applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash_seg, vm::get_super_ptr(seg.addr), seg.size); + _applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash_seg, [&](u32 addr) { return prx->get_ptr(addr + seg.addr); }, seg.size); } // Rebase patch offsets @@ -1605,12 +1643,15 @@ std::shared_ptr 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(size, 0x10000) + 4096; + _main.allocations.push_back(std::shared_ptr(new u8[alloc_size])); + _seg.ptr = _main.allocations.back().get(); + std::memset(static_cast(_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(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().apply(!ar ? hash : std::string{}, vm::g_base_addr); + auto applied = g_fxo->get().apply(!ar ? hash : std::string{}, [&](u32 addr) { return _main.get_ptr(addr); }); if (!ar && !Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr); + applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [&](u32 addr) { return _main.get_ptr(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(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 crash_dump_param_addr; }; - const auto& info = vm::_ref(vm::cast(prog.p_vaddr)); + const auto& info = *ensure(_main.get_ptr(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 unk2; }; - const auto& proc_prx_param = vm::_ref(vm::cast(prog.p_vaddr)); + const auto& proc_prx_param = *ensure(_main.get_ptr(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(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, CellError> ppu_load_overlay(const ppu_exec_object& elf, const std::string& path, s64 file_offset, utils::serial* ar) +std::pair, 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, 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(size, 0x10000) + 4096; + ovlm->allocations.push_back(std::shared_ptr(new u8[alloc_size])); + _seg.ptr = ovlm->allocations.back().get(); + std::memset(static_cast(_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, 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(addr)), prog.bin.data(), prog.bin.size()); sha1_update(&sha, reinterpret_cast(&prog.p_vaddr), sizeof(prog.p_vaddr)); sha1_update(&sha, reinterpret_cast(&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, 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, CellError> ppu_load_overlay(const ppu_ex } // Apply the patch - auto applied = g_fxo->get().apply(hash, vm::g_base_addr); + auto applied = g_fxo->get().apply(hash, [ovlm](u32 addr) { return ovlm->get_ptr(addr); }); if (!Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, vm::g_base_addr); + applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, [ovlm](u32 addr) { return ovlm->get_ptr(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, CellError> ppu_load_overlay(const ppu_ex //and a lot of zeros. }; - const auto& info = vm::_ref(vm::cast(prog.p_vaddr)); + const auto& info = *ensure(ovlm->get_ptr(vm::cast(prog.p_vaddr))); if (info.size < sizeof(process_param_t)) { @@ -2561,7 +2648,7 @@ std::pair, CellError> ppu_load_overlay(const ppu_ex be_t unk2; }; - const auto& proc_prx_param = vm::_ref(vm::cast(prog.p_vaddr)); + const auto& proc_prx_param = *ensure(ovlm->get_ptr(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, 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, CellError> ppu_load_overlay(const ppu_ex // Validate analyser results (not required) ovlm->validate(0); - if (!ar) + if (!ar && !virtual_load) { idm::import_existing(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; } diff --git a/rpcs3/Emu/Cell/PPUThread.cpp b/rpcs3/Emu/Cell/PPUThread.cpp index d7df3552a6..36973a5c66 100644 --- a/rpcs3/Emu/Cell/PPUThread.cpp +++ b/rpcs3/Emu/Cell/PPUThread.cpp @@ -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, 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, 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 ppu_load_prx(const ppu_prx_object&, const std::string&, s64 file_offset, utils::serial* = nullptr); +extern std::shared_ptr 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*, ppu_intrp_func*); @@ -3140,7 +3141,8 @@ extern void ppu_precompile(std::vector& dir_queue, std::vector fnext = 0; - shared_mutex sprx_mtx, ovl_mtx; + lf_queue possible_exec_file_paths; + shared_mutex ovl_mtx; named_thread_group workers("SPRX Worker ", std::min(utils::get_thread_count(), ::size32(file_queue)), [&] { @@ -3192,17 +3194,11 @@ extern void ppu_precompile(std::vector& dir_queue, std::vector(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& dir_queue, std::vector& dir_queue, std::vectorsegs) { - 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(idm::last_id()); ppu_finalize(*ovlm); break; } @@ -3248,7 +3239,8 @@ extern void ppu_precompile(std::vector& dir_queue, std::vector& dir_queue, std::vectorget()); + + 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(); + + 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() = 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(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(addr), roff - addr); + sha1_update(&ctx, ensure(info.get_ptr(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(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(addr), block.second - (addr - block.first)); + sha1_update(&ctx, ensure(info.get_ptr(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(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(func.addr), func.size); + sha1_update(&ctx, ensure(info.get_ptr(func.addr)), func.size); } if (false) diff --git a/rpcs3/Emu/Cell/PPUTranslator.cpp b/rpcs3/Emu/Cell/PPUTranslator.cpp index 5bb1dd7c4f..884f409db8 100644 --- a/rpcs3/Emu/Cell/PPUTranslator.cpp +++ b/rpcs3/Emu/Cell/PPUTranslator.cpp @@ -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(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(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(m_addr + base)); (this->*(s_ppu_decoder.decode(op)))({op}); diff --git a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp index 79baf0ce6b..e9b321ce3a 100644 --- a/rpcs3/Emu/Cell/lv2/sys_overlay.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_overlay.cpp @@ -13,7 +13,7 @@ #include "sys_overlay.h" #include "sys_fs.h" -extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 file_offset, utils::serial* ar = nullptr); +extern std::pair, 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 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 lv2_overlay::load(utils::serial& ar) { u128 klic = g_fxo->get().last_key(); file = make_file_view(std::move(file), offset); - ovlm = ppu_load_overlay(ppu_exec_object{ decrypt_self(std::move(file), reinterpret_cast(&klic)) }, path, 0, &ar).first; + ovlm = ppu_load_overlay(ppu_exec_object{ decrypt_self(std::move(file), reinterpret_cast(&klic)) }, false, path, 0, &ar).first; ensure(ovlm); } else diff --git a/rpcs3/Emu/Cell/lv2/sys_prx.cpp b/rpcs3/Emu/Cell/lv2/sys_prx.cpp index 8433836a17..bfbf8c5fa1 100644 --- a/rpcs3/Emu/Cell/lv2/sys_prx.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_prx.cpp @@ -17,7 +17,7 @@ #include "sys_memory.h" #include -extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, const std::string&, s64, utils::serial* = nullptr); +extern std::shared_ptr 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 lv2_prx::load(utils::serial& ar) { u128 klic = g_fxo->get().last_key(); file = make_file_view(std::move(file), offset); - prx = ppu_load_prx(ppu_prx_object{ decrypt_self(std::move(file), reinterpret_cast(&klic)) }, path, 0, &ar); + prx = ppu_load_prx(ppu_prx_object{ decrypt_self(std::move(file), reinterpret_cast(&klic)) }, false, path, 0, &ar); prx->m_loaded_flags = std::move(loaded_flags); prx->m_external_loaded_flags = std::move(external_flags); diff --git a/rpcs3/Emu/Cell/lv2/sys_spu.cpp b/rpcs3/Emu/Cell/lv2/sys_spu.cpp index 134196d76f..5e1ced65a2 100644 --- a/rpcs3/Emu/Cell/lv2/sys_spu.cpp +++ b/rpcs3/Emu/Cell/lv2/sys_spu.cpp @@ -185,12 +185,12 @@ void sys_spu_image::deploy(u8* loc, std::span segs, bool } // Apply the patch - auto applied = g_fxo->get().apply(hash, loc); + auto applied = g_fxo->get().apply(hash, [loc](u32 addr) { return loc + addr; }); if (!Emu.GetTitleID().empty()) { // Alternative patch - applied += g_fxo->get().apply(Emu.GetTitleID() + '-' + hash, loc); + applied += g_fxo->get().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); diff --git a/rpcs3/Emu/System.cpp b/rpcs3/Emu/System.cpp index b7f0b93f1f..2ce0d364f2 100644 --- a/rpcs3/Emu/System.cpp +++ b/rpcs3/Emu/System.cpp @@ -70,15 +70,15 @@ std::string g_cfg_defaults; atomic_t 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& dir_queue, std::vector* 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 ppu_load_prx(const ppu_prx_object&, const std::string&, s64 = 0, utils::serial* = nullptr); -extern std::pair, CellError> ppu_load_overlay(const ppu_exec_object&, const std::string& path, s64 = 0, utils::serial* = nullptr); +extern std::shared_ptr ppu_load_prx(const ppu_prx_object&, bool virtual_load, const std::string&, s64 = 0, utils::serial* = nullptr); +extern std::pair, 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>& data, bool is_boot_check); extern std::vector> read_used_savestate_versions(); @@ -1338,6 +1338,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch, std::vector 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().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.path.empty()) - { - init_fxo_for_exec(nullptr, true); - } - - if (auto main_ppu = idm::get>(ppu_thread::id_base)) - { - // Created by ppu_load_exec, unwanted - main_ppu->state += cpu_flag::exit; } g_fxo->init("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(); - 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)