From 282b7e902ce9992eaa60d2c259284f83751beaaa Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 13:59:40 -0500 Subject: [PATCH 1/8] settings: Store list of disabled add-ons per title ID --- src/core/settings.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/core/settings.h b/src/core/settings.h index a0c5fd447..de01b05c0 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -6,8 +6,10 @@ #include #include +#include #include #include +#include #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> disabled_addons; } extern values; void Apply(); From c381f46428268a57f0aef1d99918c8f1bb6beec7 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 14:00:01 -0500 Subject: [PATCH 2/8] config: Store and load disabled add-ons list --- src/yuzu/configuration/config.cpp | 30 ++++++++++++++++++++++++++++++ src/yuzu_cmd/config.cpp | 18 ++++++++++++++++++ src/yuzu_cmd/default_ini.h | 7 +++++++ 3 files changed, 55 insertions(+) diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 83ebbd1fe..4d4bd2a46 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -444,6 +444,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 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 = @@ -650,6 +665,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", 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); diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 097c1fbe3..fe0d1eebf 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -3,6 +3,7 @@ // Refer to the license.txt file included. #include +#include #include #include #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 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); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index d73669f36..25236d05d 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -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_` 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 )"; } From c7b41ade74f55bd910414f20bbcc62a68101d768 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 14:00:44 -0500 Subject: [PATCH 3/8] core: Make GetGameFileFromPath function externally accessible --- src/core/core.cpp | 8 +++++--- src/core/core.h | 4 ++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/core.cpp b/src/core/core.cpp index 795fabc65..ce7851538 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -8,6 +8,7 @@ #include #include +#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(); } diff --git a/src/core/core.h b/src/core/core.h index be71bd437..71031dfcf 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -9,6 +9,7 @@ #include #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; From 0cea05cdf7c258715fb39572cfb124003fae7810 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 14:01:09 -0500 Subject: [PATCH 4/8] patch_manager: Obey disabled add-ons list when patching game --- src/core/file_sys/patch_manager.cpp | 56 +++++++++++++++++++++++------ src/core/file_sys/patch_manager.h | 5 +++ 2 files changed, 50 insertions(+), 11 deletions(-) diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index 6b14e08be..ecdc21c87 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -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 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 CollectPatches(const std::vector& patch_dirs, - const std::string& build_id) { +std::vector PatchManager::CollectPatches(const std::vector& patch_dirs, + const std::string& build_id) const { + const auto& disabled = Settings::values.disabled_addons[title_id]; + std::vector 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(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(update_raw, romfs, ivfc_offset); if (new_nca->GetStatus() == Loader::ResultStatus::Success && new_nca->GetRomFS() != nullptr) { @@ -320,25 +345,30 @@ std::map> PatchManager::GetPatchVersionNam VirtualFile update_raw) const { std::map> 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> 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> 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; diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 7d168837f..b8a1652fd 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -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, VirtualFile> ParseControlNCA(const NCA& nca) const; private: + std::vector CollectPatches(const std::vector& patch_dirs, + const std::string& build_id) const; + u64 title_id; }; From 51483d83bbe6a7d3a29a8bf129b2f4b9a04d229c Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 14:01:29 -0500 Subject: [PATCH 5/8] aoc_u: Obey disabled add-ons list when listing DLC --- src/core/hle/service/aoc/aoc_u.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/core/hle/service/aoc/aoc_u.cpp b/src/core/hle/service/aoc/aoc_u.cpp index 0417fdb92..b506bc3dd 100644 --- a/src/core/hle/service/aoc/aoc_u.cpp +++ b/src/core/hle/service/aoc/aoc_u.cpp @@ -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(0); + return; + } + rb.Push(static_cast( 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(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. From 5f0217592b9f27a32c236471c0a27e8ce345c926 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 14:01:56 -0500 Subject: [PATCH 6/8] loader: Add support for reading the name of game's developer --- src/core/loader/loader.h | 10 ++++++++++ src/core/loader/nsp.cpp | 7 +++++++ src/core/loader/nsp.h | 1 + src/core/loader/xci.cpp | 7 +++++++ src/core/loader/xci.h | 1 + 5 files changed, 26 insertions(+) diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 7686634bf..c589b3bd1 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h @@ -12,6 +12,7 @@ #include #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; diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index 080d89904..b4ab88ae8 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp @@ -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 diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index db91cd01e..842327a49 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector& buffer) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadDeveloper(std::string& developer) override; private: std::unique_ptr nsp; diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 461607c95..bd5a83b49 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp @@ -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 diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index 46f8dfc9e..f3b5a3936 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h @@ -43,6 +43,7 @@ public: ResultStatus ReadProgramId(u64& out_program_id) override; ResultStatus ReadIcon(std::vector& buffer) override; ResultStatus ReadTitle(std::string& title) override; + ResultStatus ReadDeveloper(std::string& developer) override; private: std::unique_ptr xci; From 60e27252a52e9cb4f2d9671036c0bfc811979a15 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Wed, 28 Nov 2018 14:02:29 -0500 Subject: [PATCH 7/8] qt: Add UI to display game properties and disable add-ons --- src/yuzu/CMakeLists.txt | 3 + .../configuration/configure_per_general.cpp | 166 +++++++++++ .../configuration/configure_per_general.h | 56 ++++ .../configuration/configure_per_general.ui | 276 ++++++++++++++++++ 4 files changed, 501 insertions(+) create mode 100644 src/yuzu/configuration/configure_per_general.cpp create mode 100644 src/yuzu/configuration/configure_per_general.h create mode 100644 src/yuzu/configuration/configure_per_general.ui diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index cfca8f4a8..3232aa8fb 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt @@ -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 diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp new file mode 100644 index 000000000..ed85f84a9 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -0,0 +1,166 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/param_package.h" +#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 "input_common/main.h" +#include "yuzu/configuration/config.h" +#include "yuzu/configuration/configure_input.h" +#include "yuzu/configuration/configure_per_general.h" +#include "yuzu/ui_settings.h" + +ConfigurePerGameGeneral::ConfigurePerGameGeneral(u64 title_id, QWidget* parent) + : QDialog(parent), ui(std::make_unique()), 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"); + + 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(); +} + +void ConfigurePerGameGeneral::applyConfiguration() { + std::vector 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("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 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{ + 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()))); + + QLocale locale = this->locale(); + QString valueText = locale.formattedDataSize(file->GetSize()); + ui->display_size->setText(valueText); +} diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h new file mode 100644 index 000000000..5f958bbba --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.h @@ -0,0 +1,56 @@ +// Copyright 2016 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "common/param_package.h" +#include "core/file_sys/vfs.h" +#include "core/settings.h" +#include "input_common/main.h" +#include "ui_configure_per_general.h" +#include "yuzu/configuration/config.h" + +class QTreeView; +class QGraphicsScene; +class QStandardItem; +class QStandardItemModel; + +namespace Ui { +class ConfigurePerGameGeneral; +} + +class ConfigurePerGameGeneral : public QDialog { + Q_OBJECT + +public: + explicit ConfigurePerGameGeneral(u64 title_id, QWidget* parent = nullptr); + + /// Save all button configurations to settings file + void applyConfiguration(); + + void loadFromFile(FileSys::VirtualFile file); + +private: + std::unique_ptr ui; + FileSys::VirtualFile file; + u64 title_id; + + QVBoxLayout* layout; + QTreeView* tree_view; + QStandardItemModel* item_model; + QGraphicsScene* scene; + + std::vector> list_items; + + void loadConfiguration(); +}; diff --git a/src/yuzu/configuration/configure_per_general.ui b/src/yuzu/configuration/configure_per_general.ui new file mode 100644 index 000000000..8fdd96fa4 --- /dev/null +++ b/src/yuzu/configuration/configure_per_general.ui @@ -0,0 +1,276 @@ + + + ConfigurePerGameGeneral + + + + 0 + 0 + 400 + 520 + + + + ConfigurePerGameGeneral + + + + + + + + Info + + + + + + + + true + + + true + + + + + + + true + + + true + + + + + + + Developer + + + + + + + true + + + true + + + + + + + Name + + + + + + + Filename + + + + + + + Version + + + + + + + Format + + + + + + + true + + + true + + + + + + + true + + + true + + + + + + + Size + + + + + + + true + + + true + + + + + + + Title ID + + + + + + + true + + + true + + + + + + + + 0 + 0 + + + + + 128 + 128 + + + + + 128 + 128 + + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustToContents + + + false + + + + + + + + + + + + Add-Ons + + + + + + true + + + + + 0 + 0 + 350 + 169 + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + buttonBox + accepted() + ConfigurePerGameGeneral + accept() + + + 269 + 567 + + + 269 + 294 + + + + + buttonBox + rejected() + ConfigurePerGameGeneral + reject() + + + 269 + 567 + + + 269 + 294 + + + + + From f6f65035785479934ddd443a9236ec142b7a0ed6 Mon Sep 17 00:00:00 2001 From: Zach Hilman Date: Tue, 4 Dec 2018 13:34:46 -0500 Subject: [PATCH 8/8] qt: Add Properties menu to game list right-click --- src/yuzu/configuration/config.cpp | 2 +- .../configuration/configure_per_general.cpp | 18 +++++++----- .../configuration/configure_per_general.h | 18 ++++-------- src/yuzu/game_list.cpp | 3 ++ src/yuzu/game_list.h | 1 + src/yuzu/game_list_worker.cpp | 2 +- src/yuzu/main.cpp | 29 +++++++++++++++++++ src/yuzu/main.h | 1 + src/yuzu_cmd/default_ini.h | 2 +- 9 files changed, 54 insertions(+), 22 deletions(-) diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 4d4bd2a46..97a1633b0 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -669,7 +669,7 @@ void Config::SaveValues() { int i = 0; for (const auto& elem : Settings::values.disabled_addons) { qt_config->setArrayIndex(i); - qt_config->setValue("title_id", elem.first); + qt_config->setValue("title_id", QVariant::fromValue(elem.first)); qt_config->beginWriteArray("disabled"); for (std::size_t j = 0; j < elem.second.size(); ++j) { qt_config->setArrayIndex(j); diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp index ed85f84a9..80109b434 100644 --- a/src/yuzu/configuration/configure_per_general.cpp +++ b/src/yuzu/configuration/configure_per_general.cpp @@ -5,24 +5,27 @@ #include #include #include + +#include #include #include #include #include #include #include -#include "common/param_package.h" + #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 "input_common/main.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(u64 title_id, QWidget* parent) +ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id) : QDialog(parent), ui(std::make_unique()), title_id(title_id) { ui->setupUi(this); @@ -61,11 +64,13 @@ ConfigurePerGameGeneral::ConfigurePerGameGeneral(u64 title_id, QWidget* parent) ui->icon_view->setScene(scene); connect(item_model, &QStandardItemModel::itemChanged, - [&]() { UISettings::values.is_game_list_reload_pending.exchange(true); }); + [] { UISettings::values.is_game_list_reload_pending.exchange(true); }); this->loadConfiguration(); } +ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default; + void ConfigurePerGameGeneral::applyConfiguration() { std::vector disabled_addons; @@ -107,7 +112,7 @@ void ConfigurePerGameGeneral::loadConfiguration() { if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success) ui->display_developer->setText(QString::fromStdString(developer)); - ui->display_version->setText("1.0.0"); + ui->display_version->setText(QStringLiteral("1.0.0")); } if (control.second != nullptr) { @@ -160,7 +165,6 @@ void ConfigurePerGameGeneral::loadConfiguration() { ui->display_format->setText( QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))); - QLocale locale = this->locale(); - QString valueText = locale.formattedDataSize(file->GetSize()); + const auto valueText = ReadableByteSize(file->GetSize()); ui->display_size->setText(valueText); } diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h index 5f958bbba..a4494446c 100644 --- a/src/yuzu/configuration/configure_per_general.h +++ b/src/yuzu/configuration/configure_per_general.h @@ -4,21 +4,14 @@ #pragma once -#include -#include #include -#include -#include +#include + #include #include #include -#include -#include "common/param_package.h" -#include "core/file_sys/vfs.h" -#include "core/settings.h" -#include "input_common/main.h" -#include "ui_configure_per_general.h" -#include "yuzu/configuration/config.h" + +#include "core/file_sys/vfs_types.h" class QTreeView; class QGraphicsScene; @@ -33,7 +26,8 @@ class ConfigurePerGameGeneral : public QDialog { Q_OBJECT public: - explicit ConfigurePerGameGeneral(u64 title_id, QWidget* parent = nullptr); + explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id); + ~ConfigurePerGameGeneral() override; /// Save all button configurations to settings file void applyConfiguration(); diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index b52a50915..8e9524fd6 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp @@ -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)); } diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 05e115e19..b317eb2fc 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h @@ -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); diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp index 1edc60df7..553269269 100644 --- a/src/yuzu/game_list_worker.cpp +++ b/src/yuzu/game_list_worker.cpp @@ -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; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 22c207a3a..90b212ba5 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -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"); diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 674e73412..ca9c50367 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -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(); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 25236d05d..0f3f8da50 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -225,7 +225,7 @@ yuzu_token = [AddOns] # Used to disable add-ons # List of title IDs of games that will have add-ons disabled (separated by '|'): -title_ids = +title_ids = # For each title ID, have a key/value pair called `disabled_` 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 )";