mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-03-02 19:13:36 +00:00
PPU: HACK instruction removed
Breakpoints fixed
This commit is contained in:
parent
ddd6ebc58d
commit
3bfe17a14f
@ -13,7 +13,6 @@
|
||||
logs::channel cellGcmSys("cellGcmSys", logs::level::notice);
|
||||
|
||||
extern s32 cellGcmCallback(ppu_thread& ppu, vm::ptr<CellGcmContextData> context, u32 count);
|
||||
extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr);
|
||||
|
||||
const u32 tiled_pitches[] = {
|
||||
0x00000000, 0x00000200, 0x00000300, 0x00000400,
|
||||
@ -379,13 +378,7 @@ s32 _cellGcmInitBody(vm::pptr<CellGcmContextData> context, u32 cmdSize, u32 ioSi
|
||||
current_context.begin.set(g_defaultCommandBufferBegin + 4096); // 4 kb reserved at the beginning
|
||||
current_context.end.set(g_defaultCommandBufferBegin + 32 * 1024 - 4); // 4b at the end for jump
|
||||
current_context.current = current_context.begin;
|
||||
current_context.callback.set(gcm_info.context_addr + 0x40);
|
||||
|
||||
vm::write32(gcm_info.context_addr + 0x40, gcm_info.context_addr + 0x48);
|
||||
vm::write32(gcm_info.context_addr + 0x44, 0xabadcafe);
|
||||
vm::write32(gcm_info.context_addr + 0x48, ppu_instructions::HACK(FIND_FUNC(cellGcmCallback)));
|
||||
vm::write32(gcm_info.context_addr + 0x4c, ppu_instructions::BLR());
|
||||
ppu_register_function_at(gcm_info.context_addr + 0x48, 8, BIND_FUNC(cellGcmCallback));
|
||||
current_context.callback.set(ppu_function_manager::addr + 8 * FIND_FUNC(cellGcmCallback));
|
||||
|
||||
vm::_ref<CellGcmContextData>(gcm_info.context_addr) = current_context;
|
||||
context->set(gcm_info.context_addr);
|
||||
|
@ -689,7 +689,6 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
|
||||
}
|
||||
|
||||
if (ptr + 8 <= fend &&
|
||||
(ptr[0] == STD(r2, r1, 0x28) && (ptr[1] & 0xfc000000) == HACK(0) && ptr[2] == BLR() ||
|
||||
(ptr[0] & 0xffff0000) == LI(r12, 0) &&
|
||||
(ptr[1] & 0xffff0000) == ORIS(r12, r12, 0) &&
|
||||
(ptr[2] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
@ -697,7 +696,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
|
||||
ptr[4] == LWZ(r0, r12, 0) &&
|
||||
ptr[5] == LWZ(r2, r12, 4) &&
|
||||
ptr[6] == MTCTR(r0) &&
|
||||
ptr[7] == BCTR()))
|
||||
ptr[7] == BCTR())
|
||||
{
|
||||
// The most used simple import stub
|
||||
func.size = 0x20;
|
||||
@ -709,7 +708,6 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
|
||||
auto p2 = ptr + 8;
|
||||
|
||||
while (p2 + 8 <= fend &&
|
||||
(p2[0] == STD(r2, r1, 0x28) && (p2[1] & 0xfc000000) == HACK(0) && p2[2] == BLR() ||
|
||||
(p2[0] & 0xffff0000) == LI(r12, 0) &&
|
||||
(p2[1] & 0xffff0000) == ORIS(r12, r12, 0) &&
|
||||
(p2[2] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
@ -717,7 +715,7 @@ std::vector<ppu_function> ppu_analyse(const std::vector<std::pair<u32, u32>>& se
|
||||
p2[4] == LWZ(r0, r12, 0) &&
|
||||
p2[5] == LWZ(r2, r12, 4) &&
|
||||
p2[6] == MTCTR(r0) &&
|
||||
p2[7] == BCTR()))
|
||||
p2[7] == BCTR())
|
||||
{
|
||||
auto& next = add_func(p2.addr(), 0, func.addr);
|
||||
next.size = 0x20;
|
||||
|
@ -274,7 +274,6 @@ struct ppu_itype
|
||||
ADDI,
|
||||
ADDIS,
|
||||
BC,
|
||||
HACK,
|
||||
SC,
|
||||
B,
|
||||
MCRF,
|
||||
@ -691,7 +690,6 @@ struct ppu_iflag
|
||||
ADDI = read_ra,
|
||||
ADDIS = read_ra,
|
||||
BC = 0,
|
||||
HACK = 0,
|
||||
SC = 0,
|
||||
B = 0,
|
||||
MCRF = 0,
|
||||
@ -1093,7 +1091,6 @@ struct ppu_iname
|
||||
NAME(ADDI)
|
||||
NAME(ADDIS)
|
||||
NAME(BC)
|
||||
NAME(HACK)
|
||||
NAME(SC)
|
||||
NAME(B)
|
||||
NAME(MCRF)
|
||||
|
@ -892,11 +892,6 @@ void PPUDisAsm::BC(ppu_opcode_t op)
|
||||
Write(fmt::format("bc [%x:%x:%x:%x:%x], cr%d[%x], 0x%x, %d, %d", bo0, bo1, bo2, bo3, bo4, bi / 4, bi % 4, bd, aa, lk));
|
||||
}
|
||||
|
||||
void PPUDisAsm::HACK(ppu_opcode_t op)
|
||||
{
|
||||
Write(fmt::format("hack %d", op.opcode & 0x3ffffff));
|
||||
}
|
||||
|
||||
void PPUDisAsm::SC(ppu_opcode_t op)
|
||||
{
|
||||
if (op.opcode != ppu_instructions::SC(0))
|
||||
|
@ -397,7 +397,6 @@ public:
|
||||
void ADDI(ppu_opcode_t op);
|
||||
void ADDIS(ppu_opcode_t op);
|
||||
void BC(ppu_opcode_t op);
|
||||
void HACK(ppu_opcode_t op);
|
||||
void SC(ppu_opcode_t op);
|
||||
void B(ppu_opcode_t op);
|
||||
void MCRF(ppu_opcode_t op);
|
||||
|
@ -2381,8 +2381,17 @@ std::vector<ppu_function_t>& ppu_function_manager::access()
|
||||
{
|
||||
static std::vector<ppu_function_t> list
|
||||
{
|
||||
nullptr,
|
||||
[](ppu_thread& ppu) { ppu.state += cpu_flag::ret; },
|
||||
[](ppu_thread& ppu) -> bool
|
||||
{
|
||||
LOG_ERROR(PPU, "Unregistered function called (LR=0x%x)", ppu.lr);
|
||||
ppu.gpr[3] = 0;
|
||||
return true;
|
||||
},
|
||||
[](ppu_thread& ppu) -> bool
|
||||
{
|
||||
ppu.state += cpu_flag::ret;
|
||||
return true;
|
||||
},
|
||||
};
|
||||
|
||||
return list;
|
||||
@ -2396,3 +2405,5 @@ u32 ppu_function_manager::add_function(ppu_function_t function)
|
||||
|
||||
return ::size32(list) - 1;
|
||||
}
|
||||
|
||||
DECLARE(ppu_function_manager::addr);
|
||||
|
@ -2,15 +2,16 @@
|
||||
|
||||
#include "PPUThread.h"
|
||||
|
||||
using ppu_function_t = void(*)(ppu_thread&);
|
||||
using ppu_function_t = bool(*)(ppu_thread&);
|
||||
|
||||
// BIND_FUNC macro "converts" any appropriate HLE function to ppu_function_t, binding it to PPU thread context.
|
||||
#define BIND_FUNC(func) (static_cast<ppu_function_t>([](ppu_thread& ppu) {\
|
||||
#define BIND_FUNC(func) (static_cast<ppu_function_t>([](ppu_thread& ppu) -> bool {\
|
||||
const auto old_f = ppu.last_function;\
|
||||
ppu.last_function = #func;\
|
||||
ppu_func_detail::do_call(ppu, func);\
|
||||
ppu.test_state();\
|
||||
ppu.last_function = old_f;\
|
||||
return true;\
|
||||
}))
|
||||
|
||||
struct ppu_va_args_t
|
||||
@ -274,6 +275,9 @@ public:
|
||||
{
|
||||
return access();
|
||||
}
|
||||
|
||||
// Allocation address
|
||||
static u32 addr;
|
||||
};
|
||||
|
||||
template<typename T, T Func>
|
||||
|
@ -1936,12 +1936,6 @@ bool ppu_interpreter::BC(ppu_thread& ppu, ppu_opcode_t op)
|
||||
}
|
||||
}
|
||||
|
||||
bool ppu_interpreter::HACK(ppu_thread& ppu, ppu_opcode_t op)
|
||||
{
|
||||
ppu_execute_function(ppu, op.opcode & 0x3ffffff);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ppu_interpreter::SC(ppu_thread& ppu, ppu_opcode_t op)
|
||||
{
|
||||
if (op.opcode != ppu_instructions::SC(0))
|
||||
|
@ -160,7 +160,6 @@ struct ppu_interpreter
|
||||
static bool ADDI(ppu_thread&, ppu_opcode_t);
|
||||
static bool ADDIS(ppu_thread&, ppu_opcode_t);
|
||||
static bool BC(ppu_thread&, ppu_opcode_t);
|
||||
static bool HACK(ppu_thread&, ppu_opcode_t);
|
||||
static bool SC(ppu_thread&, ppu_opcode_t);
|
||||
static bool B(ppu_thread&, ppu_opcode_t);
|
||||
static bool MCRF(ppu_thread&, ppu_opcode_t);
|
||||
|
@ -13,7 +13,8 @@
|
||||
|
||||
#include "Emu/Cell/lv2/sys_prx.h"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <algorithm>
|
||||
|
||||
namespace vm { using namespace ps3; }
|
||||
@ -118,6 +119,7 @@ cfg::set_entry g_cfg_load_libs(cfg::root.core, "Load libraries");
|
||||
extern std::string ppu_get_function_name(const std::string& module, u32 fnid);
|
||||
extern std::string ppu_get_variable_name(const std::string& module, u32 vnid);
|
||||
extern void ppu_register_range(u32 addr, u32 size);
|
||||
extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr);
|
||||
extern void ppu_initialize(const ppu_module& info);
|
||||
extern void ppu_initialize();
|
||||
|
||||
@ -125,53 +127,6 @@ extern void sys_initialize_tls(ppu_thread&, u64, u32, u32, u32);
|
||||
|
||||
extern u32 g_ps3_sdk_version;
|
||||
|
||||
// Function lookup table. Not supposed to grow after emulation start.
|
||||
std::vector<ppu_function_t> g_ppu_function_cache;
|
||||
|
||||
// Function name cache in format %s.%s (module name, function name)
|
||||
std::vector<std::string> g_ppu_function_names;
|
||||
|
||||
// Function NID cache for autopause. Autopause tool should probably be rewritten.
|
||||
std::vector<u32> g_ppu_fnid_cache;
|
||||
|
||||
extern std::string ppu_get_module_function_name(u32 index)
|
||||
{
|
||||
if (index < g_ppu_function_names.size())
|
||||
{
|
||||
return g_ppu_function_names[index];
|
||||
}
|
||||
|
||||
return fmt::format(".%u", index);
|
||||
}
|
||||
|
||||
extern void ppu_execute_function(ppu_thread& ppu, u32 index)
|
||||
{
|
||||
if (index < g_ppu_function_cache.size())
|
||||
{
|
||||
// If autopause occures, check_status() will hold the thread until unpaused
|
||||
if (debug::autopause::pause_function(g_ppu_fnid_cache[index])) ppu.test_state();
|
||||
|
||||
if (const auto func = g_ppu_function_cache[index])
|
||||
{
|
||||
func(ppu);
|
||||
LOG_TRACE(HLE, "'%s' finished, r3=0x%llx", ppu_get_module_function_name(index), ppu.gpr[3]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
fmt::throw_exception("Function not registered (index %u)" HERE, index);
|
||||
}
|
||||
|
||||
extern ppu_function_t ppu_get_function(u32 index)
|
||||
{
|
||||
if (index < g_ppu_function_cache.size())
|
||||
{
|
||||
return g_ppu_function_cache[index];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
extern u32 ppu_generate_id(const char* name)
|
||||
{
|
||||
// Symbol name suffix
|
||||
@ -224,6 +179,26 @@ const ppu_static_module* ppu_module_manager::get_module(const std::string& name)
|
||||
return found != map.end() ? found->second : nullptr;
|
||||
}
|
||||
|
||||
// Global linkage information
|
||||
struct ppu_linkage_info
|
||||
{
|
||||
struct module
|
||||
{
|
||||
struct info
|
||||
{
|
||||
u32 export_addr = 0;
|
||||
std::set<u32> imports;
|
||||
};
|
||||
|
||||
// FNID -> (export; [imports...])
|
||||
std::map<u32, info> functions;
|
||||
std::map<u32, info> variables;
|
||||
};
|
||||
|
||||
// Module map
|
||||
std::unordered_map<std::string, module> modules;
|
||||
};
|
||||
|
||||
// Initialize static modules.
|
||||
static void ppu_initialize_modules()
|
||||
{
|
||||
@ -323,13 +298,6 @@ static void ppu_initialize_modules()
|
||||
&ppu_module_manager::sys_lv2dbg,
|
||||
};
|
||||
|
||||
// Reinitialize function cache
|
||||
g_ppu_function_cache = ppu_function_manager::get();
|
||||
g_ppu_function_names.clear();
|
||||
g_ppu_function_names.resize(g_ppu_function_cache.size());
|
||||
g_ppu_fnid_cache.clear();
|
||||
g_ppu_fnid_cache.resize(g_ppu_function_cache.size());
|
||||
|
||||
// "Use" all the modules for correct linkage
|
||||
for (auto& module : registered)
|
||||
{
|
||||
@ -338,8 +306,6 @@ static void ppu_initialize_modules()
|
||||
for (auto& function : module->functions)
|
||||
{
|
||||
LOG_TRACE(LOADER, "** 0x%08X: %s", function.first, function.second.name);
|
||||
g_ppu_function_names.at(function.second.index) = fmt::format("%s.%s", module->name, function.second.name);
|
||||
g_ppu_fnid_cache.at(function.second.index) = function.first;
|
||||
}
|
||||
|
||||
for (auto& variable : module->variables)
|
||||
@ -348,152 +314,31 @@ static void ppu_initialize_modules()
|
||||
variable.second.var->set(0);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize double-purpose fake OPD array for HLE functions
|
||||
const auto& hle_funcs = ppu_function_manager::get();
|
||||
|
||||
// Allocate memory for the array (must be called after fixed allocations)
|
||||
ppu_function_manager::addr = vm::alloc(::size32(hle_funcs) * 8, vm::main);
|
||||
|
||||
// Initialize as PPU executable code
|
||||
ppu_register_range(ppu_function_manager::addr, ::size32(hle_funcs) * 8);
|
||||
|
||||
// Fill the array (visible data: self address and function index)
|
||||
for (u32 addr = ppu_function_manager::addr, index = 0; index < hle_funcs.size(); addr += 8, index++)
|
||||
{
|
||||
// Function address = current address, RTOC = BLR instruction for the interpreter
|
||||
vm::ps3::write32(addr + 0, addr);
|
||||
vm::ps3::write32(addr + 4, ppu_instructions::BLR());
|
||||
|
||||
// Register the HLE function directly
|
||||
ppu_register_function_at(addr + 0, 4, hle_funcs[index]);
|
||||
}
|
||||
|
||||
// Set memory protection to read-only
|
||||
vm::page_protect(ppu_function_manager::addr, ::align(::size32(hle_funcs) * 8, 0x1000), 0, 0, vm::page_writable);
|
||||
}
|
||||
|
||||
// Detect import stub at specified address and inject HACK instruction with index immediate.
|
||||
static bool ppu_patch_import_stub(u32 addr, u32 index)
|
||||
{
|
||||
const auto data = vm::_ptr<u32>(addr);
|
||||
|
||||
using namespace ppu_instructions;
|
||||
|
||||
// Check various patterns:
|
||||
|
||||
if (vm::check_addr(addr, 32) &&
|
||||
(data[0] & 0xffff0000) == LI(r12, 0) &&
|
||||
(data[1] & 0xffff0000) == ORIS(r12, r12, 0) &&
|
||||
(data[2] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
data[3] == STD(r2, r1, 0x28) &&
|
||||
data[4] == LWZ(r0, r12, 0) &&
|
||||
data[5] == LWZ(r2, r12, 4) &&
|
||||
data[6] == MTCTR(r0) &&
|
||||
data[7] == BCTR())
|
||||
{
|
||||
data[0] = STD(r2, r1, 0x28); // Save RTOC
|
||||
data[1] = HACK(index);
|
||||
data[2] = BLR();
|
||||
std::fill(data + 3, data + 8, NOP());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (vm::check_addr(addr, 12) &&
|
||||
(data[0] & 0xffff0000) == LI(r0, 0) &&
|
||||
(data[1] & 0xffff0000) == ORIS(r0, r0, 0) &&
|
||||
(data[2] & 0xfc000003) == B(0, 0, 0))
|
||||
{
|
||||
const auto sub = vm::cptr<u32>::make(addr + 8 + ((s32)data[2] << 6 >> 8 << 2));
|
||||
|
||||
if (vm::check_addr(sub.addr(), 60) &&
|
||||
sub[0x0] == STDU(r1, r1, -0x80) &&
|
||||
sub[0x1] == STD(r2, r1, 0x70) &&
|
||||
sub[0x2] == MR(r2, r0) &&
|
||||
sub[0x3] == MFLR(r0) &&
|
||||
sub[0x4] == STD(r0, r1, 0x90) &&
|
||||
sub[0x5] == LWZ(r2, r2, 0) &&
|
||||
sub[0x6] == LWZ(r0, r2, 0) &&
|
||||
sub[0x7] == LWZ(r2, r2, 4) &&
|
||||
sub[0x8] == MTCTR(r0) &&
|
||||
sub[0x9] == BCTRL() &&
|
||||
sub[0xa] == LD(r2, r1, 0x70) &&
|
||||
sub[0xb] == ADDI(r1, r1, 0x80) &&
|
||||
sub[0xc] == LD(r0, r1, 0x10) &&
|
||||
sub[0xd] == MTLR(r0) &&
|
||||
sub[0xe] == BLR())
|
||||
{
|
||||
data[0] = HACK(index);
|
||||
data[1] = BLR();
|
||||
data[2] = NOP();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (vm::check_addr(addr, 64) &&
|
||||
data[0x0] == MFLR(r0) &&
|
||||
data[0x1] == STD(r0, r1, 0x10) &&
|
||||
data[0x2] == STDU(r1, r1, -0x80) &&
|
||||
data[0x3] == STD(r2, r1, 0x70) &&
|
||||
(data[0x4] & 0xffff0000) == LI(r2, 0) &&
|
||||
(data[0x5] & 0xffff0000) == ORIS(r2, r2, 0) &&
|
||||
data[0x6] == LWZ(r2, r2, 0) &&
|
||||
data[0x7] == LWZ(r0, r2, 0) &&
|
||||
data[0x8] == LWZ(r2, r2, 4) &&
|
||||
data[0x9] == MTCTR(r0) &&
|
||||
data[0xa] == BCTRL() &&
|
||||
data[0xb] == LD(r2, r1, 0x70) &&
|
||||
data[0xc] == ADDI(r1, r1, 0x80) &&
|
||||
data[0xd] == LD(r0, r1, 0x10) &&
|
||||
data[0xe] == MTLR(r0) &&
|
||||
data[0xf] == BLR())
|
||||
{
|
||||
data[0] = HACK(index);
|
||||
data[1] = BLR();
|
||||
std::fill(data + 2, data + 16, NOP());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (vm::check_addr(addr, 60) &&
|
||||
data[0x0] == MFLR(r0) &&
|
||||
data[0x1] == STD(r0, r1, 0x10) &&
|
||||
data[0x2] == STDU(r1, r1, -0x80) &&
|
||||
data[0x3] == STD(r2, r1, 0x70) &&
|
||||
(data[0x4] & 0xffff0000) == LIS(r12, 0) &&
|
||||
(data[0x5] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
data[0x6] == LWZ(r0, r12, 0) &&
|
||||
data[0x7] == LWZ(r2, r12, 4) &&
|
||||
data[0x8] == MTCTR(r0) &&
|
||||
data[0x9] == BCTRL() &&
|
||||
data[0xa] == LD(r2, r1, 0x70) &&
|
||||
data[0xb] == ADDI(r1, r1, 0x80) &&
|
||||
data[0xc] == LD(r0, r1, 0x10) &&
|
||||
data[0xd] == MTLR(r0) &&
|
||||
data[0xe] == BLR())
|
||||
{
|
||||
data[0] = HACK(index);
|
||||
data[1] = BLR();
|
||||
std::fill(data + 2, data + 15, NOP());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (vm::check_addr(addr, 56) &&
|
||||
(data[0x0] & 0xffff0000) == LI(r12, 0) &&
|
||||
(data[0x1] & 0xffff0000) == ORIS(r12, r12, 0) &&
|
||||
(data[0x2] & 0xffff0000) == LWZ(r12, r12, 0) &&
|
||||
data[0x3] == STD(r2, r1, 0x28) &&
|
||||
data[0x4] == MFLR(r0) &&
|
||||
data[0x5] == STD(r0, r1, 0x20) &&
|
||||
data[0x6] == LWZ(r0, r12, 0) &&
|
||||
data[0x7] == LWZ(r2, r12, 4) &&
|
||||
data[0x8] == MTCTR(r0) &&
|
||||
data[0x9] == BCTRL() &&
|
||||
data[0xa] == LD(r0, r1, 0x20) &&
|
||||
data[0xb] == MTLR(r0) &&
|
||||
data[0xc] == LD(r2, r1, 0x28) &&
|
||||
data[0xd] == BLR())
|
||||
{
|
||||
data[0] = HACK(index);
|
||||
data[1] = BLR();
|
||||
std::fill(data + 2, data + 14, NOP());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Global linkage information
|
||||
struct ppu_linkage_info
|
||||
{
|
||||
struct module
|
||||
{
|
||||
using info_t = std::unordered_map<u32, std::pair<u32, std::unordered_set<u32>>>;
|
||||
|
||||
info_t functions;
|
||||
info_t variables;
|
||||
};
|
||||
|
||||
// Module -> (NID -> (export; [imports...]))
|
||||
std::unordered_map<std::string, module> modules;
|
||||
};
|
||||
|
||||
// Link variable
|
||||
static void ppu_patch_variable_refs(u32 vref, u32 vaddr)
|
||||
{
|
||||
@ -605,7 +450,7 @@ static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32
|
||||
// Function linkage info
|
||||
auto& flink = link->modules[module_name].functions[fnid];
|
||||
|
||||
if (flink.first)
|
||||
if (flink.export_addr)
|
||||
{
|
||||
LOG_FATAL(LOADER, "Already linked function '%s' in module '%s'", ppu_get_function_name(module_name, fnid), module_name);
|
||||
}
|
||||
@ -616,18 +461,32 @@ static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32
|
||||
|
||||
if (_sf && (_sf->flags & MFF_FORCED_HLE))
|
||||
{
|
||||
// Inject HACK instruction (TODO: guess function size and analyse B instruction, or reimplement BLR flag for HACK instruction)
|
||||
const auto code = vm::ptr<u32>::make(vm::read32(faddr));
|
||||
code[0] = ppu_instructions::HACK(_sf->index);
|
||||
code[1] = ppu_instructions::BLR();
|
||||
// Inject a branch to the HLE implementation
|
||||
const u32 _entry = vm::read32(faddr);
|
||||
const u32 target = ppu_function_manager::addr + 8 * _sf->index;
|
||||
|
||||
if ((target <= _entry && _entry - target <= 0x2000000) || (target > _entry && target - _entry < 0x2000000))
|
||||
{
|
||||
// Use relative branch
|
||||
vm::write32(_entry, ppu_instructions::B(target - _entry));
|
||||
}
|
||||
else if (target < 0x2000000)
|
||||
{
|
||||
// Use absolute branch if possible
|
||||
vm::write32(_entry, ppu_instructions::B(target, true));
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_FATAL(LOADER, "Failed to patch function at 0x%x (0x%x)", _entry, target);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set exported function
|
||||
flink.first = faddr;
|
||||
flink.export_addr = faddr;
|
||||
|
||||
// Fix imports
|
||||
for (const auto addr : flink.second)
|
||||
for (const u32 addr : flink.imports)
|
||||
{
|
||||
vm::write32(addr, faddr);
|
||||
//LOG_WARNING(LOADER, "Exported function '%s' in module '%s'", ppu_get_function_name(module_name, fnid), module_name);
|
||||
@ -649,17 +508,17 @@ static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32
|
||||
// Variable linkage info
|
||||
auto& vlink = link->modules[module_name].variables[vnid];
|
||||
|
||||
if (vlink.first)
|
||||
if (vlink.export_addr)
|
||||
{
|
||||
LOG_FATAL(LOADER, "Already linked variable '%s' in module '%s'", ppu_get_variable_name(module_name, vnid), module_name);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set exported variable
|
||||
vlink.first = vaddr;
|
||||
vlink.export_addr = vaddr;
|
||||
|
||||
// Fix imports
|
||||
for (const auto vref : vlink.second)
|
||||
for (const auto vref : vlink.imports)
|
||||
{
|
||||
ppu_patch_variable_refs(vref, vaddr);
|
||||
//LOG_WARNING(LOADER, "Exported variable '%s' in module '%s'", ppu_get_variable_name(module_name, vnid), module_name);
|
||||
@ -705,10 +564,13 @@ static void ppu_load_imports(const std::shared_ptr<ppu_linkage_info>& link, u32
|
||||
auto& flink = link->modules[module_name].functions[fnid];
|
||||
|
||||
// Add new import
|
||||
flink.second.emplace(faddr);
|
||||
flink.imports.emplace(faddr);
|
||||
|
||||
// Link if available
|
||||
if (flink.first) vm::write32(faddr, flink.first);
|
||||
if (flink.export_addr)
|
||||
{
|
||||
vm::write32(faddr, flink.export_addr);
|
||||
}
|
||||
|
||||
//LOG_WARNING(LOADER, "Imported function '%s' in module '%s' (0x%x)", ppu_get_function_name(module_name, fnid), module_name, faddr);
|
||||
}
|
||||
@ -726,10 +588,13 @@ static void ppu_load_imports(const std::shared_ptr<ppu_linkage_info>& link, u32
|
||||
auto& vlink = link->modules[module_name].variables[vnid];
|
||||
|
||||
// Add new import
|
||||
vlink.second.emplace(vref);
|
||||
vlink.imports.emplace(vref);
|
||||
|
||||
// Link if available
|
||||
if (vlink.first) ppu_patch_variable_refs(vref, vlink.first);
|
||||
if (vlink.export_addr)
|
||||
{
|
||||
ppu_patch_variable_refs(vref, vlink.export_addr);
|
||||
}
|
||||
|
||||
//LOG_WARNING(LOADER, "Imported variable '%s' in module '%s' (0x%x)", ppu_get_variable_name(module_name, vnid), module_name, vlink.first);
|
||||
}
|
||||
@ -940,8 +805,6 @@ std::shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object& elf, const std::stri
|
||||
|
||||
void ppu_load_exec(const ppu_exec_object& elf)
|
||||
{
|
||||
ppu_initialize_modules();
|
||||
|
||||
if (g_cfg_hook_ppu_funcs)
|
||||
{
|
||||
LOG_TODO(LOADER, "'Hook static functions' option deactivated");
|
||||
@ -996,6 +859,7 @@ void ppu_load_exec(const ppu_exec_object& elf)
|
||||
}
|
||||
}
|
||||
|
||||
// Load section list, used by the analyser
|
||||
for (const auto& s : elf.shdrs)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "** Section: sh_type=0x%x, addr=0x%llx, size=0x%llx, flags=0x%x", s.sh_type, s.sh_addr, s.sh_size, s.sh_flags);
|
||||
@ -1009,6 +873,9 @@ void ppu_load_exec(const ppu_exec_object& elf)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize HLE modules
|
||||
ppu_initialize_modules();
|
||||
|
||||
// Load other programs
|
||||
for (auto& prog : elf.progs)
|
||||
{
|
||||
@ -1296,55 +1163,31 @@ void ppu_load_exec(const ppu_exec_object& elf)
|
||||
for (auto& entry : module.second.functions)
|
||||
{
|
||||
const u32 fnid = entry.first;
|
||||
const u32 faddr = entry.second.first;
|
||||
const u32 faddr = entry.second.export_addr;
|
||||
|
||||
if (faddr == 0)
|
||||
{
|
||||
const std::string fname = ppu_get_function_name(module.first, fnid);
|
||||
|
||||
// Link HLE implementation if available
|
||||
if (const auto _sf = _sm && _sm->functions.count(fnid) ? &_sm->functions.at(fnid) : nullptr)
|
||||
{
|
||||
// Static function
|
||||
for (auto& import : entry.second.second)
|
||||
{
|
||||
const u32 stub = vm::read32(import);
|
||||
LOG_NOTICE(LOADER, "Linking HLE function '%s' in module '%s' (index %u)", fname, module.first, _sf->index);
|
||||
|
||||
if (!ppu_patch_import_stub(stub, _sf->index))
|
||||
{
|
||||
LOG_ERROR(LOADER, "Failed to inject code for function '%s' in module '%s' (0x%x)", _sf->name, module.first, stub);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_NOTICE(LOADER, "Injected hack for function '%s' in module '%s' (*0x%x)", _sf->name, module.first, stub);
|
||||
}
|
||||
for (const u32 import : entry.second.imports)
|
||||
{
|
||||
LOG_TRACE(LOADER, "** Linked at *0x%x (0x%x)", import, vm::read32(import));
|
||||
vm::write32(import, ppu_function_manager::addr + 8 * _sf->index);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO
|
||||
const u32 index = ::size32(g_ppu_function_cache);
|
||||
const std::string& fname = ppu_get_function_name(module.first, fnid);
|
||||
g_ppu_function_cache.emplace_back();
|
||||
g_ppu_function_names.emplace_back(fmt::format("%s.%s", module.first, fname));
|
||||
g_ppu_fnid_cache.emplace_back(fnid);
|
||||
LOG_ERROR(LOADER, "Unknown function '%s' in module '%s'", fname, module.first);
|
||||
|
||||
LOG_ERROR(LOADER, "Unknown function '%s' in module '%s' (index %u)", fname, module.first, index);
|
||||
|
||||
for (auto& import : entry.second.second)
|
||||
for (const u32 import : entry.second.imports)
|
||||
{
|
||||
if (_sm)
|
||||
{
|
||||
const u32 stub = vm::read32(import);
|
||||
|
||||
if (!ppu_patch_import_stub(stub, index))
|
||||
{
|
||||
LOG_ERROR(LOADER, "Failed to inject code for function '%s' in module '%s' (0x%x)", fname, module.first, stub);
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_NOTICE(LOADER, "Injected hack for function '%s' in module '%s' (*0x%x)", fname, module.first, stub);
|
||||
}
|
||||
}
|
||||
|
||||
LOG_WARNING(LOADER, "** Not linked at *0x%x", import);
|
||||
LOG_WARNING(LOADER, "** Not linked at *0x%x (0x%x)", import, vm::read32(import));
|
||||
vm::write32(import, ppu_function_manager::addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1353,16 +1196,18 @@ void ppu_load_exec(const ppu_exec_object& elf)
|
||||
for (auto& entry : module.second.variables)
|
||||
{
|
||||
const u32 vnid = entry.first;
|
||||
const u32 vaddr = entry.second.first;
|
||||
const u32 vaddr = entry.second.export_addr;
|
||||
|
||||
if (vaddr == 0)
|
||||
{
|
||||
// Static variable
|
||||
const std::string vname = ppu_get_variable_name(module.first, vnid);
|
||||
|
||||
// Link HLE variable if available
|
||||
if (const auto _sv = _sm && _sm->variables.count(vnid) ? &_sm->variables.at(vnid) : nullptr)
|
||||
{
|
||||
LOG_NOTICE(LOADER, "Linking HLE variable '%s' in module '%s' (*0x%x):", ppu_get_variable_name(module.first, vnid), module.first, _sv->var->addr());
|
||||
LOG_NOTICE(LOADER, "Linking HLE variable '%s' in module '%s' (*0x%x):", vname, module.first, _sv->var->addr());
|
||||
|
||||
for (auto& ref : entry.second.second)
|
||||
for (const u32 ref : entry.second.imports)
|
||||
{
|
||||
ppu_patch_variable_refs(ref, _sv->var->addr());
|
||||
LOG_NOTICE(LOADER, "** Linked at ref=*0x%x", ref);
|
||||
@ -1370,9 +1215,9 @@ void ppu_load_exec(const ppu_exec_object& elf)
|
||||
}
|
||||
else
|
||||
{
|
||||
LOG_ERROR(LOADER, "Unknown variable '%s' in module '%s'", ppu_get_variable_name(module.first, vnid), module.first);
|
||||
LOG_ERROR(LOADER, "Unknown variable '%s' in module '%s'", vname, module.first);
|
||||
|
||||
for (auto& ref : entry.second.second)
|
||||
for (const u32 ref : entry.second.imports)
|
||||
{
|
||||
LOG_WARNING(LOADER, "** Not linked at ref=*0x%x", ref);
|
||||
}
|
||||
|
@ -121,7 +121,6 @@ public:
|
||||
// Main opcodes (field 0..5)
|
||||
fill_table(0x00, 6, -1,
|
||||
{
|
||||
{ 0x01, &D::HACK },
|
||||
{ 0x02, &D::TDI },
|
||||
{ 0x03, &D::TWI },
|
||||
{ 0x07, &D::MULLI },
|
||||
@ -609,7 +608,6 @@ namespace ppu_instructions
|
||||
|
||||
namespace implicts
|
||||
{
|
||||
inline u32 HACK(u32 index) { return 0x01 << 26 | index; }
|
||||
inline u32 NOP() { return ORI(r0, r0, 0); }
|
||||
inline u32 MR(u32 rt, u32 ra) { return OR(rt, ra, ra, false); }
|
||||
inline u32 LI(u32 rt, u32 imm) { return ADDI(rt, r0, imm); }
|
||||
|
@ -102,7 +102,6 @@ const ppu_decoder<ppu_interpreter_fast> s_ppu_interpreter_fast;
|
||||
extern void ppu_initialize();
|
||||
extern void ppu_initialize(const ppu_module& info);
|
||||
extern void ppu_execute_syscall(ppu_thread& ppu, u64 code);
|
||||
extern void ppu_execute_function(ppu_thread& ppu, u32 index);
|
||||
|
||||
const auto s_ppu_compiled = static_cast<u32*>(utils::memory_reserve(0x100000000));
|
||||
|
||||
@ -157,24 +156,34 @@ extern void ppu_register_range(u32 addr, u32 size)
|
||||
|
||||
extern void ppu_register_function_at(u32 addr, u32 size, ppu_function_t ptr)
|
||||
{
|
||||
// Initialize specific function
|
||||
if (ptr)
|
||||
{
|
||||
s_ppu_compiled[addr / 4] = ::narrow<u32>(reinterpret_cast<std::uintptr_t>(ptr));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!size)
|
||||
{
|
||||
LOG_ERROR(PPU, "ppu_register_function_at(0x%x): empty range", addr);
|
||||
return;
|
||||
}
|
||||
|
||||
ppu_register_range(addr, size);
|
||||
|
||||
if (g_cfg_ppu_decoder.get() == ppu_decoder_type::llvm)
|
||||
{
|
||||
s_ppu_compiled[addr / 4] = ::narrow<u32>(reinterpret_cast<std::uintptr_t>(ptr));
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize interpreter cache
|
||||
const u32 fallback = ::narrow<u32>(reinterpret_cast<std::uintptr_t>(ppu_fallback));
|
||||
|
||||
while (size)
|
||||
{
|
||||
s_ppu_compiled[addr / 4] = ppu_cache(addr);
|
||||
if (s_ppu_compiled[addr / 4] == fallback)
|
||||
{
|
||||
s_ppu_compiled[addr / 4] = ppu_cache(addr);
|
||||
}
|
||||
|
||||
addr += 4;
|
||||
size -= 4;
|
||||
}
|
||||
@ -354,7 +363,7 @@ void ppu_thread::cpu_task()
|
||||
}
|
||||
case ppu_cmd::hle_call:
|
||||
{
|
||||
cmd_pop(), ppu_execute_function(*this, arg);
|
||||
cmd_pop(), ppu_function_manager::get().at(arg)(*this);
|
||||
break;
|
||||
}
|
||||
case ppu_cmd::initialize:
|
||||
@ -379,7 +388,8 @@ void ppu_thread::exec_task()
|
||||
{
|
||||
if (g_cfg_ppu_decoder.get() == ppu_decoder_type::llvm)
|
||||
{
|
||||
return reinterpret_cast<ppu_function_t>((std::uintptr_t)s_ppu_compiled[cia / 4])(*this);
|
||||
reinterpret_cast<ppu_function_t>(static_cast<std::uintptr_t>(s_ppu_compiled[cia / 4]))(*this);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto base = vm::_ptr<const u8>(0);
|
||||
@ -558,7 +568,7 @@ void ppu_thread::fast_call(u32 addr, u32 rtoc)
|
||||
|
||||
cia = addr;
|
||||
gpr[2] = rtoc;
|
||||
lr = Emu.GetCPUThreadStop();
|
||||
lr = ppu_function_manager::addr + 8; // HLE stop address
|
||||
last_function = nullptr;
|
||||
|
||||
g_tls_log_prefix = []
|
||||
@ -662,8 +672,6 @@ const ppu_decoder<ppu_itype> s_ppu_itype;
|
||||
extern u64 get_timebased_time();
|
||||
extern ppu_function_t ppu_get_syscall(u64 code);
|
||||
extern std::string ppu_get_syscall_name(u64 code);
|
||||
extern ppu_function_t ppu_get_function(u32 index);
|
||||
extern std::string ppu_get_module_function_name(u32 index);
|
||||
|
||||
extern __m128 sse_exp2_ps(__m128 A);
|
||||
extern __m128 sse_log2_ps(__m128 A);
|
||||
@ -782,25 +790,6 @@ extern void ppu_initialize()
|
||||
return;
|
||||
}
|
||||
|
||||
if (g_cfg_ppu_decoder.get() != ppu_decoder_type::llvm || _funcs->empty())
|
||||
{
|
||||
if (!Emu.GetCPUThreadStop())
|
||||
{
|
||||
auto ppu_thr_stop_data = vm::ptr<u32>::make(vm::alloc(2 * 4, vm::main));
|
||||
Emu.SetCPUThreadStop(ppu_thr_stop_data.addr());
|
||||
ppu_thr_stop_data[0] = ppu_instructions::HACK(1);
|
||||
ppu_thr_stop_data[1] = ppu_instructions::BLR();
|
||||
ppu_register_function_at(ppu_thr_stop_data.addr(), 8, nullptr);
|
||||
}
|
||||
|
||||
for (const auto& func : *_funcs)
|
||||
{
|
||||
ppu_register_function_at(func.addr, func.size, nullptr);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t fpos = 0;
|
||||
|
||||
while (fpos < _funcs->size())
|
||||
@ -891,7 +880,6 @@ extern void ppu_initialize(const ppu_module& info)
|
||||
{ "__end", (u64)&ppu_unreachable },
|
||||
{ "__check", (u64)&ppu_check },
|
||||
{ "__trace", (u64)&ppu_trace },
|
||||
{ "__hlecall", (u64)&ppu_execute_function },
|
||||
{ "__syscall", (u64)&ppu_execute_syscall },
|
||||
{ "__get_tb", (u64)&get_timebased_time },
|
||||
{ "__lwarx", (u64)&ppu_lwarx },
|
||||
@ -918,18 +906,6 @@ extern void ppu_initialize(const ppu_module& info)
|
||||
}
|
||||
}
|
||||
|
||||
for (u64 index = 1; ; index++)
|
||||
{
|
||||
if (auto func = ppu_get_function(index))
|
||||
{
|
||||
link_table.emplace(ppu_get_module_function_name(index), (u64)func);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const auto jit = fxm::make<jit_compiler>(std::move(link_table), g_cfg_llvm_cpu.get());
|
||||
|
||||
LOG_SUCCESS(PPU, "LLVM: JIT initialized (%s)", jit->cpu());
|
||||
@ -1058,7 +1034,6 @@ extern void ppu_initialize(const ppu_module& info)
|
||||
pm.run(*func);
|
||||
|
||||
const auto _syscall = module->getFunction("__syscall");
|
||||
const auto _hlecall = module->getFunction("__hlecall");
|
||||
|
||||
for (auto i = inst_begin(*func), end = inst_end(*func); i != end;)
|
||||
{
|
||||
@ -1084,20 +1059,6 @@ extern void ppu_initialize(const ppu_module& info)
|
||||
}
|
||||
}
|
||||
|
||||
if (cif == _hlecall && op1 && isa<ConstantInt>(op1))
|
||||
{
|
||||
const u32 index = static_cast<u32>(cast<ConstantInt>(op1)->getZExtValue());
|
||||
|
||||
if (const auto ptr = ppu_get_function(index))
|
||||
{
|
||||
const auto n = ppu_get_module_function_name(index);
|
||||
const auto f = cast<Function>(module->getOrInsertFunction(n, _func));
|
||||
|
||||
// Call the function directly
|
||||
ReplaceInstWithInst(ci, CallInst::Create(f, {ci->getArgOperand(0)}));
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -1775,12 +1775,6 @@ void PPUTranslator::BC(ppu_opcode_t op)
|
||||
CallFunction(target, !op.lk);
|
||||
}
|
||||
|
||||
void PPUTranslator::HACK(ppu_opcode_t op)
|
||||
{
|
||||
Call(GetType<void>(), "__hlecall", m_thread, m_ir->getInt32(op.opcode & 0x3ffffff));
|
||||
UndefineVolatileRegisters();
|
||||
}
|
||||
|
||||
void PPUTranslator::SC(ppu_opcode_t op)
|
||||
{
|
||||
if (op.opcode != ppu_instructions::SC(0) && op.opcode != ppu_instructions::SC(1))
|
||||
|
@ -599,7 +599,6 @@ public:
|
||||
void ADDI(ppu_opcode_t op);
|
||||
void ADDIS(ppu_opcode_t op);
|
||||
void BC(ppu_opcode_t op);
|
||||
void HACK(ppu_opcode_t op);
|
||||
void SC(ppu_opcode_t op);
|
||||
void B(ppu_opcode_t op);
|
||||
void MCRF(ppu_opcode_t op);
|
||||
|
@ -54,3 +54,5 @@ u32 arm_function_manager::add_function(arm_function_t function)
|
||||
|
||||
return ::size32(list) - 1;
|
||||
}
|
||||
|
||||
DECLARE(arm_function_manager::addr);
|
||||
|
@ -468,6 +468,9 @@ public:
|
||||
{
|
||||
return access();
|
||||
}
|
||||
|
||||
// Allocation address
|
||||
static u32 addr;
|
||||
};
|
||||
|
||||
template<typename T, T Func>
|
||||
|
@ -630,7 +630,7 @@ void arm_load_exec(const arm_exec_object& elf)
|
||||
const auto stop_code = vm::ptr<u32>::make(vm::alloc(3 * 4, vm::main));
|
||||
stop_code[0] = 0xf870; // HACK instruction (Thumb)
|
||||
stop_code[1] = 1; // Predefined function index (HLE return)
|
||||
Emu.SetCPUThreadStop(stop_code.addr());
|
||||
arm_function_manager::addr = stop_code.addr();
|
||||
|
||||
const std::string& thread_name = proc_param->sceUserMainThreadName ? proc_param->sceUserMainThreadName.get_ptr() : "main_thread";
|
||||
const u32 stack_size = proc_param->sceUserMainThreadStackSize ? proc_param->sceUserMainThreadStackSize->value() : 256 * 1024;
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include "ARMv7Thread.h"
|
||||
#include "ARMv7Opcodes.h"
|
||||
#include "ARMv7Interpreter.h"
|
||||
#include "ARMv7Function.h"
|
||||
|
||||
#include "Utilities/GSL.h"
|
||||
|
||||
@ -122,7 +123,7 @@ void ARMv7Thread::fast_call(u32 addr)
|
||||
const auto old_func = last_function;
|
||||
|
||||
PC = addr;
|
||||
LR = Emu.GetCPUThreadStop();
|
||||
LR = arm_function_manager::addr; // TODO
|
||||
last_function = nullptr;
|
||||
|
||||
auto at_ret = gsl::finally([&]()
|
||||
|
@ -62,12 +62,6 @@ namespace rpcs3
|
||||
event<void>& on_resume() { static event<void> on_resume; return on_resume; }
|
||||
}
|
||||
|
||||
Emulator::Emulator()
|
||||
: m_status(Stopped)
|
||||
, m_cpu_thr_stop(0)
|
||||
{
|
||||
}
|
||||
|
||||
void Emulator::Init()
|
||||
{
|
||||
if (!g_tty)
|
||||
@ -106,8 +100,6 @@ void Emulator::Init()
|
||||
fs::create_dir(dev_hdd1 + "game/");
|
||||
fs::create_path(dev_hdd1);
|
||||
fs::create_path(dev_usb);
|
||||
|
||||
SetCPUThreadStop(0);
|
||||
}
|
||||
|
||||
void Emulator::SetPath(const std::string& path, const std::string& elf_path)
|
||||
|
@ -39,15 +39,13 @@ enum Status : u32
|
||||
|
||||
class Emulator final
|
||||
{
|
||||
atomic_t<u32> m_status;
|
||||
atomic_t<u32> m_status{Stopped};
|
||||
|
||||
EmuCallbacks m_cb;
|
||||
|
||||
atomic_t<u64> m_pause_start_time; // set when paused
|
||||
atomic_t<u64> m_pause_amend_time; // increased when resumed
|
||||
|
||||
u32 m_cpu_thr_stop;
|
||||
|
||||
std::string m_path;
|
||||
std::string m_elf_path;
|
||||
std::string m_cache_path;
|
||||
@ -55,7 +53,7 @@ class Emulator final
|
||||
std::string m_title;
|
||||
|
||||
public:
|
||||
Emulator();
|
||||
Emulator() = default;
|
||||
|
||||
void SetCallbacks(EmuCallbacks&& cb)
|
||||
{
|
||||
@ -109,13 +107,6 @@ public:
|
||||
return m_pause_amend_time;
|
||||
}
|
||||
|
||||
void SetCPUThreadStop(u32 addr)
|
||||
{
|
||||
m_cpu_thr_stop = addr;
|
||||
}
|
||||
|
||||
u32 GetCPUThreadStop() const { return m_cpu_thr_stop; }
|
||||
|
||||
bool BootGame(const std::string& path, bool direct = false);
|
||||
|
||||
static std::string GetGameDir();
|
||||
|
Loading…
x
Reference in New Issue
Block a user