2020-06-02 10:39:24 +02:00
#include "bin_patch.h"
2017-03-29 02:54:05 +03:00
#include "File.h"
#include "Config.h"
2020-06-19 15:38:51 +02:00
#include "version.h"
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"
2020-11-07 18:21:23 +01:00
LOG_CHANNEL ( patch_log , "PAT" );
2020-02-01 07:15:50 +03:00
2020-06-30 00:16:24 +02:00
namespace config_key
{
static const std :: string enable_legacy_patches = "Enable Legacy Patches" ;
}
2020-06-11 15:12:44 +02:00
2020-06-02 10:39:24 +02:00
template <>
void fmt_class_string < YAML :: NodeType :: value >:: format ( std :: string & out , u64 arg )
{
format_enum ( out , arg , []( YAML :: NodeType :: value value )
{
switch ( value )
{
case YAML :: NodeType :: Undefined : return "Undefined" ;
case YAML :: NodeType :: Null : return "Null" ;
case YAML :: NodeType :: Scalar : return "Scalar" ;
case YAML :: NodeType :: Sequence : return "Sequence" ;
case YAML :: NodeType :: Map : return "Map" ;
}
return unknown ;
});
}
2017-03-29 02:54:05 +03:00
template <>
void fmt_class_string < patch_type >:: format ( std :: string & out , u64 arg )
{
format_enum ( out , arg , []( patch_type value )
{
switch ( value )
{
2020-06-13 02:16:28 +02:00
case patch_type :: invalid : return "invalid" ;
2017-09-17 17:33:59 +03:00
case patch_type :: load : return "load" ;
2017-03-29 02:54:05 +03:00
case patch_type :: byte : return "byte" ;
case patch_type :: le16 : return "le16" ;
case patch_type :: le32 : return "le32" ;
case patch_type :: le64 : return "le64" ;
2017-07-17 16:34:04 +03:00
case patch_type :: bef32 : return "bef32" ;
case patch_type :: bef64 : return "bef64" ;
2017-03-29 02:54:05 +03:00
case patch_type :: be16 : return "be16" ;
case patch_type :: be32 : return "be32" ;
case patch_type :: be64 : return "be64" ;
2017-07-17 16:34:04 +03:00
case patch_type :: lef32 : return "lef32" ;
case patch_type :: lef64 : return "lef64" ;
2017-03-29 02:54:05 +03:00
}
return unknown ;
});
}
2020-06-02 10:39:24 +02:00
patch_engine :: patch_engine ()
2017-03-29 02:54:05 +03:00
{
2020-07-22 22:02:07 +02:00
const std :: string patches_path = get_patches_path ();
2018-01-30 00:26:22 +03:00
2020-06-02 10:39:24 +02:00
if ( ! fs :: create_path ( patches_path ))
{
patch_log . fatal ( "Failed to create path: %s (%s)" , patches_path , fs :: g_tls_error );
}
}
std :: string patch_engine :: get_patch_config_path ()
{
#ifdef _WIN32
2020-06-23 21:31:32 +02:00
const std :: string config_dir = fs :: get_config_dir () + "config/" ;
const std :: string patch_path = config_dir + "patch_config.yml" ;
if ( ! fs :: create_path ( config_dir ))
{
2020-09-09 22:25:02 +02:00
patch_log . error ( "Could not create path: %s (%s)" , patch_path , fs :: g_tls_error );
2020-06-23 21:31:32 +02:00
}
return patch_path ;
2020-06-02 10:39:24 +02:00
#else
return fs :: get_config_dir () + "patch_config.yml" ;
#endif
}
2020-06-30 21:35:15 +02:00
std :: string patch_engine :: get_patches_path ()
{
return fs :: get_config_dir () + "patches/" ;
}
2020-06-19 19:47:51 +02:00
std :: string patch_engine :: get_imported_patch_path ()
{
2020-07-22 22:02:07 +02:00
return get_patches_path () + "imported_patch.yml" ;
2020-06-19 19:47:51 +02:00
}
2020-06-12 21:09:08 +02:00
static void append_log_message ( std :: stringstream * log_messages , const std :: string & message )
2020-06-02 10:39:24 +02:00
{
2020-06-12 21:09:08 +02:00
if ( log_messages )
2020-06-13 02:16:28 +02:00
* log_messages << message << std :: endl ;
2020-06-12 21:09:08 +02:00
};
2020-09-06 11:47:45 +02:00
bool patch_engine :: load ( patch_map & patches_map , const std :: string & path , std :: string content , bool importing , std :: stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
2020-09-06 11:47:45 +02:00
if ( content . empty ())
2020-06-02 10:39:24 +02:00
{
2020-09-06 11:47:45 +02:00
// Load patch file
fs :: file file { path };
if ( ! file )
{
// Do nothing
return true ;
}
content = file . to_string ();
2020-06-02 10:39:24 +02:00
}
// Interpret yaml nodes
2020-09-06 11:47:45 +02:00
auto [ root , error ] = yaml_load ( content );
2020-06-02 10:39:24 +02:00
2020-06-13 02:16:28 +02:00
if ( ! error . empty () || ! root )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , "Fatal Error: Failed to load file!" );
2020-06-02 10:39:24 +02:00
patch_log . fatal ( "Failed to load patch file %s: \n %s" , path , error );
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
// Load patch config to determine which patches are enabled
2020-06-29 19:40:24 +02:00
bool enable_legacy_patches = false ;
2020-06-26 02:58:06 +02:00
patch_map patch_config ;
2020-06-12 21:09:08 +02:00
if ( ! importing )
{
patch_config = load_config ( enable_legacy_patches );
}
2020-06-02 10:39:24 +02:00
std :: string version ;
bool is_legacy_patch = false ;
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
{
2020-06-29 20:34:40 +02:00
append_log_message ( log_messages , fmt :: format ( "Error: File version %s does not match patch engine target version %s (file: %s)" , version , patch_engine_version , path ));
2020-06-29 19:40:24 +02:00
patch_log . error ( "File version %s does not match patch engine target version %s (file: %s)" , version , patch_engine_version , path );
2020-06-12 21:09:08 +02:00
return false ;
2018-01-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
}
2020-06-12 21:09:08 +02:00
else if ( importing )
{
2020-06-30 00:16:24 +02:00
append_log_message ( log_messages , fmt :: format ( "Error: No '%s' entry found. Patch engine version = %s (file: %s)" , patch_key :: version , patch_engine_version , path ));
patch_log . error ( "No '%s' entry found. Patch engine version = %s (file: %s)" , patch_key :: version , patch_engine_version , path );
2020-06-12 21:09:08 +02:00
return false ;
}
2020-06-02 10:39:24 +02:00
else
{
2020-06-12 21:09:08 +02:00
patch_log . warning ( "Patch engine version %s: Reading legacy patch file %s" , patch_engine_version , path );
2020-06-02 10:39:24 +02:00
is_legacy_patch = true ;
}
2020-06-12 21:09:08 +02:00
bool is_valid = true ;
2020-06-02 10:39:24 +02:00
// Go through each main key in the file
for ( auto pair : root )
{
const auto & main_key = pair . first . Scalar ();
// Use old logic and yaml layout if this is a legacy patch
if ( is_legacy_patch )
2017-03-29 02:54:05 +03:00
{
2020-06-02 10:39:24 +02:00
struct patch_info info {};
2020-06-19 16:13:36 +02:00
info . hash = main_key ;
2020-06-26 02:58:06 +02:00
info . is_enabled = enable_legacy_patches ;
2020-06-19 16:13:36 +02:00
info . is_legacy = true ;
info . source_path = path ;
2018-01-30 00:26:22 +03:00
2020-06-12 21:09:08 +02:00
if ( ! read_patch_node ( info , pair . second , root , log_messages ))
{
is_valid = false ;
}
2020-06-02 10:39:24 +02:00
// Find or create an entry matching the key/hash in our map
2020-06-19 13:46:56 +02:00
auto & container = patches_map [ main_key ];
container . hash = main_key ;
container . is_legacy = true ;
container . patch_info_map [ "legacy" ] = info ;
2020-06-02 10:39:24 +02:00
continue ;
}
// Use new logic and yaml layout
if ( const auto yml_type = pair . second . Type (); yml_type != YAML :: NodeType :: Map )
{
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping key %s: expected Map, found %s" , main_key , yml_type ));
2020-06-02 10:39:24 +02:00
patch_log . error ( "Skipping key %s: expected Map, found %s (file: %s)" , main_key , yml_type , path );
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
}
// Skip Anchors
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 ];
container . is_legacy = false ;
container . hash = main_key ;
container . version = version ;
// 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
{
2020-06-26 02:58:06 +02:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping Patch key %s: expected Map, found %s (key: %s)" , description , yml_type , main_key ));
patch_log . error ( "Skipping Patch key %s: expected Map, found %s (key: %s, file: %s)" , description , yml_type , main_key , path );
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
}
2017-03-29 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
{
2020-06-26 02:58:06 +02:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping Games key: expected Map, found %s (patch: %s, key: %s)" , yml_type , description , main_key ));
patch_log . error ( "Skipping Games key: expected Map, found %s (patch: %s, key: %s, file: %s)" , yml_type , description , main_key , path );
2020-06-12 21:09:08 +02:00
is_valid = false ;
2020-06-02 10:39:24 +02:00
continue ;
2017-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
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
{
2020-06-26 02:58:06 +02:00
append_log_message ( log_messages , fmt :: format ( "Error: Skipping %s: expected Map, found %s (patch: %s, key: %s)" , title , yml_type , description , main_key ));
patch_log . error ( "Skipping %s: expected Map, found %s (patch: %s, key: %s, file: %s)" , title , yml_type , description , main_key , path );
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 ();
2020-06-27 15:24:34 +02:00
if ( serial == patch_key :: all )
{
if ( ! title_is_all_key )
{
append_log_message ( log_messages , fmt :: format ( "Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s)" , patch_key :: all , patch_key :: all , title , description , main_key ));
patch_log . error ( "Error: Using '%s' as serial is not allowed for titles other than '%s' (title: %s, patch: %s, key: %s, file: %s)" , patch_key :: all , patch_key :: all , title , description , main_key , path );
is_valid = false ;
continue ;
}
}
else if ( title_is_all_key )
{
append_log_message ( log_messages , fmt :: format ( "Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s)" , patch_key :: all , patch_key :: all , serial , description , main_key ));
patch_log . error ( "Error: Only '%s' is allowed as serial if the title is '%s' (serial: %s, patch: %s, key: %s, file: %s)" , patch_key :: all , patch_key :: all , serial , description , main_key , path );
is_valid = false ;
continue ;
}
2020-06-26 02:58:06 +02:00
if ( const auto yml_type = serial_node . second . Type (); yml_type != YAML :: NodeType :: Sequence )
{
append_log_message ( log_messages , fmt :: format ( "Error: Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s)" , serial , title , yml_type , description , main_key ));
patch_log . error ( "Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, file: %s)" , serial , title , yml_type , description , main_key , path );
is_valid = false ;
continue ;
}
patch_engine :: patch_app_versions app_versions ;
for ( const auto version : serial_node . second )
{
const auto & app_version = version . Scalar ();
// Find out if this patch was enabled in the patch config
const bool enabled = patch_config [ main_key ]. patch_info_map [ description ]. titles [ title ][ serial ][ app_version ];
app_versions . emplace ( version . Scalar (), enabled );
}
if ( app_versions . empty ())
{
append_log_message ( log_messages , fmt :: format ( "Error: Skipping %s: empty Sequence (title: %s, patch: %s, key: %s)" , serial , title , description , main_key ));
2020-06-29 13:53:39 +02:00
patch_log . error ( "Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, file: %s)" , serial , title , description , main_key , path );
2020-06-26 02:58:06 +02:00
is_valid = false ;
}
else
{
info . titles [ title ][ serial ] = app_versions ;
}
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
{
append_log_message ( log_messages , fmt :: format ( "Error: Skipping sequenced Note (patch: %s, key: %s)" , description , main_key ));
patch_log . error ( "Skipping sequenced Note (patch: %s, key: %s, file: %s)" , description , main_key , path );
is_valid = false ;
}
}
}
else
{
info . notes = notes_node . Scalar ();
}
2020-06-26 02:58:06 +02:00
}
2020-06-30 00:16:24 +02:00
if ( const auto patch_group_node = patches_entry . second [ patch_key :: group ])
2020-06-26 02:58:06 +02:00
{
info . patch_group = patch_group_node . Scalar ();
}
2020-06-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
if ( container . patch_info_map . find ( description ) != container . patch_info_map . end ())
{
bool ok ;
const auto existing_version = container . patch_info_map [ description ]. patch_version ;
const bool version_is_bigger = utils :: compare_versions ( info . patch_version , existing_version , ok ) > 0 ;
if ( ! ok || ! version_is_bigger )
{
patch_log . warning ( "A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s)" , info . patch_version , existing_version , main_key , description , path );
append_log_message ( log_messages , fmt :: format ( "A higher or equal patch version already exists ('%s' vs '%s') for %s: %s (in file %s)" , info . patch_version , existing_version , main_key , description , path ));
continue ;
}
else if ( ! importing )
{
patch_log . warning ( "A lower patch version was found ('%s' vs '%s') for %s: %s (in file %s)" , existing_version , info . patch_version , main_key , description , container . patch_info_map [ description ]. source_path );
}
}
// 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
}
2020-06-02 10:39:24 +02:00
patch_type patch_engine :: get_patch_type ( YAML :: Node node )
{
u64 type_val = 0 ;
2020-06-13 02:16:28 +02:00
if ( ! node || ! node . IsScalar () || ! cfg :: try_to_enum_value ( & type_val , & fmt_class_string < patch_type >:: format , node . Scalar ()))
{
return patch_type :: invalid ;
}
2020-06-02 10:39:24 +02:00
return static_cast < patch_type > ( type_val );
}
2020-06-12 21:09:08 +02:00
bool patch_engine :: add_patch_data ( YAML :: Node node , patch_info & info , u32 modifier , const YAML :: Node & root , std :: stringstream * log_messages )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
if ( ! node || ! node . IsSequence ())
{
append_log_message ( log_messages , fmt :: format ( "Skipping invalid patch node %s. (key: %s)" , info . description , info . hash ));
patch_log . error ( "Skipping invalid patch node %s. (key: %s)" , info . description , info . hash );
return false ;
}
2020-06-02 10:39:24 +02:00
const auto type_node = node [ 0 ];
auto addr_node = node [ 1 ];
const auto value_node = node [ 2 ];
2020-06-13 02:16:28 +02:00
const auto type = get_patch_type ( type_node );
2020-06-02 10:39:24 +02:00
2020-06-13 02:16:28 +02:00
if ( type == patch_type :: invalid )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
const auto type_str = type_node && type_node . IsScalar () ? type_node . Scalar () : "" ;
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s: type '%s' is invalid. (key: %s)" , info . description , type_str , info . hash ));
patch_log . error ( "Skipping patch node %s: type '%s' is invalid. (key: %s)" , info . description , type_str , info . hash );
return false ;
}
if ( type == patch_type :: load )
2020-06-02 10:39:24 +02:00
{
// Special syntax: anchors (named sequence)
// Most legacy patches don't use the anchor syntax correctly, so try to sanitize it.
if ( info . is_legacy )
{
if ( const auto yml_type = addr_node . Type (); yml_type == YAML :: NodeType :: Scalar )
{
2020-06-13 02:16:28 +02:00
if ( ! root )
{
patch_log . fatal ( "Trying to parse legacy patch with invalid root." ); // Sanity Check
return false ;
}
2020-06-02 10:39:24 +02:00
const auto anchor = addr_node . Scalar ();
2020-06-11 21:49:32 +02:00
const auto anchor_node = root [ anchor ];
2020-06-02 10:39:24 +02:00
2020-06-11 21:49:32 +02:00
if ( anchor_node )
{
addr_node = anchor_node ;
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt :: format ( "Incorrect anchor syntax found in legacy patch: %s (key: %s)" , anchor , info . hash ));
2020-06-11 21:49:32 +02:00
patch_log . warning ( "Incorrect anchor syntax found in legacy patch: %s (key: %s)" , anchor , info . hash );
}
else
2020-06-02 10:39:24 +02:00
{
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt :: format ( "Anchor not found in legacy patch: %s (key: %s)" , anchor , info . hash ));
2020-06-02 10:39:24 +02:00
patch_log . error ( "Anchor not found in legacy patch: %s (key: %s)" , anchor , info . hash );
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
}
}
// Check if the anchor was resolved.
if ( const auto yml_type = addr_node . Type (); yml_type != YAML :: NodeType :: Sequence )
{
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt :: format ( "Skipping sequence: expected Sequence, found %s (key: %s)" , yml_type , info . hash ));
2020-06-02 10:39:24 +02:00
patch_log . error ( "Skipping sequence: expected Sequence, found %s (key: %s)" , yml_type , info . hash );
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
// Address modifier (optional)
const u32 mod = value_node . as < u32 > ( 0 );
2020-06-12 21:09:08 +02:00
bool is_valid = true ;
2020-06-02 10:39:24 +02:00
for ( const auto & item : addr_node )
{
2020-06-12 21:09:08 +02:00
if ( ! add_patch_data ( item , info , mod , root , log_messages ))
{
is_valid = false ;
}
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
return is_valid ;
2020-06-02 10:39:24 +02:00
}
2020-06-13 02:16:28 +02:00
struct patch_data p_data {};
2020-06-19 14:04:39 +02:00
p_data . type = type ;
p_data . offset = addr_node . as < u32 > ( 0 ) + modifier ;
p_data . original_value = value_node . IsScalar () ? value_node . Scalar () : "" ;
2020-06-13 02:16:28 +02:00
2020-06-19 14:34:03 +02:00
std :: string error_message ;
switch ( p_data . type )
2020-06-02 10:39:24 +02:00
{
2020-06-19 14:34:03 +02:00
case patch_type :: bef32 :
case patch_type :: lef32 :
case patch_type :: bef64 :
case patch_type :: lef64 :
{
p_data . value . double_value = get_yaml_node_value < f64 > ( value_node , error_message );
break ;
2020-06-02 10:39:24 +02:00
}
2020-06-19 14:34:03 +02:00
default :
2020-06-02 10:39:24 +02:00
{
2020-06-19 14:34:03 +02:00
p_data . value . long_value = get_yaml_node_value < u64 > ( value_node , error_message );
break ;
}
}
if ( ! error_message . empty ())
{
error_message = fmt :: format ( "Skipping patch data entry: [ %s, 0x%.8x, %s ] (key: %s) %s" ,
p_data . type , p_data . offset , p_data . original_value . empty () ? "?" : p_data . original_value , info . hash , error_message );
2020-06-13 02:16:28 +02:00
append_log_message ( log_messages , error_message );
patch_log . error ( "%s" , error_message );
return false ;
2020-06-02 10:39:24 +02:00
}
info . data_list . emplace_back ( p_data );
2020-06-12 21:09:08 +02:00
return true ;
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
bool patch_engine :: read_patch_node ( patch_info & info , YAML :: Node node , const YAML :: Node & root , std :: stringstream * log_messages )
2020-06-02 10:39:24 +02:00
{
2020-06-13 02:16:28 +02:00
if ( ! node )
{
append_log_message ( log_messages , fmt :: format ( "Skipping invalid patch node %s. (key: %s)" , info . description , info . hash ));
2020-12-09 18:04:52 +03:00
patch_log . error ( "Skipping invalid patch node %s. (key: %s)" , info . description , info . hash );
2020-06-13 02:16:28 +02:00
return false ;
}
2020-06-02 10:39:24 +02:00
if ( const auto yml_type = node . Type (); yml_type != YAML :: NodeType :: Sequence )
{
2020-06-12 21:09:08 +02:00
append_log_message ( log_messages , fmt :: format ( "Skipping patch node %s: expected Sequence, found %s (key: %s)" , info . description , yml_type , info . hash ));
2020-06-02 10:39:24 +02:00
patch_log . error ( "Skipping patch node %s: expected Sequence, found %s (key: %s)" , info . description , yml_type , info . hash );
2020-06-12 21:09:08 +02:00
return false ;
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
bool is_valid = true ;
2020-06-02 10:39:24 +02:00
for ( auto patch : node )
{
2020-06-12 21:09:08 +02:00
if ( ! add_patch_data ( patch , info , 0 , root , log_messages ))
{
is_valid = false ;
}
2020-06-02 10:39:24 +02:00
}
2020-06-12 21:09:08 +02:00
return is_valid ;
2020-06-02 10:39:24 +02:00
}
void patch_engine :: append_global_patches ()
{
// Legacy patch.yml
load ( m_map , fs :: get_config_dir () + "patch.yml" );
// New 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 ;
}
// Legacy patch.yml
load ( m_map , fs :: get_config_dir () + "data/" + title_id + "/patch.yml" );
// New 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
}
2020-06-28 13:26:10 +02:00
std :: size_t patch_engine :: apply ( const std :: string & name , u8 * dst )
2017-03-29 02:54:05 +03:00
{
2020-06-02 10:39:24 +02:00
return apply_patch < false > ( name , dst , 0 , 0 );
2017-03-29 02:54:05 +03:00
}
2020-01-07 04:10:23 -05:00
2020-06-28 13:26:10 +02:00
std :: size_t patch_engine :: apply_with_ls_check ( const std :: string & name , u8 * dst , u32 filesz , u32 ls_addr )
2020-01-07 04:10:23 -05:00
{
2020-06-02 10:39:24 +02:00
return apply_patch < true > ( name , dst , filesz , ls_addr );
}
2020-01-07 04:10:23 -05:00
2020-06-28 14:19:44 +02:00
template < bool check_local_storage >
static std :: size_t apply_modification ( const patch_engine :: patch_info & patch , u8 * dst , u32 filesz , u32 ls_addr )
{
size_t applied = 0 ;
for ( const auto & p : patch . data_list )
{
u32 offset = p . offset ;
if constexpr ( check_local_storage )
{
if ( offset < ls_addr || offset >= ( ls_addr + filesz ))
{
// This patch is out of range for this segment
continue ;
}
2020-12-09 18:04:52 +03:00
2020-06-28 14:19:44 +02:00
offset -= ls_addr ;
}
auto ptr = dst + offset ;
switch ( p . type )
{
case patch_type :: invalid :
case patch_type :: load :
{
// Invalid in this context
continue ;
}
case patch_type :: byte :
{
* ptr = static_cast < u8 > ( p . value . long_value );
break ;
}
case patch_type :: le16 :
{
* reinterpret_cast < le_t < u16 , 1 >*> ( ptr ) = static_cast < u16 > ( p . value . long_value );
break ;
}
case patch_type :: le32 :
{
* reinterpret_cast < le_t < u32 , 1 >*> ( ptr ) = static_cast < u32 > ( p . value . long_value );
break ;
}
case patch_type :: lef32 :
{
* reinterpret_cast < le_t < u32 , 1 >*> ( ptr ) = std :: bit_cast < u32 , f32 > ( static_cast < f32 > ( p . value . double_value ));
break ;
}
case patch_type :: le64 :
{
* reinterpret_cast < le_t < u64 , 1 >*> ( ptr ) = static_cast < u64 > ( p . value . long_value );
break ;
}
case patch_type :: lef64 :
{
* reinterpret_cast < le_t < u64 , 1 >*> ( ptr ) = std :: bit_cast < u64 , f64 > ( p . value . double_value );
break ;
}
case patch_type :: be16 :
{
* reinterpret_cast < be_t < u16 , 1 >*> ( ptr ) = static_cast < u16 > ( p . value . long_value );
break ;
}
case patch_type :: be32 :
{
* reinterpret_cast < be_t < u32 , 1 >*> ( ptr ) = static_cast < u32 > ( p . value . long_value );
break ;
}
case patch_type :: bef32 :
{
* reinterpret_cast < be_t < u32 , 1 >*> ( ptr ) = std :: bit_cast < u32 , f32 > ( static_cast < f32 > ( p . value . double_value ));
break ;
}
case patch_type :: be64 :
{
* reinterpret_cast < be_t < u64 , 1 >*> ( ptr ) = static_cast < u64 > ( p . value . long_value );
break ;
}
case patch_type :: bef64 :
{
* reinterpret_cast < be_t < u64 , 1 >*> ( ptr ) = std :: bit_cast < u64 , f64 > ( p . value . double_value );
break ;
}
}
++ applied ;
}
return applied ;
}
2020-06-02 10:39:24 +02:00
template < bool check_local_storage >
2020-06-28 13:26:10 +02:00
std :: size_t patch_engine :: apply_patch ( const std :: string & name , u8 * dst , u32 filesz , u32 ls_addr )
2020-06-02 10:39:24 +02:00
{
if ( m_map . find ( name ) == m_map . cend ())
2020-01-07 04:10:23 -05:00
{
return 0 ;
}
2020-06-02 10:39:24 +02:00
size_t applied_total = 0 ;
2020-06-19 13:46:56 +02:00
const auto & container = m_map . at ( name );
2020-06-26 02:58:06 +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
2020-06-29 19:40:24 +02:00
std :: vector < patch_engine :: patch_info > legacy_patches ;
2020-06-28 14:19:44 +02:00
std :: vector < patch_engine :: patch_info > patches_for_this_serial_and_this_version ;
std :: vector < patch_engine :: patch_info > patches_for_this_serial_and_all_versions ;
std :: vector < patch_engine :: patch_info > patches_for_all_serials_and_this_version ;
std :: vector < patch_engine :: patch_info > patches_for_all_serials_and_all_versions ;
// 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 legacy patch is enabled
2020-06-29 19:40:24 +02:00
if ( patch . is_legacy )
2020-06-26 02:58:06 +02:00
{
2020-06-29 19:40:24 +02:00
if ( patch . is_enabled )
{
legacy_patches . push_back ( patch );
}
2020-06-26 02:58:06 +02:00
continue ;
}
2020-06-28 14:19:44 +02:00
// Find out if this patch is enabled
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : patch . titles )
{
2020-06-28 14:19:44 +02:00
bool is_all_serials = false ;
bool is_all_versions = false ;
2020-06-27 10:32:00 +02:00
std :: string found_serial ;
2020-06-27 14:14:08 +02:00
if ( serials . find ( serial ) != serials . end ())
2020-06-27 10:32:00 +02:00
{
found_serial = serial ;
}
2020-06-27 14:14:08 +02:00
else if ( serials . find ( patch_key :: all ) != serials . end ())
{
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
2020-06-28 14:19:44 +02:00
const auto & app_versions = serials . at ( found_serial );
std :: string found_app_version ;
if ( app_versions . find ( app_version ) != app_versions . end ())
{
found_app_version = app_version ;
}
else if ( app_versions . find ( patch_key :: all ) != app_versions . end ())
{
found_app_version = patch_key :: all ;
is_all_versions = true ;
}
if ( ! found_app_version . empty () && app_versions . at ( found_app_version ))
{
// This patch is enabled
if ( is_all_serials )
2020-06-27 10:32:00 +02:00
{
2020-06-28 14:19:44 +02:00
if ( is_all_versions )
{
patches_for_all_serials_and_all_versions . push_back ( patch );
}
else
{
patches_for_all_serials_and_this_version . push_back ( patch );
}
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
{
2020-06-28 14:19:44 +02:00
patches_for_this_serial_and_all_versions . push_back ( patch );
}
else
{
patches_for_this_serial_and_this_version . push_back ( patch );
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
2020-06-28 14:19:44 +02:00
// Sort specific patches in front of global patches
std :: vector < patch_engine :: patch_info > sorted_patches ;
2020-06-29 19:40:24 +02:00
sorted_patches . insert ( sorted_patches . end (), legacy_patches . begin (), legacy_patches . end ());
2020-06-28 14:19:44 +02:00
sorted_patches . insert ( sorted_patches . end (), patches_for_this_serial_and_this_version . begin (), patches_for_this_serial_and_this_version . end ());
sorted_patches . insert ( sorted_patches . end (), patches_for_this_serial_and_all_versions . begin (), patches_for_this_serial_and_all_versions . end ());
sorted_patches . insert ( sorted_patches . end (), patches_for_all_serials_and_this_version . begin (), patches_for_all_serials_and_this_version . end ());
sorted_patches . insert ( sorted_patches . end (), patches_for_all_serials_and_all_versions . begin (), patches_for_all_serials_and_all_versions . end ());
2020-01-07 04:10:23 -05:00
2020-06-28 14:19:44 +02:00
// Apply modifications sequentially
for ( const auto & patch : sorted_patches )
{
2020-06-27 14:13:28 +02:00
if ( ! patch . patch_group . empty ())
{
2020-06-28 13:26:10 +02:00
if ( m_applied_groups . contains ( patch . patch_group ))
2020-06-27 14:13:28 +02:00
{
continue ;
}
2020-06-28 13:26:10 +02:00
m_applied_groups . insert ( patch . patch_group );
2020-06-27 14:13:28 +02:00
}
2020-06-28 14:19:44 +02:00
const size_t applied = apply_modification < check_local_storage > ( patch , dst , filesz , ls_addr );
applied_total += applied ;
2020-06-02 10:39:24 +02:00
2020-06-28 14:19:44 +02:00
if ( patch . is_legacy )
2020-01-07 04:10:23 -05:00
{
2020-07-14 06:49:13 +11:00
patch_log . success ( "Applied legacy patch (hash='%s')(<- %d)" , patch . hash , applied );
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
else
2020-01-07 04:10:23 -05:00
{
2020-07-14 06:49:13 +11:00
patch_log . success ( "Applied patch (hash='%s', description='%s', author='%s', patch_version='%s', file_version='%s') (<- %d)" , patch . hash , patch . description , patch . author , patch . patch_version , patch . version , applied );
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
}
return applied_total ;
}
2020-06-11 15:12:44 +02:00
void patch_engine :: save_config ( const patch_map & patches_map , bool enable_legacy_patches )
2020-06-02 10:39:24 +02:00
{
const std :: string path = get_patch_config_path ();
patch_log . notice ( "Saving patch config file %s" , path );
fs :: file file ( path , fs :: rewrite );
if ( ! file )
{
2020-09-09 22:25:02 +02:00
patch_log . fatal ( "Failed to open patch config file %s (%s)" , path , fs :: g_tls_error );
2020-06-02 10:39:24 +02:00
return ;
}
YAML :: Emitter out ;
out << YAML :: BeginMap ;
2020-06-11 15:12:44 +02:00
// Save "Enable Legacy Patches"
2020-06-30 00:16:24 +02:00
out << config_key :: enable_legacy_patches << enable_legacy_patches ;
2020-06-11 15:12:44 +02:00
2020-06-26 02:58:06 +02:00
// Save 'enabled' state per hash, description, serial and app_version
patch_map config_map ;
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , container ] : patches_map )
2020-06-02 10:39:24 +02:00
{
2020-06-19 13:46:56 +02:00
if ( container . is_legacy )
2020-01-07 04:10:23 -05:00
{
2020-06-02 10:39:24 +02:00
continue ;
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ description , patch ] : container . patch_info_map )
2020-01-07 04:10:23 -05:00
{
2020-06-26 02:58:06 +02:00
if ( patch . is_legacy )
{
continue ;
}
for ( const auto & [ title , serials ] : patch . titles )
{
for ( const auto & [ serial , app_versions ] : serials )
{
for ( const auto & [ app_version , enabled ] : app_versions )
{
if ( enabled )
{
config_map [ hash ]. patch_info_map [ description ]. titles [ title ][ serial ][ app_version ] = true ;
}
}
}
}
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
2020-06-26 02:58:06 +02:00
if ( const auto & enabled_patches = config_map [ hash ]. patch_info_map ; enabled_patches . size () > 0 )
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
2020-06-26 02:58:06 +02:00
for ( const auto & [ description , patch ] : enabled_patches )
2020-06-02 10:39:24 +02:00
{
2020-06-26 02:58:06 +02:00
const auto & titles = patch . titles ;
out << description << YAML :: BeginMap ;
for ( const auto & [ title , serials ] : titles )
{
out << title << YAML :: BeginMap ;
for ( const auto & [ serial , app_versions ] : serials )
{
out << serial << YAML :: BeginMap ;
for ( const auto & [ app_version , enabled ] : app_versions )
{
out << app_version << enabled ;
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
2020-06-02 10:39:24 +02:00
}
out << YAML :: EndMap ;
2020-01-07 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 ;
file . write ( out . c_str (), out . size ());
}
2020-06-19 21:48:59 +02:00
static void append_patches ( patch_engine :: patch_map & existing_patches , const patch_engine :: patch_map & new_patches , size_t & count , size_t & total , std :: stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , new_container ] : new_patches )
2020-06-12 21:09:08 +02:00
{
2020-06-19 21:48:59 +02:00
total += new_container . patch_info_map . size ();
2020-06-12 21:09:08 +02:00
if ( existing_patches . find ( hash ) == existing_patches . end ())
{
2020-06-19 13:46:56 +02:00
existing_patches [ hash ] = new_container ;
2020-06-19 21:48:59 +02:00
count += new_container . patch_info_map . size ();
2020-06-12 21:09:08 +02:00
continue ;
}
2020-06-19 13:46:56 +02:00
auto & container = existing_patches [ hash ];
2020-06-12 21:09:08 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ description , new_info ] : new_container . patch_info_map )
2020-06-12 21:09:08 +02:00
{
2020-06-19 13:46:56 +02:00
if ( container . patch_info_map . find ( description ) == container . patch_info_map . end ())
2020-06-12 21:09:08 +02:00
{
2020-06-19 13:46:56 +02:00
container . patch_info_map [ description ] = new_info ;
2020-06-19 21:48:59 +02:00
count ++ ;
2020-06-12 21:09:08 +02:00
continue ;
}
2020-06-19 13:46:56 +02:00
auto & info = container . patch_info_map [ description ];
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
bool ok ;
const bool version_is_bigger = utils :: compare_versions ( new_info . patch_version , info . patch_version , ok ) > 0 ;
if ( ! ok )
2020-06-12 21:09:08 +02:00
{
2020-06-19 15:38:51 +02:00
patch_log . error ( "Failed to compare patch versions ('%s' vs '%s') for %s: %s" , new_info . patch_version , info . patch_version , hash , description );
append_log_message ( log_messages , fmt :: format ( "Failed to compare patch versions ('%s' vs '%s') for %s: %s" , new_info . patch_version , info . patch_version , hash , description ));
2020-06-19 21:48:59 +02:00
continue ;
2020-06-19 15:38:51 +02:00
}
2020-06-12 21:09:08 +02:00
2020-06-19 15:38:51 +02:00
if ( ! version_is_bigger )
2020-06-12 21:09:08 +02:00
{
2020-06-19 15:38:51 +02:00
patch_log . error ( "A higher or equal patch version already exists ('%s' vs '%s') for %s: %s" , new_info . patch_version , info . patch_version , hash , description );
append_log_message ( log_messages , fmt :: format ( "A higher or equal patch version already exists ('%s' vs '%s') for %s: %s" , new_info . patch_version , info . patch_version , hash , description ));
2020-06-19 21:48:59 +02:00
continue ;
2020-06-12 21:09:08 +02:00
}
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , new_serials ] : new_info . titles )
{
for ( const auto & [ serial , new_app_versions ] : new_serials )
{
if ( ! new_app_versions . empty ())
{
info . titles [ title ][ serial ]. insert ( new_app_versions . begin (), new_app_versions . end ());
}
}
}
2020-06-12 21:09:08 +02:00
if ( ! new_info . patch_version . empty ()) info . patch_version = new_info . patch_version ;
if ( ! new_info . author . empty ()) info . author = new_info . author ;
if ( ! new_info . notes . empty ()) info . notes = new_info . notes ;
if ( ! new_info . data_list . empty ()) info . data_list = new_info . data_list ;
2020-06-19 16:13:36 +02:00
if ( ! new_info . source_path . empty ()) info . source_path = new_info . source_path ;
2020-06-19 21:48:59 +02:00
count ++ ;
2020-06-12 21:09:08 +02:00
}
}
}
2020-06-19 15:38:51 +02:00
bool patch_engine :: save_patches ( const patch_map & patches , const std :: string & path , std :: stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
fs :: file file ( path , fs :: rewrite );
if ( ! file )
{
2020-09-09 22:25:02 +02:00
patch_log . fatal ( "save_patches: Failed to open patch file %s (%s)" , path , fs :: g_tls_error );
append_log_message ( log_messages , fmt :: format ( "Failed to open patch file %s (%s)" , path , fs :: g_tls_error ));
2020-06-12 21:09:08 +02:00
return false ;
}
YAML :: Emitter out ;
out << YAML :: BeginMap ;
2020-06-30 00:16:24 +02:00
out << patch_key :: version << patch_engine_version ;
2020-06-12 21:09:08 +02:00
2020-06-19 13:46:56 +02:00
for ( const auto & [ hash , container ] : patches )
2020-06-12 21:09:08 +02:00
{
2020-06-19 19:47:51 +02:00
if ( container . patch_info_map . empty ())
{
continue ;
}
2020-06-12 21:09:08 +02:00
out << YAML :: Newline << YAML :: Newline ;
out << hash << YAML :: BeginMap ;
2020-06-26 02:58:06 +02:00
for ( const auto & [ description , info ] : container . patch_info_map )
2020-06-12 21:09:08 +02:00
{
2020-06-26 02:58:06 +02:00
out << description << YAML :: BeginMap ;
2020-06-30 00:16:24 +02:00
out << patch_key :: games << YAML :: BeginMap ;
2020-06-26 02:58:06 +02:00
for ( const auto & [ title , serials ] : info . titles )
{
out << title << YAML :: BeginMap ;
for ( const auto & [ serial , app_versions ] : serials )
{
out << serial << YAML :: BeginSeq ;
2020-06-30 00:16:24 +02:00
for ( const auto & app_version : app_versions )
2020-06-26 02:58:06 +02:00
{
out << app_version . first ;
}
out << YAML :: EndSeq ;
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
2020-06-12 21:09:08 +02:00
2020-06-30 00:16:24 +02:00
if ( ! info . author . empty ()) out << patch_key :: author << info . author ;
if ( ! info . patch_version . empty ()) out << patch_key :: patch_version << info . patch_version ;
if ( ! info . patch_group . empty ()) out << patch_key :: group << info . patch_group ;
if ( ! info . notes . empty ()) out << patch_key :: notes << info . notes ;
2020-06-12 21:09:08 +02:00
2020-06-30 00:16:24 +02:00
out << patch_key :: patch << YAML :: BeginSeq ;
2020-06-12 21:09:08 +02:00
for ( const auto & data : info . data_list )
{
2020-06-13 02:16:28 +02:00
if ( data . type == patch_type :: invalid || data . type == patch_type :: load )
2020-06-12 21:09:08 +02:00
{
// Unreachable with current logic
continue ;
}
out << YAML :: Flow ;
out << YAML :: BeginSeq ;
out << fmt :: format ( "%s" , data . type );
out << fmt :: format ( "0x%.8x" , data . offset );
2020-06-19 14:04:39 +02:00
out << data . original_value ;
2020-06-12 21:09:08 +02:00
out << YAML :: EndSeq ;
}
out << YAML :: EndSeq ;
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
}
out << YAML :: EndMap ;
file . write ( out . c_str (), out . size ());
return true ;
}
2020-06-19 21:48:59 +02:00
bool patch_engine :: import_patches ( const patch_engine :: patch_map & patches , const std :: string & path , size_t & count , size_t & total , std :: stringstream * log_messages )
2020-06-12 21:09:08 +02:00
{
patch_engine :: patch_map existing_patches ;
2020-09-06 11:47:45 +02:00
if ( load ( existing_patches , path , "" , true , log_messages ))
2020-06-12 21:09:08 +02:00
{
2020-06-19 21:48:59 +02:00
append_patches ( existing_patches , patches , count , total , log_messages );
return count == 0 || save_patches ( existing_patches , path , log_messages );
2020-06-12 21:09:08 +02:00
}
return false ;
}
2020-06-19 19:47:51 +02:00
bool patch_engine :: remove_patch ( const patch_info & info )
{
patch_engine :: patch_map patches ;
if ( load ( patches , info . source_path ))
{
if ( patches . find ( info . hash ) != patches . end ())
{
auto & container = patches [ info . hash ];
if ( container . patch_info_map . find ( info . description ) != container . patch_info_map . end ())
{
container . patch_info_map . erase ( info . description );
return save_patches ( patches , info . source_path );
}
}
}
return false ;
}
2020-06-26 02:58:06 +02:00
patch_engine :: patch_map patch_engine :: load_config ( bool & enable_legacy_patches )
2020-06-02 10:39:24 +02:00
{
2020-06-11 15:12:44 +02:00
enable_legacy_patches = true ; // Default to true
2020-06-26 02:58:06 +02:00
patch_map config_map ;
2020-06-02 10:39:24 +02:00
const std :: string path = get_patch_config_path ();
patch_log . notice ( "Loading patch config file %s" , path );
if ( fs :: file f { path })
{
auto [ root , error ] = yaml_load ( f . to_string ());
if ( ! error . empty ())
2020-01-07 04:10:23 -05:00
{
2020-06-02 10:39:24 +02:00
patch_log . fatal ( "Failed to load patch config file %s: \n %s" , path , error );
return config_map ;
2020-01-07 04:10:23 -05:00
}
2020-06-02 10:39:24 +02:00
2020-06-11 15:12:44 +02:00
// Try to load "Enable Legacy Patches" (default to true)
2020-06-30 00:16:24 +02:00
if ( auto enable_legacy_node = root [ config_key :: enable_legacy_patches ])
2020-06-11 15:12:44 +02:00
{
enable_legacy_patches = enable_legacy_node . as < bool > ( true );
2020-06-30 00:16:24 +02:00
root . remove ( config_key :: enable_legacy_patches ); // Remove the node in order to skip it in the next part
2020-06-11 15:12:44 +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 ();
const bool enabled = app_version_node . second . as < bool > ( false );
config_map [ hash ]. patch_info_map [ description ]. titles [ title ][ serial ][ app_version ] = enabled ;
}
}
}
2020-06-02 10:39:24 +02:00
}
2020-01-07 04:10:23 -05:00
}
}
2020-06-02 10:39:24 +02:00
return config_map ;
2020-01-07 04:10:23 -05:00
}