mirror of
				https://github.com/yuzu-emu/yuzu.git
				synced 2025-11-03 23:43:41 +00:00 
			
		
		
		
	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);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										170
									
								
								src/yuzu/configuration/configure_per_general.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								src/yuzu/configuration/configure_per_general.cpp
									
									
									
									
									
										Normal file
									
								
							@ -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);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										50
									
								
								src/yuzu/configuration/configure_per_general.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/yuzu/configuration/configure_per_general.h
									
									
									
									
									
										Normal file
									
								
							@ -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();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										276
									
								
								src/yuzu/configuration/configure_per_general.ui
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										276
									
								
								src/yuzu/configuration/configure_per_general.ui
									
									
									
									
									
										Normal file
									
								
							@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user