From ae4ba287d5e2b56accfaa490616572e44245a14a Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Sun, 15 Mar 2020 17:54:13 +0100
Subject: [PATCH 1/6] warn if cia contend is encrypted

---
 src/citra_qt/game_list_worker.cpp    |  8 ++++--
 src/core/file_sys/ncch_container.cpp | 33 ++++++++++++++++++++--
 src/core/file_sys/ncch_container.h   |  6 ++++
 src/core/hle/service/am/am.cpp       | 41 ++++++++++++++++++++++++++++
 4 files changed, 84 insertions(+), 4 deletions(-)

diff --git a/src/citra_qt/game_list_worker.cpp b/src/citra_qt/game_list_worker.cpp
index a20ae4794..de02e79c0 100644
--- a/src/citra_qt/game_list_worker.cpp
+++ b/src/citra_qt/game_list_worker.cpp
@@ -47,12 +47,16 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
         if (!is_dir && HasSupportedFileExtension(physical_name)) {
             std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
             if (!loader)
+            {
                 return true;
+            }
 
             bool executable = false;
-            loader->IsExecutable(executable);
-            if (!executable)
+            auto res  = loader->IsExecutable(executable);
+            if (!executable && res != Loader::ResultStatus::ErrorEncrypted)
+            {
                 return true;
+            }
 
             u64 program_id = 0;
             loader->ReadProgramId(program_id);
diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp
index 056f7a901..651b0d307 100644
--- a/src/core/file_sys/ncch_container.cpp
+++ b/src/core/file_sys/ncch_container.cpp
@@ -133,8 +133,37 @@ Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 nc
     return Loader::ResultStatus::Success;
 }
 
+Loader::ResultStatus NCCHContainer::LoadHeader() {
+    if (has_header)
+        return Loader::ResultStatus::Success;
+    if (!file.IsOpen()) {
+
+        return Loader::ResultStatus::Error;
+    }
+
+    // Reset read pointer in case this file has been read before.
+    file.Seek(ncch_offset, SEEK_SET);
+
+    if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header))
+        return Loader::ResultStatus::Error;
+
+    // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
+    if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
+        LOG_DEBUG(Service_FS, "Only loading the first (bootable) NCCH within the NCSD file!");
+        ncch_offset += 0x4000;
+        file.Seek(ncch_offset, SEEK_SET);
+        file.ReadBytes(&ncch_header, sizeof(NCCH_Header));
+    }
+
+    // Verify we are loading the correct file type...
+    if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic)
+        return Loader::ResultStatus::ErrorInvalidFormat;
+
+    has_header = true;
+    return Loader::ResultStatus::Success;
+}
+
 Loader::ResultStatus NCCHContainer::Load() {
-    LOG_INFO(Service_FS, "Loading NCCH from file {}", filepath);
     if (is_loaded)
         return Loader::ResultStatus::Success;
 
@@ -697,7 +726,7 @@ Loader::ResultStatus NCCHContainer::ReadOverrideRomFS(std::shared_ptr<RomFSReade
 }
 
 Loader::ResultStatus NCCHContainer::ReadProgramId(u64_le& program_id) {
-    Loader::ResultStatus result = Load();
+    Loader::ResultStatus result = LoadHeader();
     if (result != Loader::ResultStatus::Success)
         return result;
 
diff --git a/src/core/file_sys/ncch_container.h b/src/core/file_sys/ncch_container.h
index 7eadd9835..fa435cee0 100644
--- a/src/core/file_sys/ncch_container.h
+++ b/src/core/file_sys/ncch_container.h
@@ -210,6 +210,12 @@ public:
 
     Loader::ResultStatus OpenFile(const std::string& filepath, u32 ncch_offset = 0);
 
+    /**
+     * Ensure NCCH header is loaded and ready for reading sections
+     * @return ResultStatus result of function
+     */
+    Loader::ResultStatus LoadHeader();
+
     /**
      * Ensure ExeFS and exheader is loaded and ready for reading sections
      * @return ResultStatus result of function
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 65b9c3e01..79ae93130 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -11,6 +11,7 @@
 #include <fmt/format.h>
 #include "common/file_util.h"
 #include "common/logging/log.h"
+#include "common/common_paths.h"
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/errors.h"
@@ -33,6 +34,16 @@
 #include "core/loader/loader.h"
 #include "core/loader/smdh.h"
 
+namespace {
+bool HasSupportedFileExtension(std::string path) {
+    static const std::array<std::string, 7> extensions = {{".3ds", ".3dsx", ".elf", ".axf",
+    ".cci", ".cxi" ".app"
+    }};
+    const auto file_ext = FileUtil::GetExtensionFromFilename(path);
+    return std::find(extensions.begin(), extensions.end(), file_ext) != extensions.end();
+}
+}
+
 namespace Service::AM {
 
 constexpr u16 PLATFORM_CTR = 0x0004;
@@ -373,6 +384,36 @@ InstallStatus InstallCIA(const std::string& path,
         installFile.Close();
 
         LOG_INFO(Service_AM, "Installed {} successfully.", path);
+
+        const FileUtil::DirectoryEntryCallable callback = [&callback](u64* num_entries_out,
+                                                        const std::string& directory,
+                                                        const std::string& virtual_name) -> bool {
+            const std::string physical_name = directory + DIR_SEP + virtual_name;
+            const bool is_dir = FileUtil::IsDirectory(physical_name);
+            if (!is_dir && HasSupportedFileExtension(physical_name)) {
+                std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
+                if (!loader)
+                {
+                return true;
+                }
+
+                bool executable = false;
+                auto res  = loader->IsExecutable(executable);
+                if (res == Loader::ResultStatus::ErrorEncrypted)
+                {
+                    return false;
+                }
+                return true;
+            } else {
+                return FileUtil::ForeachDirectoryEntry(nullptr, physical_name, callback);
+            }
+
+        };
+        if (!FileUtil::ForeachDirectoryEntry(nullptr, path, callback))
+        {
+            LOG_ERROR(Service_AM, "CIA {} contained encrypted files.", path);
+            return InstallStatus::ErrorEncrypted;
+        }
         return InstallStatus::Success;
     }
 

From 391580c658ad3060a80186f6562f4183c23ebe5b Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Sun, 15 Mar 2020 18:42:53 +0100
Subject: [PATCH 2/6] fix clang-format

---
 src/citra_qt/game_list_worker.cpp |  8 +++-----
 src/core/hle/service/am/am.cpp    | 30 +++++++++++++-----------------
 2 files changed, 16 insertions(+), 22 deletions(-)

diff --git a/src/citra_qt/game_list_worker.cpp b/src/citra_qt/game_list_worker.cpp
index de02e79c0..c00825b9e 100644
--- a/src/citra_qt/game_list_worker.cpp
+++ b/src/citra_qt/game_list_worker.cpp
@@ -46,15 +46,13 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
         const bool is_dir = FileUtil::IsDirectory(physical_name);
         if (!is_dir && HasSupportedFileExtension(physical_name)) {
             std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
-            if (!loader)
-            {
+            if (!loader) {
                 return true;
             }
 
             bool executable = false;
-            auto res  = loader->IsExecutable(executable);
-            if (!executable && res != Loader::ResultStatus::ErrorEncrypted)
-            {
+            auto res = loader->IsExecutable(executable);
+            if (!executable && res != Loader::ResultStatus::ErrorEncrypted) {
                 return true;
             }
 
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 79ae93130..4c1fdc33d 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -9,9 +9,9 @@
 #include <cryptopp/aes.h>
 #include <cryptopp/modes.h>
 #include <fmt/format.h>
+#include "common/common_paths.h"
 #include "common/file_util.h"
 #include "common/logging/log.h"
-#include "common/common_paths.h"
 #include "common/string_util.h"
 #include "core/core.h"
 #include "core/file_sys/errors.h"
@@ -36,13 +36,13 @@
 
 namespace {
 bool HasSupportedFileExtension(std::string path) {
-    static const std::array<std::string, 7> extensions = {{".3ds", ".3dsx", ".elf", ".axf",
-    ".cci", ".cxi" ".app"
-    }};
+    static const std::array<std::string, 7> extensions = {{".3ds", ".3dsx", ".elf", ".axf", ".cci",
+                                                           ".cxi"
+                                                           ".app"}};
     const auto file_ext = FileUtil::GetExtensionFromFilename(path);
     return std::find(extensions.begin(), extensions.end(), file_ext) != extensions.end();
 }
-}
+} // namespace
 
 namespace Service::AM {
 
@@ -385,32 +385,28 @@ InstallStatus InstallCIA(const std::string& path,
 
         LOG_INFO(Service_AM, "Installed {} successfully.", path);
 
-        const FileUtil::DirectoryEntryCallable callback = [&callback](u64* num_entries_out,
-                                                        const std::string& directory,
-                                                        const std::string& virtual_name) -> bool {
+        const FileUtil::DirectoryEntryCallable callback =
+            [&callback](u64* num_entries_out, const std::string& directory,
+                        const std::string& virtual_name) -> bool {
             const std::string physical_name = directory + DIR_SEP + virtual_name;
             const bool is_dir = FileUtil::IsDirectory(physical_name);
             if (!is_dir && HasSupportedFileExtension(physical_name)) {
                 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
-                if (!loader)
-                {
-                return true;
+                if (!loader) {
+                    return true;
                 }
 
                 bool executable = false;
-                auto res  = loader->IsExecutable(executable);
-                if (res == Loader::ResultStatus::ErrorEncrypted)
-                {
+                auto res = loader->IsExecutable(executable);
+                if (res == Loader::ResultStatus::ErrorEncrypted) {
                     return false;
                 }
                 return true;
             } else {
                 return FileUtil::ForeachDirectoryEntry(nullptr, physical_name, callback);
             }
-
         };
-        if (!FileUtil::ForeachDirectoryEntry(nullptr, path, callback))
-        {
+        if (!FileUtil::ForeachDirectoryEntry(nullptr, path, callback)) {
             LOG_ERROR(Service_AM, "CIA {} contained encrypted files.", path);
             return InstallStatus::ErrorEncrypted;
         }

From 2465fd423a405da85dd02235efa90d8f3b96be67 Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Sun, 15 Mar 2020 18:57:29 +0100
Subject: [PATCH 3/6] fix review comments

---
 src/core/file_sys/ncch_container.cpp | 1 -
 src/core/hle/service/am/am.cpp       | 5 ++---
 2 files changed, 2 insertions(+), 4 deletions(-)

diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp
index 651b0d307..bb972c431 100644
--- a/src/core/file_sys/ncch_container.cpp
+++ b/src/core/file_sys/ncch_container.cpp
@@ -137,7 +137,6 @@ Loader::ResultStatus NCCHContainer::LoadHeader() {
     if (has_header)
         return Loader::ResultStatus::Success;
     if (!file.IsOpen()) {
-
         return Loader::ResultStatus::Error;
     }
 
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 4c1fdc33d..f3d7ea985 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -36,9 +36,8 @@
 
 namespace {
 bool HasSupportedFileExtension(std::string path) {
-    static const std::array<std::string, 7> extensions = {{".3ds", ".3dsx", ".elf", ".axf", ".cci",
-                                                           ".cxi"
-                                                           ".app"}};
+    static const std::array<std::string, 7> extensions = {
+        {".3ds", ".3dsx", ".elf", ".axf", ".cci", ".cxi", ".app"}};
     const auto file_ext = FileUtil::GetExtensionFromFilename(path);
     return std::find(extensions.begin(), extensions.end(), file_ext) != extensions.end();
 }

From 1d92343344d1e5696631863f26fd4c98c9a0c92f Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Sun, 15 Mar 2020 19:00:26 +0100
Subject: [PATCH 4/6] fix more review comments

---
 src/citra_qt/game_list_worker.cpp    | 2 +-
 src/core/file_sys/ncch_container.cpp | 9 ++++++---
 src/core/hle/service/am/am.cpp       | 2 +-
 3 files changed, 8 insertions(+), 5 deletions(-)

diff --git a/src/citra_qt/game_list_worker.cpp b/src/citra_qt/game_list_worker.cpp
index c00825b9e..f03244637 100644
--- a/src/citra_qt/game_list_worker.cpp
+++ b/src/citra_qt/game_list_worker.cpp
@@ -51,7 +51,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
             }
 
             bool executable = false;
-            auto res = loader->IsExecutable(executable);
+            const auto res = loader->IsExecutable(executable);
             if (!executable && res != Loader::ResultStatus::ErrorEncrypted) {
                 return true;
             }
diff --git a/src/core/file_sys/ncch_container.cpp b/src/core/file_sys/ncch_container.cpp
index bb972c431..cb8a2d513 100644
--- a/src/core/file_sys/ncch_container.cpp
+++ b/src/core/file_sys/ncch_container.cpp
@@ -134,8 +134,9 @@ Loader::ResultStatus NCCHContainer::OpenFile(const std::string& filepath, u32 nc
 }
 
 Loader::ResultStatus NCCHContainer::LoadHeader() {
-    if (has_header)
+    if (has_header) {
         return Loader::ResultStatus::Success;
+    }
     if (!file.IsOpen()) {
         return Loader::ResultStatus::Error;
     }
@@ -143,8 +144,9 @@ Loader::ResultStatus NCCHContainer::LoadHeader() {
     // Reset read pointer in case this file has been read before.
     file.Seek(ncch_offset, SEEK_SET);
 
-    if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header))
+    if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) {
         return Loader::ResultStatus::Error;
+    }
 
     // Skip NCSD header and load first NCCH (NCSD is just a container of NCCH files)...
     if (Loader::MakeMagic('N', 'C', 'S', 'D') == ncch_header.magic) {
@@ -155,8 +157,9 @@ Loader::ResultStatus NCCHContainer::LoadHeader() {
     }
 
     // Verify we are loading the correct file type...
-    if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic)
+    if (Loader::MakeMagic('N', 'C', 'C', 'H') != ncch_header.magic) {
         return Loader::ResultStatus::ErrorInvalidFormat;
+    }
 
     has_header = true;
     return Loader::ResultStatus::Success;
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index f3d7ea985..44bdfc8a2 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -396,7 +396,7 @@ InstallStatus InstallCIA(const std::string& path,
                 }
 
                 bool executable = false;
-                auto res = loader->IsExecutable(executable);
+                const auto res = loader->IsExecutable(executable);
                 if (res == Loader::ResultStatus::ErrorEncrypted) {
                     return false;
                 }

From a4457d871c1035975d45c59ade64ba7fbc27c447 Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Sun, 15 Mar 2020 20:58:43 +0100
Subject: [PATCH 5/6] Log if common key during Ticket::Load is missing

---
 src/core/file_sys/ticket.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/core/file_sys/ticket.cpp b/src/core/file_sys/ticket.cpp
index 4a62b297b..2b6926adc 100644
--- a/src/core/file_sys/ticket.cpp
+++ b/src/core/file_sys/ticket.cpp
@@ -47,6 +47,7 @@ std::optional<std::array<u8, 16>> Ticket::GetTitleKey() const {
     std::memcpy(ctr.data(), &ticket_body.title_id, sizeof(u64));
     HW::AES::SelectCommonKeyIndex(ticket_body.common_key_index);
     if (!HW::AES::IsNormalKeyAvailable(HW::AES::KeySlotID::TicketCommonKey)) {
+        LOG_ERROR(Service_FS, "CommonKey {} missing", ticket_body.common_key_index);
         return {};
     }
     auto key = HW::AES::GetNormalKey(HW::AES::KeySlotID::TicketCommonKey);

From ed51f0609773687007b66c08dd0dc598dc3db1c6 Mon Sep 17 00:00:00 2001
From: B3n30 <benediktthomas@gmail.com>
Date: Tue, 17 Mar 2020 13:40:58 +0100
Subject: [PATCH 6/6] fix path usage in InstallCia

---
 src/core/hle/service/am/am.cpp | 18 +++++++-----------
 1 file changed, 7 insertions(+), 11 deletions(-)

diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 44bdfc8a2..31df828e3 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -34,15 +34,6 @@
 #include "core/loader/loader.h"
 #include "core/loader/smdh.h"
 
-namespace {
-bool HasSupportedFileExtension(std::string path) {
-    static const std::array<std::string, 7> extensions = {
-        {".3ds", ".3dsx", ".elf", ".axf", ".cci", ".cxi", ".app"}};
-    const auto file_ext = FileUtil::GetExtensionFromFilename(path);
-    return std::find(extensions.begin(), extensions.end(), file_ext) != extensions.end();
-}
-} // namespace
-
 namespace Service::AM {
 
 constexpr u16 PLATFORM_CTR = 0x0004;
@@ -389,7 +380,7 @@ InstallStatus InstallCIA(const std::string& path,
                         const std::string& virtual_name) -> bool {
             const std::string physical_name = directory + DIR_SEP + virtual_name;
             const bool is_dir = FileUtil::IsDirectory(physical_name);
-            if (!is_dir && HasSupportedFileExtension(physical_name)) {
+            if (!is_dir) {
                 std::unique_ptr<Loader::AppLoader> loader = Loader::GetLoader(physical_name);
                 if (!loader) {
                     return true;
@@ -405,7 +396,12 @@ InstallStatus InstallCIA(const std::string& path,
                 return FileUtil::ForeachDirectoryEntry(nullptr, physical_name, callback);
             }
         };
-        if (!FileUtil::ForeachDirectoryEntry(nullptr, path, callback)) {
+        if (!FileUtil::ForeachDirectoryEntry(
+                nullptr,
+                GetTitlePath(
+                    Service::AM::GetTitleMediaType(container.GetTitleMetadata().GetTitleID()),
+                    container.GetTitleMetadata().GetTitleID()),
+                callback)) {
             LOG_ERROR(Service_AM, "CIA {} contained encrypted files.", path);
             return InstallStatus::ErrorEncrypted;
         }