This commit is contained in:
FlexBy420 2026-07-09 16:21:47 -05:00 committed by GitHub
commit afb1280deb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 316 additions and 4 deletions

View File

@ -14,6 +14,7 @@
#include "sceNp.h"
#include "sceNpTrophy.h"
#include "cellSysutil.h"
#include "Emu/NP/np_handler.h"
#include "Utilities/StrUtil.h"
@ -39,6 +40,7 @@ struct trophy_context_t
SAVESTATE_INIT_POS(42);
std::string trp_name;
SceNpCommunicationId comm_id{}; // set at CreateContext, not serialized
std::unique_ptr<TROPUSRLoader> tropusr;
bool read_only = false;
@ -506,6 +508,7 @@ error_code sceNpTrophyCreateContext(vm::ptr<u32> context, vm::cptr<SceNpCommunic
// set trophy context parameters (could be passed to constructor through make_ptr call)
ctxt->trp_name = name;
ctxt->comm_id = *commId; // stored for RPCN trophy sync/unlock
ctxt->read_only = !!(options & SCE_NP_TROPHY_OPTIONS_CREATE_CONTEXT_READ_ONLY);
*context = idm::last_id();
@ -714,7 +717,42 @@ error_code sceNpTrophyRegisterContext(ppu_thread& ppu, u32 context, u32 handle,
ensure(tropusr->Load(trophyUsrPath, trophyConfPath).success);
lock2.unlock();
if (g_cfg.net.psn_status == np_psn_status::psn_rpcn)
{
const SceNpCommunicationId ctx_comm_id = ctxt->comm_id;
const u32 trophy_count = tropusr->GetTrophiesCount();
std::vector<std::pair<s32, u64>> local_unlocked;
local_unlocked.reserve(trophy_count);
for (u32 i = 0; i < trophy_count; i++)
{
if (tropusr->GetTrophyUnlockState(static_cast<s32>(i)))
local_unlocked.emplace_back(static_cast<s32>(i), u64{0});
}
// Release lock before the blocking network call
lock2.unlock();
auto& np = g_fxo->get<np::np_handler>();
std::vector<std::pair<s32, u64>> srv_trophies = np.rpcn_trophy_sync(ctx_comm_id, local_unlocked);
bool changed = false;
for (const auto& [tid, ts] : srv_trophies)
{
if (tid >= 0 && tid < static_cast<s32>(trophy_count) && !tropusr->GetTrophyUnlockState(tid))
{
tropusr->UnlockTrophy(tid, ts, ts);
changed = true;
}
}
if (changed && !tropusr->Save(trophyUsrPath))
sceNpTrophy.error("sceNpTrophyRegisterContext(): Failed to save trophy data after RPCN sync");
}
else
{
lock2.unlock();
}
lv2_obj::sleep(ppu);
{
@ -1110,6 +1148,15 @@ error_code sceNpTrophyUnlockTrophy(ppu_thread& ppu, u32 context, u32 handle, s32
}
}
if (g_cfg.net.psn_status == np_psn_status::psn_rpcn)
{
auto& np = g_fxo->get<np::np_handler>();
np.rpcn_trophy_unlock(ctxt->comm_id, trophyId, tick->tick);
if (unlocked_platinum_id != SCE_NP_TROPHY_INVALID_TROPHY_ID)
np.rpcn_trophy_unlock(ctxt->comm_id, static_cast<s32>(unlocked_platinum_id), tick->tick);
}
return CELL_OK;
}

View File

@ -1609,6 +1609,32 @@ namespace np
}
}
void np_handler::rpcn_trophy_unlock(const SceNpCommunicationId& communication_id, s32 trophy_id, u64 timestamp)
{
if (!is_psn_active || g_cfg.net.psn_status != np_psn_status::psn_rpcn)
return;
std::lock_guard lock(mutex_rpcn);
if (!rpcn || !rpcn->is_authentified())
return;
rpcn->unlock_trophy(communication_id, trophy_id, timestamp);
}
std::vector<std::pair<s32, u64>> np_handler::rpcn_trophy_sync(
const SceNpCommunicationId& communication_id,
const std::vector<std::pair<s32, u64>>& local_unlocked)
{
if (!is_psn_active || g_cfg.net.psn_status != np_psn_status::psn_rpcn)
return {};
std::lock_guard lock(mutex_rpcn);
if (!rpcn || !rpcn->is_authentified())
return {};
return rpcn->sync_trophies(communication_id, local_unlocked);
}
template <typename T>
error_code np_handler::get_friend_presence_by_index(u32 index, SceNpUserInfo* user, T* pres)
{

View File

@ -248,6 +248,11 @@ namespace np
std::pair<error_code, std::optional<SceNpId>> get_friend_by_index(u32 index);
void set_presence(std::optional<std::string> status, std::optional<std::vector<u8>> data);
// RPCN trophy support
void rpcn_trophy_unlock(const SceNpCommunicationId& communication_id, s32 trophy_id, u64 timestamp);
std::vector<std::pair<s32, u64>> rpcn_trophy_sync(const SceNpCommunicationId& communication_id,
const std::vector<std::pair<s32, u64>>& local_unlocked);
template <typename T>
error_code get_friend_presence_by_index(u32 index, SceNpUserInfo* user, T* pres);

View File

@ -161,6 +161,8 @@ void fmt_class_string<rpcn::CommandType>::format(std::string& out, u64 arg)
case rpcn::CommandType::GetRoomInfoGUI: return "GetRoomInfoGUI";
case rpcn::CommandType::QuickMatchGUI: return "QuickMatchGUI";
case rpcn::CommandType::SearchJoinRoomGUI: return "SearchJoinRoomGUI";
case rpcn::CommandType::UnlockTrophy: return "UnlockTrophy";
case rpcn::CommandType::SyncTrophies: return "SyncTrophies";
}
return unknown;
@ -257,7 +259,7 @@ namespace rpcn
rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size()));
}
constexpr u32 RPCN_PROTOCOL_VERSION = 30;
constexpr u32 RPCN_PROTOCOL_VERSION = 31;
constexpr usz RPCN_HEADER_SIZE = 15;
const char* error_to_explanation(rpcn::ErrorType error)
@ -663,7 +665,8 @@ namespace rpcn
command == CommandType::AddBlock || command == CommandType::RemoveBlock ||
command == CommandType::SendMessage || command == CommandType::SendToken ||
command == CommandType::SendResetToken || command == CommandType::ResetPassword ||
command == CommandType::GetNetworkTime || command == CommandType::SetPresence || command == CommandType::Terminate)
command == CommandType::GetNetworkTime || command == CommandType::SetPresence || command == CommandType::Terminate ||
command == CommandType::SyncTrophies)
{
std::lock_guard lock(mutex_replies_sync);
replies_sync.insert(std::make_pair(packet_id, std::make_pair(command, std::move(data))));
@ -2605,6 +2608,64 @@ namespace rpcn
return forge_request_with_com_id(serialized, pr_com_id, CommandType::SetPresence, rpcn_request_counter.fetch_add(1));
}
bool rpcn_client::unlock_trophy(const SceNpCommunicationId& communication_id, s32 trophy_id, u64 timestamp)
{
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(s32) + sizeof(u64));
rpcn_client::write_communication_id(communication_id, data);
reinterpret_cast<le_t<s32>&>(data[COMMUNICATION_ID_SIZE]) = trophy_id;
reinterpret_cast<le_t<u64>&>(data[COMMUNICATION_ID_SIZE + sizeof(s32)]) = timestamp;
return forge_send(CommandType::UnlockTrophy, rpcn_request_counter.fetch_add(1), data);
}
std::vector<std::pair<s32, u64>> rpcn_client::sync_trophies(
const SceNpCommunicationId& communication_id,
const std::vector<std::pair<s32, u64>>& local_unlocked)
{
const u32 count = static_cast<u32>(local_unlocked.size());
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u32) + count * (sizeof(s32) + sizeof(u64))), reply_data;
rpcn_client::write_communication_id(communication_id, data);
reinterpret_cast<le_t<u32>&>(data[COMMUNICATION_ID_SIZE]) = count;
usz offset = COMMUNICATION_ID_SIZE + sizeof(u32);
for (const auto& [tid, ts] : local_unlocked)
{
reinterpret_cast<le_t<s32>&>(data[offset]) = tid;
offset += sizeof(s32);
reinterpret_cast<le_t<u64>&>(data[offset]) = ts;
offset += sizeof(u64);
}
if (!forge_send_reply(CommandType::SyncTrophies, rpcn_request_counter.fetch_add(1), data, reply_data))
return {};
vec_stream reply(reply_data);
const auto error = static_cast<ErrorType>(reply.get<u8>());
if (error != rpcn::ErrorType::NoError)
{
rpcn_log.error("sync_trophies: server returned error %s", fmt::format("%s", error));
return {};
}
const u32 server_count = reply.get<u32>();
std::vector<std::pair<s32, u64>> result;
result.reserve(server_count);
for (u32 i = 0; i < server_count; i++)
{
const s32 tid = reply.get<s32>();
const u64 ts = reply.get<u64>();
result.emplace_back(tid, ts);
}
if (reply.is_error())
{
error_and_disconnect("Malformed reply to SyncTrophies command");
return {};
}
return result;
}
bool rpcn_client::createjoin_room_gui(u32 req_id, const SceNpCommunicationId& communication_id, const SceNpMatchingAttr* attr_list)
{
np2_structs::CreateRoomGUIRequest pb_req;

View File

@ -370,6 +370,8 @@ namespace rpcn
bool tus_get_friends_data_status(u32 req_id, SceNpCommunicationId& communication_id, SceNpTusSlotId slotId, bool includeSelf, s32 sortType, s32 arrayNum);
bool tus_delete_multislot_data(u32 req_id, SceNpCommunicationId& communication_id, const SceNpOnlineId& targetNpId, vm::cptr<SceNpTusSlotId> slotIdArray, s32 arrayNum, bool vuser);
bool send_presence(const SceNpCommunicationId& pr_com_id, const std::string& pr_title, const std::string& pr_status, const std::string& pr_comment, const std::vector<u8>& pr_data);
bool unlock_trophy(const SceNpCommunicationId& communication_id, s32 trophy_id, u64 timestamp);
std::vector<std::pair<s32, u64>> sync_trophies(const SceNpCommunicationId& communication_id, const std::vector<std::pair<s32, u64>>& local_unlocked);
bool createjoin_room_gui(u32 req_id, const SceNpCommunicationId& communication_id, const SceNpMatchingAttr* attr_list);
bool join_room_gui(u32 req_id, const SceNpRoomId& room_id);
bool leave_room_gui(u32 req_id, const SceNpRoomId& room_id);

View File

@ -69,6 +69,8 @@ namespace rpcn
QuickMatchGUI,
SearchJoinRoomGUI,
GetRoomMemberDataExternalList,
UnlockTrophy,
SyncTrophies,
};
enum class NotificationType : u16

View File

@ -2,6 +2,7 @@
#include "../overlay_manager.h"
#include "overlay_trophy_list_dialog.h"
#include "Emu/Cell/Modules/sceNpTrophy.h"
#include "Emu/NP/rpcn_config.h"
#include "Emu/System.h"
#include "Emu/VFS.h"
@ -110,6 +111,13 @@ namespace rsx
m_show_hidden_trophies_button->set_pos(180, trophy_list_y + trophy_list_h + 20);
m_show_hidden_trophies_button->set_font("Arial", 16);
m_sync_trophies_button = std::make_unique<image_button>();
m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_TROPHIES);
m_sync_trophies_button->set_image_resource(resource_config::standard_image_resource::triangle);
m_sync_trophies_button->set_size(120, 30);
m_sync_trophies_button->set_pos(460, trophy_list_y + trophy_list_h + 20);
m_sync_trophies_button->set_font("Arial", 16);
fade_animation.duration_sec = 0.15f;
return_code = selection_code::canceled;
@ -139,6 +147,14 @@ namespace rsx
m_show_hidden_trophies = !m_show_hidden_trophies;
m_list_dirty = true;
break;
case pad_button::triangle:
// Only start a new sync if not already in progress
if (m_sync_status.load() != 1)
{
play_sound(sound_effect::cursor);
sync_trophies_async();
}
break;
case pad_button::dpad_up:
case pad_button::ls_up:
m_list->select_previous();
@ -189,6 +205,15 @@ namespace rsx
m_show_hidden_trophies_last = m_show_hidden_trophies;
}
// Update sync button label based on current sync state
switch (m_sync_status.load())
{
case 1: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNCING_TROPHIES); break;
case 2: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_SUCCESS); break;
case 3: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_FAILED); break;
default: m_sync_trophies_button->set_text(localized_string_id::HOME_MENU_TROPHY_SYNC_TROPHIES); break;
}
compiled_resource result;
result.add(m_dim_background->get_compiled());
if (m_list_dirty.exchange(false))
@ -201,6 +226,7 @@ namespace rsx
}
result.add(m_description->get_compiled());
result.add(m_show_hidden_trophies_button->get_compiled());
result.add(m_sync_trophies_button->get_compiled());
fade_animation.apply(result);
@ -210,7 +236,8 @@ namespace rsx
void trophy_list_dialog::show(const std::string& trop_name)
{
visible = false;
m_trop_name = trop_name;
m_trophy_data = load_trophies(trop_name);
ensure(m_trophy_data && m_trophy_data->trop_usr);
@ -234,6 +261,122 @@ namespace rsx
}
}
bool trophy_list_dialog::rpcn_configured()
{
cfg_rpcn cfg;
cfg.load();
return !cfg.get_npid().empty() && !cfg.get_password().empty();
}
void trophy_list_dialog::sync_trophies_async()
{
if (!rpcn_configured())
{
rsx_log.warning("Trophy sync requested but RPCN is not configured.");
m_sync_status = 3; // error
return;
}
if (!m_trophy_data || !m_trophy_data->trop_usr)
{
rsx_log.error("Trophy sync requested but trophy data is not loaded.");
m_sync_status = 3;
return;
}
m_sync_status = 1; // syncing
const std::string trop_name = m_trop_name;
atomic_t<u8>* sync_status = &m_sync_status;
atomic_t<bool>* list_dirty = &m_list_dirty;
SceNpCommunicationId comm_id{};
{
if (trop_name.size() >= COMMUNICATION_ID_SIZE)
{
const auto& n = trop_name;
std::memcpy(comm_id.data, n.c_str(), COMMUNICATION_ID_COMID_COMPONENT_SIZE);
comm_id.data[COMMUNICATION_ID_COMID_COMPONENT_SIZE] = '\0';
comm_id.num = static_cast<u8>(std::atoi(n.c_str() + COMMUNICATION_ID_COMID_COMPONENT_SIZE + 1));
}
else
{
rsx_log.error("Trophy sync: unexpected trop_name format: %s", trop_name);
m_sync_status = 3;
return;
}
}
const u32 trophy_count = m_trophy_data->trop_usr->GetTrophiesCount();
std::vector<std::pair<s32, u64>> local_unlocked;
local_unlocked.reserve(trophy_count);
for (u32 i = 0; i < trophy_count; i++)
{
if (m_trophy_data->trop_usr->GetTrophyUnlockState(static_cast<s32>(i)))
local_unlocked.emplace_back(static_cast<s32>(i), u64{0});
}
const std::string tropusr_vfs_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + trop_name + "/TROPUSR.DAT";
const std::string tropconf_vfs_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + trop_name + "/TROPCONF.SFM";
std::thread([=, local_unlocked = std::move(local_unlocked)]() mutable
{
g_cfg_rpcn.load();
auto rpcn = rpcn::rpcn_client::get_instance(0);
if (auto res = rpcn->wait_for_connection(); res != rpcn::rpcn_state::failure_no_failure)
{
rsx_log.error("Trophy sync: failed to connect to RPCN: %s", rpcn::rpcn_state_to_string(res));
*sync_status = 3;
return;
}
if (auto res = rpcn->wait_for_authentified(); res != rpcn::rpcn_state::failure_no_failure)
{
rsx_log.error("Trophy sync: failed to authenticate with RPCN: %s", rpcn::rpcn_state_to_string(res));
*sync_status = 3;
return;
}
std::vector<std::pair<s32, u64>> srv_trophies = rpcn->sync_trophies(comm_id, local_unlocked);
if (!srv_trophies.empty())
{
auto tropusr = std::make_unique<TROPUSRLoader>();
if (tropusr->Load(tropusr_vfs_path, tropconf_vfs_path).success)
{
const u32 count = tropusr->GetTrophiesCount();
bool changed = false;
for (const auto& [tid, ts] : srv_trophies)
{
if (tid >= 0 && tid < static_cast<s32>(count) && !tropusr->GetTrophyUnlockState(tid))
{
(void)tropusr->UnlockTrophy(tid, ts, ts);
changed = true;
}
}
if (changed)
{
if (!tropusr->Save(tropusr_vfs_path))
rsx_log.error("Trophy sync: failed to save TROPUSR after sync for %s", trop_name);
*list_dirty = true;
}
}
else
{
rsx_log.error("Trophy sync: failed to reload TROPUSR for %s", trop_name);
*sync_status = 3;
return;
}
}
*sync_status = 2; // success
}).detach();
}
std::unique_ptr<trophy_data> trophy_list_dialog::load_trophies(const std::string& trop_name) const
{
// Populate GameTrophiesData
@ -309,6 +452,15 @@ namespace rsx
{
ensure(m_trophy_data);
if (!m_trop_name.empty())
{
const std::string tropusr_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + m_trop_name + "/TROPUSR.DAT";
const std::string tropconf_path = "/dev_hdd0/home/" + Emu.GetUsr() + "/trophy/" + m_trop_name + "/TROPCONF.SFM";
auto fresh_usr = std::make_unique<TROPUSRLoader>();
if (fresh_usr->Load(tropusr_path, tropconf_path).success)
m_trophy_data->trop_usr = std::move(fresh_usr);
}
rsx_log.trace("Reloading Trophy List Overlay with %s %s", m_trophy_data->game_name, m_trophy_data->path);
std::string selected_trophy;

View File

@ -4,6 +4,7 @@
#include "../overlay_list_view.hpp"
#include "Loader/TROPUSR.h"
#include "Emu/NP/rpcn_client.h"
class TROPUSRLoader;
@ -25,19 +26,25 @@ namespace rsx
private:
std::unique_ptr<trophy_data> load_trophies(const std::string& trop_name) const;
void reload();
void sync_trophies_async();
std::unique_ptr<overlay_element> m_dim_background;
std::unique_ptr<list_view> m_list;
std::unique_ptr<label> m_description;
std::unique_ptr<image_button> m_show_hidden_trophies_button;
std::unique_ptr<image_button> m_sync_trophies_button;
animation_color_interpolate fade_animation;
std::unique_ptr<trophy_data> m_trophy_data;
std::string m_trop_name;
atomic_t<bool> m_list_dirty { true };
bool m_show_hidden_trophies = false;
bool m_show_hidden_trophies_last = false;
// Sync state: 0 = idle, 1 = syncing, 2 = success, 3 = error
atomic_t<u8> m_sync_status { 0 };
public:
trophy_list_dialog();
@ -47,6 +54,8 @@ namespace rsx
compiled_resource get_compiled() override;
void show(const std::string& trop_name);
static bool rpcn_configured();
};
}
}

View File

@ -303,6 +303,10 @@ enum class localized_string_id
HOME_MENU_TROPHY_GRADE_SILVER,
HOME_MENU_TROPHY_GRADE_GOLD,
HOME_MENU_TROPHY_GRADE_PLATINUM,
HOME_MENU_TROPHY_SYNC_TROPHIES,
HOME_MENU_TROPHY_SYNCING_TROPHIES,
HOME_MENU_TROPHY_SYNC_SUCCESS,
HOME_MENU_TROPHY_SYNC_FAILED,
AUDIO_MUTED,
AUDIO_UNMUTED,

View File

@ -323,6 +323,10 @@ private:
case localized_string_id::HOME_MENU_TROPHY_GRADE_SILVER: return tr("Silver", "Trophy type");
case localized_string_id::HOME_MENU_TROPHY_GRADE_GOLD: return tr("Gold", "Trophy type");
case localized_string_id::HOME_MENU_TROPHY_GRADE_PLATINUM: return tr("Platinum", "Trophy type");
case localized_string_id::HOME_MENU_TROPHY_SYNC_TROPHIES: return tr("Sync trophies");
case localized_string_id::HOME_MENU_TROPHY_SYNCING_TROPHIES: return tr("Syncing...");
case localized_string_id::HOME_MENU_TROPHY_SYNC_SUCCESS: return tr("Synced!");
case localized_string_id::HOME_MENU_TROPHY_SYNC_FAILED: return tr("Sync failed");
case localized_string_id::AUDIO_MUTED: return tr("Audio muted", "Audio");
case localized_string_id::AUDIO_UNMUTED: return tr("Audio unmuted", "Audio");
case localized_string_id::AUDIO_CHANGED: return tr("Volume changed to %0", "Audio").arg(std::forward<Args>(args)...);