summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2018-12-10 21:52:19 -0500
committerGravatar GitHub2018-12-10 21:52:19 -0500
commit2c45c6d23431021efb1053abcb40064256aac338 (patch)
treeedd00a30fa32460e8158992d53768038e83141dc /src
parentMerge pull request #1887 from FearlessTobi/port-4476 (diff)
parentqt: Add Properties menu to game list right-click (diff)
downloadyuzu-2c45c6d23431021efb1053abcb40064256aac338.tar.gz
yuzu-2c45c6d23431021efb1053abcb40064256aac338.tar.xz
yuzu-2c45c6d23431021efb1053abcb40064256aac338.zip
Merge pull request #1819 from DarkLordZach/disable-addons
patch_manager: Add support for disabling patches
Diffstat (limited to 'src')
-rw-r--r--src/core/core.cpp8
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/file_sys/patch_manager.cpp56
-rw-r--r--src/core/file_sys/patch_manager.h5
-rw-r--r--src/core/hle/service/aoc/aoc_u.cpp12
-rw-r--r--src/core/loader/loader.h10
-rw-r--r--src/core/loader/nsp.cpp7
-rw-r--r--src/core/loader/nsp.h1
-rw-r--r--src/core/loader/xci.cpp7
-rw-r--r--src/core/loader/xci.h1
-rw-r--r--src/core/settings.h5
-rw-r--r--src/yuzu/CMakeLists.txt3
-rw-r--r--src/yuzu/configuration/config.cpp30
-rw-r--r--src/yuzu/configuration/configure_per_general.cpp170
-rw-r--r--src/yuzu/configuration/configure_per_general.h50
-rw-r--r--src/yuzu/configuration/configure_per_general.ui276
-rw-r--r--src/yuzu/game_list.cpp3
-rw-r--r--src/yuzu/game_list.h1
-rw-r--r--src/yuzu/game_list_worker.cpp2
-rw-r--r--src/yuzu/main.cpp29
-rw-r--r--src/yuzu/main.h1
-rw-r--r--src/yuzu_cmd/config.cpp18
-rw-r--r--src/yuzu_cmd/default_ini.h7
23 files changed, 691 insertions, 15 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 @@
8#include <thread> 8#include <thread>
9#include <utility> 9#include <utility>
10 10
11#include "common/file_util.h"
11#include "common/logging/log.h" 12#include "common/logging/log.h"
12#include "common/string_util.h" 13#include "common/string_util.h"
13#include "core/arm/exclusive_monitor.h" 14#include "core/arm/exclusive_monitor.h"
@@ -40,7 +41,6 @@ namespace Core {
40 41
41/*static*/ System System::s_instance; 42/*static*/ System System::s_instance;
42 43
43namespace {
44FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs, 44FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
45 const std::string& path) { 45 const std::string& path) {
46 // To account for split 00+01+etc files. 46 // To account for split 00+01+etc files.
@@ -69,11 +69,13 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
69 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName()); 69 return FileSys::ConcatenatedVfsFile::MakeConcatenatedFile(concat, dir->GetName());
70 } 70 }
71 71
72 if (FileUtil::IsDirectory(path))
73 return vfs->OpenFile(path + "/" + "main", FileSys::Mode::Read);
74
72 return vfs->OpenFile(path, FileSys::Mode::Read); 75 return vfs->OpenFile(path, FileSys::Mode::Read);
73} 76}
74} // Anonymous namespace
75
76struct System::Impl { 77struct System::Impl {
78
77 Cpu& CurrentCpuCore() { 79 Cpu& CurrentCpuCore() {
78 return cpu_core_manager.GetCurrentCore(); 80 return cpu_core_manager.GetCurrentCore();
79 } 81 }
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 @@
9#include <string> 9#include <string>
10 10
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "core/file_sys/vfs_types.h"
12#include "core/hle/kernel/object.h" 13#include "core/hle/kernel/object.h"
13 14
14namespace Core::Frontend { 15namespace Core::Frontend {
@@ -55,6 +56,9 @@ class TelemetrySession;
55 56
56struct PerfStatsResults; 57struct PerfStatsResults;
57 58
59FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
60 const std::string& path);
61
58class System { 62class System {
59public: 63public:
60 System(const System&) = delete; 64 System(const System&) = delete;
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) {}
56 56
57PatchManager::~PatchManager() = default; 57PatchManager::~PatchManager() = default;
58 58
59u64 PatchManager::GetTitleID() const {
60 return title_id;
61}
62
59VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { 63VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
60 LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); 64 LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
61 65
@@ -73,11 +77,15 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
73 77
74 const auto installed = Service::FileSystem::GetUnionContents(); 78 const auto installed = Service::FileSystem::GetUnionContents();
75 79
80 const auto& disabled = Settings::values.disabled_addons[title_id];
81 const auto update_disabled =
82 std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
83
76 // Game Updates 84 // Game Updates
77 const auto update_tid = GetUpdateTitleID(title_id); 85 const auto update_tid = GetUpdateTitleID(title_id);
78 const auto update = installed.GetEntry(update_tid, ContentRecordType::Program); 86 const auto update = installed.GetEntry(update_tid, ContentRecordType::Program);
79 87
80 if (update != nullptr && update->GetExeFS() != nullptr && 88 if (!update_disabled && update != nullptr && update->GetExeFS() != nullptr &&
81 update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) { 89 update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
82 LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully", 90 LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
83 FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); 91 FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
@@ -95,6 +103,9 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
95 std::vector<VirtualDir> layers; 103 std::vector<VirtualDir> layers;
96 layers.reserve(patch_dirs.size() + 1); 104 layers.reserve(patch_dirs.size() + 1);
97 for (const auto& subdir : patch_dirs) { 105 for (const auto& subdir : patch_dirs) {
106 if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
107 continue;
108
98 auto exefs_dir = subdir->GetSubdirectory("exefs"); 109 auto exefs_dir = subdir->GetSubdirectory("exefs");
99 if (exefs_dir != nullptr) 110 if (exefs_dir != nullptr)
100 layers.push_back(std::move(exefs_dir)); 111 layers.push_back(std::move(exefs_dir));
@@ -111,11 +122,16 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
111 return exefs; 122 return exefs;
112} 123}
113 124
114static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs, 125std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualDir>& patch_dirs,
115 const std::string& build_id) { 126 const std::string& build_id) const {
127 const auto& disabled = Settings::values.disabled_addons[title_id];
128
116 std::vector<VirtualFile> out; 129 std::vector<VirtualFile> out;
117 out.reserve(patch_dirs.size()); 130 out.reserve(patch_dirs.size());
118 for (const auto& subdir : patch_dirs) { 131 for (const auto& subdir : patch_dirs) {
132 if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
133 continue;
134
119 auto exefs_dir = subdir->GetSubdirectory("exefs"); 135 auto exefs_dir = subdir->GetSubdirectory("exefs");
120 if (exefs_dir != nullptr) { 136 if (exefs_dir != nullptr) {
121 for (const auto& file : exefs_dir->GetFiles()) { 137 for (const auto& file : exefs_dir->GetFiles()) {
@@ -228,6 +244,7 @@ static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType t
228 return; 244 return;
229 } 245 }
230 246
247 const auto& disabled = Settings::values.disabled_addons[title_id];
231 auto patch_dirs = load_dir->GetSubdirectories(); 248 auto patch_dirs = load_dir->GetSubdirectories();
232 std::sort(patch_dirs.begin(), patch_dirs.end(), 249 std::sort(patch_dirs.begin(), patch_dirs.end(),
233 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 250 [](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
237 layers.reserve(patch_dirs.size() + 1); 254 layers.reserve(patch_dirs.size() + 1);
238 layers_ext.reserve(patch_dirs.size() + 1); 255 layers_ext.reserve(patch_dirs.size() + 1);
239 for (const auto& subdir : patch_dirs) { 256 for (const auto& subdir : patch_dirs) {
257 if (std::find(disabled.begin(), disabled.end(), subdir->GetName()) != disabled.end())
258 continue;
259
240 auto romfs_dir = subdir->GetSubdirectory("romfs"); 260 auto romfs_dir = subdir->GetSubdirectory("romfs");
241 if (romfs_dir != nullptr) 261 if (romfs_dir != nullptr)
242 layers.push_back(std::move(romfs_dir)); 262 layers.push_back(std::move(romfs_dir));
@@ -282,7 +302,12 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
282 // Game Updates 302 // Game Updates
283 const auto update_tid = GetUpdateTitleID(title_id); 303 const auto update_tid = GetUpdateTitleID(title_id);
284 const auto update = installed.GetEntryRaw(update_tid, type); 304 const auto update = installed.GetEntryRaw(update_tid, type);
285 if (update != nullptr) { 305
306 const auto& disabled = Settings::values.disabled_addons[title_id];
307 const auto update_disabled =
308 std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
309
310 if (!update_disabled && update != nullptr) {
286 const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset); 311 const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
287 if (new_nca->GetStatus() == Loader::ResultStatus::Success && 312 if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
288 new_nca->GetRomFS() != nullptr) { 313 new_nca->GetRomFS() != nullptr) {
@@ -290,7 +315,7 @@ VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset, Content
290 FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0))); 315 FormatTitleVersion(installed.GetEntryVersion(update_tid).value_or(0)));
291 romfs = new_nca->GetRomFS(); 316 romfs = new_nca->GetRomFS();
292 } 317 }
293 } else if (update_raw != nullptr) { 318 } else if (!update_disabled && update_raw != nullptr) {
294 const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset); 319 const auto new_nca = std::make_shared<NCA>(update_raw, romfs, ivfc_offset);
295 if (new_nca->GetStatus() == Loader::ResultStatus::Success && 320 if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
296 new_nca->GetRomFS() != nullptr) { 321 new_nca->GetRomFS() != nullptr) {
@@ -320,25 +345,30 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
320 VirtualFile update_raw) const { 345 VirtualFile update_raw) const {
321 std::map<std::string, std::string, std::less<>> out; 346 std::map<std::string, std::string, std::less<>> out;
322 const auto installed = Service::FileSystem::GetUnionContents(); 347 const auto installed = Service::FileSystem::GetUnionContents();
348 const auto& disabled = Settings::values.disabled_addons[title_id];
323 349
324 // Game Updates 350 // Game Updates
325 const auto update_tid = GetUpdateTitleID(title_id); 351 const auto update_tid = GetUpdateTitleID(title_id);
326 PatchManager update{update_tid}; 352 PatchManager update{update_tid};
327 auto [nacp, discard_icon_file] = update.GetControlMetadata(); 353 auto [nacp, discard_icon_file] = update.GetControlMetadata();
328 354
355 const auto update_disabled =
356 std::find(disabled.begin(), disabled.end(), "Update") != disabled.end();
357 const auto update_label = update_disabled ? "[D] Update" : "Update";
358
329 if (nacp != nullptr) { 359 if (nacp != nullptr) {
330 out.insert_or_assign("Update", nacp->GetVersionString()); 360 out.insert_or_assign(update_label, nacp->GetVersionString());
331 } else { 361 } else {
332 if (installed.HasEntry(update_tid, ContentRecordType::Program)) { 362 if (installed.HasEntry(update_tid, ContentRecordType::Program)) {
333 const auto meta_ver = installed.GetEntryVersion(update_tid); 363 const auto meta_ver = installed.GetEntryVersion(update_tid);
334 if (meta_ver.value_or(0) == 0) { 364 if (meta_ver.value_or(0) == 0) {
335 out.insert_or_assign("Update", ""); 365 out.insert_or_assign(update_label, "");
336 } else { 366 } else {
337 out.insert_or_assign( 367 out.insert_or_assign(
338 "Update", FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements)); 368 update_label, FormatTitleVersion(*meta_ver, TitleVersionFormat::ThreeElements));
339 } 369 }
340 } else if (update_raw != nullptr) { 370 } else if (update_raw != nullptr) {
341 out.insert_or_assign("Update", "PACKED"); 371 out.insert_or_assign(update_label, "PACKED");
342 } 372 }
343 } 373 }
344 374
@@ -378,7 +408,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
378 if (types.empty()) 408 if (types.empty())
379 continue; 409 continue;
380 410
381 out.insert_or_assign(mod->GetName(), types); 411 const auto mod_disabled =
412 std::find(disabled.begin(), disabled.end(), mod->GetName()) != disabled.end();
413 out.insert_or_assign(mod_disabled ? "[D] " + mod->GetName() : mod->GetName(), types);
382 } 414 }
383 } 415 }
384 416
@@ -401,7 +433,9 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
401 433
402 list += fmt::format("{}", dlc_match.back().title_id & 0x7FF); 434 list += fmt::format("{}", dlc_match.back().title_id & 0x7FF);
403 435
404 out.insert_or_assign("DLC", std::move(list)); 436 const auto dlc_disabled =
437 std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end();
438 out.insert_or_assign(dlc_disabled ? "[D] DLC" : "DLC", std::move(list));
405 } 439 }
406 440
407 return out; 441 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:
30 explicit PatchManager(u64 title_id); 30 explicit PatchManager(u64 title_id);
31 ~PatchManager(); 31 ~PatchManager();
32 32
33 u64 GetTitleID() const;
34
33 // Currently tracked ExeFS patches: 35 // Currently tracked ExeFS patches:
34 // - Game Updates 36 // - Game Updates
35 VirtualDir PatchExeFS(VirtualDir exefs) const; 37 VirtualDir PatchExeFS(VirtualDir exefs) const;
@@ -63,6 +65,9 @@ public:
63 std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const; 65 std::pair<std::unique_ptr<NACP>, VirtualFile> ParseControlNCA(const NCA& nca) const;
64 66
65private: 67private:
68 std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
69 const std::string& build_id) const;
70
66 u64 title_id; 71 u64 title_id;
67}; 72};
68 73
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 @@
20#include "core/hle/service/aoc/aoc_u.h" 20#include "core/hle/service/aoc/aoc_u.h"
21#include "core/hle/service/filesystem/filesystem.h" 21#include "core/hle/service/filesystem/filesystem.h"
22#include "core/loader/loader.h" 22#include "core/loader/loader.h"
23#include "core/settings.h"
23 24
24namespace Service::AOC { 25namespace Service::AOC {
25 26
@@ -76,6 +77,13 @@ void AOC_U::CountAddOnContent(Kernel::HLERequestContext& ctx) {
76 rb.Push(RESULT_SUCCESS); 77 rb.Push(RESULT_SUCCESS);
77 78
78 const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID(); 79 const auto current = Core::System::GetInstance().CurrentProcess()->GetTitleID();
80
81 const auto& disabled = Settings::values.disabled_addons[current];
82 if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end()) {
83 rb.Push<u32>(0);
84 return;
85 }
86
79 rb.Push<u32>(static_cast<u32>( 87 rb.Push<u32>(static_cast<u32>(
80 std::count_if(add_on_content.begin(), add_on_content.end(), 88 std::count_if(add_on_content.begin(), add_on_content.end(),
81 [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); }))); 89 [current](u64 tid) { return CheckAOCTitleIDMatchesBase(tid, current); })));
@@ -96,6 +104,10 @@ void AOC_U::ListAddOnContent(Kernel::HLERequestContext& ctx) {
96 out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF)); 104 out.push_back(static_cast<u32>(add_on_content[i] & 0x7FF));
97 } 105 }
98 106
107 const auto& disabled = Settings::values.disabled_addons[current];
108 if (std::find(disabled.begin(), disabled.end(), "DLC") != disabled.end())
109 out = {};
110
99 if (out.size() < offset) { 111 if (out.size() < offset) {
100 IPC::ResponseBuilder rb{ctx, 2}; 112 IPC::ResponseBuilder rb{ctx, 2};
101 // TODO(DarkLordZach): Find the correct error code. 113 // TODO(DarkLordZach): Find the correct error code.
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 5390ab9ee..0838e303b 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -12,6 +12,7 @@
12#include <vector> 12#include <vector>
13 13
14#include "common/common_types.h" 14#include "common/common_types.h"
15#include "core/file_sys/control_metadata.h"
15#include "core/file_sys/vfs.h" 16#include "core/file_sys/vfs.h"
16 17
17namespace Kernel { 18namespace Kernel {
@@ -243,6 +244,15 @@ public:
243 return ResultStatus::ErrorNotImplemented; 244 return ResultStatus::ErrorNotImplemented;
244 } 245 }
245 246
247 /**
248 * Get the developer of the application
249 * @param developer Reference to store the application developer into
250 * @return ResultStatus result of function
251 */
252 virtual ResultStatus ReadDeveloper(std::string& developer) {
253 return ResultStatus::ErrorNotImplemented;
254 }
255
246protected: 256protected:
247 FileSys::VirtualFile file; 257 FileSys::VirtualFile file;
248 bool is_loaded = false; 258 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) {
151 title = nacp_file->GetApplicationName(); 151 title = nacp_file->GetApplicationName();
152 return ResultStatus::Success; 152 return ResultStatus::Success;
153} 153}
154
155ResultStatus AppLoader_NSP::ReadDeveloper(std::string& developer) {
156 if (nacp_file == nullptr)
157 return ResultStatus::ErrorNoControl;
158 developer = nacp_file->GetDeveloperName();
159 return ResultStatus::Success;
160}
154} // namespace Loader 161} // namespace Loader
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 9ed8a59cf..2b1e0719b 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -43,6 +43,7 @@ public:
43 ResultStatus ReadProgramId(u64& out_program_id) override; 43 ResultStatus ReadProgramId(u64& out_program_id) override;
44 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 44 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
45 ResultStatus ReadTitle(std::string& title) override; 45 ResultStatus ReadTitle(std::string& title) override;
46 ResultStatus ReadDeveloper(std::string& developer) override;
46 47
47private: 48private:
48 std::unique_ptr<FileSys::NSP> nsp; 49 std::unique_ptr<FileSys::NSP> 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) {
120 title = nacp_file->GetApplicationName(); 120 title = nacp_file->GetApplicationName();
121 return ResultStatus::Success; 121 return ResultStatus::Success;
122} 122}
123
124ResultStatus AppLoader_XCI::ReadDeveloper(std::string& developer) {
125 if (nacp_file == nullptr)
126 return ResultStatus::ErrorNoControl;
127 developer = nacp_file->GetDeveloperName();
128 return ResultStatus::Success;
129}
123} // namespace Loader 130} // namespace Loader
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h
index ded5bb88a..15d1b1a23 100644
--- a/src/core/loader/xci.h
+++ b/src/core/loader/xci.h
@@ -43,6 +43,7 @@ public:
43 ResultStatus ReadProgramId(u64& out_program_id) override; 43 ResultStatus ReadProgramId(u64& out_program_id) override;
44 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 44 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
45 ResultStatus ReadTitle(std::string& title) override; 45 ResultStatus ReadTitle(std::string& title) override;
46 ResultStatus ReadDeveloper(std::string& developer) override;
46 47
47private: 48private:
48 std::unique_ptr<FileSys::XCI> xci; 49 std::unique_ptr<FileSys::XCI> xci;
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 @@
6 6
7#include <array> 7#include <array>
8#include <atomic> 8#include <atomic>
9#include <map>
9#include <optional> 10#include <optional>
10#include <string> 11#include <string>
12#include <vector>
11#include "common/common_types.h" 13#include "common/common_types.h"
12 14
13namespace Settings { 15namespace Settings {
@@ -411,6 +413,9 @@ struct Values {
411 std::string web_api_url; 413 std::string web_api_url;
412 std::string yuzu_username; 414 std::string yuzu_username;
413 std::string yuzu_token; 415 std::string yuzu_token;
416
417 // Add-Ons
418 std::map<u64, std::vector<std::string>> disabled_addons;
414} extern values; 419} extern values;
415 420
416void Apply(); 421void Apply();
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
35 configuration/configure_mouse_advanced.h 35 configuration/configure_mouse_advanced.h
36 configuration/configure_system.cpp 36 configuration/configure_system.cpp
37 configuration/configure_system.h 37 configuration/configure_system.h
38 configuration/configure_per_general.cpp
39 configuration/configure_per_general.h
38 configuration/configure_touchscreen_advanced.cpp 40 configuration/configure_touchscreen_advanced.cpp
39 configuration/configure_touchscreen_advanced.h 41 configuration/configure_touchscreen_advanced.h
40 configuration/configure_web.cpp 42 configuration/configure_web.cpp
@@ -86,6 +88,7 @@ set(UIS
86 configuration/configure_input.ui 88 configuration/configure_input.ui
87 configuration/configure_input_player.ui 89 configuration/configure_input_player.ui
88 configuration/configure_mouse_advanced.ui 90 configuration/configure_mouse_advanced.ui
91 configuration/configure_per_general.ui
89 configuration/configure_system.ui 92 configuration/configure_system.ui
90 configuration/configure_touchscreen_advanced.ui 93 configuration/configure_touchscreen_advanced.ui
91 configuration/configure_web.ui 94 configuration/configure_web.ui
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index c26161169..eb2077b0f 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -441,6 +441,21 @@ void Config::ReadValues() {
441 Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString(); 441 Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
442 qt_config->endGroup(); 442 qt_config->endGroup();
443 443
444 const auto size = qt_config->beginReadArray("DisabledAddOns");
445 for (int i = 0; i < size; ++i) {
446 qt_config->setArrayIndex(i);
447 const auto title_id = qt_config->value("title_id", 0).toULongLong();
448 std::vector<std::string> out;
449 const auto d_size = qt_config->beginReadArray("disabled");
450 for (int j = 0; j < d_size; ++j) {
451 qt_config->setArrayIndex(j);
452 out.push_back(qt_config->value("d", "").toString().toStdString());
453 }
454 qt_config->endArray();
455 Settings::values.disabled_addons.insert_or_assign(title_id, out);
456 }
457 qt_config->endArray();
458
444 qt_config->beginGroup("UI"); 459 qt_config->beginGroup("UI");
445 UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString(); 460 UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
446 UISettings::values.enable_discord_presence = 461 UISettings::values.enable_discord_presence =
@@ -645,6 +660,21 @@ void Config::SaveValues() {
645 qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token)); 660 qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
646 qt_config->endGroup(); 661 qt_config->endGroup();
647 662
663 qt_config->beginWriteArray("DisabledAddOns");
664 int i = 0;
665 for (const auto& elem : Settings::values.disabled_addons) {
666 qt_config->setArrayIndex(i);
667 qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
668 qt_config->beginWriteArray("disabled");
669 for (std::size_t j = 0; j < elem.second.size(); ++j) {
670 qt_config->setArrayIndex(j);
671 qt_config->setValue("d", QString::fromStdString(elem.second[j]));
672 }
673 qt_config->endArray();
674 ++i;
675 }
676 qt_config->endArray();
677
648 qt_config->beginGroup("UI"); 678 qt_config->beginGroup("UI");
649 qt_config->setValue("theme", UISettings::values.theme); 679 qt_config->setValue("theme", UISettings::values.theme);
650 qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence); 680 qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
new file mode 100644
index 000000000..80109b434
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_general.cpp
@@ -0,0 +1,170 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <algorithm>
6#include <memory>
7#include <utility>
8
9#include <QHeaderView>
10#include <QMenu>
11#include <QMessageBox>
12#include <QStandardItemModel>
13#include <QString>
14#include <QTimer>
15#include <QTreeView>
16
17#include "core/file_sys/control_metadata.h"
18#include "core/file_sys/patch_manager.h"
19#include "core/file_sys/xts_archive.h"
20#include "core/loader/loader.h"
21#include "ui_configure_per_general.h"
22#include "yuzu/configuration/config.h"
23#include "yuzu/configuration/configure_input.h"
24#include "yuzu/configuration/configure_per_general.h"
25#include "yuzu/ui_settings.h"
26#include "yuzu/util/util.h"
27
28ConfigurePerGameGeneral::ConfigurePerGameGeneral(QWidget* parent, u64 title_id)
29 : QDialog(parent), ui(std::make_unique<Ui::ConfigurePerGameGeneral>()), title_id(title_id) {
30
31 ui->setupUi(this);
32 setFocusPolicy(Qt::ClickFocus);
33 setWindowTitle(tr("Properties"));
34
35 layout = new QVBoxLayout;
36 tree_view = new QTreeView;
37 item_model = new QStandardItemModel(tree_view);
38 tree_view->setModel(item_model);
39 tree_view->setAlternatingRowColors(true);
40 tree_view->setSelectionMode(QHeaderView::SingleSelection);
41 tree_view->setSelectionBehavior(QHeaderView::SelectRows);
42 tree_view->setVerticalScrollMode(QHeaderView::ScrollPerPixel);
43 tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
44 tree_view->setSortingEnabled(true);
45 tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
46 tree_view->setUniformRowHeights(true);
47 tree_view->setContextMenuPolicy(Qt::NoContextMenu);
48
49 item_model->insertColumns(0, 2);
50 item_model->setHeaderData(0, Qt::Horizontal, "Patch Name");
51 item_model->setHeaderData(1, Qt::Horizontal, "Version");
52
53 // We must register all custom types with the Qt Automoc system so that we are able to use it
54 // with signals/slots. In this case, QList falls under the umbrells of custom types.
55 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
56
57 layout->setContentsMargins(0, 0, 0, 0);
58 layout->setSpacing(0);
59 layout->addWidget(tree_view);
60
61 ui->scrollArea->setLayout(layout);
62
63 scene = new QGraphicsScene;
64 ui->icon_view->setScene(scene);
65
66 connect(item_model, &QStandardItemModel::itemChanged,
67 [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
68
69 this->loadConfiguration();
70}
71
72ConfigurePerGameGeneral::~ConfigurePerGameGeneral() = default;
73
74void ConfigurePerGameGeneral::applyConfiguration() {
75 std::vector<std::string> disabled_addons;
76
77 for (const auto& item : list_items) {
78 const auto disabled = item.front()->checkState() == Qt::Unchecked;
79 if (disabled)
80 disabled_addons.push_back(item.front()->text().toStdString());
81 }
82
83 Settings::values.disabled_addons[title_id] = disabled_addons;
84}
85
86void ConfigurePerGameGeneral::loadFromFile(FileSys::VirtualFile file) {
87 this->file = std::move(file);
88 this->loadConfiguration();
89}
90
91void ConfigurePerGameGeneral::loadConfiguration() {
92 if (file == nullptr)
93 return;
94
95 const auto loader = Loader::GetLoader(file);
96
97 ui->display_title_id->setText(fmt::format("{:016X}", title_id).c_str());
98
99 FileSys::PatchManager pm{title_id};
100 const auto control = pm.GetControlMetadata();
101
102 if (control.first != nullptr) {
103 ui->display_version->setText(QString::fromStdString(control.first->GetVersionString()));
104 ui->display_name->setText(QString::fromStdString(control.first->GetApplicationName()));
105 ui->display_developer->setText(QString::fromStdString(control.first->GetDeveloperName()));
106 } else {
107 std::string title;
108 if (loader->ReadTitle(title) == Loader::ResultStatus::Success)
109 ui->display_name->setText(QString::fromStdString(title));
110
111 std::string developer;
112 if (loader->ReadDeveloper(developer) == Loader::ResultStatus::Success)
113 ui->display_developer->setText(QString::fromStdString(developer));
114
115 ui->display_version->setText(QStringLiteral("1.0.0"));
116 }
117
118 if (control.second != nullptr) {
119 scene->clear();
120
121 QPixmap map;
122 const auto bytes = control.second->ReadAllBytes();
123 map.loadFromData(bytes.data(), bytes.size());
124
125 scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
126 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
127 } else {
128 std::vector<u8> bytes;
129 if (loader->ReadIcon(bytes) == Loader::ResultStatus::Success) {
130 scene->clear();
131
132 QPixmap map;
133 map.loadFromData(bytes.data(), bytes.size());
134
135 scene->addPixmap(map.scaled(ui->icon_view->width(), ui->icon_view->height(),
136 Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
137 }
138 }
139
140 FileSys::VirtualFile update_raw;
141 loader->ReadUpdateRaw(update_raw);
142
143 const auto& disabled = Settings::values.disabled_addons[title_id];
144
145 for (const auto& patch : pm.GetPatchVersionNames(update_raw)) {
146 QStandardItem* first_item = new QStandardItem;
147 const auto name = QString::fromStdString(patch.first).replace("[D] ", "");
148 first_item->setText(name);
149 first_item->setCheckable(true);
150
151 const auto patch_disabled =
152 std::find(disabled.begin(), disabled.end(), name.toStdString()) != disabled.end();
153
154 first_item->setCheckState(patch_disabled ? Qt::Unchecked : Qt::Checked);
155
156 list_items.push_back(QList<QStandardItem*>{
157 first_item, new QStandardItem{QString::fromStdString(patch.second)}});
158 item_model->appendRow(list_items.back());
159 }
160
161 tree_view->setColumnWidth(0, 5 * tree_view->width() / 16);
162
163 ui->display_filename->setText(QString::fromStdString(file->GetName()));
164
165 ui->display_format->setText(
166 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())));
167
168 const auto valueText = ReadableByteSize(file->GetSize());
169 ui->display_size->setText(valueText);
170}
diff --git a/src/yuzu/configuration/configure_per_general.h b/src/yuzu/configuration/configure_per_general.h
new file mode 100644
index 000000000..a4494446c
--- /dev/null
+++ b/src/yuzu/configuration/configure_per_general.h
@@ -0,0 +1,50 @@
1// Copyright 2016 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include <vector>
9
10#include <QKeyEvent>
11#include <QList>
12#include <QWidget>
13
14#include "core/file_sys/vfs_types.h"
15
16class QTreeView;
17class QGraphicsScene;
18class QStandardItem;
19class QStandardItemModel;
20
21namespace Ui {
22class ConfigurePerGameGeneral;
23}
24
25class ConfigurePerGameGeneral : public QDialog {
26 Q_OBJECT
27
28public:
29 explicit ConfigurePerGameGeneral(QWidget* parent, u64 title_id);
30 ~ConfigurePerGameGeneral() override;
31
32 /// Save all button configurations to settings file
33 void applyConfiguration();
34
35 void loadFromFile(FileSys::VirtualFile file);
36
37private:
38 std::unique_ptr<Ui::ConfigurePerGameGeneral> ui;
39 FileSys::VirtualFile file;
40 u64 title_id;
41
42 QVBoxLayout* layout;
43 QTreeView* tree_view;
44 QStandardItemModel* item_model;
45 QGraphicsScene* scene;
46
47 std::vector<QList<QStandardItem*>> list_items;
48
49 void loadConfiguration();
50};
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 @@
1<?xml version="1.0" encoding="UTF-8"?>
2<ui version="4.0">
3 <class>ConfigurePerGameGeneral</class>
4 <widget class="QDialog" name="ConfigurePerGameGeneral">
5 <property name="geometry">
6 <rect>
7 <x>0</x>
8 <y>0</y>
9 <width>400</width>
10 <height>520</height>
11 </rect>
12 </property>
13 <property name="windowTitle">
14 <string>ConfigurePerGameGeneral</string>
15 </property>
16 <layout class="QHBoxLayout" name="HorizontalLayout">
17 <item>
18 <layout class="QVBoxLayout" name="VerticalLayout">
19 <item>
20 <widget class="QGroupBox" name="GeneralGroupBox">
21 <property name="title">
22 <string>Info</string>
23 </property>
24 <layout class="QHBoxLayout" name="GeneralHorizontalLayout">
25 <item>
26 <layout class="QGridLayout" name="gridLayout_2">
27 <item row="6" column="1" colspan="2">
28 <widget class="QLineEdit" name="display_filename">
29 <property name="enabled">
30 <bool>true</bool>
31 </property>
32 <property name="readOnly">
33 <bool>true</bool>
34 </property>
35 </widget>
36 </item>
37 <item row="0" column="1">
38 <widget class="QLineEdit" name="display_name">
39 <property name="enabled">
40 <bool>true</bool>
41 </property>
42 <property name="readOnly">
43 <bool>true</bool>
44 </property>
45 </widget>
46 </item>
47 <item row="1" column="0">
48 <widget class="QLabel" name="label_2">
49 <property name="text">
50 <string>Developer</string>
51 </property>
52 </widget>
53 </item>
54 <item row="5" column="1" colspan="2">
55 <widget class="QLineEdit" name="display_size">
56 <property name="enabled">
57 <bool>true</bool>
58 </property>
59 <property name="readOnly">
60 <bool>true</bool>
61 </property>
62 </widget>
63 </item>
64 <item row="0" column="0">
65 <widget class="QLabel" name="label">
66 <property name="text">
67 <string>Name</string>
68 </property>
69 </widget>
70 </item>
71 <item row="6" column="0">
72 <widget class="QLabel" name="label_7">
73 <property name="text">
74 <string>Filename</string>
75 </property>
76 </widget>
77 </item>
78 <item row="2" column="0">
79 <widget class="QLabel" name="label_3">
80 <property name="text">
81 <string>Version</string>
82 </property>
83 </widget>
84 </item>
85 <item row="4" column="0">
86 <widget class="QLabel" name="label_5">
87 <property name="text">
88 <string>Format</string>
89 </property>
90 </widget>
91 </item>
92 <item row="2" column="1">
93 <widget class="QLineEdit" name="display_version">
94 <property name="enabled">
95 <bool>true</bool>
96 </property>
97 <property name="readOnly">
98 <bool>true</bool>
99 </property>
100 </widget>
101 </item>
102 <item row="4" column="1">
103 <widget class="QLineEdit" name="display_format">
104 <property name="enabled">
105 <bool>true</bool>
106 </property>
107 <property name="readOnly">
108 <bool>true</bool>
109 </property>
110 </widget>
111 </item>
112 <item row="5" column="0">
113 <widget class="QLabel" name="label_6">
114 <property name="text">
115 <string>Size</string>
116 </property>
117 </widget>
118 </item>
119 <item row="1" column="1">
120 <widget class="QLineEdit" name="display_developer">
121 <property name="enabled">
122 <bool>true</bool>
123 </property>
124 <property name="readOnly">
125 <bool>true</bool>
126 </property>
127 </widget>
128 </item>
129 <item row="3" column="0">
130 <widget class="QLabel" name="label_4">
131 <property name="text">
132 <string>Title ID</string>
133 </property>
134 </widget>
135 </item>
136 <item row="3" column="1">
137 <widget class="QLineEdit" name="display_title_id">
138 <property name="enabled">
139 <bool>true</bool>
140 </property>
141 <property name="readOnly">
142 <bool>true</bool>
143 </property>
144 </widget>
145 </item>
146 <item row="0" column="2" rowspan="5">
147 <widget class="QGraphicsView" name="icon_view">
148 <property name="sizePolicy">
149 <sizepolicy hsizetype="Maximum" vsizetype="Maximum">
150 <horstretch>0</horstretch>
151 <verstretch>0</verstretch>
152 </sizepolicy>
153 </property>
154 <property name="minimumSize">
155 <size>
156 <width>128</width>
157 <height>128</height>
158 </size>
159 </property>
160 <property name="maximumSize">
161 <size>
162 <width>128</width>
163 <height>128</height>
164 </size>
165 </property>
166 <property name="verticalScrollBarPolicy">
167 <enum>Qt::ScrollBarAlwaysOff</enum>
168 </property>
169 <property name="horizontalScrollBarPolicy">
170 <enum>Qt::ScrollBarAlwaysOff</enum>
171 </property>
172 <property name="sizeAdjustPolicy">
173 <enum>QAbstractScrollArea::AdjustToContents</enum>
174 </property>
175 <property name="interactive">
176 <bool>false</bool>
177 </property>
178 </widget>
179 </item>
180 </layout>
181 </item>
182 </layout>
183 </widget>
184 </item>
185 <item>
186 <widget class="QGroupBox" name="PerformanceGroupBox">
187 <property name="title">
188 <string>Add-Ons</string>
189 </property>
190 <layout class="QHBoxLayout" name="PerformanceHorizontalLayout">
191 <item>
192 <widget class="QScrollArea" name="scrollArea">
193 <property name="widgetResizable">
194 <bool>true</bool>
195 </property>
196 <widget class="QWidget" name="scrollAreaWidgetContents">
197 <property name="geometry">
198 <rect>
199 <x>0</x>
200 <y>0</y>
201 <width>350</width>
202 <height>169</height>
203 </rect>
204 </property>
205 </widget>
206 </widget>
207 </item>
208 <item>
209 <layout class="QVBoxLayout" name="PerformanceVerticalLayout"/>
210 </item>
211 </layout>
212 </widget>
213 </item>
214 <item>
215 <spacer name="verticalSpacer">
216 <property name="orientation">
217 <enum>Qt::Vertical</enum>
218 </property>
219 <property name="sizeType">
220 <enum>QSizePolicy::Fixed</enum>
221 </property>
222 <property name="sizeHint" stdset="0">
223 <size>
224 <width>20</width>
225 <height>40</height>
226 </size>
227 </property>
228 </spacer>
229 </item>
230 <item>
231 <widget class="QDialogButtonBox" name="buttonBox">
232 <property name="standardButtons">
233 <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
234 </property>
235 </widget>
236 </item>
237 </layout>
238 </item>
239 </layout>
240 </widget>
241 <resources/>
242 <connections>
243 <connection>
244 <sender>buttonBox</sender>
245 <signal>accepted()</signal>
246 <receiver>ConfigurePerGameGeneral</receiver>
247 <slot>accept()</slot>
248 <hints>
249 <hint type="sourcelabel">
250 <x>269</x>
251 <y>567</y>
252 </hint>
253 <hint type="destinationlabel">
254 <x>269</x>
255 <y>294</y>
256 </hint>
257 </hints>
258 </connection>
259 <connection>
260 <sender>buttonBox</sender>
261 <signal>rejected()</signal>
262 <receiver>ConfigurePerGameGeneral</receiver>
263 <slot>reject()</slot>
264 <hints>
265 <hint type="sourcelabel">
266 <x>269</x>
267 <y>567</y>
268 </hint>
269 <hint type="destinationlabel">
270 <x>269</x>
271 <y>294</y>
272 </hint>
273 </hints>
274 </connection>
275 </connections>
276</ui>
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) {
333 QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS")); 333 QAction* dump_romfs = context_menu.addAction(tr("Dump RomFS"));
334 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); 334 QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
335 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); 335 QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
336 context_menu.addSeparator();
337 QAction* properties = context_menu.addAction(tr("Properties"));
336 338
337 open_save_location->setEnabled(program_id != 0); 339 open_save_location->setEnabled(program_id != 0);
338 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 340 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
@@ -346,6 +348,7 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
346 connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); 348 connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); });
347 connect(navigate_to_gamedb_entry, &QAction::triggered, 349 connect(navigate_to_gamedb_entry, &QAction::triggered,
348 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); 350 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); });
351 connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); });
349 352
350 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); 353 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
351} 354}
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:
70 void CopyTIDRequested(u64 program_id); 70 void CopyTIDRequested(u64 program_id);
71 void NavigateToGamedbEntryRequested(u64 program_id, 71 void NavigateToGamedbEntryRequested(u64 program_id,
72 const CompatibilityList& compatibility_list); 72 const CompatibilityList& compatibility_list);
73 void OpenPerGameGeneralRequested(const std::string& file);
73 74
74private slots: 75private slots:
75 void onTextChanged(const QString& newText); 76 void onTextChanged(const QString& newText);
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 20f5e8798..b37710f59 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,
62 FileSys::VirtualFile update_raw; 62 FileSys::VirtualFile update_raw;
63 loader.ReadUpdateRaw(update_raw); 63 loader.ReadUpdateRaw(update_raw);
64 for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) { 64 for (const auto& kv : patch_manager.GetPatchVersionNames(update_raw)) {
65 const bool is_update = kv.first == "Update"; 65 const bool is_update = kv.first == "Update" || kv.first == "[D] Update";
66 if (!updatable && is_update) { 66 if (!updatable && is_update) {
67 continue; 67 continue;
68 } 68 }
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 @@
9 9
10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
11#include "applets/software_keyboard.h" 11#include "applets/software_keyboard.h"
12#include "configuration/configure_per_general.h"
12#include "core/file_sys/vfs.h" 13#include "core/file_sys/vfs.h"
13#include "core/file_sys/vfs_real.h" 14#include "core/file_sys/vfs_real.h"
14#include "core/hle/service/acc/profile_manager.h" 15#include "core/hle/service/acc/profile_manager.h"
@@ -441,6 +442,8 @@ void GMainWindow::ConnectWidgetEvents() {
441 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); 442 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
442 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 443 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
443 &GMainWindow::OnGameListNavigateToGamedbEntry); 444 &GMainWindow::OnGameListNavigateToGamedbEntry);
445 connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
446 &GMainWindow::OnGameListOpenPerGameProperties);
444 447
445 connect(this, &GMainWindow::EmulationStarting, render_window, 448 connect(this, &GMainWindow::EmulationStarting, render_window,
446 &GRenderWindow::OnEmulationStarting); 449 &GRenderWindow::OnEmulationStarting);
@@ -988,6 +991,32 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
988 QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory)); 991 QDesktopServices::openUrl(QUrl("https://yuzu-emu.org/game/" + directory));
989} 992}
990 993
994void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
995 u64 title_id{};
996 const auto v_file = Core::GetGameFileFromPath(vfs, file);
997 const auto loader = Loader::GetLoader(v_file);
998 if (loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success) {
999 QMessageBox::information(this, tr("Properties"),
1000 tr("The game properties could not be loaded."));
1001 return;
1002 }
1003
1004 ConfigurePerGameGeneral dialog(this, title_id);
1005 dialog.loadFromFile(v_file);
1006 auto result = dialog.exec();
1007 if (result == QDialog::Accepted) {
1008 dialog.applyConfiguration();
1009
1010 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1011 if (reload) {
1012 game_list->PopulateAsync(UISettings::values.gamedir,
1013 UISettings::values.gamedir_deepscan);
1014 }
1015
1016 config->Save();
1017 }
1018}
1019
991void GMainWindow::OnMenuLoadFile() { 1020void GMainWindow::OnMenuLoadFile() {
992 const QString extensions = 1021 const QString extensions =
993 QString("*.").append(GameList::supported_file_extensions.join(" *.")).append(" main"); 1022 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:
168 void OnGameListCopyTID(u64 program_id); 168 void OnGameListCopyTID(u64 program_id);
169 void OnGameListNavigateToGamedbEntry(u64 program_id, 169 void OnGameListNavigateToGamedbEntry(u64 program_id,
170 const CompatibilityList& compatibility_list); 170 const CompatibilityList& compatibility_list);
171 void OnGameListOpenPerGameProperties(const std::string& file);
171 void OnMenuLoadFile(); 172 void OnMenuLoadFile();
172 void OnMenuLoadFolder(); 173 void OnMenuLoadFolder();
173 void OnMenuInstallToNAND(); 174 void OnMenuInstallToNAND();
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 @@
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <memory> 5#include <memory>
6#include <sstream>
6#include <SDL.h> 7#include <SDL.h>
7#include <inih/cpp/INIReader.h> 8#include <inih/cpp/INIReader.h>
8#include "common/file_util.h" 9#include "common/file_util.h"
@@ -369,6 +370,23 @@ void Config::ReadValues() {
369 Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); 370 Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false);
370 Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); 371 Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
371 372
373 const auto title_list = sdl2_config->Get("AddOns", "title_ids", "");
374 std::stringstream ss(title_list);
375 std::string line;
376 while (std::getline(ss, line, '|')) {
377 const auto title_id = std::stoul(line, nullptr, 16);
378 const auto disabled_list = sdl2_config->Get("AddOns", "disabled_" + line, "");
379
380 std::stringstream inner_ss(disabled_list);
381 std::string inner_line;
382 std::vector<std::string> out;
383 while (std::getline(inner_ss, inner_line, '|')) {
384 out.push_back(inner_line);
385 }
386
387 Settings::values.disabled_addons.insert_or_assign(title_id, out);
388 }
389
372 // Web Service 390 // Web Service
373 Settings::values.enable_telemetry = 391 Settings::values.enable_telemetry =
374 sdl2_config->GetBoolean("WebService", "enable_telemetry", true); 392 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..0f3f8da50 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
221# See https://profile.yuzu-emu.org/ for more info 221# See https://profile.yuzu-emu.org/ for more info
222yuzu_username = 222yuzu_username =
223yuzu_token = 223yuzu_token =
224
225[AddOns]
226# Used to disable add-ons
227# List of title IDs of games that will have add-ons disabled (separated by '|'):
228title_ids =
229# 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 '|')
230# e.x. disabled_0100000000010000 = Update|DLC <- disables Updates and DLC on Super Mario Odyssey
224)"; 231)";
225} 232}