2020-12-25 03:18:36 +01: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"
2021-08-23 16:21:49 +03:00
#include "Emu/Memory/vm.h"
2020-06-26 02:58:06 +02:00
#include "Emu/System.h"
2017-03-29 02:54:05 +03:00
2020-12-13 16:34:45 +03:00
#include "util/types.hpp"
#include "util/endian.hpp"
2021-08-23 16:21:49 +03:00
#include "util/asm.hpp"
2020-12-13 16:34:45 +03:00
2021-09-06 10:33:44 +03:00
#include <charconv>
2020-11-07 18:21:23 +01:00
LOG_CHANNEL ( patch_log , "PAT" );
2020-02-01 07:15:50 +03: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 ;
});
}
2023-02-18 23:57:23 +01:00
template <>
void fmt_class_string < patch_dynamic_type >:: format ( std :: string & out , u64 arg )
{
format_enum ( out , arg , []( patch_dynamic_type value )
{
switch ( value )
{
case patch_dynamic_type :: double_range : return "double_range" ;
case patch_dynamic_type :: double_enum : return "double_enum" ;
case patch_dynamic_type :: long_range : return "long_range" ;
case patch_dynamic_type :: long_enum : return "long_enum" ;
}
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" ;
2021-08-23 16:21:49 +03:00
case patch_type :: alloc : return "alloc" ;
2021-09-01 13:38:17 +03:00
case patch_type :: code_alloc : return "calloc" ;
case patch_type :: jump : return "jump" ;
2021-09-02 18:14:26 +03:00
case patch_type :: jump_link : return "jumpl" ;
2021-09-06 10:33:44 +03:00
case patch_type :: jump_func : return "jumpf" ;
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" ;
2021-02-12 15:02:31 +03:00
case patch_type :: bd32 : return "bd32" ;
2017-03-29 02:54:05 +03:00
case patch_type :: be64 : return "be64" ;
2021-02-12 15:02:31 +03:00
case patch_type :: bd64 : return "bd64" ;
2017-07-17 16:34:04 +03:00
case patch_type :: lef32 : return "lef32" ;
case patch_type :: lef64 : return "lef64" ;
2021-02-12 15:02:31 +03:00
case patch_type :: utf8 : return "utf8" ;
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
}
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
}
2023-02-18 16:40:12 +01:00
static void append_log_message ( std :: stringstream * log_messages , const std :: string & message , const logs :: message * channel = nullptr )
2020-06-02 10:39:24 +02:00
{
2023-02-18 16:40:12 +01:00
if ( channel )
{
channel -> operator ()( "%s" , message );
}
if ( log_messages && ! message . empty ())
{
2020-06-13 02:16:28 +02:00
* log_messages << message << std :: endl ;
2023-02-18 16:40:12 +01:00
}
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-30 00:26:22 +03:00
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: File version %s does not match patch engine target version %s (location: %s, file: %s)" , version , patch_engine_version , get_yaml_node_location ( version_node ), path ), & patch_log . error );
2020-06-12 21:09:08 +02:00
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
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
{
2023-02-18 16:40:12 +01: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 );
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 )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping key %s: expected Map, found %s (location: %s, file: %s)" , main_key , yml_type , get_yaml_node_location ( pair . second ), path ), & patch_log . error );
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 ())
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping empty key (location: %s, file: %s)" , get_yaml_node_location ( pair . second ), path ), & patch_log . error );
2021-09-02 20:48:56 +02:00
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 02:54:05 +03:00
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping Patch key %s: expected Map, found %s (key: %s, location: %s, file: %s)" , description , yml_type , main_key , get_yaml_node_location ( patches_entry . second ), path ), & patch_log . error );
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-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 16:34:04 +03: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-07-17 16:34:04 +03:00
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping Games key: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)" , yml_type , description , main_key , get_yaml_node_location ( games_node ), path ), & patch_log . error );
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-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 ())
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Empty game title (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( game_node ), path ), & patch_log . error );
2021-09-02 20:48:56 +02:00
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 )
2020-06-12 21:09:08 +02:00
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping game %s: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)" , title , yml_type , description , main_key , get_yaml_node_location ( game_node ), path ), & patch_log . error );
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-19 16:13:36 +02:00
continue ;
}
2020-06-26 02:58:06 +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 )
2020-06-19 16:13:36 +02:00
{
2020-06-26 02:58:06 +02:00
const std :: string & serial = serial_node . first . Scalar ();
2021-09-02 20:48:56 +02:00
if ( serial . empty ())
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Using empty serial (title: %s, patch: %s, key: %s, location: %s, file: %s)" , title , description , main_key , get_yaml_node_location ( serial_node ), path ), & patch_log . error );
2021-09-02 20:48:56 +02:00
is_valid = false ;
continue ;
}
2023-02-18 16:40:12 +01:00
if ( serial == patch_key :: all )
2020-06-27 15:24:34 +02:00
{
if ( ! title_is_all_key )
{
2023-02-18 16:40:12 +01:00
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, location: %s, file: %s)" , patch_key :: all , patch_key :: all , title , description , main_key , get_yaml_node_location ( serial_node ), path ), & patch_log . error );
2020-06-27 15:24:34 +02:00
is_valid = false ;
continue ;
}
}
else if ( title_is_all_key )
{
2023-02-18 16:40:12 +01:00
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, location: %s, file: %s)" , patch_key :: all , patch_key :: all , serial , description , main_key , get_yaml_node_location ( serial_node ), path ), & patch_log . error );
2020-06-27 15:24:34 +02:00
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 )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, location: %s, file: %s)" , serial , title , yml_type , description , main_key , get_yaml_node_location ( serial_node ), path ), & patch_log . error );
2020-06-26 02:58:06 +02:00
is_valid = false ;
continue ;
}
patch_engine :: patch_app_versions app_versions ;
for ( const auto version : serial_node . second )
{
2023-02-18 16:40:12 +01:00
const std :: string & app_version = version . Scalar ();
2020-06-26 02:58:06 +02:00
2023-02-18 16:40:12 +01:00
// Get this patch's config values
const patch_config_values & config_values = patch_config [ main_key ]. patch_info_map [ description ]. titles [ title ][ serial ][ app_version ];
2020-06-26 02:58:06 +02:00
2023-02-18 16:40:12 +01:00
app_versions [ version . Scalar ()] = config_values ;
2020-06-26 02:58:06 +02:00
}
if ( app_versions . empty ())
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, location: %s, file: %s)" , serial , title , description , main_key , get_yaml_node_location ( serial_node ), path ), & patch_log . error );
2020-06-26 02:58:06 +02:00
is_valid = false ;
}
else
{
info . titles [ title ][ serial ] = app_versions ;
}
2020-06-19 16:13:36 +02:00
}
}
2017-03-29 02:54:05 +03:00
}
2020-06-26 02:58:06 +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
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping sequenced Note (patch: %s, key: %s, location: %s, file: %s)" , description , main_key , get_yaml_node_location ( note ), path ), & patch_log . error );
2020-06-29 13:53:39 +02:00
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 ();
}
2023-02-18 16:40:12 +01:00
if ( const auto dynamic_values_node = patches_entry . second [ patch_key :: dynamic_values ])
{
if ( const auto yml_type = dynamic_values_node . Type (); yml_type != YAML :: NodeType :: Map || dynamic_values_node . size () == 0 )
{
append_log_message ( log_messages , fmt :: format ( "Error: Skipping dynamic values: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)" , yml_type , description , main_key , get_yaml_node_location ( dynamic_values_node ), path ), & patch_log . error );
is_valid = false ;
}
else
{
for ( const auto dynamic_value_node : dynamic_values_node )
{
2023-02-18 23:57:23 +01:00
const std :: string & value_key = dynamic_value_node . first . Scalar ();
if ( const auto yml_type = dynamic_value_node . second . Type (); yml_type != YAML :: NodeType :: Map )
2023-02-18 16:40:12 +01:00
{
2023-02-18 23:57:23 +01:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping dynamic value %s: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s)" , value_key , yml_type , description , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
2023-02-18 16:40:12 +01:00
}
else
{
2023-02-18 23:57:23 +01:00
patch_dynamic_value & dynamic_value = info . default_dynamic_values [ value_key ];
if ( const auto dynamic_value_type_node = dynamic_value_node . second [ patch_key :: type ]; dynamic_value_type_node && dynamic_value_type_node . IsScalar ())
{
const std :: string & str_type = dynamic_value_type_node . Scalar ();
bool is_valid_type = false ;
for ( patch_dynamic_type type : { patch_dynamic_type :: double_range , patch_dynamic_type :: double_enum , patch_dynamic_type :: long_range , patch_dynamic_type :: long_enum })
{
if ( str_type == fmt :: format ( "%s" , type ))
{
dynamic_value . type = type ;
is_valid_type = true ;
break ;
}
}
if ( is_valid_type )
{
const auto get_and_check_dynamic_value = [ & ]( const YAML :: Node & node )
{
std :: string err ;
f64 val {};
switch ( dynamic_value . type )
{
case patch_dynamic_type :: double_range :
case patch_dynamic_type :: double_enum :
val = get_yaml_node_value < f64 > ( node , err );
break ;
case patch_dynamic_type :: long_range :
case patch_dynamic_type :: long_enum :
2023-02-19 10:58:16 +01:00
val = static_cast < f64 > ( get_yaml_node_value < s64 > ( node , err ));
2023-02-18 23:57:23 +01:00
break ;
}
if ( ! err . empty ())
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid data type found in dynamic value: %s (key: %s, location: %s, file: %s)" , err , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
return val ;
};
if ( const auto dynamic_value_value_node = dynamic_value_node . second [ patch_key :: value ]; dynamic_value_value_node && dynamic_value_value_node . IsScalar ())
{
dynamic_value . value = get_and_check_dynamic_value ( dynamic_value_value_node );
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid or missing dynamic value (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
switch ( dynamic_value . type )
{
case patch_dynamic_type :: double_range :
case patch_dynamic_type :: long_range :
{
if ( const auto dynamic_value_min_node = dynamic_value_node . second [ patch_key :: min ]; dynamic_value_min_node && dynamic_value_min_node . IsScalar ())
{
dynamic_value . min = get_and_check_dynamic_value ( dynamic_value_min_node );
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid or missing dynamic min (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
if ( const auto dynamic_value_max_node = dynamic_value_node . second [ patch_key :: max ]; dynamic_value_max_node && dynamic_value_max_node . IsScalar ())
{
dynamic_value . max = get_and_check_dynamic_value ( dynamic_value_max_node );
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid or missing dynamic max (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
if ( dynamic_value . min >= dynamic_value . max )
{
append_log_message ( log_messages , fmt :: format ( "Error: dynamic max has to be larger than dynamic min (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
if ( dynamic_value . value < dynamic_value . min || dynamic_value . value > dynamic_value . max )
{
append_log_message ( log_messages , fmt :: format ( "Error: dynamic value out of range (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
break ;
}
case patch_dynamic_type :: double_enum :
case patch_dynamic_type :: long_enum :
{
2023-02-19 10:58:16 +01:00
if ( const auto dynamic_value_allowed_values_node = dynamic_value_node . second [ patch_key :: allowed_values ]; dynamic_value_allowed_values_node && dynamic_value_allowed_values_node . IsMap ())
2023-02-18 23:57:23 +01:00
{
dynamic_value . allowed_values . clear ();
for ( const auto allowed_value : dynamic_value_allowed_values_node )
{
2023-02-19 10:58:16 +01:00
if ( allowed_value . second && allowed_value . second . IsScalar ())
2023-02-18 23:57:23 +01:00
{
2023-02-19 10:58:16 +01:00
patch_allowed_value new_allowed_value {};
new_allowed_value . label = allowed_value . first . Scalar ();
new_allowed_value . value = get_and_check_dynamic_value ( allowed_value . second );
if ( std :: any_of ( dynamic_value . allowed_values . begin (), dynamic_value . allowed_values . end (), [ & new_allowed_value ]( const patch_allowed_value & other ){ return new_allowed_value . value == other . value || new_allowed_value . label == other . label ; }))
{
append_log_message ( log_messages , fmt :: format ( "Error: Skipping dynamic allowed value. Another entry with the same label or value already exists. (patch: %s, key: %s, location: %s, file: %s)" , description , main_key , get_yaml_node_location ( allowed_value ), path ), & patch_log . error );
is_valid = false ;
}
else
{
dynamic_value . allowed_values . push_back ( new_allowed_value );
}
2023-02-18 23:57:23 +01:00
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Skipping dynamic allowed value (patch: %s, key: %s, location: %s, file: %s)" , description , main_key , get_yaml_node_location ( allowed_value ), path ), & patch_log . error );
is_valid = false ;
}
}
if ( dynamic_value . allowed_values . size () < 2 )
{
append_log_message ( log_messages , fmt :: format ( "Error: Dynamic allowed values need at least 2 entries (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_allowed_values_node ), path ), & patch_log . error );
is_valid = false ;
}
2023-02-19 10:58:16 +01:00
if ( std :: none_of ( dynamic_value . allowed_values . begin (), dynamic_value . allowed_values . end (), [ & dynamic_value ]( const patch_allowed_value & other ){ return other . value == dynamic_value . value ; }))
2023-02-18 23:57:23 +01:00
{
append_log_message ( log_messages , fmt :: format ( "Error: Dynamic value was not found in allowed values (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_allowed_values_node ), path ), & patch_log . error );
is_valid = false ;
}
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid or missing dynamic allowed values (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
break ;
}
}
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid dynamic type (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_type_node ), path ), & patch_log . error );
is_valid = false ;
}
}
else
{
append_log_message ( log_messages , fmt :: format ( "Error: Invalid or missing dynamic type (key: %s, location: %s, file: %s)" , main_key , get_yaml_node_location ( dynamic_value_node ), path ), & patch_log . error );
is_valid = false ;
}
2023-02-18 16:40:12 +01: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 ))
{
is_valid = false ;
}
}
// Skip this patch if a higher patch version already exists
2023-02-18 16:40:12 +01:00
if ( container . patch_info_map . contains ( description ))
2020-06-26 02:58:06 +02:00
{
bool ok ;
2022-04-09 01:14:31 +02:00
const std :: string & existing_version = container . patch_info_map [ description ]. patch_version ;
2020-06-26 02:58:06 +02:00
const bool version_is_bigger = utils :: compare_versions ( info . patch_version , existing_version , ok ) > 0 ;
if ( ! ok || ! version_is_bigger )
{
2023-02-18 16:40:12 +01:00
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 ), & patch_log . warning );
2020-06-26 02:58:06 +02:00
continue ;
}
2023-02-18 16:40:12 +01:00
if ( ! importing )
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 );
}
}
// Insert patch information
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
}
2021-09-02 20:48:56 +02:00
patch_type patch_engine :: get_patch_type ( const std :: string & text )
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 );
}
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 ())
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping invalid patch node %s. (key: %s, location: %s)" , info . description , info . hash , get_yaml_node_location ( node )), & patch_log . error );
2020-06-13 02:16:28 +02:00
return false ;
}
2020-06-02 10:39:24 +02:00
const auto type_node = node [ 0 ];
2022-04-09 01:14:31 +02:00
const auto addr_node = node [ 1 ];
2020-06-02 10:39:24 +02:00
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 () : "" ;
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s: type '%s' is invalid. (key: %s, location: %s)" , info . description , type_str , info . hash , get_yaml_node_location ( node )), & patch_log . error );
2020-06-13 02:16:28 +02:00
return false ;
}
if ( type == patch_type :: load )
2020-06-02 10:39:24 +02:00
{
// Special syntax: anchors (named sequence)
// Check if the anchor was resolved.
if ( const auto yml_type = addr_node . Type (); yml_type != YAML :: NodeType :: Sequence )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)" , info . description , yml_type , info . hash , get_yaml_node_location ( node )), & patch_log . error );
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
2022-04-09 20:16:23 +02:00
if ( const auto yml_type = value_node . Type (); yml_type != YAML :: NodeType :: Scalar )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s. Value element has wrong type %s. (key: %s, location: %s)" , info . description , yml_type , info . hash , get_yaml_node_location ( node )), & patch_log . error );
2022-04-09 20:16:23 +02:00
return false ;
}
2022-04-12 09:26:05 +02:00
if ( ! addr_node . Scalar (). starts_with ( "0x" ))
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s. Address element has wrong format %s. (key: %s, location: %s)" , info . description , addr_node . Scalar (), info . hash , get_yaml_node_location ( node )), & patch_log . error );
2022-04-12 09:26:05 +02:00
return false ;
}
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 ;
2022-04-09 20:16:23 +02:00
p_data . original_value = value_node . Scalar ();
2020-06-13 02:16:28 +02:00
2023-02-18 16:40:12 +01:00
const bool is_dynamic_value = info . default_dynamic_values . contains ( p_data . original_value );
2023-02-18 23:57:23 +01:00
const patch_dynamic_value dynamic_value = is_dynamic_value ? :: at32 ( info . default_dynamic_values , p_data . original_value ) : patch_dynamic_value {};
2023-02-18 16:40:12 +01: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 15:02:31 +03:00
case patch_type :: utf8 :
2021-09-06 10:33:44 +03:00
case patch_type :: jump_func :
2021-02-12 15:02:31 +03: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 :
{
2023-02-18 23:57:23 +01:00
p_data . value . double_value = is_dynamic_value ? dynamic_value . value : get_yaml_node_value < f64 > ( value_node , error_message );
2020-06-19 14:34:03 +02:00
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
{
2023-02-18 23:57:23 +01:00
p_data . value . long_value = is_dynamic_value ? static_cast < u64 > ( dynamic_value . value ) : get_yaml_node_value < u64 > ( value_node , error_message );
2023-02-18 16:40:12 +01:00
2022-04-18 00:07:10 +03:00
if ( error_message . find ( "bad conversion" ) != std :: string :: npos )
{
2023-02-18 16:40:12 +01:00
error_message . clear ();
2022-04-18 00:07:10 +03:00
p_data . value . long_value = get_yaml_node_value < s64 > ( value_node , error_message );
}
2020-06-19 14:34:03 +02:00
break ;
}
}
if ( ! error_message . empty ())
{
2022-04-09 01:14:31 +02:00
error_message = fmt :: format ( "Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s, location: %s) %s" ,
p_data . type , p_data . offset , p_data . original_value . empty () ? "?" : p_data . original_value , info . hash , get_yaml_node_location ( node ), error_message );
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , error_message , & patch_log . error );
2020-06-13 02:16:28 +02:00
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 )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping invalid patch node %s. (key: %s, location: %s)" , info . description , info . hash , get_yaml_node_location ( node )), & patch_log . error );
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 )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s)" , info . description , yml_type , info . hash , get_yaml_node_location ( node )), & patch_log . error );
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 16:21:49 +03:00
void ppu_register_range ( u32 addr , u32 size );
2021-09-06 10:33:44 +03: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 16:21:49 +03:00
2021-09-01 13:38:17 +03:00
void unmap_vm_area ( std :: shared_ptr < vm :: block_t >& ptr )
2020-06-28 14:19:44 +02:00
{
2021-09-01 13:38:17 +03:00
if ( ptr && ptr -> flags & ( 1ull << 62 ))
{
2021-09-11 08:26:08 +03:00
vm :: unmap ( 0 , true , & ptr );
2021-09-01 13:38:17 +03:00
}
}
// Returns old 'applied' size
2023-02-18 23:57:23 +01:00
static usz apply_modification ( std :: basic_string < u32 >& applied , patch_engine :: patch_info & patch , u8 * dst , u32 filesz , u32 min_addr )
2021-09-01 13:38:17 +03:00
{
const usz old_applied_size = applied . size ();
2020-06-28 14:19:44 +02:00
2023-02-18 23:57:23 +01:00
// Update dynamic values
for ( const auto & [ key , dynamic_value ] : patch . actual_dynamic_values )
{
for ( usz i = 0 ; i < patch . data_list . size (); i ++ )
{
patch_engine :: patch_data & p = :: at32 ( patch . data_list , i );
if ( p . original_value == key )
{
switch ( p . type )
{
case patch_type :: bef32 :
case patch_type :: lef32 :
case patch_type :: bef64 :
case patch_type :: lef64 :
{
p . value . double_value = dynamic_value . value ;
patch_log . notice ( "Using dynamic value (key='%s', value=%f, index=%d, hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s')" ,
key , p . value . double_value , i , patch . hash , patch . description , patch . author , patch . patch_version , patch . version );
break ;
}
default :
{
2023-02-19 10:58:16 +01:00
p . value . long_value = static_cast < u64 > ( dynamic_value . value );
2023-02-18 23:57:23 +01:00
patch_log . notice ( "Using dynamic value (key='%s', value=0x%x=%d, index=%d, hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s')" ,
key , p . value . long_value , p . value . long_value , i , patch . hash , patch . description , patch . author , patch . patch_version , patch . version );
break ;
}
}
}
}
}
for ( const patch_engine :: patch_data & p : patch . data_list )
2021-08-23 16:21:49 +03:00
{
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 23:25:23 +03: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 16:21:49 +03:00
2021-09-01 22:52:50 +03:00
u64 flags = vm :: alloc_unwritable ;
2021-08-23 16:21:49 +03:00
switch ( p . offset % patch_engine :: mem_protection :: mask )
{
2021-09-01 22:52:50 +03:00
case patch_engine :: mem_protection :: wx : flags = vm :: alloc_executable ; break ;
2021-09-01 13:38:17 +03:00
case patch_engine :: mem_protection :: ro : break ;
2021-09-01 22:52:50 +03: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 16:21:49 +03:00
default : ensure ( false );
}
if ( alloc_map )
{
2021-10-16 12:13:29 +03:00
if ( alloc_map -> falloc ( alloc_at , alloc_size , nullptr , flags ))
2021-08-23 16:21:49 +03:00
{
2021-10-16 12:13:29 +03:00
if ( vm :: check_addr ( alloc_at , vm :: page_1m_size ))
{
p . alloc_addr = alloc_at & - 0x100000 ;
}
else if ( vm :: check_addr ( alloc_at , vm :: page_64k_size ))
{
p . alloc_addr = alloc_at & - 0x10000 ;
}
else
{
p . alloc_addr = alloc_at & - 0x1000 ;
}
2021-09-01 22:52:50 +03:00
if ( flags & vm :: alloc_executable )
2021-08-23 16:21:49 +03: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 13:38:17 +03:00
unmap_vm_area ( alloc_map );
2021-08-23 16:21:49 +03:00
}
}
// Revert in case of failure
2021-09-01 13:38:17 +03:00
std :: for_each ( applied . begin () + old_applied_size , applied . end (), [ & ]( u32 index )
2021-08-23 16:21:49 +03:00
{
2021-09-01 13:38:17 +03:00
const u32 addr = std :: exchange ( patch . data_list [ index ]. alloc_addr , 0 );
2021-08-23 16:21:49 +03:00
2021-09-01 13:38:17 +03:00
vm :: dealloc ( addr );
2021-08-23 16:21:49 +03:00
2021-09-01 13:38:17 +03:00
auto alloc_map = vm :: get ( vm :: any , addr );
unmap_vm_area ( alloc_map );
});
2021-08-23 16:21:49 +03:00
2021-09-01 13:38:17 +03:00
applied . resize ( old_applied_size );
return old_applied_size ;
2021-08-23 16:21:49 +03:00
}
// Fixup values from before
2021-09-01 13:38:17 +03:00
std :: fill ( applied . begin () + old_applied_size , applied . end (), u32 { umax });
u32 relocate_instructions_at = 0 ;
2021-08-23 16:21:49 +03:00
2023-02-18 23:57:23 +01:00
for ( const patch_engine :: patch_data & p : patch . data_list )
2020-06-28 14:19:44 +02:00
{
u32 offset = p . offset ;
2021-09-01 13:38:17 +03: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 17:04:50 +02:00
// This patch is out of range for this segment
continue ;
2020-06-28 14:19:44 +02:00
}
2021-02-08 17:04:50 +02:00
offset -= min_addr ;
2020-06-28 14:19:44 +02:00
auto ptr = dst + offset ;
2021-09-01 13:38:17 +03: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-29 00:13:12 +03:00
u32 resval = umax ;
2021-02-08 17:04:50 +02: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 16:21:49 +03:00
case patch_type :: alloc :
{
// Applied before
continue ;
}
2021-09-01 13:38:17 +03:00
case patch_type :: code_alloc :
{
const u32 out_branch = vm :: try_get_addr ( dst + ( offset & - 4 )). first ;
// Allow only if points to a PPU executable instruction
2021-09-11 08:26:08 +03:00
if ( out_branch < 0x10000 || out_branch >= 0x4000'0000 || ! vm :: check_addr < 4 > ( out_branch , vm :: page_executable ))
2021-09-01 13:38:17 +03:00
{
continue ;
}
const u32 alloc_size = utils :: align ( static_cast < u32 > ( p . value . long_value + 1 ) * 4 , 0x10000 );
2022-12-09 20:06:50 +02:00
// Check if should maybe reuse previous code cave allocation (0 size)
if ( alloc_size - 4 != 0 )
{
// Nope
relocate_instructions_at = 0 ;
}
2021-09-01 13:38:17 +03:00
// Always executable
2021-09-01 22:52:50 +03:00
u64 flags = vm :: alloc_executable | vm :: alloc_unwritable ;
2021-09-01 13:38:17 +03:00
2022-11-19 13:50:31 +02:00
switch ( p . offset & patch_engine :: mem_protection :: mask )
2021-09-01 13:38:17 +03:00
{
case patch_engine :: mem_protection :: rw :
case patch_engine :: mem_protection :: wx :
{
2021-09-01 22:52:50 +03:00
flags &= ~ vm :: alloc_unwritable ;
2021-09-01 13:38:17 +03: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
2022-12-09 20:06:50 +02:00
const u32 addr = p . alloc_addr = ( relocate_instructions_at ? relocate_instructions_at : alloc_map -> alloc ( alloc_size , nullptr , 0x10000 , flags ));
2021-09-01 13:38:17 +03:00
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 );
2023-02-13 02:33:06 -08:00
// Check if already registered by previous code allocation
2022-12-09 20:06:50 +02:00
if ( relocate_instructions_at != addr )
{
// Register code
ppu_register_range ( addr , alloc_size );
}
2021-09-01 13:38:17 +03:00
resval = out_branch & - 4 ;
2022-11-19 13:50:31 +02:00
// Write branch to return to code
if ( ! ppu_form_branch_to_code ( addr + static_cast < u32 > ( p . value . long_value ) * 4 , resval + 4 ))
{
patch_log . error ( "Failed to write return jump at 0x%x" , addr + static_cast < u32 > ( p . value . long_value ) * 4 );
ensure ( alloc_map -> dealloc ( addr ));
continue ;
}
// Write branch to code
if ( ! ppu_form_branch_to_code ( out_branch , addr ))
{
patch_log . error ( "Failed to jump to code cave at 0x%x" , out_branch );
ensure ( alloc_map -> dealloc ( addr ));
continue ;
}
2021-09-01 13:38:17 +03:00
relocate_instructions_at = addr ;
break ;
}
case patch_type :: jump :
2021-09-02 18:14:26 +03:00
case patch_type :: jump_link :
2021-09-01 13:38:17 +03: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 18:14:26 +03:00
if ( ! ppu_form_branch_to_code ( out_branch , dest , p . type == patch_type :: jump_link ))
2021-09-01 13:38:17 +03:00
{
continue ;
}
resval = out_branch & - 4 ;
break ;
}
2021-09-06 10:33:44 +03: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 23:41:23 +03: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 23:41:23 +03: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 23:41:23 +03: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 23:41:23 +03: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 23:41:23 +03: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 23:41:23 +03: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 15:02:31 +03:00
case patch_type :: bd32 :
{
2021-03-08 23:41:23 +03:00
be_t < u32 > val = static_cast < u32 > ( p . value . long_value );
std :: memcpy ( ptr , & val , sizeof ( val ));
2021-02-12 15:02:31 +03:00
break ;
}
2020-06-28 14:19:44 +02:00
case patch_type :: be32 :
{
2021-03-08 23:41:23 +03: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 23:41:23 +03: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 15:02:31 +03:00
case patch_type :: bd64 :
{
2021-03-08 23:41:23 +03:00
be_t < u64 > val = static_cast < u64 > ( p . value . long_value );
std :: memcpy ( ptr , & val , sizeof ( val ));
2021-02-12 15:02:31 +03:00
break ;
}
2020-06-28 14:19:44 +02:00
case patch_type :: be64 :
{
2021-03-08 23:41:23 +03:00
be_t < u64 > val = static_cast < u64 > ( p . value . long_value );
std :: memcpy ( ptr , & val , sizeof ( val ));
2021-02-08 17:04:50 +02: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 23:41:23 +03: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 15:02:31 +03: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 17:04:50 +02:00
// Possibly an executable instruction
2021-02-02 19:18:50 +03:00
applied . push_back ( resval );
2020-06-28 14:19:44 +02:00
}
2021-09-01 13:38:17 +03:00
return old_applied_size ;
2020-06-28 14:19:44 +02:00
}
2021-02-08 17:04:50 +02: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
{
2023-02-18 16:40:12 +01:00
if ( ! m_map . contains ( name ))
2020-01-07 04:10:23 -05:00
{
2021-02-02 19:18:50 +03:00
return {};
2020-01-07 04:10:23 -05:00
}
2021-02-02 19:18:50 +03:00
std :: basic_string < u32 > applied_total ;
2022-09-19 15:57:51 +03:00
const auto & container = :: at32 ( m_map , name );
2021-04-09 21:12:47 +02:00
const auto & serial = Emu . GetTitleID ();
const auto & app_version = Emu . GetAppVersion ();
2020-01-07 04:10:23 -05:00
2020-06-28 14:19:44 +02:00
// Different containers in order to seperate the patches
2023-02-18 23:57:23 +01:00
std :: vector < std :: shared_ptr < patch_info >> patches_for_this_serial_and_this_version ;
std :: vector < std :: shared_ptr < patch_info >> patches_for_this_serial_and_all_versions ;
std :: vector < std :: shared_ptr < patch_info >> patches_for_all_serials_and_this_version ;
std :: vector < std :: shared_ptr < 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-06-02 10:39:24 +02: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 ;
2023-02-18 16:40:12 +01:00
if ( serials . contains ( serial ))
2020-06-27 10:32:00 +02:00
{
found_serial = serial ;
}
2023-02-18 16:40:12 +01:00
else if ( serials . contains ( patch_key :: all ))
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 14:14:08 +02:00
}
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 ;
}
2020-06-27 10:32:00 +02:00
2022-09-19 15:57:51 +03:00
const auto & app_versions = :: at32 ( serials , found_serial );
2020-06-28 14:19:44 +02:00
std :: string found_app_version ;
2023-02-18 16:40:12 +01:00
if ( app_versions . contains ( app_version ))
2020-06-28 14:19:44 +02:00
{
found_app_version = app_version ;
}
2023-02-18 16:40:12 +01:00
else if ( app_versions . contains ( patch_key :: all ))
2020-06-28 14:19:44 +02:00
{
found_app_version = patch_key :: all ;
is_all_versions = true ;
}
2023-02-18 16:40:12 +01:00
if ( found_app_version . empty ())
{
continue ;
}
const patch_config_values & config_values = :: at32 ( app_versions , found_app_version );
2023-02-18 23:57:23 +01:00
// Check if this patch is enabled
2023-02-18 16:40:12 +01:00
if ( config_values . enabled )
2020-06-28 14:19:44 +02:00
{
2023-02-18 23:57:23 +01:00
// Make copy of this patch
std :: shared_ptr < patch_info > p_ptr = std :: make_shared < patch_info > ( patch );
// Move dynamic values to special container for readability
p_ptr -> actual_dynamic_values = p_ptr -> default_dynamic_values ;
// Update dynamic values
for ( auto & [ key , dynamic_value ] : config_values . dynamic_values )
{
if ( p_ptr -> actual_dynamic_values . contains ( key ))
{
:: at32 ( p_ptr -> actual_dynamic_values , key ). value = dynamic_value . value ;
}
}
// Sort the patch by priority
2020-06-28 14:19:44 +02:00
if ( is_all_serials )
2020-06-27 10:32:00 +02:00
{
2020-06-28 14:19:44 +02:00
if ( is_all_versions )
{
2023-02-18 23:57:23 +01:00
patches_for_all_serials_and_all_versions . emplace_back ( p_ptr );
2020-06-28 14:19:44 +02:00
}
else
{
2023-02-18 23:57:23 +01:00
patches_for_all_serials_and_this_version . emplace_back ( p_ptr );
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 14:14:08 +02:00
{
2023-02-18 23:57:23 +01:00
patches_for_this_serial_and_all_versions . emplace_back ( p_ptr );
2020-06-28 14:19:44 +02:00
}
else
{
2023-02-18 23:57:23 +01:00
patches_for_this_serial_and_this_version . emplace_back ( p_ptr );
2020-06-27 14:14:08 +02:00
}
2020-06-27 10:32:00 +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
2021-08-07 08:54:53 +03: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 )
{
2023-02-18 23:57:23 +01:00
patch . reset ();
2021-08-07 08:54:53 +03:00
}
}
}
}
2023-02-18 23:57:23 +01:00
// Apply modifications sequentially
2021-08-07 08:54:53 +03:00
for ( auto patch_list : patch_super_list )
{
2023-02-18 23:57:23 +01:00
for ( const std :: shared_ptr < patch_info >& patch : * patch_list )
2021-08-07 08:54:53 +03:00
{
if ( patch )
{
2023-02-18 23:57:23 +01:00
const usz old_size = apply_modification ( applied_total , * patch , dst , filesz , min_addr );
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 08:54:53 +03:00
}
}
2020-06-02 10:39:24 +02:00
}
return applied_total ;
}
2021-09-01 13:38:17 +03:00
void patch_engine :: unload ( const std :: string & name )
{
2023-02-18 16:40:12 +01:00
if ( ! m_map . contains ( name ))
2021-09-01 13:38:17 +03:00
{
return ;
}
2022-09-19 15:57:51 +03:00
const auto & container = :: at32 ( m_map , name );
2021-09-01 13:38:17 +03:00
for ( const auto & [ description , patch ] : container . patch_info_map )
{
2021-09-01 14:38:20 +03:00
for ( auto & entry : patch . data_list )
2021-09-01 13:38:17 +03:00
{
2021-09-01 14:38:20 +03:00
// Deallocate used memory
if ( u32 addr = std :: exchange ( entry . alloc_addr , 0 ))
2021-09-01 13:38:17 +03:00
{
2021-09-01 14:38:20 +03:00
vm :: dealloc ( addr );
2021-09-01 13:38:17 +03:00
2021-09-01 14:38:20 +03:00
auto alloc_map = vm :: get ( vm :: any , addr );
unmap_vm_area ( alloc_map );
2021-09-01 13:38:17 +03: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 ;
2023-02-18 16:40:12 +01:00
// Save values per hash, description, serial and app_version
2020-06-26 02:58:06 +02:00
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 04:10:23 -05:00
{
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : patch . titles )
{
for ( const auto & [ serial , app_versions ] : serials )
{
2023-02-18 16:40:12 +01:00
for ( const auto & [ app_version , config_values ] : app_versions )
2020-06-26 02:58:06 +02:00
{
2023-02-18 16:40:12 +01:00
const bool dynamic_values_dirty = ! patch . default_dynamic_values . empty () && ! config_values . dynamic_values . empty () && patch . default_dynamic_values != config_values . dynamic_values ;
if ( config_values . enabled || dynamic_values_dirty )
2020-06-26 02:58:06 +02:00
{
2023-02-18 16:40:12 +01:00
config_map [ hash ]. patch_info_map [ description ]. titles [ title ][ serial ][ app_version ] = config_values ;
2020-06-26 02:58:06 +02:00
}
}
}
}
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
2023-02-18 16:40:12 +01:00
if ( const auto & patches_to_save = config_map [ hash ]. patch_info_map ; ! patches_to_save . empty ())
2020-01-07 04:10:23 -05:00
{
2020-06-26 02:58:06 +02:00
out << hash << YAML :: BeginMap ;
2020-06-02 10:39:24 +02:00
2023-02-18 16:40:12 +01:00
for ( const auto & [ description , patch ] : patches_to_save )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
out << description << YAML :: BeginMap ;
2023-02-18 16:40:12 +01:00
for ( const auto & [ title , serials ] : patch . titles )
2020-06-26 02:58:06 +02:00
{
out << title << YAML :: BeginMap ;
for ( const auto & [ serial , app_versions ] : serials )
{
out << serial << YAML :: BeginMap ;
2023-02-18 16:40:12 +01:00
for ( const auto & [ app_version , config_values ] : app_versions )
2020-06-26 02:58:06 +02:00
{
2023-02-18 16:40:12 +01:00
const auto & default_dynamic_values = :: at32 ( container . patch_info_map , description ). default_dynamic_values ;
const bool dynamic_values_dirty = ! default_dynamic_values . empty () && ! config_values . dynamic_values . empty () && default_dynamic_values != config_values . dynamic_values ;
if ( config_values . enabled || dynamic_values_dirty )
{
out << app_version << YAML :: BeginMap ;
if ( config_values . enabled )
{
out << patch_key :: enabled << config_values . enabled ;
}
if ( dynamic_values_dirty )
{
out << patch_key :: dynamic_values << YAML :: BeginMap ;
2023-02-18 23:57:23 +01:00
for ( const auto & [ name , dynamic_value ] : config_values . dynamic_values )
2023-02-18 16:40:12 +01:00
{
2023-02-18 23:57:23 +01:00
out << name << dynamic_value . value ;
2023-02-18 16:40:12 +01:00
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
}
2020-06-26 02:58:06 +02:00
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
2020-06-02 10:39:24 +02:00
}
out << YAML :: EndMap ;
2020-01-07 04:10:23 -05: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 20:56:50 +03:00
fs :: pending_file file ( path );
if ( ! file . file || ( file . file . write ( out . c_str (), out . size ()), ! file . commit ()))
{
2023-01-07 22:11:41 +01:00
patch_log . error ( "Failed to create patch config file %s (error=%s)" , path , fs :: g_tls_error );
2021-10-09 20:56:50 +03:00
}
2020-06-02 10:39:24 +02:00
}
2020-12-18 10:39:54 +03: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 ();
2023-02-18 16:40:12 +01:00
if ( ! existing_patches . contains ( hash ))
2020-06-12 21:09:08 +02:00
{
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
{
2023-02-18 16:40:12 +01:00
if ( ! container . patch_info_map . contains ( description ))
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
{
2023-02-18 16:40:12 +01:00
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 ), & patch_log . error );
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
{
2023-02-18 16:40:12 +01:00
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 ), & patch_log . error );
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 )
{
2023-02-18 16:40:12 +01:00
append_log_message ( log_messages , fmt :: format ( "Failed to open patch file %s (%s)" , path , fs :: g_tls_error ), & patch_log . fatal );
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
2023-02-18 16:40:12 +01:00
if ( ! info . default_dynamic_values . empty ())
{
out << patch_key :: dynamic_values << YAML :: BeginMap ;
2023-02-18 23:57:23 +01:00
for ( const auto & [ key , dynamic_value ] : info . default_dynamic_values )
2023-02-18 16:40:12 +01:00
{
2023-02-18 23:57:23 +01:00
out << key << YAML :: BeginMap ;
out << patch_key :: type << fmt :: format ( "%s" , dynamic_value . type );
out << patch_key :: value << dynamic_value . value ;
switch ( dynamic_value . type )
{
case patch_dynamic_type :: double_range :
case patch_dynamic_type :: long_range :
out << patch_key :: min << dynamic_value . min ;
out << patch_key :: max << dynamic_value . max ;
break ;
case patch_dynamic_type :: double_enum :
case patch_dynamic_type :: long_enum :
2023-02-19 10:58:16 +01:00
out << patch_key :: allowed_values << YAML :: BeginMap ;
2023-02-18 23:57:23 +01:00
for ( const auto & allowed_value : dynamic_value . allowed_values )
{
2023-02-19 10:58:16 +01:00
out << allowed_value . label << allowed_value . value ;
2023-02-18 23:57:23 +01:00
}
2023-02-19 10:58:16 +01:00
out << YAML :: EndMap ;
2023-02-18 23:57:23 +01:00
break ;
}
out << YAML :: EndMap ;
2023-02-18 16:40:12 +01:00
}
out << YAML :: EndMap ;
}
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 10:39:54 +03: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 ))
{
2023-02-18 16:40:12 +01:00
if ( patches . contains ( info . hash ))
2020-06-19 19:47:51 +02:00
{
auto & container = patches [ info . hash ];
2023-02-18 16:40:12 +01:00
if ( container . patch_info_map . contains ( info . description ))
2020-06-19 19:47:51 +02:00
{
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
{
2023-02-18 16:40:12 +01: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 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-26 02:58:06 +02:00
for ( const auto pair : root )
2020-01-07 04:10:23 -05: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 ();
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
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 ();
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 ();
2023-02-18 16:40:12 +01:00
auto & config_values = config_map [ hash ]. patch_info_map [ description ]. titles [ title ][ serial ][ app_version ];
if ( app_version_node . second . IsMap ())
{
if ( const auto enable_node = app_version_node . second [ patch_key :: enabled ])
{
config_values . enabled = enable_node . as < bool > ( false );
}
if ( const auto dynamic_values_node = app_version_node . second [ patch_key :: dynamic_values ])
{
for ( const auto dynamic_value_node : dynamic_values_node )
{
2023-02-18 23:57:23 +01:00
patch_dynamic_value & dynamic_value = config_values . dynamic_values [ dynamic_value_node . first . Scalar ()];
dynamic_value . value = dynamic_value_node . second . as < f64 > ( 0.0 );
2023-02-18 16:40:12 +01:00
}
}
}
else
{
// Legacy
config_values . enabled = app_version_node . second . as < bool > ( false );
}
2020-06-26 02:58:06 +02:00
}
}
}
2020-06-02 10:39:24 +02:00
}
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
}