rpcsx/rpcs3/Emu/Cell/lv2/sys_prx.cpp
Elad 575a245f8d
IDM: Implement lock-free smart pointers (#16403)
Replaces `std::shared_pointer` with `stx::atomic_ptr` and `stx::shared_ptr`.

Notes to programmers:

* This pr kills the use of `dynamic_cast`, `std::dynamic_pointer_cast` and `std::weak_ptr` on IDM objects, possible replacement is to save the object ID on the base object, then use idm::check/get_unlocked to the destination type via the saved ID which may be null. Null pointer check is how you can tell type mismatch (as dynamic cast) or object destruction (as weak_ptr locking).
* Double-inheritance on IDM objects should be used with care, `stx::shared_ptr` does not support constant-evaluated pointer offsetting to parent/child type.
* `idm::check/get_unlocked` can now be used anywhere.

Misc fixes:
* Fixes some segfaults with RPCN with interaction with IDM.
* Fix deadlocks in access violation handler due locking recursion.
* Fixes race condition in process exit-spawn on memory containers read.
* Fix bug that theoretically can prevent RPCS3 from booting - fix `id_manager::typeinfo` comparison to compare members instead of `memcmp` which can fail spuriously on padding bytes.
* Ensure all IDM inherited types of base, either has `id_base` or `id_type` defined locally, this allows to make getters such as `idm::get_unlocked<lv2_socket, lv2_socket_raw>()` which were broken before. (requires save-states invalidation)
* Removes broken operator[] overload of `stx::shared_ptr` and `stx::single_ptr` for non-array types.
2024-12-22 20:59:48 +02:00

1174 lines
30 KiB
C++

#include "stdafx.h"
#include "sys_prx.h"
#include "Emu/System.h"
#include "Emu/system_config.h"
#include "Emu/VFS.h"
#include "Emu/IdManager.h"
#include "Crypto/unself.h"
#include "Loader/ELF.h"
#include "Emu/Cell/PPUThread.h"
#include "Emu/Cell/ErrorCodes.h"
#include "Crypto/unedat.h"
#include "Utilities/StrUtil.h"
#include "sys_fs.h"
#include "sys_process.h"
#include "sys_memory.h"
#include <span>
extern void dump_executable(std::span<const u8> data, const ppu_module<lv2_obj>* _module, std::string_view title_id);
extern shared_ptr<lv2_prx> ppu_load_prx(const ppu_prx_object&, bool virtual_load, const std::string&, s64, utils::serial* = nullptr);
extern void ppu_unload_prx(const lv2_prx& prx);
extern bool ppu_initialize(const ppu_module<lv2_obj>&, bool check_only = false, u64 file_size = 0);
extern void ppu_finalize(const ppu_module<lv2_obj>& info, bool force_mem_release = false);
extern void ppu_manual_load_imports_exports(u32 imports_start, u32 imports_size, u32 exports_start, u32 exports_size, std::basic_string<char>& loaded_flags);
LOG_CHANNEL(sys_prx);
// <string: firmware sprx, int: should hle if 1>
extern const std::map<std::string_view, int> g_prx_list
{
{ "/dev_flash/sys/internal/libfs_utility_init.sprx", 1 },
{ "libaacenc.sprx", 0 },
{ "libaacenc_spurs.sprx", 0 },
{ "libac3dec.sprx", 0 },
{ "libac3dec2.sprx", 0 },
{ "libadec.sprx", 1 },
{ "libadec2.sprx", 0 },
{ "libadec_internal.sprx", 0 },
{ "libad_async.sprx", 0 },
{ "libad_billboard_util.sprx", 0 },
{ "libad_core.sprx", 0 },
{ "libapostsrc_mini.sprx", 0 },
{ "libasfparser2_astd.sprx", 0 },
{ "libat3dec.sprx", 0 },
{ "libat3multidec.sprx", 0 },
{ "libatrac3multi.sprx", 0 },
{ "libatrac3plus.sprx", 0 },
{ "libatxdec.sprx", 1 },
{ "libatxdec2.sprx", 0 },
{ "libaudio.sprx", 1 },
{ "libavcdec.sprx", 0 },
{ "libavcenc.sprx", 0 },
{ "libavcenc_small.sprx", 0 },
{ "libavchatjpgdec.sprx", 0 },
{ "libbeisobmf.sprx", 0 },
{ "libbemp2sys.sprx", 0 },
{ "libcamera.sprx", 1 },
{ "libcelp8dec.sprx", 0 },
{ "libcelp8enc.sprx", 0 },
{ "libcelpdec.sprx", 0 },
{ "libcelpenc.sprx", 0 },
{ "libddpdec.sprx", 0 },
{ "libdivxdec.sprx", 0 },
{ "libdmux.sprx", 0 },
{ "libdmuxpamf.sprx", 0 },
{ "libdtslbrdec.sprx", 0 },
{ "libfiber.sprx", 0 },
{ "libfont.sprx", 0 },
{ "libfontFT.sprx", 0 },
{ "libfreetype.sprx", 0 },
{ "libfreetypeTT.sprx", 0 },
{ "libfs.sprx", 0 },
{ "libfs_155.sprx", 0 },
{ "libgcm_sys.sprx", 0 },
{ "libgem.sprx", 1 },
{ "libgifdec.sprx", 0 },
{ "libhttp.sprx", 0 },
{ "libio.sprx", 1 },
{ "libjpgdec.sprx", 0 },
{ "libjpgenc.sprx", 0 },
{ "libkey2char.sprx", 0 },
{ "libl10n.sprx", 0 },
{ "liblv2.sprx", 0 },
{ "liblv2coredump.sprx", 0 },
{ "liblv2dbg_for_cex.sprx", 0 },
{ "libm2bcdec.sprx", 0 },
{ "libm4aacdec.sprx", 0 },
{ "libm4aacdec2ch.sprx", 0 },
{ "libm4hdenc.sprx", 0 },
{ "libm4venc.sprx", 0 },
{ "libmedi.sprx", 1 },
{ "libmic.sprx", 1 },
{ "libmp3dec.sprx", 0 },
{ "libmp4.sprx", 0 },
{ "libmpl1dec.sprx", 0 },
{ "libmvcdec.sprx", 0 },
{ "libnet.sprx", 0 },
{ "libnetctl.sprx", 1 },
{ "libpamf.sprx", 1 },
{ "libpngdec.sprx", 0 },
{ "libpngenc.sprx", 0 },
{ "libresc.sprx", 0 },
{ "librtc.sprx", 1 },
{ "librudp.sprx", 0 },
{ "libsail.sprx", 0 },
{ "libsail_avi.sprx", 0 },
{ "libsail_rec.sprx", 0 },
{ "libsjvtd.sprx", 0 },
{ "libsmvd2.sprx", 0 },
{ "libsmvd4.sprx", 0 },
{ "libspurs_jq.sprx", 0 },
{ "libsre.sprx", 0 },
{ "libssl.sprx", 0 },
{ "libsvc1d.sprx", 0 },
{ "libsync2.sprx", 0 },
{ "libsysmodule.sprx", 0 },
{ "libsysutil.sprx", 1 },
{ "libsysutil_ap.sprx", 1 },
{ "libsysutil_authdialog.sprx", 1 },
{ "libsysutil_avc2.sprx", 1 },
{ "libsysutil_avconf_ext.sprx", 1 },
{ "libsysutil_avc_ext.sprx", 1 },
{ "libsysutil_bgdl.sprx", 1 },
{ "libsysutil_cross_controller.sprx", 1 },
{ "libsysutil_dec_psnvideo.sprx", 1 },
{ "libsysutil_dtcp_ip.sprx", 1 },
{ "libsysutil_game.sprx", 1 },
{ "libsysutil_game_exec.sprx", 1 },
{ "libsysutil_imejp.sprx", 1 },
{ "libsysutil_misc.sprx", 1 },
{ "libsysutil_music.sprx", 1 },
{ "libsysutil_music_decode.sprx", 1 },
{ "libsysutil_music_export.sprx", 1 },
{ "libsysutil_np.sprx", 1 },
{ "libsysutil_np2.sprx", 1 },
{ "libsysutil_np_clans.sprx", 1 },
{ "libsysutil_np_commerce2.sprx", 1 },
{ "libsysutil_np_eula.sprx", 1 },
{ "libsysutil_np_installer.sprx", 1 },
{ "libsysutil_np_sns.sprx", 1 },
{ "libsysutil_np_trophy.sprx", 1 },
{ "libsysutil_np_tus.sprx", 1 },
{ "libsysutil_np_util.sprx", 1 },
{ "libsysutil_oskdialog_ext.sprx", 1 },
{ "libsysutil_pesm.sprx", 1 },
{ "libsysutil_photo_decode.sprx", 1 },
{ "libsysutil_photo_export.sprx", 1 },
{ "libsysutil_photo_export2.sprx", 1 },
{ "libsysutil_photo_import.sprx", 1 },
{ "libsysutil_photo_network_sharing.sprx", 1 },
{ "libsysutil_print.sprx", 1 },
{ "libsysutil_rec.sprx", 1 },
{ "libsysutil_remoteplay.sprx", 1 },
{ "libsysutil_rtcalarm.sprx", 1 },
{ "libsysutil_savedata.sprx", 1 },
{ "libsysutil_savedata_psp.sprx", 1 },
{ "libsysutil_screenshot.sprx", 1 },
{ "libsysutil_search.sprx", 1 },
{ "libsysutil_storagedata.sprx", 1 },
{ "libsysutil_subdisplay.sprx", 1 },
{ "libsysutil_syschat.sprx", 1 },
{ "libsysutil_sysconf_ext.sprx", 1 },
{ "libsysutil_userinfo.sprx", 1 },
{ "libsysutil_video_export.sprx", 1 },
{ "libsysutil_video_player.sprx", 1 },
{ "libsysutil_video_upload.sprx", 1 },
{ "libusbd.sprx", 0 },
{ "libusbpspcm.sprx", 0 },
{ "libvdec.sprx", 1 },
{ "libvoice.sprx", 1 },
{ "libvpost.sprx", 0 },
{ "libvpost2.sprx", 0 },
{ "libwmadec.sprx", 0 },
};
bool ppu_register_library_lock(std::string_view libname, bool lock_lib);
static error_code prx_load_module(const std::string& vpath, u64 flags, vm::ptr<sys_prx_load_module_option_t> /*pOpt*/, fs::file src = {}, s64 file_offset = 0)
{
if (flags != 0)
{
if (flags & SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK)
{
return CELL_EINVAL;
}
if (flags & SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR && !g_ps3_process_info.ppc_seg)
{
return CELL_ENOSYS;
}
fmt::throw_exception("sys_prx: Unimplemented fixed address allocations");
}
std::string vpath0;
std::string path = vfs::get(vpath, nullptr, &vpath0);
std::string name = vpath0.substr(vpath0.find_last_of('/') + 1);
bool ignore = false;
constexpr std::string_view firmware_sprx_dir = "/dev_flash/sys/external/";
const bool is_firmware_sprx = vpath0.starts_with(firmware_sprx_dir) && g_prx_list.count(std::string_view(vpath0).substr(firmware_sprx_dir.size()));
if (is_firmware_sprx)
{
if (g_cfg.core.libraries_control.get_set().count(name + ":lle"))
{
// Force LLE
ignore = false;
}
else if (g_cfg.core.libraries_control.get_set().count(name + ":hle"))
{
// Force HLE
ignore = true;
}
else
{
// Use list
ignore = ::at32(g_prx_list, name) != 0;
}
}
else if (vpath0.starts_with("/"))
{
// Special case : HLE for files outside of "/dev_flash/sys/external/"
// Have to specify full path for them
ignore = g_prx_list.count(vpath0) && ::at32(g_prx_list, vpath0);
}
auto hle_load = [&]()
{
const auto prx = idm::make_ptr<lv2_obj, lv2_prx>();
prx->name = std::move(name);
prx->path = std::move(path);
sys_prx.warning("Ignored module: \"%s\" (id=0x%x)", vpath, idm::last_id());
return not_an_error(idm::last_id());
};
if (ignore)
{
return hle_load();
}
if (!src)
{
auto [fs_error, ppath, path0, lv2_file, type] = lv2_file::open(vpath, 0, 0);
if (fs_error)
{
if (fs_error + 0u == CELL_ENOENT && is_firmware_sprx)
{
sys_prx.error("firmware SPRX not found: \"%s\" (forcing HLE implementation)", vpath, idm::last_id());
return hle_load();
}
return {fs_error, vpath};
}
src = std::move(lv2_file);
}
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
src = decrypt_self(std::move(src), reinterpret_cast<u8*>(&klic), nullptr, true);
if (!src)
{
return {CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE, +"Failed to decrypt file"};
}
const auto src_data = g_cfg.core.ppu_debug ? src.to_vector<u8>() : std::vector<u8>{};
ppu_prx_object obj = std::move(src);
src.close();
if (obj != elf_error::ok)
{
return {CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE, obj.get_error()};
}
const auto prx = ppu_load_prx(obj, false, path, file_offset);
if (g_cfg.core.ppu_debug)
{
dump_executable({src_data.data(), src_data.size()}, prx.get(), Emu.GetTitleID());
}
obj.clear();
if (!prx)
{
return CELL_PRX_ERROR_ILLEGAL_LIBRARY;
}
ppu_initialize(*prx);
sys_prx.success("Loaded module: \"%s\" (id=0x%x)", vpath, idm::last_id());
return not_an_error(idm::last_id());
}
fs::file make_file_view(fs::file&& file, u64 offset, u64 size);
std::function<void(void*)> lv2_prx::load(utils::serial& ar)
{
[[maybe_unused]] const s32 version = GET_SERIALIZATION_VERSION(lv2_prx_overlay);
const std::string path = vfs::get(ar.pop<std::string>());
const s64 offset = ar;
const u32 state = ar;
usz seg_count = 0;
ar.deserialize_vle(seg_count);
shared_ptr<lv2_prx> prx;
auto hle_load = [&]()
{
prx = make_shared<lv2_prx>();
prx->path = path;
prx->name = path.substr(path.find_last_of(fs::delim) + 1);
};
if (seg_count)
{
std::basic_string<char> loaded_flags, external_flags;
ar(loaded_flags, external_flags);
fs::file file{path.substr(0, path.size() - (offset ? fmt::format("_x%x", offset).size() : 0))};
if (file)
{
u128 klic = g_fxo->get<loaded_npdrm_keys>().last_key();
file = make_file_view(std::move(file), offset, umax);
prx = ppu_load_prx(ppu_prx_object{decrypt_self(std::move(file), reinterpret_cast<u8*>(&klic))}, false, path, 0, &ar);
prx->m_loaded_flags = std::move(loaded_flags);
prx->m_external_loaded_flags = std::move(external_flags);
if (state <= PRX_STATE_STARTED)
{
prx->restore_exports();
}
ensure(prx);
}
else
{
ensure(g_cfg.savestate.state_inspection_mode.get());
hle_load();
// Partially recover information
for (usz i = 0; i < seg_count; i++)
{
auto& seg = prx->segs.emplace_back();
seg.addr = ar;
seg.size = 1; // TODO
}
}
}
else
{
hle_load();
}
prx->state = state;
return [prx](void* storage)
{
*static_cast<shared_ptr<lv2_obj>*>(storage) = prx;
};
}
void lv2_prx::save(utils::serial& ar)
{
USING_SERIALIZATION_VERSION(lv2_prx_overlay);
ar(vfs::retrieve(path), offset, state);
// Save segments count
ar.serialize_vle(segs.size());
if (!segs.empty())
{
ar(m_loaded_flags);
ar(m_external_loaded_flags);
}
for (const ppu_segment& seg : segs)
{
if (seg.type == 0x1u && seg.size) ar(seg.addr);
}
}
error_code sys_prx_get_ppu_guid(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("sys_prx_get_ppu_guid()");
return CELL_OK;
}
error_code _sys_prx_load_module_by_fd(ppu_thread& ppu, s32 fd, u64 offset, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_load_module_by_fd(fd=%d, offset=0x%x, flags=0x%x, pOpt=*0x%x)", fd, offset, flags, pOpt);
const auto file = idm::get_unlocked<lv2_fs_object, lv2_file>(fd);
if (!file)
{
return CELL_EBADF;
}
std::lock_guard lock(file->mp->mutex);
if (!file->file)
{
return CELL_EBADF;
}
return prx_load_module(offset ? fmt::format("%s_x%x", file->name.data(), offset) : file->name.data(), flags, pOpt, lv2_file::make_view(file, offset), offset);
}
error_code _sys_prx_load_module_on_memcontainer_by_fd(ppu_thread& ppu, s32 fd, u64 offset, u32 mem_ct, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_load_module_on_memcontainer_by_fd(fd=%d, offset=0x%x, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x)", fd, offset, mem_ct, flags, pOpt);
return _sys_prx_load_module_by_fd(ppu, fd, offset, flags, pOpt);
}
static error_code prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr<char, u32, u64> path_list, u32 /*mem_ct*/, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt, vm::ptr<u32> id_list)
{
if (flags != 0)
{
if (flags & SYS_PRX_LOAD_MODULE_FLAGS_INVALIDMASK)
{
return CELL_EINVAL;
}
if (flags & SYS_PRX_LOAD_MODULE_FLAGS_FIXEDADDR && !g_ps3_process_info.ppc_seg)
{
return CELL_ENOSYS;
}
fmt::throw_exception("sys_prx: Unimplemented fixed address allocations");
}
for (s32 i = 0; i < count; ++i)
{
const auto result = prx_load_module(path_list[i].get_ptr(), flags, pOpt);
if (result < 0)
{
while (--i >= 0)
{
// Unload already loaded modules
_sys_prx_unload_module(ppu, id_list[i], 0, vm::null);
}
// Fill with -1
std::memset(id_list.get_ptr(), -1, count * sizeof(id_list[0]));
return result;
}
id_list[i] = result;
}
return CELL_OK;
}
error_code _sys_prx_load_module_list(ppu_thread& ppu, s32 count, vm::cpptr<char, u32, u64> path_list, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt, vm::ptr<u32> id_list)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_load_module_list(count=%d, path_list=**0x%x, flags=0x%x, pOpt=*0x%x, id_list=*0x%x)", count, path_list, flags, pOpt, id_list);
return prx_load_module_list(ppu, count, path_list, SYS_MEMORY_CONTAINER_ID_INVALID, flags, pOpt, id_list);
}
error_code _sys_prx_load_module_list_on_memcontainer(ppu_thread& ppu, s32 count, vm::cpptr<char, u32, u64> path_list, u32 mem_ct, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt, vm::ptr<u32> id_list)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_load_module_list_on_memcontainer(count=%d, path_list=**0x%x, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x, id_list=*0x%x)", count, path_list, mem_ct, flags, pOpt, id_list);
return prx_load_module_list(ppu, count, path_list, mem_ct, flags, pOpt, id_list);
}
error_code _sys_prx_load_module_on_memcontainer(ppu_thread& ppu, vm::cptr<char> path, u32 mem_ct, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_load_module_on_memcontainer(path=%s, mem_ct=0x%x, flags=0x%x, pOpt=*0x%x)", path, mem_ct, flags, pOpt);
return prx_load_module(path.get_ptr(), flags, pOpt);
}
error_code _sys_prx_load_module(ppu_thread& ppu, vm::cptr<char> path, u64 flags, vm::ptr<sys_prx_load_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_load_module(path=%s, flags=0x%x, pOpt=*0x%x)", path, flags, pOpt);
return prx_load_module(path.get_ptr(), flags, pOpt);
}
error_code _sys_prx_start_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_start_stop_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_start_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, flags, pOpt);
if (id == 0 || !pOpt)
{
return CELL_EINVAL;
}
const auto prx = idm::get_unlocked<lv2_obj, lv2_prx>(id);
if (!prx)
{
return CELL_ESRCH;
}
switch (pOpt->cmd & 0xf)
{
case 1:
{
std::lock_guard lock(prx->mutex);
if (!prx->state.compare_and_swap_test(PRX_STATE_INITIALIZED, PRX_STATE_STARTING))
{
if (prx->state == PRX_STATE_DESTROYED)
{
return CELL_ESRCH;
}
return CELL_PRX_ERROR_ERROR;
}
prx->load_exports();
break;
}
case 2:
{
switch (const u64 res = pOpt->res)
{
case SYS_PRX_RESIDENT:
{
// No error code on invalid state, so throw on unexpected state
ensure(prx->state.compare_and_swap_test(PRX_STATE_STARTING, PRX_STATE_STARTED));
return CELL_OK;
}
default:
{
if (res & 0xffff'ffffu)
{
// Unload the module (SYS_PRX_NO_RESIDENT expected)
sys_prx.warning("_sys_prx_start_module(): Start entry function returned SYS_PRX_NO_RESIDENT (res=0x%llx)", res);
// Thread-safe if called from liblv2.sprx, due to internal lwmutex lock before it
prx->state = PRX_STATE_STOPPED;
prx->unload_exports();
_sys_prx_unload_module(ppu, id, 0, vm::null);
// Return the exact value returned by the start function (as an error)
return static_cast<s32>(res);
}
// Return type of start entry function is s32
// And according to RE this path results in weird behavior
sys_prx.error("_sys_prx_start_module(): Start entry function returned weird value (res=0x%llx)", res);
return CELL_OK;
}
}
}
default:
return CELL_PRX_ERROR_ERROR;
}
ppu.check_state();
pOpt->entry.set(prx->start ? prx->start.addr() : ~0ull);
// This check is probably for older fw
if (pOpt->size != 0x20u)
{
pOpt->entry2.set(prx->prologue ? prx->prologue.addr() : ~0ull);
}
return CELL_OK;
}
error_code _sys_prx_stop_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_start_stop_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_stop_module(id=0x%x, flags=0x%x, pOpt=*0x%x)", id, flags, pOpt);
const auto prx = idm::get_unlocked<lv2_obj, lv2_prx>(id);
if (!prx)
{
return CELL_ESRCH;
}
if (!pOpt)
{
return CELL_EINVAL;
}
auto set_entry2 = [&](u64 addr)
{
if (pOpt->size != 0x20u)
{
pOpt->entry2.set(addr);
}
};
switch (pOpt->cmd & 0xf)
{
case 1:
{
switch (const auto old = prx->state.compare_and_swap(PRX_STATE_STARTED, PRX_STATE_STOPPING))
{
case PRX_STATE_INITIALIZED: return CELL_PRX_ERROR_NOT_STARTED;
case PRX_STATE_STOPPED: return CELL_PRX_ERROR_ALREADY_STOPPED;
case PRX_STATE_STOPPING: return CELL_PRX_ERROR_ALREADY_STOPPING; // Internal error
case PRX_STATE_STARTING: return CELL_PRX_ERROR_ERROR; // Internal error
case PRX_STATE_DESTROYED: return CELL_ESRCH;
case PRX_STATE_STARTED: break;
default:
fmt::throw_exception("Invalid prx state (%d)", old);
}
ppu.check_state();
pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull);
set_entry2(prx->epilogue ? prx->epilogue.addr() : ~0ull);
return CELL_OK;
}
case 2:
{
switch (pOpt->res)
{
case 0:
{
// No error code on invalid state, so throw on unexpected state
std::lock_guard lock(prx->mutex);
ensure(prx->exports_end <= prx->exports_start || (prx->state == PRX_STATE_STOPPING));
prx->unload_exports();
ensure(prx->state.compare_and_swap_test(PRX_STATE_STOPPING, PRX_STATE_STOPPED));
return CELL_OK;
}
case 1:
return CELL_PRX_ERROR_CAN_NOT_STOP; // Internal error
default:
// Nothing happens (probably unexpected value)
return CELL_OK;
}
}
// These commands are not used by liblv2.sprx
case 4: // Get start entry and stop functions
case 8: // Disable stop function execution
{
switch (const auto old = +prx->state)
{
case PRX_STATE_INITIALIZED: return CELL_PRX_ERROR_NOT_STARTED;
case PRX_STATE_STOPPED: return CELL_PRX_ERROR_ALREADY_STOPPED;
case PRX_STATE_STOPPING: return CELL_PRX_ERROR_ALREADY_STOPPING; // Internal error
case PRX_STATE_STARTING: return CELL_PRX_ERROR_ERROR; // Internal error
case PRX_STATE_DESTROYED: return CELL_ESRCH;
case PRX_STATE_STARTED: break;
default:
fmt::throw_exception("Invalid prx state (%d)", old);
}
if (pOpt->cmd == 4u)
{
ppu.check_state();
pOpt->entry.set(prx->stop ? prx->stop.addr() : ~0ull);
set_entry2(prx->epilogue ? prx->epilogue.addr() : ~0ull);
}
else
{
// Disables stop function execution (but the real value can be read through _sys_prx_get_module_info)
sys_prx.todo("_sys_prx_stop_module(): cmd is 8 (stop function = *0x%x)", prx->stop);
//prx->stop = vm::null;
}
return CELL_OK;
}
default:
return CELL_PRX_ERROR_ERROR;
}
}
error_code _sys_prx_unload_module(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_unload_module_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
// Get the PRX, free the used memory and delete the object and its ID
const auto prx = idm::withdraw<lv2_obj, lv2_prx>(id, [](lv2_prx& prx) -> CellPrxError
{
switch (prx.state.fetch_op([](u32& value)
{
if (value == PRX_STATE_INITIALIZED || value == PRX_STATE_STOPPED)
{
value = PRX_STATE_DESTROYED;
return true;
}
return false;
}).first)
{
case PRX_STATE_INITIALIZED:
case PRX_STATE_STOPPED:
return {};
default: break;
}
return CELL_PRX_ERROR_NOT_REMOVABLE;
});
if (!prx)
{
return {CELL_PRX_ERROR_UNKNOWN_MODULE, id};
}
if (prx.ret)
{
return {prx.ret, "%s (id=%s)", prx->name, id};
}
sys_prx.success("_sys_prx_unload_module(id=0x%x, flags=0x%x, pOpt=*0x%x): name='%s'", id, flags, pOpt, prx->name);
prx->mutex.lock_unlock();
ppu_unload_prx(*prx);
ppu_finalize(*prx);
//s32 result = prx->exit ? prx->exit() : CELL_OK;
return CELL_OK;
}
void lv2_prx::load_exports()
{
if (exports_end <= exports_start)
{
// Nothing to load
return;
}
if (!m_loaded_flags.empty())
{
// Already loaded
return;
}
ppu_manual_load_imports_exports(0, 0, exports_start, exports_end - exports_start, m_loaded_flags);
}
void lv2_prx::restore_exports()
{
constexpr usz sizeof_export_data = 0x1C;
std::basic_string<char> loaded_flags_empty;
for (u32 start = exports_start, i = 0; start < exports_end; i++, start += vm::read8(start) ? vm::read8(start) : sizeof_export_data)
{
if (::at32(m_external_loaded_flags, i) || (!m_loaded_flags.empty() && ::at32(m_loaded_flags, i)))
{
loaded_flags_empty.clear();
ppu_manual_load_imports_exports(0, 0, start, sizeof_export_data, loaded_flags_empty);
}
}
}
void lv2_prx::unload_exports()
{
if (m_loaded_flags.empty())
{
// Not loaded
return;
}
std::basic_string<char> merged = m_loaded_flags;
for (usz i = 0; i < merged.size(); i++)
{
merged[i] |= ::at32(m_external_loaded_flags, i);
}
ppu_manual_load_imports_exports(0, 0, exports_start, exports_end - exports_start, merged);
}
error_code _sys_prx_register_module(ppu_thread& ppu, vm::cptr<char> name, vm::ptr<void> opt)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_register_module(name=%s, opt=*0x%x)", name, opt);
if (!opt)
{
return CELL_EINVAL;
}
sys_prx_register_module_0x30_type_1_t info{};
switch (const u64 size_struct = vm::read64(opt.addr()))
{
case 0x1c:
case 0x20:
{
const auto _info = vm::static_ptr_cast<sys_prx_register_module_0x20_t>(opt);
sys_prx.todo("_sys_prx_register_module(): opt size is 0x%x", size_struct);
// Rebuild info with corresponding members of old structures
// Weird that type is set to 0 because 0 means NO-OP in this syscall
info.size = 0x30;
info.lib_stub_size = _info->stubs_size;
info.lib_stub_ea = _info->stubs_ea;
info.error_handler = _info->error_handler;
info.type = 0;
break;
}
case 0x30:
{
std::memcpy(&info, opt.get_ptr(), sizeof(info));
break;
}
default: return CELL_EINVAL;
}
sys_prx.warning("opt: size=0x%x, type=0x%x, unk3=0x%x, unk4=0x%x, lib_entries_ea=%s, lib_entries_size=0x%x"
", lib_stub_ea=%s, lib_stub_size=0x%x, error_handler=%s", info.size, info.type, info.unk3, info.unk4
, info.lib_entries_ea, info.lib_entries_size, info.lib_stub_ea, info.lib_stub_size, info.error_handler);
if (info.type & 0x1)
{
if (Emu.IsVsh())
{
ppu_manual_load_imports_exports(info.lib_stub_ea.addr(), info.lib_stub_size, info.lib_entries_ea.addr(), info.lib_entries_size, *std::make_unique<std::basic_string<char>>());
}
else
{
// Only VSH is allowed to load it manually
return not_an_error(CELL_PRX_ERROR_ELF_IS_REGISTERED);
}
}
return CELL_OK;
}
error_code _sys_prx_query_module(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_query_module()");
return CELL_OK;
}
error_code _sys_prx_register_library(ppu_thread& ppu, vm::ptr<void> library)
{
ppu.state += cpu_flag::wait;
sys_prx.notice("_sys_prx_register_library(library=*0x%x)", library);
if (!vm::check_addr(library.addr()))
{
return CELL_EFAULT;
}
constexpr u32 sizeof_lib = 0x1c;
std::array<char, sizeof_lib> mem_copy{};
std::memcpy(mem_copy.data(), library.get_ptr(), sizeof_lib);
std::basic_string<char> flags;
ppu_manual_load_imports_exports(0, 0, library.addr(), sizeof_lib, flags);
if (flags.front())
{
const bool success = idm::select<lv2_obj, lv2_prx>([&](u32 /*id*/, lv2_prx& prx)
{
if (prx.state == PRX_STATE_INITIALIZED)
{
for (u32 lib_addr = prx.exports_start, index = 0; lib_addr < prx.exports_end; index++, lib_addr += vm::read8(lib_addr) ? vm::read8(lib_addr) : sizeof_lib)
{
if (std::memcpy(vm::base(lib_addr), mem_copy.data(), sizeof_lib) == 0)
{
atomic_storage<char>::release(prx.m_external_loaded_flags[index], true);
return true;
}
}
}
return false;
}).ret;
if (!success)
{
sys_prx.error("_sys_prx_register_library(): Failed to associate library to PRX!");
}
}
return CELL_OK;
}
error_code _sys_prx_unregister_library(ppu_thread& ppu, vm::ptr<void> library)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_unregister_library(library=*0x%x)", library);
return CELL_OK;
}
error_code _sys_prx_link_library(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_link_library()");
return CELL_OK;
}
error_code _sys_prx_unlink_library(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_unlink_library()");
return CELL_OK;
}
error_code _sys_prx_query_library(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_query_library()");
return CELL_OK;
}
error_code _sys_prx_get_module_list(ppu_thread& ppu, u64 flags, vm::ptr<sys_prx_get_module_list_option_t> pInfo)
{
ppu.state += cpu_flag::wait;
if (flags & 0x1)
{
sys_prx.todo("_sys_prx_get_module_list(flags=%d, pInfo=*0x%x)", flags, pInfo);
}
else
{
sys_prx.warning("_sys_prx_get_module_list(flags=%d, pInfo=*0x%x)", flags, pInfo);
}
// TODO: Some action occurs if LSB of flags is set here
if (!(flags & 0x2))
{
// Do nothing
return CELL_OK;
}
if (pInfo->size == pInfo.size())
{
const u32 max_count = pInfo->max;
const auto idlist = +pInfo->idlist;
u32 count = 0;
if (max_count)
{
const std::string liblv2_path = vfs::get("/dev_flash/sys/external/liblv2.sprx");
idm::select<lv2_obj, lv2_prx>([&](u32 id, lv2_prx& prx)
{
if (count >= max_count)
{
return true;
}
if (prx.path == liblv2_path)
{
// Hide liblv2.sprx for now
return false;
}
idlist[count++] = id;
return false;
});
}
pInfo->count = count;
}
else
{
// TODO: A different structure should be served here with sizeof == 0x18
sys_prx.todo("_sys_prx_get_module_list(): Unknown structure specified (size=0x%llx)", pInfo->size);
}
return CELL_OK;
}
error_code _sys_prx_get_module_info(ppu_thread& ppu, u32 id, u64 flags, vm::ptr<sys_prx_module_info_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_get_module_info(id=0x%x, flags=%d, pOpt=*0x%x)", id, flags, pOpt);
const auto prx = idm::get_unlocked<lv2_obj, lv2_prx>(id);
if (!pOpt)
{
return CELL_EFAULT;
}
if (pOpt->size != pOpt.size())
{
return CELL_EINVAL;
}
if (!pOpt->info)
{
return CELL_EFAULT;
}
if (pOpt->info->size != pOpt->info.size())
{
return CELL_EINVAL;
}
if (!prx)
{
return CELL_PRX_ERROR_UNKNOWN_MODULE;
}
strcpy_trunc(pOpt->info->name, prx->module_info_name);
pOpt->info->version[0] = prx->module_info_version[0];
pOpt->info->version[1] = prx->module_info_version[1];
pOpt->info->modattribute = prx->module_info_attributes;
pOpt->info->start_entry = prx->start.addr();
pOpt->info->stop_entry = prx->stop.addr();
pOpt->info->all_segments_num = ::size32(prx->segs);
if (pOpt->info->filename)
{
std::span dst(pOpt->info->filename.get_ptr(), pOpt->info->filename_size);
strcpy_trunc(dst, vfs::retrieve(prx->path));
}
if (pOpt->info->segments)
{
u32 i = 0;
for (; i < prx->segs.size() && i < pOpt->info->segments_num; i++)
{
if (!prx->segs[i].addr) continue; // TODO: Check this
pOpt->info->segments[i].index = i;
pOpt->info->segments[i].base = prx->segs[i].addr;
pOpt->info->segments[i].filesz = prx->segs[i].filesz;
pOpt->info->segments[i].memsz = prx->segs[i].size;
pOpt->info->segments[i].type = prx->segs[i].type;
}
pOpt->info->segments_num = i;
}
return CELL_OK;
}
error_code _sys_prx_get_module_id_by_name(ppu_thread& ppu, vm::cptr<char> name, u64 flags, vm::ptr<sys_prx_get_module_id_by_name_option_t> pOpt)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("_sys_prx_get_module_id_by_name(name=%s, flags=%d, pOpt=*0x%x)", name, flags, pOpt);
//if (realName == "?") ...
return not_an_error(CELL_PRX_ERROR_UNKNOWN_MODULE);
}
error_code _sys_prx_get_module_id_by_address(ppu_thread& ppu, u32 addr)
{
ppu.state += cpu_flag::wait;
sys_prx.warning("_sys_prx_get_module_id_by_address(addr=0x%x)", addr);
if (!vm::check_addr(addr))
{
// Fast check for an invalid argument
return {CELL_PRX_ERROR_UNKNOWN_MODULE, addr};
}
const auto [prx, id] = idm::select<lv2_obj, lv2_prx>([&](u32 id, lv2_prx& prx) -> u32
{
for (const ppu_segment& seg : prx.segs)
{
if (seg.size && addr >= seg.addr && addr < seg.addr + seg.size)
{
return id;
}
}
return 0;
});
if (!id)
{
return {CELL_PRX_ERROR_UNKNOWN_MODULE, addr};
}
return not_an_error(id);
}
error_code _sys_prx_start(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("sys_prx_start()");
return CELL_OK;
}
error_code _sys_prx_stop(ppu_thread& ppu)
{
ppu.state += cpu_flag::wait;
sys_prx.todo("sys_prx_stop()");
return CELL_OK;
}
template <>
void fmt_class_string<CellPrxError>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](CellPrxError value)
{
switch (value)
{
STR_CASE(CELL_PRX_ERROR_ERROR);
STR_CASE(CELL_PRX_ERROR_ILLEGAL_PERM);
STR_CASE(CELL_PRX_ERROR_UNKNOWN_MODULE);
STR_CASE(CELL_PRX_ERROR_ALREADY_STARTED);
STR_CASE(CELL_PRX_ERROR_NOT_STARTED);
STR_CASE(CELL_PRX_ERROR_ALREADY_STOPPED);
STR_CASE(CELL_PRX_ERROR_CAN_NOT_STOP);
STR_CASE(CELL_PRX_ERROR_NOT_REMOVABLE);
STR_CASE(CELL_PRX_ERROR_LIBRARY_NOT_YET_LINKED);
STR_CASE(CELL_PRX_ERROR_LIBRARY_FOUND);
STR_CASE(CELL_PRX_ERROR_LIBRARY_NOTFOUND);
STR_CASE(CELL_PRX_ERROR_ILLEGAL_LIBRARY);
STR_CASE(CELL_PRX_ERROR_LIBRARY_INUSE);
STR_CASE(CELL_PRX_ERROR_ALREADY_STOPPING);
STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_PRX_TYPE);
STR_CASE(CELL_PRX_ERROR_INVAL);
STR_CASE(CELL_PRX_ERROR_ILLEGAL_PROCESS);
STR_CASE(CELL_PRX_ERROR_NO_LIBLV2);
STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_ELF_TYPE);
STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_ELF_CLASS);
STR_CASE(CELL_PRX_ERROR_UNDEFINED_SYMBOL);
STR_CASE(CELL_PRX_ERROR_UNSUPPORTED_RELOCATION_TYPE);
STR_CASE(CELL_PRX_ERROR_ELF_IS_REGISTERED);
STR_CASE(CELL_PRX_ERROR_NO_EXIT_ENTRY);
}
return unknown;
});
}