mirror of
https://github.com/RPCS3/rpcs3.git
synced 2024-11-25 12:12:50 +01:00
patch manager
This commit is contained in:
parent
1668900fb8
commit
2dca8d84e1
@ -1,10 +1,27 @@
|
||||
#include "bin_patch.h"
|
||||
#include "util/yaml.hpp"
|
||||
#include "bin_patch.h"
|
||||
#include "File.h"
|
||||
#include "Config.h"
|
||||
|
||||
LOG_CHANNEL(patch_log);
|
||||
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
template <>
|
||||
void fmt_class_string<patch_type>::format(std::string& out, u64 arg)
|
||||
{
|
||||
@ -30,219 +47,507 @@ void fmt_class_string<patch_type>::format(std::string& out, u64 arg)
|
||||
});
|
||||
}
|
||||
|
||||
void patch_engine::append(const std::string& patch)
|
||||
patch_engine::patch_engine()
|
||||
{
|
||||
if (fs::file f{patch})
|
||||
{
|
||||
auto [root, error] = yaml_load(f.to_string());
|
||||
const std::string patches_path = fs::get_config_dir() + "patches/";
|
||||
|
||||
if (!error.empty())
|
||||
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
|
||||
return fs::get_config_dir() + "config/patch_config.yml";
|
||||
#else
|
||||
return fs::get_config_dir() + "patch_config.yml";
|
||||
#endif
|
||||
}
|
||||
|
||||
void patch_engine::load(patch_map& patches_map, const std::string& path)
|
||||
{
|
||||
// Load patch file
|
||||
fs::file file{ path };
|
||||
|
||||
if (!file)
|
||||
{
|
||||
// Do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
// Interpret yaml nodes
|
||||
auto [root, error] = yaml_load(file.to_string());
|
||||
|
||||
if (!error.empty())
|
||||
{
|
||||
patch_log.fatal("Failed to load patch file %s:\n%s", path, error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Load patch config to determine which patches are enabled
|
||||
patch_config_map patch_config = load_config();
|
||||
|
||||
static const std::string target_version = "1.0";
|
||||
std::string version;
|
||||
bool is_legacy_patch = false;
|
||||
|
||||
if (const auto version_node = root["Version"])
|
||||
{
|
||||
version = version_node.Scalar();
|
||||
|
||||
if (version != target_version)
|
||||
{
|
||||
patch_log.fatal("Failed to load patch file %s:\n%s", patch, error);
|
||||
patch_log.error("Patch engine target version %s does not match file version %s in %s", target_version, version, path);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto pair : root)
|
||||
// We don't need the Version node in local memory anymore
|
||||
root.remove("Version");
|
||||
}
|
||||
else
|
||||
{
|
||||
patch_log.warning("Patch engine version %s: Reading legacy patch file %s", target_version, path);
|
||||
is_legacy_patch = true;
|
||||
}
|
||||
|
||||
// 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)
|
||||
{
|
||||
auto& name = pair.first.Scalar();
|
||||
auto& data = m_map[name];
|
||||
struct patch_info info{};
|
||||
info.hash = main_key;
|
||||
info.enabled = true;
|
||||
info.is_legacy = true;
|
||||
|
||||
for (auto patch : pair.second)
|
||||
read_patch_node(info, pair.second, root);
|
||||
|
||||
// Find or create an entry matching the key/hash in our map
|
||||
auto& title_info = patches_map[main_key];
|
||||
title_info.hash = main_key;
|
||||
title_info.is_legacy = true;
|
||||
title_info.patch_info_map["legacy"] = info;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use new logic and yaml layout
|
||||
|
||||
if (const auto yml_type = pair.second.Type(); yml_type != YAML::NodeType::Map)
|
||||
{
|
||||
patch_log.error("Skipping key %s: expected Map, found %s (file: %s)", main_key, yml_type, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip Anchors
|
||||
if (main_key == "Anchors")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string title;
|
||||
std::string serials;
|
||||
|
||||
if (const auto title_node = pair.second["Title"])
|
||||
{
|
||||
title = title_node.Scalar();
|
||||
}
|
||||
|
||||
if (const auto serials_node = pair.second["Serials"])
|
||||
{
|
||||
serials = serials_node.Scalar();
|
||||
}
|
||||
|
||||
if (const auto patches_node = pair.second["Patches"])
|
||||
{
|
||||
if (const auto yml_type = patches_node.Type(); yml_type != YAML::NodeType::Map)
|
||||
{
|
||||
u64 type64 = 0;
|
||||
cfg::try_to_enum_value(&type64, &fmt_class_string<patch_type>::format, patch[0].Scalar());
|
||||
patch_log.error("Skipping Patches: expected Map, found %s (key: %s, file: %s)", yml_type, main_key, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct patch info{};
|
||||
info.type = static_cast<patch_type>(type64);
|
||||
info.offset = patch[1].as<u32>(0);
|
||||
// Find or create an entry matching the key/hash in our map
|
||||
auto& title_info = patches_map[main_key];
|
||||
title_info.is_legacy = false;
|
||||
title_info.hash = main_key;
|
||||
title_info.title = title;
|
||||
title_info.serials = serials;
|
||||
title_info.version = version;
|
||||
|
||||
switch (info.type)
|
||||
// Go through each patch
|
||||
for (auto patches_entry : patches_node)
|
||||
{
|
||||
// Each key in "Patches" is also the patch description
|
||||
const std::string description = patches_entry.first.Scalar();
|
||||
|
||||
// Find out if this patch was enabled in the patch config
|
||||
const bool enabled = patch_config[main_key][description];
|
||||
|
||||
// Compile patch information
|
||||
|
||||
if (const auto yml_type = patches_entry.second.Type(); yml_type != YAML::NodeType::Map)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// Special syntax: copy named sequence (must be loaded before)
|
||||
const auto found = m_map.find(patch[1].Scalar());
|
||||
|
||||
if (found != m_map.end())
|
||||
{
|
||||
// Address modifier (optional)
|
||||
const u32 mod = patch[2].as<u32>(0);
|
||||
|
||||
for (const auto& rd : found->second)
|
||||
{
|
||||
info = rd;
|
||||
info.offset += mod;
|
||||
data.emplace_back(info);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO: error
|
||||
break;
|
||||
}
|
||||
case patch_type::bef32:
|
||||
case patch_type::lef32:
|
||||
{
|
||||
info.value = std::bit_cast<u32>(patch[2].as<f32>());
|
||||
break;
|
||||
}
|
||||
case patch_type::bef64:
|
||||
case patch_type::lef64:
|
||||
{
|
||||
info.value = std::bit_cast<u64>(patch[2].as<f64>());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
info.value = patch[2].as<u64>();
|
||||
break;
|
||||
}
|
||||
patch_log.error("Skipping Patch key %s: expected Map, found %s (key: %s, file: %s)", description, yml_type, main_key, path);
|
||||
continue;
|
||||
}
|
||||
|
||||
data.emplace_back(info);
|
||||
struct patch_info info {};
|
||||
info.enabled = enabled;
|
||||
info.description = description;
|
||||
info.hash = main_key;
|
||||
info.version = version;
|
||||
info.serials = serials;
|
||||
info.title = title;
|
||||
|
||||
if (const auto author_node = patches_entry.second["Author"])
|
||||
{
|
||||
info.author = author_node.Scalar();
|
||||
}
|
||||
|
||||
if (const auto patch_version_node = patches_entry.second["Version"])
|
||||
{
|
||||
info.patch_version = patch_version_node.Scalar();
|
||||
}
|
||||
|
||||
if (const auto notes_node = patches_entry.second["Notes"])
|
||||
{
|
||||
info.notes = notes_node.Scalar();
|
||||
}
|
||||
|
||||
if (const auto patch_node = patches_entry.second["Patch"])
|
||||
{
|
||||
read_patch_node(info, patch_node, root);
|
||||
}
|
||||
|
||||
// Insert patch information
|
||||
title_info.patch_info_map[description] = info;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
patch_type patch_engine::get_patch_type(YAML::Node node)
|
||||
{
|
||||
u64 type_val = 0;
|
||||
cfg::try_to_enum_value(&type_val, &fmt_class_string<patch_type>::format, node.Scalar());
|
||||
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)
|
||||
{
|
||||
const auto type_node = node[0];
|
||||
auto addr_node = node[1];
|
||||
const auto value_node = node[2];
|
||||
|
||||
struct patch_data p_data{};
|
||||
p_data.type = get_patch_type(type_node);
|
||||
p_data.offset = addr_node.as<u32>(0) + modifier;
|
||||
|
||||
switch (p_data.type)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
const auto anchor = addr_node.Scalar();
|
||||
patch_log.warning("Incorrect anchor syntax found in legacy patch: %s (key: %s)", anchor, info.hash);
|
||||
|
||||
if (!(addr_node = root[anchor]))
|
||||
{
|
||||
patch_log.error("Anchor not found in legacy patch: %s (key: %s)", anchor, info.hash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check if the anchor was resolved.
|
||||
if (const auto yml_type = addr_node.Type(); yml_type != YAML::NodeType::Sequence)
|
||||
{
|
||||
patch_log.error("Skipping sequence: expected Sequence, found %s (key: %s)", yml_type, info.hash);
|
||||
return;
|
||||
}
|
||||
|
||||
// Address modifier (optional)
|
||||
const u32 mod = value_node.as<u32>(0);
|
||||
|
||||
for (const auto& item : addr_node)
|
||||
{
|
||||
add_patch_data(item, info, mod, root);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
case patch_type::bef32:
|
||||
case patch_type::lef32:
|
||||
{
|
||||
p_data.value = std::bit_cast<u32>(value_node.as<f32>());
|
||||
break;
|
||||
}
|
||||
case patch_type::bef64:
|
||||
case patch_type::lef64:
|
||||
{
|
||||
p_data.value = std::bit_cast<u64>(value_node.as<f64>());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
p_data.value = value_node.as<u64>();
|
||||
}
|
||||
}
|
||||
|
||||
info.data_list.emplace_back(p_data);
|
||||
}
|
||||
|
||||
void patch_engine::read_patch_node(patch_info& info, YAML::Node node, const YAML::Node& root)
|
||||
{
|
||||
if (const auto yml_type = node.Type(); yml_type != YAML::NodeType::Sequence)
|
||||
{
|
||||
patch_log.error("Skipping patch node %s: expected Sequence, found %s (key: %s)", info.description, yml_type, info.hash);
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto patch : node)
|
||||
{
|
||||
add_patch_data(patch, info, 0, root);
|
||||
}
|
||||
}
|
||||
|
||||
void patch_engine::append(const std::string& patch)
|
||||
{
|
||||
load(m_map, patch);
|
||||
}
|
||||
|
||||
void patch_engine::append_global_patches()
|
||||
{
|
||||
// Legacy patch.yml
|
||||
load(m_map, fs::get_config_dir() + "patch.yml");
|
||||
|
||||
// New patch.yml
|
||||
load(m_map, fs::get_config_dir() + "patches/patch.yml");
|
||||
}
|
||||
|
||||
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
|
||||
load(m_map, fs::get_config_dir() + "patches/" + title_id + "_patch.yml");
|
||||
}
|
||||
|
||||
std::size_t patch_engine::apply(const std::string& name, u8* dst) const
|
||||
{
|
||||
const auto found = m_map.find(name);
|
||||
|
||||
if (found == m_map.cend())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply modifications sequentially
|
||||
for (const auto& p : found->second)
|
||||
{
|
||||
auto ptr = dst + p.offset;
|
||||
|
||||
switch (p.type)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// Invalid in this context
|
||||
break;
|
||||
}
|
||||
case patch_type::byte:
|
||||
{
|
||||
*ptr = static_cast<u8>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le16:
|
||||
{
|
||||
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le32:
|
||||
case patch_type::lef32:
|
||||
{
|
||||
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le64:
|
||||
case patch_type::lef64:
|
||||
{
|
||||
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be16:
|
||||
{
|
||||
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be32:
|
||||
case patch_type::bef32:
|
||||
{
|
||||
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be64:
|
||||
case patch_type::bef64:
|
||||
{
|
||||
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return found->second.size();
|
||||
return apply_patch<false>(name, dst, 0, 0);
|
||||
}
|
||||
|
||||
std::size_t patch_engine::apply_with_ls_check(const std::string& name, u8* dst, u32 filesz, u32 ls_addr) const
|
||||
{
|
||||
u32 rejected = 0;
|
||||
return apply_patch<true>(name, dst, filesz, ls_addr);
|
||||
}
|
||||
|
||||
const auto found = m_map.find(name);
|
||||
|
||||
if (found == m_map.cend())
|
||||
template <bool check_local_storage>
|
||||
std::size_t patch_engine::apply_patch(const std::string& name, u8* dst, u32 filesz, u32 ls_addr) const
|
||||
{
|
||||
if (m_map.find(name) == m_map.cend())
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Apply modifications sequentially
|
||||
for (const auto& p : found->second)
|
||||
{
|
||||
auto ptr = dst + (p.offset - ls_addr);
|
||||
size_t applied_total = 0;
|
||||
const auto& title_info = m_map.at(name);
|
||||
|
||||
if(p.offset < ls_addr || p.offset >= (ls_addr + filesz))
|
||||
// Apply modifications sequentially
|
||||
for (const auto& [description, patch] : title_info.patch_info_map)
|
||||
{
|
||||
if (!patch.enabled)
|
||||
{
|
||||
// This patch is out of range for this segment
|
||||
rejected++;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (p.type)
|
||||
size_t applied = 0;
|
||||
|
||||
for (const auto& p : patch.data_list)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// Invalid in this context
|
||||
break;
|
||||
u32 offset = p.offset;
|
||||
|
||||
if constexpr (check_local_storage)
|
||||
{
|
||||
offset -= ls_addr;
|
||||
|
||||
if (offset < ls_addr || offset >= (ls_addr + filesz))
|
||||
{
|
||||
// This patch is out of range for this segment
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto ptr = dst + offset;
|
||||
|
||||
switch (p.type)
|
||||
{
|
||||
case patch_type::load:
|
||||
{
|
||||
// Invalid in this context
|
||||
continue;
|
||||
}
|
||||
case patch_type::byte:
|
||||
{
|
||||
*ptr = static_cast<u8>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le16:
|
||||
{
|
||||
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le32:
|
||||
case patch_type::lef32:
|
||||
{
|
||||
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::le64:
|
||||
case patch_type::lef64:
|
||||
{
|
||||
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be16:
|
||||
{
|
||||
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be32:
|
||||
case patch_type::bef32:
|
||||
{
|
||||
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
}
|
||||
case patch_type::be64:
|
||||
case patch_type::bef64:
|
||||
{
|
||||
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
++applied;
|
||||
}
|
||||
case patch_type::byte:
|
||||
|
||||
if (title_info.is_legacy)
|
||||
{
|
||||
*ptr = static_cast<u8>(p.value);
|
||||
break;
|
||||
patch_log.notice("Applied legacy patch (<- %d)", applied);
|
||||
}
|
||||
case patch_type::le16:
|
||||
else
|
||||
{
|
||||
*reinterpret_cast<le_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
patch_log.notice("Applied patch (description='%s', author='%s', patch_version='%s', file_version='%s') (<- %d)", description, patch.author, patch.patch_version, patch.version, applied);
|
||||
}
|
||||
case patch_type::le32:
|
||||
case patch_type::lef32:
|
||||
|
||||
applied_total += applied;
|
||||
}
|
||||
|
||||
return applied_total;
|
||||
}
|
||||
|
||||
void patch_engine::save_config(const patch_map& patches_map)
|
||||
{
|
||||
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)
|
||||
{
|
||||
patch_log.fatal("Failed to open patch config file %s", path);
|
||||
return;
|
||||
}
|
||||
|
||||
YAML::Emitter out;
|
||||
out << YAML::BeginMap;
|
||||
|
||||
patch_config_map config_map;
|
||||
|
||||
for (const auto& [hash, title_info] : patches_map)
|
||||
{
|
||||
if (title_info.is_legacy)
|
||||
{
|
||||
*reinterpret_cast<le_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
continue;
|
||||
}
|
||||
case patch_type::le64:
|
||||
case patch_type::lef64:
|
||||
|
||||
for (const auto& [description, patch] : title_info.patch_info_map)
|
||||
{
|
||||
*reinterpret_cast<le_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
config_map[hash][description] = patch.enabled;
|
||||
}
|
||||
case patch_type::be16:
|
||||
|
||||
if (config_map[hash].size() > 0)
|
||||
{
|
||||
*reinterpret_cast<be_t<u16, 1>*>(ptr) = static_cast<u16>(p.value);
|
||||
break;
|
||||
out << hash;
|
||||
out << YAML::BeginMap;
|
||||
|
||||
for (const auto& [description, enabled] : config_map[hash])
|
||||
{
|
||||
out << description;
|
||||
out << enabled;
|
||||
}
|
||||
|
||||
out << YAML::EndMap;
|
||||
}
|
||||
case patch_type::be32:
|
||||
case patch_type::bef32:
|
||||
}
|
||||
out << YAML::EndMap;
|
||||
|
||||
file.write(out.c_str(), out.size());
|
||||
}
|
||||
|
||||
patch_engine::patch_config_map patch_engine::load_config()
|
||||
{
|
||||
patch_config_map config_map;
|
||||
|
||||
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())
|
||||
{
|
||||
*reinterpret_cast<be_t<u32, 1>*>(ptr) = static_cast<u32>(p.value);
|
||||
break;
|
||||
patch_log.fatal("Failed to load patch config file %s:\n%s", path, error);
|
||||
return config_map;
|
||||
}
|
||||
case patch_type::be64:
|
||||
case patch_type::bef64:
|
||||
|
||||
for (auto pair : root)
|
||||
{
|
||||
*reinterpret_cast<be_t<u64, 1>*>(ptr) = static_cast<u64>(p.value);
|
||||
break;
|
||||
}
|
||||
auto& hash = pair.first.Scalar();
|
||||
auto& data = config_map[hash];
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
for (auto patch : pair.second)
|
||||
{
|
||||
const auto description = patch.first.Scalar();
|
||||
const auto enabled = patch.second.as<bool>(false);
|
||||
|
||||
data[description] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (found->second.size() - rejected);
|
||||
return config_map;
|
||||
}
|
||||
|
@ -1,10 +1,12 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "BEType.h"
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "util/yaml.hpp"
|
||||
|
||||
enum class patch_type
|
||||
{
|
||||
load,
|
||||
@ -23,22 +25,100 @@ enum class patch_type
|
||||
|
||||
class patch_engine
|
||||
{
|
||||
struct patch
|
||||
public:
|
||||
struct patch_data
|
||||
{
|
||||
patch_type type;
|
||||
u32 offset;
|
||||
u64 value;
|
||||
patch_type type = patch_type::load;
|
||||
u32 offset = 0;
|
||||
u64 value = 0;
|
||||
};
|
||||
|
||||
// Database
|
||||
std::unordered_map<std::string, std::vector<patch>> m_map;
|
||||
struct patch_info
|
||||
{
|
||||
// Patch information
|
||||
std::vector<patch_engine::patch_data> data_list;
|
||||
std::string description;
|
||||
std::string patch_version;
|
||||
std::string author;
|
||||
std::string notes;
|
||||
bool enabled = false;
|
||||
|
||||
public:
|
||||
// Load from file
|
||||
void append(const std::string& path);
|
||||
// Redundant information for accessibility (see patch_title_info)
|
||||
std::string hash;
|
||||
std::string version;
|
||||
std::string title;
|
||||
std::string serials;
|
||||
bool is_legacy = false;
|
||||
};
|
||||
|
||||
struct patch_title_info
|
||||
{
|
||||
std::unordered_map<std::string /*description*/, patch_engine::patch_info> patch_info_map;
|
||||
std::string hash;
|
||||
std::string version;
|
||||
std::string title;
|
||||
std::string serials;
|
||||
bool is_legacy = false;
|
||||
};
|
||||
|
||||
using patch_map = std::unordered_map<std::string /*hash*/, patch_title_info>;
|
||||
using patch_config_map = std::unordered_map<std::string /*hash*/, std::unordered_map<std::string /*description*/, bool /*enabled*/>>;
|
||||
|
||||
patch_engine();
|
||||
|
||||
// Returns the directory in which patch_config.yml is located
|
||||
static std::string get_patch_config_path();
|
||||
|
||||
// Load from file and append to specified patches map
|
||||
// Example entry:
|
||||
//
|
||||
// PPU-8007056e52279bea26c15669d1ee08c2df321d00:
|
||||
// Title: Fancy Game
|
||||
// Serials: ABCD12345, SUPA13337 v.1.3
|
||||
// Patches:
|
||||
// 60fps:
|
||||
// Author: Batman bin Suparman
|
||||
// Notes: This is super
|
||||
// Patch:
|
||||
// - [ be32, 0x000e522c, 0x995d0072 ]
|
||||
// - [ be32, 0x000e5234, 0x995d0074 ]
|
||||
static void load(patch_map& patches, const std::string& path);
|
||||
|
||||
// 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);
|
||||
|
||||
// Get the patch type of a patch node
|
||||
static patch_type get_patch_type(YAML::Node 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);
|
||||
|
||||
// Save to patch_config.yml
|
||||
static void save_config(const patch_map& patches_map);
|
||||
|
||||
// Load patch_config.yml
|
||||
static patch_config_map load_config();
|
||||
|
||||
// Load from file and append to member patches map
|
||||
void append_global_patches();
|
||||
|
||||
// Load from title relevant files and append to member patches map
|
||||
void append_title_patches(const std::string& title_id);
|
||||
|
||||
// Apply patch (returns the number of entries applied)
|
||||
std::size_t apply(const std::string& name, u8* dst) const;
|
||||
|
||||
// Apply patch with a check that the address exists in SPU local storage
|
||||
std::size_t apply_with_ls_check(const std::string&name, u8*dst, u32 filesz, u32 ls_addr) const;
|
||||
std::size_t apply_with_ls_check(const std::string& name, u8* dst, u32 filesz, u32 ls_addr) const;
|
||||
|
||||
private:
|
||||
// Load from file and append to member patches map
|
||||
void append(const std::string& path);
|
||||
|
||||
// Internal: Apply patch (returns the number of entries applied)
|
||||
template <bool check_local_storage>
|
||||
std::size_t apply_patch(const std::string& name, u8* dst, u32 filesz, u32 ls_addr) const;
|
||||
|
||||
// Database
|
||||
patch_map m_map;
|
||||
};
|
||||
|
@ -220,7 +220,7 @@ void Emulator::Init()
|
||||
make_path_verbose(fs::get_config_dir() + "captures/");
|
||||
|
||||
// Initialize patch engine
|
||||
g_fxo->init<patch_engine>()->append(fs::get_config_dir() + "/patch.yml");
|
||||
g_fxo->init<patch_engine>()->append_global_patches();
|
||||
}
|
||||
|
||||
namespace
|
||||
@ -914,7 +914,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
||||
}
|
||||
|
||||
// Load patches from different locations
|
||||
g_fxo->get<patch_engine>()->append(fs::get_config_dir() + "data/" + m_title_id + "/patch.yml");
|
||||
g_fxo->get<patch_engine>()->append_title_patches(m_title_id);
|
||||
|
||||
// Mount all devices
|
||||
const std::string emu_dir = GetEmuDir();
|
||||
|
@ -508,6 +508,11 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_patch_manager_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_persistent_settings.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
@ -788,6 +793,11 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_patch_manager_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_persistent_settings.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
@ -1088,6 +1098,11 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release - LLVM\moc_patch_manager_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release - LLVM\moc_persistent_settings.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
@ -1368,6 +1383,11 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_patch_manager_dialog.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">true</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_persistent_settings.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
@ -1494,6 +1514,7 @@
|
||||
<ClCompile Include="rpcs3qt\microphone_creator.cpp" />
|
||||
<ClCompile Include="rpcs3qt\osk_dialog_frame.cpp" />
|
||||
<ClCompile Include="rpcs3qt\pad_led_settings_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\patch_manager_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\pkg_install_dialog.cpp" />
|
||||
<ClCompile Include="rpcs3qt\persistent_settings.cpp" />
|
||||
<ClCompile Include="rpcs3qt\render_creator.cpp" />
|
||||
@ -1909,6 +1930,7 @@
|
||||
<ClInclude Include="QTGeneratedFiles\ui_main_window.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_pad_led_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_pad_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_patch_manager_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_settings_dialog.h" />
|
||||
<ClInclude Include="QTGeneratedFiles\ui_welcome_dialog.h" />
|
||||
<CustomBuild Include="rpcs3qt\about_dialog.h">
|
||||
@ -2267,6 +2289,24 @@
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
</CustomBuild>
|
||||
<ClInclude Include="rpcs3qt\gui_save.h" />
|
||||
<CustomBuild Include="rpcs3qt\patch_manager_dialog.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DWITH_DISCORD_RPC -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">Moc%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">.\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">"$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DQT_CONCURRENT_LIB -D%(PreprocessorDefinitions) "-I.\..\3rdparty\wolfssl" "-I.\..\3rdparty\curl\include" "-I.\..\3rdparty\libusb\libusb" "-I$(VULKAN_SDK)\Include" "-I.\..\3rdparty\XAudio2Redist\include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)" "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" "-I$(QTDIR)\include\QtConcurrent"</Command>
|
||||
</CustomBuild>
|
||||
<ClInclude Include="rpcs3qt\stylesheets.h" />
|
||||
<CustomBuild Include="rpcs3qt\skylander_dialog.h">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">$(QTDIR)\bin\moc.exe;%(FullPath)</AdditionalInputs>
|
||||
@ -2862,6 +2902,26 @@
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="rpcs3qt\patch_manager_dialog.ui">
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release - LLVM|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Release|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Release|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
<AdditionalInputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">$(QTDIR)\bin\uic.exe;%(AdditionalInputs)</AdditionalInputs>
|
||||
<Message Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">Uic%27ing %(Identity)...</Message>
|
||||
<Outputs Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">.\QTGeneratedFiles\ui_%(Filename).h;%(Outputs)</Outputs>
|
||||
<Command Condition="'$(Configuration)|$(Platform)'=='Debug - LLVM|x64'">"$(QTDIR)\bin\uic.exe" -o ".\QTGeneratedFiles\ui_%(Filename).h" "%(FullPath)"</Command>
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
<ProjectExtensions>
|
||||
|
@ -128,6 +128,9 @@
|
||||
<Filter Include="Gui\screenshot manager">
|
||||
<UniqueIdentifier>{b227bdd4-16f5-4f6e-a8b2-8b1f4bdc606a}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="Gui\patch manager">
|
||||
<UniqueIdentifier>{e72a0cbe-fbcd-4a0b-8c17-a2a3b7a42258}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="main.cpp">
|
||||
@ -1039,6 +1042,21 @@
|
||||
<ClCompile Include="rpcs3qt\config_adapter.cpp">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="rpcs3qt\patch_manager_dialog.cpp">
|
||||
<Filter>Gui\patch manager</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release - LLVM\moc_patch_manager_dialog.cpp">
|
||||
<Filter>Generated Files\Release - LLVM</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug\moc_patch_manager_dialog.cpp">
|
||||
<Filter>Generated Files\Debug</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Release\moc_patch_manager_dialog.cpp">
|
||||
<Filter>Generated Files\Release</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="QTGeneratedFiles\Debug - LLVM\moc_patch_manager_dialog.cpp">
|
||||
<Filter>Generated Files\Debug - LLVM</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Input\ds4_pad_handler.h">
|
||||
@ -1137,6 +1155,9 @@
|
||||
<ClInclude Include="Input\product_info.h">
|
||||
<Filter>Io</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="QTGeneratedFiles\ui_patch_manager_dialog.h">
|
||||
<Filter>Generated Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<CustomBuild Include="debug\moc_predefs.h.cbt">
|
||||
@ -1349,6 +1370,12 @@
|
||||
<CustomBuild Include="rpcs3qt\render_creator.h">
|
||||
<Filter>Gui\settings</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\patch_manager_dialog.h">
|
||||
<Filter>Gui\patch manager</Filter>
|
||||
</CustomBuild>
|
||||
<CustomBuild Include="rpcs3qt\patch_manager_dialog.ui">
|
||||
<Filter>Form Files</Filter>
|
||||
</CustomBuild>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="rpcs3.ico" />
|
||||
|
@ -36,6 +36,7 @@
|
||||
osk_dialog_frame.cpp
|
||||
pad_led_settings_dialog.cpp
|
||||
pad_settings_dialog.cpp
|
||||
patch_manager_dialog.cpp
|
||||
persistent_settings.cpp
|
||||
pkg_install_dialog.cpp
|
||||
progress_dialog.cpp
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "progress_dialog.h"
|
||||
#include "skylander_dialog.h"
|
||||
#include "cheat_manager.h"
|
||||
#include "patch_manager_dialog.h"
|
||||
#include "pkg_install_dialog.h"
|
||||
#include "category.h"
|
||||
#include "gui_settings.h"
|
||||
@ -1548,6 +1549,12 @@ void main_window::CreateConnects()
|
||||
cheat_manager->show();
|
||||
});
|
||||
|
||||
connect(ui->actionManage_Game_Patches, &QAction::triggered, [this]
|
||||
{
|
||||
patch_manager_dialog patch_manager(this);
|
||||
patch_manager.exec();
|
||||
});
|
||||
|
||||
connect(ui->actionManage_Users, &QAction::triggered, [this]
|
||||
{
|
||||
user_manager_dialog user_manager(m_gui_settings, this);
|
||||
|
@ -237,6 +237,7 @@
|
||||
<addaction name="actionManage_Trophy_Data"/>
|
||||
<addaction name="actionManage_Skylanders_Portal"/>
|
||||
<addaction name="actionManage_Cheats"/>
|
||||
<addaction name="actionManage_Game_Patches"/>
|
||||
<addaction name="actionManage_Screenshots"/>
|
||||
</widget>
|
||||
<widget class="QMenu" name="menuUtilities">
|
||||
@ -1068,6 +1069,11 @@
|
||||
<string>Create RSX Capture</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionManage_Game_Patches">
|
||||
<property name="text">
|
||||
<string>Game Patches</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<layoutdefault spacing="6" margin="11"/>
|
||||
<resources>
|
||||
|
344
rpcs3/rpcs3qt/patch_manager_dialog.cpp
Normal file
344
rpcs3/rpcs3qt/patch_manager_dialog.cpp
Normal file
@ -0,0 +1,344 @@
|
||||
#include <QPushButton>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QGuiApplication>
|
||||
#include <QScreen>
|
||||
#include <QDir>
|
||||
#include <QMenu>
|
||||
#include <QAction>
|
||||
|
||||
#include "ui_patch_manager_dialog.h"
|
||||
#include "patch_manager_dialog.h"
|
||||
#include "table_item_delegate.h"
|
||||
#include "qt_utils.h"
|
||||
#include "Utilities/File.h"
|
||||
#include "util/logs.hpp"
|
||||
|
||||
LOG_CHANNEL(patch_log);
|
||||
|
||||
enum patch_column : int
|
||||
{
|
||||
enabled,
|
||||
title,
|
||||
serials,
|
||||
description,
|
||||
patch_version,
|
||||
author,
|
||||
notes
|
||||
};
|
||||
|
||||
enum patch_role : int
|
||||
{
|
||||
hash_role = Qt::UserRole,
|
||||
description_role
|
||||
};
|
||||
|
||||
patch_manager_dialog::patch_manager_dialog(QWidget* parent)
|
||||
: QDialog(parent)
|
||||
, ui(new Ui::patch_manager_dialog)
|
||||
{
|
||||
ui->setupUi(this);
|
||||
setModal(true);
|
||||
|
||||
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_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::customContextMenuRequested, this, &patch_manager_dialog::on_custom_context_menu_requested);
|
||||
connect(ui->pb_expand_all, &QAbstractButton::clicked, ui->patch_tree, &QTreeWidget::expandAll);
|
||||
connect(ui->pb_collapse_all, &QAbstractButton::clicked, ui->patch_tree, &QTreeWidget::collapseAll);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QWidget::close);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
||||
{
|
||||
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
||||
{
|
||||
save();
|
||||
accept();
|
||||
}
|
||||
else if (button == ui->buttonBox->button(QDialogButtonBox::Apply))
|
||||
{
|
||||
save();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
patch_manager_dialog::~patch_manager_dialog()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void patch_manager_dialog::load_patches()
|
||||
{
|
||||
// Legacy path (in case someone puts it there)
|
||||
patch_engine::load(m_map, fs::get_config_dir() + "patch.yml");
|
||||
|
||||
// New paths
|
||||
const std::string patches_path = fs::get_config_dir() + "patches/";
|
||||
const QStringList filters = QStringList() << "*patch.yml";
|
||||
const QStringList path_list = QDir(QString::fromStdString(patches_path)).entryList(filters);
|
||||
|
||||
for (const auto& path : path_list)
|
||||
{
|
||||
patch_engine::load(m_map, patches_path + path.toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
static QList<QTreeWidgetItem*> find_children_by_data(QTreeWidgetItem* parent, int role, const QVariant& data)
|
||||
{
|
||||
QList<QTreeWidgetItem*> list;
|
||||
|
||||
if (parent)
|
||||
{
|
||||
for (int i = 0; i < parent->childCount(); i++)
|
||||
{
|
||||
if (parent->child(i)->data(0, role) == data)
|
||||
{
|
||||
list << parent->child(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void patch_manager_dialog::populate_tree()
|
||||
{
|
||||
ui->patch_tree->clear();
|
||||
|
||||
for (const auto& [hash, title_info] : m_map)
|
||||
{
|
||||
// Don't show legacy patches, because you can't configure them anyway
|
||||
if (title_info.is_legacy)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
QTreeWidgetItem* title_level_item = nullptr;
|
||||
QTreeWidgetItem* serial_level_item = nullptr;
|
||||
|
||||
const QString q_hash = QString::fromStdString(hash);
|
||||
const QString q_serials = title_info.serials.empty() ? tr("Unknown Version") : QString::fromStdString(title_info.serials);
|
||||
const QString q_title = QString::fromStdString(title_info.title);
|
||||
|
||||
// Find top level item for this title
|
||||
if (const auto list = ui->patch_tree->findItems(q_title, Qt::MatchFlag::MatchExactly, 0); list.size() > 0)
|
||||
{
|
||||
title_level_item = list[0];
|
||||
}
|
||||
|
||||
// Find out if there is a node item for this serial
|
||||
serial_level_item = gui::utils::find_child(title_level_item, q_serials);
|
||||
|
||||
// Add patch items
|
||||
for (const auto& [description, patch] : title_info.patch_info_map)
|
||||
{
|
||||
// Add a top level item for this title if it doesn't exist yet
|
||||
if (!title_level_item)
|
||||
{
|
||||
title_level_item = new QTreeWidgetItem();
|
||||
title_level_item->setText(0, q_title);
|
||||
title_level_item->setData(0, hash_role, q_hash);
|
||||
|
||||
ui->patch_tree->addTopLevelItem(title_level_item);
|
||||
}
|
||||
assert(title_level_item);
|
||||
|
||||
// Add a node item for this serial if it doesn't exist yet
|
||||
if (!serial_level_item)
|
||||
{
|
||||
serial_level_item = new QTreeWidgetItem();
|
||||
serial_level_item->setText(0, q_serials);
|
||||
serial_level_item->setData(0, hash_role, q_hash);
|
||||
|
||||
title_level_item->addChild(serial_level_item);
|
||||
}
|
||||
assert(serial_level_item);
|
||||
|
||||
// Add a checkable leaf item for this patch
|
||||
const QString q_description = QString::fromStdString(description);
|
||||
QString visible_description = q_description;
|
||||
|
||||
// Add counter to leafs if the name already exists due to different hashes of the same game (PPU, SPU, PRX, OVL)
|
||||
if (const auto matches = find_children_by_data(serial_level_item, description_role, q_description); matches.count() > 0)
|
||||
{
|
||||
if (auto only_match = matches.count() == 1 ? matches[0] : nullptr)
|
||||
{
|
||||
only_match->setText(0, q_description + QStringLiteral(" (1)"));
|
||||
}
|
||||
visible_description += QStringLiteral(" (") + QString::number(matches.count() + 1) + QStringLiteral(")");
|
||||
}
|
||||
|
||||
QTreeWidgetItem* patch_level_item = new QTreeWidgetItem();
|
||||
patch_level_item->setText(0, visible_description);
|
||||
patch_level_item->setCheckState(0, patch.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||
patch_level_item->setData(0, hash_role, q_hash);
|
||||
patch_level_item->setData(0, description_role, q_description);
|
||||
|
||||
serial_level_item->addChild(patch_level_item);
|
||||
}
|
||||
|
||||
if (title_level_item)
|
||||
{
|
||||
title_level_item->sortChildren(0, Qt::SortOrder::AscendingOrder);
|
||||
}
|
||||
if (serial_level_item)
|
||||
{
|
||||
serial_level_item->sortChildren(0, Qt::SortOrder::AscendingOrder);
|
||||
}
|
||||
}
|
||||
|
||||
ui->patch_tree->sortByColumn(0, Qt::SortOrder::AscendingOrder);
|
||||
}
|
||||
|
||||
void patch_manager_dialog::save()
|
||||
{
|
||||
patch_engine::save_config(m_map);
|
||||
}
|
||||
|
||||
void patch_manager_dialog::filter_patches(const QString& term)
|
||||
{
|
||||
// Recursive function to show all matching items and their children.
|
||||
// @return number of visible children of item, including item
|
||||
std::function<int(QTreeWidgetItem*, bool)> show_matches;
|
||||
show_matches = [&show_matches, search_text = term.toLower()](QTreeWidgetItem* item, bool parent_visible) -> int
|
||||
{
|
||||
if (!item) return 0;
|
||||
|
||||
// Only try to match if the parent is not visible
|
||||
parent_visible = parent_visible || item->text(0).toLower().contains(search_text);
|
||||
int visible_items = parent_visible ? 1 : 0;
|
||||
|
||||
// Get the number of visible children recursively
|
||||
for (int i = 0; i < item->childCount(); i++)
|
||||
{
|
||||
visible_items += show_matches(item->child(i), parent_visible);
|
||||
}
|
||||
|
||||
// Only show item if itself or any of its children is visible
|
||||
item->setHidden(visible_items <= 0);
|
||||
return visible_items;
|
||||
};
|
||||
|
||||
// Go through each top level item and try to find matches
|
||||
for (auto top_level_item : ui->patch_tree->findItems(".*", Qt::MatchRegExp))
|
||||
{
|
||||
show_matches(top_level_item, false);
|
||||
}
|
||||
}
|
||||
|
||||
void patch_manager_dialog::update_patch_info(const patch_engine::patch_info& info)
|
||||
{
|
||||
ui->label_hash->setText(QString::fromStdString(info.hash));
|
||||
ui->label_author->setText(QString::fromStdString(info.author));
|
||||
ui->label_notes->setText(QString::fromStdString(info.notes));
|
||||
ui->label_description->setText(QString::fromStdString(info.description));
|
||||
ui->label_patch_version->setText(QString::fromStdString(info.patch_version));
|
||||
ui->label_serials->setText(QString::fromStdString(info.serials));
|
||||
ui->label_title->setText(QString::fromStdString(info.title));
|
||||
}
|
||||
|
||||
void patch_manager_dialog::on_item_selected(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/)
|
||||
{
|
||||
if (!current)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get patch identifiers stored in item data
|
||||
const std::string hash = current->data(0, hash_role).toString().toStdString();
|
||||
const std::string description = current->data(0, description_role).toString().toStdString();
|
||||
|
||||
if (m_map.find(hash) != m_map.end())
|
||||
{
|
||||
const auto& title_info = m_map.at(hash);
|
||||
|
||||
// Find the patch for this item and show its metadata
|
||||
if (!title_info.is_legacy && title_info.patch_info_map.find(description) != title_info.patch_info_map.end())
|
||||
{
|
||||
update_patch_info(title_info.patch_info_map.at(description));
|
||||
return;
|
||||
}
|
||||
|
||||
// Show shared info if no patch was found
|
||||
patch_engine::patch_info info{};
|
||||
info.hash = hash;
|
||||
info.title = title_info.title;
|
||||
info.serials = title_info.serials;
|
||||
info.version = title_info.version;
|
||||
update_patch_info(info);
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear patch info if no info was found
|
||||
patch_engine::patch_info info{};
|
||||
update_patch_info(info);
|
||||
}
|
||||
|
||||
void patch_manager_dialog::on_item_changed(QTreeWidgetItem *item, int /*column*/)
|
||||
{
|
||||
if (!item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get checkstate of the item
|
||||
const bool enabled = item->checkState(0) == Qt::CheckState::Checked;
|
||||
|
||||
// Get patch identifiers stored in item data
|
||||
const std::string hash = item->data(0, hash_role).toString().toStdString();
|
||||
const std::string description = item->data(0, description_role).toString().toStdString();
|
||||
|
||||
// Enable/disable the patch for this item and show its metadata
|
||||
if (m_map.find(hash) != m_map.end())
|
||||
{
|
||||
auto& title_info = m_map[hash];
|
||||
|
||||
if (!title_info.is_legacy && title_info.patch_info_map.find(description) != title_info.patch_info_map.end())
|
||||
{
|
||||
auto& patch = m_map[hash].patch_info_map[description];
|
||||
patch.enabled = enabled;
|
||||
update_patch_info(patch);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void patch_manager_dialog::on_custom_context_menu_requested(const QPoint &pos)
|
||||
{
|
||||
QTreeWidgetItem* item = ui->patch_tree->itemAt(pos);
|
||||
|
||||
if (!item)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
QMenu* menu = new QMenu(this);
|
||||
|
||||
if (item->childCount() > 0)
|
||||
{
|
||||
QAction* collapse_children = new QAction("Collapse Children");
|
||||
menu->addAction(collapse_children);
|
||||
connect(collapse_children, &QAction::triggered, this, [&item](bool)
|
||||
{
|
||||
for (int i = 0; i < item->childCount(); i++)
|
||||
{
|
||||
item->child(i)->setExpanded(false);
|
||||
}
|
||||
});
|
||||
|
||||
QAction* expand_children = new QAction("Expand Children");
|
||||
menu->addAction(expand_children);
|
||||
connect(expand_children, &QAction::triggered, this, [&item](bool)
|
||||
{
|
||||
for (int i = 0; i < item->childCount(); i++)
|
||||
{
|
||||
item->child(i)->setExpanded(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
menu->exec(ui->patch_tree->viewport()->mapToGlobal(pos));
|
||||
}
|
37
rpcs3/rpcs3qt/patch_manager_dialog.h
Normal file
37
rpcs3/rpcs3qt/patch_manager_dialog.h
Normal file
@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <QDialog>
|
||||
#include <QTreeWidgetItem>
|
||||
|
||||
#include "Utilities/bin_patch.h"
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
class patch_manager_dialog;
|
||||
}
|
||||
|
||||
class patch_manager_dialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit patch_manager_dialog(QWidget* parent = nullptr);
|
||||
~patch_manager_dialog();
|
||||
|
||||
private Q_SLOTS:
|
||||
void filter_patches(const QString& term);
|
||||
void on_item_selected(QTreeWidgetItem *current, QTreeWidgetItem *previous);
|
||||
void on_item_changed(QTreeWidgetItem *item, int column);
|
||||
void on_custom_context_menu_requested(const QPoint& pos);
|
||||
|
||||
private:
|
||||
void load_patches();
|
||||
void populate_tree();
|
||||
void save();
|
||||
|
||||
void update_patch_info(const patch_engine::patch_info& info);
|
||||
|
||||
patch_engine::patch_map m_map;
|
||||
|
||||
Ui::patch_manager_dialog *ui;
|
||||
};
|
247
rpcs3/rpcs3qt/patch_manager_dialog.ui
Normal file
247
rpcs3/rpcs3qt/patch_manager_dialog.ui
Normal file
@ -0,0 +1,247 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>patch_manager_dialog</class>
|
||||
<widget class="QDialog" name="patch_manager_dialog">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>837</width>
|
||||
<height>659</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Patch Manager</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="dialog_layout">
|
||||
<item>
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="verticalLayoutWidget">
|
||||
<layout class="QVBoxLayout" name="patch_tree_layout">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="patch_filter_layout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="patch_filter">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="placeholderText">
|
||||
<string>Filter patches</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_collapse_all">
|
||||
<property name="text">
|
||||
<string>Collapse All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="pb_expand_all">
|
||||
<property name="text">
|
||||
<string>Expand All</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTreeWidget" name="patch_tree">
|
||||
<property name="contextMenuPolicy">
|
||||
<enum>Qt::CustomContextMenu</enum>
|
||||
</property>
|
||||
<property name="headerHidden">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<column>
|
||||
<property name="text">
|
||||
<string notr="true">1</string>
|
||||
</property>
|
||||
</column>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="verticalLayoutWidget_2">
|
||||
<layout class="QVBoxLayout" name="patch_info_layout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_patch_info">
|
||||
<property name="title">
|
||||
<string>Patch Information</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="patch_info_gb_layout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_hash">
|
||||
<property name="title">
|
||||
<string>Hash</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="hash_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_hash">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_title">
|
||||
<property name="title">
|
||||
<string>Title</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="title_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_title">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_serials">
|
||||
<property name="title">
|
||||
<string>Serials</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="serials_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_serials">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_description">
|
||||
<property name="title">
|
||||
<string>Description</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="description_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_description">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_patch_version">
|
||||
<property name="title">
|
||||
<string>Patch Version</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="patch_version_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_patch_version">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_author">
|
||||
<property name="title">
|
||||
<string>Author</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="author_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_author">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="gb_notes">
|
||||
<property name="title">
|
||||
<string>Notes</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="notes_layout">
|
||||
<item>
|
||||
<widget class="QLabel" name="label_notes">
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacerPatchInfo">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::MinimumExpanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Apply|QDialogButtonBox::Cancel|QDialogButtonBox::Save</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -314,6 +314,21 @@ namespace gui
|
||||
open_dir(sstr(path));
|
||||
}
|
||||
|
||||
QTreeWidgetItem* find_child(QTreeWidgetItem* parent, const QString& text)
|
||||
{
|
||||
if (parent)
|
||||
{
|
||||
for (int i = 0; i < parent->childCount(); i++)
|
||||
{
|
||||
if (parent->child(i)->text(0) == text)
|
||||
{
|
||||
return parent->child(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QTreeWidgetItem* add_child(QTreeWidgetItem *parent, const QString& text, int column)
|
||||
{
|
||||
if (parent)
|
||||
|
@ -66,6 +66,9 @@ namespace gui
|
||||
|
||||
// Open a path in the explorer and mark the file
|
||||
void open_dir(const QString& path);
|
||||
|
||||
// Finds a child of a QTreeWidgetItem with given text
|
||||
QTreeWidgetItem* find_child(QTreeWidgetItem* parent, const QString& text);
|
||||
|
||||
// Constructs and adds a child to a QTreeWidgetItem
|
||||
QTreeWidgetItem* add_child(QTreeWidgetItem* parent, const QString& text, int column = 0);
|
||||
|
@ -2,6 +2,15 @@
|
||||
#include <QInputDialog>
|
||||
#include <QKeyEvent>
|
||||
#include <QEvent>
|
||||
#include <QScreen>
|
||||
#include <QHeaderView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMenu>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QGuiApplication>
|
||||
#include <QUrl>
|
||||
|
||||
#include "user_manager_dialog.h"
|
||||
#include "table_item_delegate.h"
|
||||
@ -14,19 +23,6 @@
|
||||
#include "Utilities/File.h"
|
||||
#include "util/logs.hpp"
|
||||
|
||||
#include <QRegExpValidator>
|
||||
#include <QInputDialog>
|
||||
#include <QScreen>
|
||||
#include <QKeyEvent>
|
||||
#include <QHeaderView>
|
||||
#include <QHBoxLayout>
|
||||
#include <QPushButton>
|
||||
#include <QMenu>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QGuiApplication>
|
||||
#include <QUrl>
|
||||
|
||||
constexpr auto qstr = QString::fromStdString;
|
||||
|
||||
LOG_CHANNEL(gui_log, "GUI");
|
||||
|
Loading…
Reference in New Issue
Block a user