2020-06-02 10:39:24 +02:00
#include "bin_patch.h"
2017-03-29 02:54:05 +03:00
#include "File.h"
#include "Config.h"
2020-06-19 15:38:51 +02:00
#include "version.h"
2017-03-29 02:54:05 +03:00
2020-02-01 07:15:50 +03: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 02:54:05 +03: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 17:33:59 +03:00
case patch_type :: load : return "load" ;
2017-03-29 02:54:05 +03: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 16:34:04 +03:00
case patch_type :: bef32 : return "bef32" ;
case patch_type :: bef64 : return "bef64" ;
2017-03-29 02:54:05 +03:00
case patch_type :: be16 : return "be16" ;
case patch_type :: be32 : return "be32" ;
case patch_type :: be64 : return "be64" ;
2017-07-17 16:34:04 +03:00
case patch_type :: lef32 : return "lef32" ;
case patch_type :: lef64 : return "lef64" ;
2017-03-29 02:54:05 +03:00
}
return unknown ;
});
}
2020-06-02 10:39:24 +02:00
patch_engine :: patch_engine ()
2017-03-29 02:54:05 +03:00
{
2020-06-02 10:39:24 +02:00
const std :: string patches_path = fs :: get_config_dir () + "patches/" ;
2018-01-30 00:26:22 +03:00
2020-06-02 10:39:24 +02:00
if ( ! fs :: create_path ( patches_path ))
{
patch_log . fatal ( "Failed to create path: %s (%s)" , patches_path , fs :: g_tls_error );
}
}
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-19 19:47:51 +02:00
std :: string patch_engine :: get_imported_patch_path ()
{
return fs :: get_config_dir () + "patches/imported_patch.yml" ;
}
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-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-30 00:26:22 +03: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-30 00:26:22 +03:00
}
2017-03-29 02:54:05 +03: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 02:54:05 +03: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-30 00:26:22 +03: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 02:54:05 +03: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 02:54:05 +03: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 16:34:04 +03: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-07-17 16:34:04 +03: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-07-17 16:34:04 +03:00
}
2018-01-30 00:26:22 +03: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" ])
{
info . author = author_node . Scalar ();
}
2020-06-19 14:37:49 +02:00
if ( const auto patch_version_node = patches_entry . second [ "Patch Version" ])
2020-06-02 10:39:24 +02:00
{
info . patch_version = patch_version_node . Scalar ();
}
if ( const auto notes_node = patches_entry . second [ "Notes" ])
{
info . notes = notes_node . Scalar ();
}
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 ;
}
2020-06-02 10:39:24 +02: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 02:54:05 +03:00
}
}
}
2020-06-12 21:09:08 +02:00
return is_valid ;
2017-03-29 02:54:05 +03:00
}
2020-06-02 10:39:24 +02:00
patch_type patch_engine :: get_patch_type ( YAML :: Node node )
{
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 );
}
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 ];
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 )
2020-06-02 10:39:24 +02:00
{
// Special syntax: anchors (named sequence)
// Most legacy patches don't use the anchor syntax correctly, so try to sanitize it.
if ( info . is_legacy )
{
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
}
}
}
// Check if the anchor was resolved.
if ( const auto yml_type = addr_node . Type (); yml_type != YAML :: NodeType :: Sequence )
{
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 ;
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 )
{
2020-06-12 21:09:08 +02:00
if ( ! add_patch_data ( item , info , mod , 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
}
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 ;
2020-06-02 10:39:24 +02:00
}
2020-06-19 14:34:03 +02:00
default :
2020-06-02 10:39:24 +02:00
{
2020-06-19 14:34:03 +02:00
p_data . value . long_value = get_yaml_node_value < u64 > ( value_node , error_message );
break ;
}
}
if ( ! error_message . empty ())
{
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 ;
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 ;
2020-06-02 10:39:24 +02: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
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
load ( m_map , fs :: get_config_dir () + "patches/" + title_id + "_patch.yml" );
}
2017-07-17 16:34:04 +03:00
std :: size_t patch_engine :: apply ( const std :: string & name , u8 * dst ) const
2017-03-29 02:54:05 +03:00
{
2020-06-02 10:39:24 +02:00
return apply_patch < false > ( name , dst , 0 , 0 );
2017-03-29 02:54:05 +03:00
}
2020-01-07 04:10:23 -05:00
std :: size_t patch_engine :: apply_with_ls_check ( const std :: string & name , u8 * dst , u32 filesz , u32 ls_addr ) const
{
2020-06-02 10:39:24 +02:00
return apply_patch < true > ( name , dst , filesz , ls_addr );
}
2020-01-07 04:10:23 -05: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 04:10:23 -05: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-01-07 04:10:23 -05:00
2020-06-02 10:39:24 +02:00
// Apply modifications sequentially
2020-06-19 13:46:56 +02:00
for ( const auto & [ description , patch ] : container . patch_info_map )
2020-06-02 10:39:24 +02:00
{
if ( ! patch . enabled )
2020-01-07 04:10:23 -05:00
{
continue ;
}
2020-06-02 10:39:24 +02:00
size_t applied = 0 ;
for ( const auto & p : patch . data_list )
2020-01-07 04:10:23 -05: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 16:31:11 -04: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 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
if ( container . is_legacy )
2020-01-07 04:10:23 -05:00
{
2020-06-02 10:39:24 +02:00
patch_log . notice ( "Applied legacy patch (<- %d)" , applied );
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
else
2020-01-07 04:10:23 -05: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 04:10:23 -05: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 04:10:23 -05:00
{
2020-06-02 10:39:24 +02:00
continue ;
2020-01-07 04:10:23 -05: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 04:10:23 -05:00
{
2020-06-02 10:39:24 +02:00
config_map [ hash ][ description ] = patch . enabled ;
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
if ( config_map [ hash ]. size () > 0 )
2020-01-07 04:10:23 -05: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 04:10:23 -05: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 ;
if ( ! ok )
2020-06-12 21:09:08 +02:00
{
2020-06-19 15:38:51 +02:00
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
}
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-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-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
{
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 ;
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 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-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 04:10:23 -05: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 04:10:23 -05: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 04:10:23 -05: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 04:10:23 -05:00
}
}
2020-06-02 10:39:24 +02:00
return config_map ;
2020-01-07 04:10:23 -05:00
}