2020-12-05 13:08:24 +01:00
# include "stdafx.h"
2020-02-15 23:36:20 +01:00
# include "Emu/VFS.h"
2014-03-20 19:23:14 +01:00
# include "TROPUSR.h"
2020-02-01 08:43:43 +01:00
LOG_CHANNEL ( trp_log , " Trophy " ) ;
2020-02-01 05:36:53 +01:00
2021-03-13 14:36:59 +01:00
enum : u32
{
TROPUSR_MAGIC = 0x818F54AD
} ;
2022-03-17 23:18:33 +01:00
std : : shared_ptr < rXmlNode > trophy_xml_document : : GetRoot ( )
{
auto trophy_base = rXmlDocument : : GetRoot ( ) ;
2023-05-28 13:36:45 +02:00
if ( ! trophy_base )
{
return nullptr ;
}
2022-03-17 23:18:33 +01:00
if ( auto trophy_conf = trophy_base - > GetChildren ( ) ;
trophy_conf & & trophy_conf - > GetName ( ) = = " trophyconf " )
{
trophy_base = trophy_conf ;
}
else
{
trp_log . error ( " trophy_xml_document: Root name does not match trophyconf in trophy. Name: %s " , trophy_conf ? trophy_conf - > GetName ( ) : trophy_base - > GetName ( ) ) ;
// TODO: return nullptr or is this possible?
}
return trophy_base ;
}
2023-11-17 21:33:30 +01:00
TROPUSRLoader : : load_result TROPUSRLoader : : Load ( std : : string_view filepath , std : : string_view configpath )
2014-03-20 19:23:14 +01:00
{
2023-11-17 21:33:30 +01:00
const std : : string path = vfs : : get ( filepath ) ;
2016-02-01 22:47:09 +01:00
2021-03-13 14:36:59 +01:00
load_result res { } ;
// Generate TROPUSR.DAT
auto generate = [ & ]
2015-07-26 10:14:56 +02:00
{
2021-03-13 14:36:59 +01:00
// Reset filesystem error
fs : : g_tls_error = fs : : error : : ok ;
// Generate TROPUSR.DAT if not existing
res . success = Generate ( filepath , configpath ) ;
if ( ! res . success )
2017-10-24 17:43:05 +02:00
{
2021-03-13 14:36:59 +01:00
trp_log . error ( " TROPUSRLoader::Load(): Failed to generate TROPUSR.DAT (path='%s', cfg='%s', %s) " , path , configpath , fs : : g_tls_error ) ;
2017-10-24 17:43:05 +02:00
}
2021-03-13 14:36:59 +01:00
m_file . close ( ) ;
return res ;
} ;
if ( ! m_file . open ( path ) )
{
return generate ( ) ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
2016-02-01 22:47:09 +01:00
if ( ! LoadHeader ( ) | | ! LoadTableHeaders ( ) | | ! LoadTables ( ) )
{
2021-03-13 14:36:59 +01:00
// Ignore existing TROPUSR.DAT because it is invalid
m_file . close ( ) ;
res . discarded_existing = true ;
trp_log . error ( " TROPUSRLoader::Load(): Failed to load existing TROPUSR.DAT, trying to generate new file with empty trophies history! (path='%s') " , path ) ;
return generate ( ) ;
2016-02-01 22:47:09 +01:00
}
2014-03-20 19:23:14 +01:00
2021-03-13 14:36:59 +01:00
m_file . close ( ) ;
res . success = true ;
return res ;
2014-03-20 19:23:14 +01:00
}
bool TROPUSRLoader : : LoadHeader ( )
{
2016-02-01 22:47:09 +01:00
if ( ! m_file )
2015-07-26 10:14:56 +02:00
{
2014-03-20 19:23:14 +01:00
return false ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
2016-02-01 22:47:09 +01:00
m_file . seek ( 0 ) ;
2015-07-26 10:14:56 +02:00
2021-03-13 14:36:59 +01:00
if ( ! m_file . read ( m_header ) | | m_header . magic ! = TROPUSR_MAGIC )
2015-07-26 10:14:56 +02:00
{
2014-03-20 19:23:14 +01:00
return false ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
return true ;
}
bool TROPUSRLoader : : LoadTableHeaders ( )
{
2016-02-01 22:47:09 +01:00
if ( ! m_file )
2015-07-26 10:14:56 +02:00
{
2014-03-20 19:23:14 +01:00
return false ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
2016-02-01 22:47:09 +01:00
m_file . seek ( 0x30 ) ;
2014-03-20 19:23:14 +01:00
m_tableHeaders . clear ( ) ;
2015-07-26 10:14:56 +02:00
2021-12-01 17:09:07 +01:00
if ( ! m_file . read ( m_tableHeaders , m_header . tables_count ) )
2015-07-26 10:14:56 +02:00
{
2021-03-21 06:03:21 +01:00
return false ;
2014-03-20 19:23:14 +01:00
}
2015-07-26 10:14:56 +02:00
2014-03-20 19:23:14 +01:00
return true ;
}
bool TROPUSRLoader : : LoadTables ( )
{
2016-02-01 22:47:09 +01:00
if ( ! m_file )
2015-07-26 10:14:56 +02:00
{
2014-03-20 19:23:14 +01:00
return false ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
for ( const TROPUSRTableHeader & tableHeader : m_tableHeaders )
{
2016-02-01 22:47:09 +01:00
m_file . seek ( tableHeader . offset ) ;
2014-03-20 19:23:14 +01:00
2020-02-19 16:26:41 +01:00
if ( tableHeader . type = = 4u )
2014-03-20 19:23:14 +01:00
{
m_table4 . clear ( ) ;
2015-07-26 10:14:56 +02:00
2021-12-01 17:09:07 +01:00
if ( ! m_file . read ( m_table4 , tableHeader . entries_count ) )
2021-03-21 06:03:21 +01:00
return false ;
2014-03-20 19:23:14 +01:00
}
2020-02-19 16:26:41 +01:00
if ( tableHeader . type = = 6u )
2014-03-20 19:23:14 +01:00
{
m_table6 . clear ( ) ;
2015-07-26 10:14:56 +02:00
2021-12-01 17:09:07 +01:00
if ( ! m_file . read ( m_table6 , tableHeader . entries_count ) )
2021-03-21 06:03:21 +01:00
return false ;
2014-03-20 19:23:14 +01:00
}
// TODO: Other tables
}
return true ;
}
// TODO: TROPUSRLoader::Save deletes the TROPUSR and creates it again. This is probably very slow.
2023-11-17 21:33:30 +01:00
bool TROPUSRLoader : : Save ( std : : string_view filepath )
2014-03-20 19:23:14 +01:00
{
2021-03-02 16:13:15 +01:00
fs : : pending_file temp ( vfs : : get ( filepath ) ) ;
if ( ! temp . file )
2015-07-26 10:14:56 +02:00
{
2016-02-01 22:47:09 +01:00
return false ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
2021-03-02 16:13:15 +01:00
temp . file . write ( m_header ) ;
2021-03-13 14:42:35 +01:00
temp . file . write ( m_tableHeaders ) ;
temp . file . write ( m_table4 ) ;
temp . file . write ( m_table6 ) ;
2014-03-20 19:23:14 +01:00
2021-03-02 16:13:15 +01:00
return temp . commit ( ) ;
2014-03-20 19:23:14 +01:00
}
2023-11-17 21:33:30 +01:00
bool TROPUSRLoader : : Generate ( std : : string_view filepath , std : : string_view configpath )
2014-03-20 19:23:14 +01:00
{
2017-09-03 21:29:20 +02:00
fs : : file config ( vfs : : get ( configpath ) ) ;
if ( ! config )
{
return false ;
}
2016-02-01 22:47:09 +01:00
2022-03-17 23:18:33 +01:00
trophy_xml_document doc { } ;
pugi : : xml_parse_result res = doc . Read ( config . to_string ( ) ) ;
if ( ! res )
{
trp_log . error ( " TROPUSRLoader::Generate: Failed to read file: %s " , filepath ) ;
return false ;
}
2014-03-20 19:23:14 +01:00
m_table4 . clear ( ) ;
m_table6 . clear ( ) ;
2015-07-26 10:14:56 +02:00
2017-09-02 13:43:44 +02:00
auto trophy_base = doc . GetRoot ( ) ;
2023-05-28 13:36:45 +02:00
if ( ! trophy_base )
{
trp_log . error ( " TROPUSRLoader::Generate: Failed to read file (root is null): %s " , filepath ) ;
return false ;
}
2017-09-02 13:43:44 +02:00
for ( std : : shared_ptr < rXmlNode > n = trophy_base - > GetChildren ( ) ; n ; n = n - > GetNext ( ) )
2014-03-20 19:23:14 +01:00
{
if ( n - > GetName ( ) = = " trophy " )
{
2019-12-19 02:57:40 +01:00
const u32 trophy_id = std : : atoi ( n - > GetAttribute ( " id " ) . c_str ( ) ) ;
2019-12-21 11:46:43 +01:00
const u32 trophy_pid = std : : atoi ( n - > GetAttribute ( " pid " ) . c_str ( ) ) ;
2019-12-19 02:57:40 +01:00
2014-03-20 19:23:14 +01:00
u32 trophy_grade ;
2019-11-29 23:28:06 +01:00
switch ( n - > GetAttribute ( " ttype " ) [ 0 ] )
2014-03-20 19:23:14 +01:00
{
2019-12-21 11:46:43 +01:00
case ' B ' : trophy_grade = trophy_grade : : bronze ; break ;
case ' S ' : trophy_grade = trophy_grade : : silver ; break ;
case ' G ' : trophy_grade = trophy_grade : : gold ; break ;
case ' P ' : trophy_grade = trophy_grade : : platinum ; break ;
default : trophy_grade = trophy_grade : : unknown ; break ;
2014-03-20 19:23:14 +01:00
}
2025-04-05 21:50:45 +02:00
TROPUSREntry4 entry4 = { 4 , u32 { sizeof ( TROPUSREntry4 ) } - 0x10 , : : size32 ( m_table4 ) , 0 , trophy_id , trophy_grade , trophy_pid } ;
TROPUSREntry6 entry6 = { 6 , u32 { sizeof ( TROPUSREntry6 ) } - 0x10 , : : size32 ( m_table6 ) , 0 , trophy_id } ;
2014-03-20 19:23:14 +01:00
m_table4 . push_back ( entry4 ) ;
m_table6 . push_back ( entry6 ) ;
}
}
u64 offset = sizeof ( TROPUSRHeader ) + 2 * sizeof ( TROPUSRTableHeader ) ;
2025-04-05 21:50:45 +02:00
TROPUSRTableHeader table4header = { 4 , u32 { sizeof ( TROPUSREntry4 ) } - 0x10 , 1 , : : size32 ( m_table4 ) , offset } ;
2014-03-20 19:23:14 +01:00
offset + = m_table4 . size ( ) * sizeof ( TROPUSREntry4 ) ;
2025-04-05 21:50:45 +02:00
TROPUSRTableHeader table6header = { 6 , u32 { sizeof ( TROPUSREntry6 ) } - 0x10 , 1 , : : size32 ( m_table6 ) , offset } ;
2014-03-20 19:23:14 +01:00
offset + = m_table6 . size ( ) * sizeof ( TROPUSREntry6 ) ;
m_tableHeaders . clear ( ) ;
m_tableHeaders . push_back ( table4header ) ;
m_tableHeaders . push_back ( table6header ) ;
2021-03-13 14:36:59 +01:00
std : : memset ( & m_header , 0 , sizeof ( m_header ) ) ;
m_header . magic = TROPUSR_MAGIC ;
2014-03-20 19:23:14 +01:00
m_header . unk1 = 0x00010000 ;
2019-11-29 23:28:06 +01:00
m_header . tables_count = : : size32 ( m_tableHeaders ) ;
2014-03-20 19:23:14 +01:00
m_header . unk2 = 0 ;
2021-03-13 14:36:59 +01:00
return Save ( filepath ) ;
2014-03-20 19:23:14 +01:00
}
2021-10-30 20:44:25 +02:00
u32 TROPUSRLoader : : GetTrophiesCount ( ) const
2014-03-20 19:23:14 +01:00
{
2019-11-29 23:28:06 +01:00
return : : size32 ( m_table6 ) ;
2014-03-20 19:23:14 +01:00
}
2021-10-30 20:44:25 +02:00
u32 TROPUSRLoader : : GetUnlockedTrophiesCount ( ) const
2018-04-18 19:37:07 +02:00
{
u32 count = 0 ;
for ( const auto & trophy : m_table6 )
{
if ( trophy . trophy_state )
{
count + + ;
}
}
return count ;
}
2019-12-21 11:46:43 +01:00
u32 TROPUSRLoader : : GetUnlockedPlatinumID ( u32 trophy_id , const std : : string & config_path )
{
constexpr u32 invalid_trophy_id = - 1 ; // SCE_NP_TROPHY_INVALID_TROPHY_ID;
if ( trophy_id > = m_table6 . size ( ) | | trophy_id > = m_table4 . size ( ) )
{
2020-02-01 05:36:53 +01:00
trp_log . warning ( " TROPUSRLoader::GetUnlockedPlatinumID: Invalid id=%d " , trophy_id ) ;
2019-12-21 11:46:43 +01:00
return invalid_trophy_id ;
}
if ( m_table6 . size ( ) ! = m_table4 . size ( ) )
{
2020-02-01 05:36:53 +01:00
trp_log . warning ( " TROPUSRLoader::GetUnlockedPlatinumID: Table size mismatch: %d vs. %d " , m_table6 . size ( ) , m_table4 . size ( ) ) ;
2019-12-21 11:46:43 +01:00
return invalid_trophy_id ;
}
// We need to read the trophy info from file here and update it for backwards compatibility.
// TROPUSRLoader::Generate will currently not be called on existing trophy data which might lack the pid.
fs : : file config ( config_path ) ;
if ( ! config )
{
return invalid_trophy_id ;
}
2022-03-17 23:18:33 +01:00
trophy_xml_document doc { } ;
pugi : : xml_parse_result res = doc . Read ( config . to_string ( ) ) ;
if ( ! res )
2019-12-21 11:46:43 +01:00
{
2022-03-17 23:18:33 +01:00
trp_log . error ( " TROPUSRLoader::GetUnlockedPlatinumID: Failed to read file: %s " , config_path ) ;
return invalid_trophy_id ;
2019-12-21 11:46:43 +01:00
}
2022-03-17 23:18:33 +01:00
auto trophy_base = doc . GetRoot ( ) ;
2023-05-28 13:36:45 +02:00
if ( ! trophy_base )
{
trp_log . error ( " TROPUSRLoader::GetUnlockedPlatinumID: Failed to read file (root is null): %s " , config_path ) ;
return false ;
}
2022-03-17 23:18:33 +01:00
2020-12-18 08:39:54 +01:00
const usz trophy_count = m_table4 . size ( ) ;
2019-12-21 11:46:43 +01:00
for ( std : : shared_ptr < rXmlNode > n = trophy_base - > GetChildren ( ) ; n ; n = n - > GetNext ( ) )
{
if ( n - > GetName ( ) = = " trophy " )
{
const u32 trophy_id = std : : atoi ( n - > GetAttribute ( " id " ) . c_str ( ) ) ;
const u32 trophy_pid = std : : atoi ( n - > GetAttribute ( " pid " ) . c_str ( ) ) ;
// We currently assume that trophies are ordered
if ( trophy_id < trophy_count & & m_table4 [ trophy_id ] . trophy_id = = trophy_id )
{
// Update the pid for backwards compatibility
m_table4 [ trophy_id ] . trophy_pid = trophy_pid ;
}
}
}
// Get this trophy's platinum link id
const u32 pid = m_table4 [ trophy_id ] . trophy_pid ;
// The platinum trophy has to have a valid id and must still be locked
if ( pid = = invalid_trophy_id | | GetTrophyUnlockState ( pid ) ) // the first check is redundant but I'll keep it to prevent regressions
{
return invalid_trophy_id ;
}
// The platinum trophy stays locked if any relevant trophy is still locked
2020-12-18 08:39:54 +01:00
for ( usz i = 0 ; i < trophy_count ; i + + )
2019-12-21 11:46:43 +01:00
{
if ( m_table4 [ i ] . trophy_pid = = pid & & ! m_table6 [ i ] . trophy_state )
{
return invalid_trophy_id ;
}
}
// All relevant trophies for this platinum link id were unlocked
return pid ;
}
2021-10-30 20:44:25 +02:00
u32 TROPUSRLoader : : GetTrophyGrade ( u32 id ) const
2020-01-06 09:47:11 +01:00
{
if ( id > = m_table4 . size ( ) )
{
2020-02-01 05:36:53 +01:00
trp_log . warning ( " TROPUSRLoader::GetTrophyGrade: Invalid id=%d " , id ) ;
2020-01-06 09:47:11 +01:00
return trophy_grade : : unknown ;
}
return m_table4 [ id ] . trophy_grade ; // Let's assume the trophies are stored ordered
}
2021-10-30 20:44:25 +02:00
u32 TROPUSRLoader : : GetTrophyUnlockState ( u32 id ) const
2014-03-20 19:23:14 +01:00
{
2015-07-26 10:14:56 +02:00
if ( id > = m_table6 . size ( ) )
{
2020-02-01 05:36:53 +01:00
trp_log . warning ( " TROPUSRLoader::GetTrophyUnlockState: Invalid id=%d " , id ) ;
2017-10-30 20:13:06 +01:00
return 0 ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
return m_table6 [ id ] . trophy_state ; // Let's assume the trophies are stored ordered
}
2021-10-30 20:44:25 +02:00
u64 TROPUSRLoader : : GetTrophyTimestamp ( u32 id ) const
2014-03-20 19:23:14 +01:00
{
2014-08-30 22:41:01 +02:00
if ( id > = m_table6 . size ( ) )
2015-07-26 10:14:56 +02:00
{
2020-02-01 05:36:53 +01:00
trp_log . warning ( " TROPUSRLoader::GetTrophyTimestamp: Invalid id=%d " , id ) ;
2017-10-30 20:13:06 +01:00
return 0 ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
2018-09-03 17:46:14 +02:00
// TODO: What timestamp does sceNpTrophyGetTrophyInfo want, timestamp1 or timestamp2?
2014-03-20 19:23:14 +01:00
return m_table6 [ id ] . timestamp2 ; // Let's assume the trophies are stored ordered
}
bool TROPUSRLoader : : UnlockTrophy ( u32 id , u64 timestamp1 , u64 timestamp2 )
{
2015-07-26 10:14:56 +02:00
if ( id > = m_table6 . size ( ) )
{
2020-02-01 05:36:53 +01:00
trp_log . warning ( " TROPUSRLoader::UnlockTrophy: Invalid id=%d " , id ) ;
2014-03-20 19:23:14 +01:00
return false ;
2015-07-26 10:14:56 +02:00
}
2014-03-20 19:23:14 +01:00
m_table6 [ id ] . trophy_state = 1 ;
m_table6 [ id ] . timestamp1 = timestamp1 ;
m_table6 [ id ] . timestamp2 = timestamp2 ;
2015-07-26 10:14:56 +02:00
2014-03-20 19:23:14 +01:00
return true ;
}
2023-11-17 21:33:30 +01:00
bool TROPUSRLoader : : LockTrophy ( u32 id )
{
if ( id > = m_table6 . size ( ) )
{
trp_log . warning ( " TROPUSRLoader::LockTrophy: Invalid id=%d " , id ) ;
return false ;
}
m_table6 [ id ] . trophy_state = 0 ;
m_table6 [ id ] . timestamp1 = 0 ;
m_table6 [ id ] . timestamp2 = 0 ;
return true ;
}