2020-12-25 03:18:36 +01:00
# include "bin_patch.h"
2024-08-31 04:56:54 +02:00
# include "ppu_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"
2024-08-31 04:56:54 +02:00
# include "Emu/IdManager.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"
2023-03-03 14:43:46 +01:00
# include "Emu/VFS.h"
2017-03-29 01:54:05 +02:00
2020-12-13 14:34:45 +01:00
# include "util/types.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>
2024-08-15 00:28:32 +02:00
# include <regex>
2024-11-11 20:54:44 +01:00
# include <vector>
2021-09-06 09:33:44 +02:00
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 ;
} ) ;
}
2023-02-18 23:57:23 +01:00
template < >
2023-02-19 12:59:46 +01:00
void fmt_class_string < patch_configurable_type > : : format ( std : : string & out , u64 arg )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
format_enum ( out , arg , [ ] ( patch_configurable_type value )
2023-02-18 23:57:23 +01:00
{
switch ( value )
{
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : double_range : return " double_range " ;
case patch_configurable_type : : double_enum : return " double_enum " ;
case patch_configurable_type : : long_range : return " long_range " ;
case patch_configurable_type : : long_enum : return " long_enum " ;
2023-02-18 23:57:23 +01:00
}
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 " ;
2023-09-25 17:32:50 +02:00
case patch_type : : bp_exec : return " bpex " ;
2021-02-12 13:02:31 +01:00
case patch_type : : utf8 : return " utf8 " ;
2023-07-12 19:43:33 +02:00
case patch_type : : c_utf8 : return " cutf8 " ;
2023-03-03 14:43:46 +01:00
case patch_type : : move_file : return " move_file " ;
case patch_type : : hide_file : return " hide_file " ;
2017-03-29 01:54:05 +02:00
}
return unknown ;
} ) ;
}
2024-08-14 18:20:08 +02:00
void patch_engine : : patch_config_value : : set_and_check_value ( f64 new_value , std : : string_view name )
2023-02-19 20:34:21 +01:00
{
switch ( type )
{
case patch_configurable_type : : double_enum :
case patch_configurable_type : : long_enum :
{
if ( std : : none_of ( allowed_values . begin ( ) , allowed_values . end ( ) , [ & new_value ] ( const patch_allowed_value & allowed_value ) { return allowed_value . value = = new_value ; } ) )
{
patch_log . error ( " Can't set configurable enumerated value '%s' to %f. Using default value %f " , name , new_value , value ) ;
return ;
}
break ;
}
case patch_configurable_type : : double_range :
case patch_configurable_type : : long_range :
{
if ( new_value < min | | new_value > max )
{
patch_log . error ( " Can't set configurable range value '%s' to %f. Using default value %f " , name , new_value , value ) ;
return ;
}
break ;
}
}
value = new_value ;
}
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
}
2024-08-14 18:20:08 +02:00
static void append_log_message ( std : : stringstream * log_messages , std : : string_view 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-29 22:26:22 +01: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-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
{
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 )
{
2023-07-12 01:12:14 +02:00
const std : : string & main_key = pair . first . Scalar ( ) ;
2020-06-02 10:39:24 +02:00
2023-07-12 01:12:14 +02:00
if ( main_key . empty ( ) )
2020-06-02 10:39:24 +02:00
{
2023-07-12 01:12:14 +02:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping empty key (location: %s, file: %s) " , get_yaml_node_location ( pair . first ) , path ) , & patch_log . error ) ;
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
}
2023-07-12 01:12:14 +02:00
if ( const auto yml_type = pair . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
2021-09-02 20:48:56 +02:00
{
2023-07-12 01:12:14 +02: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 ) ;
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
2023-07-12 01:12:14 +02:00
patch_container & 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 ( ) ;
2023-07-12 01:11:26 +02:00
if ( description . empty ( ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Empty patch name (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( patches_entry . first ) , path ) , & patch_log . error ) ;
is_valid = false ;
continue ;
}
2020-06-26 02:58:06 +02:00
// 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
{
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 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
{
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-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 ( ) )
{
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 )
{
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-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 ( ) )
{
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 ;
}
2024-08-15 00:28:05 +02:00
else if ( serial . size ( ) ! = 9 | | ! std : : all_of ( serial . begin ( ) , serial . end ( ) , [ ] ( char c ) { return std : : isalnum ( c ) ; } ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Serial '%s' invalid (patch: %s, key: %s, location: %s, file: %s) " , serial , description , main_key , get_yaml_node_location ( serial_node ) , path ) , & patch_log . error ) ;
is_valid = false ;
continue ;
}
2020-06-27 15:24:34 +02:00
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 ;
}
2023-07-12 01:12:14 +02:00
patch_app_versions app_versions ;
2020-06-26 02:58:06 +02:00
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
2024-08-15 00:28:32 +02:00
static const std : : regex app_ver_regexp ( " ^([0-9]{2} \\ .[0-9]{2})$ " ) ;
if ( app_version ! = patch_key : : all & & ( app_version . size ( ) ! = 5 | | ! std : : regex_match ( app_version , app_ver_regexp ) ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Skipping invalid app version '%s' (title: %s, serial: %s, patch: %s, key: %s, location: %s, file: %s) " , app_version , title , serial , description , main_key , get_yaml_node_location ( serial_node ) , path ) , & patch_log . error ) ;
is_valid = false ;
2024-08-15 00:42:22 +02:00
continue ;
}
if ( app_versions . contains ( app_version ) )
{
append_log_message ( log_messages , fmt : : format ( " Error: Skipping duplicate app version '%s' (title: %s, serial: %s, patch: %s, key: %s, location: %s, file: %s) " , app_version , title , serial , description , main_key , get_yaml_node_location ( serial_node ) , path ) , & patch_log . error ) ;
is_valid = false ;
2024-08-15 00:28:32 +02:00
continue ;
}
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
2024-08-14 23:57:50 +02:00
app_versions [ app_version ] = 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
{
2024-08-14 23:57:50 +02:00
info . titles [ title ] [ serial ] = std : : move ( app_versions ) ;
2020-06-26 02:58:06 +02:00
}
}
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 )
{
2023-08-12 13:54:10 +02:00
if ( note )
2020-06-29 13:53:39 +02:00
{
2023-08-12 13:54:10 +02:00
if ( note . IsScalar ( ) )
{
info . notes + = note . Scalar ( ) ;
}
else
{
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 ) ;
is_valid = false ;
}
2020-06-29 13:53:39 +02:00
}
else
{
2023-08-12 13:54:10 +02: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 ( notes_node ) , 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 ( ) ;
}
2020-06-02 10:39:24 +02:00
2023-02-19 12:59:46 +01:00
if ( const auto config_values_node = patches_entry . second [ patch_key : : config_values ] )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
if ( const auto yml_type = config_values_node . Type ( ) ; yml_type ! = YAML : : NodeType : : Map | | config_values_node . size ( ) = = 0 )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping configurable values: expected Map, found %s (patch: %s, key: %s, location: %s, file: %s) " , yml_type , description , main_key , get_yaml_node_location ( config_values_node ) , path ) , & patch_log . error ) ;
2023-02-18 16:40:12 +01:00
is_valid = false ;
}
else
{
2023-02-19 12:59:46 +01:00
for ( const auto config_value_node : config_values_node )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
const std : : string & value_key = config_value_node . first . Scalar ( ) ;
2023-02-18 23:57:23 +01:00
2023-02-19 12:59:46 +01:00
if ( const auto yml_type = config_value_node . second . Type ( ) ; yml_type ! = YAML : : NodeType : : Map )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping configurable 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 ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
2023-02-18 16:40:12 +01:00
}
else
{
2023-02-19 12:59:46 +01:00
patch_config_value & config_value = info . default_config_values [ value_key ] ;
2023-02-18 23:57:23 +01:00
2023-02-19 12:59:46 +01:00
if ( const auto config_value_type_node = config_value_node . second [ patch_key : : type ] ; config_value_type_node & & config_value_type_node . IsScalar ( ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
const std : : string & str_type = config_value_type_node . Scalar ( ) ;
2023-02-18 23:57:23 +01:00
bool is_valid_type = false ;
2023-02-19 12:59:46 +01:00
for ( patch_configurable_type type : { patch_configurable_type : : double_range , patch_configurable_type : : double_enum , patch_configurable_type : : long_range , patch_configurable_type : : long_enum } )
2023-02-18 23:57:23 +01:00
{
if ( str_type = = fmt : : format ( " %s " , type ) )
{
2023-02-19 12:59:46 +01:00
config_value . type = type ;
2023-02-18 23:57:23 +01:00
is_valid_type = true ;
break ;
}
}
if ( is_valid_type )
{
2023-02-19 12:59:46 +01:00
const auto get_and_check_config_value = [ & ] ( const YAML : : Node & node )
2023-02-18 23:57:23 +01:00
{
std : : string err ;
f64 val { } ;
2023-02-19 12:59:46 +01:00
switch ( config_value . type )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : double_range :
case patch_configurable_type : : double_enum :
2023-02-18 23:57:23 +01:00
val = get_yaml_node_value < f64 > ( node , err ) ;
break ;
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : long_range :
case patch_configurable_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 ( ) )
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid data type found in configurable value: %s (key: %s, location: %s, file: %s) " , err , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
return val ;
} ;
2023-02-19 12:59:46 +01:00
if ( const auto config_value_value_node = config_value_node . second [ patch_key : : value ] ; config_value_value_node & & config_value_value_node . IsScalar ( ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
config_value . value = get_and_check_config_value ( config_value_value_node ) ;
2023-02-18 23:57:23 +01:00
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid or missing configurable value (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
2023-02-19 12:59:46 +01:00
switch ( config_value . type )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : double_range :
case patch_configurable_type : : long_range :
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
if ( const auto config_value_min_node = config_value_node . second [ patch_key : : min ] ; config_value_min_node & & config_value_min_node . IsScalar ( ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
config_value . min = get_and_check_config_value ( config_value_min_node ) ;
2023-02-18 23:57:23 +01:00
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid or missing configurable min (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
2023-02-19 12:59:46 +01:00
if ( const auto config_value_max_node = config_value_node . second [ patch_key : : max ] ; config_value_max_node & & config_value_max_node . IsScalar ( ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
config_value . max = get_and_check_config_value ( config_value_max_node ) ;
2023-02-18 23:57:23 +01:00
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid or missing configurable max (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
2023-02-19 12:59:46 +01:00
if ( config_value . min > = config_value . max )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Configurable max has to be larger than configurable min (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
2023-02-19 12:59:46 +01:00
if ( config_value . value < config_value . min | | config_value . value > config_value . max )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Configurable value out of range (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
break ;
}
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : double_enum :
case patch_configurable_type : : long_enum :
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
if ( const auto config_value_allowed_values_node = config_value_node . second [ patch_key : : allowed_values ] ; config_value_allowed_values_node & & config_value_allowed_values_node . IsMap ( ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
config_value . allowed_values . clear ( ) ;
2023-02-18 23:57:23 +01:00
2023-02-19 12:59:46 +01:00
for ( const auto allowed_value : config_value_allowed_values_node )
2023-02-18 23:57:23 +01:00
{
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 ( ) ;
2023-02-19 12:59:46 +01:00
new_allowed_value . value = get_and_check_config_value ( allowed_value . second ) ;
2023-02-19 10:58:16 +01:00
2023-02-19 12:59:46 +01:00
if ( std : : any_of ( config_value . allowed_values . begin ( ) , config_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 ; } ) )
2023-02-19 10:58:16 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping configurable 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 ) ;
2023-02-19 10:58:16 +01:00
is_valid = false ;
}
else
{
2024-08-14 23:57:50 +02:00
config_value . allowed_values . push_back ( std : : move ( new_allowed_value ) ) ;
2023-02-19 10:58:16 +01:00
}
2023-02-18 23:57:23 +01:00
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Skipping configurable allowed value (patch: %s, key: %s, location: %s, file: %s) " , description , main_key , get_yaml_node_location ( allowed_value ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
}
2023-02-19 12:59:46 +01:00
if ( config_value . allowed_values . size ( ) < 2 )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Configurable allowed values need at least 2 entries (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_allowed_values_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
2023-02-19 12:59:46 +01:00
if ( std : : none_of ( config_value . allowed_values . begin ( ) , config_value . allowed_values . end ( ) , [ & config_value ] ( const patch_allowed_value & other ) { return other . value = = config_value . value ; } ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Configurable value was not found in allowed values (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_allowed_values_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid or missing configurable allowed values (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
break ;
}
}
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid configurable type (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_type_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
is_valid = false ;
}
}
else
{
2023-02-19 12:59:46 +01:00
append_log_message ( log_messages , fmt : : format ( " Error: Invalid or missing configurable type (key: %s, location: %s, file: %s) " , main_key , get_yaml_node_location ( config_value_node ) , path ) , & patch_log . error ) ;
2023-02-18 23:57:23 +01:00
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
{
2024-08-14 18:20:08 +02:00
if ( ! read_patch_node ( info , patch_node , root , path , log_messages ) )
2017-07-17 15:34:04 +02:00
{
2023-07-12 01:12:14 +02:00
for ( const auto & it : patches_entry . second )
{
if ( it . first . Scalar ( ) = = patch_key : : patch )
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping invalid patch node %s: (key: %s, location: %s, file: %s) " , info . description , main_key , get_yaml_node_location ( it . first ) , path ) , & patch_log . error ) ;
2023-07-12 01:12:14 +02:00
break ;
}
}
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
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 )
2020-06-02 10:39:24 +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 (in file %s) " , info . patch_version , existing_version , main_key , description , path ) , & patch_log . warning ) ;
2020-06-26 02:58:06 +02:00
continue ;
2017-07-17 15:34:04 +02:00
}
2023-02-18 16:40:12 +01:00
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
2024-08-14 23:57:50 +02:00
container . patch_info_map [ description ] = std : : move ( 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
}
2024-08-14 18:20:08 +02:00
patch_type patch_engine : : get_patch_type ( std : : string_view 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 ( ) ) ;
}
2024-08-14 18:20:08 +02:00
bool patch_engine : : add_patch_data ( YAML : : Node node , patch_info & info , u32 modifier , const YAML : : Node & root , std : : string_view path , std : : stringstream * log_messages )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
if ( ! node | | ! node . IsSequence ( ) )
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping invalid patch node %s. (key: %s, location: %s, file: %s) " , info . description , info . hash , get_yaml_node_location ( node ) , path ) , & 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 ] ;
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 ( ) : " " ;
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping patch node %s: type '%s' is invalid. (key: %s, location: %s, file: %s) " , info . description , type_str , info . hash , get_yaml_node_location ( node ) , path ) , & patch_log . error ) ;
2020-06-13 02:16:28 +02:00
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
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping patch node %s: expected Sequence, found %s (key: %s, location: %s, file: %s) " , info . description , yml_type , info . hash , get_yaml_node_location ( node ) , path ) , & patch_log . error ) ;
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
{
2024-08-14 18:20:08 +02:00
if ( ! add_patch_data ( item , info , mod , root , path , log_messages ) )
2020-06-12 21:09:08 +02:00
{
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
2022-04-09 20:16:23 +02:00
if ( const auto yml_type = value_node . Type ( ) ; yml_type ! = YAML : : NodeType : : Scalar )
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping patch node %s. Value element has wrong type %s. (key: %s, location: %s, file: %s) " , info . description , yml_type , info . hash , get_yaml_node_location ( node ) , path ) , & patch_log . error ) ;
2022-04-09 20:16:23 +02:00
return false ;
}
2023-03-03 14:43:46 +01:00
if ( patch_type_uses_hex_offset ( type ) & & ! addr_node . Scalar ( ) . starts_with ( " 0x " ) )
2022-04-12 09:26:05 +02:00
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Skipping patch node %s. Address element has wrong format %s. (key: %s, location: %s, file: %s) " , info . description , addr_node . Scalar ( ) , info . hash , get_yaml_node_location ( node ) , path ) , & 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 { } ;
2023-03-03 14:43:46 +01:00
p_data . type = type ;
p_data . offset = addr_node . as < u32 > ( 0 ) + modifier ;
p_data . original_offset = addr_node . Scalar ( ) ;
p_data . original_value = value_node . Scalar ( ) ;
2020-06-13 02:16:28 +02:00
2023-02-19 12:59:46 +01:00
const bool is_config_value = info . default_config_values . contains ( p_data . original_value ) ;
const patch_config_value config_value = is_config_value ? : : at32 ( info . default_config_values , p_data . original_value ) : patch_config_value { } ;
2023-02-18 16:40:12 +01:00
2020-06-19 14:34:03 +02:00
std : : string error_message ;
2023-07-11 21:52:44 +02:00
// Validate offset
switch ( p_data . type )
{
case patch_type : : move_file :
case patch_type : : hide_file :
break ;
default :
{
2021-05-22 10:42:05 +02:00
[[maybe_unused]] const u32 offset = get_yaml_node_value < u32 > ( addr_node , error_message ) ;
2023-07-11 21:52:44 +02:00
if ( ! error_message . empty ( ) )
{
2024-08-14 18:20:08 +02:00
error_message = fmt : : format ( " Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s, location: %s, file: %s) Invalid patch offset '%s' (not a valid u32 or overflow) " ,
p_data . type , p_data . offset , p_data . original_value . empty ( ) ? " ? " : p_data . original_value , info . hash , get_yaml_node_location ( node ) , path , p_data . original_offset ) ;
2023-07-11 21:52:44 +02:00
append_log_message ( log_messages , error_message , & patch_log . error ) ;
return false ;
}
if ( ( 0xFFFFFFFF - modifier ) < p_data . offset )
{
2024-08-14 18:20:08 +02:00
error_message = fmt : : format ( " Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s, location: %s, file: %s) Invalid combination of patch offset 0x%.8x and modifier 0x%.8x (overflow) " ,
p_data . type , p_data . offset , p_data . original_value . empty ( ) ? " ? " : p_data . original_value , info . hash , get_yaml_node_location ( node ) , path , p_data . offset , modifier ) ;
2023-07-11 21:52:44 +02:00
append_log_message ( log_messages , error_message , & patch_log . error ) ;
return false ;
}
break ;
}
}
2024-08-14 01:38:52 +02:00
// Validate value
const auto get_node_value = [ & ] < typename U , typename S > ( U , S ) // Add unused params. The lambda doesn't compile otherwise.
{
p_data . value . long_value = is_config_value ? static_cast < U > ( config_value . value ) : get_yaml_node_value < U > ( value_node , error_message ) ;
if ( error_message . find ( " bad conversion " ) ! = std : : string : : npos )
{
error_message . clear ( ) ;
p_data . value . long_value = get_yaml_node_value < S > ( value_node , error_message ) ;
}
} ;
2020-06-19 14:34:03 +02:00
switch ( p_data . type )
2020-06-02 10:39:24 +02:00
{
2023-09-25 17:32:50 +02:00
case patch_type : : bp_exec :
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 :
2023-03-04 18:39:11 +01:00
case patch_type : : move_file :
case patch_type : : hide_file :
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 :
2024-08-14 01:38:52 +02:00
{
p_data . value . double_value = is_config_value ? config_value . value : get_yaml_node_value < f32 > ( value_node , error_message ) ;
break ;
}
2020-06-19 14:34:03 +02:00
case patch_type : : bef64 :
case patch_type : : lef64 :
{
2023-02-19 12:59:46 +01:00
p_data . value . double_value = is_config_value ? config_value . value : get_yaml_node_value < f64 > ( value_node , error_message ) ;
2020-06-19 14:34:03 +02:00
break ;
}
2024-08-14 01:38:52 +02:00
case patch_type : : byte :
{
get_node_value ( u8 { } , s8 { } ) ;
break ;
}
case patch_type : : le16 :
case patch_type : : be16 :
{
get_node_value ( u16 { } , s16 { } ) ;
break ;
}
case patch_type : : le32 :
case patch_type : : be32 :
case patch_type : : bd32 :
{
get_node_value ( u32 { } , s32 { } ) ;
break ;
}
2020-06-19 14:34:03 +02:00
default :
{
2024-08-14 01:38:52 +02:00
get_node_value ( u64 { } , s64 { } ) ;
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
if ( ! error_message . empty ( ) )
2020-06-02 10:39:24 +02:00
{
2024-08-14 18:20:08 +02:00
error_message = fmt : : format ( " Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s, location: %s, file: %s) %s " ,
p_data . type , p_data . offset , p_data . original_value . empty ( ) ? " ? " : p_data . original_value , info . hash , get_yaml_node_location ( node ) , path , 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 ;
2017-03-29 01:54:05 +02:00
}
2017-07-17 15:34:04 +02:00
2024-08-14 23:57:50 +02:00
info . data_list . emplace_back ( std : : move ( 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
2024-08-14 18:20:08 +02:00
bool patch_engine : : read_patch_node ( patch_info & info , YAML : : Node node , const YAML : : Node & root , std : : string_view path , 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 )
{
2024-08-14 18:20:08 +02:00
if ( ! add_patch_data ( patch , info , 0 , root , path , log_messages ) )
2020-06-12 21:09:08 +02:00
{
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
}
2024-08-14 18:20:08 +02:00
void patch_engine : : append_title_patches ( std : : string_view title_id )
2020-06-02 10:39:24 +02:00
{
if ( title_id . empty ( ) )
{
return ;
}
2021-01-08 18:36:12 +01:00
// Regular patch.yml
2024-08-14 18:20:08 +02:00
load ( m_map , fmt : : format ( " %s%s_patch.yml " , get_patches_path ( ) , title_id ) ) ;
2020-06-02 10:39:24 +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
2024-11-11 20:54:44 +01:00
static usz apply_modification ( std : : vector < u32 > & applied , patch_engine : : patch_info & patch , std : : function < u8 * ( u32 , u32 ) > mem_translate , u32 filesz , u32 min_addr )
2021-09-01 12:38:17 +02:00
{
const usz old_applied_size = applied . size ( ) ;
2020-06-28 14:19:44 +02:00
2023-02-19 12:59:46 +01:00
// Update configurable values
for ( const auto & [ key , config_value ] : patch . actual_config_values )
2023-02-18 23:57:23 +01:00
{
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 :
{
2023-02-19 12:59:46 +01:00
p . value . double_value = config_value . value ;
patch_log . notice ( " Using configurable value (key='%s', value=%f, index=%d, hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') " ,
2023-02-18 23:57:23 +01:00
key , p . value . double_value , i , patch . hash , patch . description , patch . author , patch . patch_version , patch . version ) ;
break ;
}
default :
{
2023-02-19 12:59:46 +01:00
p . value . long_value = static_cast < u64 > ( config_value . value ) ;
patch_log . notice ( " Using configurable value (key='%s', value=0x%x=%d, index=%d, hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') " ,
2023-02-18 23:57:23 +01:00
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 15:21:49 +02:00
{
if ( p . type ! = patch_type : : alloc ) continue ;
2023-06-25 14:53:42 +02:00
// Do not allow null address or if resultant ptr is not a VM ptr
2023-07-12 19:43:33 +02:00
if ( const u32 alloc_at = ( p . offset & - 4096 ) ; alloc_at > > 16 )
2021-08-23 15:21:49 +02:00
{
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-10-16 11:13:29 +02:00
if ( alloc_map - > falloc ( alloc_at , alloc_size , nullptr , flags ) )
2021-08-23 15:21:49 +02:00
{
2021-10-16 11:13:29 +02: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 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
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 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 ;
}
2023-07-12 19:43:33 +02:00
u32 memory_size = 0 ;
switch ( p . type )
{
case patch_type : : invalid :
case patch_type : : load :
{
// Invalid in this context
break ;
}
case patch_type : : alloc :
{
// Applied before
memory_size = 0 ;
continue ;
}
case patch_type : : byte :
{
memory_size = sizeof ( u8 ) ;
break ;
}
case patch_type : : le16 :
case patch_type : : be16 :
{
memory_size = sizeof ( u16 ) ;
break ;
}
case patch_type : : code_alloc :
case patch_type : : jump :
case patch_type : : jump_link :
case patch_type : : jump_func :
2023-09-25 17:32:50 +02:00
case patch_type : : bp_exec :
2023-07-12 19:43:33 +02:00
case patch_type : : le32 :
case patch_type : : lef32 :
case patch_type : : bd32 :
case patch_type : : be32 :
case patch_type : : bef32 :
{
memory_size = sizeof ( u32 ) ;
break ;
}
case patch_type : : lef64 :
case patch_type : : le64 :
case patch_type : : bd64 :
case patch_type : : be64 :
case patch_type : : bef64 :
{
memory_size = sizeof ( u64 ) ;
break ;
}
case patch_type : : utf8 :
{
2023-12-29 18:33:29 +01:00
memory_size = : : size32 ( p . original_value ) ;
2023-07-12 19:43:33 +02:00
break ;
}
case patch_type : : c_utf8 :
{
2023-12-29 18:33:29 +01:00
memory_size = utils : : add_saturate < u32 > ( : : size32 ( p . original_value ) , 1 ) ;
2023-07-12 19:43:33 +02:00
break ;
}
case patch_type : : move_file :
case patch_type : : hide_file :
{
memory_size = 0 ;
break ;
}
}
if ( memory_size ! = 0 & & ! relocate_instructions_at & & ( filesz < memory_size | | offset < min_addr | | offset - min_addr > filesz - memory_size ) )
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 ;
2023-07-12 19:43:33 +02:00
auto ptr = mem_translate ( offset , memory_size ) ;
2023-06-25 14:53:42 +02:00
2023-07-24 12:24:30 +02:00
if ( ! ptr & & memory_size ! = 0 & & ! relocate_instructions_at )
2023-06-25 14:53:42 +02:00
{
// Memory translation failed
continue ;
}
2020-06-28 14:19:44 +02:00
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 :
{
2023-07-24 12:24:30 +02:00
const u32 out_branch = vm : : try_get_addr ( relocate_instructions_at ? vm : : get_super_ptr < u8 > ( offset & - 4 ) : mem_translate ( offset & - 4 , 4 ) ) . first ;
2021-09-01 12:38:17 +02:00
// 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 ) ;
2022-12-09 19:06:50 +01: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 12:38:17 +02:00
// 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
2022-11-19 12:50:31 +01:00
switch ( p . offset & patch_engine : : mem_protection : : mask )
2021-09-01 12:38:17 +02:00
{
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
2022-12-09 19:06:50 +01:00
const u32 addr = p . alloc_addr = ( relocate_instructions_at ? relocate_instructions_at : alloc_map - > alloc ( alloc_size , nullptr , 0x10000 , flags ) ) ;
2021-09-01 12:38:17 +02: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 11:33:06 +01:00
// Check if already registered by previous code allocation
2022-12-09 19:06:50 +01:00
if ( relocate_instructions_at ! = addr )
{
// Register code
ppu_register_range ( addr , alloc_size ) ;
}
2021-09-01 12:38:17 +02:00
resval = out_branch & - 4 ;
2022-11-19 12:50:31 +01: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 ;
}
2024-08-31 04:56:54 +02:00
// Record the insertion point as a faux block.
g_fxo - > need < ppu_patch_block_registry_t > ( ) ;
g_fxo - > get < ppu_patch_block_registry_t > ( ) . block_addresses . insert ( resval ) ;
2021-09-01 12:38:17 +02:00
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
{
2023-07-24 12:24:30 +02:00
const u32 out_branch = vm : : try_get_addr ( relocate_instructions_at ? vm : : get_super_ptr < u8 > ( offset & - 4 ) : mem_translate ( offset & - 4 , 4 ) ) . first ;
2021-09-01 12:38:17 +02:00
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 ;
2023-07-24 12:24:30 +02:00
const u32 out_branch = vm : : try_get_addr ( relocate_instructions_at ? vm : : get_super_ptr < u8 > ( offset & - 4 ) : mem_translate ( offset & - 4 , 4 ) ) . first ;
2021-09-06 09:33:44 +02:00
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 ) )
{
2023-07-12 19:43:33 +02:00
// Raw hexadecimal-formatted FNID (real function name cannot contain a digit at the start, derived from C/CPP which were used in PS3 development)
2021-09-06 09:33:44 +02:00
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 ;
}
2023-09-25 17:32:50 +02:00
case patch_type : : bp_exec :
{
const u32 exec_addr = vm : : try_get_addr ( relocate_instructions_at ? vm : : get_super_ptr < u8 > ( offset & - 4 ) : mem_translate ( offset & - 4 , 4 ) ) . first ;
if ( exec_addr )
{
Emu . GetCallbacks ( ) . add_breakpoint ( exec_addr ) ;
}
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 ;
}
2023-07-12 19:43:33 +02:00
case patch_type : : c_utf8 :
{
std : : memcpy ( ptr , p . original_value . data ( ) , p . original_value . size ( ) + 1 ) ;
break ;
}
2023-03-03 14:43:46 +01:00
case patch_type : : move_file :
case patch_type : : hide_file :
{
const bool is_hide = p . type = = patch_type : : hide_file ;
std : : string original_vfs_path = p . original_offset ;
std : : string dest_vfs_path = p . original_value ;
if ( original_vfs_path . empty ( ) )
{
patch_log . error ( " Failed to patch file: original path is empty " , original_vfs_path ) ;
continue ;
}
if ( ! is_hide & & dest_vfs_path . empty ( ) )
{
patch_log . error ( " Failed to patch file: destination path is empty " , dest_vfs_path ) ;
continue ;
}
if ( ! original_vfs_path . starts_with ( " /dev_ " ) )
{
original_vfs_path . insert ( 0 , " /dev_ " ) ;
}
if ( ! is_hide & & ! dest_vfs_path . starts_with ( " /dev_ " ) )
{
dest_vfs_path . insert ( 0 , " /dev_ " ) ;
}
const std : : string dest_path = is_hide ? fs : : get_config_dir ( ) + " delete_this_dir.../delete_this... " : vfs : : get ( dest_vfs_path ) ;
if ( dest_path . empty ( ) )
{
patch_log . error ( " Failed to patch file path at '%s': destination is not mounted " , original_vfs_path , dest_vfs_path ) ;
continue ;
}
if ( ! vfs : : mount ( original_vfs_path , dest_path ) )
{
patch_log . error ( " Failed to patch file path at '%s': vfs::mount(dest='%s') failed " , original_vfs_path , dest_vfs_path ) ;
continue ;
}
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
}
2024-11-11 20:54:44 +01:00
void patch_engine : : apply ( std : : vector < u32 > & applied_total , const std : : string & name , std : : function < u8 * ( u32 , u32 ) > mem_translate , 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 10:10:23 +01:00
{
2024-11-11 20:54:44 +01:00
return ;
2020-01-07 10:10:23 +01:00
}
2023-07-12 01:12:14 +02:00
const patch_container & container = : : at32 ( m_map , name ) ;
const std : : string & serial = Emu . GetTitleID ( ) ;
const std : : string & app_version = Emu . GetAppVersion ( ) ;
2020-06-02 10:39:24 +02:00
2023-07-12 19:43:33 +02:00
// Different containers in order to separate 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-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 ;
2023-02-18 16:40:12 +01:00
if ( serials . contains ( serial ) )
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
}
2023-02-18 16:40:12 +01:00
else if ( serials . contains ( patch_key : : all ) )
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 ;
}
2023-07-12 01:12:14 +02:00
const patch_app_versions & app_versions = : : at32 ( serials , found_serial ) ;
2020-06-28 14:19:44 +02:00
std : : string found_app_version ;
2020-06-27 10:32:00 +02:00
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 ) ;
2023-02-19 12:59:46 +01:00
// Move configurable values to special container for readability
p_ptr - > actual_config_values = p_ptr - > default_config_values ;
2023-02-18 23:57:23 +01:00
2023-02-19 12:59:46 +01:00
// Update configurable values
for ( auto & [ key , config_value ] : config_values . config_values )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
if ( p_ptr - > actual_config_values . contains ( key ) )
2023-02-18 23:57:23 +01:00
{
2023-02-19 20:34:21 +01:00
patch_config_value & actual_config_value = : : at32 ( p_ptr - > actual_config_values , key ) ;
actual_config_value . set_and_check_value ( config_value . value , key ) ;
2023-02-18 23:57:23 +01:00
}
}
// Sort the patch by priority
2020-06-28 14:19:44 +02:00
if ( is_all_serials )
2020-06-26 02:58:06 +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 10:32:00 +02:00
{
2023-02-18 23:57:23 +01:00
patches_for_this_serial_and_all_versions . emplace_back ( p_ptr ) ;
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
{
2023-02-18 23:57:23 +01:00
patches_for_this_serial_and_this_version . emplace_back ( p_ptr ) ;
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
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 )
{
2023-02-18 23:57:23 +01:00
patch . reset ( ) ;
2021-08-07 07:54:53 +02:00
}
}
}
}
2023-02-18 23:57:23 +01:00
// Apply modifications sequentially
2021-08-07 07:54:53 +02: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 07:54:53 +02:00
{
if ( patch )
{
2023-06-25 14:53:42 +02:00
const usz old_size = apply_modification ( applied_total , * patch , mem_translate , filesz , min_addr ) ;
2023-02-18 23:57:23 +01: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
}
}
2020-06-02 10:39:24 +02:00
}
}
2021-09-01 12:38:17 +02: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 12:38:17 +02:00
{
return ;
}
2023-07-12 01:12:14 +02:00
const patch_container & container = : : at32 ( m_map , name ) ;
2021-09-01 12:38:17 +02:00
for ( const auto & [ description , patch ] : container . patch_info_map )
{
2023-07-12 01:12:14 +02:00
for ( const patch_data & 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 ;
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 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 )
{
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-19 12:59:46 +01:00
const bool config_values_dirty = ! patch . default_config_values . empty ( ) & & ! config_values . config_values . empty ( ) & & patch . default_config_values ! = config_values . config_values ;
2023-02-18 16:40:12 +01:00
2023-02-19 12:59:46 +01:00
if ( config_values . enabled | | config_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 10:10:23 +01: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 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
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-19 12:59:46 +01:00
const auto & default_config_values = : : at32 ( container . patch_info_map , description ) . default_config_values ;
const bool config_values_dirty = ! default_config_values . empty ( ) & & ! config_values . config_values . empty ( ) & & default_config_values ! = config_values . config_values ;
2023-02-18 16:40:12 +01:00
2023-02-19 12:59:46 +01:00
if ( config_values . enabled | | config_values_dirty )
2023-02-18 16:40:12 +01:00
{
out < < app_version < < YAML : : BeginMap ;
if ( config_values . enabled )
{
out < < patch_key : : enabled < < config_values . enabled ;
}
2023-02-19 12:59:46 +01:00
if ( config_values_dirty )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
out < < patch_key : : config_values < < YAML : : BeginMap ;
2023-02-18 16:40:12 +01:00
2023-02-19 12:59:46 +01:00
for ( const auto & [ name , config_value ] : config_values . config_values )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
out < < name < < config_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 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 ( ) ) )
{
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 19:56:50 +02:00
}
2020-06-02 10:39:24 +02:00
}
2024-08-14 18:20:08 +02: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 , std : : string_view path )
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 ;
}
2023-07-12 01:12:14 +02:00
patch_engine : : patch_container & 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 ;
}
2023-07-12 01:12:14 +02:00
patch_engine : : patch_info & 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 )
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " Failed to compare patch versions ('%s' vs '%s') for %s: %s (file: %s) " , new_info . patch_version , info . patch_version , hash , description , path ) , & 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
{
2024-08-14 18:20:08 +02:00
append_log_message ( log_messages , fmt : : format ( " A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (file: %s) " , new_info . patch_version , info . patch_version , hash , description , path ) , & 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 ;
2023-08-12 13:54:10 +02:00
for ( const auto & [ app_version , patch_config ] : app_versions )
2020-06-26 02:58:06 +02:00
{
2023-08-12 13:54:10 +02:00
out < < app_version ;
2020-06-26 02:58:06 +02:00
}
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-19 12:59:46 +01:00
if ( ! info . default_config_values . empty ( ) )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
out < < patch_key : : config_values < < YAML : : BeginMap ;
2023-02-18 16:40:12 +01:00
2023-02-19 12:59:46 +01:00
for ( const auto & [ key , config_value ] : info . default_config_values )
2023-02-18 16:40:12 +01:00
{
2023-02-18 23:57:23 +01:00
out < < key < < YAML : : BeginMap ;
2023-02-19 12:59:46 +01:00
out < < patch_key : : type < < fmt : : format ( " %s " , config_value . type ) ;
out < < patch_key : : value < < config_value . value ;
2023-02-18 23:57:23 +01:00
2023-02-19 12:59:46 +01:00
switch ( config_value . type )
2023-02-18 23:57:23 +01:00
{
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : double_range :
case patch_configurable_type : : long_range :
out < < patch_key : : min < < config_value . min ;
out < < patch_key : : max < < config_value . max ;
2023-02-18 23:57:23 +01:00
break ;
2023-02-19 12:59:46 +01:00
case patch_configurable_type : : double_enum :
case patch_configurable_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
2023-02-19 12:59:46 +01:00
for ( const auto & allowed_value : config_value . allowed_values )
2023-02-18 23:57:23 +01:00
{
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 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
{
2023-07-12 01:12:14 +02:00
patch_map existing_patches ;
2020-06-12 21:09:08 +02:00
2020-09-06 11:47:45 +02:00
if ( load ( existing_patches , path , " " , true , log_messages ) )
2020-06-12 21:09:08 +02:00
{
2024-08-14 18:20:08 +02:00
append_patches ( existing_patches , patches , count , total , log_messages , path ) ;
2020-06-19 21:48:59 +02:00
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 )
{
2023-07-12 01:12:14 +02:00
patch_map patches ;
2020-06-19 19:47:51 +02:00
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
{
2023-07-12 01:12:14 +02:00
patch_container & container = patches [ info . hash ] ;
2020-06-19 19:47:51 +02:00
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 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
{
2023-08-12 13:54:10 +02:00
const std : : string & 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
{
2023-08-12 13:54:10 +02:00
const std : : string & description = patch . first . Scalar ( ) ;
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 )
{
2023-08-12 13:54:10 +02:00
const std : : string & 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 )
{
2023-08-12 13:54:10 +02:00
const std : : string & serial = serial_node . first . Scalar ( ) ;
2020-06-26 02:58:06 +02:00
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 ) ;
}
2023-02-19 12:59:46 +01:00
if ( const auto config_values_node = app_version_node . second [ patch_key : : config_values ] )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
for ( const auto config_value_node : config_values_node )
2023-02-18 16:40:12 +01:00
{
2023-02-19 12:59:46 +01:00
patch_config_value & config_value = config_values . config_values [ config_value_node . first . Scalar ( ) ] ;
config_value . value = config_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 10:10:23 +01:00
}
}
2020-06-02 10:39:24 +02:00
return config_map ;
2020-01-07 10:10:23 +01:00
}