mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-01-28 14:54:11 +00:00
patch_manager: implement serials and app_versions
This commit is contained in:
parent
a7a5034958
commit
12dded403f
@ -2,10 +2,11 @@
|
|||||||
#include "File.h"
|
#include "File.h"
|
||||||
#include "Config.h"
|
#include "Config.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#include "Emu/System.h"
|
||||||
|
|
||||||
LOG_CHANNEL(patch_log);
|
LOG_CHANNEL(patch_log);
|
||||||
|
|
||||||
static const std::string patch_engine_version = "1.1";
|
static const std::string patch_engine_version = "1.2";
|
||||||
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 <>
|
||||||
@ -113,7 +114,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
|
|
||||||
// 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;
|
patch_map patch_config;
|
||||||
|
|
||||||
if (!importing)
|
if (!importing)
|
||||||
{
|
{
|
||||||
@ -161,7 +162,7 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
{
|
{
|
||||||
struct patch_info info{};
|
struct patch_info info{};
|
||||||
info.hash = main_key;
|
info.hash = main_key;
|
||||||
info.enabled = enable_legacy_patches;
|
info.is_enabled = enable_legacy_patches;
|
||||||
info.is_legacy = true;
|
info.is_legacy = true;
|
||||||
info.source_path = path;
|
info.source_path = path;
|
||||||
|
|
||||||
@ -194,16 +195,6 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto patches_node = pair.second["Patches"])
|
|
||||||
{
|
|
||||||
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)", yml_type, main_key));
|
|
||||||
patch_log.error("Skipping Patches: expected Map, found %s (key: %s, file: %s)", yml_type, main_key, path);
|
|
||||||
is_valid = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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& container = patches_map[main_key];
|
auto& container = patches_map[main_key];
|
||||||
container.is_legacy = false;
|
container.is_legacy = false;
|
||||||
@ -211,13 +202,10 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
container.version = version;
|
container.version = version;
|
||||||
|
|
||||||
// Go through each patch
|
// Go through each patch
|
||||||
for (auto patches_entry : patches_node)
|
for (auto patches_entry : pair.second)
|
||||||
{
|
{
|
||||||
// Each key in "Patches" is also the patch description
|
// Each key in "Patches" is also the patch description
|
||||||
const std::string description = patches_entry.first.Scalar();
|
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
|
// Compile patch information
|
||||||
|
|
||||||
@ -230,20 +218,69 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct patch_info info {};
|
struct patch_info info {};
|
||||||
info.enabled = enabled;
|
|
||||||
info.description = description;
|
info.description = description;
|
||||||
info.hash = main_key;
|
info.hash = main_key;
|
||||||
info.version = version;
|
info.version = version;
|
||||||
info.source_path = path;
|
info.source_path = path;
|
||||||
|
|
||||||
if (const auto title_node = patches_entry.second["Game Title"])
|
if (const auto games_node = patches_entry.second["Games"])
|
||||||
{
|
{
|
||||||
info.title = title_node.Scalar();
|
if (const auto yml_type = games_node.Type(); yml_type != YAML::NodeType::Map)
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping Games key: expected Map, found %s (patch: %s, key: %s)", yml_type, description, main_key));
|
||||||
|
patch_log.error("Skipping Games key: expected Map, found %s (patch: %s, key: %s, file: %s)", yml_type, description, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto serials_node = patches_entry.second["Serials"])
|
for (const auto game_node : games_node)
|
||||||
{
|
{
|
||||||
info.serials = serials_node.Scalar();
|
const std::string& title = game_node.first.Scalar();
|
||||||
|
|
||||||
|
if (const auto yml_type = game_node.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping %s: expected Map, found %s (patch: %s, key: %s)", title, yml_type, description, main_key));
|
||||||
|
patch_log.error("Skipping %s: expected Map, found %s (patch: %s, key: %s, file: %s)", title, yml_type, description, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto serial_node : game_node.second)
|
||||||
|
{
|
||||||
|
const std::string& serial = serial_node.first.Scalar();
|
||||||
|
|
||||||
|
if (const auto yml_type = serial_node.second.Type(); yml_type != YAML::NodeType::Sequence)
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s)", serial, title, yml_type, description, main_key));
|
||||||
|
patch_log.error("Skipping %s: expected Sequence, found %s (title: %s, patch: %s, key: %s, file: %s)", serial, title, yml_type, description, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
patch_engine::patch_app_versions app_versions;
|
||||||
|
|
||||||
|
for (const auto version : serial_node.second)
|
||||||
|
{
|
||||||
|
const auto& app_version = version.Scalar();
|
||||||
|
|
||||||
|
// Find out if this patch was enabled in the patch config
|
||||||
|
const bool enabled = patch_config[main_key].patch_info_map[description].titles[title][serial][app_version];
|
||||||
|
|
||||||
|
app_versions.emplace(version.Scalar(), enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (app_versions.empty())
|
||||||
|
{
|
||||||
|
append_log_message(log_messages, fmt::format("Error: Skipping %s: empty Sequence (title: %s, patch: %s, key: %s)", serial, title, description, main_key));
|
||||||
|
patch_log.error("Skipping %s: empty Sequence (title: %s, patch: %s, key: %s, file: %s)", serial,title, description, main_key, path);
|
||||||
|
is_valid = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
info.titles[title][serial] = app_versions;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (const auto author_node = patches_entry.second["Author"])
|
if (const auto author_node = patches_entry.second["Author"])
|
||||||
@ -261,6 +298,11 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
info.notes = notes_node.Scalar();
|
info.notes = notes_node.Scalar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (const auto patch_group_node = patches_entry.second["Group"])
|
||||||
|
{
|
||||||
|
info.patch_group = patch_group_node.Scalar();
|
||||||
|
}
|
||||||
|
|
||||||
if (const auto patch_node = patches_entry.second["Patch"])
|
if (const auto patch_node = patches_entry.second["Patch"])
|
||||||
{
|
{
|
||||||
if (!read_patch_node(info, patch_node, root, log_messages))
|
if (!read_patch_node(info, patch_node, root, log_messages))
|
||||||
@ -292,7 +334,6 @@ bool patch_engine::load(patch_map& patches_map, const std::string& path, bool im
|
|||||||
container.patch_info_map[description] = info;
|
container.patch_info_map[description] = info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return is_valid;
|
return is_valid;
|
||||||
}
|
}
|
||||||
@ -507,11 +548,35 @@ std::size_t patch_engine::apply_patch(const std::string& name, u8* dst, u32 file
|
|||||||
|
|
||||||
size_t applied_total = 0;
|
size_t applied_total = 0;
|
||||||
const auto& container = m_map.at(name);
|
const auto& container = m_map.at(name);
|
||||||
|
const auto serial = Emu.GetTitleID();
|
||||||
|
const auto app_version = Emu.GetAppVersion();
|
||||||
|
|
||||||
// Apply modifications sequentially
|
// Apply modifications sequentially
|
||||||
for (const auto& [description, patch] : container.patch_info_map)
|
for (const auto& [description, patch] : container.patch_info_map)
|
||||||
{
|
{
|
||||||
if (!patch.enabled)
|
if (patch.is_legacy && !patch.is_enabled)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enabled = false;
|
||||||
|
|
||||||
|
for (const auto& [title, serials] : patch.titles)
|
||||||
|
{
|
||||||
|
if (serials.find(serial) != serials.end())
|
||||||
|
{
|
||||||
|
if (const auto& app_versions = serials.at(serial); app_versions.find(app_version) != app_versions.end())
|
||||||
|
{
|
||||||
|
if (app_versions.at(app_version))
|
||||||
|
{
|
||||||
|
enabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!enabled)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -636,8 +701,8 @@ void patch_engine::save_config(const patch_map& patches_map, bool enable_legacy_
|
|||||||
// Save "Enable Legacy Patches"
|
// Save "Enable Legacy Patches"
|
||||||
out << yml_key_enable_legacy_patches << enable_legacy_patches;
|
out << yml_key_enable_legacy_patches << enable_legacy_patches;
|
||||||
|
|
||||||
// Save 'enabled' state per hash and description
|
// Save 'enabled' state per hash, description, serial and app_version
|
||||||
patch_config_map config_map;
|
patch_map config_map;
|
||||||
|
|
||||||
for (const auto& [hash, container] : patches_map)
|
for (const auto& [hash, container] : patches_map)
|
||||||
{
|
{
|
||||||
@ -648,23 +713,62 @@ void patch_engine::save_config(const patch_map& patches_map, bool enable_legacy_
|
|||||||
|
|
||||||
for (const auto& [description, patch] : container.patch_info_map)
|
for (const auto& [description, patch] : container.patch_info_map)
|
||||||
{
|
{
|
||||||
config_map[hash][description] = patch.enabled;
|
if (patch.is_legacy)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (config_map[hash].size() > 0)
|
for (const auto& [title, serials] : patch.titles)
|
||||||
{
|
{
|
||||||
out << hash;
|
for (const auto& [serial, app_versions] : serials)
|
||||||
out << YAML::BeginMap;
|
{
|
||||||
|
for (const auto& [app_version, enabled] : app_versions)
|
||||||
|
{
|
||||||
|
if (enabled)
|
||||||
|
{
|
||||||
|
config_map[hash].patch_info_map[description].titles[title][serial][app_version] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& [description, enabled] : config_map[hash])
|
if (const auto& enabled_patches = config_map[hash].patch_info_map; enabled_patches.size() > 0)
|
||||||
{
|
{
|
||||||
out << description;
|
out << hash << YAML::BeginMap;
|
||||||
out << enabled;
|
|
||||||
|
for (const auto& [description, patch] : enabled_patches)
|
||||||
|
{
|
||||||
|
const auto& titles = patch.titles;
|
||||||
|
|
||||||
|
out << description << YAML::BeginMap;
|
||||||
|
|
||||||
|
for (const auto& [title, serials] : titles)
|
||||||
|
{
|
||||||
|
out << title << YAML::BeginMap;
|
||||||
|
|
||||||
|
for (const auto& [serial, app_versions] : serials)
|
||||||
|
{
|
||||||
|
out << serial << YAML::BeginMap;
|
||||||
|
|
||||||
|
for (const auto& [app_version, enabled] : app_versions)
|
||||||
|
{
|
||||||
|
out << app_version << enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
out << YAML::EndMap;
|
out << YAML::EndMap;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
out << YAML::EndMap;
|
out << YAML::EndMap;
|
||||||
|
|
||||||
file.write(out.c_str(), out.size());
|
file.write(out.c_str(), out.size());
|
||||||
@ -713,9 +817,18 @@ static void append_patches(patch_engine::patch_map& existing_patches, const patc
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const auto& [title, new_serials] : new_info.titles)
|
||||||
|
{
|
||||||
|
for (const auto& [serial, new_app_versions] : new_serials)
|
||||||
|
{
|
||||||
|
if (!new_app_versions.empty())
|
||||||
|
{
|
||||||
|
info.titles[title][serial].insert(new_app_versions.begin(), new_app_versions.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!new_info.patch_version.empty()) info.patch_version = new_info.patch_version;
|
if (!new_info.patch_version.empty()) info.patch_version = new_info.patch_version;
|
||||||
if (!new_info.title.empty()) info.title = new_info.title;
|
|
||||||
if (!new_info.serials.empty()) info.serials = new_info.serials;
|
|
||||||
if (!new_info.author.empty()) info.author = new_info.author;
|
if (!new_info.author.empty()) info.author = new_info.author;
|
||||||
if (!new_info.notes.empty()) info.notes = new_info.notes;
|
if (!new_info.notes.empty()) info.notes = new_info.notes;
|
||||||
if (!new_info.data_list.empty()) info.data_list = new_info.data_list;
|
if (!new_info.data_list.empty()) info.data_list = new_info.data_list;
|
||||||
@ -751,19 +864,37 @@ bool patch_engine::save_patches(const patch_map& patches, const std::string& pat
|
|||||||
out << hash << YAML::BeginMap;
|
out << hash << YAML::BeginMap;
|
||||||
out << "Patches" << YAML::BeginMap;
|
out << "Patches" << YAML::BeginMap;
|
||||||
|
|
||||||
for (auto [description, info] : container.patch_info_map)
|
for (const auto& [description, info] : container.patch_info_map)
|
||||||
{
|
{
|
||||||
out << description;
|
out << description << YAML::BeginMap;
|
||||||
out << YAML::BeginMap;
|
out << "Games" << YAML::BeginMap;
|
||||||
|
|
||||||
|
for (const auto& [title, serials] : info.titles)
|
||||||
|
{
|
||||||
|
out << title << YAML::BeginMap;
|
||||||
|
|
||||||
|
for (const auto& [serial, app_versions] : serials)
|
||||||
|
{
|
||||||
|
out << serial << YAML::BeginSeq;
|
||||||
|
|
||||||
|
for (const auto& app_version : serials)
|
||||||
|
{
|
||||||
|
out << app_version.first;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndSeq;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
out << YAML::EndMap;
|
||||||
|
|
||||||
if (!info.title.empty()) out << "Game Title" << info.title;
|
|
||||||
if (!info.serials.empty()) out << "Serials" << info.serials;
|
|
||||||
if (!info.author.empty()) out << "Author" << info.author;
|
if (!info.author.empty()) out << "Author" << info.author;
|
||||||
if (!info.patch_version.empty()) out << "Patch Version" << info.patch_version;
|
if (!info.patch_version.empty()) out << "Patch Version" << info.patch_version;
|
||||||
if (!info.notes.empty()) out << "Notes" << info.notes;
|
if (!info.notes.empty()) out << "Notes" << info.notes;
|
||||||
|
|
||||||
out << "Patch";
|
out << "Patch" << YAML::BeginSeq;
|
||||||
out << YAML::BeginSeq;
|
|
||||||
|
|
||||||
for (const auto& data : info.data_list)
|
for (const auto& data : info.data_list)
|
||||||
{
|
{
|
||||||
@ -830,11 +961,11 @@ bool patch_engine::remove_patch(const patch_info& info)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
patch_engine::patch_config_map patch_engine::load_config(bool& enable_legacy_patches)
|
patch_engine::patch_map patch_engine::load_config(bool& enable_legacy_patches)
|
||||||
{
|
{
|
||||||
enable_legacy_patches = true; // Default to true
|
enable_legacy_patches = true; // Default to true
|
||||||
|
|
||||||
patch_config_map config_map;
|
patch_map config_map;
|
||||||
|
|
||||||
const std::string path = get_patch_config_path();
|
const std::string path = get_patch_config_path();
|
||||||
patch_log.notice("Loading patch config file %s", path);
|
patch_log.notice("Loading patch config file %s", path);
|
||||||
@ -856,10 +987,9 @@ patch_engine::patch_config_map patch_engine::load_config(bool& enable_legacy_pat
|
|||||||
root.remove(yml_key_enable_legacy_patches); // Remove the node in order to skip it in the next part
|
root.remove(yml_key_enable_legacy_patches); // Remove the node in order to skip it in the next part
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto pair : root)
|
for (const auto pair : root)
|
||||||
{
|
{
|
||||||
auto& hash = pair.first.Scalar();
|
const auto& hash = pair.first.Scalar();
|
||||||
auto& data = config_map[hash];
|
|
||||||
|
|
||||||
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)
|
||||||
{
|
{
|
||||||
@ -867,12 +997,44 @@ patch_engine::patch_config_map patch_engine::load_config(bool& enable_legacy_pat
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto patch : pair.second)
|
for (const auto patch : pair.second)
|
||||||
{
|
{
|
||||||
const auto description = patch.first.Scalar();
|
const auto& description = patch.first.Scalar();
|
||||||
const auto enabled = patch.second.as<bool>(false);
|
|
||||||
|
|
||||||
data[description] = enabled;
|
if (const auto yml_type = patch.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
|
{
|
||||||
|
patch_log.error("Error loading patch %s: expected Map, found %s (hash: %s, file: %s)", description, yml_type, hash, path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto title_node : patch.second)
|
||||||
|
{
|
||||||
|
const auto& title = title_node.first.Scalar();
|
||||||
|
|
||||||
|
if (const auto yml_type = title_node.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
|
{
|
||||||
|
patch_log.error("Error loading %s: expected Map, found %s (description: %s, hash: %s, file: %s)", title, yml_type, description, hash, path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto serial_node : title_node.second)
|
||||||
|
{
|
||||||
|
const auto& serial = serial_node.first.Scalar();
|
||||||
|
|
||||||
|
if (const auto yml_type = serial_node.second.Type(); yml_type != YAML::NodeType::Map)
|
||||||
|
{
|
||||||
|
patch_log.error("Error loading %s: expected Map, found %s (title: %s, description: %s, hash: %s, file: %s)", serial, yml_type, title, description, hash, path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto app_version_node : serial_node.second)
|
||||||
|
{
|
||||||
|
const auto& app_version = app_version_node.first.Scalar();
|
||||||
|
const bool enabled = app_version_node.second.as<bool>(false);
|
||||||
|
config_map[hash].patch_info_map[description].titles[title][serial][app_version] = enabled;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,35 +39,38 @@ public:
|
|||||||
} value { 0 };
|
} value { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using patch_app_versions = std::unordered_map<std::string /*app_version*/, bool /*enabled*/>;
|
||||||
|
using patch_serials = std::unordered_map<std::string /*serial*/, patch_app_versions>;
|
||||||
|
using patch_titles = std::unordered_map<std::string /*serial*/, patch_serials>;
|
||||||
|
|
||||||
struct patch_info
|
struct patch_info
|
||||||
{
|
{
|
||||||
// Patch information
|
// Patch information
|
||||||
std::vector<patch_engine::patch_data> data_list;
|
std::vector<patch_data> data_list;
|
||||||
|
patch_titles titles;
|
||||||
std::string description;
|
std::string description;
|
||||||
std::string title;
|
|
||||||
std::string serials;
|
|
||||||
std::string patch_version;
|
std::string patch_version;
|
||||||
|
std::string patch_group;
|
||||||
std::string author;
|
std::string author;
|
||||||
std::string notes;
|
std::string notes;
|
||||||
std::string source_path;
|
std::string source_path;
|
||||||
bool enabled = false;
|
|
||||||
|
|
||||||
// Redundant information for accessibility (see patch_container)
|
// Redundant information for accessibility (see patch_container)
|
||||||
std::string hash;
|
std::string hash;
|
||||||
std::string version;
|
std::string version;
|
||||||
bool is_legacy = false;
|
bool is_legacy = false;
|
||||||
|
bool is_enabled = false; // only for legacy patches
|
||||||
};
|
};
|
||||||
|
|
||||||
struct patch_container
|
struct patch_container
|
||||||
{
|
{
|
||||||
std::unordered_map<std::string /*description*/, patch_engine::patch_info> patch_info_map;
|
std::unordered_map<std::string /*description*/, patch_info> patch_info_map;
|
||||||
std::string hash;
|
std::string hash;
|
||||||
std::string version;
|
std::string version;
|
||||||
bool is_legacy = false;
|
bool is_legacy = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
using patch_map = std::unordered_map<std::string /*hash*/, patch_container>;
|
using patch_map = std::unordered_map<std::string /*hash*/, patch_container>;
|
||||||
using patch_config_map = std::unordered_map<std::string /*hash*/, std::unordered_map<std::string /*description*/, bool /*enabled*/>>;
|
|
||||||
|
|
||||||
patch_engine();
|
patch_engine();
|
||||||
|
|
||||||
@ -115,7 +118,7 @@ public:
|
|||||||
static bool remove_patch(const patch_info& info);
|
static bool remove_patch(const patch_info& info);
|
||||||
|
|
||||||
// Load patch_config.yml
|
// Load patch_config.yml
|
||||||
static patch_config_map load_config(bool& enable_legacy_patches);
|
static patch_map load_config(bool& enable_legacy_patches);
|
||||||
|
|
||||||
// Load from file and append to member patches map
|
// Load from file and append to member patches map
|
||||||
void append_global_patches();
|
void append_global_patches();
|
||||||
|
@ -850,7 +850,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
m_title_id = psf::get_string(_psf, "TITLE_ID");
|
m_title_id = psf::get_string(_psf, "TITLE_ID");
|
||||||
m_cat = psf::get_string(_psf, "CATEGORY");
|
m_cat = psf::get_string(_psf, "CATEGORY");
|
||||||
|
|
||||||
const std::string version_app = psf::get_string(_psf, "APP_VER", "Unknown");
|
m_app_version = psf::get_string(_psf, "APP_VER", "Unknown");
|
||||||
const std::string version_disc = psf::get_string(_psf, "VERSION", "Unknown");
|
const std::string version_disc = psf::get_string(_psf, "VERSION", "Unknown");
|
||||||
|
|
||||||
if (!_psf.empty() && m_cat.empty())
|
if (!_psf.empty() && m_cat.empty())
|
||||||
@ -862,7 +862,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool add_only, bool
|
|||||||
sys_log.notice("Title: %s", GetTitle());
|
sys_log.notice("Title: %s", GetTitle());
|
||||||
sys_log.notice("Serial: %s", GetTitleID());
|
sys_log.notice("Serial: %s", GetTitleID());
|
||||||
sys_log.notice("Category: %s", GetCat());
|
sys_log.notice("Category: %s", GetCat());
|
||||||
sys_log.notice("Version: %s / %s", version_app, version_disc);
|
sys_log.notice("Version: %s / %s", GetAppVersion(), version_disc);
|
||||||
|
|
||||||
if (!add_only && !force_global_config)
|
if (!add_only && !force_global_config)
|
||||||
{
|
{
|
||||||
|
@ -64,6 +64,7 @@ class Emulator final
|
|||||||
std::string m_path_old;
|
std::string m_path_old;
|
||||||
std::string m_title_id;
|
std::string m_title_id;
|
||||||
std::string m_title;
|
std::string m_title;
|
||||||
|
std::string m_app_version;
|
||||||
std::string m_cat;
|
std::string m_cat;
|
||||||
std::string m_dir;
|
std::string m_dir;
|
||||||
std::string m_sfo_dir;
|
std::string m_sfo_dir;
|
||||||
@ -131,6 +132,11 @@ public:
|
|||||||
return m_title + (m_title_id.empty() ? "" : " [" + m_title_id + "]");
|
return m_title + (m_title_id.empty() ? "" : " [" + m_title_id + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const std::string& GetAppVersion() const
|
||||||
|
{
|
||||||
|
return m_app_version;
|
||||||
|
}
|
||||||
|
|
||||||
const std::string& GetCat() const
|
const std::string& GetCat() const
|
||||||
{
|
{
|
||||||
return m_cat;
|
return m_cat;
|
||||||
|
@ -32,7 +32,11 @@ enum patch_column : int
|
|||||||
enum patch_role : int
|
enum patch_role : int
|
||||||
{
|
{
|
||||||
hash_role = Qt::UserRole,
|
hash_role = Qt::UserRole,
|
||||||
|
title_role,
|
||||||
|
serial_role,
|
||||||
|
app_version_role,
|
||||||
description_role,
|
description_role,
|
||||||
|
patch_group_role,
|
||||||
persistance_role,
|
persistance_role,
|
||||||
node_level_role
|
node_level_role
|
||||||
};
|
};
|
||||||
@ -41,6 +45,7 @@ enum node_level : int
|
|||||||
{
|
{
|
||||||
title_level,
|
title_level,
|
||||||
serial_level,
|
serial_level,
|
||||||
|
app_version_level,
|
||||||
patch_level
|
patch_level
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -166,8 +171,16 @@ void patch_manager_dialog::populate_tree()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString q_title = patch.title.empty() ? tr("Unknown Title") : QString::fromStdString(patch.title);
|
const QString q_patch_group = QString::fromStdString(patch.patch_group);
|
||||||
const QString q_serials = patch.serials.empty() ? tr("Unknown Version") : QString::fromStdString(patch.serials);
|
|
||||||
|
for (const auto& [title, serials] : patch.titles)
|
||||||
|
{
|
||||||
|
if (serials.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString q_title = QString::fromStdString(title);
|
||||||
|
|
||||||
QTreeWidgetItem* title_level_item = nullptr;
|
QTreeWidgetItem* title_level_item = nullptr;
|
||||||
|
|
||||||
@ -182,28 +195,62 @@ void patch_manager_dialog::populate_tree()
|
|||||||
{
|
{
|
||||||
title_level_item = new QTreeWidgetItem();
|
title_level_item = new QTreeWidgetItem();
|
||||||
title_level_item->setText(0, q_title);
|
title_level_item->setText(0, q_title);
|
||||||
title_level_item->setData(0, hash_role, q_hash);
|
title_level_item->setData(0, title_role, q_title);
|
||||||
title_level_item->setData(0, node_level_role, node_level::title_level);
|
title_level_item->setData(0, node_level_role, node_level::title_level);
|
||||||
|
title_level_item->setData(0, persistance_role, true);
|
||||||
|
|
||||||
ui->patch_tree->addTopLevelItem(title_level_item);
|
ui->patch_tree->addTopLevelItem(title_level_item);
|
||||||
}
|
}
|
||||||
assert(title_level_item);
|
assert(title_level_item);
|
||||||
|
|
||||||
|
for (const auto& [serial, app_versions] : serials)
|
||||||
|
{
|
||||||
|
if (app_versions.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString q_serial = QString::fromStdString(serial);
|
||||||
|
|
||||||
// Find out if there is a node item for this serial
|
// Find out if there is a node item for this serial
|
||||||
QTreeWidgetItem* serial_level_item = gui::utils::find_child(title_level_item, q_serials);
|
QTreeWidgetItem* serial_level_item = gui::utils::find_child(title_level_item, q_serial);
|
||||||
|
|
||||||
// Add a node item for this serial if it doesn't exist yet
|
// Add a node item for this serial if it doesn't exist yet
|
||||||
if (!serial_level_item)
|
if (!serial_level_item)
|
||||||
{
|
{
|
||||||
serial_level_item = new QTreeWidgetItem();
|
serial_level_item = new QTreeWidgetItem();
|
||||||
serial_level_item->setText(0, q_serials);
|
serial_level_item->setText(0, q_serial);
|
||||||
serial_level_item->setData(0, hash_role, q_hash);
|
serial_level_item->setData(0, title_role, q_title);
|
||||||
|
serial_level_item->setData(0, serial_role, q_serial);
|
||||||
serial_level_item->setData(0, node_level_role, node_level::serial_level);
|
serial_level_item->setData(0, node_level_role, node_level::serial_level);
|
||||||
|
serial_level_item->setData(0, persistance_role, true);
|
||||||
|
|
||||||
title_level_item->addChild(serial_level_item);
|
title_level_item->addChild(serial_level_item);
|
||||||
}
|
}
|
||||||
assert(serial_level_item);
|
assert(serial_level_item);
|
||||||
|
|
||||||
|
for (const auto& [app_version, enabled] : app_versions)
|
||||||
|
{
|
||||||
|
const QString q_app_version = QString::fromStdString(app_version);
|
||||||
|
|
||||||
|
// Find out if there is a node item for this app version
|
||||||
|
QTreeWidgetItem* app_version_level_item = gui::utils::find_child(serial_level_item, q_app_version);
|
||||||
|
|
||||||
|
// Add a node item for this app version if it doesn't exist yet
|
||||||
|
if (!app_version_level_item)
|
||||||
|
{
|
||||||
|
app_version_level_item = new QTreeWidgetItem();
|
||||||
|
app_version_level_item->setText(0, q_app_version);
|
||||||
|
app_version_level_item->setData(0, title_role, q_title);
|
||||||
|
app_version_level_item->setData(0, serial_role, q_serial);
|
||||||
|
app_version_level_item->setData(0, app_version_role, q_app_version);
|
||||||
|
app_version_level_item->setData(0, node_level_role, node_level::app_version_level);
|
||||||
|
app_version_level_item->setData(0, persistance_role, true);
|
||||||
|
|
||||||
|
serial_level_item->addChild(app_version_level_item);
|
||||||
|
}
|
||||||
|
assert(app_version_level_item);
|
||||||
|
|
||||||
// Add a checkable leaf item for this patch
|
// Add a checkable leaf item for this patch
|
||||||
const QString q_description = QString::fromStdString(description);
|
const QString q_description = QString::fromStdString(description);
|
||||||
QString visible_description = q_description;
|
QString visible_description = q_description;
|
||||||
@ -211,7 +258,7 @@ void patch_manager_dialog::populate_tree()
|
|||||||
const auto match_criteria = QList<QPair<int, QVariant>>() << QPair(description_role, q_description) << QPair(persistance_role, true);
|
const auto match_criteria = QList<QPair<int, QVariant>>() << QPair(description_role, q_description) << QPair(persistance_role, true);
|
||||||
|
|
||||||
// Add counter to leafs if the name already exists due to different hashes of the same game (PPU, SPU, PRX, OVL)
|
// 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 = gui::utils::find_children_by_data(serial_level_item, match_criteria); matches.count() > 0)
|
if (const auto matches = gui::utils::find_children_by_data(app_version_level_item, match_criteria, false); matches.count() > 0)
|
||||||
{
|
{
|
||||||
if (auto only_match = matches.count() == 1 ? matches[0] : nullptr)
|
if (auto only_match = matches.count() == 1 ? matches[0] : nullptr)
|
||||||
{
|
{
|
||||||
@ -222,21 +269,24 @@ void patch_manager_dialog::populate_tree()
|
|||||||
|
|
||||||
QTreeWidgetItem* patch_level_item = new QTreeWidgetItem();
|
QTreeWidgetItem* patch_level_item = new QTreeWidgetItem();
|
||||||
patch_level_item->setText(0, visible_description);
|
patch_level_item->setText(0, visible_description);
|
||||||
patch_level_item->setCheckState(0, patch.enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
patch_level_item->setCheckState(0, enabled ? Qt::CheckState::Checked : Qt::CheckState::Unchecked);
|
||||||
patch_level_item->setData(0, hash_role, q_hash);
|
patch_level_item->setData(0, hash_role, q_hash);
|
||||||
|
patch_level_item->setData(0, title_role, q_title);
|
||||||
|
patch_level_item->setData(0, serial_role, q_serial);
|
||||||
|
patch_level_item->setData(0, app_version_role, q_app_version);
|
||||||
patch_level_item->setData(0, description_role, q_description);
|
patch_level_item->setData(0, description_role, q_description);
|
||||||
|
patch_level_item->setData(0, patch_group_role, q_patch_group);
|
||||||
patch_level_item->setData(0, node_level_role, node_level::patch_level);
|
patch_level_item->setData(0, node_level_role, node_level::patch_level);
|
||||||
|
|
||||||
serial_level_item->addChild(patch_level_item);
|
|
||||||
|
|
||||||
// Persist items
|
|
||||||
title_level_item->setData(0, persistance_role, true);
|
|
||||||
serial_level_item->setData(0, persistance_role, true);
|
|
||||||
patch_level_item->setData(0, persistance_role, true);
|
patch_level_item->setData(0, persistance_role, true);
|
||||||
|
|
||||||
|
app_version_level_item->addChild(patch_level_item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ui->patch_tree->sortByColumn(0, Qt::SortOrder::AscendingOrder);
|
const auto match_criteria = QList<QPair<int, QVariant>>() << QPair(persistance_role, true);
|
||||||
|
|
||||||
for (int i = ui->patch_tree->topLevelItemCount() - 1; i >= 0; i--)
|
for (int i = ui->patch_tree->topLevelItemCount() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
@ -248,34 +298,11 @@ void patch_manager_dialog::populate_tree()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
title_level_item->sortChildren(0, Qt::SortOrder::AscendingOrder);
|
gui::utils::remove_children(title_level_item, match_criteria, true);
|
||||||
|
|
||||||
for (int j = title_level_item->childCount() - 1; j >= 0; j--)
|
|
||||||
{
|
|
||||||
if (auto serial_level_item = title_level_item->child(j))
|
|
||||||
{
|
|
||||||
if (!serial_level_item->data(0, persistance_role).toBool())
|
|
||||||
{
|
|
||||||
delete title_level_item->takeChild(j);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int k = serial_level_item->childCount() - 1; k >= 0; k--)
|
|
||||||
{
|
|
||||||
if (auto leaf_item = serial_level_item->child(k))
|
|
||||||
{
|
|
||||||
if (!leaf_item->data(0, persistance_role).toBool())
|
|
||||||
{
|
|
||||||
delete serial_level_item->takeChild(k);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
serial_level_item->sortChildren(0, Qt::SortOrder::AscendingOrder);
|
gui::utils::sort_tree(ui->patch_tree, Qt::SortOrder::AscendingOrder, true);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_manager_dialog::save_config()
|
void patch_manager_dialog::save_config()
|
||||||
@ -314,74 +341,75 @@ void patch_manager_dialog::filter_patches(const QString& term)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_manager_dialog::update_patch_info(const patch_engine::patch_info& info)
|
void patch_manager_dialog::update_patch_info(const patch_manager_dialog::gui_patch_info& info)
|
||||||
{
|
{
|
||||||
ui->label_hash->setText(QString::fromStdString(info.hash));
|
ui->label_hash->setText(info.hash);
|
||||||
ui->label_author->setText(QString::fromStdString(info.author));
|
ui->label_author->setText(info.author);
|
||||||
ui->label_notes->setText(QString::fromStdString(info.notes));
|
ui->label_notes->setText(info.notes);
|
||||||
ui->label_description->setText(QString::fromStdString(info.description));
|
ui->label_description->setText(info.description);
|
||||||
ui->label_patch_version->setText(QString::fromStdString(info.patch_version));
|
ui->label_patch_version->setText(info.patch_version);
|
||||||
ui->label_serials->setText(QString::fromStdString(info.serials));
|
ui->label_serial->setText(info.serial);
|
||||||
ui->label_title->setText(QString::fromStdString(info.title));
|
ui->label_title->setText(info.title);
|
||||||
|
ui->label_app_version->setText(info.app_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
void patch_manager_dialog::on_item_selected(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/)
|
void patch_manager_dialog::on_item_selected(QTreeWidgetItem *current, QTreeWidgetItem * /*previous*/)
|
||||||
{
|
{
|
||||||
if (!current)
|
if (!current)
|
||||||
{
|
{
|
||||||
|
// Clear patch info if no item is selected
|
||||||
|
update_patch_info({});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get patch identifiers stored in item data
|
|
||||||
const node_level level = static_cast<node_level>(current->data(0, node_level_role).toInt());
|
const node_level level = static_cast<node_level>(current->data(0, node_level_role).toInt());
|
||||||
const std::string hash = current->data(0, hash_role).toString().toStdString();
|
|
||||||
std::string description = current->data(0, description_role).toString().toStdString();
|
|
||||||
|
|
||||||
|
patch_manager_dialog::gui_patch_info info{};
|
||||||
|
|
||||||
|
switch (level)
|
||||||
|
{
|
||||||
|
case node_level::patch_level:
|
||||||
|
{
|
||||||
|
// Get patch identifiers stored in item data
|
||||||
|
info.hash = current->data(0, hash_role).toString();
|
||||||
|
const std::string hash = info.hash.toStdString();
|
||||||
|
const std::string description = current->data(0, description_role).toString().toStdString();
|
||||||
|
|
||||||
|
// Find the patch for this item and get its metadata
|
||||||
if (m_map.find(hash) != m_map.end())
|
if (m_map.find(hash) != m_map.end())
|
||||||
{
|
{
|
||||||
const auto& container = m_map.at(hash);
|
const auto& container = m_map.at(hash);
|
||||||
|
|
||||||
// Find the patch for this item and show its metadata
|
|
||||||
if (!container.is_legacy && container.patch_info_map.find(description) != container.patch_info_map.end())
|
if (!container.is_legacy && container.patch_info_map.find(description) != container.patch_info_map.end())
|
||||||
{
|
{
|
||||||
update_patch_info(container.patch_info_map.at(description));
|
const auto& found_info = container.patch_info_map.at(description);
|
||||||
return;
|
info.author = QString::fromStdString(found_info.author);
|
||||||
|
info.notes = QString::fromStdString(found_info.notes);
|
||||||
|
info.description = QString::fromStdString(found_info.description);
|
||||||
|
info.patch_version = QString::fromStdString(found_info.patch_version);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Show shared info if no patch was found
|
[[fallthrough]];
|
||||||
patch_engine::patch_info info{};
|
}
|
||||||
info.hash = hash;
|
case node_level::app_version_level:
|
||||||
info.version = container.version;
|
|
||||||
|
|
||||||
// Use the first entry for more shared information
|
|
||||||
const auto match_criteria = QList<QPair<int, QVariant>>() << QPair(node_level_role, node_level::patch_level);
|
|
||||||
|
|
||||||
if (const auto matches = gui::utils::find_children_by_data(current, match_criteria, true); matches.count() > 0 && matches[0])
|
|
||||||
{
|
{
|
||||||
description = matches[0]->data(0, description_role).toString().toStdString();
|
info.app_version = current->data(0, app_version_role).toString();
|
||||||
|
[[fallthrough]];
|
||||||
if (container.patch_info_map.find(description) != container.patch_info_map.end())
|
}
|
||||||
|
case node_level::serial_level:
|
||||||
{
|
{
|
||||||
const auto& fallback_info = container.patch_info_map.at(description);
|
info.serial = current->data(0, serial_role).toString();
|
||||||
|
[[fallthrough]];
|
||||||
if (level >= node_level::title_level)
|
}
|
||||||
|
case node_level::title_level:
|
||||||
|
default:
|
||||||
{
|
{
|
||||||
info.title = fallback_info.title;
|
info.title = current->data(0, title_role).toString();
|
||||||
}
|
break;
|
||||||
if (level >= node_level::serial_level)
|
|
||||||
{
|
|
||||||
info.serials = fallback_info.serials;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
update_patch_info(info);
|
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*/)
|
void patch_manager_dialog::on_item_changed(QTreeWidgetItem *item, int /*column*/)
|
||||||
@ -395,8 +423,30 @@ void patch_manager_dialog::on_item_changed(QTreeWidgetItem *item, int /*column*/
|
|||||||
const bool enabled = item->checkState(0) == Qt::CheckState::Checked;
|
const bool enabled = item->checkState(0) == Qt::CheckState::Checked;
|
||||||
|
|
||||||
// Get patch identifiers stored in item data
|
// Get patch identifiers stored in item data
|
||||||
|
const node_level level = static_cast<node_level>(item->data(0, node_level_role).toInt());
|
||||||
const std::string hash = item->data(0, hash_role).toString().toStdString();
|
const std::string hash = item->data(0, hash_role).toString().toStdString();
|
||||||
|
const std::string title = item->data(0, title_role).toString().toStdString();
|
||||||
|
const std::string serial = item->data(0, serial_role).toString().toStdString();
|
||||||
|
const std::string app_version = item->data(0, app_version_role).toString().toStdString();
|
||||||
const std::string description = item->data(0, description_role).toString().toStdString();
|
const std::string description = item->data(0, description_role).toString().toStdString();
|
||||||
|
const std::string patch_group = item->data(0, patch_group_role).toString().toStdString();
|
||||||
|
|
||||||
|
// Uncheck other patches with the same patch_group if this patch was enabled
|
||||||
|
if (const auto node = item->parent(); node && enabled && !patch_group.empty() && level == node_level::patch_level)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < node->childCount(); i++)
|
||||||
|
{
|
||||||
|
if (const auto other = node->child(i); other && other != item)
|
||||||
|
{
|
||||||
|
const std::string other_patch_group = other->data(0, patch_group_role).toString().toStdString();
|
||||||
|
|
||||||
|
if (other_patch_group == patch_group)
|
||||||
|
{
|
||||||
|
other->setCheckState(0, Qt::CheckState::Unchecked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Enable/disable the patch for this item and show its metadata
|
// Enable/disable the patch for this item and show its metadata
|
||||||
if (m_map.find(hash) != m_map.end())
|
if (m_map.find(hash) != m_map.end())
|
||||||
@ -405,9 +455,8 @@ void patch_manager_dialog::on_item_changed(QTreeWidgetItem *item, int /*column*/
|
|||||||
|
|
||||||
if (!container.is_legacy && container.patch_info_map.find(description) != container.patch_info_map.end())
|
if (!container.is_legacy && container.patch_info_map.find(description) != container.patch_info_map.end())
|
||||||
{
|
{
|
||||||
auto& patch = m_map[hash].patch_info_map[description];
|
m_map[hash].patch_info_map[description].titles[title][serial][app_version] = enabled;
|
||||||
patch.enabled = enabled;
|
on_item_selected(item, nullptr);
|
||||||
update_patch_info(patch);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,18 @@ class patch_manager_dialog : public QDialog
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
struct gui_patch_info
|
||||||
|
{
|
||||||
|
QString hash;
|
||||||
|
QString title;
|
||||||
|
QString serial;
|
||||||
|
QString app_version;
|
||||||
|
QString author;
|
||||||
|
QString notes;
|
||||||
|
QString description;
|
||||||
|
QString patch_version;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit patch_manager_dialog(std::shared_ptr<gui_settings> gui_settings, QWidget* parent = nullptr);
|
explicit patch_manager_dialog(std::shared_ptr<gui_settings> gui_settings, QWidget* parent = nullptr);
|
||||||
~patch_manager_dialog();
|
~patch_manager_dialog();
|
||||||
@ -36,7 +48,7 @@ private:
|
|||||||
void load_patches();
|
void load_patches();
|
||||||
void populate_tree();
|
void populate_tree();
|
||||||
void save_config();
|
void save_config();
|
||||||
void update_patch_info(const patch_engine::patch_info& info);
|
void update_patch_info(const gui_patch_info& info);
|
||||||
bool is_valid_file(const QMimeData& md, QStringList* drop_paths = nullptr);
|
bool is_valid_file(const QMimeData& md, QStringList* drop_paths = nullptr);
|
||||||
|
|
||||||
std::shared_ptr<gui_settings> m_gui_settings;
|
std::shared_ptr<gui_settings> m_gui_settings;
|
||||||
|
@ -87,25 +87,6 @@
|
|||||||
<string>Patch Information</string>
|
<string>Patch Information</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="patch_info_gb_layout">
|
<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>
|
<item>
|
||||||
<widget class="QGroupBox" name="gb_title">
|
<widget class="QGroupBox" name="gb_title">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
@ -126,13 +107,48 @@
|
|||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QGroupBox" name="gb_serials">
|
<widget class="QGroupBox" name="gb_serial">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Serials</string>
|
<string>Serial</string>
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="serials_layout">
|
<layout class="QVBoxLayout" name="serial_layout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QLabel" name="label_serials">
|
<widget class="QLabel" name="label_serial">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QGroupBox" name="gb_app_version">
|
||||||
|
<property name="title">
|
||||||
|
<string>App Version</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="layout_app_version">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_app_version">
|
||||||
|
<property name="text">
|
||||||
|
<string/>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<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">
|
<property name="text">
|
||||||
<string/>
|
<string/>
|
||||||
</property>
|
</property>
|
||||||
|
@ -382,11 +382,75 @@ namespace gui
|
|||||||
{
|
{
|
||||||
if (parent)
|
if (parent)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < parent->childCount(); i++)
|
for (int i = parent->childCount() - 1; i >= 0; i--)
|
||||||
{
|
{
|
||||||
parent->removeChild(parent->child(i));
|
parent->removeChild(parent->child(i));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void remove_children(QTreeWidgetItem* parent, const QList<QPair<int /*role*/, QVariant /*data*/>>& criteria, bool recursive)
|
||||||
|
{
|
||||||
|
if (parent)
|
||||||
|
{
|
||||||
|
for (int i = parent->childCount() - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
if (auto item = parent->child(i))
|
||||||
|
{
|
||||||
|
bool match = true;
|
||||||
|
|
||||||
|
for (const auto [role, data] : criteria)
|
||||||
|
{
|
||||||
|
if (item->data(0, role) != data)
|
||||||
|
{
|
||||||
|
match = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!match)
|
||||||
|
{
|
||||||
|
parent->removeChild(item);
|
||||||
|
}
|
||||||
|
else if (recursive)
|
||||||
|
{
|
||||||
|
remove_children(item, criteria, recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort_tree_item(QTreeWidgetItem* item, Qt::SortOrder sort_order, bool recursive)
|
||||||
|
{
|
||||||
|
if (item)
|
||||||
|
{
|
||||||
|
item->sortChildren(0, sort_order);
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < item->childCount(); i++)
|
||||||
|
{
|
||||||
|
sort_tree_item(item->child(i), sort_order, recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void sort_tree(QTreeWidget* tree, Qt::SortOrder sort_order, bool recursive)
|
||||||
|
{
|
||||||
|
if (tree)
|
||||||
|
{
|
||||||
|
tree->sortByColumn(0, sort_order);
|
||||||
|
|
||||||
|
if (recursive)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < tree->topLevelItemCount(); i++)
|
||||||
|
{
|
||||||
|
sort_tree_item(tree->topLevelItem(i), sort_order, recursive);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} // utils
|
} // utils
|
||||||
} // gui
|
} // gui
|
||||||
|
@ -71,12 +71,18 @@ namespace gui
|
|||||||
QTreeWidgetItem* find_child(QTreeWidgetItem* parent, const QString& text);
|
QTreeWidgetItem* find_child(QTreeWidgetItem* parent, const QString& text);
|
||||||
|
|
||||||
// Finds all children of a QTreeWidgetItem that match the given criteria
|
// Finds all children of a QTreeWidgetItem that match the given criteria
|
||||||
QList<QTreeWidgetItem*> find_children_by_data(QTreeWidgetItem* parent, const QList<QPair<int /*role*/, QVariant /*data*/>>& criteria, bool recursive = false);
|
QList<QTreeWidgetItem*> find_children_by_data(QTreeWidgetItem* parent, const QList<QPair<int /*role*/, QVariant /*data*/>>& criteria, bool recursive);
|
||||||
|
|
||||||
// Constructs and adds a child to a QTreeWidgetItem
|
// Constructs and adds a child to a QTreeWidgetItem
|
||||||
QTreeWidgetItem* add_child(QTreeWidgetItem* parent, const QString& text, int column = 0);
|
QTreeWidgetItem* add_child(QTreeWidgetItem* parent, const QString& text, int column = 0);
|
||||||
|
|
||||||
// Removes all children of a QTreeWidgetItem
|
// Removes all children of a QTreeWidgetItem
|
||||||
void remove_children(QTreeWidgetItem* parent);
|
void remove_children(QTreeWidgetItem* parent);
|
||||||
|
|
||||||
|
// Removes all children of a QTreeWidgetItem that don't match the given criteria
|
||||||
|
void remove_children(QTreeWidgetItem* parent, const QList<QPair<int /*role*/, QVariant /*data*/>>& criteria, bool recursive);
|
||||||
|
|
||||||
|
// Sort a QTreeWidget (currently only column 0)
|
||||||
|
void sort_tree(QTreeWidget* tree, Qt::SortOrder sort_order, bool recursive);
|
||||||
} // utils
|
} // utils
|
||||||
} // gui
|
} // gui
|
||||||
|
Loading…
x
Reference in New Issue
Block a user