diff options
| author | 2018-09-04 16:20:40 -0400 | |
|---|---|---|
| committer | 2018-09-04 16:20:40 -0400 | |
| commit | faa9e066aba320bcd38fd023ee58c6f9e1d3efdd (patch) | |
| tree | c369b13af5a30698564ee54acbae639be4576482 /src | |
| parent | Merge pull request #1238 from lioncash/explicit (diff) | |
| parent | main: Only show DRD deprecation warning once (diff) | |
| download | yuzu-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.txt | 4 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.cpp | 16 | ||||
| -rw-r--r-- | src/core/crypto/key_manager.h | 2 | ||||
| -rw-r--r-- | src/core/file_sys/card_image.cpp | 38 | ||||
| -rw-r--r-- | src/core/file_sys/card_image.h | 8 | ||||
| -rw-r--r-- | src/core/file_sys/control_metadata.cpp | 12 | ||||
| -rw-r--r-- | src/core/file_sys/control_metadata.h | 9 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.cpp | 21 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.h | 7 | ||||
| -rw-r--r-- | src/core/file_sys/submission_package.cpp | 236 | ||||
| -rw-r--r-- | src/core/file_sys/submission_package.h | 73 | ||||
| -rw-r--r-- | src/core/loader/deconstructed_rom_directory.cpp | 4 | ||||
| -rw-r--r-- | src/core/loader/loader.cpp | 14 | ||||
| -rw-r--r-- | src/core/loader/loader.h | 2 | ||||
| -rw-r--r-- | src/core/loader/nsp.cpp | 135 | ||||
| -rw-r--r-- | src/core/loader/nsp.h | 54 | ||||
| -rw-r--r-- | src/core/loader/xci.cpp | 7 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 2 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 47 |
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 | ||
| 233 | void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { | 233 | void 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 | ||
| 245 | void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { | 253 | void 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 | ||
| 18 | namespace Core::Crypto { | 18 | namespace Core::Crypto { |
| 19 | 19 | ||
| 20 | constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | ||
| 21 | |||
| 20 | using Key128 = std::array<u8, 0x10>; | 22 | using Key128 = std::array<u8, 0x10>; |
| 21 | using Key256 = std::array<u8, 0x20>; | 23 | using Key256 = std::array<u8, 0x20>; |
| 22 | using SHA256Hash = std::array<u8, 0x20>; | 24 | 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..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 | ||
| 98 | std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const { | ||
| 99 | return secure_partition; | ||
| 100 | } | ||
| 101 | |||
| 92 | VirtualDir XCI::GetSecurePartition() const { | 102 | VirtualDir 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 | ||
| 118 | u64 XCI::GetProgramTitleID() const { | ||
| 119 | return secure_partition->GetProgramTitleID(); | ||
| 120 | } | ||
| 121 | |||
| 122 | std::shared_ptr<NCA> XCI::GetProgramNCA() const { | ||
| 123 | return program; | ||
| 124 | } | ||
| 125 | |||
| 126 | VirtualFile XCI::GetProgramNCAFile() const { | ||
| 127 | if (GetProgramNCA() == nullptr) | ||
| 128 | return nullptr; | ||
| 129 | return GetProgramNCA()->GetBaseFile(); | ||
| 130 | } | ||
| 131 | |||
| 108 | const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { | 132 | const 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 | ||
| 20 | class NCA; | 20 | class NCA; |
| 21 | enum class NCAContentType : u8; | 21 | enum class NCAContentType : u8; |
| 22 | class NSP; | ||
| 22 | 23 | ||
| 23 | enum class GamecardSize : u8 { | 24 | enum 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 | ||
| 23 | const LanguageEntry& NACP::GetLanguageEntry(Language language) const { | 23 | const 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 | ||
| 27 | std::string NACP::GetApplicationName(Language language) const { | 37 | 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 @@ | |||
| 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 | ||
| 14 | namespace FileSys { | 15 | namespace 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 | ||
| 66 | static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { | 69 | static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { |
| @@ -75,9 +78,9 @@ static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { | |||
| 75 | class NACP { | 78 | class NACP { |
| 76 | public: | 79 | public: |
| 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 | ||
| 361 | static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { | 362 | static 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 | ||
| 369 | InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, | 369 | InstallResult 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 | |||
| 374 | InstallResult 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 @@ | |||
| 17 | namespace FileSys { | 17 | namespace FileSys { |
| 18 | class CNMT; | 18 | class CNMT; |
| 19 | class NCA; | 19 | class NCA; |
| 20 | class NSP; | ||
| 20 | class XCI; | 21 | class XCI; |
| 21 | 22 | ||
| 22 | enum class ContentRecordType : u8; | 23 | enum 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 | |||
| 14 | namespace FileSys { | ||
| 15 | NSP::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 | |||
| 99 | NSP::~NSP() = default; | ||
| 100 | |||
| 101 | Loader::ResultStatus NSP::GetStatus() const { | ||
| 102 | return status; | ||
| 103 | } | ||
| 104 | |||
| 105 | Loader::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 | |||
| 112 | u64 NSP::GetFirstTitleID() const { | ||
| 113 | if (program_status.empty()) | ||
| 114 | return 0; | ||
| 115 | return program_status.begin()->first; | ||
| 116 | } | ||
| 117 | |||
| 118 | u64 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 | |||
| 129 | std::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 | |||
| 137 | bool NSP::IsExtractedType() const { | ||
| 138 | return extracted; | ||
| 139 | } | ||
| 140 | |||
| 141 | VirtualFile NSP::GetRomFS() const { | ||
| 142 | return romfs; | ||
| 143 | } | ||
| 144 | |||
| 145 | VirtualDir NSP::GetExeFS() const { | ||
| 146 | return exefs; | ||
| 147 | } | ||
| 148 | |||
| 149 | std::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 | |||
| 160 | std::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 | |||
| 171 | std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const { | ||
| 172 | return ncas; | ||
| 173 | } | ||
| 174 | |||
| 175 | std::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 | |||
| 190 | VirtualFile 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 | |||
| 199 | std::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 | |||
| 217 | std::vector<VirtualFile> NSP::GetFiles() const { | ||
| 218 | return pfs->GetFiles(); | ||
| 219 | } | ||
| 220 | |||
| 221 | std::vector<VirtualDir> NSP::GetSubdirectories() const { | ||
| 222 | return pfs->GetSubdirectories(); | ||
| 223 | } | ||
| 224 | |||
| 225 | std::string NSP::GetName() const { | ||
| 226 | return file->GetName(); | ||
| 227 | } | ||
| 228 | |||
| 229 | VirtualDir NSP::GetParentDirectory() const { | ||
| 230 | return file->GetContainingDirectory(); | ||
| 231 | } | ||
| 232 | |||
| 233 | bool 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 | |||
| 17 | namespace FileSys { | ||
| 18 | |||
| 19 | class PartitionFilesystem; | ||
| 20 | |||
| 21 | class NSP : public ReadOnlyVfsDirectory { | ||
| 22 | public: | ||
| 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 | |||
| 55 | protected: | ||
| 56 | bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||
| 57 | |||
| 58 | private: | ||
| 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 | ||
| 161 | ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) { | 161 | ResultStatus 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 | ||
| 20 | namespace Loader { | 21 | namespace 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 | ||
| 90 | constexpr std::array<const char*, 49> RESULT_MESSAGES{ | 96 | constexpr 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 | ||
| 142 | std::ostream& operator<<(std::ostream& os, ResultStatus status) { | 148 | std::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 | ||
| 110 | std::ostream& operator<<(std::ostream& os, ResultStatus status); | 112 | std::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 | |||
| 19 | namespace Loader { | ||
| 20 | |||
| 21 | AppLoader_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 | |||
| 51 | AppLoader_NSP::~AppLoader_NSP() = default; | ||
| 52 | |||
| 53 | FileType 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 | |||
| 75 | ResultStatus 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 | |||
| 111 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) { | ||
| 112 | return secondary_loader->ReadRomFS(dir); | ||
| 113 | } | ||
| 114 | |||
| 115 | ResultStatus 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 | |||
| 122 | ResultStatus 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 | |||
| 129 | ResultStatus 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 | |||
| 12 | namespace FileSys { | ||
| 13 | class NACP; | ||
| 14 | class NSP; | ||
| 15 | } // namespace FileSys | ||
| 16 | |||
| 17 | namespace Loader { | ||
| 18 | |||
| 19 | class AppLoader_NCA; | ||
| 20 | |||
| 21 | /// Loads an XCI file | ||
| 22 | class AppLoader_NSP final : public AppLoader { | ||
| 23 | public: | ||
| 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 | |||
| 45 | private: | ||
| 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 | ||
| 18 | AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) | 18 | AppLoader_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 | ||
| 435 | const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; | 435 | const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; |
| 436 | 436 | ||
| 437 | static bool HasSupportedFileExtension(const std::string& file_name) { | 437 | static 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 | */ |
| 77 | enum class CalloutFlag : uint32_t { | 79 | enum class CalloutFlag : uint32_t { |
| 78 | Telemetry = 0x1, | 80 | Telemetry = 0x1, |
| 81 | DRDDeprecation = 0x2, | ||
| 79 | }; | 82 | }; |
| 80 | 83 | ||
| 81 | static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { | 84 | static 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 | ||
| 747 | void GMainWindow::OnMenuInstallToNAND() { | 767 | void 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 { |