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"
2017-03-29 01:54:05 +02:00
2020-02-01 05:15:50 +01:00
LOG_CHANNEL ( patch_log ) ;
2020-06-19 13:46:56 +02:00
static const std : : string patch_engine_version = " 1.1 " ;
2020-06-11 15:12:44 +02:00
static const std : : string yml_key_enable_legacy_patches = " Enable Legacy Patches " ;
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-06-02 10:39:24 +02:00
const std : : string patches_path = fs : : get_config_dir ( ) + " patches/ " ;
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
return fs : : get_config_dir ( ) + " config/patch_config.yml " ;
# else
return fs : : get_config_dir ( ) + " patch_config.yml " ;
# endif
}
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-13 02:16:28 +02:00
append_log_message ( log_messages , fmt : : format ( " Reading file %s " , path ) ) ;
2020-06-12 21:09:08 +02:00
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-11 15:12:44 +02:00
bool enable_legacy_patches ;
2020-06-12 21:09:08 +02:00
patch_config_map patch_config ;
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 ;
if ( const auto version_node = root [ " Version " ] )
{
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-13 02:16:28 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Patch engine target version %s does not match file version %s " , patch_engine_version , version ) ) ;
2020-06-12 21:09:08 +02:00
patch_log . error ( " Patch engine target version %s does not match file version %s in %s " , patch_engine_version , version , path ) ;
return false ;
2018-01-29 22:26:22 +01:00
}
2017-03-29 01:54:05 +02:00
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , fmt : : format ( " Patch file version: %s " , version ) ) ;
2020-06-12 21:09:08 +02:00
2020-06-02 10:39:24 +02:00
// We don't need the Version node in local memory anymore
root . remove ( " Version " ) ;
}
2020-06-12 21:09:08 +02:00
else if ( importing )
{
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Patch engine target version %s does not match file version %s " , patch_engine_version , version ) ) ;
2020-06-12 21:09:08 +02:00
patch_log . error ( " Patch engine version %s: No 'Version' entry found for file %s " , patch_engine_version , path ) ;
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 ;
info . enabled = enable_legacy_patches ;
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
if ( main_key = = " Anchors " )
{
continue ;
}
if ( const auto patches_node = pair . second [ " Patches " ] )
{
if ( const auto yml_type = patches_node . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
2017-03-29 01:54:05 +02:00
{
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping Patches: expected Map, found %s (key: %s) " , yml_type , main_key ) ) ;
2020-06-02 10:39:24 +02:00
patch_log . error ( " Skipping Patches: expected Map, found %s (key: %s, file: %s) " , 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-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 . is_legacy = false ;
container . hash = main_key ;
container . version = version ;
2017-07-17 15:34:04 +02:00
2020-06-02 10:39:24 +02:00
// Go through each patch
for ( auto patches_entry : patches_node )
{
// Each key in "Patches" is also the patch description
const std : : string description = patches_entry . first . Scalar ( ) ;
// Find out if this patch was enabled in the patch config
const bool enabled = patch_config [ main_key ] [ description ] ;
// Compile patch information
if ( const auto yml_type = patches_entry . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
2017-09-17 16:33:59 +02:00
{
2020-06-13 02:16:28 +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 ) ) ;
2020-06-02 10:39:24 +02:00
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-09-17 16:33:59 +02:00
}
2020-06-02 10:39:24 +02:00
struct patch_info info { } ;
info . enabled = enabled ;
info . description = description ;
info . hash = main_key ;
info . version = version ;
2020-06-19 16:13:36 +02:00
info . source_path = path ;
2020-06-19 13:46:56 +02:00
2020-06-19 14:37:49 +02:00
if ( const auto title_node = patches_entry . second [ " Game Title " ] )
2020-06-19 13:46:56 +02:00
{
info . title = title_node . Scalar ( ) ;
}
if ( const auto serials_node = patches_entry . second [ " Serials " ] )
{
info . serials = serials_node . Scalar ( ) ;
}
2020-06-02 10:39:24 +02:00
if ( const auto author_node = patches_entry . second [ " Author " ] )
2017-07-17 15:34:04 +02:00
{
2020-06-02 10:39:24 +02:00
info . author = author_node . Scalar ( ) ;
2017-07-17 15:34:04 +02:00
}
2020-06-02 10:39:24 +02:00
2020-06-19 14:37:49 +02:00
if ( const auto patch_version_node = patches_entry . second [ " Patch Version " ] )
2017-07-17 15:34:04 +02:00
{
2020-06-02 10:39:24 +02:00
info . patch_version = patch_version_node . Scalar ( ) ;
2017-07-17 15:34:04 +02:00
}
2020-06-02 10:39:24 +02:00
if ( const auto notes_node = patches_entry . second [ " Notes " ] )
2017-07-17 15:34:04 +02:00
{
2020-06-02 10:39:24 +02:00
info . notes = notes_node . Scalar ( ) ;
2017-07-17 15:34:04 +02:00
}
2020-06-02 10:39:24 +02:00
if ( const auto patch_node = patches_entry . second [ " Patch " ] )
{
2020-06-12 21:09:08 +02:00
if ( ! read_patch_node ( info , patch_node , root , log_messages ) )
{
is_valid = false ;
}
2017-07-17 15:34:04 +02:00
}
2018-01-29 22:26:22 +01:00
2020-06-19 16:13:36 +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 )
{
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 ;
}
else if ( ! importing )
{
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-02 10:39:24 +02:00
// Insert patch information
2020-06-19 13:46:56 +02:00
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
load ( m_map , fs : : get_config_dir ( ) + " patches/patch.yml " ) ;
2020-06-12 21:09:08 +02:00
// Imported patch.yml
load ( m_map , fs : : get_config_dir ( ) + " patches/imported_patch.yml " ) ;
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
load ( m_map , fs : : get_config_dir ( ) + " patches/ " + title_id + " _patch.yml " ) ;
}
std : : size_t patch_engine : : apply ( const std : : string & name , u8 * dst ) const
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-02 10:39:24 +02:00
std : : size_t patch_engine : : apply_with_ls_check ( const std : : string & name , u8 * dst , u32 filesz , u32 ls_addr ) const
{
return apply_patch < true > ( name , dst , filesz , ls_addr ) ;
}
2020-01-07 10:10:23 +01:00
2020-06-02 10:39:24 +02:00
template < bool check_local_storage >
std : : size_t patch_engine : : apply_patch ( const std : : string & name , u8 * dst , u32 filesz , u32 ls_addr ) const
{
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-02 10:39:24 +02:00
2020-01-07 10:10:23 +01:00
// Apply modifications sequentially
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-02 10:39:24 +02:00
if ( ! patch . enabled )
2020-01-07 10:10:23 +01:00
{
continue ;
}
2020-06-02 10:39:24 +02:00
size_t applied = 0 ;
for ( const auto & p : patch . data_list )
2020-01-07 10:10:23 +01:00
{
2020-06-02 10:39:24 +02:00
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 ;
}
2020-06-13 22:31:11 +02:00
offset - = ls_addr ;
2020-06-02 10:39:24 +02:00
}
auto ptr = dst + offset ;
switch ( p . type )
{
2020-06-13 02:16:28 +02:00
case patch_type : : invalid :
2020-06-02 10:39:24 +02:00
case patch_type : : load :
{
// Invalid in this context
continue ;
}
case patch_type : : byte :
{
2020-06-12 21:09:08 +02:00
* ptr = static_cast < u8 > ( p . value . long_value ) ;
2020-06-02 10:39:24 +02:00
break ;
}
case patch_type : : le16 :
{
2020-06-12 21:09:08 +02:00
* reinterpret_cast < le_t < u16 , 1 > * > ( ptr ) = static_cast < u16 > ( p . value . long_value ) ;
2020-06-02 10:39:24 +02:00
break ;
}
case patch_type : : le32 :
2020-06-12 21:09:08 +02:00
{
* reinterpret_cast < le_t < u32 , 1 > * > ( ptr ) = static_cast < u32 > ( p . value . long_value ) ;
break ;
}
2020-06-02 10:39:24 +02:00
case patch_type : : lef32 :
{
2020-06-12 21:09:08 +02:00
* reinterpret_cast < le_t < u32 , 1 > * > ( ptr ) = std : : bit_cast < u32 , f32 > ( static_cast < f32 > ( p . value . double_value ) ) ;
2020-06-02 10:39:24 +02:00
break ;
}
case patch_type : : le64 :
2020-06-12 21:09:08 +02:00
{
* reinterpret_cast < le_t < u64 , 1 > * > ( ptr ) = static_cast < u64 > ( p . value . long_value ) ;
break ;
}
2020-06-02 10:39:24 +02:00
case patch_type : : lef64 :
{
2020-06-12 21:09:08 +02:00
* reinterpret_cast < le_t < u64 , 1 > * > ( ptr ) = std : : bit_cast < u64 , f64 > ( p . value . double_value ) ;
2020-06-02 10:39:24 +02:00
break ;
}
case patch_type : : be16 :
{
2020-06-12 21:09:08 +02:00
* reinterpret_cast < be_t < u16 , 1 > * > ( ptr ) = static_cast < u16 > ( p . value . long_value ) ;
2020-06-02 10:39:24 +02:00
break ;
}
case patch_type : : be32 :
2020-06-12 21:09:08 +02:00
{
* reinterpret_cast < be_t < u32 , 1 > * > ( ptr ) = static_cast < u32 > ( p . value . long_value ) ;
break ;
}
2020-06-02 10:39:24 +02:00
case patch_type : : bef32 :
{
2020-06-12 21:09:08 +02:00
* reinterpret_cast < be_t < u32 , 1 > * > ( ptr ) = std : : bit_cast < u32 , f32 > ( static_cast < f32 > ( p . value . double_value ) ) ;
2020-06-02 10:39:24 +02:00
break ;
}
case patch_type : : be64 :
2020-06-12 21:09:08 +02:00
{
* reinterpret_cast < be_t < u64 , 1 > * > ( ptr ) = static_cast < u64 > ( p . value . long_value ) ;
break ;
}
2020-06-02 10:39:24 +02:00
case patch_type : : bef64 :
{
2020-06-12 21:09:08 +02:00
* reinterpret_cast < be_t < u64 , 1 > * > ( ptr ) = std : : bit_cast < u64 , f64 > ( p . value . double_value ) ;
2020-06-02 10:39:24 +02:00
break ;
}
}
+ + applied ;
2020-01-07 10:10:23 +01:00
}
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
patch_log . notice ( " Applied legacy patch (<- %d) " , 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-06-02 10:39:24 +02:00
patch_log . notice ( " Applied patch (description='%s', author='%s', patch_version='%s', file_version='%s') (<- %d) " , 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
applied_total + = applied ;
}
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"
out < < yml_key_enable_legacy_patches < < enable_legacy_patches ;
// Save 'enabled' state per hash and description
2020-06-02 10:39:24 +02:00
patch_config_map config_map ;
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-02 10:39:24 +02:00
config_map [ hash ] [ description ] = patch . enabled ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
if ( config_map [ hash ] . size ( ) > 0 )
2020-01-07 10:10:23 +01:00
{
2020-06-02 10:39:24 +02:00
out < < hash ;
out < < YAML : : BeginMap ;
for ( const auto & [ description , enabled ] : config_map [ hash ] )
{
out < < description ;
out < < enabled ;
}
out < < YAML : : EndMap ;
2020-01-07 10:10:23 +01:00
}
2020-06-02 10:39:24 +02:00
}
out < < YAML : : EndMap ;
file . write ( out . c_str ( ) , out . size ( ) ) ;
}
2020-06-19 15:38:51 +02:00
static bool append_patches ( patch_engine : : patch_map & existing_patches , const patch_engine : : patch_map & new_patches , 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
{
if ( existing_patches . find ( hash ) = = existing_patches . end ( ) )
{
2020-06-19 13:46:56 +02:00
existing_patches [ hash ] = new_container ;
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-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-12 21:09:08 +02:00
return false ;
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 ) ) ;
return false ;
2020-06-12 21:09:08 +02:00
}
if ( ! new_info . patch_version . empty ( ) ) info . patch_version = new_info . patch_version ;
2020-06-19 13:46:56 +02:00
if ( ! new_info . title . empty ( ) ) info . title = new_info . title ;
if ( ! new_info . serials . empty ( ) ) info . serials = new_info . serials ;
2020-06-12 21:09:08 +02:00
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-12 21:09:08 +02:00
}
}
2020-06-19 15:38:51 +02:00
return true ;
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-19 13:46:56 +02:00
out < < " 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
{
out < < YAML : : Newline < < YAML : : Newline ;
out < < hash < < YAML : : BeginMap ;
out < < " Patches " < < YAML : : BeginMap ;
2020-06-19 13:46:56 +02:00
for ( auto [ description , info ] : container . patch_info_map )
2020-06-12 21:09:08 +02:00
{
out < < description ;
out < < YAML : : BeginMap ;
2020-06-19 14:37:49 +02:00
if ( ! info . title . empty ( ) ) out < < " Game Title " < < info . title ;
if ( ! info . serials . empty ( ) ) out < < " Serials " < < info . serials ;
if ( ! info . author . empty ( ) ) out < < " Author " < < info . author ;
if ( ! info . patch_version . empty ( ) ) out < < " Patch Version " < < info . patch_version ;
if ( ! info . notes . empty ( ) ) out < < " Notes " < < info . notes ;
2020-06-12 21:09:08 +02:00
out < < " Patch " ;
out < < YAML : : BeginSeq ;
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 ;
}
out < < YAML : : EndMap ;
file . write ( out . c_str ( ) , out . size ( ) ) ;
return true ;
}
2020-06-19 15:38:51 +02:00
bool patch_engine : : import_patches ( const patch_engine : : patch_map & patches , const std : : string & path , 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 15:38:51 +02:00
if ( append_patches ( existing_patches , patches , log_messages ) )
{
return save_patches ( existing_patches , path , log_messages ) ;
}
2020-06-12 21:09:08 +02:00
}
return false ;
}
2020-06-11 15:12:44 +02:00
patch_engine : : patch_config_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-02 10:39:24 +02:00
patch_config_map config_map ;
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)
if ( auto enable_legacy_node = root [ yml_key_enable_legacy_patches ] )
{
enable_legacy_patches = enable_legacy_node . as < bool > ( true ) ;
root . remove ( yml_key_enable_legacy_patches ) ; // Remove the node in order to skip it in the next part
}
2020-06-02 10:39:24 +02:00
for ( auto pair : root )
2020-01-07 10:10:23 +01:00
{
2020-06-02 10:39:24 +02:00
auto & hash = pair . first . Scalar ( ) ;
auto & data = config_map [ hash ] ;
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 ;
}
for ( auto patch : pair . second )
{
const auto description = patch . first . Scalar ( ) ;
const auto enabled = patch . second . as < bool > ( false ) ;
data [ description ] = enabled ;
}
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
}