rpcs3/rpcs3/Emu/Cell/PPUModule.cpp
Ivan aafcf44581 Header optimizations (#1684)
Shouldn't break anything. I hope.
2016-04-27 01:27:24 +03:00

1181 lines
33 KiB
C++

#include "stdafx.h"
#include "Utilities/AutoPause.h"
#include "Crypto/sha1.h"
#include "Loader/ELF.h"
#include "Emu/System.h"
#include "Emu/IdManager.h"
#include "Emu/Cell/PPUOpcodes.h"
#include "Emu/Cell/PPUModule.h"
#include "Emu/Cell/lv2/sys_prx.h"
cfg::bool_entry g_cfg_hook_ppu_funcs(cfg::root.core, "Hook static functions");
cfg::bool_entry g_cfg_load_liblv2(cfg::root.core, "Load liblv2.sprx only");
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 sys_initialize_tls(PPUThread&, u64, u32, u32, u32);
// Function lookup table. Not supposed to grow after emulation start.
std::vector<ppu_function_t> g_ppu_function_cache;
// Function NID cache for autopause. Autopause tool should probably be rewritten.
std::vector<u32> g_ppu_fnid_cache;
extern void ppu_execute_function(PPUThread& 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.check_status()) throw cpu_state::ret;
if (const auto func = g_ppu_function_cache[index])
{
const auto previous_function = ppu.last_function; // TODO: use gsl::finally or something, but only if it's equally fast
try
{
func(ppu);
}
catch (...)
{
LOG_WARNING(PPU, "Function '%s' aborted", ppu.last_function);
ppu.last_function = previous_function;
throw;
}
LOG_TRACE(PPU, "Function '%s' finished, r3=0x%llx", ppu.last_function, ppu.GPR[3]);
ppu.last_function = previous_function;
return;
}
}
throw fmt::exception("Function not registered (index %u)" HERE, index);
}
extern u32 ppu_generate_id(const char* name)
{
// Symbol name suffix
const auto suffix = "\x67\x59\x65\x99\x04\x25\x04\x90\x56\x64\x27\x49\x94\x89\x74\x1A";
sha1_context ctx;
u8 output[20];
// Compute SHA-1 hash
sha1_starts(&ctx);
sha1_update(&ctx, reinterpret_cast<const u8*>(name), std::strlen(name));
sha1_update(&ctx, reinterpret_cast<const u8*>(suffix), std::strlen(suffix));
sha1_finish(&ctx, output);
return reinterpret_cast<le_t<u32>&>(output[0]);
}
ppu_static_module::ppu_static_module(const char* name)
: name(name)
{
ppu_module_manager::register_module(this);
}
std::unordered_map<std::string, ppu_static_module*>& ppu_module_manager::access()
{
static std::unordered_map<std::string, ppu_static_module*> map;
return map;
}
void ppu_module_manager::register_module(ppu_static_module* module)
{
access().emplace(module->name, module);
}
ppu_static_function& ppu_module_manager::access_static_function(const char* module, u32 fnid)
{
return access().at(module)->functions[fnid];
}
ppu_static_variable& ppu_module_manager::access_static_variable(const char* module, u32 vnid)
{
return access().at(module)->variables[vnid];
}
const ppu_static_module* ppu_module_manager::get_module(const std::string& name)
{
const auto& map = access();
const auto found = map.find(name);
return found != map.end() ? found->second : nullptr;
}
// Initialize static modules.
static void ppu_initialize_modules()
{
const std::initializer_list<const ppu_static_module*> registered
{
&ppu_module_manager::cellAdec,
&ppu_module_manager::cellAtrac,
&ppu_module_manager::cellAtracMulti,
&ppu_module_manager::cellAudio,
&ppu_module_manager::cellAvconfExt,
&ppu_module_manager::cellBGDL,
&ppu_module_manager::cellCamera,
&ppu_module_manager::cellCelp8Enc,
&ppu_module_manager::cellCelpEnc,
&ppu_module_manager::cellDaisy,
&ppu_module_manager::cellDmux,
&ppu_module_manager::cellFiber,
&ppu_module_manager::cellFont,
&ppu_module_manager::cellFontFT,
&ppu_module_manager::cellFs,
&ppu_module_manager::cellGame,
&ppu_module_manager::cellGameExec,
&ppu_module_manager::cellGcmSys,
&ppu_module_manager::cellGem,
&ppu_module_manager::cellGifDec,
&ppu_module_manager::cellHttp,
&ppu_module_manager::cellHttps,
&ppu_module_manager::cellHttpUtil,
&ppu_module_manager::cellImeJp,
&ppu_module_manager::cellJpgDec,
&ppu_module_manager::cellJpgEnc,
&ppu_module_manager::cellKey2char,
&ppu_module_manager::cellL10n,
&ppu_module_manager::cellMic,
&ppu_module_manager::cellMusic,
&ppu_module_manager::cellMusicDecode,
&ppu_module_manager::cellMusicExport,
&ppu_module_manager::cellNetCtl,
&ppu_module_manager::cellOskDialog,
&ppu_module_manager::cellOvis,
&ppu_module_manager::cellPamf,
&ppu_module_manager::cellPhotoDecode,
&ppu_module_manager::cellPhotoExport,
&ppu_module_manager::cellPhotoImportUtil,
&ppu_module_manager::cellPngDec,
&ppu_module_manager::cellPngEnc,
&ppu_module_manager::cellPrint,
&ppu_module_manager::cellRec,
&ppu_module_manager::cellRemotePlay,
&ppu_module_manager::cellResc,
&ppu_module_manager::cellRtc,
&ppu_module_manager::cellRudp,
&ppu_module_manager::cellSail,
&ppu_module_manager::cellSailRec,
&ppu_module_manager::cellSaveData,
&ppu_module_manager::cellMinisSaveData,
&ppu_module_manager::cellScreenShot,
&ppu_module_manager::cellSearch,
&ppu_module_manager::cellSheap,
&ppu_module_manager::cellSpudll,
&ppu_module_manager::cellSpurs,
&ppu_module_manager::cellSpursJq,
&ppu_module_manager::cellSsl,
&ppu_module_manager::cellSubdisplay,
&ppu_module_manager::cellSync,
&ppu_module_manager::cellSync2,
&ppu_module_manager::cellSysconf,
&ppu_module_manager::cellSysmodule,
&ppu_module_manager::cellSysutil,
&ppu_module_manager::cellSysutilAp,
&ppu_module_manager::cellSysutilAvc,
&ppu_module_manager::cellSysutilAvc2,
&ppu_module_manager::cellSysutilMisc,
&ppu_module_manager::cellUsbd,
&ppu_module_manager::cellUsbPspcm,
&ppu_module_manager::cellUserInfo,
&ppu_module_manager::cellVdec,
&ppu_module_manager::cellVideoExport,
&ppu_module_manager::cellVideoUpload,
&ppu_module_manager::cellVoice,
&ppu_module_manager::cellVpost,
&ppu_module_manager::libmixer,
&ppu_module_manager::libsnd3,
&ppu_module_manager::libsynth2,
&ppu_module_manager::sceNp,
&ppu_module_manager::sceNp2,
&ppu_module_manager::sceNpClans,
&ppu_module_manager::sceNpCommerce2,
&ppu_module_manager::sceNpSns,
&ppu_module_manager::sceNpTrophy,
&ppu_module_manager::sceNpTus,
&ppu_module_manager::sceNpUtil,
&ppu_module_manager::sys_io,
&ppu_module_manager::libnet,
&ppu_module_manager::sysPrxForUser,
&ppu_module_manager::sys_libc,
&ppu_module_manager::sys_lv2dbg,
};
// Reinitialize function cache
g_ppu_function_cache = ppu_function_manager::get();
g_ppu_fnid_cache = std::vector<u32>(g_ppu_function_cache.size());
// "Use" all the modules for correct linkage
for (auto& module : registered)
{
LOG_TRACE(LOADER, "Registered static module: %s", module->name);
for (auto& function : module->functions)
{
LOG_TRACE(LOADER, "** 0x%08X: %s", function.first, function.second.name);
g_ppu_fnid_cache.at(function.second.index) = function.first;
}
for (auto& variable : module->variables)
{
LOG_TRACE(LOADER, "** &0x%08X: %s (size=0x%x, align=0x%x)", variable.first, variable.second.name, variable.second.size, variable.second.align);
variable.second.var->set(0);
}
}
}
// 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::cptr<u32>::make(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())
{
std::memset(vm::base(addr), 0, 32);
vm::write32(addr + 0, STD(r2, r1, 0x28)); // Save RTOC
vm::write32(addr + 4, HACK(index));
vm::write32(addr + 8, BLR());
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())
{
vm::write32(addr + 0, HACK(index));
vm::write32(addr + 4, BLR());
vm::write32(addr + 8, 0);
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())
{
std::memset(vm::base(addr), 0, 64);
vm::write32(addr + 0, HACK(index));
vm::write32(addr + 4, BLR());
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) == 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())
{
std::memset(vm::base(addr), 0, 64);
vm::write32(addr + 0, HACK(index));
vm::write32(addr + 4, BLR());
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())
{
std::memset(vm::base(addr), 0, 56);
vm::write32(addr + 0, HACK(index));
vm::write32(addr + 4, BLR());
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_stub(u32 vref, u32 vaddr)
{
struct vref_t
{
be_t<u32> type;
be_t<u32> addr;
be_t<u32> unk0;
};
for (auto ref = vm::ptr<vref_t>::make(vref); ref->type; ref++)
{
if (ref->unk0) LOG_ERROR(LOADER, "**** VREF(%u): Unknown values (0x%x, 0x%x)", ref->type, ref->addr, ref->unk0);
// OPs are probably similar to relocations
switch (u32 type = ref->type)
{
case 0x1:
{
const u32 value = vm::_ref<u32>(ref->addr) = vaddr;
LOG_WARNING(LOADER, "**** VREF(1): 0x%x <- 0x%x", ref->addr, value);
break;
}
case 0x4:
case 0x6:
default: LOG_ERROR(LOADER, "**** VREF(%u): Unknown/Illegal type (0x%x, 0x%x)", ref->type, ref->addr, ref->unk0);
}
}
}
// Export or import module struct
struct ppu_prx_module_info
{
u8 size;
u8 unk0;
be_t<u16> version;
be_t<u16> attributes;
be_t<u16> num_func;
be_t<u16> num_var;
be_t<u16> num_tlsvar;
u8 info_hash;
u8 info_tlshash;
u8 unk1[2];
vm::bcptr<char> name;
vm::bcptr<u32> nids; // Imported FNIDs, Exported NIDs
vm::bptr<u32> addrs;
vm::bcptr<u32> vnids; // Imported VNIDs
vm::bcptr<u32> vstubs;
be_t<u32> unk4;
be_t<u32> unk5;
};
// Load and register exports; return special exports found (nameless module)
static auto ppu_load_exports(const std::shared_ptr<ppu_linkage_info>& link, u32 exports_start, u32 exports_end)
{
std::unordered_map<u32, u32> result;
for (u32 addr = exports_start; addr < exports_end;)
{
const auto& lib = vm::_ref<const ppu_prx_module_info>(addr);
if (!lib.name)
{
// Set special exports
for (u32 i = 0, end = lib.num_func + lib.num_var; i < end; i++)
{
const u32 nid = lib.nids[i];
const u32 addr = lib.addrs[i];
if (i < lib.num_func)
{
LOG_NOTICE(LOADER, "** Special: [%s] at 0x%x", ppu_get_function_name({}, nid), addr);
}
else
{
LOG_NOTICE(LOADER, "** Special: &[%s] at 0x%x", ppu_get_variable_name({}, nid), addr);
}
result.emplace(nid, addr);
}
addr += lib.size ? lib.size : sizeof(ppu_prx_module_info);
continue;
}
const std::string module_name(lib.name.get_ptr());
LOG_NOTICE(LOADER, "** Exported module '%s' (0x%x, 0x%x, 0x%x, 0x%x)", module_name, lib.vnids, lib.vstubs, lib.unk4, lib.unk5);
if (lib.num_tlsvar)
{
LOG_FATAL(LOADER, "Unexpected num_tlsvar (%u)!", lib.num_tlsvar);
}
// Static module
const auto _sm = ppu_module_manager::get_module(module_name);
const auto fnids = +lib.nids;
const auto faddrs = +lib.addrs;
// Get functions
for (u32 i = 0, end = lib.num_func; i < end; i++)
{
const u32 fnid = fnids[i];
const u32 faddr = faddrs[i];
LOG_NOTICE(LOADER, "**** %s export: [%s] at 0x%x", module_name, ppu_get_function_name(module_name, fnid), faddr);
// Function linkage info
auto& flink = link->modules[module_name].functions[fnid];
if (flink.first)
{
LOG_FATAL(LOADER, "Already linked function '%s' in module '%s'", ppu_get_function_name(module_name, fnid), module_name);
}
else
{
// Static function
const auto _sf = _sm && _sm->functions.count(fnid) ? &_sm->functions.at(fnid) : nullptr;
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();
}
else
{
// Set exported function
flink.first = faddr;
// Fix imports
for (const auto addr : flink.second)
{
vm::write32(addr, faddr);
//LOG_WARNING(LOADER, "Exported function '%s' in module '%s'", ppu_get_function_name(module_name, fnid), module_name);
}
}
}
}
const auto vnids = lib.nids + lib.num_func;
const auto vaddrs = lib.addrs + lib.num_func;
// Get variables
for (u32 i = 0, end = lib.num_var; i < end; i++)
{
const u32 vnid = vnids[i];
const u32 vaddr = vaddrs[i];
LOG_NOTICE(LOADER, "**** %s export: &[%s] at 0x%x", module_name, ppu_get_variable_name(module_name, vnid), vaddr);
// Variable linkage info
auto& vlink = link->modules[module_name].variables[vnid];
if (vlink.first)
{
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;
// Fix imports
for (const auto vref : vlink.second)
{
ppu_patch_variable_stub(vref, vaddr);
//LOG_WARNING(LOADER, "Exported variable '%s' in module '%s'", ppu_get_variable_name(module_name, vnid), module_name);
}
}
}
addr += lib.size ? lib.size : sizeof(ppu_prx_module_info);
}
return result;
}
static void ppu_load_imports(const std::shared_ptr<ppu_linkage_info>& link, u32 imports_start, u32 imports_end)
{
for (u32 addr = imports_start; addr < imports_end;)
{
const auto& lib = vm::_ref<const ppu_prx_module_info>(addr);
const std::string module_name(lib.name.get_ptr());
LOG_NOTICE(LOADER, "** Imported module '%s' (0x%x, 0x%x)", module_name, lib.unk4, lib.unk5);
if (lib.num_tlsvar)
{
LOG_FATAL(LOADER, "Unexpected num_tlsvar (%u)!", lib.num_tlsvar);
}
// Static module
const auto _sm = ppu_module_manager::get_module(module_name);
const auto fnids = +lib.nids;
const auto faddrs = +lib.addrs;
for (u32 i = 0, end = lib.num_func; i < end; i++)
{
const u32 fnid = fnids[i];
const u32 fstub = faddrs[i];
const u32 faddr = (faddrs + i).addr();
LOG_NOTICE(LOADER, "**** %s import: [%s] -> 0x%x", module_name, ppu_get_function_name(module_name, fnid), fstub);
// Function linkage info
auto& flink = link->modules[module_name].functions[fnid];
// Add new import
flink.second.emplace(faddr);
// Link if available
if (flink.first) vm::write32(faddr, flink.first);
//LOG_WARNING(LOADER, "Imported function '%s' in module '%s' (0x%x)", ppu_get_function_name(module_name, fnid), module_name, faddr);
}
const auto vnids = +lib.vnids;
const auto vstubs = +lib.vstubs;
for (u32 i = 0, end = lib.num_var; i < end; i++)
{
const u32 vnid = vnids[i];
const u32 vref = vstubs[i];
LOG_NOTICE(LOADER, "**** %s import: &[%s] (ref=*0x%x)", module_name, ppu_get_variable_name(module_name, vnid), vref);
// Variable linkage info
auto& vlink = link->modules[module_name].variables[vnid];
// Add new import
vlink.second.emplace(vref);
// Link if available
if (vlink.first) ppu_patch_variable_stub(vref, vlink.first);
//LOG_WARNING(LOADER, "Imported variable '%s' in module '%s' (0x%x)", ppu_get_variable_name(module_name, vnid), module_name, vlink.first);
}
addr += lib.size ? lib.size : sizeof(ppu_prx_module_info);
}
}
template<>
std::shared_ptr<lv2_prx_t> ppu_prx_loader::load() const
{
std::vector<u32> segments;
for (const auto& prog : progs)
{
LOG_NOTICE(LOADER, "** 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);
switch (const u32 p_type = prog.p_type)
{
case 0x1: // LOAD
{
if (prog.p_memsz)
{
const u32 mem_size = fmt::narrow<u32>("Invalid p_memsz (0x%llx)" HERE, prog.p_memsz);
const u32 file_size = fmt::narrow<u32>("Invalid p_filesz (0x%llx)" HERE, prog.p_filesz);
const u32 init_addr = fmt::narrow<u32>("Invalid p_vaddr (0x%llx)" HERE, prog.p_vaddr);
// Alloc segment memory
const u32 addr = vm::alloc(mem_size, vm::main);
if (!addr)
{
throw fmt::exception("vm::alloc() failed (size=0x%x)", mem_size);
}
// Copy data
std::memcpy(vm::base(addr), prog.bin.data(), file_size);
LOG_WARNING(LOADER, "**** Loaded to 0x%x (size=0x%x)", addr, mem_size);
segments.push_back(addr);
}
break;
}
case 0x700000a4: break; // Relocations
default: LOG_ERROR(LOADER, "Unknown segment type! 0x%08x", p_type);
}
}
// Do relocations
for (auto& prog : progs)
{
switch (const u32 p_type = prog.p_type)
{
case 0x700000a4:
{
// Relocation information of the SCE_PPURELA segment
struct ppu_prx_relocation_info
{
be_t<u64> offset;
be_t<u16> unk0;
u8 index_value;
u8 index_addr;
be_t<u32> type;
vm::bptr<void, u64> ptr;
};
for (uint i = 0; i < prog.p_filesz; i += sizeof(ppu_prx_relocation_info))
{
const auto& rel = reinterpret_cast<const ppu_prx_relocation_info&>(prog.bin[i]);
const u32 raddr = vm::cast(segments.at(rel.index_addr) + rel.offset, HERE);
const u64 rdata = segments.at(rel.index_value) + rel.ptr.addr();
switch (const u32 type = rel.type)
{
case 1:
{
const u32 value = vm::_ref<u32>(raddr) = static_cast<u32>(rdata);
LOG_TRACE(LOADER, "**** RELOCATION(1): 0x%x <- 0x%08x (0x%llx)", raddr, value, rdata);
break;
}
case 4:
{
const u16 value = vm::_ref<u16>(raddr) = static_cast<u16>(rdata);
LOG_TRACE(LOADER, "**** RELOCATION(4): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
break;
}
case 5:
{
const u16 value = vm::_ref<u16>(raddr) = static_cast<u16>(rdata >> 16);
LOG_TRACE(LOADER, "**** RELOCATION(5): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
break;
}
case 6:
{
const u16 value = vm::_ref<u16>(raddr) = static_cast<u16>(rdata >> 16) + (rdata & 0x8000 ? 1 : 0);
LOG_TRACE(LOADER, "**** RELOCATION(6): 0x%x <- 0x%04x (0x%llx)", raddr, value, rdata);
break;
}
case 10:
case 44:
case 57:
default: LOG_ERROR(LOADER, "**** RELOCATION(%u): Illegal/Unknown type! (addr=0x%x)", type, raddr);
}
}
break;
}
}
}
// Access linkage information object
const auto link = fxm::get_always<ppu_linkage_info>();
// Create new PRX object
auto prx = idm::make_ptr<lv2_prx_t>();
if (!progs.empty() && progs[0].p_paddr)
{
struct ppu_prx_library_info
{
be_t<u16> attributes;
be_t<u16> version;
char name[28];
be_t<u32> toc;
be_t<u32> exports_start;
be_t<u32> exports_end;
be_t<u32> imports_start;
be_t<u32> imports_end;
};
// Access library information (TODO)
const auto& lib_info = vm::_ref<const ppu_prx_library_info>(vm::cast(segments[0] + progs[0].p_paddr - progs[0].p_offset, HERE));
const auto& lib_name = std::string(lib_info.name);
LOG_WARNING(LOADER, "Library %s (toc=0x%x, rtoc=0x%x):", lib_name, lib_info.toc, lib_info.toc + segments[0]);
prx->specials = ppu_load_exports(link, lib_info.exports_start, lib_info.exports_end);
ppu_load_imports(link, lib_info.imports_start, lib_info.imports_end);
}
else
{
LOG_FATAL(LOADER, "Library %s: PRX library info not found");
}
prx->start.set(prx->specials[0xbc9a0086]);
prx->stop.set(prx->specials[0xab779874]);
prx->exit.set(prx->specials[0x3ab9a95e]);
return prx;
}
template<>
void ppu_exec_loader::load() const
{
ppu_initialize_modules();
if (g_cfg_hook_ppu_funcs)
{
LOG_TODO(LOADER, "'Hook static functions' option deactivated");
}
// Access linkage information object
const auto link = fxm::get_always<ppu_linkage_info>();
// Allocate memory at fixed positions
for (const auto& prog : progs)
{
const u32 addr = vm::cast(prog.p_vaddr, HERE);
const u32 size = fmt::narrow<u32>("Invalid p_memsz: 0x%llx" HERE, prog.p_memsz);
if (prog.p_type == 0x1 /* LOAD */ && prog.p_memsz)
{
if (prog.bin.size() > size || prog.bin.size() != prog.p_filesz)
throw fmt::exception("Invalid binary size (0x%llx, memsz=0x%x)", prog.bin.size(), size);
if (!vm::falloc(addr, size, vm::main))
throw fmt::exception("vm::falloc() failed (addr=0x%x, memsz=0x%x)", addr, size);
std::memcpy(vm::base(addr), prog.bin.data(), prog.bin.size());
}
}
// Load other programs
for (auto& prog : progs)
{
switch (const u32 p_type = prog.p_type)
{
case 0x00000001: break; //LOAD
case 0x00000007: //TLS
{
const u32 addr = vm::cast(prog.p_vaddr, HERE);
const u32 filesz = fmt::narrow<u32>("Invalid p_filesz (0x%llx)" HERE, prog.p_filesz);
const u32 memsz = fmt::narrow<u32>("Invalid p_memsz (0x%llx)" HERE, prog.p_memsz);
Emu.SetTLSData(addr, filesz, memsz);
LOG_NOTICE(LOADER, "*** TLS segment addr: 0x%08x", Emu.GetTLSAddr());
LOG_NOTICE(LOADER, "*** TLS segment size: 0x%08x", Emu.GetTLSFilesz());
LOG_NOTICE(LOADER, "*** TLS memory size: 0x%08x", Emu.GetTLSMemsz());
break;
}
case 0x60000001: //LOOS+1
{
if (prog.p_filesz)
{
struct process_param_t
{
be_t<u32> size;
be_t<u32> magic;
be_t<u32> version;
be_t<u32> sdk_version;
be_t<s32> primary_prio;
be_t<u32> primary_stacksize;
be_t<u32> malloc_pagesize;
be_t<u32> ppc_seg;
//be_t<u32> crash_dump_param_addr;
};
const auto& info = vm::ps3::_ref<process_param_t>(vm::cast(prog.p_vaddr, HERE));
if (info.size < sizeof(process_param_t))
{
LOG_WARNING(LOADER, "Bad process_param size! [0x%x : 0x%x]", info.size, SIZE_32(process_param_t));
}
if (info.magic != 0x13bcc5f6)
{
LOG_ERROR(LOADER, "Bad process_param magic! [0x%x]", info.magic);
}
else
{
LOG_NOTICE(LOADER, "*** sdk version: 0x%x", info.sdk_version);
LOG_NOTICE(LOADER, "*** primary prio: %d", info.primary_prio);
LOG_NOTICE(LOADER, "*** primary stacksize: 0x%x", info.primary_stacksize);
LOG_NOTICE(LOADER, "*** malloc pagesize: 0x%x", info.malloc_pagesize);
LOG_NOTICE(LOADER, "*** ppc seg: 0x%x", info.ppc_seg);
//LOG_NOTICE(LOADER, "*** crash dump param addr: 0x%x", info.crash_dump_param_addr);
Emu.SetParams(info.sdk_version, info.malloc_pagesize, std::max<u32>(info.primary_stacksize, 0x4000), info.primary_prio);
}
}
break;
}
case 0x60000002: //LOOS+2
{
if (prog.p_filesz)
{
struct ppu_proc_prx_param_t
{
be_t<u32> size;
be_t<u32> magic;
be_t<u32> version;
be_t<u32> unk0;
be_t<u32> libent_start;
be_t<u32> libent_end;
be_t<u32> libstub_start;
be_t<u32> libstub_end;
be_t<u16> ver;
be_t<u16> unk1;
be_t<u32> unk2;
};
const auto& proc_prx_param = vm::_ref<const ppu_proc_prx_param_t>(vm::cast(prog.p_vaddr, HERE));
if (proc_prx_param.magic != 0x1b434cec)
{
throw fmt::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(link, proc_prx_param.libstub_start, proc_prx_param.libstub_end);
}
break;
}
default:
{
LOG_ERROR(LOADER, "Unknown phdr type (0x%08x)", p_type);
}
}
}
// Initialize process
std::vector<u32> start_funcs;
// Load modules
const std::string& lle_dir = vfs::get("/dev_flash/sys/external");
if (g_cfg_load_liblv2)
{
const ppu_prx_loader loader = fs::file(lle_dir + "/liblv2.sprx");
if (loader == elf_error::ok)
{
start_funcs.push_back(loader.load()->start.addr());
}
else
{
throw fmt::exception("Failed to load liblv2.sprx: %s", bijective_find<elf_error>(loader, "???"));
}
}
else
{
for (const auto& name : g_cfg_load_libs.get_set())
{
const ppu_prx_loader loader = fs::file(lle_dir + '/' + name);
if (loader == elf_error::ok)
{
LOG_WARNING(LOADER, "Loading library: %s", name);
const auto prx = loader.load();
if (prx->start)
{
start_funcs.push_back(prx->start.addr());
}
}
else
{
LOG_FATAL(LOADER, "Failed to load %s: %s", name, bijective_find<elf_error>(loader, "???"));
}
}
}
// Check unlinked functions and variables
for (auto& module : link->modules)
{
const auto _sm = ppu_module_manager::get_module(module.first);
if (!_sm)
{
LOG_ERROR(LOADER, "Unknown module '%s'", module.first);
}
else
{
// Allocate HLE variables (TODO)
for (auto& var : _sm->variables)
{
var.second.var->set(vm::alloc(var.second.size, vm::main, std::max<u32>(var.second.align, 4096)));
LOG_WARNING(LOADER, "Allocated variable '%s' in module '%s' at *0x%x", var.second.name, module.first, var.second.var->addr());
}
// Initialize HLE variables (TODO)
for (auto& var : _sm->variables)
{
var.second.init();
}
}
for (auto& entry : module.second.functions)
{
const u32 fnid = entry.first;
const u32 faddr = entry.second.first;
if (faddr == 0)
{
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);
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);
}
}
}
else
{
// TODO
const u32 index = ::size32(g_ppu_function_cache);
g_ppu_function_cache.emplace_back();
g_ppu_fnid_cache.emplace_back(fnid);
LOG_ERROR(LOADER, "Unknown function '%s' in module '%s' (index %u)", ppu_get_function_name(module.first, fnid), module.first, index);
for (auto& import : entry.second.second)
{
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)", ppu_get_function_name(module.first, fnid), module.first, stub);
}
else
{
LOG_NOTICE(LOADER, "Injected hack for function '%s' in module '%s' (*0x%x)", ppu_get_function_name(module.first, fnid), module.first, stub);
}
}
LOG_WARNING(LOADER, "** Not linked at *0x%x", import);
}
}
}
}
for (auto& entry : module.second.variables)
{
const u32 vnid = entry.first;
const u32 vaddr = entry.second.first;
if (vaddr == 0)
{
// Static variable
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());
for (auto& ref : entry.second.second)
{
ppu_patch_variable_stub(ref, _sv->var->addr());
LOG_NOTICE(LOADER, "** Linked at ref=*0x%x", ref);
}
}
else
{
LOG_ERROR(LOADER, "Unknown variable '%s' in module '%s'", ppu_get_variable_name(module.first, vnid), module.first);
for (auto& ref : entry.second.second)
{
LOG_WARNING(LOADER, "** Not linked at ref=*0x%x", ref);
}
}
}
else
{
// Retro-link LLE variable (TODO: HLE must not be allocated/initialized in this case)
if (const auto _sv = _sm && _sm->variables.count(vnid) ? &_sm->variables.at(vnid) : nullptr)
{
_sv->var->set(vaddr);
LOG_NOTICE(LOADER, "Linked LLE variable '%s' in module '%s' -> 0x%x", ppu_get_variable_name(module.first, vnid), module.first, vaddr);
}
}
}
}
// TODO: adjust for liblv2 loading option
using namespace ppu_instructions;
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] = HACK(1);
ppu_thr_stop_data[1] = BLR();
static const int branch_size = 10 * 4;
auto make_branch = [](vm::ptr<u32>& ptr, u32 addr)
{
const u32 stub = vm::read32(addr);
const u32 rtoc = vm::read32(addr + 4);
*ptr++ = LI(r0, 0);
*ptr++ = ORI(r0, r0, stub & 0xffff);
*ptr++ = ORIS(r0, r0, stub >> 16);
*ptr++ = LI(r2, 0);
*ptr++ = ORI(r2, r2, rtoc & 0xffff);
*ptr++ = ORIS(r2, r2, rtoc >> 16);
*ptr++ = MTCTR(r0);
*ptr++ = BCTRL();
};
auto entry = vm::ptr<u32>::make(vm::alloc(48 + branch_size * (::size32(start_funcs) + 1), vm::main));
// Save initialization args
*entry++ = MR(r14, r3);
*entry++ = MR(r15, r4);
*entry++ = MR(r16, r5);
*entry++ = MR(r17, r6);
*entry++ = MR(r18, r11);
*entry++ = MR(r19, r12);
if (!g_cfg_load_liblv2)
{
// Call sys_initialize_tls explicitly
*entry++ = MR(r3, r7);
*entry++ = MR(r4, r8);
*entry++ = MR(r5, r9);
*entry++ = MR(r6, r10);
*entry++ = HACK(FIND_FUNC(sys_initialize_tls));
}
for (auto& f : start_funcs)
{
// Reset arguments (TODO)
*entry++ = LI(r3, 0);
*entry++ = LI(r4, 0);
make_branch(entry, f);
}
// Restore initialization args
*entry++ = MR(r3, r14);
*entry++ = MR(r4, r15);
*entry++ = MR(r5, r16);
*entry++ = MR(r6, r17);
*entry++ = MR(r11, r18);
*entry++ = MR(r12, r19);
// Branch to initialization
make_branch(entry, vm::cast(header.e_entry, HERE));
auto ppu = idm::make_ptr<PPUThread>("main_thread");
ppu->PC = entry.addr() & -0x1000;
ppu->stack_size = Emu.GetPrimaryStackSize();
ppu->prio = Emu.GetPrimaryPrio();
ppu->cpu_init();
ppu->GPR[2] = 0xdeadbeef; // rtoc
ppu->GPR[11] = 0xabadcafe; // OPD ???
ppu->GPR[12] = Emu.GetMallocPageSize();
std::initializer_list<std::string> args = { Emu.GetPath()/*, "-emu"s*/ };
auto argv = vm::ptr<u64>::make(vm::alloc(SIZE_32(u64) * ::size32(args), vm::main));
auto envp = vm::ptr<u64>::make(vm::alloc(::align(SIZE_32(u64), 0x10), vm::main));
*envp = 0;
ppu->GPR[3] = args.size(); // argc
ppu->GPR[4] = argv.addr();
ppu->GPR[5] = envp.addr();
ppu->GPR[6] = 0; // ???
for (const auto& arg : args)
{
const u32 arg_size = ::align(::size32(arg) + 1, 0x10);
const u32 arg_addr = vm::alloc(arg_size, vm::main);
std::memcpy(vm::base(arg_addr), arg.data(), arg_size);
*argv++ = arg_addr;
}
// Arguments for sys_initialize_tls()
ppu->GPR[7] = ppu->id;
ppu->GPR[8] = Emu.GetTLSAddr();
ppu->GPR[9] = Emu.GetTLSFilesz();
ppu->GPR[10] = Emu.GetTLSMemsz();
// Set memory protections
//for (const auto& prog : progs)
//{
// const u32 addr = static_cast<u32>(prog.p_vaddr);
// const u32 size = static_cast<u32>(prog.p_memsz);
// if (prog.p_type == 0x1 /* LOAD */ && prog.p_memsz && (prog.p_flags & 0x2) == 0 /* W */)
// {
// // Set memory protection to read-only where necessary
// ASSERT(vm::page_protect(addr, ::align(size, 0x1000), 0, 0, vm::page_writable));
// }
//}
}