mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-22 02:32:36 +01:00
patch manager: import patches
This commit is contained in:
parent
7d5c8fe553
commit
a7ee059419
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
LOG_CHANNEL(patch_log);
|
LOG_CHANNEL(patch_log);
|
||||||
|
|
||||||
|
static const std::string patch_engine_version = "1.0";
|
||||||
static const std::string yml_key_enable_legacy_patches = "Enable Legacy Patches";
|
static const std::string yml_key_enable_legacy_patches = "Enable Legacy Patches";
|
||||||
|
|
||||||
template <>
|
template <>
|
||||||
@ -68,15 +69,23 @@ std::string patch_engine::get_patch_config_path()
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_engine::load(patch_map& patches_map, const std::string& path)
|
static void append_log_message(std::stringstream* log_messages, const std::string& message)
|
||||||
{
|
{
|
||||||
|
if (log_messages)
|
||||||
|
*log_messages << message;
|
||||||
|
};
|
||||||
|
|
||||||
|
bool patch_engine::load(patch_map& patches_map, const std::string& path, bool importing, std::stringstream* log_messages)
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Reading file %s\n", path));
|
||||||
|
|
||||||
// Load patch file
|
// Load patch file
|
||||||
fs::file file{ path };
|
fs::file file{ path };
|
||||||
|
|
||||||
if (!file)
|
if (!file)
|
||||||
{
|
{
|
||||||
// Do nothing
|
// Do nothing
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Interpret yaml nodes
|
// Interpret yaml nodes
|
||||||
@ -84,15 +93,20 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
|
|
||||||
if (!error.empty())
|
if (!error.empty())
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, "Fatal Error: Failed to load file!\n");
|
||||||
patch_log.fatal("Failed to load patch file %s:\n%s", path, error);
|
patch_log.fatal("Failed to load patch file %s:\n%s", path, error);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load patch config to determine which patches are enabled
|
// Load patch config to determine which patches are enabled
|
||||||
bool enable_legacy_patches;
|
bool enable_legacy_patches;
|
||||||
patch_config_map patch_config = load_config(enable_legacy_patches);
|
patch_config_map patch_config;
|
||||||
|
|
||||||
|
if (!importing)
|
||||||
|
{
|
||||||
|
patch_config = load_config(enable_legacy_patches);
|
||||||
|
}
|
||||||
|
|
||||||
static const std::string target_version = "1.0";
|
|
||||||
std::string version;
|
std::string version;
|
||||||
bool is_legacy_patch = false;
|
bool is_legacy_patch = false;
|
||||||
|
|
||||||
@ -100,21 +114,32 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
{
|
{
|
||||||
version = version_node.Scalar();
|
version = version_node.Scalar();
|
||||||
|
|
||||||
if (version != target_version)
|
if (version != patch_engine_version)
|
||||||
{
|
{
|
||||||
patch_log.error("Patch engine target version %s does not match file version %s in %s", target_version, version, path);
|
append_log_message(log_messages, fmt::format("Error: Patch engine target version %s does not match file version %s\n", patch_engine_version, version));
|
||||||
return;
|
patch_log.error("Patch engine target version %s does not match file version %s in %s", patch_engine_version, version, path);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
append_log_message(log_messages, fmt::format("Patch file version: %s\n", version));
|
||||||
|
|
||||||
// We don't need the Version node in local memory anymore
|
// We don't need the Version node in local memory anymore
|
||||||
root.remove("Version");
|
root.remove("Version");
|
||||||
}
|
}
|
||||||
|
else if (importing)
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Patch engine target version %s does not match file version %s\n", patch_engine_version, version));
|
||||||
|
patch_log.error("Patch engine version %s: No 'Version' entry found for file %s", patch_engine_version, path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
patch_log.warning("Patch engine version %s: Reading legacy patch file %s", target_version, path);
|
patch_log.warning("Patch engine version %s: Reading legacy patch file %s", patch_engine_version, path);
|
||||||
is_legacy_patch = true;
|
is_legacy_patch = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_valid = true;
|
||||||
|
|
||||||
// Go through each main key in the file
|
// Go through each main key in the file
|
||||||
for (auto pair : root)
|
for (auto pair : root)
|
||||||
{
|
{
|
||||||
@ -128,7 +153,10 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
info.enabled = enable_legacy_patches;
|
info.enabled = enable_legacy_patches;
|
||||||
info.is_legacy = true;
|
info.is_legacy = true;
|
||||||
|
|
||||||
read_patch_node(info, pair.second, root);
|
if (!read_patch_node(info, pair.second, root, log_messages))
|
||||||
|
{
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
|
|
||||||
// Find or create an entry matching the key/hash in our map
|
// Find or create an entry matching the key/hash in our map
|
||||||
auto& title_info = patches_map[main_key];
|
auto& title_info = patches_map[main_key];
|
||||||
@ -142,7 +170,9 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
|
|
||||||
if (const auto yml_type = pair.second.Type(); yml_type != YAML::NodeType::Map)
|
if (const auto yml_type = pair.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping key %s: expected Map, found %s\n", main_key, yml_type));
|
||||||
patch_log.error("Skipping key %s: expected Map, found %s (file: %s)", main_key, yml_type, path);
|
patch_log.error("Skipping key %s: expected Map, found %s (file: %s)", main_key, yml_type, path);
|
||||||
|
is_valid = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,7 +199,9 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
{
|
{
|
||||||
if (const auto yml_type = patches_node.Type(); yml_type != YAML::NodeType::Map)
|
if (const auto yml_type = patches_node.Type(); yml_type != YAML::NodeType::Map)
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping Patches: expected Map, found %s (key: %s)\n", yml_type, main_key));
|
||||||
patch_log.error("Skipping Patches: expected Map, found %s (key: %s, file: %s)", yml_type, main_key, path);
|
patch_log.error("Skipping Patches: expected Map, found %s (key: %s, file: %s)", yml_type, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +226,9 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
|
|
||||||
if (const auto yml_type = patches_entry.second.Type(); yml_type != YAML::NodeType::Map)
|
if (const auto yml_type = patches_entry.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping Patch key %s: expected Map, found %s (key: %s)\n", 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);
|
patch_log.error("Skipping Patch key %s: expected Map, found %s (key: %s, file: %s)", description, yml_type, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -223,7 +257,10 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
|
|
||||||
if (const auto patch_node = patches_entry.second["Patch"])
|
if (const auto patch_node = patches_entry.second["Patch"])
|
||||||
{
|
{
|
||||||
read_patch_node(info, patch_node, root);
|
if (!read_patch_node(info, patch_node, root, log_messages))
|
||||||
|
{
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert patch information
|
// Insert patch information
|
||||||
@ -231,6 +268,8 @@ void patch_engine::load(patch_map& patches_map, const std::string& path)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return is_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_type patch_engine::get_patch_type(YAML::Node node)
|
patch_type patch_engine::get_patch_type(YAML::Node node)
|
||||||
@ -240,7 +279,7 @@ patch_type patch_engine::get_patch_type(YAML::Node node)
|
|||||||
return static_cast<patch_type>(type_val);
|
return static_cast<patch_type>(type_val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifier, const YAML::Node& root)
|
bool patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifier, const YAML::Node& root, std::stringstream* log_messages)
|
||||||
{
|
{
|
||||||
const auto type_node = node[0];
|
const auto type_node = node[0];
|
||||||
auto addr_node = node[1];
|
auto addr_node = node[1];
|
||||||
@ -267,12 +306,14 @@ void patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie
|
|||||||
if (anchor_node)
|
if (anchor_node)
|
||||||
{
|
{
|
||||||
addr_node = anchor_node;
|
addr_node = anchor_node;
|
||||||
|
append_log_message(log_messages, fmt::format("Incorrect anchor syntax found in legacy patch: %s (key: %s)", anchor, info.hash));
|
||||||
patch_log.warning("Incorrect anchor syntax found in legacy patch: %s (key: %s)", anchor, info.hash);
|
patch_log.warning("Incorrect anchor syntax found in legacy patch: %s (key: %s)", anchor, info.hash);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Anchor not found in legacy patch: %s (key: %s)", anchor, info.hash));
|
||||||
patch_log.error("Anchor not found in legacy patch: %s (key: %s)", anchor, info.hash);
|
patch_log.error("Anchor not found in legacy patch: %s (key: %s)", anchor, info.hash);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -280,53 +321,66 @@ void patch_engine::add_patch_data(YAML::Node node, patch_info& info, u32 modifie
|
|||||||
// Check if the anchor was resolved.
|
// Check if the anchor was resolved.
|
||||||
if (const auto yml_type = addr_node.Type(); yml_type != YAML::NodeType::Sequence)
|
if (const auto yml_type = addr_node.Type(); yml_type != YAML::NodeType::Sequence)
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Skipping sequence: expected Sequence, found %s (key: %s)", yml_type, info.hash));
|
||||||
patch_log.error("Skipping sequence: expected Sequence, found %s (key: %s)", yml_type, info.hash);
|
patch_log.error("Skipping sequence: expected Sequence, found %s (key: %s)", yml_type, info.hash);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address modifier (optional)
|
// Address modifier (optional)
|
||||||
const u32 mod = value_node.as<u32>(0);
|
const u32 mod = value_node.as<u32>(0);
|
||||||
|
|
||||||
|
bool is_valid = true;
|
||||||
|
|
||||||
for (const auto& item : addr_node)
|
for (const auto& item : addr_node)
|
||||||
{
|
{
|
||||||
add_patch_data(item, info, mod, root);
|
if (!add_patch_data(item, info, mod, root, log_messages))
|
||||||
|
{
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return is_valid;
|
||||||
}
|
}
|
||||||
case patch_type::bef32:
|
case patch_type::bef32:
|
||||||
case patch_type::lef32:
|
case patch_type::lef32:
|
||||||
{
|
|
||||||
p_data.value = std::bit_cast<u32>(value_node.as<f32>());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case patch_type::bef64:
|
case patch_type::bef64:
|
||||||
case patch_type::lef64:
|
case patch_type::lef64:
|
||||||
{
|
{
|
||||||
p_data.value = std::bit_cast<u64>(value_node.as<f64>());
|
p_data.value.double_value = value_node.as<f64>();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
p_data.value = value_node.as<u64>();
|
p_data.value.long_value = value_node.as<u64>();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
info.data_list.emplace_back(p_data);
|
info.data_list.emplace_back(p_data);
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_engine::read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root)
|
bool patch_engine::read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root, std::stringstream* log_messages)
|
||||||
{
|
{
|
||||||
if (const auto yml_type = node.Type(); yml_type != YAML::NodeType::Sequence)
|
if (const auto yml_type = node.Type(); yml_type != YAML::NodeType::Sequence)
|
||||||
{
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Skipping patch node %s: expected Sequence, found %s (key: %s)", info.description, yml_type, info.hash));
|
||||||
patch_log.error("Skipping patch node %s: expected Sequence, found %s (key: %s)", info.description, yml_type, info.hash);
|
patch_log.error("Skipping patch node %s: expected Sequence, found %s (key: %s)", info.description, yml_type, info.hash);
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_valid = true;
|
||||||
|
|
||||||
for (auto patch : node)
|
for (auto patch : node)
|
||||||
{
|
{
|
||||||
add_patch_data(patch, info, 0, root);
|
if (!add_patch_data(patch, info, 0, root, log_messages))
|
||||||
|
{
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return is_valid;
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_engine::append(const std::string& patch)
|
void patch_engine::append(const std::string& patch)
|
||||||
@ -341,6 +395,9 @@ void patch_engine::append_global_patches()
|
|||||||
|
|
||||||
// New patch.yml
|
// New patch.yml
|
||||||
load(m_map, fs::get_config_dir() + "patches/patch.yml");
|
load(m_map, fs::get_config_dir() + "patches/patch.yml");
|
||||||
|
|
||||||
|
// Imported patch.yml
|
||||||
|
load(m_map, fs::get_config_dir() + "patches/imported_patch.yml");
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_engine::append_title_patches(const std::string& title_id)
|
void patch_engine::append_title_patches(const std::string& title_id)
|
||||||
@ -414,41 +471,57 @@ std::size_t patch_engine::apply_patch(const std::string& name, u8* dst, u32 file
|
|||||||
}
|
}
|
||||||
case patch_type::byte:
|
case patch_type::byte:
|
||||||
{
|
{
|
||||||
*ptr = static_cast<u8>(p.value);
|
*ptr = static_cast<u8>(p.value.long_value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case patch_type::le16:
|
case patch_type::le16:
|
||||||
{
|
{
|
||||||
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value.long_value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case patch_type::le32:
|
case patch_type::le32:
|
||||||
|
{
|
||||||
|
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value.long_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case patch_type::lef32:
|
case patch_type::lef32:
|
||||||
{
|
{
|
||||||
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
*reinterpret_cast<le_t<u32, 1>*>(ptr) = std::bit_cast<u32, f32>(static_cast<f32>(p.value.double_value));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case patch_type::le64:
|
case patch_type::le64:
|
||||||
|
{
|
||||||
|
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value.long_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case patch_type::lef64:
|
case patch_type::lef64:
|
||||||
{
|
{
|
||||||
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
*reinterpret_cast<le_t<u64, 1>*>(ptr) = std::bit_cast<u64, f64>(p.value.double_value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case patch_type::be16:
|
case patch_type::be16:
|
||||||
{
|
{
|
||||||
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value.long_value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case patch_type::be32:
|
case patch_type::be32:
|
||||||
|
{
|
||||||
|
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value.long_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case patch_type::bef32:
|
case patch_type::bef32:
|
||||||
{
|
{
|
||||||
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
*reinterpret_cast<be_t<u32, 1>*>(ptr) = std::bit_cast<u32, f32>(static_cast<f32>(p.value.double_value));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case patch_type::be64:
|
case patch_type::be64:
|
||||||
|
{
|
||||||
|
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value.long_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
case patch_type::bef64:
|
case patch_type::bef64:
|
||||||
{
|
{
|
||||||
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
*reinterpret_cast<be_t<u64, 1>*>(ptr) = std::bit_cast<u64, f64>(p.value.double_value);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -523,6 +596,156 @@ void patch_engine::save_config(const patch_map& patches_map, bool enable_legacy_
|
|||||||
file.write(out.c_str(), out.size());
|
file.write(out.c_str(), out.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void append_patches(patch_engine::patch_map& existing_patches, const patch_engine::patch_map& new_patches)
|
||||||
|
{
|
||||||
|
for (const auto& [hash, new_title_info] : new_patches)
|
||||||
|
{
|
||||||
|
if (existing_patches.find(hash) == existing_patches.end())
|
||||||
|
{
|
||||||
|
existing_patches[hash] = new_title_info;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& title_info = existing_patches[hash];
|
||||||
|
|
||||||
|
if (!new_title_info.title.empty()) title_info.title = new_title_info.title;
|
||||||
|
if (!new_title_info.serials.empty()) title_info.serials = new_title_info.serials;
|
||||||
|
|
||||||
|
for (const auto& [description, new_info] : new_title_info.patch_info_map)
|
||||||
|
{
|
||||||
|
if (title_info.patch_info_map.find(description) == title_info.patch_info_map.end())
|
||||||
|
{
|
||||||
|
title_info.patch_info_map[description] = new_info;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& info = title_info.patch_info_map[description];
|
||||||
|
|
||||||
|
const auto version_is_bigger = [](const std::string& v0, const std::string& v1, const std::string& hash, const std::string& description)
|
||||||
|
{
|
||||||
|
std::add_pointer_t<char> ev0, ev1;
|
||||||
|
const double ver0 = std::strtod(v0.c_str(), &ev0);
|
||||||
|
const double ver1 = std::strtod(v1.c_str(), &ev1);
|
||||||
|
|
||||||
|
if (v0.c_str() + v0.size() == ev0 && v1.c_str() + v1.size() == ev1)
|
||||||
|
{
|
||||||
|
return ver0 > ver1;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_log.error("Failed to compare patch versions ('%s' vs '%s') for %s: %s", v0, v1, hash, description);
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!version_is_bigger(new_info.patch_version, info.patch_version, hash, description))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patch_engine::save_patches(const patch_map& patches, const std::string& path)
|
||||||
|
{
|
||||||
|
fs::file file(path, fs::rewrite);
|
||||||
|
if (!file)
|
||||||
|
{
|
||||||
|
patch_log.fatal("save_patches: Failed to open patch file %s", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
YAML::Emitter out;
|
||||||
|
out << YAML::BeginMap;
|
||||||
|
out << "Version" << "1.0";
|
||||||
|
|
||||||
|
for (const auto& [hash, title_info] : patches)
|
||||||
|
{
|
||||||
|
out << YAML::Newline << YAML::Newline;
|
||||||
|
out << hash << YAML::BeginMap;
|
||||||
|
|
||||||
|
if (!title_info.title.empty()) out << "Title" << title_info.title;
|
||||||
|
if (!title_info.serials.empty()) out << "Serials" << title_info.serials;
|
||||||
|
|
||||||
|
out << "Patches" << YAML::BeginMap;
|
||||||
|
|
||||||
|
for (auto [description, info] : title_info.patch_info_map)
|
||||||
|
{
|
||||||
|
out << description;
|
||||||
|
out << YAML::BeginMap;
|
||||||
|
|
||||||
|
if (!info.author.empty()) out << "Author" << info.author;
|
||||||
|
if (!info.patch_version.empty()) out << "Version" << info.patch_version;
|
||||||
|
if (!info.notes.empty()) out << "Notes" << info.notes;
|
||||||
|
|
||||||
|
out << "Patch";
|
||||||
|
out << YAML::BeginSeq;
|
||||||
|
|
||||||
|
for (const auto& data : info.data_list)
|
||||||
|
{
|
||||||
|
if (data.type == patch_type::load)
|
||||||
|
{
|
||||||
|
// Unreachable with current logic
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::Flow;
|
||||||
|
out << YAML::BeginSeq;
|
||||||
|
out << fmt::format("%s", data.type);
|
||||||
|
out << fmt::format("0x%.8x", data.offset);
|
||||||
|
|
||||||
|
switch (data.type)
|
||||||
|
{
|
||||||
|
case patch_type::lef32:
|
||||||
|
case patch_type::bef32:
|
||||||
|
case patch_type::lef64:
|
||||||
|
case patch_type::bef64:
|
||||||
|
{
|
||||||
|
// Using YAML formatting seems good enough for now
|
||||||
|
out << data.value.double_value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
{
|
||||||
|
out << fmt::format("0x%.8x", data.value.long_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndSeq;
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
|
|
||||||
|
file.write(out.c_str(), out.size());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool patch_engine::import_patches(const patch_engine::patch_map& patches, const std::string& path)
|
||||||
|
{
|
||||||
|
patch_engine::patch_map existing_patches;
|
||||||
|
|
||||||
|
if (load(existing_patches, path, true))
|
||||||
|
{
|
||||||
|
append_patches(existing_patches, patches);
|
||||||
|
return save_patches(existing_patches, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
patch_engine::patch_config_map patch_engine::load_config(bool& enable_legacy_patches)
|
patch_engine::patch_config_map patch_engine::load_config(bool& enable_legacy_patches)
|
||||||
{
|
{
|
||||||
enable_legacy_patches = true; // Default to true
|
enable_legacy_patches = true; // Default to true
|
||||||
|
@ -30,7 +30,11 @@ public:
|
|||||||
{
|
{
|
||||||
patch_type type = patch_type::load;
|
patch_type type = patch_type::load;
|
||||||
u32 offset = 0;
|
u32 offset = 0;
|
||||||
u64 value = 0;
|
union
|
||||||
|
{
|
||||||
|
u64 long_value;
|
||||||
|
f64 double_value;
|
||||||
|
} value { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct patch_info
|
struct patch_info
|
||||||
@ -78,24 +82,31 @@ public:
|
|||||||
// Patches:
|
// Patches:
|
||||||
// 60fps:
|
// 60fps:
|
||||||
// Author: Batman bin Suparman
|
// Author: Batman bin Suparman
|
||||||
|
// Version: 1.3
|
||||||
// Notes: This is super
|
// Notes: This is super
|
||||||
// Patch:
|
// Patch:
|
||||||
// - [ be32, 0x000e522c, 0x995d0072 ]
|
// - [ be32, 0x000e522c, 0x995d0072 ]
|
||||||
// - [ be32, 0x000e5234, 0x995d0074 ]
|
// - [ be32, 0x000e5234, 0x995d0074 ]
|
||||||
static void load(patch_map& patches, const std::string& path);
|
static bool load(patch_map& patches, const std::string& path, bool importing = false, std::stringstream* log_messages = nullptr);
|
||||||
|
|
||||||
// Read and add a patch node to the patch info
|
// Read and add a patch node to the patch info
|
||||||
static void read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root);
|
static bool read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root, std::stringstream* log_messages = nullptr);
|
||||||
|
|
||||||
// Get the patch type of a patch node
|
// Get the patch type of a patch node
|
||||||
static patch_type get_patch_type(YAML::Node node);
|
static patch_type get_patch_type(YAML::Node node);
|
||||||
|
|
||||||
// Add the data of a patch node
|
// Add the data of a patch node
|
||||||
static void add_patch_data(YAML::Node node, patch_info& info, u32 modifier, const YAML::Node& root);
|
static bool add_patch_data(YAML::Node node, patch_info& info, u32 modifier, const YAML::Node& root, std::stringstream* log_messages = nullptr);
|
||||||
|
|
||||||
// Save to patch_config.yml
|
// Save to patch_config.yml
|
||||||
static void save_config(const patch_map& patches_map, bool enable_legacy_patches);
|
static void save_config(const patch_map& patches_map, bool enable_legacy_patches);
|
||||||
|
|
||||||
|
// Save a patch file
|
||||||
|
static bool save_patches(const patch_map& patches, const std::string& path);
|
||||||
|
|
||||||
|
// Create or append patches to a file
|
||||||
|
static bool import_patches(const patch_map& patches, const std::string& path);
|
||||||
|
|
||||||
// Load patch_config.yml
|
// Load patch_config.yml
|
||||||
static patch_config_map load_config(bool& enable_legacy_patches);
|
static patch_config_map load_config(bool& enable_legacy_patches);
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
|
#include <QMessageBox>
|
||||||
|
|
||||||
#include "ui_patch_manager_dialog.h"
|
#include "ui_patch_manager_dialog.h"
|
||||||
#include "patch_manager_dialog.h"
|
#include "patch_manager_dialog.h"
|
||||||
@ -44,11 +45,6 @@ patch_manager_dialog::patch_manager_dialog(QWidget* parent)
|
|||||||
patch_engine::load_config(m_legacy_patches_enabled);
|
patch_engine::load_config(m_legacy_patches_enabled);
|
||||||
ui->cb_enable_legacy_patches->setChecked(m_legacy_patches_enabled);
|
ui->cb_enable_legacy_patches->setChecked(m_legacy_patches_enabled);
|
||||||
|
|
||||||
load_patches();
|
|
||||||
populate_tree();
|
|
||||||
|
|
||||||
resize(QGuiApplication::primaryScreen()->availableSize() * 0.7);
|
|
||||||
|
|
||||||
connect(ui->patch_filter, &QLineEdit::textChanged, this, &patch_manager_dialog::filter_patches);
|
connect(ui->patch_filter, &QLineEdit::textChanged, this, &patch_manager_dialog::filter_patches);
|
||||||
connect(ui->patch_tree, &QTreeWidget::currentItemChanged, this, &patch_manager_dialog::on_item_selected);
|
connect(ui->patch_tree, &QTreeWidget::currentItemChanged, this, &patch_manager_dialog::on_item_selected);
|
||||||
connect(ui->patch_tree, &QTreeWidget::itemChanged, this, &patch_manager_dialog::on_item_changed);
|
connect(ui->patch_tree, &QTreeWidget::itemChanged, this, &patch_manager_dialog::on_item_changed);
|
||||||
@ -61,14 +57,18 @@ patch_manager_dialog::patch_manager_dialog(QWidget* parent)
|
|||||||
{
|
{
|
||||||
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
||||||
{
|
{
|
||||||
save();
|
save_config();
|
||||||
accept();
|
accept();
|
||||||
}
|
}
|
||||||
else if (button == ui->buttonBox->button(QDialogButtonBox::Apply))
|
else if (button == ui->buttonBox->button(QDialogButtonBox::Apply))
|
||||||
{
|
{
|
||||||
save();
|
save_config();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
refresh();
|
||||||
|
|
||||||
|
resize(QGuiApplication::primaryScreen()->availableSize() * 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_manager_dialog::~patch_manager_dialog()
|
patch_manager_dialog::~patch_manager_dialog()
|
||||||
@ -76,6 +76,12 @@ patch_manager_dialog::~patch_manager_dialog()
|
|||||||
delete ui;
|
delete ui;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void patch_manager_dialog::refresh()
|
||||||
|
{
|
||||||
|
load_patches();
|
||||||
|
populate_tree();
|
||||||
|
}
|
||||||
|
|
||||||
void patch_manager_dialog::load_patches()
|
void patch_manager_dialog::load_patches()
|
||||||
{
|
{
|
||||||
// Legacy path (in case someone puts it there)
|
// Legacy path (in case someone puts it there)
|
||||||
@ -199,7 +205,7 @@ void patch_manager_dialog::populate_tree()
|
|||||||
ui->patch_tree->sortByColumn(0, Qt::SortOrder::AscendingOrder);
|
ui->patch_tree->sortByColumn(0, Qt::SortOrder::AscendingOrder);
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_manager_dialog::save()
|
void patch_manager_dialog::save_config()
|
||||||
{
|
{
|
||||||
patch_engine::save_config(m_map, m_legacy_patches_enabled);
|
patch_engine::save_config(m_map, m_legacy_patches_enabled);
|
||||||
}
|
}
|
||||||
@ -349,7 +355,115 @@ void patch_manager_dialog::on_custom_context_menu_requested(const QPoint &pos)
|
|||||||
menu->exec(ui->patch_tree->viewport()->mapToGlobal(pos));
|
menu->exec(ui->patch_tree->viewport()->mapToGlobal(pos));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool patch_manager_dialog::is_valid_file(const QMimeData& md, QStringList* drop_paths)
|
||||||
|
{
|
||||||
|
const QList<QUrl> list = md.urls(); // Get list of all the dropped file urls
|
||||||
|
|
||||||
|
if (list.size() != 1) // We only accept one file for now
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto&& url : list) // Check each file in url list for valid type
|
||||||
|
{
|
||||||
|
const QString path = url.toLocalFile(); // Convert url to filepath
|
||||||
|
const QFileInfo info = path;
|
||||||
|
|
||||||
|
if (!info.fileName().endsWith("patch.yml"))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (drop_paths)
|
||||||
|
{
|
||||||
|
drop_paths->append(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_manager_dialog::dropEvent(QDropEvent* event)
|
||||||
|
{
|
||||||
|
QStringList drop_paths;
|
||||||
|
|
||||||
|
if (!is_valid_file(*event->mimeData(), &drop_paths))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMessageBox box(QMessageBox::Icon::Question, tr("Patch Manager"), tr("What do you want to do with the patch file?"), QMessageBox::StandardButton::NoButton, this);
|
||||||
|
QAbstractButton* button_yes = box.addButton(tr("Import"), QMessageBox::YesRole);
|
||||||
|
QAbstractButton* button_no = box.addButton(tr("Validate"), QMessageBox::NoRole);
|
||||||
|
box.exec();
|
||||||
|
|
||||||
|
const bool do_import = box.clickedButton() == button_yes;
|
||||||
|
const bool do_validate = do_import || box.clickedButton() == button_no;
|
||||||
|
|
||||||
|
if (!do_validate)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto drop_path : drop_paths)
|
||||||
|
{
|
||||||
|
const auto path = drop_path.toStdString();
|
||||||
|
patch_engine::patch_map patches;
|
||||||
|
std::stringstream log_message;
|
||||||
|
|
||||||
|
if (patch_engine::load(patches, path, true, &log_message))
|
||||||
|
{
|
||||||
|
patch_log.success("Successfully validated patch file %s", path);
|
||||||
|
|
||||||
|
if (do_import)
|
||||||
|
{
|
||||||
|
static const std::string imported_patch_yml_path = fs::get_config_dir() + "patches/imported_patch.yml";
|
||||||
|
|
||||||
|
if (patch_engine::import_patches(patches, imported_patch_yml_path))
|
||||||
|
{
|
||||||
|
refresh();
|
||||||
|
QMessageBox::information(this, tr("Import successful"), tr("The patch file was imported to:\n%0").arg(QString::fromStdString(imported_patch_yml_path)));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMessageBox::information(this, tr("Import failed"), tr("The patch file was not imported.\nPlease see the log for more information."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
QMessageBox::information(this, tr("Validation successful"), tr("The patch file passed the validation."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
patch_log.error("Errors found in patch file %s", path);
|
||||||
|
QMessageBox::warning(this, tr("Validation failed"), tr("Errors were found in the patch file. Log:\n\n") + QString::fromStdString(log_message.str()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void patch_manager_dialog::on_legacy_patches_enabled(int state)
|
void patch_manager_dialog::on_legacy_patches_enabled(int state)
|
||||||
{
|
{
|
||||||
m_legacy_patches_enabled = state == Qt::CheckState::Checked;
|
m_legacy_patches_enabled = state == Qt::CheckState::Checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void patch_manager_dialog::dragEnterEvent(QDragEnterEvent* event)
|
||||||
|
{
|
||||||
|
if (is_valid_file(*event->mimeData()))
|
||||||
|
{
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_manager_dialog::dragMoveEvent(QDragMoveEvent* event)
|
||||||
|
{
|
||||||
|
if (is_valid_file(*event->mimeData()))
|
||||||
|
{
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void patch_manager_dialog::dragLeaveEvent(QDragLeaveEvent* event)
|
||||||
|
{
|
||||||
|
event->accept();
|
||||||
|
}
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
#include <QDialog>
|
#include <QDialog>
|
||||||
#include <QTreeWidgetItem>
|
#include <QTreeWidgetItem>
|
||||||
|
#include <QDragMoveEvent>
|
||||||
|
#include <QMimeData>
|
||||||
|
|
||||||
#include "Utilities/bin_patch.h"
|
#include "Utilities/bin_patch.h"
|
||||||
|
|
||||||
@ -26,14 +28,21 @@ private Q_SLOTS:
|
|||||||
void on_legacy_patches_enabled(int state);
|
void on_legacy_patches_enabled(int state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void refresh();
|
||||||
void load_patches();
|
void load_patches();
|
||||||
void populate_tree();
|
void populate_tree();
|
||||||
void save();
|
void save_config();
|
||||||
|
|
||||||
void update_patch_info(const patch_engine::patch_info& info);
|
void update_patch_info(const patch_engine::patch_info& info);
|
||||||
|
bool is_valid_file(const QMimeData& md, QStringList* drop_paths = nullptr);
|
||||||
|
|
||||||
patch_engine::patch_map m_map;
|
patch_engine::patch_map m_map;
|
||||||
bool m_legacy_patches_enabled = false;
|
bool m_legacy_patches_enabled = false;
|
||||||
|
|
||||||
Ui::patch_manager_dialog *ui;
|
Ui::patch_manager_dialog *ui;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void dropEvent(QDropEvent* event) override;
|
||||||
|
void dragEnterEvent(QDragEnterEvent* event) override;
|
||||||
|
void dragMoveEvent(QDragMoveEvent* event) override;
|
||||||
|
void dragLeaveEvent(QDragLeaveEvent* event) override;
|
||||||
};
|
};
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
<height>659</height>
|
<height>659</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="acceptDrops">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
<property name="windowTitle">
|
<property name="windowTitle">
|
||||||
<string>Patch Manager</string>
|
<string>Patch Manager</string>
|
||||||
</property>
|
</property>
|
||||||
|
Loading…
Reference in New Issue
Block a user