summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2018-09-04 16:20:40 -0400
committerGravatar GitHub2018-09-04 16:20:40 -0400
commitfaa9e066aba320bcd38fd023ee58c6f9e1d3efdd (patch)
treec369b13af5a30698564ee54acbae639be4576482 /src
parentMerge pull request #1238 from lioncash/explicit (diff)
parentmain: Only show DRD deprecation warning once (diff)
downloadyuzu-faa9e066aba320bcd38fd023ee58c6f9e1d3efdd.tar.gz
yuzu-faa9e066aba320bcd38fd023ee58c6f9e1d3efdd.tar.xz
yuzu-faa9e066aba320bcd38fd023ee58c6f9e1d3efdd.zip
Merge pull request #1178 from DarkLordZach/nsp
file_sys: Add Nintendo Submissions Package (NSP) file format
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/crypto/key_manager.cpp16
-rw-r--r--src/core/crypto/key_manager.h2
-rw-r--r--src/core/file_sys/card_image.cpp38
-rw-r--r--src/core/file_sys/card_image.h8
-rw-r--r--src/core/file_sys/control_metadata.cpp12
-rw-r--r--src/core/file_sys/control_metadata.h9
-rw-r--r--src/core/file_sys/registered_cache.cpp21
-rw-r--r--src/core/file_sys/registered_cache.h7
-rw-r--r--src/core/file_sys/submission_package.cpp236
-rw-r--r--src/core/file_sys/submission_package.h73
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp4
-rw-r--r--src/core/loader/loader.cpp14
-rw-r--r--src/core/loader/loader.h2
-rw-r--r--src/core/loader/nsp.cpp135
-rw-r--r--src/core/loader/nsp.h54
-rw-r--r--src/core/loader/xci.cpp7
-rw-r--r--src/yuzu/game_list.cpp2
-rw-r--r--src/yuzu/main.cpp47
19 files changed, 650 insertions, 41 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
49 file_sys/savedata_factory.h 49 file_sys/savedata_factory.h
50 file_sys/sdmc_factory.cpp 50 file_sys/sdmc_factory.cpp
51 file_sys/sdmc_factory.h 51 file_sys/sdmc_factory.h
52 file_sys/submission_package.cpp
53 file_sys/submission_package.h
52 file_sys/vfs.cpp 54 file_sys/vfs.cpp
53 file_sys/vfs.h 55 file_sys/vfs.h
54 file_sys/vfs_concat.cpp 56 file_sys/vfs_concat.cpp
@@ -359,6 +361,8 @@ add_library(core STATIC
359 loader/nro.h 361 loader/nro.h
360 loader/nso.cpp 362 loader/nso.cpp
361 loader/nso.h 363 loader/nso.h
364 loader/nsp.cpp
365 loader/nsp.h
362 loader/xci.cpp 366 loader/xci.cpp
363 loader/xci.h 367 loader/xci.h
364 memory.cpp 368 memory.cpp
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index f768533da..6f27f990b 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,
231} 231}
232 232
233void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { 233void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) {
234 const auto iter = std::find_if( 234 if (s128_keys.find({id, field1, field2}) != s128_keys.end())
235 return;
236 if (id == S128KeyType::Titlekey) {
237 Key128 rights_id;
238 std::memcpy(rights_id.data(), &field2, sizeof(u64));
239 std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64));
240 WriteKeyToFile(true, Common::HexArrayToString(rights_id), key);
241 }
242 const auto iter2 = std::find_if(
235 s128_file_id.begin(), s128_file_id.end(), 243 s128_file_id.begin(), s128_file_id.end(),
236 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { 244 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
237 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == 245 return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
238 std::tie(id, field1, field2); 246 std::tie(id, field1, field2);
239 }); 247 });
240 if (iter != s128_file_id.end()) 248 if (iter2 != s128_file_id.end())
241 WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key); 249 WriteKeyToFile(false, iter2->first, key);
242 s128_keys[{id, field1, field2}] = key; 250 s128_keys[{id, field1, field2}] = key;
243} 251}
244 252
245void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { 253void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) {
254 if (s256_keys.find({id, field1, field2}) != s256_keys.end())
255 return;
246 const auto iter = std::find_if( 256 const auto iter = std::find_if(
247 s256_file_id.begin(), s256_file_id.end(), 257 s256_file_id.begin(), s256_file_id.end(),
248 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) { 258 [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {
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;
17 17
18namespace Core::Crypto { 18namespace Core::Crypto {
19 19
20constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
21
20using Key128 = std::array<u8, 0x10>; 22using Key128 = std::array<u8, 0x10>;
21using Key256 = std::array<u8, 0x20>; 23using Key256 = std::array<u8, 0x20>;
22using SHA256Hash = std::array<u8, 0x20>; 24using 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..1bd3353e4 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -10,7 +10,9 @@
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "core/file_sys/card_image.h" 11#include "core/file_sys/card_image.h"
12#include "core/file_sys/content_archive.h" 12#include "core/file_sys/content_archive.h"
13#include "core/file_sys/nca_metadata.h"
13#include "core/file_sys/partition_filesystem.h" 14#include "core/file_sys/partition_filesystem.h"
15#include "core/file_sys/submission_package.h"
14#include "core/file_sys/vfs_offset.h" 16#include "core/file_sys/vfs_offset.h"
15#include "core/loader/loader.h" 17#include "core/loader/loader.h"
16 18
@@ -44,15 +46,19 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
44 partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); 46 partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw);
45 } 47 }
46 48
47 program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; 49 secure_partition = std::make_shared<NSP>(
50 main_hfs.GetFile(partition_names[static_cast<size_t>(XCIPartition::Secure)]));
48 51
49 auto result = AddNCAFromPartition(XCIPartition::Secure); 52 const auto secure_ncas = secure_partition->GetNCAsCollapsed();
50 if (result != Loader::ResultStatus::Success) { 53 std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
51 status = result; 54
52 return; 55 program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
53 } 56 program =
57 secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
58 if (program != nullptr)
59 program_nca_status = program->GetStatus();
54 60
55 result = AddNCAFromPartition(XCIPartition::Update); 61 auto result = AddNCAFromPartition(XCIPartition::Update);
56 if (result != Loader::ResultStatus::Success) { 62 if (result != Loader::ResultStatus::Success) {
57 status = result; 63 status = result;
58 return; 64 return;
@@ -89,6 +95,10 @@ VirtualDir XCI::GetPartition(XCIPartition partition) const {
89 return partitions[static_cast<size_t>(partition)]; 95 return partitions[static_cast<size_t>(partition)];
90} 96}
91 97
98std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
99 return secure_partition;
100}
101
92VirtualDir XCI::GetSecurePartition() const { 102VirtualDir XCI::GetSecurePartition() const {
93 return GetPartition(XCIPartition::Secure); 103 return GetPartition(XCIPartition::Secure);
94} 104}
@@ -105,6 +115,20 @@ VirtualDir XCI::GetLogoPartition() const {
105 return GetPartition(XCIPartition::Logo); 115 return GetPartition(XCIPartition::Logo);
106} 116}
107 117
118u64 XCI::GetProgramTitleID() const {
119 return secure_partition->GetProgramTitleID();
120}
121
122std::shared_ptr<NCA> XCI::GetProgramNCA() const {
123 return program;
124}
125
126VirtualFile XCI::GetProgramNCAFile() const {
127 if (GetProgramNCA() == nullptr)
128 return nullptr;
129 return GetProgramNCA()->GetBaseFile();
130}
131
108const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { 132const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
109 return ncas; 133 return ncas;
110} 134}
diff --git a/src/core/file_sys/card_image.h b/src/core/file_sys/card_image.h
index 4f104d18a..ce514dfa0 100644
--- a/src/core/file_sys/card_image.h
+++ b/src/core/file_sys/card_image.h
@@ -19,6 +19,7 @@ namespace FileSys {
19 19
20class NCA; 20class NCA;
21enum class NCAContentType : u8; 21enum class NCAContentType : u8;
22class NSP;
22 23
23enum class GamecardSize : u8 { 24enum class GamecardSize : u8 {
24 S_1GB = 0xFA, 25 S_1GB = 0xFA,
@@ -71,11 +72,16 @@ public:
71 u8 GetFormatVersion() const; 72 u8 GetFormatVersion() const;
72 73
73 VirtualDir GetPartition(XCIPartition partition) const; 74 VirtualDir GetPartition(XCIPartition partition) const;
75 std::shared_ptr<NSP> GetSecurePartitionNSP() const;
74 VirtualDir GetSecurePartition() const; 76 VirtualDir GetSecurePartition() const;
75 VirtualDir GetNormalPartition() const; 77 VirtualDir GetNormalPartition() const;
76 VirtualDir GetUpdatePartition() const; 78 VirtualDir GetUpdatePartition() const;
77 VirtualDir GetLogoPartition() const; 79 VirtualDir GetLogoPartition() const;
78 80
81 u64 GetProgramTitleID() const;
82
83 std::shared_ptr<NCA> GetProgramNCA() const;
84 VirtualFile GetProgramNCAFile() const;
79 const std::vector<std::shared_ptr<NCA>>& GetNCAs() const; 85 const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
80 std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; 86 std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
81 VirtualFile GetNCAFileByType(NCAContentType type) const; 87 VirtualFile GetNCAFileByType(NCAContentType type) const;
@@ -101,6 +107,8 @@ private:
101 Loader::ResultStatus program_nca_status; 107 Loader::ResultStatus program_nca_status;
102 108
103 std::vector<VirtualDir> partitions; 109 std::vector<VirtualDir> partitions;
110 std::shared_ptr<NSP> secure_partition;
111 std::shared_ptr<NCA> program;
104 std::vector<std::shared_ptr<NCA>> ncas; 112 std::vector<std::shared_ptr<NCA>> ncas;
105}; 113};
106} // namespace FileSys 114} // namespace FileSys
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>()) {
21} 21}
22 22
23const LanguageEntry& NACP::GetLanguageEntry(Language language) const { 23const LanguageEntry& NACP::GetLanguageEntry(Language language) const {
24 return raw->language_entries.at(static_cast<u8>(language)); 24 if (language != Language::Default) {
25 return raw->language_entries.at(static_cast<u8>(language));
26 } else {
27 for (const auto& language_entry : raw->language_entries) {
28 if (!language_entry.GetApplicationName().empty())
29 return language_entry;
30 }
31
32 // Fallback to English
33 return GetLanguageEntry(Language::AmericanEnglish);
34 }
25} 35}
26 36
27std::string NACP::GetApplicationName(Language language) const { 37std::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 @@
9#include <string> 9#include <string>
10#include "common/common_funcs.h" 10#include "common/common_funcs.h"
11#include "common/common_types.h" 11#include "common/common_types.h"
12#include "common/swap.h"
12#include "core/file_sys/vfs.h" 13#include "core/file_sys/vfs.h"
13 14
14namespace FileSys { 15namespace FileSys {
@@ -61,6 +62,8 @@ enum class Language : u8 {
61 Korean = 12, 62 Korean = 12,
62 Taiwanese = 13, 63 Taiwanese = 13,
63 Chinese = 14, 64 Chinese = 14,
65
66 Default = 255,
64}; 67};
65 68
66static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { 69static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
@@ -75,9 +78,9 @@ static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
75class NACP { 78class NACP {
76public: 79public:
77 explicit NACP(VirtualFile file); 80 explicit NACP(VirtualFile file);
78 const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const; 81 const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const;
79 std::string GetApplicationName(Language language = Language::AmericanEnglish) const; 82 std::string GetApplicationName(Language language = Language::Default) const;
80 std::string GetDeveloperName(Language language = Language::AmericanEnglish) const; 83 std::string GetDeveloperName(Language language = Language::Default) const;
81 u64 GetTitleId() const; 84 u64 GetTitleId() const;
82 std::string GetVersionString() const; 85 std::string GetVersionString() const;
83 86
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index d9decc104..cf6f77401 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -13,6 +13,7 @@
13#include "core/file_sys/content_archive.h" 13#include "core/file_sys/content_archive.h"
14#include "core/file_sys/nca_metadata.h" 14#include "core/file_sys/nca_metadata.h"
15#include "core/file_sys/registered_cache.h" 15#include "core/file_sys/registered_cache.h"
16#include "core/file_sys/submission_package.h"
16#include "core/file_sys/vfs_concat.h" 17#include "core/file_sys/vfs_concat.h"
17#include "core/loader/loader.h" 18#include "core/loader/loader.h"
18 19
@@ -358,17 +359,21 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
358 return out; 359 return out;
359} 360}
360 361
361static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { 362static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) {
362 const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false)); 363 const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
363 const auto iter = 364 if (file == nullptr)
364 std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), 365 return nullptr;
365 [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; }); 366 return std::make_shared<NCA>(file);
366 return iter == xci->GetNCAs().end() ? nullptr : *iter;
367} 367}
368 368
369InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, 369InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
370 const VfsCopyFunction& copy) { 370 const VfsCopyFunction& copy) {
371 const auto& ncas = xci->GetNCAs(); 371 return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy);
372}
373
374InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists,
375 const VfsCopyFunction& copy) {
376 const auto& ncas = nsp->GetNCAsCollapsed();
372 const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { 377 const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
373 return nca->GetType() == NCAContentType::Meta; 378 return nca->GetType() == NCAContentType::Meta;
374 }); 379 });
@@ -392,7 +397,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overw
392 const auto cnmt_file = section0->GetFiles()[0]; 397 const auto cnmt_file = section0->GetFiles()[0];
393 const CNMT cnmt(cnmt_file); 398 const CNMT cnmt(cnmt_file);
394 for (const auto& record : cnmt.GetContentRecords()) { 399 for (const auto& record : cnmt.GetContentRecords()) {
395 const auto nca = GetNCAFromXCIForID(xci, record.nca_id); 400 const auto nca = GetNCAFromNSPForID(nsp, record.nca_id);
396 if (nca == nullptr) 401 if (nca == nullptr)
397 return InstallResult::ErrorCopyFailed; 402 return InstallResult::ErrorCopyFailed;
398 const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); 403 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..467ceeef1 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -17,6 +17,7 @@
17namespace FileSys { 17namespace FileSys {
18class CNMT; 18class CNMT;
19class NCA; 19class NCA;
20class NSP;
20class XCI; 21class XCI;
21 22
22enum class ContentRecordType : u8; 23enum class ContentRecordType : u8;
@@ -89,10 +90,12 @@ public:
89 boost::optional<ContentRecordType> record_type = boost::none, 90 boost::optional<ContentRecordType> record_type = boost::none,
90 boost::optional<u64> title_id = boost::none) const; 91 boost::optional<u64> title_id = boost::none) const;
91 92
92 // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there 93 // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
93 // is a meta NCA and all of them are accessible. 94 // there is a meta NCA and all of them are accessible.
94 InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, 95 InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
95 const VfsCopyFunction& copy = &VfsRawCopy); 96 const VfsCopyFunction& copy = &VfsRawCopy);
97 InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false,
98 const VfsCopyFunction& copy = &VfsRawCopy);
96 99
97 // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this 100 // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
98 // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a 101 // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
new file mode 100644
index 000000000..bde879861
--- /dev/null
+++ b/src/core/file_sys/submission_package.cpp
@@ -0,0 +1,236 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <fmt/ostream.h>
6#include "common/assert.h"
7#include "common/hex_util.h"
8#include "core/file_sys/content_archive.h"
9#include "core/file_sys/nca_metadata.h"
10#include "core/file_sys/partition_filesystem.h"
11#include "core/file_sys/submission_package.h"
12#include "core/loader/loader.h"
13
14namespace FileSys {
15NSP::NSP(VirtualFile file_)
16 : file(std::move(file_)),
17 pfs(std::make_shared<PartitionFilesystem>(file)), status{Loader::ResultStatus::Success} {
18 if (pfs->GetStatus() != Loader::ResultStatus::Success) {
19 status = pfs->GetStatus();
20 return;
21 }
22
23 if (IsDirectoryExeFS(pfs)) {
24 extracted = true;
25 exefs = pfs;
26
27 const auto& files = pfs->GetFiles();
28 const auto romfs_iter =
29 std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) {
30 return file->GetName().find(".romfs") != std::string::npos;
31 });
32 if (romfs_iter != files.end())
33 romfs = *romfs_iter;
34 return;
35 }
36
37 extracted = false;
38 const auto files = pfs->GetFiles();
39
40 Core::Crypto::KeyManager keys;
41 for (const auto& ticket_file : files) {
42 if (ticket_file->GetExtension() == "tik") {
43 if (ticket_file == nullptr ||
44 ticket_file->GetSize() <
45 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
46 continue;
47 }
48
49 Core::Crypto::Key128 key{};
50 ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
51 std::string_view name_only(ticket_file->GetName());
52 name_only.remove_suffix(4);
53 const auto rights_id_raw = Common::HexStringToArray<16>(name_only);
54 u128 rights_id;
55 std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128));
56 keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
57 }
58 }
59
60 for (const auto& outer_file : files) {
61 if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
62 const auto nca = std::make_shared<NCA>(outer_file);
63 if (nca->GetStatus() != Loader::ResultStatus::Success)
64 continue;
65 const auto section0 = nca->GetSubdirectories()[0];
66
67 for (const auto& inner_file : section0->GetFiles()) {
68 if (inner_file->GetExtension() != "cnmt")
69 continue;
70
71 const CNMT cnmt(inner_file);
72 auto& ncas_title = ncas[cnmt.GetTitleID()];
73
74 ncas_title[ContentRecordType::Meta] = nca;
75 for (const auto& rec : cnmt.GetContentRecords()) {
76 const auto id_string = Common::HexArrayToString(rec.nca_id, false);
77 const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string));
78 if (next_file == nullptr) {
79 LOG_WARNING(Service_FS,
80 "NCA with ID {}.nca is listed in content metadata, but cannot "
81 "be found in PFS. NSP appears to be corrupted.",
82 id_string);
83 continue;
84 }
85
86 auto next_nca = std::make_shared<NCA>(next_file);
87 if (next_nca->GetType() == NCAContentType::Program)
88 program_status[cnmt.GetTitleID()] = next_nca->GetStatus();
89 if (next_nca->GetStatus() == Loader::ResultStatus::Success)
90 ncas_title[rec.type] = std::move(next_nca);
91 }
92
93 break;
94 }
95 }
96 }
97}
98
99NSP::~NSP() = default;
100
101Loader::ResultStatus NSP::GetStatus() const {
102 return status;
103}
104
105Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const {
106 const auto iter = program_status.find(title_id);
107 if (iter == program_status.end())
108 return Loader::ResultStatus::ErrorNSPMissingProgramNCA;
109 return iter->second;
110}
111
112u64 NSP::GetFirstTitleID() const {
113 if (program_status.empty())
114 return 0;
115 return program_status.begin()->first;
116}
117
118u64 NSP::GetProgramTitleID() const {
119 const auto out = GetFirstTitleID();
120 if ((out & 0x800) == 0)
121 return out;
122
123 const auto ids = GetTitleIDs();
124 const auto iter =
125 std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; });
126 return iter == ids.end() ? out : *iter;
127}
128
129std::vector<u64> NSP::GetTitleIDs() const {
130 std::vector<u64> out;
131 out.reserve(ncas.size());
132 for (const auto& kv : ncas)
133 out.push_back(kv.first);
134 return out;
135}
136
137bool NSP::IsExtractedType() const {
138 return extracted;
139}
140
141VirtualFile NSP::GetRomFS() const {
142 return romfs;
143}
144
145VirtualDir NSP::GetExeFS() const {
146 return exefs;
147}
148
149std::vector<std::shared_ptr<NCA>> NSP::GetNCAsCollapsed() const {
150 if (extracted)
151 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
152 std::vector<std::shared_ptr<NCA>> out;
153 for (const auto& map : ncas) {
154 for (const auto& inner_map : map.second)
155 out.push_back(inner_map.second);
156 }
157 return out;
158}
159
160std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const {
161 if (extracted)
162 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
163 std::multimap<u64, std::shared_ptr<NCA>> out;
164 for (const auto& map : ncas) {
165 for (const auto& inner_map : map.second)
166 out.emplace(map.first, inner_map.second);
167 }
168 return out;
169}
170
171std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const {
172 return ncas;
173}
174
175std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const {
176 if (extracted)
177 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
178
179 const auto title_id_iter = ncas.find(title_id);
180 if (title_id_iter == ncas.end())
181 return nullptr;
182
183 const auto type_iter = title_id_iter->second.find(type);
184 if (type_iter == title_id_iter->second.end())
185 return nullptr;
186
187 return type_iter->second;
188}
189
190VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const {
191 if (extracted)
192 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
193 const auto nca = GetNCA(title_id, type);
194 if (nca != nullptr)
195 return nca->GetBaseFile();
196 return nullptr;
197}
198
199std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const {
200 if (extracted)
201 LOG_WARNING(Service_FS, "called on an NSP that is of type extracted.");
202 std::vector<Core::Crypto::Key128> out;
203 for (const auto& ticket_file : ticket_files) {
204 if (ticket_file == nullptr ||
205 ticket_file->GetSize() <
206 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) {
207 continue;
208 }
209
210 out.emplace_back();
211 ticket_file->Read(out.back().data(), out.back().size(),
212 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET);
213 }
214 return out;
215}
216
217std::vector<VirtualFile> NSP::GetFiles() const {
218 return pfs->GetFiles();
219}
220
221std::vector<VirtualDir> NSP::GetSubdirectories() const {
222 return pfs->GetSubdirectories();
223}
224
225std::string NSP::GetName() const {
226 return file->GetName();
227}
228
229VirtualDir NSP::GetParentDirectory() const {
230 return file->GetContainingDirectory();
231}
232
233bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
234 return false;
235}
236} // 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..0292164f9
--- /dev/null
+++ b/src/core/file_sys/submission_package.h
@@ -0,0 +1,73 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <map>
9#include <vector>
10#include "common/common_types.h"
11#include "common/swap.h"
12#include "core/file_sys/content_archive.h"
13#include "core/file_sys/romfs_factory.h"
14#include "core/file_sys/vfs.h"
15#include "core/loader/loader.h"
16
17namespace FileSys {
18
19class PartitionFilesystem;
20
21class NSP : public ReadOnlyVfsDirectory {
22public:
23 explicit NSP(VirtualFile file);
24 ~NSP();
25
26 Loader::ResultStatus GetStatus() const;
27 Loader::ResultStatus GetProgramStatus(u64 title_id) const;
28 // Should only be used when one title id can be assured.
29 u64 GetFirstTitleID() const;
30 u64 GetProgramTitleID() const;
31 std::vector<u64> GetTitleIDs() const;
32
33 bool IsExtractedType() const;
34
35 // Common (Can be safely called on both types)
36 VirtualFile GetRomFS() const;
37 VirtualDir GetExeFS() const;
38
39 // Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
40 std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const;
41 std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const;
42 std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const;
43 std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const;
44 VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const;
45 std::vector<Core::Crypto::Key128> GetTitlekey() const;
46
47 std::vector<VirtualFile> GetFiles() const override;
48
49 std::vector<VirtualDir> GetSubdirectories() const override;
50
51 std::string GetName() const override;
52
53 VirtualDir GetParentDirectory() const override;
54
55protected:
56 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
57
58private:
59 VirtualFile file;
60
61 bool extracted;
62 Loader::ResultStatus status;
63 std::map<u64, Loader::ResultStatus> program_status;
64
65 std::shared_ptr<PartitionFilesystem> pfs;
66 // Map title id -> {map type -> NCA}
67 std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas;
68 std::vector<VirtualFile> ticket_files;
69
70 VirtualFile romfs;
71 VirtualDir exefs;
72};
73} // namespace FileSys
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
61 61
62 if (nacp_file != nullptr) { 62 if (nacp_file != nullptr) {
63 FileSys::NACP nacp(nacp_file); 63 FileSys::NACP nacp(nacp_file);
64 title_id = nacp.GetTitleId();
65 name = nacp.GetApplicationName(); 64 name = nacp.GetApplicationName();
66 } 65 }
67} 66}
@@ -120,6 +119,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
120 } 119 }
121 120
122 auto& kernel = Core::System::GetInstance().Kernel(); 121 auto& kernel = Core::System::GetInstance().Kernel();
122 title_id = metadata.GetTitleID();
123 process->program_id = metadata.GetTitleID(); 123 process->program_id = metadata.GetTitleID();
124 process->svc_access_mask.set(); 124 process->svc_access_mask.set();
125 process->resource_limit = 125 process->resource_limit =
@@ -159,8 +159,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buff
159} 159}
160 160
161ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) { 161ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) {
162 if (name.empty())
163 return ResultStatus::ErrorNoControl;
164 out_program_id = title_id; 162 out_program_id = title_id;
165 return ResultStatus::Success; 163 return ResultStatus::Success;
166} 164}
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 @@
15#include "core/loader/nca.h" 15#include "core/loader/nca.h"
16#include "core/loader/nro.h" 16#include "core/loader/nro.h"
17#include "core/loader/nso.h" 17#include "core/loader/nso.h"
18#include "core/loader/nsp.h"
18#include "core/loader/xci.h" 19#include "core/loader/xci.h"
19 20
20namespace Loader { 21namespace Loader {
@@ -34,6 +35,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
34 CHECK_TYPE(NCA) 35 CHECK_TYPE(NCA)
35 CHECK_TYPE(XCI) 36 CHECK_TYPE(XCI)
36 CHECK_TYPE(NAX) 37 CHECK_TYPE(NAX)
38 CHECK_TYPE(NSP)
37 39
38#undef CHECK_TYPE 40#undef CHECK_TYPE
39 41
@@ -59,6 +61,8 @@ FileType GuessFromFilename(const std::string& name) {
59 return FileType::NCA; 61 return FileType::NCA;
60 if (extension == "xci") 62 if (extension == "xci")
61 return FileType::XCI; 63 return FileType::XCI;
64 if (extension == "nsp")
65 return FileType::NSP;
62 66
63 return FileType::Unknown; 67 return FileType::Unknown;
64} 68}
@@ -77,6 +81,8 @@ std::string GetFileTypeString(FileType type) {
77 return "XCI"; 81 return "XCI";
78 case FileType::NAX: 82 case FileType::NAX:
79 return "NAX"; 83 return "NAX";
84 case FileType::NSP:
85 return "NSP";
80 case FileType::DeconstructedRomDirectory: 86 case FileType::DeconstructedRomDirectory:
81 return "Directory"; 87 return "Directory";
82 case FileType::Error: 88 case FileType::Error:
@@ -87,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
87 return "unknown"; 93 return "unknown";
88} 94}
89 95
90constexpr std::array<const char*, 49> RESULT_MESSAGES{ 96constexpr std::array<const char*, 50> RESULT_MESSAGES{
91 "The operation completed successfully.", 97 "The operation completed successfully.",
92 "The loader requested to load is already loaded.", 98 "The loader requested to load is already loaded.",
93 "The operation is not implemented.", 99 "The operation is not implemented.",
@@ -137,7 +143,7 @@ constexpr std::array<const char*, 49> RESULT_MESSAGES{
137 "The AES Key Generation Source could not be found.", 143 "The AES Key Generation Source could not be found.",
138 "The SD Save Key Source could not be found.", 144 "The SD Save Key Source could not be found.",
139 "The SD NCA Key Source could not be found.", 145 "The SD NCA Key Source could not be found.",
140}; 146 "The NSP file is missing a Program-type NCA."};
141 147
142std::ostream& operator<<(std::ostream& os, ResultStatus status) { 148std::ostream& operator<<(std::ostream& os, ResultStatus status) {
143 os << RESULT_MESSAGES.at(static_cast<size_t>(status)); 149 os << RESULT_MESSAGES.at(static_cast<size_t>(status));
@@ -182,6 +188,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT
182 case FileType::NAX: 188 case FileType::NAX:
183 return std::make_unique<AppLoader_NAX>(std::move(file)); 189 return std::make_unique<AppLoader_NAX>(std::move(file));
184 190
191 // NX NSP (Nintendo Submission Package) file format
192 case FileType::NSP:
193 return std::make_unique<AppLoader_NSP>(std::move(file));
194
185 // NX deconstructed ROM directory. 195 // NX deconstructed ROM directory.
186 case FileType::DeconstructedRomDirectory: 196 case FileType::DeconstructedRomDirectory:
187 return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); 197 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 {
29 NSO, 29 NSO,
30 NRO, 30 NRO,
31 NCA, 31 NCA,
32 NSP,
32 XCI, 33 XCI,
33 NAX, 34 NAX,
34 DeconstructedRomDirectory, 35 DeconstructedRomDirectory,
@@ -105,6 +106,7 @@ enum class ResultStatus : u16 {
105 ErrorMissingAESKeyGenerationSource, 106 ErrorMissingAESKeyGenerationSource,
106 ErrorMissingSDSaveKeySource, 107 ErrorMissingSDSaveKeySource,
107 ErrorMissingSDNCAKeySource, 108 ErrorMissingSDNCAKeySource,
109 ErrorNSPMissingProgramNCA,
108}; 110};
109 111
110std::ostream& operator<<(std::ostream& os, ResultStatus status); 112std::ostream& operator<<(std::ostream& os, ResultStatus status);
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
new file mode 100644
index 000000000..7c06239f2
--- /dev/null
+++ b/src/core/loader/nsp.cpp
@@ -0,0 +1,135 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <vector>
6
7#include "common/common_types.h"
8#include "core/file_sys/card_image.h"
9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/romfs.h"
13#include "core/file_sys/submission_package.h"
14#include "core/hle/kernel/process.h"
15#include "core/loader/deconstructed_rom_directory.h"
16#include "core/loader/nca.h"
17#include "core/loader/nsp.h"
18
19namespace Loader {
20
21AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
22 : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)),
23 title_id(nsp->GetProgramTitleID()) {
24
25 if (nsp->GetStatus() != ResultStatus::Success)
26 return;
27 if (nsp->IsExtractedType())
28 return;
29
30 const auto control_nca =
31 nsp->GetNCA(nsp->GetFirstTitleID(), FileSys::ContentRecordType::Control);
32 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
33 return;
34
35 const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS());
36 if (romfs == nullptr)
37 return;
38
39 for (const auto& language : FileSys::LANGUAGE_NAMES) {
40 icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
41 if (icon_file != nullptr)
42 break;
43 }
44
45 const auto nacp_raw = romfs->GetFile("control.nacp");
46 if (nacp_raw == nullptr)
47 return;
48 nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
49}
50
51AppLoader_NSP::~AppLoader_NSP() = default;
52
53FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) {
54 FileSys::NSP nsp(file);
55
56 if (nsp.GetStatus() == ResultStatus::Success) {
57 // Extracted Type case
58 if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr &&
59 FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) {
60 return FileType::NSP;
61 }
62
63 // Non-Ectracted Type case
64 if (!nsp.IsExtractedType() &&
65 nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr &&
66 AppLoader_NCA::IdentifyType(nsp.GetNCAFile(
67 nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA) {
68 return FileType::NSP;
69 }
70 }
71
72 return FileType::Error;
73}
74
75ResultStatus AppLoader_NSP::Load(Kernel::SharedPtr<Kernel::Process>& process) {
76 if (is_loaded) {
77 return ResultStatus::ErrorAlreadyLoaded;
78 }
79
80 if (nsp->IsExtractedType()) {
81 secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS());
82 } else {
83 if (title_id == 0)
84 return ResultStatus::ErrorNSPMissingProgramNCA;
85
86 secondary_loader = std::make_unique<AppLoader_NCA>(
87 nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program));
88
89 if (nsp->GetStatus() != ResultStatus::Success)
90 return nsp->GetStatus();
91
92 if (nsp->GetProgramStatus(title_id) != ResultStatus::Success)
93 return nsp->GetProgramStatus(title_id);
94
95 if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) {
96 if (!Core::Crypto::KeyManager::KeyFileExists(false))
97 return ResultStatus::ErrorMissingProductionKeyFile;
98 return ResultStatus::ErrorNSPMissingProgramNCA;
99 }
100 }
101
102 const auto result = secondary_loader->Load(process);
103 if (result != ResultStatus::Success)
104 return result;
105
106 is_loaded = true;
107
108 return ResultStatus::Success;
109}
110
111ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) {
112 return secondary_loader->ReadRomFS(dir);
113}
114
115ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) {
116 if (title_id == 0)
117 return ResultStatus::ErrorNotInitialized;
118 out_program_id = title_id;
119 return ResultStatus::Success;
120}
121
122ResultStatus AppLoader_NSP::ReadIcon(std::vector<u8>& buffer) {
123 if (icon_file == nullptr)
124 return ResultStatus::ErrorNoControl;
125 buffer = icon_file->ReadAllBytes();
126 return ResultStatus::Success;
127}
128
129ResultStatus AppLoader_NSP::ReadTitle(std::string& title) {
130 if (nacp_file == nullptr)
131 return ResultStatus::ErrorNoControl;
132 title = nacp_file->GetApplicationName();
133 return ResultStatus::Success;
134}
135} // namespace Loader
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h
new file mode 100644
index 000000000..7ef810499
--- /dev/null
+++ b/src/core/loader/nsp.h
@@ -0,0 +1,54 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "common/common_types.h"
9#include "core/file_sys/vfs.h"
10#include "core/loader/loader.h"
11
12namespace FileSys {
13class NACP;
14class NSP;
15} // namespace FileSys
16
17namespace Loader {
18
19class AppLoader_NCA;
20
21/// Loads an XCI file
22class AppLoader_NSP final : public AppLoader {
23public:
24 explicit AppLoader_NSP(FileSys::VirtualFile file);
25 ~AppLoader_NSP() override;
26
27 /**
28 * Returns the type of the file
29 * @param file std::shared_ptr<VfsFile> open file
30 * @return FileType found, or FileType::Error if this loader doesn't know it
31 */
32 static FileType IdentifyType(const FileSys::VirtualFile& file);
33
34 FileType GetFileType() override {
35 return IdentifyType(file);
36 }
37
38 ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
39
40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
41 ResultStatus ReadProgramId(u64& out_program_id) override;
42 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
43 ResultStatus ReadTitle(std::string& title) override;
44
45private:
46 std::unique_ptr<FileSys::NSP> nsp;
47 std::unique_ptr<AppLoader> secondary_loader;
48
49 FileSys::VirtualFile icon_file;
50 std::shared_ptr<FileSys::NACP> nacp_file;
51 u64 title_id;
52};
53
54} // namespace Loader
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 {
17 17
18AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) 18AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
19 : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), 19 : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
20 nca_loader(std::make_unique<AppLoader_NCA>( 20 nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
21 xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {
22 if (xci->GetStatus() != ResultStatus::Success) 21 if (xci->GetStatus() != ResultStatus::Success)
23 return; 22 return;
24 const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); 23 const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
@@ -64,11 +63,11 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) {
64 if (xci->GetProgramNCAStatus() != ResultStatus::Success) 63 if (xci->GetProgramNCAStatus() != ResultStatus::Success)
65 return xci->GetProgramNCAStatus(); 64 return xci->GetProgramNCAStatus();
66 65
67 const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program); 66 const auto nca = xci->GetProgramNCA();
68 if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) 67 if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
69 return ResultStatus::ErrorMissingProductionKeyFile; 68 return ResultStatus::ErrorMissingProductionKeyFile;
70 69
71 auto result = nca_loader->Load(process); 70 const auto result = nca_loader->Load(process);
72 if (result != ResultStatus::Success) 71 if (result != ResultStatus::Success)
73 return result; 72 return result;
74 73
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() {
432 item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); 432 item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder());
433} 433}
434 434
435const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; 435const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"};
436 436
437static bool HasSupportedFileExtension(const std::string& file_name) { 437static bool HasSupportedFileExtension(const std::string& file_name) {
438 const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); 438 const QFileInfo file = QFileInfo(QString::fromStdString(file_name));
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 262e33487..56bd3ee2e 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -34,7 +34,9 @@
34#include "core/file_sys/content_archive.h" 34#include "core/file_sys/content_archive.h"
35#include "core/file_sys/registered_cache.h" 35#include "core/file_sys/registered_cache.h"
36#include "core/file_sys/savedata_factory.h" 36#include "core/file_sys/savedata_factory.h"
37#include "core/file_sys/submission_package.h"
37#include "core/file_sys/vfs_real.h" 38#include "core/file_sys/vfs_real.h"
39#include "core/hle/kernel/process.h"
38#include "core/hle/service/filesystem/filesystem.h" 40#include "core/hle/service/filesystem/filesystem.h"
39#include "core/loader/loader.h" 41#include "core/loader/loader.h"
40#include "core/perf_stats.h" 42#include "core/perf_stats.h"
@@ -76,6 +78,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
76 */ 78 */
77enum class CalloutFlag : uint32_t { 79enum class CalloutFlag : uint32_t {
78 Telemetry = 0x1, 80 Telemetry = 0x1,
81 DRDDeprecation = 0x2,
79}; 82};
80 83
81static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { 84static void ShowCalloutMessage(const QString& message, CalloutFlag flag) {
@@ -488,6 +491,23 @@ bool GMainWindow::LoadROM(const QString& filename) {
488 491
489 const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; 492 const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())};
490 493
494 const auto drd_callout =
495 (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0;
496
497 if (result == Core::System::ResultStatus::Success &&
498 system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory &&
499 drd_callout) {
500 UISettings::values.callout_flags |= static_cast<u32>(CalloutFlag::DRDDeprecation);
501 QMessageBox::warning(
502 this, tr("Warning Outdated Game Format"),
503 tr("You are using the deconstructed ROM directory format for this game, which is an "
504 "outdated format that has been superseded by others such as NCA, NAX, XCI, or "
505 "NSP. Deconstructed ROM directories lack icons, metadata, and update "
506 "support.<br><br>For an explanation of the various Switch formats yuzu supports, <a "
507 "href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our "
508 "wiki</a>. This message will not be shown again."));
509 }
510
491 render_window->DoneCurrent(); 511 render_window->DoneCurrent();
492 512
493 if (result != Core::System::ResultStatus::Success) { 513 if (result != Core::System::ResultStatus::Success) {
@@ -746,7 +766,8 @@ void GMainWindow::OnMenuLoadFolder() {
746 766
747void GMainWindow::OnMenuInstallToNAND() { 767void GMainWindow::OnMenuInstallToNAND() {
748 const QString file_filter = 768 const QString file_filter =
749 tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge " 769 tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive "
770 "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge "
750 "Image (*.xci)"); 771 "Image (*.xci)");
751 QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), 772 QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
752 UISettings::values.roms_path, file_filter); 773 UISettings::values.roms_path, file_filter);
@@ -806,22 +827,34 @@ void GMainWindow::OnMenuInstallToNAND() {
806 QMessageBox::Yes; 827 QMessageBox::Yes;
807 }; 828 };
808 829
809 if (filename.endsWith("xci", Qt::CaseInsensitive)) { 830 if (filename.endsWith("xci", Qt::CaseInsensitive) ||
810 const auto xci = std::make_shared<FileSys::XCI>( 831 filename.endsWith("nsp", Qt::CaseInsensitive)) {
811 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); 832
812 if (xci->GetStatus() != Loader::ResultStatus::Success) { 833 std::shared_ptr<FileSys::NSP> nsp;
834 if (filename.endsWith("nsp", Qt::CaseInsensitive)) {
835 nsp = std::make_shared<FileSys::NSP>(
836 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
837 if (nsp->IsExtractedType())
838 failed();
839 } else {
840 const auto xci = std::make_shared<FileSys::XCI>(
841 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
842 nsp = xci->GetSecurePartitionNSP();
843 }
844
845 if (nsp->GetStatus() != Loader::ResultStatus::Success) {
813 failed(); 846 failed();
814 return; 847 return;
815 } 848 }
816 const auto res = 849 const auto res =
817 Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy); 850 Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy);
818 if (res == FileSys::InstallResult::Success) { 851 if (res == FileSys::InstallResult::Success) {
819 success(); 852 success();
820 } else { 853 } else {
821 if (res == FileSys::InstallResult::ErrorAlreadyExists) { 854 if (res == FileSys::InstallResult::ErrorAlreadyExists) {
822 if (overwrite()) { 855 if (overwrite()) {
823 const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( 856 const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
824 xci, true, qt_raw_copy); 857 nsp, true, qt_raw_copy);
825 if (res2 == FileSys::InstallResult::Success) { 858 if (res2 == FileSys::InstallResult::Success) {
826 success(); 859 success();
827 } else { 860 } else {