2020-12-25 03:18:36 +01: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"
2021-08-23 15:21:49 +02:00
# include "Emu/Memory/vm.h"
2020-06-26 02:58:06 +02:00
# include "Emu/System.h"
2017-03-29 01:54:05 +02:00
2020-12-13 14:34:45 +01:00
# include "util/types.hpp"
# include "util/endian.hpp"
2021-08-23 15:21:49 +02:00
# include "util/asm.hpp"
2020-12-13 14:34:45 +01:00
2021-09-06 09:33:44 +02:00
# include <charconv>
2020-11-07 18:21:23 +01:00
LOG_CHANNEL ( patch_log , " PAT " ) ;
2020-02-01 05:15:50 +01: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 " ;
2021-08-23 15:21:49 +02:00
case patch_type : : alloc : return " alloc " ;
2021-09-01 12:38:17 +02:00
case patch_type : : code_alloc : return " calloc " ;
case patch_type : : jump : return " jump " ;
2021-09-02 17:14:26 +02:00
case patch_type : : jump_link : return " jumpl " ;
2021-09-06 09:33:44 +02:00
case patch_type : : jump_func : return " jumpf " ;
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 " ;
2021-02-12 13:02:31 +01:00
case patch_type : : bd32 : return " bd32 " ;
2017-03-29 01:54:05 +02:00
case patch_type : : be64 : return " be64 " ;
2021-02-12 13:02:31 +01:00
case patch_type : : bd64 : return " bd64 " ;
2017-07-17 15:34:04 +02:00
case patch_type : : lef32 : return " lef32 " ;
case patch_type : : lef64 : return " lef64 " ;
2021-02-12 13:02:31 +01:00
case patch_type : : utf8 : return " utf8 " ;
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
}
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 ) )
{
2020-09-09 22:25:02 +02:00
patch_log . error ( " Could not create path: %s (%s) " , patch_path , fs : : g_tls_error ) ;
2020-06-23 21:31:32 +02:00
}
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
} ;
2020-09-06 11:47:45 +02:00
bool patch_engine : : load ( patch_map & patches_map , const std : : string & path , std : : string content , bool importing , std : : stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
2020-09-06 11:47:45 +02:00
if ( content . empty ( ) )
2020-06-02 10:39:24 +02:00
{
2020-09-06 11:47:45 +02:00
// Load patch file
fs : : file file { path } ;
if ( ! file )
{
// Do nothing
return true ;
}
content = file . to_string ( ) ;
2020-06-02 10:39:24 +02:00
}
// Interpret yaml nodes
2020-09-06 11:47:45 +02:00
auto [ root , error ] = yaml_load ( content ) ;
2020-06-02 10:39:24 +02:00
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-26 02:58:06 +02:00
patch_map patch_config ;
2020-06-12 21:09:08 +02:00
if ( ! importing )
{
2021-01-08 18:36:12 +01:00
patch_config = load_config ( ) ;
2020-06-12 21:09:08 +02:00
}
2020-06-02 10:39:24 +02:00
std : : string version ;
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
}
2021-01-08 18:36:12 +01:00
else
2020-06-12 21:09:08 +02:00
{
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
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 ( ) ;
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 ;
}
2021-09-02 20:48:56 +02:00
if ( main_key . empty ( ) )
{
append_log_message ( log_messages , " Error: Skipping empty key " ) ;
patch_log . error ( " Skipping empty key (file: %s) " , path ) ;
is_valid = false ;
continue ;
}
2020-06-02 10:39:24 +02:00
// 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 ] ;
2021-09-02 20:48:56 +02:00
container . hash = main_key ;
container . version = version ;
2020-06-26 02:58:06 +02:00
// 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
2021-09-02 20:48:56 +02:00
if ( title . empty ( ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Empty game title (key: %s, file: %s) " , main_key , path ) ) ;
patch_log . error ( " Empty game title (key: %s, file: %s) " , main_key , path ) ;
is_valid = false ;
continue ;
}
2020-06-26 02:58:06 +02:00
if ( const auto yml_type = game_node . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
{
2021-09-02 20:48:56 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping game %s: expected Map, found %s (patch: %s, key: %s) " , title , yml_type , description , main_key ) ) ;
patch_log . error ( " Skipping game %s: expected Map, found %s (patch: %s, key: %s, file: %s) " , title , yml_type , description , main_key , path ) ;
2020-06-26 02:58:06 +02:00
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 ( ) ;
2021-09-02 20:48:56 +02:00
if ( serial . empty ( ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Using empty serial (title: %s, patch: %s, key: %s) " , title , description , main_key ) ) ;
patch_log . error ( " Using empty serial (title: %s, patch: %s, key: %s, file: %s) " , title , description , main_key , path ) ;
is_valid = false ;
continue ;
}
else if ( serial = = patch_key : : all )
2020-06-27 15:24:34 +02:00
{
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
}
2021-09-02 20:48:56 +02:00
patch_type patch_engine : : get_patch_type ( const std : : string & text )
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
2021-09-02 20:48:56 +02:00
if ( ! cfg : : try_to_enum_value ( & type_val , & fmt_class_string < patch_type > : : format , text ) )
2020-06-13 02:16:28 +02:00
{
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
2021-09-02 20:48:56 +02:00
patch_type patch_engine : : get_patch_type ( YAML : : Node node )
{
if ( ! node | | ! node . IsScalar ( ) )
{
return patch_type : : invalid ;
}
return get_patch_type ( node . Scalar ( ) ) ;
}
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
// 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
{
2021-02-12 13:02:31 +01:00
case patch_type : : utf8 :
2021-09-06 09:33:44 +02:00
case patch_type : : jump_func :
2021-02-12 13:02:31 +01:00
{
break ;
}
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 ) ) ;
2020-12-09 16:04:52 +01:00
patch_log . error ( " Skipping invalid patch node %s. (key: %s) " , info . description , info . hash ) ;
2020-06-13 02:16:28 +02:00
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_global_patches ( )
{
2021-01-08 18:36:12 +01:00
// Regular 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 ;
}
2021-01-08 18:36:12 +01:00
// Regular 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
}
2021-08-23 15:21:49 +02:00
void ppu_register_range ( u32 addr , u32 size ) ;
2021-09-06 09:33:44 +02:00
bool ppu_form_branch_to_code ( u32 entry , u32 target , bool link = false , bool with_toc = false , std : : string module_name = { } ) ;
u32 ppu_generate_id ( std : : string_view name ) ;
2021-08-23 15:21:49 +02:00
2021-09-01 12:38:17 +02:00
void unmap_vm_area ( std : : shared_ptr < vm : : block_t > & ptr )
2020-06-28 14:19:44 +02:00
{
2021-09-01 12:38:17 +02:00
if ( ptr & & ptr - > flags & ( 1ull < < 62 ) )
{
2021-09-11 07:26:08 +02:00
vm : : unmap ( 0 , true , & ptr ) ;
2021-09-01 12:38:17 +02:00
}
}
// Returns old 'applied' size
static usz apply_modification ( std : : basic_string < u32 > & applied , const patch_engine : : patch_info & patch , u8 * dst , u32 filesz , u32 min_addr )
{
const usz old_applied_size = applied . size ( ) ;
2020-06-28 14:19:44 +02:00
2021-08-23 15:21:49 +02:00
for ( const auto & p : patch . data_list )
{
if ( p . type ! = patch_type : : alloc ) continue ;
// Do not allow null address or if dst is not a VM ptr
if ( const u32 alloc_at = vm : : try_get_addr ( dst + ( p . offset & - 4096 ) ) . first ; alloc_at > > 16 )
{
const u32 alloc_size = utils : : align ( static_cast < u32 > ( p . value . long_value ) + alloc_at % 4096 , 4096 ) ;
// Allocate map if needed, if allocated flags will indicate that bit 62 is set (unique identifier)
2021-12-21 21:25:23 +01:00
auto alloc_map = vm : : reserve_map ( vm : : any , alloc_at & - 0x10000 , utils : : align ( alloc_size , 0x10000 ) , vm : : page_size_64k | ( 1ull < < 62 ) ) ;
2021-08-23 15:21:49 +02:00
2021-09-01 21:52:50 +02:00
u64 flags = vm : : alloc_unwritable ;
2021-08-23 15:21:49 +02:00
switch ( p . offset % patch_engine : : mem_protection : : mask )
{
2021-09-01 21:52:50 +02:00
case patch_engine : : mem_protection : : wx : flags = vm : : alloc_executable ; break ;
2021-09-01 12:38:17 +02:00
case patch_engine : : mem_protection : : ro : break ;
2021-09-01 21:52:50 +02:00
case patch_engine : : mem_protection : : rx : flags | = vm : : alloc_executable ; break ;
case patch_engine : : mem_protection : : rw : flags & = ~ vm : : alloc_unwritable ; break ;
2021-08-23 15:21:49 +02:00
default : ensure ( false ) ;
}
if ( alloc_map )
{
2021-09-01 21:52:50 +02:00
if ( ( p . alloc_addr = alloc_map - > falloc ( alloc_at , alloc_size , nullptr , flags ) ) )
2021-08-23 15:21:49 +02:00
{
2021-09-01 21:52:50 +02:00
if ( flags & vm : : alloc_executable )
2021-08-23 15:21:49 +02:00
{
ppu_register_range ( alloc_at , alloc_size ) ;
}
applied . push_back ( : : narrow < u32 > ( & p - patch . data_list . data ( ) ) ) ; // Remember index in case of failure to allocate any memory
continue ;
}
// Revert if allocated map before failure
2021-09-01 12:38:17 +02:00
unmap_vm_area ( alloc_map ) ;
2021-08-23 15:21:49 +02:00
}
}
// Revert in case of failure
2021-09-01 12:38:17 +02:00
std : : for_each ( applied . begin ( ) + old_applied_size , applied . end ( ) , [ & ] ( u32 index )
2021-08-23 15:21:49 +02:00
{
2021-09-01 12:38:17 +02:00
const u32 addr = std : : exchange ( patch . data_list [ index ] . alloc_addr , 0 ) ;
2021-08-23 15:21:49 +02:00
2021-09-01 12:38:17 +02:00
vm : : dealloc ( addr ) ;
2021-08-23 15:21:49 +02:00
2021-09-01 12:38:17 +02:00
auto alloc_map = vm : : get ( vm : : any , addr ) ;
unmap_vm_area ( alloc_map ) ;
} ) ;
2021-08-23 15:21:49 +02:00
2021-09-01 12:38:17 +02:00
applied . resize ( old_applied_size ) ;
return old_applied_size ;
2021-08-23 15:21:49 +02:00
}
// Fixup values from before
2021-09-01 12:38:17 +02:00
std : : fill ( applied . begin ( ) + old_applied_size , applied . end ( ) , u32 { umax } ) ;
u32 relocate_instructions_at = 0 ;
2021-08-23 15:21:49 +02:00
2020-06-28 14:19:44 +02:00
for ( const auto & p : patch . data_list )
{
u32 offset = p . offset ;
2021-09-01 12:38:17 +02:00
if ( relocate_instructions_at & & vm : : read32 ( relocate_instructions_at ) ! = 0x6000'0000u )
{
// No longer points a NOP to be filled, meaning we ran out of instructions
relocate_instructions_at = 0 ;
}
if ( ! relocate_instructions_at & & ( offset < min_addr | | offset - min_addr > = filesz ) )
2020-06-28 14:19:44 +02:00
{
2021-02-08 16:04:50 +01:00
// This patch is out of range for this segment
continue ;
2020-06-28 14:19:44 +02:00
}
2021-02-08 16:04:50 +01:00
offset - = min_addr ;
2020-06-28 14:19:44 +02:00
auto ptr = dst + offset ;
2021-09-01 12:38:17 +02:00
if ( relocate_instructions_at )
{
offset = relocate_instructions_at ;
ptr = vm : : get_super_ptr < u8 > ( relocate_instructions_at ) ;
relocate_instructions_at + = 4 ; // Advance to the next instruction on dynamic memory
}
2021-04-28 23:13:12 +02:00
u32 resval = umax ;
2021-02-08 16:04:50 +01:00
2020-06-28 14:19:44 +02:00
switch ( p . type )
{
case patch_type : : invalid :
case patch_type : : load :
{
// Invalid in this context
continue ;
}
2021-08-23 15:21:49 +02:00
case patch_type : : alloc :
{
// Applied before
continue ;
}
2021-09-01 12:38:17 +02:00
case patch_type : : code_alloc :
{
relocate_instructions_at = 0 ;
const u32 out_branch = vm : : try_get_addr ( dst + ( offset & - 4 ) ) . first ;
// Allow only if points to a PPU executable instruction
2021-09-11 07:26:08 +02:00
if ( out_branch < 0x10000 | | out_branch > = 0x4000'0000 | | ! vm : : check_addr < 4 > ( out_branch , vm : : page_executable ) )
2021-09-01 12:38:17 +02:00
{
continue ;
}
const u32 alloc_size = utils : : align ( static_cast < u32 > ( p . value . long_value + 1 ) * 4 , 0x10000 ) ;
// Always executable
2021-09-01 21:52:50 +02:00
u64 flags = vm : : alloc_executable | vm : : alloc_unwritable ;
2021-09-01 12:38:17 +02:00
switch ( p . offset % patch_engine : : mem_protection : : mask )
{
case patch_engine : : mem_protection : : rw :
case patch_engine : : mem_protection : : wx :
{
2021-09-01 21:52:50 +02:00
flags & = ~ vm : : alloc_unwritable ;
2021-09-01 12:38:17 +02:00
break ;
}
case patch_engine : : mem_protection : : ro :
case patch_engine : : mem_protection : : rx :
{
break ;
}
default : ensure ( false ) ;
}
const auto alloc_map = ensure ( vm : : get ( vm : : any , out_branch ) ) ;
// Range allowed for absolute branches to operate at
// It takes into account that we need to put a branch for return at the end of memory space
const u32 addr = p . alloc_addr = alloc_map - > alloc ( alloc_size , nullptr , 0x10000 , flags ) ;
if ( ! addr )
{
patch_log . error ( " Failed to allocate 0x%x bytes for code (entry=0x%x) " , alloc_size , addr , out_branch ) ;
continue ;
}
patch_log . success ( " Allocated 0x%x for code at 0x%x (entry=0x%x) " , alloc_size , addr , out_branch ) ;
// NOP filled
std : : fill_n ( vm : : get_super_ptr < u32 > ( addr ) , p . value . long_value , 0x60000000 ) ;
// Register code
ppu_register_range ( addr , alloc_size ) ;
// Write branch to code
ppu_form_branch_to_code ( out_branch , addr ) ;
resval = out_branch & - 4 ;
// Write address of the allocated memory to the code entry
* vm : : get_super_ptr < u32 > ( resval ) = addr ;
2021-09-01 13:38:20 +02:00
2021-09-01 12:38:17 +02:00
// Write branch to return to code
ppu_form_branch_to_code ( addr + static_cast < u32 > ( p . value . long_value ) * 4 , resval + 4 ) ;
relocate_instructions_at = addr ;
break ;
}
case patch_type : : jump :
2021-09-02 17:14:26 +02:00
case patch_type : : jump_link :
2021-09-01 12:38:17 +02:00
{
const u32 out_branch = vm : : try_get_addr ( dst + ( offset & - 4 ) ) . first ;
const u32 dest = static_cast < u32 > ( p . value . long_value ) ;
// Allow only if points to a PPU executable instruction
2021-09-02 17:14:26 +02:00
if ( ! ppu_form_branch_to_code ( out_branch , dest , p . type = = patch_type : : jump_link ) )
2021-09-01 12:38:17 +02:00
{
continue ;
}
resval = out_branch & - 4 ;
break ;
}
2021-09-06 09:33:44 +02:00
case patch_type : : jump_func :
{
const std : : string & str = p . original_value ;
const u32 out_branch = vm : : try_get_addr ( dst + ( offset & - 4 ) ) . first ;
const usz sep_pos = str . find_first_of ( ' : ' ) ;
// Must contain only a single ':' or none
// If ':' is found: Left string is the module name, right string is the function name
// If ':' is not found: The entire string is a direct address of the function's descriptor in hexadecimal
if ( str . size ( ) < = 2 | | ! sep_pos | | sep_pos = = str . size ( ) - 1 | | sep_pos ! = str . find_last_of ( " : " ) )
{
continue ;
}
const std : : string_view func_name { std : : string_view ( str ) . substr ( sep_pos + 1 ) } ;
u32 id = 0 ;
if ( func_name . starts_with ( " 0x " sv ) )
{
// Raw hexadeciaml-formatted FNID (real function name cannot contain a digit at the start, derived from C/CPP which were used in PS3 development)
const auto result = std : : from_chars ( func_name . data ( ) + 2 , func_name . data ( ) + func_name . size ( ) - 2 , id , 16 ) ;
if ( result . ec ! = std : : errc ( ) | | str . data ( ) + sep_pos ! = result . ptr )
{
continue ;
}
}
else
{
if ( sep_pos = = umax )
{
continue ;
}
// Generate FNID using function name
id = ppu_generate_id ( func_name ) ;
}
// Allow only if points to a PPU executable instruction
// FNID/OPD-address is placed at target
if ( ! ppu_form_branch_to_code ( out_branch , id , true , true , std : : string { str . data ( ) , sep_pos ! = umax ? sep_pos : 0 } ) )
{
continue ;
}
resval = out_branch & - 4 ;
break ;
}
2020-06-28 14:19:44 +02:00
case patch_type : : byte :
{
* ptr = static_cast < u8 > ( p . value . long_value ) ;
break ;
}
case patch_type : : le16 :
{
2021-03-08 21:41:23 +01:00
le_t < u16 > val = static_cast < u16 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : le32 :
{
2021-03-08 21:41:23 +01:00
le_t < u32 > val = static_cast < u32 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : lef32 :
{
2021-03-08 21:41:23 +01:00
le_t < f32 > val = static_cast < f32 > ( p . value . double_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : le64 :
{
2021-03-08 21:41:23 +01:00
le_t < u64 > val = static_cast < u64 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : lef64 :
{
2021-03-08 21:41:23 +01:00
le_t < f64 > val = p . value . double_value ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : be16 :
{
2021-03-08 21:41:23 +01:00
be_t < u16 > val = static_cast < u16 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
2021-02-12 13:02:31 +01:00
case patch_type : : bd32 :
{
2021-03-08 21:41:23 +01:00
be_t < u32 > val = static_cast < u32 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2021-02-12 13:02:31 +01:00
break ;
}
2020-06-28 14:19:44 +02:00
case patch_type : : be32 :
{
2021-03-08 21:41:23 +01:00
be_t < u32 > val = static_cast < u32 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
if ( offset % 4 = = 0 )
resval = offset ;
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : bef32 :
{
2021-03-08 21:41:23 +01:00
be_t < f32 > val = static_cast < f32 > ( p . value . double_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
2021-02-12 13:02:31 +01:00
case patch_type : : bd64 :
{
2021-03-08 21:41:23 +01:00
be_t < u64 > val = static_cast < u64 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2021-02-12 13:02:31 +01:00
break ;
}
2020-06-28 14:19:44 +02:00
case patch_type : : be64 :
{
2021-03-08 21:41:23 +01:00
be_t < u64 > val = static_cast < u64 > ( p . value . long_value ) ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2021-02-08 16:04:50 +01:00
if ( offset % 4 )
{
break ;
}
resval = offset ;
applied . push_back ( ( offset + 7 ) & - 4 ) ; // Two 32-bit locations
2020-06-28 14:19:44 +02:00
break ;
}
case patch_type : : bef64 :
{
2021-03-08 21:41:23 +01:00
be_t < f64 > val = p . value . double_value ;
std : : memcpy ( ptr , & val , sizeof ( val ) ) ;
2020-06-28 14:19:44 +02:00
break ;
}
2021-02-12 13:02:31 +01:00
case patch_type : : utf8 :
{
std : : memcpy ( ptr , p . original_value . data ( ) , p . original_value . size ( ) ) ;
break ;
}
2020-06-28 14:19:44 +02:00
}
2021-02-08 16:04:50 +01:00
// Possibly an executable instruction
2021-02-02 17:18:50 +01:00
applied . push_back ( resval ) ;
2020-06-28 14:19:44 +02:00
}
2021-09-01 12:38:17 +02:00
return old_applied_size ;
2020-06-28 14:19:44 +02:00
}
2021-02-08 16:04:50 +01:00
std : : basic_string < u32 > patch_engine : : apply ( const std : : string & name , u8 * dst , u32 filesz , u32 min_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
{
2021-02-02 17:18:50 +01:00
return { } ;
2020-01-07 10:10:23 +01:00
}
2021-02-02 17:18:50 +01:00
std : : basic_string < u32 > applied_total ;
2020-06-19 13:46:56 +02:00
const auto & container = m_map . at ( name ) ;
2021-04-09 21:12:47 +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
2021-08-07 07:54:53 +02:00
std : : vector < const patch_info * > patches_for_this_serial_and_this_version ;
std : : vector < const patch_info * > patches_for_this_serial_and_all_versions ;
std : : vector < const patch_info * > patches_for_all_serials_and_this_version ;
std : : vector < const patch_info * > patches_for_all_serials_and_all_versions ;
2020-06-28 14:19:44 +02:00
// 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 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 )
{
2021-08-07 07:54:53 +02:00
patches_for_all_serials_and_all_versions . emplace_back ( & patch ) ;
2020-06-28 14:19:44 +02:00
}
else
{
2021-08-07 07:54:53 +02:00
patches_for_all_serials_and_this_version . emplace_back ( & patch ) ;
2020-06-28 14:19:44 +02:00
}
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
{
2021-08-07 07:54:53 +02:00
patches_for_this_serial_and_all_versions . emplace_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
{
2021-08-07 07:54:53 +02:00
patches_for_this_serial_and_this_version . emplace_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
// Apply modifications sequentially
2021-08-07 07:54:53 +02:00
auto apply_func = [ & ] ( const patch_info & patch )
2020-06-28 14:19:44 +02:00
{
2021-09-01 12:38:17 +02:00
const usz old_size = apply_modification ( applied_total , patch , dst , filesz , min_addr ) ;
2020-06-02 10:39:24 +02:00
2021-09-01 12:38:17 +02:00
if ( applied_total . size ( ) ! = old_size )
{
patch_log . success ( " Applied patch (hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') (<- %u) " , patch . hash , patch . description , patch . author , patch . patch_version , patch . version , applied_total . size ( ) - old_size ) ;
}
2021-08-07 07:54:53 +02:00
} ;
// Sort specific patches after global patches
// So they will determine the end results
const auto patch_super_list =
{
& patches_for_all_serials_and_all_versions ,
& patches_for_all_serials_and_this_version ,
& patches_for_this_serial_and_all_versions ,
& patches_for_this_serial_and_this_version
} ;
// Filter by patch group (reverse so specific patches will be prioritized over globals)
for ( auto it = std : : rbegin ( patch_super_list ) ; it ! = std : : rend ( patch_super_list ) ; it + + )
{
for ( auto & patch : * it . operator * ( ) )
{
if ( ! patch - > patch_group . empty ( ) )
{
if ( ! m_applied_groups . insert ( patch - > patch_group ) . second )
{
patch = nullptr ;
}
}
}
}
for ( auto patch_list : patch_super_list )
{
for ( const patch_info * patch : * patch_list )
{
if ( patch )
{
apply_func ( * patch ) ;
}
}
2020-06-02 10:39:24 +02:00
}
return applied_total ;
}
2021-09-01 12:38:17 +02:00
void patch_engine : : unload ( const std : : string & name )
{
if ( m_map . find ( name ) = = m_map . cend ( ) )
{
return ;
}
const auto & container = m_map . at ( name ) ;
for ( const auto & [ description , patch ] : container . patch_info_map )
{
2021-09-01 13:38:20 +02:00
for ( auto & entry : patch . data_list )
2021-09-01 12:38:17 +02:00
{
2021-09-01 13:38:20 +02:00
// Deallocate used memory
if ( u32 addr = std : : exchange ( entry . alloc_addr , 0 ) )
2021-09-01 12:38:17 +02:00
{
2021-09-01 13:38:20 +02:00
vm : : dealloc ( addr ) ;
2021-09-01 12:38:17 +02:00
2021-09-01 13:38:20 +02:00
auto alloc_map = vm : : get ( vm : : any , addr ) ;
unmap_vm_area ( alloc_map ) ;
2021-09-01 12:38:17 +02:00
}
}
}
}
2021-01-08 18:36:12 +01:00
void patch_engine : : save_config ( const patch_map & patches_map )
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 ) ;
YAML : : Emitter out ;
out < < YAML : : BeginMap ;
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
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
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
2021-04-09 21:12:47 +02:00
if ( const auto & enabled_patches = config_map [ hash ] . patch_info_map ; ! enabled_patches . empty ( ) )
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 ;
2021-10-09 19:56:50 +02:00
fs : : pending_file file ( path ) ;
if ( ! file . file | | ( file . file . write ( out . c_str ( ) , out . size ( ) ) , ! file . commit ( ) ) )
{
patch_log . error ( " Failed to create patch config file %s (%s) " , path , fs : : g_tls_error ) ;
}
2020-06-02 10:39:24 +02:00
}
2020-12-18 08:39:54 +01:00
static void append_patches ( patch_engine : : patch_map & existing_patches , const patch_engine : : patch_map & new_patches , usz & count , usz & 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 )
{
2020-09-09 22:25:02 +02:00
patch_log . fatal ( " save_patches: Failed to open patch file %s (%s) " , path , fs : : g_tls_error ) ;
append_log_message ( log_messages , fmt : : format ( " Failed to open patch file %s (%s) " , path , fs : : g_tls_error ) ) ;
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-12-18 08:39:54 +01:00
bool patch_engine : : import_patches ( const patch_engine : : patch_map & patches , const std : : string & path , usz & count , usz & total , std : : stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
patch_engine : : patch_map existing_patches ;
2020-09-06 11:47:45 +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 ;
}
2021-01-08 18:36:12 +01:00
patch_engine : : patch_map patch_engine : : load_config ( )
2020-06-02 10:39:24 +02:00
{
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-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
}