From d770c602052a9016f4577ed8c993a8f78f8e5507 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:43:27 -0400
Subject: [PATCH 01/14] key_manager: Avoid autogeneration if key exists

---
 src/core/crypto/key_manager.cpp | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index f768533da..bd4b3d7c7 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -231,18 +231,28 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname,
 }
 
 void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
-    const auto iter = std::find_if(
+    if (s128_keys.find({id, field1, field2}) != s128_keys.end())
+        return;
+    if (id == S128KeyType::Titlekey) {
+        Key128 rights_id;
+        std::memcpy(rights_id.data(), &field2, sizeof(u64));
+        std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
+        WriteKeyToFile(true, fmt::format("{}", Common::HexArrayToString(rights_id)), key);
+    }
+    const auto iter2 = std::find_if(
         s128_file_id.begin(), s128_file_id.end(),
         [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
             return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
                    std::tie(id, field1, field2);
         });
-    if (iter != s128_file_id.end())
-        WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key);
+    if (iter2 != s128_file_id.end())
+        WriteKeyToFile(false, iter2->first, key);
     s128_keys[{id, field1, field2}] = key;
 }
 
 void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
+    if (s256_keys.find({id, field1, field2}) != s256_keys.end())
+        return;
     const auto iter = std::find_if(
         s256_file_id.begin(), s256_file_id.end(),
         [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {

From b555311438ae1e2ad1e9caea55cc9f77c9ed4661 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:44:14 -0400
Subject: [PATCH 02/14] loader: Add NSP file type and NSP-specific errors

---
 src/core/loader/loader.cpp | 14 ++++++++++++--
 src/core/loader/loader.h   |  2 ++
 2 files changed, 14 insertions(+), 2 deletions(-)

diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 5980cdb25..446adf557 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -15,6 +15,7 @@
 #include "core/loader/nca.h"
 #include "core/loader/nro.h"
 #include "core/loader/nso.h"
+#include "core/loader/nsp.h"
 #include "core/loader/xci.h"
 
 namespace Loader {
@@ -34,6 +35,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
     CHECK_TYPE(NCA)
     CHECK_TYPE(XCI)
     CHECK_TYPE(NAX)
+    CHECK_TYPE(NSP)
 
 #undef CHECK_TYPE
 
@@ -59,6 +61,8 @@ FileType GuessFromFilename(const std::string& name) {
         return FileType::NCA;
     if (extension == "xci")
         return FileType::XCI;
+    if (extension == "nsp")
+        return FileType::NSP;
 
     return FileType::Unknown;
 }
@@ -77,6 +81,8 @@ std::string GetFileTypeString(FileType type) {
         return "XCI";
     case FileType::NAX:
         return "NAX";
+    case FileType::NSP:
+        return "NSP";
     case FileType::DeconstructedRomDirectory:
         return "Directory";
     case FileType::Error:
@@ -87,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
     return "unknown";
 }
 
-constexpr std::array<const char*, 49> RESULT_MESSAGES{
+constexpr std::array<const char*, 50> RESULT_MESSAGES{
     "The operation completed successfully.",
     "The loader requested to load is already loaded.",
     "The operation is not implemented.",
@@ -137,7 +143,7 @@ constexpr std::array<const char*, 49> RESULT_MESSAGES{
     "The AES Key Generation Source could not be found.",
     "The SD Save Key Source could not be found.",
     "The SD NCA Key Source could not be found.",
-};
+    "The NSP file is missing a Program-type NCA."};
 
 std::ostream& operator<<(std::ostream& os, ResultStatus status) {
     os << RESULT_MESSAGES.at(static_cast<size_t>(status));
@@ -182,6 +188,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
     case FileType::NAX:
         return std::make_unique<AppLoader_NAX>(std::move(file));
 
+    // NX NSP (Nintendo Submission Package) file format
+    case FileType::NSP:
+        return std::make_unique<AppLoader_NSP>(std::move(file));
+
     // NX deconstructed ROM directory.
     case FileType::DeconstructedRomDirectory:
         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index 5a8540b0e..be66b2257 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -29,6 +29,7 @@ enum class FileType {
     NSO,
     NRO,
     NCA,
+    NSP,
     XCI,
     NAX,
     DeconstructedRomDirectory,
@@ -105,6 +106,7 @@ enum class ResultStatus : u16 {
     ErrorMissingAESKeyGenerationSource,
     ErrorMissingSDSaveKeySource,
     ErrorMissingSDNCAKeySource,
+    ErrorNSPMissingProgramNCA,
 };
 
 std::ostream& operator<<(std::ostream& os, ResultStatus status);

From a040929c90055b520d52fc062b297578ab119800 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:44:52 -0400
Subject: [PATCH 03/14] drd: Load title ID from program metadata Previously
 only loaded from control metadata

---
 src/core/loader/deconstructed_rom_directory.cpp | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 921b899e2..1ae4bb656 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -61,7 +61,6 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
 
     if (nacp_file != nullptr) {
         FileSys::NACP nacp(nacp_file);
-        title_id = nacp.GetTitleId();
         name = nacp.GetApplicationName();
     }
 }
@@ -120,6 +119,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
     }
 
     auto& kernel = Core::System::GetInstance().Kernel();
+    title_id = metadata.GetTitleID();
     process->program_id = metadata.GetTitleID();
     process->svc_access_mask.set();
     process->resource_limit =
@@ -159,8 +159,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buff
 }
 
 ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) {
-    if (name.empty())
-        return ResultStatus::ErrorNoControl;
     out_program_id = title_id;
     return ResultStatus::Success;
 }

From 93703431e2d5318ac4a901b81d31230c40942043 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:45:26 -0400
Subject: [PATCH 04/14] file_sys: Add Nintendo Submission Package (NSP)

---
 src/core/file_sys/submission_package.cpp | 226 +++++++++++++++++++++++
 src/core/file_sys/submission_package.h   |  70 +++++++
 2 files changed, 296 insertions(+)
 create mode 100644 src/core/file_sys/submission_package.cpp
 create mode 100644 src/core/file_sys/submission_package.h

diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
new file mode 100644
index 000000000..660771cf8
--- /dev/null
+++ b/src/core/file_sys/submission_package.cpp
@@ -0,0 +1,226 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <fmt/ostream.h>
+#include "common/assert.h"
+#include "common/hex_util.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/submission_package.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+NSP::NSP(VirtualFile file_)
+    : file(std::move(file_)),
+      pfs(std::make_shared<PartitionFilesystem>(file)), status{Loader::ResultStatus::Success} {
+    if (pfs->GetStatus() != Loader::ResultStatus::Success) {
+        status = pfs->GetStatus();
+        return;
+    }
+
+    if (IsDirectoryExeFS(pfs)) {
+        extracted = true;
+        exefs = pfs;
+
+        const auto& files = pfs->GetFiles();
+        const auto romfs_iter =
+            std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
+                return file->GetName().find(".romfs") != std::string::npos;
+            });
+        if (romfs_iter != files.end())
+            romfs = *romfs_iter;
+        return;
+    }
+
+    extracted = false;
+    const auto files = pfs->GetFiles();
+
+    Core::Crypto::KeyManager keys;
+    for (const auto& ticket_file : files) {
+        if (ticket_file->GetExtension() == "tik") {
+            if (ticket_file == nullptr ||
+                ticket_file->GetSize() <
+                    Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
+                continue;
+            }
+
+            Core::Crypto::Key128 key{};
+            ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
+            std::string_view name_only(ticket_file->GetName());
+            name_only.remove_suffix(4);
+            const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
+            u128 rights_id;
+            std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
+            keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
+        }
+    }
+
+    for (const auto& outer_file : files) {
+        if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
+            const auto nca = std::make_shared<NCA>(outer_file);
+            if (nca->GetStatus() != Loader::ResultStatus::Success)
+                continue;
+            const auto section0 = nca->GetSubdirectories()[0];
+
+            for (const auto& inner_file : section0->GetFiles()) {
+                if (inner_file->GetExtension() != "cnmt")
+                    continue;
+
+                const CNMT cnmt(inner_file);
+                auto& ncas_title = ncas[cnmt.GetTitleID()];
+
+                ncas_title[ContentRecordType::Meta] = nca;
+                for (const auto& rec : cnmt.GetContentRecords()) {
+                    const auto next_file = pfs->GetFile(
+                        fmt::format("{}.nca", Common::HexArrayToString(rec.nca_id, false)));
+                    if (next_file == nullptr) {
+                        LOG_WARNING(Service_FS,
+                                    "NCA with ID {}.nca is listed in content metadata, but cannot "
+                                    "be found in PFS. NSP appears to be corrupted.",
+                                    Common::HexArrayToString(rec.nca_id, false));
+                        continue;
+                    }
+
+                    const auto next_nca = std::make_shared<NCA>(next_file);
+                    if (next_nca->GetType() == NCAContentType::Program)
+                        program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
+                    if (next_nca->GetStatus() == Loader::ResultStatus::Success)
+                        ncas_title[rec.type] = next_nca;
+                }
+
+                break;
+            }
+        }
+    }
+}
+
+Loader::ResultStatus NSP::GetStatus() const {
+    return status;
+}
+
+Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
+    if (program_status.find(title_id) != program_status.end())
+        return program_status.at(title_id);
+    return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
+}
+
+u64 NSP::GetFirstTitleID() const {
+    if (program_status.empty())
+        return 0;
+    return program_status.begin()->first;
+}
+
+u64 NSP::GetProgramTitleID() const {
+    auto out = GetFirstTitleID();
+    for (const auto other_tid : GetTitleIDs()) {
+        if ((out & 0x800) != 0)
+            out = other_tid;
+    }
+    return out;
+}
+
+std::vector<u64> NSP::GetTitleIDs() const {
+    std::vector<u64> out;
+    for (const auto& kv : ncas)
+        out.push_back(kv.first);
+    return out;
+}
+
+bool NSP::IsExtractedType() const {
+    return extracted;
+}
+
+VirtualFile NSP::GetRomFS() const {
+    return romfs;
+}
+
+VirtualDir NSP::GetExeFS() const {
+    return exefs;
+}
+
+std::vector<std::shared_ptr<NCA>> NSP::GetNCAsCollapsed() const {
+    if (extracted)
+        LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
+    std::vector<std::shared_ptr<NCA>> out;
+    for (const auto& map : ncas) {
+        for (const auto& inner_map : map.second)
+            out.push_back(inner_map.second);
+    }
+    return out;
+}
+
+std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
+    if (extracted)
+        LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
+    std::multimap<u64, std::shared_ptr<NCA>> out;
+    for (const auto& map : ncas) {
+        for (const auto& inner_map : map.second)
+            out.insert({map.first, inner_map.second});
+    }
+    return out;
+}
+
+std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
+    return ncas;
+}
+
+std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
+    if (extracted)
+        LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
+    if (ncas.find(title_id) != ncas.end()) {
+        const auto& inner_map = ncas.at(title_id);
+        if (inner_map.find(type) != inner_map.end())
+            return inner_map.at(type);
+    }
+
+    return nullptr;
+}
+
+VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
+    if (extracted)
+        LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
+    const auto nca = GetNCA(title_id, type);
+    if (nca != nullptr)
+        return nca->GetBaseFile();
+    return nullptr;
+}
+
+std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
+    if (extracted)
+        LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
+    std::vector<Core::Crypto::Key128> out;
+    for (const auto& ticket_file : ticket_files) {
+        if (ticket_file == nullptr ||
+            ticket_file->GetSize() <
+                Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
+            continue;
+        }
+
+        Core::Crypto::Key128 key{};
+        ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
+        out.push_back(key);
+    }
+    return out;
+}
+
+std::vector<VirtualFile> NSP::GetFiles() const {
+    return pfs->GetFiles();
+}
+
+std::vector<VirtualDir> NSP::GetSubdirectories() const {
+    return pfs->GetSubdirectories();
+}
+
+std::string NSP::GetName() const {
+    return file->GetName();
+}
+
+VirtualDir NSP::GetParentDirectory() const {
+    return file->GetContainingDirectory();
+}
+
+bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
+    return false;
+}
+} // namespace FileSys
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
new file mode 100644
index 000000000..7b520df57
--- /dev/null
+++ b/src/core/file_sys/submission_package.h
@@ -0,0 +1,70 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <map>
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/vfs.h"
+#include "core/loader/loader.h"
+#include "romfs_factory.h"
+
+namespace FileSys {
+
+class NSP : public ReadOnlyVfsDirectory {
+public:
+    explicit NSP(VirtualFile file);
+
+    Loader::ResultStatus GetStatus() const;
+    Loader::ResultStatus GetProgramStatus(u64 title_id) const;
+    // Should only be used when one title id can be assured.
+    u64 GetFirstTitleID() const;
+    u64 GetProgramTitleID() const;
+    std::vector<u64> GetTitleIDs() const;
+
+    bool IsExtractedType() const;
+
+    // Common (Can be safely called on both types)
+    VirtualFile GetRomFS() const;
+    VirtualDir GetExeFS() const;
+
+    // Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
+    std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
+    std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
+    std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
+    std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
+    VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
+    std::vector<Core::Crypto::Key128> GetTitlekey() const;
+
+    std::vector<VirtualFile> GetFiles() const override;
+
+    std::vector<VirtualDir> GetSubdirectories() const override;
+
+    std::string GetName() const override;
+
+    VirtualDir GetParentDirectory() const override;
+
+protected:
+    bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
+
+private:
+    VirtualFile file;
+
+    bool extracted;
+    Loader::ResultStatus status;
+    std::map<u64, Loader::ResultStatus> program_status;
+
+    std::shared_ptr<PartitionFilesystem> pfs;
+    // Map title id -> {map type -> NCA}
+    std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
+    std::vector<VirtualFile> ticket_files;
+
+    VirtualFile romfs;
+    VirtualDir exefs;
+};
+} // namespace FileSys

From 5c8aff984e47c0f471e9eafd071031bc49ad8efc Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:48:23 -0400
Subject: [PATCH 05/14] card_image: Parse XCI secure partition with NSP
 Eliminated duplicate code and adds support for Rev1+ carts

---
 src/core/crypto/key_manager.h    |  2 ++
 src/core/file_sys/card_image.cpp | 33 +++++++++++++++++++++++++-------
 src/core/file_sys/card_image.h   |  7 +++++++
 src/core/loader/xci.cpp          |  7 +++----
 4 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index bf51bf31f..ce67913bb 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -17,6 +17,8 @@ enum class ResultStatus : u16;
 
 namespace Core::Crypto {
 
+constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
+
 using Key128 = std::array<u8, 0x10>;
 using Key256 = std::array<u8, 0x20>;
 using SHA256Hash = std::array<u8, 0x20>;
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index ce4423fa6..d0f1afac0 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -10,6 +10,7 @@
 #include "common/logging/log.h"
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/content_archive.h"
+#include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/partition_filesystem.h"
 #include "core/file_sys/vfs_offset.h"
 #include "core/loader/loader.h"
@@ -44,15 +45,19 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
             partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
     }
 
+    secure_partition = std::make_shared<NSP>(
+        main_hfs.GetFile(partition_names[static_cast<size_t>(XCIPartition::Secure)]));
+
+    const auto secure_ncas = secure_partition->GetNCAsCollapsed();
+    std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
+
     program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
+    program =
+        secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
+    if (program != nullptr)
+        program_nca_status = program->GetStatus();
 
-    auto result = AddNCAFromPartition(XCIPartition::Secure);
-    if (result != Loader::ResultStatus::Success) {
-        status = result;
-        return;
-    }
-
-    result = AddNCAFromPartition(XCIPartition::Update);
+    auto result = AddNCAFromPartition(XCIPartition::Update);
     if (result != Loader::ResultStatus::Success) {
         status = result;
         return;
@@ -89,6 +94,10 @@ VirtualDir XCI::GetPartition(XCIPartition partition) const {
     return partitions[static_cast<size_t>(partition)];
 }
 
+std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
+    return secure_partition;
+}
+
 VirtualDir XCI::GetSecurePartition() const {
     return GetPartition(XCIPartition::Secure);
 }
@@ -105,6 +114,16 @@ VirtualDir XCI::GetLogoPartition() const {
     return GetPartition(XCIPartition::Logo);
 }
 
+std::shared_ptr<NCA> XCI::GetProgramNCA() const {
+    return program;
+}
+
+VirtualFile XCI::GetProgramNCAFile() const {
+    if (GetProgramNCA() == nullptr)
+        return nullptr;
+    return GetProgramNCA()->GetBaseFile();
+}
+
 const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
     return ncas;
 }
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 4f104d18a..b73f1d900 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -10,6 +10,8 @@
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/file_sys/vfs.h"
+#include "core/loader/loader.h"
+#include "submission_package.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
@@ -71,11 +73,14 @@ public:
     u8 GetFormatVersion() const;
 
     VirtualDir GetPartition(XCIPartition partition) const;
+    std::shared_ptr<NSP> GetSecurePartitionNSP() const;
     VirtualDir GetSecurePartition() const;
     VirtualDir GetNormalPartition() const;
     VirtualDir GetUpdatePartition() const;
     VirtualDir GetLogoPartition() const;
 
+    std::shared_ptr<NCA> GetProgramNCA() const;
+    VirtualFile GetProgramNCAFile() const;
     const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
     std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
     VirtualFile GetNCAFileByType(NCAContentType type) const;
@@ -101,6 +106,8 @@ private:
     Loader::ResultStatus program_nca_status;
 
     std::vector<VirtualDir> partitions;
+    std::shared_ptr<NSP> secure_partition;
+    std::shared_ptr<NCA> program;
     std::vector<std::shared_ptr<NCA>> ncas;
 };
 } // namespace FileSys
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 9dc4d1f35..75b998faa 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -17,8 +17,7 @@ namespace Loader {
 
 AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
     : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
-      nca_loader(std::make_unique<AppLoader_NCA>(
-          xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {
+      nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
     if (xci->GetStatus() != ResultStatus::Success)
         return;
     const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
@@ -64,11 +63,11 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
     if (xci->GetProgramNCAStatus() != ResultStatus::Success)
         return xci->GetProgramNCAStatus();
 
-    const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program);
+    const auto nca = xci->GetProgramNCA();
     if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
         return ResultStatus::ErrorMissingProductionKeyFile;
 
-    auto result = nca_loader->Load(process);
+    const auto result = nca_loader->Load(process);
     if (result != ResultStatus::Success)
         return result;
 

From d7518cf6e03184a25bffeef5d38257549012b98b Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:49:31 -0400
Subject: [PATCH 06/14] loader: Add AppLoader for NSP files

---
 src/core/loader/nsp.cpp | 128 ++++++++++++++++++++++++++++++++++++++++
 src/core/loader/nsp.h   |  54 +++++++++++++++++
 2 files changed, 182 insertions(+)
 create mode 100644 src/core/loader/nsp.cpp
 create mode 100644 src/core/loader/nsp.h

diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
new file mode 100644
index 000000000..75d9fc1bc
--- /dev/null
+++ b/src/core/loader/nsp.cpp
@@ -0,0 +1,128 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <vector>
+
+#include "common/common_types.h"
+#include "core/file_sys/card_image.h"
+#include "core/file_sys/content_archive.h"
+#include "core/file_sys/control_metadata.h"
+#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/romfs.h"
+#include "core/file_sys/submission_package.h"
+#include "core/hle/kernel/process.h"
+#include "core/loader/deconstructed_rom_directory.h"
+#include "core/loader/nca.h"
+#include "core/loader/nsp.h"
+
+namespace Loader {
+
+AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
+    : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
+      title_id(nsp->GetProgramTitleID()) {
+    if (nsp->GetStatus() != ResultStatus::Success)
+        return;
+    if (nsp->IsExtractedType())
+        return;
+    const auto control_nca =
+        nsp->GetNCA(nsp->GetFirstTitleID(), FileSys::ContentRecordType::Control);
+    if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
+        return;
+    const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS());
+    if (romfs == nullptr)
+        return;
+    for (const auto& language : FileSys::LANGUAGE_NAMES) {
+        icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
+        if (icon_file != nullptr)
+            break;
+    }
+    const auto nacp_raw = romfs->GetFile("control.nacp");
+    if (nacp_raw == nullptr)
+        return;
+    nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
+}
+
+AppLoader_NSP::~AppLoader_NSP() = default;
+
+FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
+    FileSys::NSP nsp(file);
+
+    if (nsp.GetStatus() == ResultStatus::Success) {
+        // Extracted Type case
+        if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
+            FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr)
+            return FileType::NSP;
+
+        // Non-Ectracted Type case
+        if (!nsp.IsExtractedType() &&
+            nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr &&
+            AppLoader_NCA::IdentifyType(nsp.GetNCAFile(
+                nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA)
+            return FileType::NSP;
+    }
+
+    return FileType::Error;
+}
+
+ResultStatus AppLoader_NSP::Load(Kernel::SharedPtr<Kernel::Process>& process) {
+    if (is_loaded) {
+        return ResultStatus::ErrorAlreadyLoaded;
+    }
+
+    if (nsp->IsExtractedType()) {
+        secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
+    } else {
+        if (title_id == 0)
+            return ResultStatus::ErrorNSPMissingProgramNCA;
+
+        secondary_loader = std::make_unique<AppLoader_NCA>(
+            nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
+
+        if (nsp->GetStatus() != ResultStatus::Success)
+            return nsp->GetStatus();
+
+        if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
+            return nsp->GetProgramStatus(title_id);
+
+        if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
+            if (!Core::Crypto::KeyManager::KeyFileExists(false))
+                return ResultStatus::ErrorMissingProductionKeyFile;
+            return ResultStatus::ErrorNSPMissingProgramNCA;
+        }
+    }
+
+    const auto result = secondary_loader->Load(process);
+    if (result != ResultStatus::Success)
+        return result;
+
+    is_loaded = true;
+
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) {
+    return secondary_loader->ReadRomFS(dir);
+}
+
+ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {
+    if (title_id == 0)
+        return ResultStatus::ErrorNotInitialized;
+    out_program_id = title_id;
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NSP::ReadIcon(std::vector<u8>& buffer) {
+    if (icon_file == nullptr)
+        return ResultStatus::ErrorNoControl;
+    buffer = icon_file->ReadAllBytes();
+    return ResultStatus::Success;
+}
+
+ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
+    if (nacp_file == nullptr)
+        return ResultStatus::ErrorNoControl;
+    title = nacp_file->GetApplicationName();
+    return ResultStatus::Success;
+}
+} // namespace Loader
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
new file mode 100644
index 000000000..785feaf37
--- /dev/null
+++ b/src/core/loader/nsp.h
@@ -0,0 +1,54 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "common/common_types.h"
+#include "core/file_sys/vfs.h"
+#include "core/loader/loader.h"
+
+namespace FileSys {
+class NACP;
+class NSP;
+} // namespace FileSys
+
+namespace Loader {
+
+class AppLoader_NCA;
+
+/// Loads an XCI file
+class AppLoader_NSP final : public AppLoader {
+public:
+    explicit AppLoader_NSP(FileSys::VirtualFile file);
+    ~AppLoader_NSP();
+
+    /**
+     * Returns the type of the file
+     * @param file std::shared_ptr<VfsFile> open file
+     * @return FileType found, or FileType::Error if this loader doesn't know it
+     */
+    static FileType IdentifyType(const FileSys::VirtualFile& file);
+
+    FileType GetFileType() override {
+        return IdentifyType(file);
+    }
+
+    ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
+
+    ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
+    ResultStatus ReadProgramId(u64& out_program_id) override;
+    ResultStatus ReadIcon(std::vector<u8>& buffer) override;
+    ResultStatus ReadTitle(std::string& title) override;
+
+private:
+    std::unique_ptr<FileSys::NSP> nsp;
+    std::unique_ptr<AppLoader> secondary_loader;
+
+    FileSys::VirtualFile icon_file;
+    std::shared_ptr<FileSys::NACP> nacp_file;
+    u64 title_id;
+};
+
+} // namespace Loader

From f7eaea424d07e971d0279257d20d408b64ef05b6 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:50:04 -0400
Subject: [PATCH 07/14] registration: Add support for installing NSP files

---
 src/core/file_sys/registered_cache.cpp | 20 ++++++++++++--------
 src/core/file_sys/registered_cache.h   |  6 ++++--
 src/yuzu/main.cpp                      | 24 ++++++++++++++++++------
 3 files changed, 34 insertions(+), 16 deletions(-)

diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index d9decc104..94268d127 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -358,17 +358,21 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
     return out;
 }
 
-static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) {
-    const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false));
-    const auto iter =
-        std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(),
-                     [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; });
-    return iter == xci->GetNCAs().end() ? nullptr : *iter;
+static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) {
+    const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
+    if (file == nullptr)
+        return nullptr;
+    return std::make_shared<NCA>(file);
 }
 
 InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
                                             const VfsCopyFunction& copy) {
-    const auto& ncas = xci->GetNCAs();
+    return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy);
+}
+
+InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists,
+                                            const VfsCopyFunction& copy) {
+    const auto& ncas = nsp->GetNCAsCollapsed();
     const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
         return nca->GetType() == NCAContentType::Meta;
     });
@@ -392,7 +396,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overw
     const auto cnmt_file = section0->GetFiles()[0];
     const CNMT cnmt(cnmt_file);
     for (const auto& record : cnmt.GetContentRecords()) {
-        const auto nca = GetNCAFromXCIForID(xci, record.nca_id);
+        const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
         if (nca == nullptr)
             return InstallResult::ErrorCopyFailed;
         const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index fe2cdc3d9..50e26f8fb 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -89,10 +89,12 @@ public:
         boost::optional<ContentRecordType> record_type = boost::none,
         boost::optional<u64> title_id = boost::none) const;
 
-    // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
-    // is a meta NCA and all of them are accessible.
+    // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
+    // there is a meta NCA and all of them are accessible.
     InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
                                const VfsCopyFunction& copy = &VfsRawCopy);
+    InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false,
+                               const VfsCopyFunction& copy = &VfsRawCopy);
 
     // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
     // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 262e33487..c4eda4bab 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -806,22 +806,34 @@ void GMainWindow::OnMenuInstallToNAND() {
                QMessageBox::Yes;
     };
 
-    if (filename.endsWith("xci", Qt::CaseInsensitive)) {
-        const auto xci = std::make_shared<FileSys::XCI>(
-            vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
-        if (xci->GetStatus() != Loader::ResultStatus::Success) {
+    if (filename.endsWith("xci", Qt::CaseInsensitive) ||
+        filename.endsWith("nsp", Qt::CaseInsensitive)) {
+
+        std::shared_ptr<FileSys::NSP> nsp;
+        if (filename.endsWith("nsp", Qt::CaseInsensitive)) {
+            nsp = std::make_shared<FileSys::NSP>(
+                vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+            if (!nsp->IsExtractedType())
+                failed();
+        } else {
+            const auto xci = std::make_shared<FileSys::XCI>(
+                vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
+            nsp = xci->GetSecurePartitionNSP();
+        }
+
+        if (nsp->GetStatus() != Loader::ResultStatus::Success) {
             failed();
             return;
         }
         const auto res =
-            Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy);
+            Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy);
         if (res == FileSys::InstallResult::Success) {
             success();
         } else {
             if (res == FileSys::InstallResult::ErrorAlreadyExists) {
                 if (overwrite()) {
                     const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
-                        xci, true, qt_raw_copy);
+                        nsp, true, qt_raw_copy);
                     if (res2 == FileSys::InstallResult::Success) {
                         success();
                     } else {

From 58473309a08979d657dc09d5594833791e5c920c Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 11:50:15 -0400
Subject: [PATCH 08/14] qt: Add UI support for NSP files

---
 src/core/CMakeLists.txt | 4 ++++
 src/yuzu/game_list.cpp  | 2 +-
 src/yuzu/main.cpp       | 3 ++-
 3 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index a74270a0f..54afa6a87 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -49,6 +49,8 @@ add_library(core STATIC
     file_sys/savedata_factory.h
     file_sys/sdmc_factory.cpp
     file_sys/sdmc_factory.h
+    file_sys/submission_package.cpp
+    file_sys/submission_package.h
     file_sys/vfs.cpp
     file_sys/vfs.h
     file_sys/vfs_concat.cpp
@@ -359,6 +361,8 @@ add_library(core STATIC
     loader/nro.h
     loader/nso.cpp
     loader/nso.h
+    loader/nsp.cpp
+    loader/nsp.h
     loader/xci.cpp
     loader/xci.h
     memory.cpp
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 71953cee3..3e2a5976b 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -432,7 +432,7 @@ void GameList::LoadInterfaceLayout() {
     item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
 }
 
-const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"};
+const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
 
 static bool HasSupportedFileExtension(const std::string& file_name) {
     const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index c4eda4bab..e7722cf95 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -746,7 +746,8 @@ void GMainWindow::OnMenuLoadFolder() {
 
 void GMainWindow::OnMenuInstallToNAND() {
     const QString file_filter =
-        tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge "
+        tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive "
+           "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge "
            "Image (*.xci)");
     QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
                                                     UISettings::values.roms_path, file_filter);

From e4e55d064edd71fbf359dec9d6b5efad4f0d6c91 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Sat, 25 Aug 2018 22:42:54 -0400
Subject: [PATCH 09/14] nsp: Comply with style and performance guidelines

---
 src/core/crypto/key_manager.cpp          |  2 +-
 src/core/file_sys/card_image.cpp         |  1 +
 src/core/file_sys/card_image.h           |  2 +-
 src/core/file_sys/submission_package.cpp | 57 ++++++++++++++----------
 src/core/file_sys/submission_package.h   |  2 +
 src/core/loader/nsp.cpp                  | 11 ++++-
 src/core/loader/nsp.h                    |  2 +-
 7 files changed, 48 insertions(+), 29 deletions(-)

diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index bd4b3d7c7..6f27f990b 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -237,7 +237,7 @@ void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
         Key128 rights_id;
         std::memcpy(rights_id.data(), &field2, sizeof(u64));
         std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
-        WriteKeyToFile(true, fmt::format("{}", Common::HexArrayToString(rights_id)), key);
+        WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
     }
     const auto iter2 = std::find_if(
         s128_file_id.begin(), s128_file_id.end(),
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index d0f1afac0..e07ac8503 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -12,6 +12,7 @@
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/partition_filesystem.h"
+#include "core/file_sys/submission_package.h"
 #include "core/file_sys/vfs_offset.h"
 #include "core/loader/loader.h"
 
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index b73f1d900..4d07d3d05 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -11,7 +11,6 @@
 #include "common/swap.h"
 #include "core/file_sys/vfs.h"
 #include "core/loader/loader.h"
-#include "submission_package.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
@@ -21,6 +20,7 @@ namespace FileSys {
 
 class NCA;
 enum class NCAContentType : u8;
+class NSP;
 
 enum class GamecardSize : u8 {
     S_1GB = 0xFA,
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index 660771cf8..ce05a5845 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -72,21 +72,21 @@ NSP::NSP(VirtualFile file_)
 
                 ncas_title[ContentRecordType::Meta] = nca;
                 for (const auto& rec : cnmt.GetContentRecords()) {
-                    const auto next_file = pfs->GetFile(
-                        fmt::format("{}.nca", Common::HexArrayToString(rec.nca_id, false)));
+                    const auto id_string = Common::HexArrayToString(rec.nca_id, false);
+                    const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
                     if (next_file == nullptr) {
                         LOG_WARNING(Service_FS,
                                     "NCA with ID {}.nca is listed in content metadata, but cannot "
                                     "be found in PFS. NSP appears to be corrupted.",
-                                    Common::HexArrayToString(rec.nca_id, false));
+                                    id_string);
                         continue;
                     }
 
-                    const auto next_nca = std::make_shared<NCA>(next_file);
+                    auto next_nca = std::make_shared<NCA>(next_file);
                     if (next_nca->GetType() == NCAContentType::Program)
                         program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
                     if (next_nca->GetStatus() == Loader::ResultStatus::Success)
-                        ncas_title[rec.type] = next_nca;
+                        ncas_title[rec.type] = std::move(next_nca);
                 }
 
                 break;
@@ -95,14 +95,17 @@ NSP::NSP(VirtualFile file_)
     }
 }
 
+NSP::~NSP() = default;
+
 Loader::ResultStatus NSP::GetStatus() const {
     return status;
 }
 
 Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
-    if (program_status.find(title_id) != program_status.end())
-        return program_status.at(title_id);
-    return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
+    const auto iter = program_status.find(title_id);
+    if (iter == program_status.end())
+        return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
+    return iter->second;
 }
 
 u64 NSP::GetFirstTitleID() const {
@@ -112,16 +115,19 @@ u64 NSP::GetFirstTitleID() const {
 }
 
 u64 NSP::GetProgramTitleID() const {
-    auto out = GetFirstTitleID();
-    for (const auto other_tid : GetTitleIDs()) {
-        if ((out & 0x800) != 0)
-            out = other_tid;
-    }
-    return out;
+    const auto out = GetFirstTitleID();
+    if ((out & 0x800) == 0)
+        return out;
+
+    const auto ids = GetTitleIDs();
+    const auto iter =
+        std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; });
+    return iter == ids.end() ? out : *iter;
 }
 
 std::vector<u64> NSP::GetTitleIDs() const {
     std::vector<u64> out;
+    out.reserve(ncas.size());
     for (const auto& kv : ncas)
         out.push_back(kv.first);
     return out;
@@ -156,7 +162,7 @@ std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
     std::multimap<u64, std::shared_ptr<NCA>> out;
     for (const auto& map : ncas) {
         for (const auto& inner_map : map.second)
-            out.insert({map.first, inner_map.second});
+            out.emplace(map.first, inner_map.second);
     }
     return out;
 }
@@ -168,13 +174,16 @@ std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs()
 std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
     if (extracted)
         LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
-    if (ncas.find(title_id) != ncas.end()) {
-        const auto& inner_map = ncas.at(title_id);
-        if (inner_map.find(type) != inner_map.end())
-            return inner_map.at(type);
-    }
 
-    return nullptr;
+    const auto title_id_iter = ncas.find(title_id);
+    if (title_id_iter == ncas.end())
+        return nullptr;
+
+    const auto type_iter = title_id_iter->second.find(type);
+    if (type_iter == title_id_iter->second.end())
+        return nullptr;
+
+    return type_iter->second;
 }
 
 VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
@@ -197,9 +206,9 @@ std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
             continue;
         }
 
-        Core::Crypto::Key128 key{};
-        ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
-        out.push_back(key);
+        out.emplace_back();
+        ticket_file->Read(out.back().data(), out.back().size(),
+                          Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
     }
     return out;
 }
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 7b520df57..482a8b71f 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -10,6 +10,7 @@
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/file_sys/content_archive.h"
+#include "core/file_sys/romfs_factory.h"
 #include "core/file_sys/vfs.h"
 #include "core/loader/loader.h"
 #include "romfs_factory.h"
@@ -19,6 +20,7 @@ namespace FileSys {
 class NSP : public ReadOnlyVfsDirectory {
 public:
     explicit NSP(VirtualFile file);
+    ~NSP();
 
     Loader::ResultStatus GetStatus() const;
     Loader::ResultStatus GetProgramStatus(u64 title_id) const;
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 75d9fc1bc..b59d40052 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -21,22 +21,27 @@ namespace Loader {
 AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
     : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
       title_id(nsp->GetProgramTitleID()) {
+
     if (nsp->GetStatus() != ResultStatus::Success)
         return;
     if (nsp->IsExtractedType())
         return;
+
     const auto control_nca =
         nsp->GetNCA(nsp->GetFirstTitleID(), FileSys::ContentRecordType::Control);
     if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
         return;
+
     const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS());
     if (romfs == nullptr)
         return;
+
     for (const auto& language : FileSys::LANGUAGE_NAMES) {
         icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
         if (icon_file != nullptr)
             break;
     }
+
     const auto nacp_raw = romfs->GetFile("control.nacp");
     if (nacp_raw == nullptr)
         return;
@@ -51,15 +56,17 @@ FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
     if (nsp.GetStatus() == ResultStatus::Success) {
         // Extracted Type case
         if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
-            FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr)
+            FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) {
             return FileType::NSP;
+        }
 
         // Non-Ectracted Type case
         if (!nsp.IsExtractedType() &&
             nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr &&
             AppLoader_NCA::IdentifyType(nsp.GetNCAFile(
-                nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA)
+                nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA) {
             return FileType::NSP;
+        }
     }
 
     return FileType::Error;
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
index 785feaf37..7ef810499 100644
--- a/src/core/loader/nsp.h
+++ b/src/core/loader/nsp.h
@@ -22,7 +22,7 @@ class AppLoader_NCA;
 class AppLoader_NSP final : public AppLoader {
 public:
     explicit AppLoader_NSP(FileSys::VirtualFile file);
-    ~AppLoader_NSP();
+    ~AppLoader_NSP() override;
 
     /**
      * Returns the type of the file

From 8974771334aceaaba0912887dacfd3eb1eb0bee6 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Thu, 30 Aug 2018 16:59:30 -0400
Subject: [PATCH 10/14] registration: Fix NSP installation errors

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

diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e7722cf95..d7c5d813f 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -814,7 +814,7 @@ void GMainWindow::OnMenuInstallToNAND() {
         if (filename.endsWith("nsp", Qt::CaseInsensitive)) {
             nsp = std::make_shared<FileSys::NSP>(
                 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
-            if (!nsp->IsExtractedType())
+            if (nsp->IsExtractedType())
                 failed();
         } else {
             const auto xci = std::make_shared<FileSys::XCI>(

From 128006172578390d7c83575f591dbd8df9361e84 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Thu, 30 Aug 2018 16:59:49 -0400
Subject: [PATCH 11/14] qt: Add deprecation warnings for DRD format

---
 src/yuzu/main.cpp | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index d7c5d813f..037bf2aef 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -488,6 +488,16 @@ bool GMainWindow::LoadROM(const QString& filename) {
 
     const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
 
+    if (system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory) {
+        QMessageBox::warning(
+            this, tr("Warning Outdated Game Format"),
+            tr("You are using the deconstructed ROM directory format for this game, which is an "
+               "outdated format that has been superseded by others such as NCA, NAX, XCI, or "
+               "NSP.<br><br>For an explanation of the various Switch formats yuzu supports, <a "
+               "href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our "
+               "wiki</a>."));
+    }
+
     render_window->DoneCurrent();
 
     if (result != Core::System::ResultStatus::Success) {

From 23d2c504795a3efadfa046cfe4b5faf95649f454 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 3 Sep 2018 18:47:23 -0400
Subject: [PATCH 12/14] card_image: Add program title ID getter

---
 src/core/file_sys/card_image.cpp | 4 ++++
 src/core/file_sys/card_image.h   | 2 ++
 2 files changed, 6 insertions(+)

diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index e07ac8503..1bd3353e4 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -115,6 +115,10 @@ VirtualDir XCI::GetLogoPartition() const {
     return GetPartition(XCIPartition::Logo);
 }
 
+u64 XCI::GetProgramTitleID() const {
+    return secure_partition->GetProgramTitleID();
+}
+
 std::shared_ptr<NCA> XCI::GetProgramNCA() const {
     return program;
 }
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 4d07d3d05..bd8c0fcbf 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -79,6 +79,8 @@ public:
     VirtualDir GetUpdatePartition() const;
     VirtualDir GetLogoPartition() const;
 
+    u64 GetProgramTitleID() const;
+
     std::shared_ptr<NCA> GetProgramNCA() const;
     VirtualFile GetProgramNCAFile() const;
     const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;

From e973cceaddf060b3738417504856db1baa4a04fa Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Mon, 3 Sep 2018 19:00:14 -0400
Subject: [PATCH 13/14] control_metadata: Use alternate language names if
 AmericanEnglish isn't available

---
 src/core/file_sys/control_metadata.cpp | 12 +++++++++++-
 src/core/file_sys/control_metadata.h   |  9 ++++++---
 2 files changed, 17 insertions(+), 4 deletions(-)

diff --git a/src/core/file_sys/control_metadata.cpp b/src/core/file_sys/control_metadata.cpp
index ae21ad5b9..e76bf77bf 100644
--- a/src/core/file_sys/control_metadata.cpp
+++ b/src/core/file_sys/control_metadata.cpp
@@ -21,7 +21,17 @@ NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
 }
 
 const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
-    return raw->language_entries.at(static_cast<u8>(language));
+    if (language != Language::Default) {
+        return raw->language_entries.at(static_cast<u8>(language));
+    } else {
+        for (const auto& language_entry : raw->language_entries) {
+            if (!language_entry.GetApplicationName().empty())
+                return language_entry;
+        }
+
+        // Fallback to English
+        return GetLanguageEntry(Language::AmericanEnglish);
+    }
 }
 
 std::string NACP::GetApplicationName(Language language) const {
diff --git a/src/core/file_sys/control_metadata.h b/src/core/file_sys/control_metadata.h
index 1568046f1..8a510bf46 100644
--- a/src/core/file_sys/control_metadata.h
+++ b/src/core/file_sys/control_metadata.h
@@ -9,6 +9,7 @@
 #include <string>
 #include "common/common_funcs.h"
 #include "common/common_types.h"
+#include "common/swap.h"
 #include "core/file_sys/vfs.h"
 
 namespace FileSys {
@@ -61,6 +62,8 @@ enum class Language : u8 {
     Korean = 12,
     Taiwanese = 13,
     Chinese = 14,
+
+    Default = 255,
 };
 
 static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
@@ -75,9 +78,9 @@ static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
 class NACP {
 public:
     explicit NACP(VirtualFile file);
-    const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const;
-    std::string GetApplicationName(Language language = Language::AmericanEnglish) const;
-    std::string GetDeveloperName(Language language = Language::AmericanEnglish) const;
+    const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const;
+    std::string GetApplicationName(Language language = Language::Default) const;
+    std::string GetDeveloperName(Language language = Language::Default) const;
     u64 GetTitleId() const;
     std::string GetVersionString() const;
 

From 87be4bc283eee72a51b5e8391147c60671351b80 Mon Sep 17 00:00:00 2001
From: Zach Hilman <zachhilman@gmail.com>
Date: Tue, 4 Sep 2018 14:44:40 -0400
Subject: [PATCH 14/14] main: Only show DRD deprecation warning once

---
 src/core/file_sys/card_image.h           |  1 -
 src/core/file_sys/registered_cache.cpp   |  1 +
 src/core/file_sys/registered_cache.h     |  1 +
 src/core/file_sys/submission_package.cpp |  1 +
 src/core/file_sys/submission_package.h   |  3 ++-
 src/core/loader/nsp.cpp                  |  2 +-
 src/yuzu/main.cpp                        | 16 +++++++++++++---
 7 files changed, 19 insertions(+), 6 deletions(-)

diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index bd8c0fcbf..ce514dfa0 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -10,7 +10,6 @@
 #include "common/common_types.h"
 #include "common/swap.h"
 #include "core/file_sys/vfs.h"
-#include "core/loader/loader.h"
 
 namespace Loader {
 enum class ResultStatus : u16;
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 94268d127..cf6f77401 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -13,6 +13,7 @@
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/registered_cache.h"
+#include "core/file_sys/submission_package.h"
 #include "core/file_sys/vfs_concat.h"
 #include "core/loader/loader.h"
 
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 50e26f8fb..467ceeef1 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -17,6 +17,7 @@
 namespace FileSys {
 class CNMT;
 class NCA;
+class NSP;
 class XCI;
 
 enum class ContentRecordType : u8;
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index ce05a5845..bde879861 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -7,6 +7,7 @@
 #include "common/hex_util.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/nca_metadata.h"
+#include "core/file_sys/partition_filesystem.h"
 #include "core/file_sys/submission_package.h"
 #include "core/loader/loader.h"
 
diff --git a/src/core/file_sys/submission_package.h b/src/core/file_sys/submission_package.h
index 482a8b71f..0292164f9 100644
--- a/src/core/file_sys/submission_package.h
+++ b/src/core/file_sys/submission_package.h
@@ -13,10 +13,11 @@
 #include "core/file_sys/romfs_factory.h"
 #include "core/file_sys/vfs.h"
 #include "core/loader/loader.h"
-#include "romfs_factory.h"
 
 namespace FileSys {
 
+class PartitionFilesystem;
+
 class NSP : public ReadOnlyVfsDirectory {
 public:
     explicit NSP(VirtualFile file);
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index b59d40052..7c06239f2 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -8,7 +8,7 @@
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/control_metadata.h"
-#include "core/file_sys/registered_cache.h"
+#include "core/file_sys/nca_metadata.h"
 #include "core/file_sys/romfs.h"
 #include "core/file_sys/submission_package.h"
 #include "core/hle/kernel/process.h"
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 037bf2aef..56bd3ee2e 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -34,7 +34,9 @@
 #include "core/file_sys/content_archive.h"
 #include "core/file_sys/registered_cache.h"
 #include "core/file_sys/savedata_factory.h"
+#include "core/file_sys/submission_package.h"
 #include "core/file_sys/vfs_real.h"
+#include "core/hle/kernel/process.h"
 #include "core/hle/service/filesystem/filesystem.h"
 #include "core/loader/loader.h"
 #include "core/perf_stats.h"
@@ -76,6 +78,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
  */
 enum class CalloutFlag : uint32_t {
     Telemetry = 0x1,
+    DRDDeprecation = 0x2,
 };
 
 static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
@@ -488,14 +491,21 @@ bool GMainWindow::LoadROM(const QString& filename) {
 
     const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
 
-    if (system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory) {
+    const auto drd_callout =
+        (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0;
+
+    if (result == Core::System::ResultStatus::Success &&
+        system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory &&
+        drd_callout) {
+        UISettings::values.callout_flags |= static_cast<u32>(CalloutFlag::DRDDeprecation);
         QMessageBox::warning(
             this, tr("Warning Outdated Game Format"),
             tr("You are using the deconstructed ROM directory format for this game, which is an "
                "outdated format that has been superseded by others such as NCA, NAX, XCI, or "
-               "NSP.<br><br>For an explanation of the various Switch formats yuzu supports, <a "
+               "NSP. Deconstructed ROM directories lack icons, metadata, and update "
+               "support.<br><br>For an explanation of the various Switch formats yuzu supports, <a "
                "href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our "
-               "wiki</a>."));
+               "wiki</a>. This message will not be shown again."));
     }
 
     render_window->DoneCurrent();