From 180f22f17e2add6264615dab68b651ff06056e2a Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 23 Apr 2019 09:07:31 -0400
Subject: [PATCH 1/5] ui_settings: Add option to cache game list

---
 src/yuzu/configuration/config.cpp | 2 ++
 src/yuzu/ui_settings.h            | 1 +
 2 files changed, 3 insertions(+)

diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index db27da23e3..d708d67863 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -645,6 +645,7 @@ void Config::ReadUIGamelistValues() {
     UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt();
     UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt();
     UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
+	UISettings::values.cache_game_list = ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
 
     qt_config->endGroup();
 }
@@ -1009,6 +1010,7 @@ void Config::SaveUIGamelistValues() {
     WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64);
     WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
     WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
+	WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
 
     qt_config->endGroup();
 }
diff --git a/src/yuzu/ui_settings.h b/src/yuzu/ui_settings.h
index dbd318e206..a62cd69115 100644
--- a/src/yuzu/ui_settings.h
+++ b/src/yuzu/ui_settings.h
@@ -79,6 +79,7 @@ struct Values {
     uint8_t row_1_text_id;
     uint8_t row_2_text_id;
     std::atomic_bool is_game_list_reload_pending{false};
+    bool cache_game_list;
 };
 
 extern Values values;

From f95bdb5088a4264b7174f22e95d5409b5e297c16 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 23 Apr 2019 09:08:38 -0400
Subject: [PATCH 2/5] game_list: Implement caching for game list

Preserves list of add ons and the icon, which are the two costliest parts of game list population.
---
 src/yuzu/game_list_worker.cpp | 106 +++++++++++++++++++++++++++++++---
 1 file changed, 99 insertions(+), 7 deletions(-)

diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 82d2826bae..bc1833289d 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -9,6 +9,7 @@
 
 #include <QDir>
 #include <QFileInfo>
+#include <QSettings>
 
 #include "common/common_paths.h"
 #include "common/file_util.h"
@@ -30,13 +31,101 @@
 #include "yuzu/ui_settings.h"
 
 namespace {
+
+template <typename T>
+T GetGameListCachedObject(const std::string& filename, const std::string& ext,
+                          const std::function<T()>& generator);
+
+template <>
+QString GetGameListCachedObject(const std::string& filename, const std::string& ext,
+                                const std::function<QString()>& generator) {
+    if (!UISettings::values.cache_game_list || filename == "0000000000000000")
+        return generator();
+
+    const auto& path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+                       DIR_SEP + filename + "." + ext;
+
+    FileUtil::CreateFullPath(path);
+
+    if (!FileUtil::Exists(path)) {
+        const auto str = generator();
+
+        std::ofstream stream(path);
+        if (stream)
+            stream << str.toStdString();
+
+        stream.close();
+        return str;
+    }
+
+    std::ifstream stream(path);
+
+    if (stream) {
+        const std::string out(std::istreambuf_iterator<char>{stream},
+                              std::istreambuf_iterator<char>{});
+        stream.close();
+        return QString::fromStdString(out);
+    }
+
+    return generator();
+}
+
+template <>
+std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
+    const std::string& filename, const std::string& ext,
+    const std::function<std::pair<std::vector<u8>, std::string>()>& generator) {
+    if (!UISettings::values.cache_game_list || filename == "0000000000000000")
+        return generator();
+
+    const auto& path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
+                        "game_list" + DIR_SEP + filename + ".jpeg";
+    const auto& path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
+                        "game_list" + DIR_SEP + filename + ".appname.txt";
+
+    FileUtil::CreateFullPath(path1);
+
+    if (!FileUtil::Exists(path1) || !FileUtil::Exists(path2)) {
+        const auto [icon, nacp] = generator();
+
+        FileUtil::IOFile file1(path1, "wb");
+        file1.Resize(icon.size());
+        file1.WriteBytes(icon.data(), icon.size());
+
+        std::ofstream stream2(path2, std::ios::out);
+        if (stream2)
+            stream2 << nacp;
+
+        file1.Close();
+        stream2.close();
+        return std::make_pair(icon, nacp);
+    }
+
+    FileUtil::IOFile file1(path1, "rb");
+    std::ifstream stream2(path2);
+
+    std::vector<u8> vec(file1.GetSize());
+    file1.ReadBytes(vec.data(), vec.size());
+
+    if (stream2 && !vec.empty()) {
+        const std::string out(std::istreambuf_iterator<char>{stream2},
+                              std::istreambuf_iterator<char>{});
+        stream2.close();
+        return std::make_pair(vec, out);
+    }
+
+    return generator();
+}
+
 void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
                                std::vector<u8>& icon, std::string& name) {
-    auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
-    if (icon_file != nullptr)
-        icon = icon_file->ReadAllBytes();
-    if (nacp != nullptr)
-        name = nacp->GetApplicationName();
+    auto res = GetGameListCachedObject<std::pair<std::vector<u8>, std::string>>(
+        fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] {
+            const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca);
+            return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName());
+        });
+
+    icon = std::move(res.first);
+    name = std::move(res.second);
 }
 
 bool HasSupportedFileExtension(const std::string& file_name) {
@@ -114,8 +203,11 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
     };
 
     if (UISettings::values.show_add_ons) {
-        list.insert(
-            2, new GameListItem(FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable())));
+        const auto patch_versions = GetGameListCachedObject<QString>(
+            fmt::format("{:016X}", patch.GetTitleID()), "pv.txt", [&patch, &loader] {
+                return FormatPatchNameVersions(patch, loader, loader.IsRomFSUpdatable());
+            });
+        list.insert(2, new GameListItem(patch_versions));
     }
 
     return list;

From 944c07ac7d90b5b146af346845f457d245b32bf2 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 23 Apr 2019 09:09:02 -0400
Subject: [PATCH 3/5] yuzu: Clear partial/full game list cache when data is
 updated

---
 src/yuzu/configuration/configure_per_general.cpp | 10 ++++++++++
 src/yuzu/main.cpp                                |  3 +++
 2 files changed, 13 insertions(+)

diff --git a/src/yuzu/configuration/configure_per_general.cpp b/src/yuzu/configuration/configure_per_general.cpp
index 2bdfc8e5ab..c3e68fdf5e 100644
--- a/src/yuzu/configuration/configure_per_general.cpp
+++ b/src/yuzu/configuration/configure_per_general.cpp
@@ -13,6 +13,8 @@
 #include <QTimer>
 #include <QTreeView>
 
+#include "common/common_paths.h"
+#include "common/file_util.h"
 #include "core/file_sys/control_metadata.h"
 #include "core/file_sys/patch_manager.h"
 #include "core/file_sys/xts_archive.h"
@@ -79,6 +81,14 @@ void ConfigurePerGameGeneral::applyConfiguration() {
             disabled_addons.push_back(item.front()->text().toStdString());
     }
 
+    auto current = Settings::values.disabled_addons[title_id];
+    std::sort(disabled_addons.begin(), disabled_addons.end());
+    std::sort(current.begin(), current.end());
+    if (disabled_addons != current) {
+        FileUtil::Delete(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
+                         "game_list" + DIR_SEP + fmt::format("{:016X}.pv.txt", title_id));
+    }
+
     Settings::values.disabled_addons[title_id] = disabled_addons;
 }
 
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index cef2cc1ae8..86aed0d942 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1396,6 +1396,9 @@ void GMainWindow::OnMenuInstallToNAND() {
                                  tr("The file was successfully installed."));
         game_list->PopulateAsync(UISettings::values.game_directory_path,
                                  UISettings::values.game_directory_deepscan);
+        // Clear the game list cache.
+        FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
+                                       DIR_SEP + "game_list");
     };
 
     const auto failed = [this]() {

From 46e2ca5475854a1ae58283804d7b712594ecf461 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sun, 26 May 2019 17:14:09 -0400
Subject: [PATCH 4/5] game_list_worker: Add better error handling to caching

---
 src/yuzu/configuration/config.cpp |  5 +--
 src/yuzu/game_list_worker.cpp     | 60 ++++++++++++++++++++-----------
 2 files changed, 42 insertions(+), 23 deletions(-)

diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index d708d67863..b1942bedc2 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -645,7 +645,8 @@ void Config::ReadUIGamelistValues() {
     UISettings::values.icon_size = ReadSetting(QStringLiteral("icon_size"), 64).toUInt();
     UISettings::values.row_1_text_id = ReadSetting(QStringLiteral("row_1_text_id"), 3).toUInt();
     UISettings::values.row_2_text_id = ReadSetting(QStringLiteral("row_2_text_id"), 2).toUInt();
-	UISettings::values.cache_game_list = ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
+    UISettings::values.cache_game_list =
+        ReadSetting(QStringLiteral("cache_game_list"), true).toBool();
 
     qt_config->endGroup();
 }
@@ -1010,7 +1011,7 @@ void Config::SaveUIGamelistValues() {
     WriteSetting(QStringLiteral("icon_size"), UISettings::values.icon_size, 64);
     WriteSetting(QStringLiteral("row_1_text_id"), UISettings::values.row_1_text_id, 3);
     WriteSetting(QStringLiteral("row_2_text_id"), UISettings::values.row_2_text_id, 2);
-	WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
+    WriteSetting(QStringLiteral("cache_game_list"), UISettings::values.cache_game_list, true);
 
     qt_config->endGroup();
 }
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index bc1833289d..4d951a4e78 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -39,11 +39,12 @@ T GetGameListCachedObject(const std::string& filename, const std::string& ext,
 template <>
 QString GetGameListCachedObject(const std::string& filename, const std::string& ext,
                                 const std::function<QString()>& generator) {
-    if (!UISettings::values.cache_game_list || filename == "0000000000000000")
+    if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
         return generator();
+    }
 
-    const auto& path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
-                       DIR_SEP + filename + "." + ext;
+    const auto path = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+                      DIR_SEP + filename + '.' + ext;
 
     FileUtil::CreateFullPath(path);
 
@@ -51,10 +52,10 @@ QString GetGameListCachedObject(const std::string& filename, const std::string&
         const auto str = generator();
 
         std::ofstream stream(path);
-        if (stream)
+        if (stream) {
             stream << str.toStdString();
+        }
 
-        stream.close();
         return str;
     }
 
@@ -63,7 +64,6 @@ QString GetGameListCachedObject(const std::string& filename, const std::string&
     if (stream) {
         const std::string out(std::istreambuf_iterator<char>{stream},
                               std::istreambuf_iterator<char>{});
-        stream.close();
         return QString::fromStdString(out);
     }
 
@@ -74,13 +74,14 @@ template <>
 std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
     const std::string& filename, const std::string& ext,
     const std::function<std::pair<std::vector<u8>, std::string>()>& generator) {
-    if (!UISettings::values.cache_game_list || filename == "0000000000000000")
+    if (!UISettings::values.cache_game_list || filename == "0000000000000000") {
         return generator();
+    }
 
-    const auto& path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
-                        "game_list" + DIR_SEP + filename + ".jpeg";
-    const auto& path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP +
-                        "game_list" + DIR_SEP + filename + ".appname.txt";
+    const auto path1 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+                       DIR_SEP + filename + ".jpeg";
+    const auto path2 = FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + DIR_SEP + "game_list" +
+                       DIR_SEP + filename + ".appname.txt";
 
     FileUtil::CreateFullPath(path1);
 
@@ -88,28 +89,48 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
         const auto [icon, nacp] = generator();
 
         FileUtil::IOFile file1(path1, "wb");
-        file1.Resize(icon.size());
-        file1.WriteBytes(icon.data(), icon.size());
+        if (!file1.IsOpen()) {
+            LOG_ERROR(Frontend, "Failed to open cache file.");
+            return generator();
+        }
+
+        if (!file1.Resize(icon.size())) {
+            LOG_ERROR(Frontend, "Failed to resize cache file to necessary size.");
+            return generator();
+        }
+
+        if (file1.WriteBytes(icon.data(), icon.size()) != icon.size()) {
+            LOG_ERROR(Frontend, "Failed to write data to cache file.");
+            return generator();
+        }
 
         std::ofstream stream2(path2, std::ios::out);
-        if (stream2)
+        if (stream2) {
             stream2 << nacp;
+        }
 
-        file1.Close();
-        stream2.close();
         return std::make_pair(icon, nacp);
     }
 
     FileUtil::IOFile file1(path1, "rb");
     std::ifstream stream2(path2);
 
+    if (!file1.IsOpen()) {
+        LOG_ERROR(Frontend, "Failed to open cache file for reading.");
+        return generator();
+    }
+
+    if (!stream2) {
+        LOG_ERROR(Frontend, "Failed to open cache file for reading.");
+        return generator();
+    }
+
     std::vector<u8> vec(file1.GetSize());
     file1.ReadBytes(vec.data(), vec.size());
 
     if (stream2 && !vec.empty()) {
         const std::string out(std::istreambuf_iterator<char>{stream2},
                               std::istreambuf_iterator<char>{});
-        stream2.close();
         return std::make_pair(vec, out);
     }
 
@@ -118,14 +139,11 @@ std::pair<std::vector<u8>, std::string> GetGameListCachedObject(
 
 void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager, const FileSys::NCA& nca,
                                std::vector<u8>& icon, std::string& name) {
-    auto res = GetGameListCachedObject<std::pair<std::vector<u8>, std::string>>(
+    std::tie(icon, name) = GetGameListCachedObject<std::pair<std::vector<u8>, std::string>>(
         fmt::format("{:016X}", patch_manager.GetTitleID()), {}, [&patch_manager, &nca] {
             const auto [nacp, icon_f] = patch_manager.ParseControlNCA(nca);
             return std::make_pair(icon_f->ReadAllBytes(), nacp->GetApplicationName());
         });
-
-    icon = std::move(res.first);
-    name = std::move(res.second);
 }
 
 bool HasSupportedFileExtension(const std::string& file_name) {

From 9b2d38582f1b7ad1d3c8dbc73a379cfca4fdabb2 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Thu, 30 May 2019 10:47:56 -0400
Subject: [PATCH 5/5] main: Remove extraneous comment

---
 src/yuzu/main.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 86aed0d942..f8a0daebd1 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -1396,7 +1396,6 @@ void GMainWindow::OnMenuInstallToNAND() {
                                  tr("The file was successfully installed."));
         game_list->PopulateAsync(UISettings::values.game_directory_path,
                                  UISettings::values.game_directory_deepscan);
-        // Clear the game list cache.
         FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
                                        DIR_SEP + "game_list");
     };