mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
RPCN: Sync trophies (#18760)
Add synching local trophies with RPCN server trophies, allows users to essentially cloud save their trophies when they are connected to RPCN. This PR also aims to add support for global earned % trophies simillar to steam for RPCN part of website. No local trophies and not connected to RPCN <img width="1183" height="717" alt="obraz" src="https://github.com/user-attachments/assets/f0b3add1-506d-43e5-8dae-cd34e041e2bd" /> After booting the game with RPCN enabled <img width="1184" height="713" alt="obraz" src="https://github.com/user-attachments/assets/d5992ddc-7fad-4b38-b4f9-006a832ffed5" /> RPCN Side PR https://github.com/RipleyTom/rpcn/pull/140
This commit is contained in:
@@ -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,46 @@ 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, s64>> 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),
|
||||
static_cast<s64>(tropusr->GetTrophyTimestamp(static_cast<s32>(i))));
|
||||
}
|
||||
}
|
||||
|
||||
// Release lock before the blocking network call
|
||||
lock2.unlock();
|
||||
|
||||
auto& np = g_fxo->get<np::np_handler>();
|
||||
std::vector<std::pair<s32, s64>> 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) && ts >= 0 && !tropusr->GetTrophyUnlockState(tid))
|
||||
{
|
||||
static_cast<void>(tropusr->UnlockTrophy(tid, static_cast<u64>(ts), static_cast<u64>(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 +1152,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, static_cast<s64>(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), static_cast<s64>(tick->tick));
|
||||
}
|
||||
|
||||
return CELL_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -1638,6 +1638,32 @@ namespace np
|
||||
}
|
||||
}
|
||||
|
||||
void np_handler::rpcn_trophy_unlock(const SceNpCommunicationId& communication_id, s32 trophy_id, s64 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, s64>> np_handler::rpcn_trophy_sync(
|
||||
const SceNpCommunicationId& communication_id,
|
||||
const std::vector<std::pair<s32, s64>>& 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)
|
||||
{
|
||||
|
||||
@@ -249,6 +249,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, s64 timestamp);
|
||||
std::vector<std::pair<s32, s64>> rpcn_trophy_sync(const SceNpCommunicationId& communication_id,
|
||||
const std::vector<std::pair<s32, s64>>& local_unlocked);
|
||||
|
||||
template <typename T>
|
||||
error_code get_friend_presence_by_index(u32 index, SceNpUserInfo* user, T* pres);
|
||||
|
||||
|
||||
@@ -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, s64 timestamp)
|
||||
{
|
||||
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(s32) + sizeof(s64));
|
||||
rpcn_client::write_communication_id(communication_id, data);
|
||||
reinterpret_cast<le_t<s32>&>(data[COMMUNICATION_ID_SIZE]) = trophy_id;
|
||||
reinterpret_cast<le_t<s64>&>(data[COMMUNICATION_ID_SIZE + sizeof(s32)]) = timestamp;
|
||||
return forge_send(CommandType::UnlockTrophy, rpcn_request_counter.fetch_add(1), data);
|
||||
}
|
||||
|
||||
std::vector<std::pair<s32, s64>> rpcn_client::sync_trophies(
|
||||
const SceNpCommunicationId& communication_id,
|
||||
const std::vector<std::pair<s32, s64>>& local_unlocked)
|
||||
{
|
||||
const u32 count = static_cast<u32>(local_unlocked.size());
|
||||
|
||||
std::vector<u8> data(COMMUNICATION_ID_SIZE + sizeof(u32) + count * (sizeof(s32) + sizeof(s64))), 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<s64>&>(data[offset]) = ts;
|
||||
offset += sizeof(s64);
|
||||
}
|
||||
|
||||
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, s64>> result;
|
||||
result.reserve(server_count);
|
||||
for (u32 i = 0; i < server_count; i++)
|
||||
{
|
||||
const s32 tid = reply.get<s32>();
|
||||
const s64 ts = reply.get<s64>();
|
||||
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;
|
||||
|
||||
@@ -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, s64 timestamp);
|
||||
std::vector<std::pair<s32, s64>> sync_trophies(const SceNpCommunicationId& communication_id, const std::vector<std::pair<s32, s64>>& 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);
|
||||
|
||||
@@ -69,6 +69,8 @@ namespace rpcn
|
||||
QuickMatchGUI,
|
||||
SearchJoinRoomGUI,
|
||||
GetRoomMemberDataExternalList,
|
||||
UnlockTrophy,
|
||||
SyncTrophies,
|
||||
};
|
||||
|
||||
enum class NotificationType : u16
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -107,7 +108,7 @@ namespace rsx
|
||||
m_sort_button->set_text(localized_string_id::HOME_MENU_TROPHY_SORT_GAME_DEFAULT);
|
||||
m_sort_button->set_image_resource(resource_config::standard_image_resource::triangle);
|
||||
m_sort_button->set_size(120, 30);
|
||||
m_sort_button->set_pos(560, trophy_list_y + trophy_list_h + 20);
|
||||
m_sort_button->set_pos(460, trophy_list_y + trophy_list_h + 20);
|
||||
m_sort_button->set_font("Arial", 16);
|
||||
|
||||
m_show_hidden_trophies_button = std::make_unique<image_button>();
|
||||
@@ -117,6 +118,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::select);
|
||||
m_sync_trophies_button->set_size(120, 30);
|
||||
m_sync_trophies_button->set_pos(700, 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;
|
||||
@@ -159,6 +167,13 @@ namespace rsx
|
||||
m_list_dirty = true;
|
||||
break;
|
||||
}
|
||||
case pad_button::select:
|
||||
// Only start a new sync if not already in progress
|
||||
if (m_sync_status.load() != 1)
|
||||
{
|
||||
sync_trophies_async();
|
||||
}
|
||||
break;
|
||||
case pad_button::dpad_up:
|
||||
case pad_button::ls_up:
|
||||
m_list->select_previous();
|
||||
@@ -209,6 +224,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;
|
||||
}
|
||||
|
||||
if (m_sort_mode_last != m_sort_mode)
|
||||
{
|
||||
localized_string_id sort_label_id;
|
||||
@@ -236,6 +260,7 @@ namespace rsx
|
||||
result.add(m_description->get_compiled());
|
||||
result.add(m_sort_button->get_compiled());
|
||||
result.add(m_show_hidden_trophies_button->get_compiled());
|
||||
result.add(m_sync_trophies_button->get_compiled());
|
||||
|
||||
fade_animation.apply(result);
|
||||
|
||||
@@ -245,7 +270,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);
|
||||
|
||||
@@ -269,6 +295,126 @@ 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, s64>> 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),
|
||||
static_cast<s64>(m_trophy_data->trop_usr->GetTrophyTimestamp(static_cast<s32>(i))));
|
||||
}
|
||||
}
|
||||
|
||||
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, s64>> 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) && ts >= 0 && !tropusr->GetTrophyUnlockState(tid))
|
||||
{
|
||||
(void)tropusr->UnlockTrophy(tid, static_cast<u64>(ts), static_cast<u64>(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
|
||||
@@ -344,6 +490,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);
|
||||
|
||||
s32 selected_index = m_list ? m_list->get_selected_index() : 0;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "../overlay_list_view.hpp"
|
||||
|
||||
#include "Loader/TROPUSR.h"
|
||||
#include "Emu/NP/rpcn_client.h"
|
||||
|
||||
class TROPUSRLoader;
|
||||
|
||||
@@ -33,22 +34,28 @@ 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_sort_button;
|
||||
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;
|
||||
trophy_sort_mode m_sort_mode = trophy_sort_mode::game_default;
|
||||
trophy_sort_mode m_sort_mode_last = trophy_sort_mode::game_default;
|
||||
|
||||
// Sync state: 0 = idle, 1 = syncing, 2 = success, 3 = error
|
||||
atomic_t<u8> m_sync_status { 0 };
|
||||
|
||||
public:
|
||||
trophy_list_dialog();
|
||||
|
||||
@@ -58,6 +65,8 @@ namespace rsx
|
||||
compiled_resource get_compiled() override;
|
||||
|
||||
void show(const std::string& trop_name);
|
||||
|
||||
static bool rpcn_configured();
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,6 +307,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,
|
||||
HOME_MENU_TROPHY_SORT_GAME_DEFAULT,
|
||||
HOME_MENU_TROPHY_SORT_NOT_EARNED,
|
||||
HOME_MENU_TROPHY_SORT_EARNED_DATE,
|
||||
|
||||
@@ -327,6 +327,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::HOME_MENU_TROPHY_SORT_GAME_DEFAULT: return tr("Sort: Game Default");
|
||||
case localized_string_id::HOME_MENU_TROPHY_SORT_NOT_EARNED: return tr("Sort: Not Earned");
|
||||
case localized_string_id::HOME_MENU_TROPHY_SORT_EARNED_DATE: return tr("Sort: Earned Date");
|
||||
|
||||
Reference in New Issue
Block a user