@ -40,7 +40,7 @@ namespace GameDatabase {
enum : u32
{
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48 ,
GAME_DATABASE_CACHE_VERSION = 29 ,
GAME_DATABASE_CACHE_VERSION = 30 ,
} ;
static const Entry * GetEntryForId ( std : : string_view code ) ;
@ -50,8 +50,10 @@ static bool SaveToCache();
static bool LoadGameDBYaml ( ) ;
static bool ParseYamlEntry ( Entry * entry , const ryml : : ConstNodeRef & value ) ;
static bool ParseYamlDiscSetEntry ( DiscSetEntry * entry , const ryml : : ConstNodeRef & value ) ;
static bool ParseYamlCodes ( PreferUnorderedStringMap < std : : string_view > & lookup , const ryml : : ConstNodeRef & value ,
std : : string_view serial ) ;
static void BindDiscSetsToEntries ( ) ;
static bool LoadTrackHashes ( ) ;
static constexpr const std : : array < const char * , static_cast < int > ( CompatibilityRating : : Count ) >
@ -154,32 +156,46 @@ static constexpr std::array s_language_names = {
static_assert ( s_language_names . size ( ) = = static_cast < size_t > ( Language : : MaxCount ) ) ;
static constexpr const char * GAMEDB_YAML_FILENAME = " gamedb.yaml " ;
static constexpr const char * DISCSETS_YAML_FILENAME = " discsets.yaml " ;
static constexpr const char * DISCDB_YAML_FILENAME = " discdb.yaml " ;
static DisplayDeinterlacingMode DEFAULT_DEINTERLACING_MODE = DisplayDeinterlacingMode : : Adaptive ;
static bool s_loaded = false ;
static bool s_track_hashes_loaded = false ;
namespace {
struct ALIGN_TO_CACHE_LINE State
{
bool loaded ;
bool track_hashes_loaded ;
DynamicHeapArray < u8 > db_data ; // we take strings from the data, so store a copy
DynamicHeapArray < u8 > disc_set_db_data ; // if loaded from binary cache, this will be empty
std : : vector < GameDatabase : : Entry > entries ;
std : : vector < GameDatabase : : DiscSetEntry > disc_sets ;
PreferUnorderedStringMap < u32 > code_lookup ;
TrackHashesMap track_hashes_map ;
} ;
} // namespace
static DynamicHeapArray < u8 > s_db_data ; // we take strings from the data, so store a copy
static std : : vector < GameDatabase : : Entry > s_entries ;
static PreferUnorderedStringMap < u32 > s_code_lookup ;
static State s_state ;
static TrackHashesMap s_track_hashes_map ;
} // namespace GameDatabase
void GameDatabase : : EnsureLoaded ( )
{
if ( s_loaded )
if ( s_ state. loaded)
return ;
Timer timer ;
s_ loaded = true ;
s_ state. loaded = true ;
if ( ! LoadFromCache ( ) )
{
s_entries = { } ;
s_code_lookup = { } ;
s_state . entries = { } ;
s_state . code_lookup = { } ;
s_state . db_data . deallocate ( ) ;
s_state . disc_set_db_data . deallocate ( ) ;
if ( LoadGameDBYaml ( ) )
{
@ -187,19 +203,21 @@ void GameDatabase::EnsureLoaded()
}
else
{
s_entries = { } ;
s_code_lookup = { } ;
s_state . entries = { } ;
s_state . code_lookup = { } ;
s_state . db_data . deallocate ( ) ;
s_state . disc_set_db_data . deallocate ( ) ;
}
}
INFO_LOG ( " Database load of {} entries took {:.0f}ms. " , s_ entries. size ( ) , timer . GetTimeMilliseconds ( ) ) ;
INFO_LOG ( " Database load of {} entries took {:.0f}ms. " , s_ state. entries. size ( ) , timer . GetTimeMilliseconds ( ) ) ;
}
void GameDatabase : : Unload ( )
{
s_ entries = { } ;
s_ code_lookup = { } ;
s_ loaded = false ;
s_ state. entries = { } ;
s_ state. code_lookup = { } ;
s_ state. loaded = false ;
}
const GameDatabase : : Entry * GameDatabase : : GetEntryForId ( std : : string_view code )
@ -209,8 +227,8 @@ const GameDatabase::Entry* GameDatabase::GetEntryForId(std::string_view code)
EnsureLoaded ( ) ;
auto iter = s_ code_lookup. find ( code ) ;
return ( iter ! = s_ code_lookup. end ( ) ) ? & s_ entries[ iter - > second ] : nullptr ;
auto iter = s_ state. code_lookup. find ( code ) ;
return ( iter ! = s_ state. code_lookup. end ( ) ) ? & s_ state. entries[ iter - > second ] : nullptr ;
}
std : : string GameDatabase : : GetSerialForDisc ( CDImage * image )
@ -278,9 +296,9 @@ const GameDatabase::Entry* GameDatabase::GetEntryForSerial(std::string_view seri
EnsureLoaded ( ) ;
const auto it =
std : : lower_bound ( s_ entries. cbegin ( ) , s_ entries. cend ( ) , serial ,
std : : lower_bound ( s_ state. entries. cbegin ( ) , s_ state. entries. cend ( ) , serial ,
[ ] ( const Entry & entry , const std : : string_view & search ) { return ( entry . serial < search ) ; } ) ;
return ( it ! = s_ entries. end ( ) & & it - > serial = = serial ) ? & ( * it ) : nullptr ;
return ( it ! = s_ state. entries. end ( ) & & it - > serial = = serial ) ? & ( * it ) : nullptr ;
}
const char * GameDatabase : : GetTraitName ( Trait trait )
@ -360,9 +378,9 @@ SmallString GameDatabase::Entry::GetLanguagesString() const
return ret ;
}
std : : string_view GameDatabase : : Entry : : GetDisplayTitle ( ) const
std : : string_view GameDatabase : : Entry : : GetDisplayTitle ( bool localized ) const
{
return ! localized_title . empty ( ) ? localized_title : title ;
return ( localized & & ! localized_title . empty ( ) ) ? localized_title : title ;
}
std : : string_view GameDatabase : : Entry : : GetSortTitle ( ) const
@ -375,6 +393,31 @@ std::string_view GameDatabase::Entry::GetSaveTitle() const
return ! save_title . empty ( ) ? save_title : title ;
}
bool GameDatabase : : Entry : : IsFirstDiscInSet ( ) const
{
return ( disc_set & & disc_set - > GetFirstSerial ( ) = = serial ) ;
}
std : : string_view GameDatabase : : DiscSetEntry : : GetDisplayTitle ( bool localized ) const
{
return ( localized & & ! localized_title . empty ( ) ) ? localized_title : title ;
}
std : : string_view GameDatabase : : DiscSetEntry : : GetSortTitle ( ) const
{
return ! sort_title . empty ( ) ? sort_title : title ;
}
std : : string_view GameDatabase : : DiscSetEntry : : GetSaveTitle ( ) const
{
return ! save_title . empty ( ) ? save_title : title ;
}
std : : string_view GameDatabase : : DiscSetEntry : : GetFirstSerial ( ) const
{
return serials [ 0 ] ;
}
void GameDatabase : : Entry : : ApplySettings ( Settings & settings , bool display_osd_messages ) const
{
if ( display_active_start_offset . has_value ( ) )
@ -937,7 +980,7 @@ std::string GameDatabase::Entry::GenerateCompatibilityReport() const
if ( ! sort_title . empty ( ) )
ret . append_format ( " **{}:** {} \n \n " , TRANSLATE_SV ( " GameDatabase " , " Sort Title " ) , title ) ;
if ( ! localized_title . empty ( ) )
ret . append_format ( " **{}:** {} \n \n " , TRANSLATE_SV ( " GameDatabase " , " Localized Title " ) , title) ;
ret . append_format ( " **{}:** {} \n \n " , TRANSLATE_SV ( " GameDatabase " , " Localized Title " ) , localized_ title) ;
if ( ! save_title . empty ( ) )
ret . append_format ( " **{}:** {} \n \n " , TRANSLATE_SV ( " GameDatabase " , " Save Title " ) , title ) ;
@ -1015,10 +1058,10 @@ std::string GameDatabase::Entry::GenerateCompatibilityReport() const
if ( settings_heading )
ret . append ( " \n " ) ;
if ( ! disc_set _name. empty ( ) )
if ( disc_set )
{
ret . append_format ( " **{}:** {} \n " , TRANSLATE_SV ( " GameDatabase " , " Disc Set " ) , disc_set _name ) ;
for ( const std : : string_view & ds_serial : disc_set _ serials)
ret . append_format ( " **{}:** {} \n " , TRANSLATE_SV ( " GameDatabase " , " Disc Set " ) , disc_set - > GetDisplayTitle ( true ) ) ;
for ( const std : : string_view & ds_serial : disc_set - > serials)
ret . append_format ( " - {} \n " , ds_serial ) ;
}
@ -1043,11 +1086,11 @@ bool GameDatabase::LoadFromCache()
BinarySpanReader reader ( db_data - > cspan ( ) ) ;
const u64 gamedb_ts = Host : : GetResourceFileTimestamp ( " gamedb.yaml " , false ) . value_or ( 0 ) ;
u32 signature , version , num_ entries, num_codes ;
u32 signature , version , num_ disc_set_entries, num_ entries, num_codes ;
u64 file_gamedb_ts ;
if ( ! reader . ReadU32 ( & signature ) | | ! reader . ReadU32 ( & version ) | | ! reader . ReadU64 ( & file_gamedb_ts ) | |
! reader . ReadU32 ( & num_ entries) | | ! reader . ReadU32 ( & num_codes ) | | signature ! = GAME_DATABASE_CACHE_SIGNATURE | |
version ! = GAME_DATABASE_CACHE_VERSION )
! reader . ReadU32 ( & num_ disc_set_entries) | | ! reader . ReadU32 ( & num_ entries) | | ! reader . ReadU32 ( & num_codes ) | |
signature ! = GAME_DATABASE_CACHE_SIGNATURE | | version ! = GAME_DATABASE_CACHE_VERSION )
{
DEV_LOG ( " Cache header is corrupted or version mismatch. " ) ;
return false ;
@ -1059,28 +1102,56 @@ bool GameDatabase::LoadFromCache()
return false ;
}
s_entries . reserve ( num_entries ) ;
s_state . entries . reserve ( num_entries ) ;
s_state . disc_sets . reserve ( num_disc_set_entries ) ;
for ( u32 i = 0 ; i < num_disc_set_entries ; i + + )
{
DiscSetEntry & disc_set = s_state . disc_sets . emplace_back ( ) ;
u32 num_serials ;
if ( ! reader . ReadSizePrefixedString ( & disc_set . title ) | | ! reader . ReadSizePrefixedString ( & disc_set . sort_title ) | |
! reader . ReadSizePrefixedString ( & disc_set . localized_title ) | |
! reader . ReadSizePrefixedString ( & disc_set . save_title ) | | ! reader . ReadU32 ( & num_serials ) )
{
DEV_LOG ( " Cache disc set entry is corrupted. " ) ;
return false ;
}
if ( num_serials > 0 )
{
disc_set . serials . reserve ( num_serials ) ;
for ( u32 j = 0 ; j < num_serials ; j + + )
{
if ( ! reader . ReadSizePrefixedString ( & disc_set . serials . emplace_back ( ) ) )
{
DEV_LOG ( " Cache disc set entry is corrupted. " ) ;
return false ;
}
}
}
}
for ( u32 i = 0 ; i < num_entries ; i + + )
{
Entry & entry = s_entries . emplace_back ( ) ;
Entry & entry = s_ state. entries. emplace_back ( ) ;
constexpr u32 trait_num_bytes = ( static_cast < u32 > ( Trait : : MaxCount ) + 7 ) / 8 ;
constexpr u32 language_num_bytes = ( static_cast < u32 > ( Language : : MaxCount ) + 7 ) / 8 ;
std : : array < u8 , trait_num_bytes > trait_bits ;
std : : array < u8 , language_num_bytes > language_bits ;
u8 compatibility ;
u32 num_disc_set_serials ;
s32 disc_set_index ;
if ( ! reader . ReadSizePrefixedString ( & entry . serial ) | | ! reader . ReadSizePrefixedString ( & entry . title ) | |
! reader . ReadSizePrefixedString ( & entry . sort_title ) | | ! reader . ReadSizePrefixedString ( & entry . localized_title ) | |
! reader . ReadSizePrefixedString ( & entry . save_title ) | | ! reader . ReadSizePrefixedString ( & entry . genre ) | |
! reader . ReadSizePrefixedString ( & entry . developer ) | | ! reader . ReadSizePrefixedString ( & entry . publisher ) | |
! reader . ReadSizePrefixedString ( & entry . compatibility_version_tested ) | |
! reader . ReadSizePrefixedString ( & entry . compatibility_comments ) | | ! reader . ReadU64 ( & entry . release_date ) | |
! reader . ReadU8 ( & entry . min_players ) | | ! reader . ReadU8 ( & entry . max_players ) | | ! reader . ReadU8 ( & entry . min_blocks ) | |
! reader . ReadU8 ( & entry . max_blocks ) | | ! reader . ReadU16 ( & entry . supported_controllers ) | |
! reader . ReadU8 ( & compatibility ) | | compatibility > = static_cast < u8 > ( GameDatabase : : CompatibilityRating : : Count ) | |
! reader . ReadSizePrefixedString ( & entry . compatibility_comments ) | | ! reader . ReadS32 ( & disc_set_index ) | |
( disc_set_index > = 0 & & static_cast < u32 > ( disc_set_index ) > = num_disc_set_entries ) | |
! reader . ReadU64 ( & entry . release_date ) | | ! reader . ReadU8 ( & entry . min_players ) | |
! reader . ReadU8 ( & entry . max_players ) | | ! reader . ReadU8 ( & entry . min_blocks ) | | ! reader . ReadU8 ( & entry . max_blocks ) | |
! reader . ReadU16 ( & entry . supported_controllers ) | | ! reader . ReadU8 ( & compatibility ) | |
compatibility > = static_cast < u8 > ( GameDatabase : : CompatibilityRating : : Count ) | |
! reader . Read ( trait_bits . data ( ) , trait_num_bytes ) | | ! reader . Read ( language_bits . data ( ) , language_num_bytes ) | |
! reader . ReadOptionalT ( & entry . display_active_start_offset ) | |
! reader . ReadOptionalT ( & entry . display_active_end_offset ) | |
@ -1092,26 +1163,13 @@ bool GameDatabase::LoadFromCache()
! reader . ReadOptionalT ( & entry . gpu_max_run_ahead ) | | ! reader . ReadOptionalT ( & entry . gpu_pgxp_tolerance ) | |
! reader . ReadOptionalT ( & entry . gpu_pgxp_depth_threshold ) | |
! reader . ReadOptionalT ( & entry . gpu_pgxp_preserve_proj_fp ) | | ! reader . ReadOptionalT ( & entry . gpu_line_detect_mode ) | |
! reader . ReadOptionalT ( & entry . cpu_overclock ) | | ! reader . ReadSizePrefixedString ( & entry . disc_set_name ) | |
! reader . ReadU32 ( & num_disc_set_serials ) )
! reader . ReadOptionalT ( & entry . cpu_overclock ) )
{
DEV_LOG ( " Cache entry is corrupted. " ) ;
return false ;
}
if ( num_disc_set_serials > 0 )
{
entry . disc_set_serials . reserve ( num_disc_set_serials ) ;
for ( u32 j = 0 ; j < num_disc_set_serials ; j + + )
{
if ( ! reader . ReadSizePrefixedString ( & entry . disc_set_serials . emplace_back ( ) ) )
{
DEV_LOG ( " Cache entry is corrupted. " ) ;
return false ;
}
}
}
entry . disc_set = ( disc_set_index > = 0 ) ? & s_state . disc_sets [ static_cast < u32 > ( disc_set_index ) ] : nullptr ;
entry . compatibility = static_cast < GameDatabase : : CompatibilityRating > ( compatibility ) ;
entry . traits . reset ( ) ;
for ( size_t j = 0 ; j < static_cast < size_t > ( Trait : : MaxCount ) ; j + + )
@ -1130,16 +1188,17 @@ bool GameDatabase::LoadFromCache()
{
std : : string code ;
u32 index ;
if ( ! reader . ReadSizePrefixedString ( & code ) | | ! reader . ReadU32 ( & index ) | | index > = static_cast < u32 > ( s_entries . size ( ) ) )
if ( ! reader . ReadSizePrefixedString ( & code ) | | ! reader . ReadU32 ( & index ) | |
index > = static_cast < u32 > ( s_state . entries . size ( ) ) )
{
DEV_LOG ( " Cache code entry is corrupted. " ) ;
return false ;
}
s_ code_lookup. emplace ( std : : move ( code ) , index ) ;
s_ state. code_lookup. emplace ( std : : move ( code ) , index ) ;
}
s_ db_data = std : : move ( db_data . value ( ) ) ;
s_ state. db_data = std : : move ( db_data . value ( ) ) ;
return true ;
}
@ -1160,10 +1219,22 @@ bool GameDatabase::SaveToCache()
writer . WriteU32 ( GAME_DATABASE_CACHE_VERSION ) ;
writer . WriteU64 ( static_cast < u64 > ( gamedb_ts ) ) ;
writer . WriteU32 ( static_cast < u32 > ( s_entries . size ( ) ) ) ;
writer . WriteU32 ( static_cast < u32 > ( s_code_lookup . size ( ) ) ) ;
writer . WriteU32 ( static_cast < u32 > ( s_state . disc_sets . size ( ) ) ) ;
writer . WriteU32 ( static_cast < u32 > ( s_state . entries . size ( ) ) ) ;
writer . WriteU32 ( static_cast < u32 > ( s_state . code_lookup . size ( ) ) ) ;
for ( const Entry & entry : s_entries )
for ( const DiscSetEntry & disc_set : s_state . disc_sets )
{
writer . WriteSizePrefixedString ( disc_set . title ) ;
writer . WriteSizePrefixedString ( disc_set . sort_title ) ;
writer . WriteSizePrefixedString ( disc_set . localized_title ) ;
writer . WriteSizePrefixedString ( disc_set . save_title ) ;
writer . WriteU32 ( static_cast < u32 > ( disc_set . serials . size ( ) ) ) ;
for ( const std : : string_view & ds_serial : disc_set . serials )
writer . WriteSizePrefixedString ( ds_serial ) ;
}
for ( const Entry & entry : s_state . entries )
{
writer . WriteSizePrefixedString ( entry . serial ) ;
writer . WriteSizePrefixedString ( entry . title ) ;
@ -1175,6 +1246,7 @@ bool GameDatabase::SaveToCache()
writer . WriteSizePrefixedString ( entry . publisher ) ;
writer . WriteSizePrefixedString ( entry . compatibility_version_tested ) ;
writer . WriteSizePrefixedString ( entry . compatibility_comments ) ;
writer . WriteS32 ( entry . disc_set ? static_cast < s32 > ( entry . disc_set - & s_state . disc_sets [ 0 ] ) : - 1 ) ;
writer . WriteU64 ( entry . release_date ) ;
writer . WriteU8 ( entry . min_players ) ;
writer . WriteU8 ( entry . max_players ) ;
@ -1219,14 +1291,9 @@ bool GameDatabase::SaveToCache()
writer . WriteOptionalT ( entry . gpu_pgxp_preserve_proj_fp ) ;
writer . WriteOptionalT ( entry . gpu_line_detect_mode ) ;
writer . WriteOptionalT ( entry . cpu_overclock ) ;
writer . WriteSizePrefixedString ( entry . disc_set_name ) ;
writer . WriteU32 ( static_cast < u32 > ( entry . disc_set_serials . size ( ) ) ) ;
for ( const std : : string_view & serial : entry . disc_set_serials )
writer . WriteSizePrefixedString ( serial ) ;
}
for ( const auto & it : s_ code_lookup)
for ( const auto & it : s_state . code_lookup )
{
writer . WriteSizePrefixedString ( it . first ) ;
writer . WriteU32 ( it . second ) ;
@ -1245,62 +1312,90 @@ bool GameDatabase::LoadGameDBYaml()
return false ;
}
const ryml : : Tree tree = ryml : : parse_in_place (
to_csubstr ( GAMEDB_YAML_FILENAME ) , c4 : : substr ( reinterpret_cast < char * > ( gamedb_data - > data ( ) ) , gamedb_data - > size ( ) ) ) ;
const ryml : : ConstNodeRef root = tree . rootref ( ) ;
s_entries . reserve ( root . num_children ( ) ) ;
std : : optional < DynamicHeapArray < u8 > > disc_set_data = Host : : ReadResourceFile ( DISCSETS_YAML_FILENAME , false , & error ) ;
if ( ! disc_set_data . has_value ( ) )
{
ERROR_LOG ( " Failed to read disc set database: {} " , error . GetDescription ( ) ) ;
return false ;
}
PreferUnorderedStringMap < std : : string_view > code_lookup ;
for ( const ryml : : ConstNodeRef & current : root . cchildren ( ) )
{
const std : : string_view serial = to_stringview ( current . key ( ) ) ;
if ( current . empty ( ) )
{
ERROR_LOG ( " Missing serial for entry. " ) ;
return false ;
}
const ryml : : Tree tree = ryml : : parse_in_place (
to_csubstr ( GAMEDB_YAML_FILENAME ) , c4 : : substr ( reinterpret_cast < char * > ( gamedb_data - > data ( ) ) , gamedb_data - > size ( ) ) ) ;
const ryml : : ConstNodeRef root = tree . rootref ( ) ;
s_state . entries . reserve ( root . num_children ( ) ) ;
Entry & entry = s_entries . emplace_back ( ) ;
entry . serial = serial ;
if ( ! ParseYamlEntry ( & entry , current ) )
for ( const ryml : : ConstNodeRef & current : root . cchildren ( ) )
{
s_entries . pop_back ( ) ;
continue ;
}
const std : : string_view serial = to_stringview ( current . key ( ) ) ;
if ( current . empty ( ) )
{
ERROR_LOG ( " Missing serial for entry. " ) ;
return false ;
}
ParseYamlCodes ( code_lookup , current , serial ) ;
Entry & entry = s_state . entries . emplace_back ( ) ;
entry . serial = serial ;
if ( ! ParseYamlEntry ( & entry , current ) )
{
s_state . entries . pop_back ( ) ;
continue ;
}
ParseYamlCodes ( code_lookup , current , serial ) ;
}
}
// Sorting must be done before generating code lookup, because otherwise the indices won't match.
s_entries . shrink_to_fit ( ) ;
std : : sort ( s_entries . begin ( ) , s_entries . end ( ) ,
s_ state. entries. shrink_to_fit ( ) ;
std : : sort ( s_ state. entries. begin ( ) , s_ state. entries. end ( ) ,
[ ] ( const Entry & lhs , const Entry & rhs ) { return ( lhs . serial < rhs . serial ) ; } ) ;
ryml : : reset_callbacks ( ) ;
for ( const auto & [ code , serial ] : code_lookup )
{
const auto it =
std : : lower_bound ( s_ entries. cbegin ( ) , s_ entries. cend ( ) , serial ,
std : : lower_bound ( s_ state. entries. cbegin ( ) , s_ state. entries. cend ( ) , serial ,
[ ] ( const Entry & entry , const std : : string_view & search ) { return ( entry . serial < search ) ; } ) ;
if ( it = = s_ entries. end ( ) | | it - > serial ! = serial )
if ( it = = s_ state. entries. end ( ) | | it - > serial ! = serial )
{
ERROR_LOG ( " Somehow we messed up our code lookup for {} and {}?! " , code , serial ) ;
continue ;
}
if ( ! s_ code_lookup. emplace ( code , static_cast < u32 > ( std : : distance ( s_ entries. cbegin ( ) , it ) ) ) . second )
if ( ! s_ state. code_lookup. emplace ( code , static_cast < u32 > ( std : : distance ( s_ state. entries. cbegin ( ) , it ) ) ) . second )
ERROR_LOG ( " Failed to insert code {} " , code ) ;
}
if ( s_ entries. empty ( ) )
if ( s_ state. entries. empty ( ) )
{
ERROR_LOG ( " Game database is empty. " ) ;
return false ;
}
s_db_data = std : : move ( gamedb_data . value ( ) ) ;
// parse disc sets
{
const ryml : : Tree tree =
ryml : : parse_in_place ( to_csubstr ( DISCSETS_YAML_FILENAME ) ,
c4 : : substr ( reinterpret_cast < char * > ( disc_set_data - > data ( ) ) , disc_set_data - > size ( ) ) ) ;
const ryml : : ConstNodeRef root = tree . rootref ( ) ;
s_state . disc_sets . reserve ( root . num_children ( ) ) ;
for ( const ryml : : ConstNodeRef & current : root . cchildren ( ) )
{
DiscSetEntry & disc_set = s_state . disc_sets . emplace_back ( ) ;
if ( ! ParseYamlDiscSetEntry ( & disc_set , current ) )
{
s_state . disc_sets . pop_back ( ) ;
continue ;
}
}
s_state . disc_sets . shrink_to_fit ( ) ;
BindDiscSetsToEntries ( ) ;
}
s_state . db_data = std : : move ( gamedb_data . value ( ) ) ;
s_state . disc_set_db_data = std : : move ( disc_set_data . value ( ) ) ;
return true ;
}
@ -1488,38 +1583,96 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
entry - > cpu_overclock = GetOptionalTFromObject < u8 > ( settings , " cpuOverclockPercent " ) ;
}
if ( const ryml : : ConstNodeRef disc_set = value . find_child ( " discSet " ) ; disc_set . valid ( ) & & disc_set . has_children ( ) )
return true ;
}
bool GameDatabase : : ParseYamlDiscSetEntry ( DiscSetEntry * entry , const ryml : : ConstNodeRef & value )
{
if ( ! GetStringFromObject ( value , " name " , & entry - > title ) | | entry - > title . empty ( ) )
{
GetStringFromObject ( disc_set , " name " , & entry - > disc_set_name ) ;
WARNING_LOG ( " Disc set entry is missing a title. " ) ;
return false ;
}
GetStringFromObject ( value , " sortName " , & entry - > sort_title ) ;
GetStringFromObject ( value , " localizedName " , & entry - > localized_title ) ;
GetStringFromObject ( value , " saveName " , & entry - > save_title ) ;
// ensure there are no duplicates, linear search is okay here because there aren't that many disc sets
const std : : string_view save_title = entry - > GetSaveTitle ( ) ;
const size_t search_count = s_state . disc_sets . size ( ) - 1 ;
for ( size_t i = 0 ; i < search_count ; i + + )
{
if ( s_state . disc_sets [ i ] . GetSaveTitle ( ) = = save_title )
{
WARNING_LOG ( " Duplicate disc set title {}. " , save_title ) ;
return false ;
}
}
if ( const ryml : : ConstNodeRef set_serials = disc_set . find_child ( " serials " ) ;
set_serials . valid ( ) & & set_serials . has_children ( ) )
if ( const ryml : : ConstNodeRef set_serials = value . find_child ( " serials " ) ;
set_serials . valid ( ) & & set_serials . has_children ( ) )
{
entry - > serials . reserve ( set_serials . num_children ( ) ) ;
for ( const ryml : : ConstNodeRef & serial : set_serials )
{
entry - > disc_set_serials . reserve ( set_serials . num_children ( ) ) ;
for ( const ryml : : ConstNodeRef & serial : set_serials )
const std : : string_view serial_str = to_stringview ( serial . val ( ) ) ;
if ( serial_str . empty ( ) )
{
const std : : string_view serial_str = to_stringview ( serial . val ( ) ) ;
if ( serial_str . empty ( ) )
{
WARNING_LOG ( " Empty disc set serial in {} " , entry - > serial ) ;
continue ;
}
WARNING_LOG ( " Empty disc set serial in {} " , entry - > title ) ;
continue ;
}
if ( std : : find ( entry - > disc_set_serials . begin ( ) , entry - > disc_set_serials . end ( ) , serial_str ) ! =
entry - > disc_set_serials . end ( ) )
{
WARNING_LOG ( " Duplicate serial {} in disc set serials for {} " , serial_str , entry - > serial ) ;
continue ;
}
if ( std : : find ( entry - > serials . begin ( ) , entry - > serials . end ( ) , serial_str ) ! = entry - > serials . end ( ) )
{
WARNING_LOG ( " Duplicate serial {} in disc set serials for {} " , serial_str , entry - > title ) ;
continue ;
}
entry - > disc_set_serials . emplace_back ( serial_str ) ;
const auto entry_it =
std : : lower_bound ( s_state . entries . begin ( ) , s_state . entries . end ( ) , serial_str ,
[ ] ( const Entry & entry , const std : : string_view & search ) { return ( entry . serial < search ) ; } ) ;
if ( entry_it = = s_state . entries . end ( ) | | entry_it - > serial ! = serial_str )
{
WARNING_LOG ( " Serial {} in disc set {} does not exist in game database. " , serial_str , entry - > title ) ;
continue ;
}
else if ( entry_it - > disc_set )
{
WARNING_LOG ( " Serial {} in disc set {} is already part of another disc set. " , serial_str , entry - > title ) ;
continue ;
}
entry - > serials . emplace_back ( serial_str ) ;
}
}
if ( entry - > serials . empty ( ) )
{
WARNING_LOG ( " Disc set {} has {} serials, dropping. " , entry - > title , entry - > serials . size ( ) ) ;
return false ;
}
return true ;
}
void GameDatabase : : BindDiscSetsToEntries ( )
{
for ( DiscSetEntry & dsentry : s_state . disc_sets )
{
for ( const std : : string_view & serial : dsentry . serials )
{
const auto entry_it =
std : : lower_bound ( s_state . entries . begin ( ) , s_state . entries . end ( ) , serial ,
[ ] ( const Entry & entry , const std : : string_view & search ) { return ( entry . serial < search ) ; } ) ;
Assert ( entry_it ! = s_state . entries . end ( ) & & entry_it - > serial = = serial ) ;
// link the entry to this disc set
entry_it - > disc_set = & dsentry ;
}
}
}
bool GameDatabase : : ParseYamlCodes ( PreferUnorderedStringMap < std : : string_view > & lookup , const ryml : : ConstNodeRef & value ,
std : : string_view serial )
{
@ -1564,10 +1717,10 @@ bool GameDatabase::ParseYamlCodes(PreferUnorderedStringMap<std::string_view>& lo
void GameDatabase : : EnsureTrackHashesMapLoaded ( )
{
if ( s_ track_hashes_loaded)
if ( s_ state. track_hashes_loaded)
return ;
s_ track_hashes_loaded = true ;
s_ state. track_hashes_loaded = true ;
LoadTrackHashes ( ) ;
}
@ -1587,7 +1740,7 @@ bool GameDatabase::LoadTrackHashes()
const ryml : : Tree tree = ryml : : parse_in_arena ( to_csubstr ( DISCDB_YAML_FILENAME ) , to_csubstr ( gamedb_data . value ( ) ) ) ;
const ryml : : ConstNodeRef root = tree . rootref ( ) ;
s_ track_hashes_map = { } ;
s_ state. track_hashes_map = { } ;
size_t serials = 0 ;
for ( const ryml : : ConstNodeRef & current : root . cchildren ( ) )
@ -1632,8 +1785,8 @@ bool GameDatabase::LoadTrackHashes()
const std : : optional < CDImageHasher : : Hash > md5o = CDImageHasher : : HashFromString ( md5_str ) ;
if ( md5o . has_value ( ) )
{
s_ track_hashes_map. emplace ( std : : piecewise_construct , std : : forward_as_tuple ( md5o . value ( ) ) ,
std : : forward_as_tuple ( std : : string ( serial ) , revision_string , revision ) ) ;
s_ state. track_hashes_map. emplace ( std : : piecewise_construct , std : : forward_as_tuple ( md5o . value ( ) ) ,
std : : forward_as_tuple ( std : : string ( serial ) , revision_string , revision ) ) ;
}
else
{
@ -1647,13 +1800,13 @@ bool GameDatabase::LoadTrackHashes()
}
ryml : : reset_callbacks ( ) ;
INFO_LOG ( " Loaded {} track hashes from {} serials in {:.0f}ms. " , s_ track_hashes_map. size ( ) , serials ,
INFO_LOG ( " Loaded {} track hashes from {} serials in {:.0f}ms. " , s_ state. track_hashes_map. size ( ) , serials ,
load_timer . GetTimeMilliseconds ( ) ) ;
return ! s_ track_hashes_map. empty ( ) ;
return ! s_ state. track_hashes_map. empty ( ) ;
}
const GameDatabase : : TrackHashesMap & GameDatabase : : GetTrackHashesMap ( )
{
EnsureTrackHashesMapLoaded ( ) ;
return s_ track_hashes_map;
return s_ state. track_hashes_map;
}