2020-06-02 10:39:24 +02:00
# include " bin_patch.h "
2017-03-29 01:54:05 +02:00
# include "File.h"
# include "Config.h"
2020-06-19 15:38:51 +02:00
# include "version.h"
2020-06-26 02:58:06 +02:00
# include "Emu/System.h"
2017-03-29 01:54:05 +02:00
2020-02-01 05:15:50 +01:00
LOG_CHANNEL ( patch_log ) ;
2020-06-30 00:16:24 +02:00
namespace config_key
{
static const std : : string enable_legacy_patches = " Enable Legacy Patches " ;
}
2020-06-11 15:12:44 +02:00
2020-06-02 10:39:24 +02:00
template < >
void fmt_class_string < YAML : : NodeType : : value > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( YAML : : NodeType : : value value )
{
switch ( value )
{
case YAML : : NodeType : : Undefined : return " Undefined " ;
case YAML : : NodeType : : Null : return " Null " ;
case YAML : : NodeType : : Scalar : return " Scalar " ;
case YAML : : NodeType : : Sequence : return " Sequence " ;
case YAML : : NodeType : : Map : return " Map " ;
}
return unknown ;
} ) ;
}
2017-03-29 01:54:05 +02:00
template < >
void fmt_class_string < patch_type > : : format ( std : : string & out , u64 arg )
{
format_enum ( out , arg , [ ] ( patch_type value )
{
switch ( value )
{
2020-06-13 02:16:28 +02:00
case patch_type : : invalid : return " invalid " ;
2017-09-17 16:33:59 +02:00
case patch_type : : load : return " load " ;
2017-03-29 01:54:05 +02:00
case patch_type : : byte : return " byte " ;
case patch_type : : le16 : return " le16 " ;
case patch_type : : le32 : return " le32 " ;
case patch_type : : le64 : return " le64 " ;
2017-07-17 15:34:04 +02:00
case patch_type : : bef32 : return " bef32 " ;
case patch_type : : bef64 : return " bef64 " ;
2017-03-29 01:54:05 +02:00
case patch_type : : be16 : return " be16 " ;
case patch_type : : be32 : return " be32 " ;
case patch_type : : be64 : return " be64 " ;
2017-07-17 15:34:04 +02:00
case patch_type : : lef32 : return " lef32 " ;
case patch_type : : lef64 : return " lef64 " ;
2017-03-29 01:54:05 +02:00
}
return unknown ;
} ) ;
}
2020-06-02 10:39:24 +02:00
patch_engine : : patch_engine ( )
2017-03-29 01:54:05 +02:00
{
2020-07-22 22:02:07 +02:00
const std : : string patches_path = get_patches_path ( ) ;
2020-06-02 10:39:24 +02:00
if ( ! fs : : create_path ( patches_path ) )
2017-03-29 01:54:05 +02:00
{
2020-06-02 10:39:24 +02:00
patch_log . fatal ( " Failed to create path: %s (%s) " , patches_path , fs : : g_tls_error ) ;
}
}
2018-01-29 22:26:22 +01:00
2020-06-02 10:39:24 +02:00
std : : string patch_engine : : get_patch_config_path ( )
{
# ifdef _WIN32
2020-06-23 21:31:32 +02:00
const std : : string config_dir = fs : : get_config_dir ( ) + " config/ " ;
const std : : string patch_path = config_dir + " patch_config.yml " ;
if ( ! fs : : create_path ( config_dir ) )
{
patch_log . error ( " Could not create path: %s " , patch_path ) ;
}
return patch_path ;
2020-06-02 10:39:24 +02:00
# else
return fs : : get_config_dir ( ) + " patch_config.yml " ;
# endif
}
2020-06-30 21:35:15 +02:00
std : : string patch_engine : : get_patches_path ( )
{
return fs : : get_config_dir ( ) + " patches/ " ;
}
2020-06-19 19:47:51 +02:00
std : : string patch_engine : : get_imported_patch_path ( )
{
2020-07-22 22:02:07 +02:00
return get_patches_path ( ) + " imported_patch.yml " ;
2020-06-19 19:47:51 +02:00
}
2020-06-12 21:09:08 +02:00
static void append_log_message ( std : : stringstream * log_messages , const std : : string & message )
2020-06-02 10:39:24 +02:00
{
2020-06-12 21:09:08 +02:00
if ( log_messages )
2020-06-13 02:16:28 +02:00
* log_messages < < message < < std : : endl ;
2020-06-12 21:09:08 +02:00
} ;
bool patch_engine : : load ( patch_map & patches_map , const std : : string & path , bool importing , std : : stringstream * log_messages )
{
2020-06-02 10:39:24 +02:00
// Load patch file
fs : : file file { path } ;
if ( ! file )
{
// Do nothing
2020-06-12 21:09:08 +02:00
return true ;
2020-06-02 10:39:24 +02:00
}
// Interpret yaml nodes
auto [ root , error ] = yaml_load ( file . to_string ( ) ) ;
2020-06-13 02:16:28 +02:00
if ( ! error . empty ( ) | | ! root )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , " Fatal Error: Failed to load file! " ) ;
2020-06-02 10:39:24 +02:00
patch_log . fatal ( " Failed to load patch file %s: \n %s " , path , error ) ;
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
// Load patch config to determine which patches are enabled
2020-06-29 19:40:24 +02:00
bool enable_legacy_patches = false ;
2020-06-26 02:58:06 +02:00
patch_map patch_config ;
2020-06-12 21:09:08 +02:00
if ( ! importing )
{
patch_config = load_config ( enable_legacy_patches ) ;
}
2020-06-02 10:39:24 +02:00
std : : string version ;
bool is_legacy_patch = false ;
2020-06-30 00:16:24 +02:00
if ( const auto version_node = root [ patch_key : : version ] )
2020-06-02 10:39:24 +02:00
{
version = version_node . Scalar ( ) ;
2020-06-12 21:09:08 +02:00
if ( version ! = patch_engine_version )
2018-01-29 22:26:22 +01:00
{
2020-06-29 20:34:40 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: File version %s does not match patch engine target version %s (file: %s) " , version , patch_engine_version , path ) ) ;
2020-06-29 19:40:24 +02:00
patch_log . error ( " File version %s does not match patch engine target version %s (file: %s) " , version , patch_engine_version , path ) ;
2020-06-12 21:09:08 +02:00
return false ;
2018-01-29 22:26:22 +01:00
}
2017-03-29 01:54:05 +02:00
2020-06-02 10:39:24 +02:00
// We don't need the Version node in local memory anymore
2020-06-30 00:16:24 +02:00
root . remove ( patch_key : : version ) ;
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
else if ( importing )
{
2020-06-30 00:16:24 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: No '%s' entry found. Patch engine version = %s (file: %s) " , patch_key : : version , patch_engine_version , path ) ) ;
patch_log . error ( " No '%s' entry found. Patch engine version = %s (file: %s) " , patch_key : : version , patch_engine_version , path ) ;
2020-06-12 21:09:08 +02:00
return false ;
}
2020-06-02 10:39:24 +02:00
else
{
2020-06-12 21:09:08 +02:00
patch_log . warning ( " Patch engine version %s: Reading legacy patch file %s " , patch_engine_version , path ) ;
2020-06-02 10:39:24 +02:00
is_legacy_patch = true ;
}
2020-06-12 21:09:08 +02:00
bool is_valid = true ;
2020-06-02 10:39:24 +02:00
// Go through each main key in the file
for ( auto pair : root )
{
const auto & main_key = pair . first . Scalar ( ) ;
// Use old logic and yaml layout if this is a legacy patch
if ( is_legacy_patch )
2017-03-29 01:54:05 +02:00
{
2020-06-02 10:39:24 +02:00
struct patch_info info { } ;
2020-06-19 16:13:36 +02:00
info . hash = main_key ;
2020-06-26 02:58:06 +02:00
info . is_enabled = enable_legacy_patches ;
2020-06-19 16:13:36 +02:00
info . is_legacy = true ;
info . source_path = path ;
2018-01-29 22:26:22 +01:00
2020-06-12 21:09:08 +02:00
if ( ! read_patch_node ( info , pair . second , root , log_messages ) )
{
is_valid = false ;
}
2020-06-02 10:39:24 +02:00
// Find or create an entry matching the key/hash in our map
2020-06-19 13:46:56 +02:00
auto & container = patches_map [ main_key ] ;
container . hash = main_key ;
container . is_legacy = true ;
container . patch_info_map [ " legacy " ] = info ;
2020-06-02 10:39:24 +02:00
continue ;
}
// Use new logic and yaml layout
if ( const auto yml_type = pair . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping key %s: expected Map, found %s " , main_key , yml_type ) ) ;
2020-06-02 10:39:24 +02:00
patch_log . error ( " Skipping key %s: expected Map, found %s (file: %s) " , main_key , yml_type , path ) ;
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
}
// Skip Anchors
2020-06-30 00:16:24 +02:00
if ( main_key = = patch_key : : anchors )
2020-06-02 10:39:24 +02:00
{
continue ;
}
2020-06-26 02:58:06 +02:00
// Find or create an entry matching the key/hash in our map
auto & container = patches_map [ main_key ] ;
container . is_legacy = false ;
container . hash = main_key ;
container . version = version ;
// Go through each patch
for ( auto patches_entry : pair . second )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
// Each key in "Patches" is also the patch description
const std : : string & description = patches_entry . first . Scalar ( ) ;
// Compile patch information
if ( const auto yml_type = patches_entry . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
2017-03-29 01:54:05 +02:00
{
2020-06-26 02:58:06 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping Patch key %s: expected Map, found %s (key: %s) " , description , yml_type , main_key ) ) ;
patch_log . error ( " Skipping Patch key %s: expected Map, found %s (key: %s, file: %s) " , description , yml_type , main_key , path ) ;
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
}
2017-03-29 01:54:05 +02:00
2020-06-26 02:58:06 +02:00
struct patch_info info { } ;
info . description = description ;
info . hash = main_key ;
info . version = version ;
info . source_path = path ;
2017-07-17 15:34:04 +02:00
2020-06-30 00:16:24 +02:00
if ( const auto games_node = patches_entry . second [ patch_key : : games ] )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
if ( const auto yml_type = games_node . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
2017-09-17 16:33:59 +02:00
{
2020-06-26 02:58:06 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping Games key: expected Map, found %s (patch: %s, key: %s) " , yml_type , description , main_key ) ) ;
patch_log . error ( " Skipping Games key: expected Map, found %s (patch: %s, key: %s, file: %s) " , yml_type , description , main_key , path ) ;
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
2017-09-17 16:33:59 +02:00
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto game_node : games_node )
2020-06-19 13:46:56 +02:00
{
2020-06-26 02:58:06 +02:00
const std : : string & title = game_node . first . Scalar ( ) ;
2020-06-19 13:46:56 +02:00
2020-06-26 02:58:06 +02:00
if ( const auto yml_type = game_node . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
append_log_message ( log_messages , fmt : : format ( " Error: Skipping %s: expected Map, found %s (patch: %s, key: %s) " , title , yml_type , description , main_key ) ) ;
patch_log . error ( " Skipping %s: expected Map, found %s (patch: %s, key: %s, file: %s) " , title , yml_type , description , main_key , path ) ;
is_valid = false ;
continue ;
}
2020-06-02 10:39:24 +02:00
2020-06-27 15:24:34 +02:00
const bool title_is_all_key = title = = patch_key : : all ;
2020-06-26 02:58:06 +02:00
for ( const auto serial_node : game_node . second )
{
const std : : string & serial = serial_node . first . Scalar ( ) ;
2020-06-27 15:24:34 +02:00
if ( serial = = patch_key : : all )
{
if ( ! title_is_all_key )
{
append_log_message ( log_messages , fmt : : format ( " Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s) " , patch_key : : all , patch_key : : all , title , description , main_key ) ) ;
patch_log . error ( " Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s, file: %s) " , patch_key : : all , patch_key : : all , title , description , main_key , path ) ;
is_valid = false ;
continue ;
}
}
else if ( title_is_all_key )
{
append_log_message ( log_messages , fmt : : format ( " Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s) " , patch_key : : all , patch_key : : all , serial , description , main_key ) ) ;
patch_log . error ( " Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s, file: %s) " , patch_key : : all , patch_key : : all , serial , description , main_key , path ) ;
is_valid = false ;
continue ;
}
2020-06-26 02:58:06 +02:00
if ( const auto yml_type = serial_node . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Sequence )
{
append_log_message ( log_messages , fmt : : format ( " Error: Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s) " , serial , title , yml_type , description , main_key ) ) ;
patch_log . error ( " Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, file: %s) " , serial , title , yml_type , description , main_key , path ) ;
is_valid = false ;
continue ;
}
patch_engine : : patch_app_versions app_versions ;
for ( const auto version : serial_node . second )
{
const auto & app_version = version . Scalar ( ) ;
// Find out if this patch was enabled in the patch config
const bool enabled = patch_config [ main_key ] . patch_info_map [ description ] . titles [ title ] [ serial ] [ app_version ] ;
app_versions . emplace ( version . Scalar ( ) , enabled ) ;
}
if ( app_versions . empty ( ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Skipping %s: empty Sequence (title: %s, patch: %s, key: %s) " , serial , title , description , main_key ) ) ;
2020-06-29 13:53:39 +02:00
patch_log . error ( " Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, file: %s) " , serial , title , description , main_key , path ) ;
2020-06-26 02:58:06 +02:00
is_valid = false ;
}
else
{
info . titles [ title ] [ serial ] = app_versions ;
}
}
2017-07-17 15:34:04 +02:00
}
2020-06-26 02:58:06 +02:00
}
2020-06-02 10:39:24 +02:00
2020-06-30 00:16:24 +02:00
if ( const auto author_node = patches_entry . second [ patch_key : : author ] )
2020-06-26 02:58:06 +02:00
{
info . author = author_node . Scalar ( ) ;
}
2020-06-30 00:16:24 +02:00
if ( const auto patch_version_node = patches_entry . second [ patch_key : : patch_version ] )
2020-06-26 02:58:06 +02:00
{
info . patch_version = patch_version_node . Scalar ( ) ;
}
2020-06-30 00:16:24 +02:00
if ( const auto notes_node = patches_entry . second [ patch_key : : notes ] )
2020-06-26 02:58:06 +02:00
{
2020-06-29 13:53:39 +02:00
if ( notes_node . IsSequence ( ) )
{
for ( const auto note : notes_node )
{
if ( note & & note . IsScalar ( ) )
{
info . notes + = note . Scalar ( ) ;
}
else
{
append_log_message ( log_messages , fmt : : format ( " Error: Skipping sequenced Note (patch: %s, key: %s) " , description , main_key ) ) ;
patch_log . error ( " Skipping sequenced Note (patch: %s, key: %s, file: %s) " , description , main_key , path ) ;
is_valid = false ;
}
}
}
else
{
info . notes = notes_node . Scalar ( ) ;
}
2020-06-26 02:58:06 +02:00
}
2020-06-30 00:16:24 +02:00
if ( const auto patch_group_node = patches_entry . second [ patch_key : : group ] )
2020-06-26 02:58:06 +02:00
{
info . patch_group = patch_group_node . Scalar ( ) ;
}
2020-06-02 10:39:24 +02:00
2020-06-30 00:16:24 +02:00
if ( const auto patch_node = patches_entry . second [ patch_key : : patch ] )
2020-06-26 02:58:06 +02:00
{
if ( ! read_patch_node ( info , patch_node , root , log_messages ) )
2017-07-17 15:34:04 +02:00
{
2020-06-26 02:58:06 +02:00
is_valid = false ;
2017-07-17 15:34:04 +02:00
}
2020-06-26 02:58:06 +02:00
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
// Skip this patch if a higher patch version already exists
if ( container . patch_info_map . find ( description ) ! = container . patch_info_map . end ( ) )
{
bool ok ;
const auto existing_version = container . patch_info_map [ description ] . patch_version ;
const bool version_is_bigger = utils : : compare_versions ( info . patch_version , existing_version , ok ) > 0 ;
if ( ! ok | | ! version_is_bigger )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
patch_log . warning ( " A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s) " , info . patch_version , existing_version , main_key , description , path ) ;
append_log_message ( log_messages , fmt : : format ( " A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s) " , info . patch_version , existing_version , main_key , description , path ) ) ;
continue ;
2017-07-17 15:34:04 +02:00
}
2020-06-26 02:58:06 +02:00
else if ( ! importing )
2020-06-19 16:13:36 +02:00
{
2020-06-26 02:58:06 +02:00
patch_log . warning ( " A lower patch version was found ('%s' vs '%s') for %s: %s (in file %s) " , existing_version , info . patch_version , main_key , description , container . patch_info_map [ description ] . source_path ) ;
2020-06-19 16:13:36 +02:00
}
2017-03-29 01:54:05 +02:00
}
2020-06-26 02:58:06 +02:00
// Insert patch information
container . patch_info_map [ description ] = info ;
2017-03-29 01:54:05 +02:00
}
}
2020-06-12 21:09:08 +02:00
return is_valid ;
2017-03-29 01:54:05 +02:00
}
2020-06-02 10:39:24 +02:00
patch_type patch_engine : : get_patch_type ( YAML : : Node node )
2017-03-29 01:54:05 +02:00
{
2020-06-02 10:39:24 +02:00
u64 type_val = 0 ;
2020-06-13 02:16:28 +02:00
if ( ! node | | ! node . IsScalar ( ) | | ! cfg : : try_to_enum_value ( & type_val , & fmt_class_string < patch_type > : : format , node . Scalar ( ) ) )
{
return patch_type : : invalid ;
}
2020-06-02 10:39:24 +02:00
return static_cast < patch_type > ( type_val ) ;
}
2017-03-29 01:54:05 +02:00
2020-06-12 21:09:08 +02:00
bool patch_engine : : add_patch_data ( YAML : : Node node , patch_info & info , u32 modifier , const YAML : : Node & root , std : : stringstream * log_messages )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
if ( ! node | | ! node . IsSequence ( ) )
{
append_log_message ( log_messages , fmt : : format ( " Skipping invalid patch node %s. (key: %s) " , info . description , info . hash ) ) ;
patch_log . error ( " Skipping invalid patch node %s. (key: %s) " , info . description , info . hash ) ;
return false ;
}
2020-06-02 10:39:24 +02:00
const auto type_node = node [ 0 ] ;
auto addr_node = node [ 1 ] ;
const auto value_node = node [ 2 ] ;
2017-03-29 01:54:05 +02:00
2020-06-13 02:16:28 +02:00
const auto type = get_patch_type ( type_node ) ;
2020-06-02 10:39:24 +02:00
2020-06-13 02:16:28 +02:00
if ( type = = patch_type : : invalid )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
const auto type_str = type_node & & type_node . IsScalar ( ) ? type_node . Scalar ( ) : " " ;
append_log_message ( log_messages , fmt : : format ( " Skipping patch node %s: type '%s' is invalid. (key: %s) " , info . description , type_str , info . hash ) ) ;
patch_log . error ( " Skipping patch node %s: type '%s' is invalid. (key: %s) " , info . description , type_str , info . hash ) ;
return false ;
}
if ( type = = patch_type : : load )
2017-03-29 01:54:05 +02:00
{
2020-06-02 10:39:24 +02:00
// Special syntax: anchors (named sequence)
2017-03-29 01:54:05 +02:00
2020-06-02 10:39:24 +02:00
// Most legacy patches don't use the anchor syntax correctly, so try to sanitize it.
if ( info . is_legacy )
2017-03-29 01:54:05 +02:00
{
2020-06-02 10:39:24 +02:00
if ( const auto yml_type = addr_node . Type ( ) ; yml_type = = YAML : : NodeType : : Scalar )
{
2020-06-13 02:16:28 +02:00
if ( ! root )
{
patch_log . fatal ( " Trying to parse legacy patch with invalid root. " ) ; // Sanity Check
return false ;
}
2020-06-02 10:39:24 +02:00
const auto anchor = addr_node . Scalar ( ) ;
2020-06-11 21:49:32 +02:00
const auto anchor_node = root [ anchor ] ;
2020-06-02 10:39:24 +02:00
2020-06-11 21:49:32 +02:00
if ( anchor_node )
{
addr_node = anchor_node ;
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Incorrect anchor syntax found in legacy patch: %s (key: %s) " , anchor , info . hash ) ) ;
2020-06-11 21:49:32 +02:00
patch_log . warning ( " Incorrect anchor syntax found in legacy patch: %s (key: %s) " , anchor , info . hash ) ;
}
else
2020-06-02 10:39:24 +02:00
{
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Anchor not found in legacy patch: %s (key: %s) " , anchor , info . hash ) ) ;
2020-06-02 10:39:24 +02:00
patch_log . error ( " Anchor not found in legacy patch: %s (key: %s) " , anchor , info . hash ) ;
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
}
2017-03-29 01:54:05 +02:00
}
2020-06-02 10:39:24 +02:00
// Check if the anchor was resolved.
if ( const auto yml_type = addr_node . Type ( ) ; yml_type ! = YAML : : NodeType : : Sequence )
2017-03-29 01:54:05 +02:00
{
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping sequence: expected Sequence, found %s (key: %s) " , yml_type , info . hash ) ) ;
2020-06-02 10:39:24 +02:00
patch_log . error ( " Skipping sequence: expected Sequence, found %s (key: %s) " , yml_type , info . hash ) ;
2020-06-12 21:09:08 +02:00
return false ;
2017-03-29 01:54:05 +02:00
}
2020-06-02 10:39:24 +02:00
// Address modifier (optional)
const u32 mod = value_node . as < u32 > ( 0 ) ;
2020-06-12 21:09:08 +02:00
bool is_valid = true ;
2020-06-02 10:39:24 +02:00
for ( const auto & item : addr_node )
2017-03-29 01:54:05 +02:00
{
2020-06-12 21:09:08 +02:00
if ( ! add_patch_data ( item , info , mod , root , log_messages ) )
{
is_valid = false ;
}
2017-03-29 01:54:05 +02:00
}
2020-06-02 10:39:24 +02:00
2020-06-12 21:09:08 +02:00
return is_valid ;
2020-06-02 10:39:24 +02:00
}
2020-06-13 02:16:28 +02:00
struct patch_data p_data { } ;
2020-06-19 14:04:39 +02:00
p_data . type = type ;
p_data . offset = addr_node . as < u32 > ( 0 ) + modifier ;
p_data . original_value = value_node . IsScalar ( ) ? value_node . Scalar ( ) : " " ;
2020-06-13 02:16:28 +02:00
2020-06-19 14:34:03 +02:00
std : : string error_message ;
switch ( p_data . type )
2020-06-02 10:39:24 +02:00
{
2020-06-19 14:34:03 +02:00
case patch_type : : bef32 :
case patch_type : : lef32 :
case patch_type : : bef64 :
case patch_type : : lef64 :
{
p_data . value . double_value = get_yaml_node_value < f64 > ( value_node , error_message ) ;
break ;
}
default :
{
p_data . value . long_value = get_yaml_node_value < u64 > ( value_node , error_message ) ;
break ;
}
2020-06-02 10:39:24 +02:00
}
2020-06-19 14:34:03 +02:00
if ( ! error_message . empty ( ) )
2020-06-02 10:39:24 +02:00
{
2020-06-19 14:34:03 +02:00
error_message = fmt : : format ( " Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s) %s " ,
p_data . type , p_data . offset , p_data . original_value . empty ( ) ? " ? " : p_data . original_value , info . hash , error_message ) ;
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , error_message ) ;
patch_log . error ( " %s " , error_message ) ;
return false ;
2017-03-29 01:54:05 +02:00
}
2017-07-17 15:34:04 +02:00
2020-06-02 10:39:24 +02:00
info . data_list . emplace_back ( p_data ) ;
2020-06-12 21:09:08 +02:00
return true ;
2017-03-29 01:54:05 +02:00
}
2020-01-07 10:10:23 +01:00
2020-06-12 21:09:08 +02:00
bool patch_engine : : read_patch_node ( patch_info & info , YAML : : Node node , const YAML : : Node & root , std : : stringstream * log_messages )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
if ( ! node )
{
append_log_message ( log_messages , fmt : : format ( " Skipping invalid patch node %s. (key: %s) " , info . description , info . hash ) ) ;
patch_log . error ( " Skipping invalid patch node %s. (key: %s) " HERE , info . description , info . hash ) ;
return false ;
}
2020-06-02 10:39:24 +02:00
if ( const auto yml_type = node . Type ( ) ; yml_type ! = YAML : : NodeType : : Sequence )
{
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping patch node %s: expected Sequence, found %s (key: %s) " , info . description , yml_type , info . hash ) ) ;
2020-06-02 10:39:24 +02:00
patch_log . error ( " Skipping patch node %s: expected Sequence, found %s (key: %s) " , info . description , yml_type , info . hash ) ;
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
bool is_valid = true ;
2020-06-02 10:39:24 +02:00
for ( auto patch : node )
{
2020-06-12 21:09:08 +02:00
if ( ! add_patch_data ( patch , info , 0 , root , log_messages ) )
{
is_valid = false ;
}
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
return is_valid ;
2020-06-02 10:39:24 +02:00
}
void patch_engine : : append ( const std : : string & patch )
{
load ( m_map , patch ) ;
}
void patch_engine : : append_global_patches ( )
{
// Legacy patch.yml
load ( m_map , fs : : get_config_dir ( ) + " patch.yml " ) ;
// New patch.yml
2020-07-22 22:02:07 +02:00
load ( m_map , get_patches_path ( ) + " patch.yml " ) ;
2020-06-12 21:09:08 +02:00
// Imported patch.yml
2020-06-19 19:47:51 +02:00
load ( m_map , get_imported_patch_path ( ) ) ;
2020-06-02 10:39:24 +02:00
}
void patch_engine : : append_title_patches ( const std : : string & title_id )
{
if ( title_id . empty ( ) )
{
return ;
}
// Legacy patch.yml
load ( m_map , fs : : get_config_dir ( ) + " data/ " + title_id + " /patch.yml " ) ;
// New patch.yml
2020-07-22 22:02:07 +02:00
load ( m_map , get_patches_path ( ) + title_id + " _patch.yml " ) ;
2020-06-02 10:39:24 +02:00
}
2020-06-28 13:26:10 +02:00
std : : size_t patch_engine : : apply ( const std : : string & name , u8 * dst )
2020-01-07 10:10:23 +01:00
{
2020-06-02 10:39:24 +02:00
return apply_patch < false > ( name , dst , 0 , 0 ) ;
}
2020-01-07 10:10:23 +01:00
2020-06-28 13:26:10 +02:00
std : : size_t patch_engine : : apply_with_ls_check ( const std : : string & name , u8 * dst , u32 filesz , u32 ls_addr )
2020-06-02 10:39:24 +02:00
{
return apply_patch < true > ( name , dst , filesz , ls_addr ) ;
}
2020-01-07 10:10:23 +01:00
2020-06-28 14:19:44 +02:00
template < bool check_local_storage >
static std : : size_t apply_modification ( const patch_engine : : patch_info & patch , u8 * dst , u32 filesz , u32 ls_addr )
{
size_t applied = 0 ;
for ( const auto & p : patch . data_list )
{
u32 offset = p . offset ;
if constexpr ( check_local_storage )
{
if ( offset < ls_addr | | offset > = ( ls_addr + filesz ) )
{
// This patch is out of range for this segment
continue ;
}
offset - = ls_addr ;
}
auto ptr = dst + offset ;
switch ( p . type )
{
case patch_type : : invalid :
case patch_type : : load :
{
// Invalid in this context
continue ;
}
case patch_type : : byte :
{
* ptr = static_cast < u8 > ( p . value . long_value ) ;
break ;
}
case patch_type : : le16 :
{
* reinterpret_cast < le_t < u16 , 1 > * > ( ptr ) = static_cast < u16 > ( p . value . long_value ) ;
break ;
}
case patch_type : : le32 :
{
* reinterpret_cast < le_t < u32 , 1 > * > ( ptr ) = static_cast < u32 > ( p . value . long_value ) ;
break ;
}
case patch_type : : lef32 :
{
* reinterpret_cast < le_t < u32 , 1 > * > ( ptr ) = std : : bit_cast < u32 , f32 > ( static_cast < f32 > ( p . value . double_value ) ) ;
break ;
}
case patch_type : : le64 :
{
* reinterpret_cast < le_t < u64 , 1 > * > ( ptr ) = static_cast < u64 > ( p . value . long_value ) ;
break ;
}
case patch_type : : lef64 :
{
* reinterpret_cast < le_t < u64 , 1 > * > ( ptr ) = std : : bit_cast < u64 , f64 > ( p . value . double_value ) ;
break ;
}
case patch_type : : be16 :
{
* reinterpret_cast < be_t < u16 , 1 > * > ( ptr ) = static_cast < u16 > ( p . value . long_value ) ;
break ;
}
case patch_type : : be32 :
{
* reinterpret_cast < be_t < u32 , 1 > * > ( ptr ) = static_cast < u32 > ( p . value . long_value ) ;
break ;
}
case patch_type : : bef32 :
{
* reinterpret_cast < be_t < u32 , 1 > * > ( ptr ) = std : : bit_cast < u32 , f32 > ( static_cast < f32 > ( p . value . double_value ) ) ;
break ;
}
case patch_type : : be64 :
{
* reinterpret_cast < be_t < u64 , 1 > * > ( ptr ) = static_cast < u64 > ( p . value . long_value ) ;
break ;
}
case patch_type : : bef64 :
{
* reinterpret_cast < be_t < u64 , 1 > * > ( ptr ) = std : : bit_cast < u64 , f64 > ( p . value . double_value ) ;
break ;
}
}
+ + applied ;
}
return applied ;
}
2020-06-02 10:39:24 +02:00
template < bool check_local_storage >
2020-06-28 13:26:10 +02:00
std : : size_t patch_engine : : apply_patch ( const std : : string & name , u8 * dst , u32 filesz , u32 ls_addr )
2020-06-02 10:39:24 +02:00
{
if ( m_map . find ( name ) = = m_map . cend ( ) )
2020-01-07 10:10:23 +01:00
{
return 0 ;
}
2020-06-02 10:39:24 +02:00
size_t applied_total = 0 ;
2020-06-19 13:46:56 +02:00
const auto & container = m_map . at ( name ) ;
2020-06-26 02:58:06 +02:00
const auto serial = Emu . GetTitleID ( ) ;
const auto app_version = Emu . GetAppVersion ( ) ;
2020-06-02 10:39:24 +02:00
2020-06-28 14:19:44 +02:00
// Different containers in order to seperate the patches
2020-06-29 19:40:24 +02:00
std : : vector < patch_engine : : patch_info > legacy_patches ;
2020-06-28 14:19:44 +02:00
std : : vector < patch_engine : : patch_info > patches_for_this_serial_and_this_version ;
std : : vector < patch_engine : : patch_info > patches_for_this_serial_and_all_versions ;
std : : vector < patch_engine : : patch_info > patches_for_all_serials_and_this_version ;
std : : vector < patch_engine : : patch_info > patches_for_all_serials_and_all_versions ;
// Sort patches into different vectors based on their serial and version
2020-06-19 13:46:56 +02:00
for ( const auto & [ description , patch ] : container . patch_info_map )
2020-01-07 10:10:23 +01:00
{
2020-06-28 14:19:44 +02:00
// Find out if this legacy patch is enabled
2020-06-29 19:40:24 +02:00
if ( patch . is_legacy )
2020-06-26 02:58:06 +02:00
{
2020-06-29 19:40:24 +02:00
if ( patch . is_enabled )
{
legacy_patches . push_back ( patch ) ;
}
2020-06-26 02:58:06 +02:00
continue ;
}
2020-06-28 14:19:44 +02:00
// Find out if this patch is enabled
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : patch . titles )
{
2020-06-28 14:19:44 +02:00
bool is_all_serials = false ;
bool is_all_versions = false ;
2020-06-27 10:32:00 +02:00
std : : string found_serial ;
2020-06-27 14:14:08 +02:00
if ( serials . find ( serial ) ! = serials . end ( ) )
2020-06-27 10:32:00 +02:00
{
2020-06-27 14:14:08 +02:00
found_serial = serial ;
2020-06-27 10:32:00 +02:00
}
2020-06-27 14:14:08 +02:00
else if ( serials . find ( patch_key : : all ) ! = serials . end ( ) )
2020-06-26 02:58:06 +02:00
{
2020-06-27 14:14:08 +02:00
found_serial = patch_key : : all ;
2020-06-28 14:19:44 +02:00
is_all_serials = true ;
2020-06-27 10:32:00 +02:00
}
2020-06-28 14:19:44 +02:00
if ( found_serial . empty ( ) )
2020-06-27 10:32:00 +02:00
{
2020-06-28 14:19:44 +02:00
continue ;
}
const auto & app_versions = serials . at ( found_serial ) ;
std : : string found_app_version ;
2020-06-27 10:32:00 +02:00
2020-06-28 14:19:44 +02:00
if ( app_versions . find ( app_version ) ! = app_versions . end ( ) )
{
found_app_version = app_version ;
}
else if ( app_versions . find ( patch_key : : all ) ! = app_versions . end ( ) )
{
found_app_version = patch_key : : all ;
is_all_versions = true ;
}
if ( ! found_app_version . empty ( ) & & app_versions . at ( found_app_version ) )
{
// This patch is enabled
if ( is_all_serials )
2020-06-26 02:58:06 +02:00
{
2020-06-28 14:19:44 +02:00
if ( is_all_versions )
{
patches_for_all_serials_and_all_versions . push_back ( patch ) ;
}
else
{
patches_for_all_serials_and_this_version . push_back ( patch ) ;
}
2020-06-27 10:32:00 +02:00
}
2020-06-28 14:19:44 +02:00
else if ( is_all_versions )
2020-06-27 10:32:00 +02:00
{
2020-06-28 14:19:44 +02:00
patches_for_this_serial_and_all_versions . push_back ( patch ) ;
2020-06-27 10:32:00 +02:00
}
2020-06-28 14:19:44 +02:00
else
2020-06-27 10:32:00 +02:00
{
2020-06-28 14:19:44 +02:00
patches_for_this_serial_and_this_version . push_back ( patch ) ;
2020-06-26 02:58:06 +02:00
}
2020-06-28 14:19:44 +02:00
break ;
2020-06-26 02:58:06 +02:00
}
}
2020-06-28 14:19:44 +02:00
}
2020-06-26 02:58:06 +02:00
2020-06-28 14:19:44 +02:00
// Sort specific patches in front of global patches
std : : vector < patch_engine : : patch_info > sorted_patches ;
2020-06-29 19:40:24 +02:00
sorted_patches . insert ( sorted_patches . end ( ) , legacy_patches . begin ( ) , legacy_patches . end ( ) ) ;
2020-06-28 14:19:44 +02:00
sorted_patches . insert ( sorted_patches . end ( ) , patches_for_this_serial_and_this_version . begin ( ) , patches_for_this_serial_and_this_version . end ( ) ) ;
sorted_patches . insert ( sorted_patches . end ( ) , patches_for_this_serial_and_all_versions . begin ( ) , patches_for_this_serial_and_all_versions . end ( ) ) ;
sorted_patches . insert ( sorted_patches . end ( ) , patches_for_all_serials_and_this_version . begin ( ) , patches_for_all_serials_and_this_version . end ( ) ) ;
sorted_patches . insert ( sorted_patches . end ( ) , patches_for_all_serials_and_all_versions . begin ( ) , patches_for_all_serials_and_all_versions . end ( ) ) ;
2020-01-07 10:10:23 +01:00
2020-06-28 14:19:44 +02:00
// Apply modifications sequentially
for ( const auto & patch : sorted_patches )
{
2020-06-27 14:13:28 +02:00
if ( ! patch . patch_group . empty ( ) )
{
2020-06-28 13:26:10 +02:00
if ( m_applied_groups . contains ( patch . patch_group ) )
2020-06-27 14:13:28 +02:00
{
continue ;
}
2020-06-28 13:26:10 +02:00
m_applied_groups . insert ( patch . patch_group ) ;
2020-06-27 14:13:28 +02:00
}
2020-06-28 14:19:44 +02:00
const size_t applied = apply_modification < check_local_storage > ( patch , dst , filesz , ls_addr ) ;
applied_total + = applied ;
2020-06-02 10:39:24 +02:00
2020-06-28 14:19:44 +02:00
if ( patch . is_legacy )
2020-01-07 10:10:23 +01:00
{
2020-07-13 21:49:13 +02:00
patch_log . success ( " Applied legacy patch (hash='%s')(<- %d) " , patch . hash , applied ) ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
else
2020-01-07 10:10:23 +01:00
{
2020-07-13 21:49:13 +02:00
patch_log . success ( " Applied patch (hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') (<- %d) " , patch . hash , patch . description , patch . author , patch . patch_version , patch . version , applied ) ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
}
return applied_total ;
}
2020-06-11 15:12:44 +02:00
void patch_engine : : save_config ( const patch_map & patches_map , bool enable_legacy_patches )
2020-06-02 10:39:24 +02:00
{
const std : : string path = get_patch_config_path ( ) ;
patch_log . notice ( " Saving patch config file %s " , path ) ;
fs : : file file ( path , fs : : rewrite ) ;
if ( ! file )
{
patch_log . fatal ( " Failed to open patch config file %s " , path ) ;
return ;
}
YAML : : Emitter out ;
out < < YAML : : BeginMap ;
2020-06-11 15:12:44 +02:00
// Save "Enable Legacy Patches"
2020-06-30 00:16:24 +02:00
out < < config_key : : enable_legacy_patches < < enable_legacy_patches ;
2020-06-11 15:12:44 +02:00
2020-06-26 02:58:06 +02:00
// Save 'enabled' state per hash, description, serial and app_version
patch_map config_map ;
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , container ] : patches_map )
2020-06-02 10:39:24 +02:00
{
2020-06-19 13:46:56 +02:00
if ( container . is_legacy )
2020-01-07 10:10:23 +01:00
{
2020-06-02 10:39:24 +02:00
continue ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ description , patch ] : container . patch_info_map )
2020-01-07 10:10:23 +01:00
{
2020-06-26 02:58:06 +02:00
if ( patch . is_legacy )
{
continue ;
}
for ( const auto & [ title , serials ] : patch . titles )
{
for ( const auto & [ serial , app_versions ] : serials )
{
for ( const auto & [ app_version , enabled ] : app_versions )
{
if ( enabled )
{
config_map [ hash ] . patch_info_map [ description ] . titles [ title ] [ serial ] [ app_version ] = true ;
}
}
}
}
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
if ( const auto & enabled_patches = config_map [ hash ] . patch_info_map ; enabled_patches . size ( ) > 0 )
2020-01-07 10:10:23 +01:00
{
2020-06-26 02:58:06 +02:00
out < < hash < < YAML : : BeginMap ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
for ( const auto & [ description , patch ] : enabled_patches )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & titles = patch . titles ;
out < < description < < YAML : : BeginMap ;
for ( const auto & [ title , serials ] : titles )
{
out < < title < < YAML : : BeginMap ;
for ( const auto & [ serial , app_versions ] : serials )
{
out < < serial < < YAML : : BeginMap ;
for ( const auto & [ app_version , enabled ] : app_versions )
{
out < < app_version < < enabled ;
}
out < < YAML : : EndMap ;
}
out < < YAML : : EndMap ;
}
out < < YAML : : EndMap ;
2020-06-02 10:39:24 +02:00
}
out < < YAML : : EndMap ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
}
2020-06-26 02:58:06 +02:00
2020-06-02 10:39:24 +02:00
out < < YAML : : EndMap ;
file . write ( out . c_str ( ) , out . size ( ) ) ;
}
2020-06-19 21:48:59 +02:00
static void append_patches ( patch_engine : : patch_map & existing_patches , const patch_engine : : patch_map & new_patches , size_t & count , size_t & total , std : : stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , new_container ] : new_patches )
2020-06-12 21:09:08 +02:00
{
2020-06-19 21:48:59 +02:00
total + = new_container . patch_info_map . size ( ) ;
2020-06-12 21:09:08 +02:00
if ( existing_patches . find ( hash ) = = existing_patches . end ( ) )
{
2020-06-19 13:46:56 +02:00
existing_patches [ hash ] = new_container ;
2020-06-19 21:48:59 +02:00
count + = new_container . patch_info_map . size ( ) ;
2020-06-12 21:09:08 +02:00
continue ;
}
2020-06-19 13:46:56 +02:00
auto & container = existing_patches [ hash ] ;
2020-06-12 21:09:08 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ description , new_info ] : new_container . patch_info_map )
2020-06-12 21:09:08 +02:00
{
2020-06-19 13:46:56 +02:00
if ( container . patch_info_map . find ( description ) = = container . patch_info_map . end ( ) )
2020-06-12 21:09:08 +02:00
{
2020-06-19 13:46:56 +02:00
container . patch_info_map [ description ] = new_info ;
2020-06-19 21:48:59 +02:00
count + + ;
2020-06-12 21:09:08 +02:00
continue ;
}
2020-06-19 13:46:56 +02:00
auto & info = container . patch_info_map [ description ] ;
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
bool ok ;
const bool version_is_bigger = utils : : compare_versions ( new_info . patch_version , info . patch_version , ok ) > 0 ;
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
if ( ! ok )
{
patch_log . error ( " Failed to compare patch versions ('%s' vs '%s') for %s: %s " , new_info . patch_version , info . patch_version , hash , description ) ;
append_log_message ( log_messages , fmt : : format ( " Failed to compare patch versions ('%s' vs '%s') for %s: %s " , new_info . patch_version , info . patch_version , hash , description ) ) ;
2020-06-19 21:48:59 +02:00
continue ;
2020-06-19 15:38:51 +02:00
}
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
if ( ! version_is_bigger )
2020-06-12 21:09:08 +02:00
{
2020-06-19 15:38:51 +02:00
patch_log . error ( " A higher or equal patch version already exists ('%s' vs '%s') for %s: %s " , new_info . patch_version , info . patch_version , hash , description ) ;
append_log_message ( log_messages , fmt : : format ( " A higher or equal patch version already exists ('%s' vs '%s') for %s: %s " , new_info . patch_version , info . patch_version , hash , description ) ) ;
2020-06-19 21:48:59 +02:00
continue ;
2020-06-12 21:09:08 +02:00
}
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , new_serials ] : new_info . titles )
{
for ( const auto & [ serial , new_app_versions ] : new_serials )
{
if ( ! new_app_versions . empty ( ) )
{
info . titles [ title ] [ serial ] . insert ( new_app_versions . begin ( ) , new_app_versions . end ( ) ) ;
}
}
}
2020-06-12 21:09:08 +02:00
if ( ! new_info . patch_version . empty ( ) ) info . patch_version = new_info . patch_version ;
if ( ! new_info . author . empty ( ) ) info . author = new_info . author ;
if ( ! new_info . notes . empty ( ) ) info . notes = new_info . notes ;
if ( ! new_info . data_list . empty ( ) ) info . data_list = new_info . data_list ;
2020-06-19 16:13:36 +02:00
if ( ! new_info . source_path . empty ( ) ) info . source_path = new_info . source_path ;
2020-06-19 21:48:59 +02:00
count + + ;
2020-06-12 21:09:08 +02:00
}
}
}
2020-06-19 15:38:51 +02:00
bool patch_engine : : save_patches ( const patch_map & patches , const std : : string & path , std : : stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
fs : : file file ( path , fs : : rewrite ) ;
if ( ! file )
{
patch_log . fatal ( " save_patches: Failed to open patch file %s " , path ) ;
2020-06-19 15:38:51 +02:00
append_log_message ( log_messages , fmt : : format ( " Failed to open patch file %s " , path ) ) ;
2020-06-12 21:09:08 +02:00
return false ;
}
YAML : : Emitter out ;
out < < YAML : : BeginMap ;
2020-06-30 00:16:24 +02:00
out < < patch_key : : version < < patch_engine_version ;
2020-06-12 21:09:08 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , container ] : patches )
2020-06-12 21:09:08 +02:00
{
2020-06-19 19:47:51 +02:00
if ( container . patch_info_map . empty ( ) )
{
continue ;
}
2020-06-12 21:09:08 +02:00
out < < YAML : : Newline < < YAML : : Newline ;
out < < hash < < YAML : : BeginMap ;
2020-06-26 02:58:06 +02:00
for ( const auto & [ description , info ] : container . patch_info_map )
2020-06-12 21:09:08 +02:00
{
2020-06-26 02:58:06 +02:00
out < < description < < YAML : : BeginMap ;
2020-06-30 00:16:24 +02:00
out < < patch_key : : games < < YAML : : BeginMap ;
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : info . titles )
{
out < < title < < YAML : : BeginMap ;
for ( const auto & [ serial , app_versions ] : serials )
{
out < < serial < < YAML : : BeginSeq ;
2020-06-30 00:16:24 +02:00
for ( const auto & app_version : app_versions )
2020-06-26 02:58:06 +02:00
{
out < < app_version . first ;
}
out < < YAML : : EndSeq ;
}
out < < YAML : : EndMap ;
}
out < < YAML : : EndMap ;
2020-06-12 21:09:08 +02:00
2020-06-30 00:16:24 +02:00
if ( ! info . author . empty ( ) ) out < < patch_key : : author < < info . author ;
if ( ! info . patch_version . empty ( ) ) out < < patch_key : : patch_version < < info . patch_version ;
if ( ! info . patch_group . empty ( ) ) out < < patch_key : : group < < info . patch_group ;
if ( ! info . notes . empty ( ) ) out < < patch_key : : notes < < info . notes ;
2020-06-12 21:09:08 +02:00
2020-06-30 00:16:24 +02:00
out < < patch_key : : patch < < YAML : : BeginSeq ;
2020-06-12 21:09:08 +02:00
for ( const auto & data : info . data_list )
{
2020-06-13 02:16:28 +02:00
if ( data . type = = patch_type : : invalid | | data . type = = patch_type : : load )
2020-06-12 21:09:08 +02:00
{
// Unreachable with current logic
continue ;
}
out < < YAML : : Flow ;
out < < YAML : : BeginSeq ;
out < < fmt : : format ( " %s " , data . type ) ;
out < < fmt : : format ( " 0x%.8x " , data . offset ) ;
2020-06-19 14:04:39 +02:00
out < < data . original_value ;
2020-06-12 21:09:08 +02:00
out < < YAML : : EndSeq ;
}
out < < YAML : : EndSeq ;
out < < YAML : : EndMap ;
}
out < < YAML : : EndMap ;
}
out < < YAML : : EndMap ;
file . write ( out . c_str ( ) , out . size ( ) ) ;
return true ;
}
2020-06-19 21:48:59 +02:00
bool patch_engine : : import_patches ( const patch_engine : : patch_map & patches , const std : : string & path , size_t & count , size_t & total , std : : stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
patch_engine : : patch_map existing_patches ;
2020-06-19 15:38:51 +02:00
if ( load ( existing_patches , path , true , log_messages ) )
2020-06-12 21:09:08 +02:00
{
2020-06-19 21:48:59 +02:00
append_patches ( existing_patches , patches , count , total , log_messages ) ;
return count = = 0 | | save_patches ( existing_patches , path , log_messages ) ;
2020-06-12 21:09:08 +02:00
}
return false ;
}
2020-06-19 19:47:51 +02:00
bool patch_engine : : remove_patch ( const patch_info & info )
{
patch_engine : : patch_map patches ;
if ( load ( patches , info . source_path ) )
{
if ( patches . find ( info . hash ) ! = patches . end ( ) )
{
auto & container = patches [ info . hash ] ;
if ( container . patch_info_map . find ( info . description ) ! = container . patch_info_map . end ( ) )
{
container . patch_info_map . erase ( info . description ) ;
return save_patches ( patches , info . source_path ) ;
}
}
}
return false ;
}
2020-06-26 02:58:06 +02:00
patch_engine : : patch_map patch_engine : : load_config ( bool & enable_legacy_patches )
2020-06-02 10:39:24 +02:00
{
2020-06-11 15:12:44 +02:00
enable_legacy_patches = true ; // Default to true
2020-06-26 02:58:06 +02:00
patch_map config_map ;
2020-06-02 10:39:24 +02:00
const std : : string path = get_patch_config_path ( ) ;
patch_log . notice ( " Loading patch config file %s " , path ) ;
if ( fs : : file f { path } )
{
auto [ root , error ] = yaml_load ( f . to_string ( ) ) ;
if ( ! error . empty ( ) )
2020-01-07 10:10:23 +01:00
{
2020-06-02 10:39:24 +02:00
patch_log . fatal ( " Failed to load patch config file %s: \n %s " , path , error ) ;
return config_map ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
2020-06-11 15:12:44 +02:00
// Try to load "Enable Legacy Patches" (default to true)
2020-06-30 00:16:24 +02:00
if ( auto enable_legacy_node = root [ config_key : : enable_legacy_patches ] )
2020-06-11 15:12:44 +02:00
{
enable_legacy_patches = enable_legacy_node . as < bool > ( true ) ;
2020-06-30 00:16:24 +02:00
root . remove ( config_key : : enable_legacy_patches ) ; // Remove the node in order to skip it in the next part
2020-06-11 15:12:44 +02:00
}
2020-06-26 02:58:06 +02:00
for ( const auto pair : root )
2020-01-07 10:10:23 +01:00
{
2020-06-26 02:58:06 +02:00
const auto & hash = pair . first . Scalar ( ) ;
2020-06-02 10:39:24 +02:00
if ( const auto yml_type = pair . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
patch_log . error ( " Error loading patch config key %s: expected Map, found %s (file: %s) " , hash , yml_type , path ) ;
continue ;
}
2020-06-26 02:58:06 +02:00
for ( const auto patch : pair . second )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & description = patch . first . Scalar ( ) ;
if ( const auto yml_type = patch . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
patch_log . error ( " Error loading patch %s: expected Map, found %s (hash: %s, file: %s) " , description , yml_type , hash , path ) ;
continue ;
}
for ( const auto title_node : patch . second )
{
const auto & title = title_node . first . Scalar ( ) ;
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
if ( const auto yml_type = title_node . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
patch_log . error ( " Error loading %s: expected Map, found %s (description: %s, hash: %s, file: %s) " , title , yml_type , description , hash , path ) ;
continue ;
}
for ( const auto serial_node : title_node . second )
{
const auto & serial = serial_node . first . Scalar ( ) ;
if ( const auto yml_type = serial_node . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
patch_log . error ( " Error loading %s: expected Map, found %s (title: %s, description: %s, hash: %s, file: %s) " , serial , yml_type , title , description , hash , path ) ;
continue ;
}
for ( const auto app_version_node : serial_node . second )
{
const auto & app_version = app_version_node . first . Scalar ( ) ;
const bool enabled = app_version_node . second . as < bool > ( false ) ;
config_map [ hash ] . patch_info_map [ description ] . titles [ title ] [ serial ] [ app_version ] = enabled ;
}
}
}
2020-06-02 10:39:24 +02:00
}
2020-01-07 10:10:23 +01:00
}
}
2020-06-02 10:39:24 +02:00
return config_map ;
2020-01-07 10:10:23 +01:00
}