rpcsx/ps3fw/sceNpTrophy.cpp

1695 lines
41 KiB
C++
Raw Normal View History

2020-12-05 13:08:24 +01:00
#include "stdafx.h"
#include "cellMsgDialog.h"
2016-03-21 20:42:14 +01:00
#include "Emu/Cell/PPUModule.h"
2025-02-11 03:00:37 +01:00
#include "Emu/Cell/timers.hpp"
#include "Emu/IdManager.h"
#include "Emu/System.h"
#include "Emu/VFS.h"
#include "Emu/system_config.h"
#include "Loader/TROPUSR.h"
#include "Loader/TRP.h"
#include "util/rXml.h"
2016-03-21 20:42:14 +01:00
#include "cellSysutil.h"
2014-08-23 22:40:04 +02:00
#include "sceNp.h"
#include "sceNpTrophy.h"
#include "util/StrUtil.h"
#include "cellos/sys_event.h"
#include "cellos/sys_fs.h"
2025-10-05 18:28:03 +02:00
#include "rx/asm.hpp"
#include <algorithm>
#include <functional>
#include <shared_mutex>
LOG_CHANNEL(sceNpTrophy);
TrophyNotificationBase::~TrophyNotificationBase() {}
struct trophy_context_t
{
2017-01-25 18:50:30 +01:00
static const u32 id_base = 1;
static const u32 id_step = 1;
static const u32 id_count = 4;
SAVESTATE_INIT_POS(42);
2017-01-25 18:50:30 +01:00
std::string trp_name;
std::unique_ptr<TROPUSRLoader> tropusr;
bool read_only = false;
trophy_context_t() = default;
trophy_context_t(utils::serial& ar) : trp_name(ar.pop<std::string>())
{
std::string trophy_path =
vfs::get(Emu.GetDir() + "TROPDIR/" + trp_name + "/TROPHY.TRP");
fs::file trp_stream(trophy_path);
if (!trp_stream)
{
// Fallback
trophy_path =
vfs::get("/dev_bdvd/PS3_GAME/TROPDIR/" + trp_name + "/TROPHY.TRP");
trp_stream.open(trophy_path);
}
2023-11-15 20:07:42 +01:00
if (!ar.pop<bool>())
{
ar(read_only);
return;
}
ar(read_only);
if (!trp_stream && g_cfg.savestate.state_inspection_mode)
{
return;
}
const std::string trophyPath =
"/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + trp_name;
tropusr = std::make_unique<TROPUSRLoader>();
const std::string trophyUsrPath = trophyPath + "/TROPUSR.DAT";
const std::string trophyConfPath = trophyPath + "/TROPCONF.SFM";
ensure(tropusr->Load(trophyUsrPath, trophyConfPath).success);
}
void save(utils::serial& ar)
{
ar(trp_name, tropusr.operator bool(), read_only);
}
};
struct trophy_handle_t
{
2017-01-25 18:50:30 +01:00
static const u32 id_base = 1;
static const u32 id_step = 1;
static const u32 id_count = 4;
SAVESTATE_INIT_POS(43);
bool is_aborted = false;
trophy_handle_t() = default;
trophy_handle_t(utils::serial& ar) : is_aborted(ar) {}
void save(utils::serial& ar)
{
ar(is_aborted);
}
};
struct sce_np_trophy_manager
{
shared_mutex mtx;
atomic_t<bool> is_initialized = false;
// Get context + check handle given
static std::pair<trophy_context_t*, SceNpTrophyError>
get_context_ex(u32 context, u32 handle, bool test_writeable = false)
{
decltype(get_context_ex(0, 0)) res{};
auto& [ctxt, error] = res;
if (context < trophy_context_t::id_base ||
context >= trophy_context_t::id_base + trophy_context_t::id_count)
{
// Id was not in range of valid ids
error = SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
return res;
}
2024-12-22 19:59:48 +01:00
ctxt = idm::check_unlocked<trophy_context_t>(context);
if (!ctxt)
{
error = SCE_NP_TROPHY_ERROR_UNKNOWN_CONTEXT;
return res;
}
if (test_writeable && ctxt->read_only)
{
error = SCE_NP_TROPHY_ERROR_INVALID_CONTEXT;
return res;
}
if (handle < trophy_handle_t::id_base ||
handle >= trophy_handle_t::id_base + trophy_handle_t::id_count)
{
error = SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
return res;
}
2024-12-22 19:59:48 +01:00
const auto hndl = idm::check_unlocked<trophy_handle_t>(handle);
if (!hndl)
{
error = SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE;
return res;
}
else if (hndl->is_aborted)
{
error = SCE_NP_TROPHY_ERROR_ABORT;
return res;
}
return res;
}
SAVESTATE_INIT_POS(12);
sce_np_trophy_manager() = default;
sce_np_trophy_manager(utils::serial& ar) : is_initialized(ar) {}
void save(utils::serial& ar)
{
ar(is_initialized);
if (is_initialized)
{
USING_SERIALIZATION_VERSION(sceNpTrophy);
}
}
};
template <>
void fmt_class_string<SceNpTrophyError>::format(std::string& out, u64 arg)
{
format_enum(out, arg, [](auto error)
{
switch (error)
{
STR_CASE(SCE_NP_TROPHY_ERROR_ALREADY_INITIALIZED);
STR_CASE(SCE_NP_TROPHY_ERROR_NOT_INITIALIZED);
STR_CASE(SCE_NP_TROPHY_ERROR_NOT_SUPPORTED);
STR_CASE(SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED);
STR_CASE(SCE_NP_TROPHY_ERROR_OUT_OF_MEMORY);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT);
STR_CASE(SCE_NP_TROPHY_ERROR_EXCEEDS_MAX);
STR_CASE(SCE_NP_TROPHY_ERROR_INSUFFICIENT);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN_CONTEXT);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_FORMAT);
STR_CASE(SCE_NP_TROPHY_ERROR_BAD_RESPONSE);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_GRADE);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_CONTEXT);
STR_CASE(SCE_NP_TROPHY_ERROR_PROCESSING_ABORTED);
STR_CASE(SCE_NP_TROPHY_ERROR_ABORT);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE);
STR_CASE(SCE_NP_TROPHY_ERROR_LOCKED);
STR_CASE(SCE_NP_TROPHY_ERROR_HIDDEN);
STR_CASE(SCE_NP_TROPHY_ERROR_CANNOT_UNLOCK_PLATINUM);
STR_CASE(SCE_NP_TROPHY_ERROR_ALREADY_UNLOCKED);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_TYPE);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_HANDLE);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN_NP_COMM_ID);
STR_CASE(SCE_NP_TROPHY_ERROR_DISC_IO);
STR_CASE(SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST);
STR_CASE(SCE_NP_TROPHY_ERROR_UNSUPPORTED_FORMAT);
STR_CASE(SCE_NP_TROPHY_ERROR_ALREADY_INSTALLED);
STR_CASE(SCE_NP_TROPHY_ERROR_BROKEN_DATA);
STR_CASE(SCE_NP_TROPHY_ERROR_VERIFICATION_FAILURE);
STR_CASE(SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN_TROPHY_ID);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN_FILE);
STR_CASE(SCE_NP_TROPHY_ERROR_DISC_NOT_MOUNTED);
STR_CASE(SCE_NP_TROPHY_ERROR_SHUTDOWN);
STR_CASE(SCE_NP_TROPHY_ERROR_TITLE_ICON_NOT_FOUND);
STR_CASE(SCE_NP_TROPHY_ERROR_TROPHY_ICON_NOT_FOUND);
STR_CASE(SCE_NP_TROPHY_ERROR_INSUFFICIENT_DISK_SPACE);
STR_CASE(SCE_NP_TROPHY_ERROR_ILLEGAL_UPDATE);
STR_CASE(SCE_NP_TROPHY_ERROR_SAVEDATA_USER_DOES_NOT_MATCH);
STR_CASE(SCE_NP_TROPHY_ERROR_TROPHY_ID_DOES_NOT_EXIST);
STR_CASE(SCE_NP_TROPHY_ERROR_SERVICE_UNAVAILABLE);
STR_CASE(SCE_NP_TROPHY_ERROR_UNKNOWN);
}
return unknown;
});
}
2020-09-20 10:55:30 +02:00
template <>
void fmt_class_string<SceNpCommunicationSignature>::format(std::string& out,
u64 arg)
2020-09-20 10:55:30 +02:00
{
const auto& sign = get_object(arg);
fmt::append(out, "%s", sign.data);
2020-09-20 10:55:30 +02:00
}
template <>
void fmt_class_string<SceNpCommunicationId>::format(std::string& out, u64 arg)
{
const auto& id = get_object(arg);
const u8 term = id.data[9];
fmt::append(out, "{ data='%s', term='%s' (0x%x), num=%d, dummy=%d }", id.data,
std::isprint(term) ? fmt::format("%c", term) : "", term, id.num,
id.dummy);
}
2019-12-21 11:46:43 +01:00
// Helpers
static error_code NpTrophyGetTrophyInfo(const trophy_context_t* ctxt,
s32 trophyId,
SceNpTrophyDetails* details,
SceNpTrophyData* data);
static void show_trophy_notification(const trophy_context_t* ctxt,
s32 trophyId)
2019-12-21 11:46:43 +01:00
{
// Get icon for the notification.
const std::string padded_trophy_id = fmt::format("%03u", trophyId);
const std::string trophy_icon_path = "/dev_hdd0/home/" + Emu.GetUsr() +
"/trophy/" + ctxt->trp_name + "/TROP" +
padded_trophy_id + ".PNG";
2019-12-21 11:46:43 +01:00
fs::file trophy_icon_file = fs::file(vfs::get(trophy_icon_path));
std::vector<uchar> trophy_icon_data;
trophy_icon_file.read(trophy_icon_data, trophy_icon_file.size());
SceNpTrophyDetails details{};
2019-12-21 11:46:43 +01:00
if (const auto ret =
NpTrophyGetTrophyInfo(ctxt, trophyId, &details, nullptr))
2019-12-21 11:46:43 +01:00
{
sceNpTrophy.error("Failed to get info for trophy dialog. Error code 0x%x",
+ret);
2019-12-21 11:46:43 +01:00
}
if (auto trophy_notification_dialog =
Emu.GetCallbacks().get_trophy_notification_dialog())
2019-12-21 11:46:43 +01:00
{
trophy_notification_dialog->ShowTrophyNotification(details,
trophy_icon_data);
2019-12-21 11:46:43 +01:00
}
}
// Functions
2019-12-21 11:46:43 +01:00
error_code sceNpTrophyInit(vm::ptr<void> pool, u32 poolSize, u32 containerId,
u64 options)
{
sceNpTrophy.warning("sceNpTrophyInit(pool=*0x%x, poolSize=0x%x, "
"containerId=0x%x, options=0x%llx)",
pool, poolSize, containerId, options);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_ALREADY_INITIALIZED;
}
if (options > 0)
{
return SCE_NP_TROPHY_ERROR_NOT_SUPPORTED;
}
2021-03-02 12:59:19 +01:00
trophy_manager.is_initialized = true;
2019-09-08 14:11:39 +02:00
return CELL_OK;
}
error_code sceNpTrophyTerm()
{
sceNpTrophy.warning("sceNpTrophyTerm()");
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2020-05-07 16:15:06 +02:00
idm::clear<trophy_context_t>();
idm::clear<trophy_handle_t>();
2021-03-02 12:59:19 +01:00
trophy_manager.is_initialized = false;
2019-09-08 14:11:39 +02:00
return CELL_OK;
}
error_code sceNpTrophyCreateHandle(vm::ptr<u32> handle)
{
sceNpTrophy.warning("sceNpTrophyCreateHandle(handle=*0x%x)", handle);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
if (!handle)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
const u32 id = idm::make<trophy_handle_t>();
if (!id)
{
return SCE_NP_TROPHY_ERROR_EXCEEDS_MAX;
}
*handle = id;
return CELL_OK;
2015-02-08 12:37:10 +01:00
}
error_code sceNpTrophyDestroyHandle(u32 handle)
{
sceNpTrophy.warning("sceNpTrophyDestroyHandle(handle=0x%x)", handle);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2019-09-08 14:11:39 +02:00
// TODO: find out if this is checked
// if (!trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
//{
// return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
//}
if (handle < trophy_handle_t::id_base ||
handle >= trophy_handle_t::id_base + trophy_handle_t::id_count)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (!idm::remove<trophy_handle_t>(handle))
{
return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE;
}
return CELL_OK;
}
2019-04-10 19:34:44 +02:00
error_code sceNpTrophyGetGameDetails()
{
UNIMPLEMENTED_FUNC(sceNpTrophy);
return CELL_OK;
}
error_code sceNpTrophyAbortHandle(u32 handle)
{
sceNpTrophy.todo("sceNpTrophyAbortHandle(handle=0x%x)", handle);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2019-09-08 14:11:39 +02:00
// TODO: find out if this is checked
// if (!trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
//{
// return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
//}
if (handle < trophy_handle_t::id_base ||
handle >= trophy_handle_t::id_base + trophy_handle_t::id_count)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
2024-12-22 19:59:48 +01:00
const auto hndl = idm::check_unlocked<trophy_handle_t>(handle);
if (!hndl)
2015-04-25 23:26:54 +02:00
{
return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE;
2015-04-25 23:26:54 +02:00
}
// Once it is aborted it cannot be used anymore
// TODO: Implement function abortion process maybe? (depends if its actually
// make sense for some functions)
hndl->is_aborted = true;
return CELL_OK;
}
2019-09-08 14:11:39 +02:00
error_code sceNpTrophyCreateContext(
vm::ptr<u32> context, vm::cptr<SceNpCommunicationId> commId,
vm::cptr<SceNpCommunicationSignature> commSign, u64 options)
{
sceNpTrophy.warning("sceNpTrophyCreateContext(context=*0x%x, commId=*0x%x, "
"commSign=*0x%x, options=0x%llx)",
context, commId, commSign, options);
2019-09-08 14:11:39 +02:00
if (!commSign)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
2020-09-20 10:55:30 +02:00
sceNpTrophy.notice("sceNpTrophyCreateContext(): commSign = %s", *commSign);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
if (!context || !commId)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (options > SCE_NP_TROPHY_OPTIONS_CREATE_CONTEXT_READ_ONLY)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_NOT_SUPPORTED;
}
sceNpTrophy.warning("sceNpTrophyCreateContext(): commId = %s", *commId);
// rough checks for further fmt::format call
const s32 comm_num = commId->num;
if (comm_num > 99)
2015-04-25 23:26:54 +02:00
{
return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID;
2015-04-25 23:26:54 +02:00
}
2019-09-08 14:11:39 +02:00
// NOTE: commId->term is unused in our code (at least until someone finds out
// if we need to account for it)
// Generate trophy context name, limited to 9 characters
// Read once for thread-safety reasons
std::string name_str(commId->data, 9);
// resize the name if it was shorter than expected
if (const auto pos = name_str.find_first_of('\0');
pos != std::string_view::npos)
{
name_str = name_str.substr(0, pos);
}
const SceNpCommunicationSignature commSign_data = *commSign;
if (read_from_ptr<be_t<u32>>(commSign_data.data, 0) !=
NP_TROPHY_COMM_SIGN_MAGIC)
{
return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID;
}
if (std::any_of(&commSign_data.data[6], &commSign_data.data[6] + 6,
FN(x != '\0')))
{
// 6 padding bytes - must be 0
return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID;
}
if (read_from_ptr<be_t<u16>>(commSign_data.data, 4) != 0x100)
{
// Signifies version (1.00), although only one constant is allowed
return SCE_NP_TROPHY_ERROR_INVALID_NP_COMM_ID;
}
// append the commId number as "_xx"
std::string name = fmt::format("%s_%02d", name_str, comm_num);
// create trophy context
const auto ctxt = idm::make_ptr<trophy_context_t>();
2014-02-16 16:37:32 +01:00
if (!ctxt)
{
return SCE_NP_TROPHY_ERROR_EXCEEDS_MAX;
}
// set trophy context parameters (could be passed to constructor through
// make_ptr call)
2024-12-31 11:09:53 +01:00
ctxt->trp_name = name;
ctxt->read_only =
!!(options & SCE_NP_TROPHY_OPTIONS_CREATE_CONTEXT_READ_ONLY);
2017-02-04 16:09:02 +01:00
*context = idm::last_id();
2024-12-31 11:09:53 +01:00
// set current trophy name for trophy list overlay
{
current_trophy_name& current_id = g_fxo->get<current_trophy_name>();
std::lock_guard lock(current_id.mtx);
current_id.name = std::move(name);
}
return CELL_OK;
}
error_code sceNpTrophyDestroyContext(u32 context)
{
sceNpTrophy.warning("sceNpTrophyDestroyContext(context=0x%x)", context);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
std::scoped_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
if (context < trophy_context_t::id_base ||
context >= trophy_context_t::id_base + trophy_context_t::id_count)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (!idm::remove<trophy_context_t>(context))
{
return SCE_NP_TROPHY_ERROR_UNKNOWN_CONTEXT;
}
return CELL_OK;
}
error_code
sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle,
vm::ptr<SceNpTrophyStatusCallback> statusCb,
vm::ptr<void> arg, u64 options)
{
sceNpTrophy.warning("sceNpTrophyRegisterContext(context=0x%x, handle=0x%x, "
"statusCb=*0x%x, arg=*0x%x, options=0x%llx)",
context, handle, statusCb, arg, options);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
std::shared_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
2015-02-08 12:37:10 +01:00
}
const auto [ctxt, error] =
trophy_manager.get_context_ex(context, handle, true);
2024-12-22 19:59:48 +01:00
const auto handle_ptr = idm::get_unlocked<trophy_handle_t>(handle);
if (error)
{
return error;
}
2019-09-08 14:11:39 +02:00
if (!statusCb)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (options > SCE_NP_TROPHY_OPTIONS_REGISTER_CONTEXT_SHOW_ERROR_EXIT)
{
return SCE_NP_TROPHY_ERROR_NOT_SUPPORTED;
}
const auto on_error = [options]()
{
if (!!(options & SCE_NP_TROPHY_OPTIONS_REGISTER_CONTEXT_SHOW_ERROR_EXIT))
{
static_cast<void>(open_exit_dialog(
"Error during trophy registration! The game will now be terminated.",
true, msg_dialog_source::_sceNpTrophy));
}
};
// open trophy pack file
std::string trp_path =
vfs::get(Emu.GetDir() + "TROPDIR/" + ctxt->trp_name + "/TROPHY.TRP");
fs::file stream(trp_path);
if (!stream && Emu.GetCat() == "GD")
{
sceNpTrophy.warning("sceNpTrophyRegisterContext failed to open trophy file "
"from boot path: '%s' (%s)",
trp_path, fs::g_tls_error);
trp_path = vfs::get("/dev_bdvd/PS3_GAME/TROPDIR/" + ctxt->trp_name +
"/TROPHY.TRP");
stream.open(trp_path);
}
// check if exists and opened
if (!stream)
{
const std::string msg = fmt::format("Failed to open trophy file: '%s' (%s)",
trp_path, fs::g_tls_error);
return {SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST, msg};
}
// TODO:
// SCE_NP_TROPHY_STATUS_DATA_CORRUPT -> reinstall
// SCE_NP_TROPHY_STATUS_REQUIRES_UPDATE -> reinstall (for example if a patch
// has updates for the trophy data) SCE_NP_TROPHY_STATUS_CHANGES_DETECTED ->
// reinstall (only possible in dev mode)
const std::string trophyPath =
"/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name;
const s32 trp_status = fs::is_dir(vfs::get(trophyPath)) ? SCE_NP_TROPHY_STATUS_INSTALLED : SCE_NP_TROPHY_STATUS_NOT_INSTALLED;
lock.unlock();
sceNpTrophy.notice(
"sceNpTrophyRegisterContext(): Callback is being called (trp_status=%u)",
trp_status);
// "Ask permission" to install the trophy data.
// The callback is called once and then if it returns >= 0 the cb is called
// through events(coming from vsh) that are passed to the CB through
// cellSysutilCheckCallback
if (statusCb(ppu, context, trp_status, 0, 0, arg) < 0)
{
on_error();
return SCE_NP_TROPHY_ERROR_PROCESSING_ABORTED;
}
2021-03-02 12:59:19 +01:00
std::unique_lock lock2(trophy_manager.mtx);
// Rerun error checks, the callback could have changed stuff by calling
// sceNpTrophy functions internally
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
on_error();
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt2, error2] = trophy_manager.get_context_ex(context, handle);
if (error2)
{
// Recheck for any errors, such as if AbortHandle was called
return error2;
}
// Paranoid checks: context/handler could have been destroyed and replaced
// with new ones with the same IDs Return an error for such cases
if (ctxt2 != ctxt)
{
on_error();
return SCE_NP_TROPHY_ERROR_UNKNOWN_CONTEXT;
}
2024-12-22 19:59:48 +01:00
if (handle_ptr.get() != idm::check_unlocked<trophy_handle_t>(handle))
{
on_error();
return SCE_NP_TROPHY_ERROR_UNKNOWN_HANDLE;
}
TRPLoader trp(stream);
if (!trp.LoadHeader())
{
sceNpTrophy.error(
"sceNpTrophyRegisterContext(): Failed to load trophy config header");
on_error();
return SCE_NP_TROPHY_ERROR_ILLEGAL_UPDATE;
}
// Rename or discard certain entries based on the files found
const usz kTargetBufferLength = 31;
char target[kTargetBufferLength + 1]{};
strcpy_trunc(target, fmt::format("TROP_%02d.SFM",
static_cast<s32>(g_cfg.sys.language)));
if (trp.ContainsEntry(target))
{
trp.RemoveEntry("TROPCONF.SFM");
trp.RemoveEntry("TROP.SFM");
trp.RenameEntry(target, "TROPCONF.SFM");
}
else if (trp.ContainsEntry("TROP.SFM"))
{
trp.RemoveEntry("TROPCONF.SFM");
trp.RenameEntry("TROP.SFM", "TROPCONF.SFM");
}
else if (!trp.ContainsEntry("TROPCONF.SFM"))
{
sceNpTrophy.error(
"sceNpTrophyRegisterContext(): Invalid/Incomplete trophy config");
on_error();
return SCE_NP_TROPHY_ERROR_ILLEGAL_UPDATE;
}
// Discard unnecessary TROP_XX.SFM files
for (s32 i = 0; i <= 18; i++)
{
strcpy_trunc(target, fmt::format("TROP_%02d.SFM", i));
if (i != g_cfg.sys.language)
{
trp.RemoveEntry(target);
}
}
if (!trp.Install(trophyPath))
{
sceNpTrophy.error("sceNpTrophyRegisterContext(): Failed to install trophy "
"context '%s' (%s)",
trophyPath, fs::g_tls_error);
on_error();
return SCE_NP_TROPHY_ERROR_ILLEGAL_UPDATE;
}
const auto& tropusr = ctxt->tropusr = std::make_unique<TROPUSRLoader>();
2019-12-19 02:57:40 +01:00
const std::string trophyUsrPath = trophyPath + "/TROPUSR.DAT";
const std::string trophyConfPath = trophyPath + "/TROPCONF.SFM";
ensure(tropusr->Load(trophyUsrPath, trophyConfPath).success);
// This emulates vsh sending the events and ensures that not 2 events are
// processed at once
const std::pair<u32, s32> statuses[] = {
{SCE_NP_TROPHY_STATUS_PROCESSING_SETUP, 3},
{SCE_NP_TROPHY_STATUS_PROCESSING_PROGRESS,
::narrow<s32>(tropusr->GetTrophiesCount()) - 1},
{SCE_NP_TROPHY_STATUS_PROCESSING_FINALIZE, 4},
{SCE_NP_TROPHY_STATUS_PROCESSING_COMPLETE, 0}};
lock2.unlock();
lv2_obj::sleep(ppu);
// Create a counter which is destroyed after the function ends
const auto queued = std::make_shared<atomic_t<u32>>(0);
for (auto status : statuses)
2017-04-14 12:08:17 +02:00
{
// One status max per cellSysutilCheckCallback call
*queued += status.second;
for (s32 completed = 0; completed <= status.second; completed++)
{
sysutil_register_cb([statusCb, status, context, completed, arg,
queued](ppu_thread& cb_ppu) -> s32
{
// TODO: it is possible that we need to check the return value here as
// well.
statusCb(cb_ppu, context, status.first, completed, status.second, arg);
if (queued && (*queued)-- == 1)
{
queued->notify_one();
}
return 0;
});
}
2019-11-30 22:16:29 +01:00
u64 current = get_system_time();
const u64 until = current + 300'000;
// If too much time passes just send the rest of the events anyway
2019-11-30 22:16:29 +01:00
for (u32 old_value; current < until && (old_value = *queued);
current = get_system_time())
{
thread_ctrl::wait_on(*queued, old_value, until - current);
2019-11-30 22:16:29 +01:00
if (ppu.is_stopped())
{
return {};
2019-11-30 22:16:29 +01:00
}
}
2017-04-14 12:08:17 +02:00
}
return CELL_OK;
}
error_code sceNpTrophyGetRequiredDiskSpace(u32 context, u32 handle,
vm::ptr<u64> reqspace, u64 options)
{
sceNpTrophy.warning("sceNpTrophyGetRequiredDiskSpace(context=0x%x, "
"handle=0x%x, reqspace=*0x%x, options=0x%llx)",
context, handle, reqspace, options);
if (!reqspace)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
2019-09-08 14:11:39 +02:00
if (options > 0)
{
return SCE_NP_TROPHY_ERROR_NOT_SUPPORTED;
}
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
2015-02-08 12:37:10 +01:00
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
u64 space = 0;
if (!fs::is_dir(vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" +
ctxt->trp_name)))
{
// open trophy pack file
std::string trophy_path =
vfs::get(Emu.GetDir() + "TROPDIR/" + ctxt->trp_name + "/TROPHY.TRP");
fs::file stream(trophy_path);
if (!stream && Emu.GetCat() == "GD")
{
sceNpTrophy.warning("sceNpTrophyGetRequiredDiskSpace failed to open "
"trophy file from boot path: '%s'",
trophy_path);
trophy_path = vfs::get("/dev_bdvd/PS3_GAME/TROPDIR/" + ctxt->trp_name +
"/TROPHY.TRP");
stream.open(trophy_path);
}
// check if exists and opened
if (!stream)
{
return {SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST, trophy_path};
}
TRPLoader trp(stream);
if (trp.LoadHeader())
{
space = trp.GetRequiredSpace();
}
else
{
sceNpTrophy.error("sceNpTrophyGetRequiredDiskSpace(): Failed to load "
"trophy header! (trp_name=%s)",
ctxt->trp_name);
}
}
else
{
sceNpTrophy.warning("sceNpTrophyGetRequiredDiskSpace(): Trophy config is "
"already installed (trp_name=%s)",
ctxt->trp_name);
}
sceNpTrophy.warning("sceNpTrophyGetRequiredDiskSpace(): reqspace is 0x%llx",
space);
*reqspace = space;
return CELL_OK;
}
error_code sceNpTrophySetSoundLevel(u32 context, u32 handle, u32 level,
u64 options)
{
sceNpTrophy.todo("sceNpTrophySetSoundLevel(context=0x%x, handle=0x%x, "
"level=%d, options=0x%llx)",
context, handle, level, options);
if (level > 100 || level < 20)
2019-09-08 14:11:39 +02:00
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (options > 0)
{
return SCE_NP_TROPHY_ERROR_NOT_SUPPORTED;
}
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
return CELL_OK;
}
error_code sceNpTrophyGetGameInfo(u32 context, u32 handle,
vm::ptr<SceNpTrophyGameDetails> details,
vm::ptr<SceNpTrophyGameData> data)
{
sceNpTrophy.warning("sceNpTrophyGetGameInfo(context=0x%x, handle=0x%x, "
"details=*0x%x, data=*0x%x)",
context, handle, details, data);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
if (!ctxt->tropusr)
{
// TODO: May return SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE for older sdk version
return SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED;
}
2019-09-08 14:11:39 +02:00
if (!details && !data)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
const std::string config_path =
vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name +
"/TROPCONF.SFM");
fs::file config(config_path);
2017-09-03 21:29:20 +02:00
if (!config)
{
return {SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST, config_path};
2017-09-03 21:29:20 +02:00
}
if (details)
*details = {};
if (data)
*data = {};
trophy_xml_document doc{};
pugi::xml_parse_result res = doc.Read(config.to_string());
if (!res)
{
sceNpTrophy.error("sceNpTrophyGetGameInfo: Failed to read TROPCONF.SFM: %s",
config_path);
// TODO: return some error
return CELL_OK;
}
std::shared_ptr<rXmlNode> trophy_base = doc.GetRoot();
if (!trophy_base)
{
sceNpTrophy.error("sceNpTrophyGetGameInfo: Failed to read TROPCONF.SFM "
"(root is null): %s",
config_path);
// TODO: return some error
return CELL_OK;
}
2018-06-15 10:28:32 +02:00
for (std::shared_ptr<rXmlNode> n = trophy_base->GetChildren(); n;
n = n->GetNext())
{
const std::string n_name = n->GetName();
if (details)
{
if (n_name == "title-name")
{
2018-06-15 10:28:32 +02:00
strcpy_trunc(details->title, n->GetNodeContent());
continue;
}
else if (n_name == "title-detail")
{
2018-06-15 10:28:32 +02:00
strcpy_trunc(details->description, n->GetNodeContent());
continue;
}
}
if (n_name == "trophy")
{
if (details)
{
details->numTrophies++;
switch (n->GetAttribute("ttype")[0])
{
case 'B':
details->numBronze++;
break;
case 'S':
details->numSilver++;
break;
case 'G':
details->numGold++;
break;
case 'P':
details->numPlatinum++;
break;
}
}
if (data)
{
2019-12-19 02:57:40 +01:00
const u32 trophy_id = atoi(n->GetAttribute("id").c_str());
if (ctxt->tropusr->GetTrophyUnlockState(trophy_id))
{
data->unlockedTrophies++;
switch (n->GetAttribute("ttype")[0])
{
case 'B':
data->unlockedBronze++;
break;
case 'S':
data->unlockedSilver++;
break;
case 'G':
data->unlockedGold++;
break;
case 'P':
data->unlockedPlatinum++;
break;
default:
break;
}
}
}
}
}
return CELL_OK;
}
2019-04-10 19:34:44 +02:00
error_code sceNpTrophyGetLatestTrophies()
{
UNIMPLEMENTED_FUNC(sceNpTrophy);
return CELL_OK;
}
error_code sceNpTrophyUnlockTrophy(ppu_thread& ppu, u32 context, u32 handle,
s32 trophyId, vm::ptr<u32> platinumId)
{
sceNpTrophy.warning("sceNpTrophyUnlockTrophy(context=0x%x, handle=0x%x, "
"trophyId=%d, platinumId=*0x%x)",
context, handle, trophyId, platinumId);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
if (!ctxt->tropusr)
{
// TODO: May return SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE for older sdk version
return SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED;
}
if (trophyId < 0 ||
trophyId >= static_cast<s32>(ctxt->tropusr->GetTrophiesCount()))
{
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
}
if (ctxt->tropusr->GetTrophyGrade(trophyId) == SCE_NP_TROPHY_GRADE_PLATINUM)
{
return SCE_NP_TROPHY_ERROR_CANNOT_UNLOCK_PLATINUM;
}
if (ctxt->tropusr->GetTrophyUnlockState(trophyId))
{
return SCE_NP_TROPHY_ERROR_ALREADY_UNLOCKED;
}
2023-05-15 23:22:13 +02:00
vm::var<CellRtcTick> tick;
if (error_code error = cellRtcGetCurrentTick(ppu, tick))
2023-05-15 23:22:13 +02:00
{
sceNpTrophy.error("sceNpTrophyUnlockTrophy: Failed to get timestamp: 0x%x",
+error);
2023-05-15 23:22:13 +02:00
}
if (ctxt->tropusr->UnlockTrophy(trophyId, tick->tick, tick->tick))
{
sceNpTrophy.notice("Trophy %d unlocked", trophyId);
}
// TODO: Make sure that unlocking platinum trophies is properly implemented
// and improve upon it
const std::string& config_path =
vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name +
"/TROPCONF.SFM");
const u32 unlocked_platinum_id =
ctxt->tropusr->GetUnlockedPlatinumID(trophyId, config_path);
2019-12-21 11:46:43 +01:00
2020-03-04 15:08:40 +01:00
if (unlocked_platinum_id != SCE_NP_TROPHY_INVALID_TROPHY_ID)
2017-09-21 10:21:43 +02:00
{
sceNpTrophy.warning("sceNpTrophyUnlockTrophy: All requirements for "
"unlocking the platinum trophy (ID = %d) were met.)",
unlocked_platinum_id);
2019-12-21 11:46:43 +01:00
if (ctxt->tropusr->UnlockTrophy(unlocked_platinum_id, tick->tick,
tick->tick))
2019-12-21 11:46:43 +01:00
{
sceNpTrophy.success("You unlocked a platinum trophy! Hooray!!!");
}
2017-09-21 10:21:43 +02:00
}
2019-12-21 11:46:43 +01:00
if (platinumId)
{
2019-12-21 11:46:43 +01:00
*platinumId = unlocked_platinum_id;
sceNpTrophy.warning("sceNpTrophyUnlockTrophy: platinumId was set to %d",
unlocked_platinum_id);
2019-12-21 11:46:43 +01:00
}
const std::string trophyPath = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" +
ctxt->trp_name + "/TROPUSR.DAT";
if (!ctxt->tropusr->Save(trophyPath))
{
sceNpTrophy.error("sceNpTrophyUnlockTrophy: failed to save '%s'",
trophyPath);
}
2019-12-21 11:46:43 +01:00
if (g_cfg.misc.show_trophy_popups)
{
// Enqueue popup for the regular trophy
show_trophy_notification(ctxt, trophyId);
2020-03-04 15:08:40 +01:00
if (unlocked_platinum_id != SCE_NP_TROPHY_INVALID_TROPHY_ID)
2019-06-20 15:51:50 +02:00
{
2019-12-21 11:46:43 +01:00
// Enqueue popup for the holy platinum trophy
show_trophy_notification(ctxt, unlocked_platinum_id);
2019-06-20 15:51:50 +02:00
}
}
return CELL_OK;
}
error_code sceNpTrophyGetTrophyUnlockState(u32 context, u32 handle,
vm::ptr<SceNpTrophyFlagArray> flags,
vm::ptr<u32> count)
{
sceNpTrophy.warning("sceNpTrophyGetTrophyUnlockState(context=0x%x, "
"handle=0x%x, flags=*0x%x, count=*0x%x)",
context, handle, flags, count);
2022-05-05 17:33:51 +02:00
if (!flags || !count)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
2015-02-08 12:37:10 +01:00
}
TROPUSRLoader* tropusr = nullptr;
TROPUSRLoader local_tropusr{};
if (ctxt->read_only)
{
const std::string trophyPath =
"/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name;
const std::string trophyUsrPath = trophyPath + "/TROPUSR.DAT";
const std::string trophyConfPath = trophyPath + "/TROPCONF.SFM";
if (local_tropusr.Load(trophyUsrPath, trophyConfPath).success)
{
tropusr = &local_tropusr;
}
else
{
// TODO: confirm
*count = 0;
*flags = {};
return CELL_OK;
}
}
else
{
if (!ctxt->tropusr)
{
// TODO: May return SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE for older sdk
// version
return SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED;
}
tropusr = ctxt->tropusr.get();
}
ensure(tropusr);
const u32 count_ = tropusr->GetTrophiesCount();
2015-02-08 12:37:10 +01:00
*count = count_;
if (count_ > 128)
sceNpTrophy.error(
"sceNpTrophyGetTrophyUnlockState: More than 128 trophies detected!");
2019-12-17 18:21:29 +01:00
// Needs hw testing
*flags = {};
// Pack up to 128 bools in u32 flag_bits[4]
2015-02-08 12:37:10 +01:00
for (u32 id = 0; id < count_; id++)
{
if (tropusr->GetTrophyUnlockState(id))
flags->flag_bits[id / 32] |= 1 << (id % 32);
else
flags->flag_bits[id / 32] &= ~(1 << (id % 32));
}
return CELL_OK;
}
2019-04-10 19:34:44 +02:00
error_code sceNpTrophyGetTrophyDetails()
{
UNIMPLEMENTED_FUNC(sceNpTrophy);
return CELL_OK;
}
static error_code NpTrophyGetTrophyInfo(const trophy_context_t* ctxt,
s32 trophyId,
SceNpTrophyDetails* details,
SceNpTrophyData* data)
{
if (!details && !data)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (!ctxt->tropusr)
{
// TODO: May return SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE for older sdk version
return SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED;
}
const std::string config_path =
vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + ctxt->trp_name +
"/TROPCONF.SFM");
fs::file config(config_path);
2017-09-03 21:29:20 +02:00
if (!config)
{
return {SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST, config_path};
2017-09-03 21:29:20 +02:00
}
2016-03-21 20:42:14 +01:00
SceNpTrophyDetails tmp_details{};
SceNpTrophyData tmp_data{};
2018-06-15 10:28:32 +02:00
trophy_xml_document doc{};
pugi::xml_parse_result res = doc.Read(config.to_string());
if (!res)
{
sceNpTrophy.error("sceNpTrophyGetGameInfo: Failed to read TROPCONF.SFM: %s",
config_path);
// TODO: return some error
}
auto trophy_base = doc.GetRoot();
if (!trophy_base)
{
sceNpTrophy.error("sceNpTrophyGetGameInfo: Failed to read TROPCONF.SFM "
"(root is null): %s",
config_path);
// TODO: return some error
}
bool found = false;
for (std::shared_ptr<rXmlNode> n = trophy_base ? trophy_base->GetChildren() : nullptr;
n; n = n->GetNext())
2017-09-03 21:29:20 +02:00
{
if (n->GetName() == "trophy" &&
(trophyId == atoi(n->GetAttribute("id").c_str())))
{
found = true;
2019-12-19 02:57:40 +01:00
const bool hidden = n->GetAttribute("hidden")[0] == 'y';
const bool unlocked = !!ctxt->tropusr->GetTrophyUnlockState(trophyId);
if (hidden && !unlocked) // Trophy is hidden
{
return SCE_NP_TROPHY_ERROR_HIDDEN;
}
if (details)
{
tmp_details.trophyId = trophyId;
tmp_details.hidden = hidden;
2019-12-19 02:57:40 +01:00
switch (n->GetAttribute("ttype")[0])
{
case 'B':
tmp_details.trophyGrade = SCE_NP_TROPHY_GRADE_BRONZE;
break;
case 'S':
tmp_details.trophyGrade = SCE_NP_TROPHY_GRADE_SILVER;
break;
case 'G':
tmp_details.trophyGrade = SCE_NP_TROPHY_GRADE_GOLD;
break;
case 'P':
tmp_details.trophyGrade = SCE_NP_TROPHY_GRADE_PLATINUM;
break;
default:
break;
}
for (std::shared_ptr<rXmlNode> n2 = n->GetChildren(); n2;
n2 = n2->GetNext())
{
const std::string n2_name = n2->GetName();
if (n2_name == "name")
{
strcpy_trunc(tmp_details.name, n2->GetNodeContent());
}
else if (n2_name == "detail")
{
strcpy_trunc(tmp_details.description, n2->GetNodeContent());
}
}
}
if (data)
{
tmp_data.trophyId = trophyId;
tmp_data.unlocked = unlocked;
tmp_data.timestamp = ctxt->tropusr->GetTrophyTimestamp(trophyId);
}
break;
}
}
if (!found)
{
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
}
if (details)
{
*details = tmp_details;
}
if (data)
{
*data = tmp_data;
}
return CELL_OK;
}
error_code sceNpTrophyGetTrophyInfo(u32 context, u32 handle, s32 trophyId,
vm::ptr<SceNpTrophyDetails> details,
vm::ptr<SceNpTrophyData> data)
{
sceNpTrophy.warning("sceNpTrophyGetTrophyInfo(context=0x%x, handle=0x%x, "
"trophyId=%d, details=*0x%x, data=*0x%x)",
context, handle, trophyId, details, data);
if (trophyId < 0 || trophyId > 127) // max 128 trophies
{
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
}
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
return NpTrophyGetTrophyInfo(ctxt, trophyId,
details ? details.get_ptr() : nullptr,
data ? data.get_ptr() : nullptr);
}
error_code sceNpTrophyGetGameProgress(u32 context, u32 handle,
vm::ptr<s32> percentage)
{
sceNpTrophy.warning(
"sceNpTrophyGetGameProgress(context=0x%x, handle=0x%x, percentage=*0x%x)",
context, handle, percentage);
if (!percentage)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2019-09-08 14:11:39 +02:00
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
if (!ctxt->tropusr)
{
// TODO: May return SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE for older sdk version
return SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED;
}
const u32 unlocked = ctxt->tropusr->GetUnlockedTrophiesCount();
const u32 trp_count = ctxt->tropusr->GetTrophiesCount();
2020-02-06 20:52:10 +01:00
// Round result to nearest (TODO: Check 0 trophies)
2025-10-05 18:28:03 +02:00
*percentage = trp_count ? rx::rounded_div(unlocked * 100, trp_count) : 0;
2020-02-06 20:52:10 +01:00
if (trp_count == 0 || trp_count > 128)
{
sceNpTrophy.warning("sceNpTrophyGetGameProgress(): Trophies count may be "
"invalid or untested (%d)",
trp_count);
2020-02-06 20:52:10 +01:00
}
return CELL_OK;
}
error_code sceNpTrophyGetGameIcon(u32 context, u32 handle, vm::ptr<void> buffer,
vm::ptr<u32> size)
{
sceNpTrophy.warning("sceNpTrophyGetGameIcon(context=0x%x, handle=0x%x, "
"buffer=*0x%x, size=*0x%x)",
context, handle, buffer, size);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
2019-09-08 14:11:39 +02:00
if (!size)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
// Try to get icon in current language first
const std::string trophy_path =
fmt::format("/dev_hdd0/home/%s/trophy/%s/", Emu.GetUsr(), ctxt->trp_name);
fs::file icon_file(vfs::get(fmt::format(
"%s/ICON0_%02d.PNG", trophy_path, static_cast<s32>(g_cfg.sys.language))));
if (!icon_file &&
!icon_file.open(vfs::get(fmt::format("%s/ICON0.PNG", trophy_path))))
{
return SCE_NP_TROPHY_ERROR_UNKNOWN_FILE;
}
2017-09-03 21:29:20 +02:00
const u32 icon_size = ::size32(icon_file);
2017-09-03 21:29:20 +02:00
if (buffer && *size >= icon_size)
{
2021-03-12 07:33:38 +01:00
lv2_file::op_read(icon_file, buffer, icon_size);
}
2017-09-03 21:29:20 +02:00
*size = icon_size;
return CELL_OK;
}
2019-04-05 20:14:01 +02:00
error_code sceNpTrophyGetUserInfo()
{
UNIMPLEMENTED_FUNC(sceNpTrophy);
return CELL_OK;
}
error_code sceNpTrophyGetTrophyIcon(u32 context, u32 handle, s32 trophyId,
vm::ptr<void> buffer, vm::ptr<u32> size)
{
sceNpTrophy.warning("sceNpTrophyGetTrophyIcon(context=0x%x, handle=0x%x, "
"trophyId=%d, buffer=*0x%x, size=*0x%x)",
context, handle, trophyId, buffer, size);
2021-03-02 12:59:19 +01:00
auto& trophy_manager = g_fxo->get<sce_np_trophy_manager>();
2021-03-02 12:59:19 +01:00
reader_lock lock(trophy_manager.mtx);
2021-03-02 12:59:19 +01:00
if (!trophy_manager.is_initialized)
{
return SCE_NP_TROPHY_ERROR_NOT_INITIALIZED;
}
2021-03-02 12:59:19 +01:00
const auto [ctxt, error] = trophy_manager.get_context_ex(context, handle);
if (error)
{
return error;
}
2019-09-08 14:11:39 +02:00
if (!size)
{
return SCE_NP_TROPHY_ERROR_INVALID_ARGUMENT;
}
if (!ctxt->tropusr)
{
// TODO: May return SCE_NP_TROPHY_ERROR_UNKNOWN_TITLE for older sdk version
return SCE_NP_TROPHY_ERROR_CONTEXT_NOT_REGISTERED;
}
2019-12-02 22:31:34 +01:00
if (ctxt->tropusr->GetTrophiesCount() <= static_cast<u32>(trophyId))
{
return SCE_NP_TROPHY_ERROR_INVALID_TROPHY_ID;
}
if (!ctxt->tropusr->GetTrophyUnlockState(trophyId))
{
const std::string config_path =
vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" +
ctxt->trp_name + "/TROPCONF.SFM");
fs::file config(config_path);
if (config)
{
trophy_xml_document doc{};
pugi::xml_parse_result res = doc.Read(config.to_string());
if (!res)
{
sceNpTrophy.error(
"sceNpTrophyGetTrophyIcon: Failed to read TROPCONF.SFM: %s",
config_path);
// TODO: return some error
}
auto trophy_base = doc.GetRoot();
if (!trophy_base)
{
sceNpTrophy.error("sceNpTrophyGetTrophyIcon: Failed to read "
"TROPCONF.SFM (root is null): %s",
config_path);
// TODO: return some error
}
for (std::shared_ptr<rXmlNode> n =
trophy_base ? trophy_base->GetChildren() : nullptr;
n; n = n->GetNext())
{
if (n->GetName() == "trophy" &&
trophyId == atoi(n->GetAttribute("id").c_str()) &&
n->GetAttribute("hidden")[0] == 'y')
{
return SCE_NP_TROPHY_ERROR_HIDDEN;
}
}
}
else
{
// TODO: Maybe return SCE_NP_TROPHY_ERROR_CONF_DOES_NOT_EXIST
}
return SCE_NP_TROPHY_ERROR_LOCKED;
}
fs::file icon_file(vfs::get("/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" +
ctxt->trp_name +
fmt::format("/TROP%03d.PNG", trophyId)));
2017-09-03 21:29:20 +02:00
if (!icon_file)
{
return SCE_NP_TROPHY_ERROR_UNKNOWN_FILE;
}
2017-09-03 21:29:20 +02:00
const u32 icon_size = ::size32(icon_file);
2017-09-03 21:29:20 +02:00
if (buffer && *size >= icon_size)
{
2021-03-12 07:33:38 +01:00
lv2_file::op_read(icon_file, buffer, icon_size);
}
2017-09-03 21:29:20 +02:00
*size = icon_size;
return CELL_OK;
}
2016-03-21 20:42:14 +01:00
DECLARE(ppu_module_manager::sceNpTrophy)("sceNpTrophy", []()
{
REG_FUNC(sceNpTrophy, sceNpTrophyGetGameProgress);
REG_FUNC(sceNpTrophy, sceNpTrophyRegisterContext);
REG_FUNC(sceNpTrophy, sceNpTrophyCreateHandle);
REG_FUNC(sceNpTrophy, sceNpTrophySetSoundLevel);
REG_FUNC(sceNpTrophy, sceNpTrophyGetRequiredDiskSpace);
REG_FUNC(sceNpTrophy, sceNpTrophyDestroyContext);
REG_FUNC(sceNpTrophy, sceNpTrophyInit);
REG_FUNC(sceNpTrophy, sceNpTrophyAbortHandle);
REG_FUNC(sceNpTrophy, sceNpTrophyGetGameInfo);
REG_FUNC(sceNpTrophy, sceNpTrophyDestroyHandle);
REG_FUNC(sceNpTrophy, sceNpTrophyGetGameDetails);
REG_FUNC(sceNpTrophy, sceNpTrophyUnlockTrophy);
REG_FUNC(sceNpTrophy, sceNpTrophyGetLatestTrophies);
REG_FUNC(sceNpTrophy, sceNpTrophyTerm);
REG_FUNC(sceNpTrophy, sceNpTrophyGetTrophyUnlockState);
REG_FUNC(sceNpTrophy, sceNpTrophyGetUserInfo);
REG_FUNC(sceNpTrophy, sceNpTrophyGetTrophyIcon);
REG_FUNC(sceNpTrophy, sceNpTrophyCreateContext);
REG_FUNC(sceNpTrophy, sceNpTrophyGetTrophyDetails);
REG_FUNC(sceNpTrophy, sceNpTrophyGetTrophyInfo);
REG_FUNC(sceNpTrophy, sceNpTrophyGetGameIcon);
});