From 9078bb9854cff3f77ac28a15dffff06bb31b3755 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 29 Oct 2018 16:08:03 -0400
Subject: [PATCH 1/3] bis_factory: Add getter for mod dump root for a title ID
 Equates to yuzu_dir/dump/<title id>/

---
 src/core/file_sys/bis_factory.cpp             |  9 ++++++-
 src/core/file_sys/bis_factory.h               |  4 ++-
 .../hle/service/filesystem/filesystem.cpp     | 25 ++++++++++++++++---
 src/core/hle/service/filesystem/filesystem.h  |  1 +
 4 files changed, 33 insertions(+), 6 deletions(-)

diff --git a/src/core/file_sys/bis_factory.cpp b/src/core/file_sys/bis_factory.cpp
index 76a2b7e863..e29f70b3a6 100644
--- a/src/core/file_sys/bis_factory.cpp
+++ b/src/core/file_sys/bis_factory.cpp
@@ -8,8 +8,9 @@
 
 namespace FileSys {
 
-BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_)
+BISFactory::BISFactory(VirtualDir nand_root_, VirtualDir load_root_, VirtualDir dump_root_)
     : nand_root(std::move(nand_root_)), load_root(std::move(load_root_)),
+      dump_root(std::move(dump_root_)),
       sysnand_cache(std::make_unique<RegisteredCache>(
           GetOrCreateDirectoryRelative(nand_root, "/system/Contents/registered"))),
       usrnand_cache(std::make_unique<RegisteredCache>(
@@ -32,4 +33,10 @@ VirtualDir BISFactory::GetModificationLoadRoot(u64 title_id) const {
     return GetOrCreateDirectoryRelative(load_root, fmt::format("/{:016X}", title_id));
 }
 
+VirtualDir BISFactory::GetModificationDumpRoot(u64 title_id) const {
+    if (title_id == 0)
+        return nullptr;
+    return GetOrCreateDirectoryRelative(dump_root, fmt::format("/{:016X}", title_id));
+}
+
 } // namespace FileSys
diff --git a/src/core/file_sys/bis_factory.h b/src/core/file_sys/bis_factory.h
index 364d309bd2..453c11ad27 100644
--- a/src/core/file_sys/bis_factory.h
+++ b/src/core/file_sys/bis_factory.h
@@ -17,17 +17,19 @@ class RegisteredCache;
 /// registered caches.
 class BISFactory {
 public:
-    explicit BISFactory(VirtualDir nand_root, VirtualDir load_root);
+    explicit BISFactory(VirtualDir nand_root, VirtualDir load_root, VirtualDir dump_root);
     ~BISFactory();
 
     RegisteredCache* GetSystemNANDContents() const;
     RegisteredCache* GetUserNANDContents() const;
 
     VirtualDir GetModificationLoadRoot(u64 title_id) const;
+    VirtualDir GetModificationDumpRoot(u64 title_id) const;
 
 private:
     VirtualDir nand_root;
     VirtualDir load_root;
+    VirtualDir dump_root;
 
     std::unique_ptr<RegisteredCache> sysnand_cache;
     std::unique_ptr<RegisteredCache> usrnand_cache;
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index e32a7c48e2..234a8687b4 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -360,6 +360,15 @@ FileSys::VirtualDir GetModificationLoadRoot(u64 title_id) {
     return bis_factory->GetModificationLoadRoot(title_id);
 }
 
+FileSys::VirtualDir GetModificationDumpRoot(u64 title_id) {
+    LOG_TRACE(Service_FS, "Opening mod dump root for tid={:016X}", title_id);
+
+    if (bis_factory == nullptr)
+        return nullptr;
+
+    return bis_factory->GetModificationDumpRoot(title_id);
+}
+
 void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
     if (overwrite) {
         bis_factory = nullptr;
@@ -373,13 +382,21 @@ void CreateFactories(FileSys::VfsFilesystem& vfs, bool overwrite) {
                                           FileSys::Mode::ReadWrite);
     auto load_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::LoadDir),
                                             FileSys::Mode::ReadWrite);
+    auto dump_directory = vfs.OpenDirectory(FileUtil::GetUserPath(FileUtil::UserPath::DumpDir),
+                                            FileSys::Mode::ReadWrite);
 
-    if (bis_factory == nullptr)
-        bis_factory = std::make_unique<FileSys::BISFactory>(nand_directory, load_directory);
-    if (save_data_factory == nullptr)
+    if (bis_factory == nullptr) {
+        bis_factory =
+            std::make_unique<FileSys::BISFactory>(nand_directory, load_directory, dump_directory);
+    }
+
+    if (save_data_factory == nullptr) {
         save_data_factory = std::make_unique<FileSys::SaveDataFactory>(std::move(nand_directory));
-    if (sdmc_factory == nullptr)
+    }
+
+    if (sdmc_factory == nullptr) {
         sdmc_factory = std::make_unique<FileSys::SDMCFactory>(std::move(sd_directory));
+    }
 }
 
 void InstallInterfaces(SM::ServiceManager& service_manager, FileSys::VfsFilesystem& vfs) {
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 6ca5c56369..b18652a685 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -54,6 +54,7 @@ FileSys::RegisteredCache* GetUserNANDContents();
 FileSys::RegisteredCache* GetSDMCContents();
 
 FileSys::VirtualDir GetModificationLoadRoot(u64 title_id);
+FileSys::VirtualDir GetModificationDumpRoot(u64 title_id);
 
 // Creates the SaveData, SDMC, and BIS Factories. Should be called once and before any function
 // above is called.

From 48eb3742b9f84108538e2a41019aa9d5d47f7352 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 29 Oct 2018 16:09:08 -0400
Subject: [PATCH 2/3] settings: Add setting to control NSO dumping Also adds UI
 option in Debug > Dump section, with the idea later things to be dumped (i.e.
 other game data or textures, etc) will use the same group box.

---
 src/core/settings.h                        |  1 +
 src/yuzu/configuration/config.cpp          |  2 ++
 src/yuzu/configuration/configure_debug.cpp |  2 ++
 src/yuzu/configuration/configure_debug.ui  | 21 ++++++++++++++++++++-
 src/yuzu_cmd/config.cpp                    |  1 +
 src/yuzu_cmd/default_ini.h                 |  2 ++
 6 files changed, 28 insertions(+), 1 deletion(-)

diff --git a/src/core/settings.h b/src/core/settings.h
index b5aeff29b1..0af9dd416c 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -157,6 +157,7 @@ struct Values {
     bool use_gdbstub;
     u16 gdbstub_port;
     std::string program_args;
+    bool dump_nso;
 
     // WebService
     bool enable_telemetry;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index d4fd60a733..b85011cd68 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -145,6 +145,7 @@ void Config::ReadValues() {
     Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
     Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
     Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
+    Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool();
     qt_config->endGroup();
 
     qt_config->beginGroup("WebService");
@@ -283,6 +284,7 @@ void Config::SaveValues() {
     qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
     qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
     qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
+    qt_config->setValue("dump_nso", Settings::values.dump_nso);
     qt_config->endGroup();
 
     qt_config->beginGroup("WebService");
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp
index 9e765fc93b..fd5876b41b 100644
--- a/src/yuzu/configuration/configure_debug.cpp
+++ b/src/yuzu/configuration/configure_debug.cpp
@@ -34,6 +34,7 @@ void ConfigureDebug::setConfiguration() {
     ui->toggle_console->setChecked(UISettings::values.show_console);
     ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter));
     ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args));
+    ui->dump_decompressed_nso->setChecked(Settings::values.dump_nso);
 }
 
 void ConfigureDebug::applyConfiguration() {
@@ -42,6 +43,7 @@ void ConfigureDebug::applyConfiguration() {
     UISettings::values.show_console = ui->toggle_console->isChecked();
     Settings::values.log_filter = ui->log_filter_edit->text().toStdString();
     Settings::values.program_args = ui->homebrew_args_edit->text().toStdString();
+    Settings::values.dump_nso = ui->dump_decompressed_nso->isChecked();
     Debugger::ToggleConsole();
     Log::Filter filter;
     filter.ParseFilterString(Settings::values.log_filter);
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui
index ff4987604e..9c5b702f89 100644
--- a/src/yuzu/configuration/configure_debug.ui
+++ b/src/yuzu/configuration/configure_debug.ui
@@ -7,7 +7,7 @@
     <x>0</x>
     <y>0</y>
     <width>400</width>
-    <height>300</height>
+    <height>357</height>
    </rect>
   </property>
   <property name="windowTitle">
@@ -129,6 +129,25 @@
      </layout>
     </widget>
    </item>
+   <item>
+    <widget class="QGroupBox" name="groupBox_4">
+     <property name="title">
+      <string>Dump</string>
+     </property>
+     <layout class="QVBoxLayout" name="verticalLayout_4">
+      <item>
+       <widget class="QCheckBox" name="dump_decompressed_nso">
+        <property name="whatsThis">
+         <string>When checked, any NSO yuzu tries to load or patch will be copied decompressed to the yuzu/dump directory.</string>
+        </property>
+        <property name="text">
+         <string>Dump Decompressed NSOs</string>
+        </property>
+       </widget>
+      </item>
+     </layout>
+    </widget>
+   </item>
    <item>
     <spacer name="verticalSpacer">
      <property name="orientation">
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index b456266a6a..f20b3a1b38 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -141,6 +141,7 @@ void Config::ReadValues() {
     Settings::values.gdbstub_port =
         static_cast<u16>(sdl2_config->GetInteger("Debugging", "gdbstub_port", 24689));
     Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", "");
+    Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false);
 
     // Web Service
     Settings::values.enable_telemetry =
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index e0b223cd64..d834983583 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -201,6 +201,8 @@ log_filter = *:Trace
 # Port for listening to GDB connections.
 use_gdbstub=false
 gdbstub_port=24689
+# Determines whether or not yuzu will dump all NSOs it attempts to load while loading them
+dump_nso=false
 
 [WebService]
 # Whether or not to enable telemetry

From 1c0226815d453acfb5e1fd766765b43fd447c15c Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 29 Oct 2018 16:10:16 -0400
Subject: [PATCH 3/3] patch_manager: Add support for dumping decompressed NSOs
 When enabled in settings, PatchNSO will dump the unmodified NSO that it was
 passed to a file named <build id>.nso in the dump root for the current title
 ID.

---
 src/core/file_sys/patch_manager.cpp | 13 +++++++++++++
 src/core/loader/nso.cpp             |  2 +-
 2 files changed, 14 insertions(+), 1 deletion(-)

diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index cb457b987e..3a1623ca08 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -19,6 +19,7 @@
 #include "core/file_sys/vfs_vector.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/loader/loader.h"
+#include "core/settings.h"
 
 namespace FileSys {
 
@@ -119,6 +120,18 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
     const auto build_id_raw = Common::HexArrayToString(header.build_id);
     const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
 
+    if (Settings::values.dump_nso) {
+        LOG_INFO(Loader, "Dumping NSO for build_id={}, title_id={:016X}", build_id, title_id);
+        const auto dump_dir = Service::FileSystem::GetModificationDumpRoot(title_id);
+        if (dump_dir != nullptr) {
+            const auto nso_dir = GetOrCreateDirectoryRelative(dump_dir, "/nso");
+            const auto file = nso_dir->CreateFile(fmt::format("{}.nso", build_id));
+
+            file->Resize(nso.size());
+            file->WriteBytes(nso);
+        }
+    }
+
     LOG_INFO(Loader, "Patching NSO for build_id={}", build_id);
 
     const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 68efca5c0d..aaf0063092 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -154,7 +154,7 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(const FileSys::VfsFile& file, VAd
     program_image.resize(image_size);
 
     // Apply patches if necessary
-    if (pm && pm->HasNSOPatch(nso_header.build_id)) {
+    if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
         std::vector<u8> pi_header(program_image.size() + 0x100);
         std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
         std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());