mirror of https://git.suyu.dev/suyu/suyu
Merge pull request #1819 from DarkLordZach/disable-addons
patch_manager: Add support for disabling patches
This commit is contained in:
commit
2c45c6d234
|
@ -8,6 +8,7 @@
|
|||
#include <thread>
|
||||
#include <utility>
|
||||
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
|
@ -40,7 +41,6 @@ namespace Core {
|
|||
|
||||
/*static*/ System System::s_instance;
|
||||
|
||||
namespace {
|
||||
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
const std::string& path) {
|
||||
// To account for split 00+01+etc files.
|
||||
|
@ -69,11 +69,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
|||
return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
|
||||
}
|
||||
|
||||
if (FileUtil::IsDirectory(path))
|
||||
return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
|
||||
|
||||
return vfs->OpenFile(path, FileSys::Mode::Read);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
struct System::Impl {
|
||||
|
||||
Cpu& CurrentCpuCore() {
|
||||
return cpu_core_manager.GetCurrentCore();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
#include <string>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
#include "core/hle/kernel/object.h"
|
||||
|
||||
namespace Core::Frontend {
|
||||
|
@ -55,6 +56,9 @@ class TelemetrySession;
|
|||
|
||||
struct PerfStatsResults;
|
||||
|
||||
FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
|
||||
const std::string& path);
|
||||
|
||||
class System {
|
||||
public:
|
||||
System(const System&) = delete;
|
||||
|
|
|
@ -56,6 +56,10 @@ PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
|
|||
|
||||
PatchManager::~PatchManager() = default;
|
||||
|
||||
u64 PatchManager::GetTitleID() const {
|
||||
return title_id;
|
||||
}
|
||||
|
||||
VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
||||
LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
|
||||
|
||||
|
@ -73,11 +77,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
|
||||
|
||||
if (update != nullptr && update->GetExeFS() != nullptr &&
|
||||
if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
|
||||
update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
|
||||
LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
|
||||
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
|
||||
|
@ -95,6 +103,9 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||
std::vector<VirtualDir> layers;
|
||||
layers.reserve(patch_dirs.size() + 1);
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
|
||||
continue;
|
||||
|
||||
auto exefs_dir = subdir->GetSubdirectory("exefs");
|
||||
if (exefs_dir != nullptr)
|
||||
layers.push_back(std::move(exefs_dir));
|
||||
|
@ -111,11 +122,16 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
|
|||
return exefs;
|
||||
}
|
||||
|
||||
static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) {
|
||||
std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const {
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
std::vector<VirtualFile> out;
|
||||
out.reserve(patch_dirs.size());
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
|
||||
continue;
|
||||
|
||||
auto exefs_dir = subdir->GetSubdirectory("exefs");
|
||||
if (exefs_dir != nullptr) {
|
||||
for (const auto& file : exefs_dir->GetFiles()) {
|
||||
|
@ -228,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
|||
return;
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
auto patch_dirs = load_dir->GetSubdirectories();
|
||||
std::sort(patch_dirs.begin(), patch_dirs.end(),
|
||||
[](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
|
||||
|
@ -237,6 +254,9 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
|
|||
layers.reserve(patch_dirs.size() + 1);
|
||||
layers_ext.reserve(patch_dirs.size() + 1);
|
||||
for (const auto& subdir : patch_dirs) {
|
||||
if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
|
||||
continue;
|
||||
|
||||
auto romfs_dir = subdir->GetSubdirectory("romfs");
|
||||
if (romfs_dir != nullptr)
|
||||
layers.push_back(std::move(romfs_dir));
|
||||
|
@ -282,7 +302,12 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
|||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
const auto update = installed.GetEntryRaw(update_tid, type);
|
||||
if (update != nullptr) {
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
const auto update_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
|
||||
|
||||
if (!update_disabled && update != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
|
@ -290,7 +315,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
|
|||
FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
|
||||
romfs = new_nca->GetRomFS();
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
} else if (!update_disabled && update_raw != nullptr) {
|
||||
const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
|
||||
if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
|
||||
new_nca->GetRomFS() != nullptr) {
|
||||
|
@ -320,25 +345,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
|||
VirtualFile update_raw) const {
|
||||
std::map<std::string, std::string, std::less<>> out;
|
||||
const auto installed = Service::FileSystem::GetUnionContents();
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
// Game Updates
|
||||
const auto update_tid = GetUpdateTitleID(title_id);
|
||||
PatchManager update{update_tid};
|
||||
auto [nacp, discard_icon_file] = update.GetControlMetadata();
|
||||
|
||||
const auto update_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
|
||||
const auto update_label = update_disabled ? "[D] Update" : "Update";
|
||||
|
||||
if (nacp != nullptr) {
|
||||
out.insert_or_assign("Update", nacp->GetVersionString());
|
||||
out.insert_or_assign(update_label, nacp->GetVersionString());
|
||||
} else {
|
||||
if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
|
||||
const auto meta_ver = installed.GetEntryVersion(update_tid);
|
||||
if (meta_ver.value_or(0) == 0) {
|
||||
out.insert_or_assign("Update", "");
|
||||
out.insert_or_assign(update_label, "");
|
||||
} else {
|
||||
out.insert_or_assign(
|
||||
"Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
|
||||
update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
|
||||
}
|
||||
} else if (update_raw != nullptr) {
|
||||
out.insert_or_assign("Update", "PACKED");
|
||||
out.insert_or_assign(update_label, "PACKED");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -378,7 +408,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
|||
if (types.empty())
|
||||
continue;
|
||||
|
||||
out.insert_or_assign(mod->GetName(), types);
|
||||
const auto mod_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
|
||||
out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -401,7 +433,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
|
|||
|
||||
list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
|
||||
|
||||
out.insert_or_assign("DLC", std::move(list));
|
||||
const auto dlc_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
|
||||
out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
|
||||
}
|
||||
|
||||
return out;
|
||||
|
|
|
@ -30,6 +30,8 @@ public:
|
|||
explicit PatchManager(u64 title_id);
|
||||
~PatchManager();
|
||||
|
||||
u64 GetTitleID() const;
|
||||
|
||||
// Currently tracked ExeFS patches:
|
||||
// - Game Updates
|
||||
VirtualDir PatchExeFS(VirtualDir exefs) const;
|
||||
|
@ -63,6 +65,9 @@ public:
|
|||
std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
|
||||
|
||||
private:
|
||||
std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
|
||||
const std::string& build_id) const;
|
||||
|
||||
u64 title_id;
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include "core/hle/service/aoc/aoc_u.h"
|
||||
#include "core/hle/service/filesystem/filesystem.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
namespace Service::AOC {
|
||||
|
||||
|
@ -76,6 +77,13 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
|
|||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[current];
|
||||
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
|
||||
rb.Push<u32>(0);
|
||||
return;
|
||||
}
|
||||
|
||||
rb.Push<u32>(static_cast<u32>(
|
||||
std::count_if(add_on_content.begin(), add_on_content.end(),
|
||||
[current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
|
||||
|
@ -96,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
|
|||
out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
|
||||
}
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[current];
|
||||
if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end())
|
||||
out = {};
|
||||
|
||||
if (out.size() < offset) {
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(DarkLordZach): Find the correct error code.
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
@ -243,6 +244,15 @@ public:
|
|||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the developer of the application
|
||||
* @param developer Reference to store the application developer into
|
||||
* @return ResultStatus result of function
|
||||
*/
|
||||
virtual ResultStatus ReadDeveloper(std::string& developer) {
|
||||
return ResultStatus::ErrorNotImplemented;
|
||||
}
|
||||
|
||||
protected:
|
||||
FileSys::VirtualFile file;
|
||||
bool is_loaded = false;
|
||||
|
|
|
@ -151,4 +151,11 @@ ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
|
|||
title = nacp_file->GetApplicationName();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) {
|
||||
if (nacp_file == nullptr)
|
||||
return ResultStatus::ErrorNoControl;
|
||||
developer = nacp_file->GetDeveloperName();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
} // namespace Loader
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
ResultStatus ReadDeveloper(std::string& developer) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::NSP> nsp;
|
||||
|
|
|
@ -120,4 +120,11 @@ ResultStatus AppLoader_XCI::ReadTitle(std::string& title) {
|
|||
title = nacp_file->GetApplicationName();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
|
||||
ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) {
|
||||
if (nacp_file == nullptr)
|
||||
return ResultStatus::ErrorNoControl;
|
||||
developer = nacp_file->GetDeveloperName();
|
||||
return ResultStatus::Success;
|
||||
}
|
||||
} // namespace Loader
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
ResultStatus ReadProgramId(u64& out_program_id) override;
|
||||
ResultStatus ReadIcon(std::vector<u8>& buffer) override;
|
||||
ResultStatus ReadTitle(std::string& title) override;
|
||||
ResultStatus ReadDeveloper(std::string& developer) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<FileSys::XCI> xci;
|
||||
|
|
|
@ -6,8 +6,10 @@
|
|||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <map>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Settings {
|
||||
|
@ -411,6 +413,9 @@ struct Values {
|
|||
std::string web_api_url;
|
||||
std::string yuzu_username;
|
||||
std::string yuzu_token;
|
||||
|
||||
// Add-Ons
|
||||
std::map<u64, std::vector<std::string>> disabled_addons;
|
||||
} extern values;
|
||||
|
||||
void Apply();
|
||||
|
|
|
@ -35,6 +35,8 @@ add_executable(yuzu
|
|||
configuration/configure_mouse_advanced.h
|
||||
configuration/configure_system.cpp
|
||||
configuration/configure_system.h
|
||||
configuration/configure_per_general.cpp
|
||||
configuration/configure_per_general.h
|
||||
configuration/configure_touchscreen_advanced.cpp
|
||||
configuration/configure_touchscreen_advanced.h
|
||||
configuration/configure_web.cpp
|
||||
|
@ -86,6 +88,7 @@ set(UIS
|
|||
configuration/configure_input.ui
|
||||
configuration/configure_input_player.ui
|
||||
configuration/configure_mouse_advanced.ui
|
||||
configuration/configure_per_general.ui
|
||||
configuration/configure_system.ui
|
||||
configuration/configure_touchscreen_advanced.ui
|
||||
configuration/configure_web.ui
|
||||
|
|
|
@ -441,6 +441,21 @@ void Config::ReadValues() {
|
|||
Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
|
||||
qt_config->endGroup();
|
||||
|
||||
const auto size = qt_config->beginReadArray("DisabledAddOns");
|
||||
for (int i = 0; i < size; ++i) {
|
||||
qt_config->setArrayIndex(i);
|
||||
const auto title_id = qt_config->value("title_id", 0).toULongLong();
|
||||
std::vector<std::string> out;
|
||||
const auto d_size = qt_config->beginReadArray("disabled");
|
||||
for (int j = 0; j < d_size; ++j) {
|
||||
qt_config->setArrayIndex(j);
|
||||
out.push_back(qt_config->value("d", "").toString().toStdString());
|
||||
}
|
||||
qt_config->endArray();
|
||||
Settings::values.disabled_addons.insert_or_assign(title_id, out);
|
||||
}
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
|
||||
UISettings::values.enable_discord_presence =
|
||||
|
@ -645,6 +660,21 @@ void Config::SaveValues() {
|
|||
qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginWriteArray("DisabledAddOns");
|
||||
int i = 0;
|
||||
for (const auto& elem : Settings::values.disabled_addons) {
|
||||
qt_config->setArrayIndex(i);
|
||||
qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
|
||||
qt_config->beginWriteArray("disabled");
|
||||
for (std::size_t j = 0; j < elem.second.size(); ++j) {
|
||||
qt_config->setArrayIndex(j);
|
||||
qt_config->setValue("d", QString::fromStdString(elem.second[j]));
|
||||
}
|
||||
qt_config->endArray();
|
||||
++i;
|
||||
}
|
||||
qt_config->endArray();
|
||||
|
||||
qt_config->beginGroup("UI");
|
||||
qt_config->setValue("theme", UISettings::values.theme);
|
||||
qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
|
||||
|
|
|
@ -0,0 +1,170 @@
|
|||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
#include <QString>
|
||||
#include <QTimer>
|
||||
#include <QTreeView>
|
||||
|
||||
#include "core/file_sys/control_metadata.h"
|
||||
#include "core/file_sys/patch_manager.h"
|
||||
#include "core/file_sys/xts_archive.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "ui_configure_per_general.h"
|
||||
#include "yuzu/configuration/config.h"
|
||||
#include "yuzu/configuration/configure_input.h"
|
||||
#include "yuzu/configuration/configure_per_general.h"
|
||||
#include "yuzu/ui_settings.h"
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
|
||||
: QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) {
|
||||
|
||||
ui->setupUi(this);
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
setWindowTitle(tr("Properties"));
|
||||
|
||||
layout = new QVBoxLayout;
|
||||
tree_view = new QTreeView;
|
||||
item_model = new QStandardItemModel(tree_view);
|
||||
tree_view->setModel(item_model);
|
||||
tree_view->setAlternatingRowColors(true);
|
||||
tree_view->setSelectionMode(QHeaderView::SingleSelection);
|
||||
tree_view->setSelectionBehavior(QHeaderView::SelectRows);
|
||||
tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
|
||||
tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
|
||||
tree_view->setSortingEnabled(true);
|
||||
tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
|
||||
tree_view->setUniformRowHeights(true);
|
||||
tree_view->setContextMenuPolicy(Qt::NoContextMenu);
|
||||
|
||||
item_model->insertColumns(0, 2);
|
||||
item_model->setHeaderData(0, Qt::Horizontal, "Patch Name");
|
||||
item_model->setHeaderData(1, Qt::Horizontal, "Version");
|
||||
|
||||
// We must register all custom types with the Qt Automoc system so that we are able to use it
|
||||
// with signals/slots. In this case, QList falls under the umbrells of custom types.
|
||||
qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
|
||||
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->setSpacing(0);
|
||||
layout->addWidget(tree_view);
|
||||
|
||||
ui->scrollArea->setLayout(layout);
|
||||
|
||||
scene = new QGraphicsScene;
|
||||
ui->icon_view->setScene(scene);
|
||||
|
||||
connect(item_model, &QStandardItemModel::itemChanged,
|
||||
[] { UISettings::values.is_game_list_reload_pending.exchange(true); });
|
||||
|
||||
this->loadConfiguration();
|
||||
}
|
||||
|
||||
ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
|
||||
|
||||
void ConfigurePerGameGeneral::applyConfiguration() {
|
||||
std::vector<std::string> disabled_addons;
|
||||
|
||||
for (const auto& item : list_items) {
|
||||
const auto disabled = item.front()->checkState() == Qt::Unchecked;
|
||||
if (disabled)
|
||||
disabled_addons.push_back(item.front()->text().toStdString());
|
||||
}
|
||||
|
||||
Settings::values.disabled_addons[title_id] = disabled_addons;
|
||||
}
|
||||
|
||||
void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
|
||||
this->file = std::move(file);
|
||||
this->loadConfiguration();
|
||||
}
|
||||
|
||||
void ConfigurePerGameGeneral::loadConfiguration() {
|
||||
if (file == nullptr)
|
||||
return;
|
||||
|
||||
const auto loader = Loader::GetLoader(file);
|
||||
|
||||
ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
|
||||
|
||||
FileSys::PatchManager pm{title_id};
|
||||
const auto control = pm.GetControlMetadata();
|
||||
|
||||
if (control.first != nullptr) {
|
||||
ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
|
||||
ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
|
||||
ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
|
||||
} else {
|
||||
std::string title;
|
||||
if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
|
||||
ui->display_name->setText(QString::fromStdString(title));
|
||||
|
||||
std::string developer;
|
||||
if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success)
|
||||
ui->display_developer->setText(QString::fromStdString(developer));
|
||||
|
||||
ui->display_version->setText(QStringLiteral("1.0.0"));
|
||||
}
|
||||
|
||||
if (control.second != nullptr) {
|
||||
scene->clear();
|
||||
|
||||
QPixmap map;
|
||||
const auto bytes = control.second->ReadAllBytes();
|
||||
map.loadFromData(bytes.data(), bytes.size());
|
||||
|
||||
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
|
||||
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
} else {
|
||||
std::vector<u8> bytes;
|
||||
if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
|
||||
scene->clear();
|
||||
|
||||
QPixmap map;
|
||||
map.loadFromData(bytes.data(), bytes.size());
|
||||
|
||||
scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
|
||||
Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
|
||||
}
|
||||
}
|
||||
|
||||
FileSys::VirtualFile update_raw;
|
||||
loader->ReadUpdateRaw(update_raw);
|
||||
|
||||
const auto& disabled = Settings::values.disabled_addons[title_id];
|
||||
|
||||
for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
|
||||
QStandardItem* first_item = new QStandardItem;
|
||||
const auto name = QString::fromStdString(patch.first).replace("[D] ", "");
|
||||
first_item->setText(name);
|
||||
first_item->setCheckable(true);
|
||||
|
||||
const auto patch_disabled =
|
||||
std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
|
||||
|
||||
first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
|
||||
|
||||
list_items.push_back(QList<QStandardItem*>{
|
||||
first_item, new QStandardItem{QString::fromStdString(patch.second)}});
|
||||
item_model->appendRow(list_items.back());
|
||||
}
|
||||
|
||||
tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
|
||||
|
||||
ui->display_filename->setText(QString::fromStdString(file->GetName()));
|
||||
|
||||
ui->display_format->setText(
|
||||
QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
|
||||
|
||||
const auto valueText = ReadableByteSize(file->GetSize());
|
||||
ui->display_size->setText(valueText);
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2016 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include <QKeyEvent>
|
||||
#include <QList>
|
||||
#include <QWidget>
|
||||
|
||||
#include "core/file_sys/vfs_types.h"
|
||||
|
||||
class QTreeView;
|
||||
class QGraphicsScene;
|
||||
class QStandardItem;
|
||||
class QStandardItemModel;
|
||||
|
||||
namespace Ui {
|
||||
class ConfigurePerGameGeneral;
|
||||
}
|
||||
|
||||
class ConfigurePerGameGeneral : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id);
|
||||
~ConfigurePerGameGeneral() override;
|
||||
|
||||
/// Save all button configurations to settings file
|
||||
void applyConfiguration();
|
||||
|
||||
void loadFromFile(FileSys::VirtualFile file);
|
||||
|
||||
private:
|
||||
std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
|
||||
FileSys::VirtualFile file;
|
||||
u64 title_id;
|
||||
|
||||
QVBoxLayout* layout;
|
||||
QTreeView* tree_view;
|
||||
QStandardItemModel* item_model;
|
||||
QGraphicsScene* scene;
|
||||
|
||||
std::vector<QList<QStandardItem*>> list_items;
|
||||
|
||||
void loadConfiguration();
|
||||
};
|
|
@ -0,0 +1,276 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>ConfigurePerGameGeneral</class>
|
||||
<widget class="QDialog" name="ConfigurePerGameGeneral">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>520</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>ConfigurePerGameGeneral</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="HorizontalLayout">
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="VerticalLayout">
|
||||
<item>
|
||||
<widget class="QGroupBox" name="GeneralGroupBox">
|
||||
<property name="title">
|
||||
<string>Info</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="GeneralHorizontalLayout">
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="display_filename">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="display_name">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Developer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="QLineEdit" name="display_size">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Name</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>Filename</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
<string>Version</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Format</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QLineEdit" name="display_version">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLineEdit" name="display_format">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Size</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="display_developer">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Title ID</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="display_title_id">
|
||||
<property name="enabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="2" rowspan="5">
|
||||
<widget class="QGraphicsView" name="icon_view">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Maximum" vsizetype="Maximum">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>128</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>128</width>
|
||||
<height>128</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="verticalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="horizontalScrollBarPolicy">
|
||||
<enum>Qt::ScrollBarAlwaysOff</enum>
|
||||
</property>
|
||||
<property name="sizeAdjustPolicy">
|
||||
<enum>QAbstractScrollArea::AdjustToContents</enum>
|
||||
</property>
|
||||
<property name="interactive">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="PerformanceGroupBox">
|
||||
<property name="title">
|
||||
<string>Add-Ons</string>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
|
||||
<item>
|
||||
<widget class="QScrollArea" name="scrollArea">
|
||||
<property name="widgetResizable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<widget class="QWidget" name="scrollAreaWidgetContents">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>350</width>
|
||||
<height>169</height>
|
||||
</rect>
|
||||
</property>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Fixed</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="standardButtons">
|
||||
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>accepted()</signal>
|
||||
<receiver>ConfigurePerGameGeneral</receiver>
|
||||
<slot>accept()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>269</x>
|
||||
<y>567</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>269</x>
|
||||
<y>294</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>buttonBox</sender>
|
||||
<signal>rejected()</signal>
|
||||
<receiver>ConfigurePerGameGeneral</receiver>
|
||||
<slot>reject()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>269</x>
|
||||
<y>567</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>269</x>
|
||||
<y>294</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
</ui>
|
|
@ -333,6 +333,8 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
|||
QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
|
||||
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
|
||||
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
|
||||
context_menu.addSeparator();
|
||||
QAction* properties = context_menu.addAction(tr("Properties"));
|
||||
|
||||
open_save_location->setEnabled(program_id != 0);
|
||||
auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
|
||||
|
@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
|
|||
connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
|
||||
connect(navigate_to_gamedb_entry, &QAction::triggered,
|
||||
[&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
|
||||
connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });
|
||||
|
||||
context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@ signals:
|
|||
void CopyTIDRequested(u64 program_id);
|
||||
void NavigateToGamedbEntryRequested(u64 program_id,
|
||||
const CompatibilityList& compatibility_list);
|
||||
void OpenPerGameGeneralRequested(const std::string& file);
|
||||
|
||||
private slots:
|
||||
void onTextChanged(const QString& newText);
|
||||
|
|
|
@ -62,7 +62,7 @@ QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
|
|||
FileSys::VirtualFile update_raw;
|
||||
loader.ReadUpdateRaw(update_raw);
|
||||
for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
|
||||
const bool is_update = kv.first == "Update";
|
||||
const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
|
||||
if (!updatable && is_update) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
|
||||
#include "applets/software_keyboard.h"
|
||||
#include "configuration/configure_per_general.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_real.h"
|
||||
#include "core/hle/service/acc/profile_manager.h"
|
||||
|
@ -441,6 +442,8 @@ void GMainWindow::ConnectWidgetEvents() {
|
|||
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
|
||||
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
|
||||
&GMainWindow::OnGameListNavigateToGamedbEntry);
|
||||
connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
|
||||
&GMainWindow::OnGameListOpenPerGameProperties);
|
||||
|
||||
connect(this, &GMainWindow::EmulationStarting, render_window,
|
||||
&GRenderWindow::OnEmulationStarting);
|
||||
|
@ -988,6 +991,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
|
|||
QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
|
||||
}
|
||||
|
||||
void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
|
||||
u64 title_id{};
|
||||
const auto v_file = Core::GetGameFileFromPath(vfs, file);
|
||||
const auto loader = Loader::GetLoader(v_file);
|
||||
if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
|
||||
QMessageBox::information(this, tr("Properties"),
|
||||
tr("The game properties could not be loaded."));
|
||||
return;
|
||||
}
|
||||
|
||||
ConfigurePerGameGeneral dialog(this, title_id);
|
||||
dialog.loadFromFile(v_file);
|
||||
auto result = dialog.exec();
|
||||
if (result == QDialog::Accepted) {
|
||||
dialog.applyConfiguration();
|
||||
|
||||
const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
|
||||
if (reload) {
|
||||
game_list->PopulateAsync(UISettings::values.gamedir,
|
||||
UISettings::values.gamedir_deepscan);
|
||||
}
|
||||
|
||||
config->Save();
|
||||
}
|
||||
}
|
||||
|
||||
void GMainWindow::OnMenuLoadFile() {
|
||||
const QString extensions =
|
||||
QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main");
|
||||
|
|
|
@ -168,6 +168,7 @@ private slots:
|
|||
void OnGameListCopyTID(u64 program_id);
|
||||
void OnGameListNavigateToGamedbEntry(u64 program_id,
|
||||
const CompatibilityList& compatibility_list);
|
||||
void OnGameListOpenPerGameProperties(const std::string& file);
|
||||
void OnMenuLoadFile();
|
||||
void OnMenuLoadFolder();
|
||||
void OnMenuInstallToNAND();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <SDL.h>
|
||||
#include <inih/cpp/INIReader.h>
|
||||
#include "common/file_util.h"
|
||||
|
@ -369,6 +370,23 @@ void Config::ReadValues() {
|
|||
Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
|
||||
Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
|
||||
|
||||
const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
|
||||
std::stringstream ss(title_list);
|
||||
std::string line;
|
||||
while (std::getline(ss, line, '|')) {
|
||||
const auto title_id = std::stoul(line, nullptr, 16);
|
||||
const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
|
||||
|
||||
std::stringstream inner_ss(disabled_list);
|
||||
std::string inner_line;
|
||||
std::vector<std::string> out;
|
||||
while (std::getline(inner_ss, inner_line, '|')) {
|
||||
out.push_back(inner_line);
|
||||
}
|
||||
|
||||
Settings::values.disabled_addons.insert_or_assign(title_id, out);
|
||||
}
|
||||
|
||||
// Web Service
|
||||
Settings::values.enable_telemetry =
|
||||
sdl2_config->GetBoolean("WebService", "enable_telemetry", true);
|
||||
|
|
|
@ -221,5 +221,12 @@ web_api_url = https://api.yuzu-emu.org
|
|||
# See https://profile.yuzu-emu.org/ for more info
|
||||
yuzu_username =
|
||||
yuzu_token =
|
||||
|
||||
[AddOns]
|
||||
# Used to disable add-ons
|
||||
# List of title IDs of games that will have add-ons disabled (separated by '|'):
|
||||
title_ids =
|
||||
# For each title ID, have a key/value pair called `disabled_<title_id>` equal to the names of the add-ons to disable (sep. by '|')
|
||||
# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
|
||||
)";
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue