mirror of
				https://git.zaroz.cloud/nintendo-back-up/yuzu/yuzu-mainline.git
				synced 2025-03-21 01:53:15 +00:00 
			
		
		
		
	Merge pull request #1178 from DarkLordZach/nsp
file_sys: Add Nintendo Submissions Package (NSP) file format
This commit is contained in:
		
						commit
						faa9e066ab
					
				| @ -49,6 +49,8 @@ add_library(core STATIC | ||||
|     file_sys/savedata_factory.h | ||||
|     file_sys/sdmc_factory.cpp | ||||
|     file_sys/sdmc_factory.h | ||||
|     file_sys/submission_package.cpp | ||||
|     file_sys/submission_package.h | ||||
|     file_sys/vfs.cpp | ||||
|     file_sys/vfs.h | ||||
|     file_sys/vfs_concat.cpp | ||||
| @ -359,6 +361,8 @@ add_library(core STATIC | ||||
|     loader/nro.h | ||||
|     loader/nso.cpp | ||||
|     loader/nso.h | ||||
|     loader/nsp.cpp | ||||
|     loader/nsp.h | ||||
|     loader/xci.cpp | ||||
|     loader/xci.h | ||||
|     memory.cpp | ||||
|  | ||||
| @ -231,18 +231,28 @@ void KeyManager::WriteKeyToFile(bool title_key, std::string_view keyname, | ||||
| } | ||||
| 
 | ||||
| void KeyManager::SetKey(S128KeyType id, Key128 key, u64 field1, u64 field2) { | ||||
|     const auto iter = std::find_if( | ||||
|     if (s128_keys.find({id, field1, field2}) != s128_keys.end()) | ||||
|         return; | ||||
|     if (id == S128KeyType::Titlekey) { | ||||
|         Key128 rights_id; | ||||
|         std::memcpy(rights_id.data(), &field2, sizeof(u64)); | ||||
|         std::memcpy(rights_id.data() + sizeof(u64), &field1, sizeof(u64)); | ||||
|         WriteKeyToFile(true, Common::HexArrayToString(rights_id), key); | ||||
|     } | ||||
|     const auto iter2 = std::find_if( | ||||
|         s128_file_id.begin(), s128_file_id.end(), | ||||
|         [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { | ||||
|             return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == | ||||
|                    std::tie(id, field1, field2); | ||||
|         }); | ||||
|     if (iter != s128_file_id.end()) | ||||
|         WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key); | ||||
|     if (iter2 != s128_file_id.end()) | ||||
|         WriteKeyToFile(false, iter2->first, key); | ||||
|     s128_keys[{id, field1, field2}] = key; | ||||
| } | ||||
| 
 | ||||
| void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { | ||||
|     if (s256_keys.find({id, field1, field2}) != s256_keys.end()) | ||||
|         return; | ||||
|     const auto iter = std::find_if( | ||||
|         s256_file_id.begin(), s256_file_id.end(), | ||||
|         [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) { | ||||
|  | ||||
| @ -17,6 +17,8 @@ enum class ResultStatus : u16; | ||||
| 
 | ||||
| namespace Core::Crypto { | ||||
| 
 | ||||
| constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180; | ||||
| 
 | ||||
| using Key128 = std::array<u8, 0x10>; | ||||
| using Key256 = std::array<u8, 0x20>; | ||||
| using SHA256Hash = std::array<u8, 0x20>; | ||||
|  | ||||
| @ -10,7 +10,9 @@ | ||||
| #include "common/logging/log.h" | ||||
| #include "core/file_sys/card_image.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/partition_filesystem.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/file_sys/vfs_offset.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| @ -44,15 +46,19 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) { | ||||
|             partitions[static_cast<size_t>(partition)] = std::make_shared<PartitionFilesystem>(raw); | ||||
|     } | ||||
| 
 | ||||
|     secure_partition = std::make_shared<NSP>( | ||||
|         main_hfs.GetFile(partition_names[static_cast<size_t>(XCIPartition::Secure)])); | ||||
| 
 | ||||
|     const auto secure_ncas = secure_partition->GetNCAsCollapsed(); | ||||
|     std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); | ||||
| 
 | ||||
|     program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA; | ||||
|     program = | ||||
|         secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); | ||||
|     if (program != nullptr) | ||||
|         program_nca_status = program->GetStatus(); | ||||
| 
 | ||||
|     auto result = AddNCAFromPartition(XCIPartition::Secure); | ||||
|     if (result != Loader::ResultStatus::Success) { | ||||
|         status = result; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     result = AddNCAFromPartition(XCIPartition::Update); | ||||
|     auto result = AddNCAFromPartition(XCIPartition::Update); | ||||
|     if (result != Loader::ResultStatus::Success) { | ||||
|         status = result; | ||||
|         return; | ||||
| @ -89,6 +95,10 @@ VirtualDir XCI::GetPartition(XCIPartition partition) const { | ||||
|     return partitions[static_cast<size_t>(partition)]; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const { | ||||
|     return secure_partition; | ||||
| } | ||||
| 
 | ||||
| VirtualDir XCI::GetSecurePartition() const { | ||||
|     return GetPartition(XCIPartition::Secure); | ||||
| } | ||||
| @ -105,6 +115,20 @@ VirtualDir XCI::GetLogoPartition() const { | ||||
|     return GetPartition(XCIPartition::Logo); | ||||
| } | ||||
| 
 | ||||
| u64 XCI::GetProgramTitleID() const { | ||||
|     return secure_partition->GetProgramTitleID(); | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<NCA> XCI::GetProgramNCA() const { | ||||
|     return program; | ||||
| } | ||||
| 
 | ||||
| VirtualFile XCI::GetProgramNCAFile() const { | ||||
|     if (GetProgramNCA() == nullptr) | ||||
|         return nullptr; | ||||
|     return GetProgramNCA()->GetBaseFile(); | ||||
| } | ||||
| 
 | ||||
| const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const { | ||||
|     return ncas; | ||||
| } | ||||
|  | ||||
| @ -19,6 +19,7 @@ namespace FileSys { | ||||
| 
 | ||||
| class NCA; | ||||
| enum class NCAContentType : u8; | ||||
| class NSP; | ||||
| 
 | ||||
| enum class GamecardSize : u8 { | ||||
|     S_1GB = 0xFA, | ||||
| @ -71,11 +72,16 @@ public: | ||||
|     u8 GetFormatVersion() const; | ||||
| 
 | ||||
|     VirtualDir GetPartition(XCIPartition partition) const; | ||||
|     std::shared_ptr<NSP> GetSecurePartitionNSP() const; | ||||
|     VirtualDir GetSecurePartition() const; | ||||
|     VirtualDir GetNormalPartition() const; | ||||
|     VirtualDir GetUpdatePartition() const; | ||||
|     VirtualDir GetLogoPartition() const; | ||||
| 
 | ||||
|     u64 GetProgramTitleID() const; | ||||
| 
 | ||||
|     std::shared_ptr<NCA> GetProgramNCA() const; | ||||
|     VirtualFile GetProgramNCAFile() const; | ||||
|     const std::vector<std::shared_ptr<NCA>>& GetNCAs() const; | ||||
|     std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; | ||||
|     VirtualFile GetNCAFileByType(NCAContentType type) const; | ||||
| @ -101,6 +107,8 @@ private: | ||||
|     Loader::ResultStatus program_nca_status; | ||||
| 
 | ||||
|     std::vector<VirtualDir> partitions; | ||||
|     std::shared_ptr<NSP> secure_partition; | ||||
|     std::shared_ptr<NCA> program; | ||||
|     std::vector<std::shared_ptr<NCA>> ncas; | ||||
| }; | ||||
| } // namespace FileSys
 | ||||
|  | ||||
| @ -21,7 +21,17 @@ NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) { | ||||
| } | ||||
| 
 | ||||
| const LanguageEntry& NACP::GetLanguageEntry(Language language) const { | ||||
|     return raw->language_entries.at(static_cast<u8>(language)); | ||||
|     if (language != Language::Default) { | ||||
|         return raw->language_entries.at(static_cast<u8>(language)); | ||||
|     } else { | ||||
|         for (const auto& language_entry : raw->language_entries) { | ||||
|             if (!language_entry.GetApplicationName().empty()) | ||||
|                 return language_entry; | ||||
|         } | ||||
| 
 | ||||
|         // Fallback to English
 | ||||
|         return GetLanguageEntry(Language::AmericanEnglish); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| std::string NACP::GetApplicationName(Language language) const { | ||||
|  | ||||
| @ -9,6 +9,7 @@ | ||||
| #include <string> | ||||
| #include "common/common_funcs.h" | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| @ -61,6 +62,8 @@ enum class Language : u8 { | ||||
|     Korean = 12, | ||||
|     Taiwanese = 13, | ||||
|     Chinese = 14, | ||||
| 
 | ||||
|     Default = 255, | ||||
| }; | ||||
| 
 | ||||
| static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { | ||||
| @ -75,9 +78,9 @@ static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { | ||||
| class NACP { | ||||
| public: | ||||
|     explicit NACP(VirtualFile file); | ||||
|     const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const; | ||||
|     std::string GetApplicationName(Language language = Language::AmericanEnglish) const; | ||||
|     std::string GetDeveloperName(Language language = Language::AmericanEnglish) const; | ||||
|     const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const; | ||||
|     std::string GetApplicationName(Language language = Language::Default) const; | ||||
|     std::string GetDeveloperName(Language language = Language::Default) const; | ||||
|     u64 GetTitleId() const; | ||||
|     std::string GetVersionString() const; | ||||
| 
 | ||||
|  | ||||
| @ -13,6 +13,7 @@ | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/file_sys/vfs_concat.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| @ -358,17 +359,21 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter( | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { | ||||
|     const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false)); | ||||
|     const auto iter = | ||||
|         std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), | ||||
|                      [&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; }); | ||||
|     return iter == xci->GetNCAs().end() ? nullptr : *iter; | ||||
| static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) { | ||||
|     const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false))); | ||||
|     if (file == nullptr) | ||||
|         return nullptr; | ||||
|     return std::make_shared<NCA>(file); | ||||
| } | ||||
| 
 | ||||
| InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, | ||||
|                                             const VfsCopyFunction& copy) { | ||||
|     const auto& ncas = xci->GetNCAs(); | ||||
|     return InstallEntry(xci->GetSecurePartitionNSP(), overwrite_if_exists, copy); | ||||
| } | ||||
| 
 | ||||
| InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists, | ||||
|                                             const VfsCopyFunction& copy) { | ||||
|     const auto& ncas = nsp->GetNCAsCollapsed(); | ||||
|     const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { | ||||
|         return nca->GetType() == NCAContentType::Meta; | ||||
|     }); | ||||
| @ -392,7 +397,7 @@ InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overw | ||||
|     const auto cnmt_file = section0->GetFiles()[0]; | ||||
|     const CNMT cnmt(cnmt_file); | ||||
|     for (const auto& record : cnmt.GetContentRecords()) { | ||||
|         const auto nca = GetNCAFromXCIForID(xci, record.nca_id); | ||||
|         const auto nca = GetNCAFromNSPForID(nsp, record.nca_id); | ||||
|         if (nca == nullptr) | ||||
|             return InstallResult::ErrorCopyFailed; | ||||
|         const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); | ||||
|  | ||||
| @ -17,6 +17,7 @@ | ||||
| namespace FileSys { | ||||
| class CNMT; | ||||
| class NCA; | ||||
| class NSP; | ||||
| class XCI; | ||||
| 
 | ||||
| enum class ContentRecordType : u8; | ||||
| @ -89,10 +90,12 @@ public: | ||||
|         boost::optional<ContentRecordType> record_type = boost::none, | ||||
|         boost::optional<u64> title_id = boost::none) const; | ||||
| 
 | ||||
|     // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there
 | ||||
|     // is a meta NCA and all of them are accessible.
 | ||||
|     // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
 | ||||
|     // there is a meta NCA and all of them are accessible.
 | ||||
|     InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, | ||||
|                                const VfsCopyFunction& copy = &VfsRawCopy); | ||||
|     InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false, | ||||
|                                const VfsCopyFunction& copy = &VfsRawCopy); | ||||
| 
 | ||||
|     // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
 | ||||
|     // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
 | ||||
|  | ||||
							
								
								
									
										236
									
								
								src/core/file_sys/submission_package.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										236
									
								
								src/core/file_sys/submission_package.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,236 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <fmt/ostream.h> | ||||
| #include "common/assert.h" | ||||
| #include "common/hex_util.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/partition_filesystem.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| NSP::NSP(VirtualFile file_) | ||||
|     : file(std::move(file_)), | ||||
|       pfs(std::make_shared<PartitionFilesystem>(file)), status{Loader::ResultStatus::Success} { | ||||
|     if (pfs->GetStatus() != Loader::ResultStatus::Success) { | ||||
|         status = pfs->GetStatus(); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     if (IsDirectoryExeFS(pfs)) { | ||||
|         extracted = true; | ||||
|         exefs = pfs; | ||||
| 
 | ||||
|         const auto& files = pfs->GetFiles(); | ||||
|         const auto romfs_iter = | ||||
|             std::find_if(files.begin(), files.end(), [](const FileSys::VirtualFile& file) { | ||||
|                 return file->GetName().find(".romfs") != std::string::npos; | ||||
|             }); | ||||
|         if (romfs_iter != files.end()) | ||||
|             romfs = *romfs_iter; | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     extracted = false; | ||||
|     const auto files = pfs->GetFiles(); | ||||
| 
 | ||||
|     Core::Crypto::KeyManager keys; | ||||
|     for (const auto& ticket_file : files) { | ||||
|         if (ticket_file->GetExtension() == "tik") { | ||||
|             if (ticket_file == nullptr || | ||||
|                 ticket_file->GetSize() < | ||||
|                     Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             Core::Crypto::Key128 key{}; | ||||
|             ticket_file->Read(key.data(), key.size(), Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||||
|             std::string_view name_only(ticket_file->GetName()); | ||||
|             name_only.remove_suffix(4); | ||||
|             const auto rights_id_raw = Common::HexStringToArray<16>(name_only); | ||||
|             u128 rights_id; | ||||
|             std::memcpy(rights_id.data(), rights_id_raw.data(), sizeof(u128)); | ||||
|             keys.SetKey(Core::Crypto::S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     for (const auto& outer_file : files) { | ||||
|         if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") { | ||||
|             const auto nca = std::make_shared<NCA>(outer_file); | ||||
|             if (nca->GetStatus() != Loader::ResultStatus::Success) | ||||
|                 continue; | ||||
|             const auto section0 = nca->GetSubdirectories()[0]; | ||||
| 
 | ||||
|             for (const auto& inner_file : section0->GetFiles()) { | ||||
|                 if (inner_file->GetExtension() != "cnmt") | ||||
|                     continue; | ||||
| 
 | ||||
|                 const CNMT cnmt(inner_file); | ||||
|                 auto& ncas_title = ncas[cnmt.GetTitleID()]; | ||||
| 
 | ||||
|                 ncas_title[ContentRecordType::Meta] = nca; | ||||
|                 for (const auto& rec : cnmt.GetContentRecords()) { | ||||
|                     const auto id_string = Common::HexArrayToString(rec.nca_id, false); | ||||
|                     const auto next_file = pfs->GetFile(fmt::format("{}.nca", id_string)); | ||||
|                     if (next_file == nullptr) { | ||||
|                         LOG_WARNING(Service_FS, | ||||
|                                     "NCA with ID {}.nca is listed in content metadata, but cannot " | ||||
|                                     "be found in PFS. NSP appears to be corrupted.", | ||||
|                                     id_string); | ||||
|                         continue; | ||||
|                     } | ||||
| 
 | ||||
|                     auto next_nca = std::make_shared<NCA>(next_file); | ||||
|                     if (next_nca->GetType() == NCAContentType::Program) | ||||
|                         program_status[cnmt.GetTitleID()] = next_nca->GetStatus(); | ||||
|                     if (next_nca->GetStatus() == Loader::ResultStatus::Success) | ||||
|                         ncas_title[rec.type] = std::move(next_nca); | ||||
|                 } | ||||
| 
 | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| NSP::~NSP() = default; | ||||
| 
 | ||||
| Loader::ResultStatus NSP::GetStatus() const { | ||||
|     return status; | ||||
| } | ||||
| 
 | ||||
| Loader::ResultStatus NSP::GetProgramStatus(u64 title_id) const { | ||||
|     const auto iter = program_status.find(title_id); | ||||
|     if (iter == program_status.end()) | ||||
|         return Loader::ResultStatus::ErrorNSPMissingProgramNCA; | ||||
|     return iter->second; | ||||
| } | ||||
| 
 | ||||
| u64 NSP::GetFirstTitleID() const { | ||||
|     if (program_status.empty()) | ||||
|         return 0; | ||||
|     return program_status.begin()->first; | ||||
| } | ||||
| 
 | ||||
| u64 NSP::GetProgramTitleID() const { | ||||
|     const auto out = GetFirstTitleID(); | ||||
|     if ((out & 0x800) == 0) | ||||
|         return out; | ||||
| 
 | ||||
|     const auto ids = GetTitleIDs(); | ||||
|     const auto iter = | ||||
|         std::find_if(ids.begin(), ids.end(), [](u64 tid) { return (tid & 0x800) == 0; }); | ||||
|     return iter == ids.end() ? out : *iter; | ||||
| } | ||||
| 
 | ||||
| std::vector<u64> NSP::GetTitleIDs() const { | ||||
|     std::vector<u64> out; | ||||
|     out.reserve(ncas.size()); | ||||
|     for (const auto& kv : ncas) | ||||
|         out.push_back(kv.first); | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| bool NSP::IsExtractedType() const { | ||||
|     return extracted; | ||||
| } | ||||
| 
 | ||||
| VirtualFile NSP::GetRomFS() const { | ||||
|     return romfs; | ||||
| } | ||||
| 
 | ||||
| VirtualDir NSP::GetExeFS() const { | ||||
|     return exefs; | ||||
| } | ||||
| 
 | ||||
| std::vector<std::shared_ptr<NCA>> NSP::GetNCAsCollapsed() const { | ||||
|     if (extracted) | ||||
|         LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||||
|     std::vector<std::shared_ptr<NCA>> out; | ||||
|     for (const auto& map : ncas) { | ||||
|         for (const auto& inner_map : map.second) | ||||
|             out.push_back(inner_map.second); | ||||
|     } | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::multimap<u64, std::shared_ptr<NCA>> NSP::GetNCAsByTitleID() const { | ||||
|     if (extracted) | ||||
|         LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||||
|     std::multimap<u64, std::shared_ptr<NCA>> out; | ||||
|     for (const auto& map : ncas) { | ||||
|         for (const auto& inner_map : map.second) | ||||
|             out.emplace(map.first, inner_map.second); | ||||
|     } | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> NSP::GetNCAs() const { | ||||
|     return ncas; | ||||
| } | ||||
| 
 | ||||
| std::shared_ptr<NCA> NSP::GetNCA(u64 title_id, ContentRecordType type) const { | ||||
|     if (extracted) | ||||
|         LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||||
| 
 | ||||
|     const auto title_id_iter = ncas.find(title_id); | ||||
|     if (title_id_iter == ncas.end()) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     const auto type_iter = title_id_iter->second.find(type); | ||||
|     if (type_iter == title_id_iter->second.end()) | ||||
|         return nullptr; | ||||
| 
 | ||||
|     return type_iter->second; | ||||
| } | ||||
| 
 | ||||
| VirtualFile NSP::GetNCAFile(u64 title_id, ContentRecordType type) const { | ||||
|     if (extracted) | ||||
|         LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||||
|     const auto nca = GetNCA(title_id, type); | ||||
|     if (nca != nullptr) | ||||
|         return nca->GetBaseFile(); | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| std::vector<Core::Crypto::Key128> NSP::GetTitlekey() const { | ||||
|     if (extracted) | ||||
|         LOG_WARNING(Service_FS, "called on an NSP that is of type extracted."); | ||||
|     std::vector<Core::Crypto::Key128> out; | ||||
|     for (const auto& ticket_file : ticket_files) { | ||||
|         if (ticket_file == nullptr || | ||||
|             ticket_file->GetSize() < | ||||
|                 Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET + sizeof(Core::Crypto::Key128)) { | ||||
|             continue; | ||||
|         } | ||||
| 
 | ||||
|         out.emplace_back(); | ||||
|         ticket_file->Read(out.back().data(), out.back().size(), | ||||
|                           Core::Crypto::TICKET_FILE_TITLEKEY_OFFSET); | ||||
|     } | ||||
|     return out; | ||||
| } | ||||
| 
 | ||||
| std::vector<VirtualFile> NSP::GetFiles() const { | ||||
|     return pfs->GetFiles(); | ||||
| } | ||||
| 
 | ||||
| std::vector<VirtualDir> NSP::GetSubdirectories() const { | ||||
|     return pfs->GetSubdirectories(); | ||||
| } | ||||
| 
 | ||||
| std::string NSP::GetName() const { | ||||
|     return file->GetName(); | ||||
| } | ||||
| 
 | ||||
| VirtualDir NSP::GetParentDirectory() const { | ||||
|     return file->GetContainingDirectory(); | ||||
| } | ||||
| 
 | ||||
| bool NSP::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | ||||
|     return false; | ||||
| } | ||||
| } // namespace FileSys
 | ||||
							
								
								
									
										73
									
								
								src/core/file_sys/submission_package.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/core/file_sys/submission_package.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,73 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <array> | ||||
| #include <map> | ||||
| #include <vector> | ||||
| #include "common/common_types.h" | ||||
| #include "common/swap.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/romfs_factory.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| 
 | ||||
| class PartitionFilesystem; | ||||
| 
 | ||||
| class NSP : public ReadOnlyVfsDirectory { | ||||
| public: | ||||
|     explicit NSP(VirtualFile file); | ||||
|     ~NSP(); | ||||
| 
 | ||||
|     Loader::ResultStatus GetStatus() const; | ||||
|     Loader::ResultStatus GetProgramStatus(u64 title_id) const; | ||||
|     // Should only be used when one title id can be assured.
 | ||||
|     u64 GetFirstTitleID() const; | ||||
|     u64 GetProgramTitleID() const; | ||||
|     std::vector<u64> GetTitleIDs() const; | ||||
| 
 | ||||
|     bool IsExtractedType() const; | ||||
| 
 | ||||
|     // Common (Can be safely called on both types)
 | ||||
|     VirtualFile GetRomFS() const; | ||||
|     VirtualDir GetExeFS() const; | ||||
| 
 | ||||
|     // Type 0 Only (Collection of NCAs + Certificate + Ticket + Meta XML)
 | ||||
|     std::vector<std::shared_ptr<NCA>> GetNCAsCollapsed() const; | ||||
|     std::multimap<u64, std::shared_ptr<NCA>> GetNCAsByTitleID() const; | ||||
|     std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> GetNCAs() const; | ||||
|     std::shared_ptr<NCA> GetNCA(u64 title_id, ContentRecordType type) const; | ||||
|     VirtualFile GetNCAFile(u64 title_id, ContentRecordType type) const; | ||||
|     std::vector<Core::Crypto::Key128> GetTitlekey() const; | ||||
| 
 | ||||
|     std::vector<VirtualFile> GetFiles() const override; | ||||
| 
 | ||||
|     std::vector<VirtualDir> GetSubdirectories() const override; | ||||
| 
 | ||||
|     std::string GetName() const override; | ||||
| 
 | ||||
|     VirtualDir GetParentDirectory() const override; | ||||
| 
 | ||||
| protected: | ||||
|     bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; | ||||
| 
 | ||||
| private: | ||||
|     VirtualFile file; | ||||
| 
 | ||||
|     bool extracted; | ||||
|     Loader::ResultStatus status; | ||||
|     std::map<u64, Loader::ResultStatus> program_status; | ||||
| 
 | ||||
|     std::shared_ptr<PartitionFilesystem> pfs; | ||||
|     // Map title id -> {map type -> NCA}
 | ||||
|     std::map<u64, std::map<ContentRecordType, std::shared_ptr<NCA>>> ncas; | ||||
|     std::vector<VirtualFile> ticket_files; | ||||
| 
 | ||||
|     VirtualFile romfs; | ||||
|     VirtualDir exefs; | ||||
| }; | ||||
| } // namespace FileSys
 | ||||
| @ -61,7 +61,6 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys | ||||
| 
 | ||||
|     if (nacp_file != nullptr) { | ||||
|         FileSys::NACP nacp(nacp_file); | ||||
|         title_id = nacp.GetTitleId(); | ||||
|         name = nacp.GetApplicationName(); | ||||
|     } | ||||
| } | ||||
| @ -120,6 +119,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load( | ||||
|     } | ||||
| 
 | ||||
|     auto& kernel = Core::System::GetInstance().Kernel(); | ||||
|     title_id = metadata.GetTitleID(); | ||||
|     process->program_id = metadata.GetTitleID(); | ||||
|     process->svc_access_mask.set(); | ||||
|     process->resource_limit = | ||||
| @ -159,8 +159,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buff | ||||
| } | ||||
| 
 | ||||
| ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) { | ||||
|     if (name.empty()) | ||||
|         return ResultStatus::ErrorNoControl; | ||||
|     out_program_id = title_id; | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
|  | ||||
| @ -15,6 +15,7 @@ | ||||
| #include "core/loader/nca.h" | ||||
| #include "core/loader/nro.h" | ||||
| #include "core/loader/nso.h" | ||||
| #include "core/loader/nsp.h" | ||||
| #include "core/loader/xci.h" | ||||
| 
 | ||||
| namespace Loader { | ||||
| @ -34,6 +35,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) { | ||||
|     CHECK_TYPE(NCA) | ||||
|     CHECK_TYPE(XCI) | ||||
|     CHECK_TYPE(NAX) | ||||
|     CHECK_TYPE(NSP) | ||||
| 
 | ||||
| #undef CHECK_TYPE | ||||
| 
 | ||||
| @ -59,6 +61,8 @@ FileType GuessFromFilename(const std::string& name) { | ||||
|         return FileType::NCA; | ||||
|     if (extension == "xci") | ||||
|         return FileType::XCI; | ||||
|     if (extension == "nsp") | ||||
|         return FileType::NSP; | ||||
| 
 | ||||
|     return FileType::Unknown; | ||||
| } | ||||
| @ -77,6 +81,8 @@ std::string GetFileTypeString(FileType type) { | ||||
|         return "XCI"; | ||||
|     case FileType::NAX: | ||||
|         return "NAX"; | ||||
|     case FileType::NSP: | ||||
|         return "NSP"; | ||||
|     case FileType::DeconstructedRomDirectory: | ||||
|         return "Directory"; | ||||
|     case FileType::Error: | ||||
| @ -87,7 +93,7 @@ std::string GetFileTypeString(FileType type) { | ||||
|     return "unknown"; | ||||
| } | ||||
| 
 | ||||
| constexpr std::array<const char*, 49> RESULT_MESSAGES{ | ||||
| constexpr std::array<const char*, 50> RESULT_MESSAGES{ | ||||
|     "The operation completed successfully.", | ||||
|     "The loader requested to load is already loaded.", | ||||
|     "The operation is not implemented.", | ||||
| @ -137,7 +143,7 @@ constexpr std::array<const char*, 49> RESULT_MESSAGES{ | ||||
|     "The AES Key Generation Source could not be found.", | ||||
|     "The SD Save Key Source could not be found.", | ||||
|     "The SD NCA Key Source could not be found.", | ||||
| }; | ||||
|     "The NSP file is missing a Program-type NCA."}; | ||||
| 
 | ||||
| std::ostream& operator<<(std::ostream& os, ResultStatus status) { | ||||
|     os << RESULT_MESSAGES.at(static_cast<size_t>(status)); | ||||
| @ -182,6 +188,10 @@ static std::unique_ptr<AppLoader> GetFileLoader(FileSys::VirtualFile file, FileT | ||||
|     case FileType::NAX: | ||||
|         return std::make_unique<AppLoader_NAX>(std::move(file)); | ||||
| 
 | ||||
|     // NX NSP (Nintendo Submission Package) file format
 | ||||
|     case FileType::NSP: | ||||
|         return std::make_unique<AppLoader_NSP>(std::move(file)); | ||||
| 
 | ||||
|     // NX deconstructed ROM directory.
 | ||||
|     case FileType::DeconstructedRomDirectory: | ||||
|         return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); | ||||
|  | ||||
| @ -29,6 +29,7 @@ enum class FileType { | ||||
|     NSO, | ||||
|     NRO, | ||||
|     NCA, | ||||
|     NSP, | ||||
|     XCI, | ||||
|     NAX, | ||||
|     DeconstructedRomDirectory, | ||||
| @ -105,6 +106,7 @@ enum class ResultStatus : u16 { | ||||
|     ErrorMissingAESKeyGenerationSource, | ||||
|     ErrorMissingSDSaveKeySource, | ||||
|     ErrorMissingSDNCAKeySource, | ||||
|     ErrorNSPMissingProgramNCA, | ||||
| }; | ||||
| 
 | ||||
| std::ostream& operator<<(std::ostream& os, ResultStatus status); | ||||
|  | ||||
							
								
								
									
										135
									
								
								src/core/loader/nsp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								src/core/loader/nsp.cpp
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,135 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #include <vector> | ||||
| 
 | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/card_image.h" | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/control_metadata.h" | ||||
| #include "core/file_sys/nca_metadata.h" | ||||
| #include "core/file_sys/romfs.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/loader/deconstructed_rom_directory.h" | ||||
| #include "core/loader/nca.h" | ||||
| #include "core/loader/nsp.h" | ||||
| 
 | ||||
| namespace Loader { | ||||
| 
 | ||||
| AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file) | ||||
|     : AppLoader(file), nsp(std::make_unique<FileSys::NSP>(file)), | ||||
|       title_id(nsp->GetProgramTitleID()) { | ||||
| 
 | ||||
|     if (nsp->GetStatus() != ResultStatus::Success) | ||||
|         return; | ||||
|     if (nsp->IsExtractedType()) | ||||
|         return; | ||||
| 
 | ||||
|     const auto control_nca = | ||||
|         nsp->GetNCA(nsp->GetFirstTitleID(), FileSys::ContentRecordType::Control); | ||||
|     if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) | ||||
|         return; | ||||
| 
 | ||||
|     const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS()); | ||||
|     if (romfs == nullptr) | ||||
|         return; | ||||
| 
 | ||||
|     for (const auto& language : FileSys::LANGUAGE_NAMES) { | ||||
|         icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat"); | ||||
|         if (icon_file != nullptr) | ||||
|             break; | ||||
|     } | ||||
| 
 | ||||
|     const auto nacp_raw = romfs->GetFile("control.nacp"); | ||||
|     if (nacp_raw == nullptr) | ||||
|         return; | ||||
|     nacp_file = std::make_shared<FileSys::NACP>(nacp_raw); | ||||
| } | ||||
| 
 | ||||
| AppLoader_NSP::~AppLoader_NSP() = default; | ||||
| 
 | ||||
| FileType AppLoader_NSP::IdentifyType(const FileSys::VirtualFile& file) { | ||||
|     FileSys::NSP nsp(file); | ||||
| 
 | ||||
|     if (nsp.GetStatus() == ResultStatus::Success) { | ||||
|         // Extracted Type case
 | ||||
|         if (nsp.IsExtractedType() && nsp.GetExeFS() != nullptr && | ||||
|             FileSys::IsDirectoryExeFS(nsp.GetExeFS()) && nsp.GetRomFS() != nullptr) { | ||||
|             return FileType::NSP; | ||||
|         } | ||||
| 
 | ||||
|         // Non-Ectracted Type case
 | ||||
|         if (!nsp.IsExtractedType() && | ||||
|             nsp.GetNCA(nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program) != nullptr && | ||||
|             AppLoader_NCA::IdentifyType(nsp.GetNCAFile( | ||||
|                 nsp.GetFirstTitleID(), FileSys::ContentRecordType::Program)) == FileType::NCA) { | ||||
|             return FileType::NSP; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     return FileType::Error; | ||||
| } | ||||
| 
 | ||||
| ResultStatus AppLoader_NSP::Load(Kernel::SharedPtr<Kernel::Process>& process) { | ||||
|     if (is_loaded) { | ||||
|         return ResultStatus::ErrorAlreadyLoaded; | ||||
|     } | ||||
| 
 | ||||
|     if (nsp->IsExtractedType()) { | ||||
|         secondary_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(nsp->GetExeFS()); | ||||
|     } else { | ||||
|         if (title_id == 0) | ||||
|             return ResultStatus::ErrorNSPMissingProgramNCA; | ||||
| 
 | ||||
|         secondary_loader = std::make_unique<AppLoader_NCA>( | ||||
|             nsp->GetNCAFile(title_id, FileSys::ContentRecordType::Program)); | ||||
| 
 | ||||
|         if (nsp->GetStatus() != ResultStatus::Success) | ||||
|             return nsp->GetStatus(); | ||||
| 
 | ||||
|         if (nsp->GetProgramStatus(title_id) != ResultStatus::Success) | ||||
|             return nsp->GetProgramStatus(title_id); | ||||
| 
 | ||||
|         if (nsp->GetNCA(title_id, FileSys::ContentRecordType::Program) == nullptr) { | ||||
|             if (!Core::Crypto::KeyManager::KeyFileExists(false)) | ||||
|                 return ResultStatus::ErrorMissingProductionKeyFile; | ||||
|             return ResultStatus::ErrorNSPMissingProgramNCA; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     const auto result = secondary_loader->Load(process); | ||||
|     if (result != ResultStatus::Success) | ||||
|         return result; | ||||
| 
 | ||||
|     is_loaded = true; | ||||
| 
 | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& dir) { | ||||
|     return secondary_loader->ReadRomFS(dir); | ||||
| } | ||||
| 
 | ||||
| ResultStatus AppLoader_NSP::ReadProgramId(u64& out_program_id) { | ||||
|     if (title_id == 0) | ||||
|         return ResultStatus::ErrorNotInitialized; | ||||
|     out_program_id = title_id; | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus AppLoader_NSP::ReadIcon(std::vector<u8>& buffer) { | ||||
|     if (icon_file == nullptr) | ||||
|         return ResultStatus::ErrorNoControl; | ||||
|     buffer = icon_file->ReadAllBytes(); | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| 
 | ||||
| ResultStatus AppLoader_NSP::ReadTitle(std::string& title) { | ||||
|     if (nacp_file == nullptr) | ||||
|         return ResultStatus::ErrorNoControl; | ||||
|     title = nacp_file->GetApplicationName(); | ||||
|     return ResultStatus::Success; | ||||
| } | ||||
| } // namespace Loader
 | ||||
							
								
								
									
										54
									
								
								src/core/loader/nsp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/core/loader/nsp.h
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,54 @@ | ||||
| // Copyright 2018 yuzu emulator team
 | ||||
| // Licensed under GPLv2 or any later version
 | ||||
| // Refer to the license.txt file included.
 | ||||
| 
 | ||||
| #pragma once | ||||
| 
 | ||||
| #include <memory> | ||||
| #include "common/common_types.h" | ||||
| #include "core/file_sys/vfs.h" | ||||
| #include "core/loader/loader.h" | ||||
| 
 | ||||
| namespace FileSys { | ||||
| class NACP; | ||||
| class NSP; | ||||
| } // namespace FileSys
 | ||||
| 
 | ||||
| namespace Loader { | ||||
| 
 | ||||
| class AppLoader_NCA; | ||||
| 
 | ||||
| /// Loads an XCI file
 | ||||
| class AppLoader_NSP final : public AppLoader { | ||||
| public: | ||||
|     explicit AppLoader_NSP(FileSys::VirtualFile file); | ||||
|     ~AppLoader_NSP() override; | ||||
| 
 | ||||
|     /**
 | ||||
|      * Returns the type of the file | ||||
|      * @param file std::shared_ptr<VfsFile> open file | ||||
|      * @return FileType found, or FileType::Error if this loader doesn't know it | ||||
|      */ | ||||
|     static FileType IdentifyType(const FileSys::VirtualFile& file); | ||||
| 
 | ||||
|     FileType GetFileType() override { | ||||
|         return IdentifyType(file); | ||||
|     } | ||||
| 
 | ||||
|     ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; | ||||
| 
 | ||||
|     ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | ||||
|     ResultStatus ReadProgramId(u64& out_program_id) override; | ||||
|     ResultStatus ReadIcon(std::vector<u8>& buffer) override; | ||||
|     ResultStatus ReadTitle(std::string& title) override; | ||||
| 
 | ||||
| private: | ||||
|     std::unique_ptr<FileSys::NSP> nsp; | ||||
|     std::unique_ptr<AppLoader> secondary_loader; | ||||
| 
 | ||||
|     FileSys::VirtualFile icon_file; | ||||
|     std::shared_ptr<FileSys::NACP> nacp_file; | ||||
|     u64 title_id; | ||||
| }; | ||||
| 
 | ||||
| } // namespace Loader
 | ||||
| @ -17,8 +17,7 @@ namespace Loader { | ||||
| 
 | ||||
| AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) | ||||
|     : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), | ||||
|       nca_loader(std::make_unique<AppLoader_NCA>( | ||||
|           xci->GetNCAFileByType(FileSys::NCAContentType::Program))) { | ||||
|       nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) { | ||||
|     if (xci->GetStatus() != ResultStatus::Success) | ||||
|         return; | ||||
|     const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); | ||||
| @ -64,11 +63,11 @@ ResultStatus AppLoader_XCI::Load(Kernel::SharedPtr<Kernel::Process>& process) { | ||||
|     if (xci->GetProgramNCAStatus() != ResultStatus::Success) | ||||
|         return xci->GetProgramNCAStatus(); | ||||
| 
 | ||||
|     const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program); | ||||
|     const auto nca = xci->GetProgramNCA(); | ||||
|     if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) | ||||
|         return ResultStatus::ErrorMissingProductionKeyFile; | ||||
| 
 | ||||
|     auto result = nca_loader->Load(process); | ||||
|     const auto result = nca_loader->Load(process); | ||||
|     if (result != ResultStatus::Success) | ||||
|         return result; | ||||
| 
 | ||||
|  | ||||
| @ -432,7 +432,7 @@ void GameList::LoadInterfaceLayout() { | ||||
|     item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); | ||||
| } | ||||
| 
 | ||||
| const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci"}; | ||||
| const QStringList GameList::supported_file_extensions = {"nso", "nro", "nca", "xci", "nsp"}; | ||||
| 
 | ||||
| static bool HasSupportedFileExtension(const std::string& file_name) { | ||||
|     const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); | ||||
|  | ||||
| @ -34,7 +34,9 @@ | ||||
| #include "core/file_sys/content_archive.h" | ||||
| #include "core/file_sys/registered_cache.h" | ||||
| #include "core/file_sys/savedata_factory.h" | ||||
| #include "core/file_sys/submission_package.h" | ||||
| #include "core/file_sys/vfs_real.h" | ||||
| #include "core/hle/kernel/process.h" | ||||
| #include "core/hle/service/filesystem/filesystem.h" | ||||
| #include "core/loader/loader.h" | ||||
| #include "core/perf_stats.h" | ||||
| @ -76,6 +78,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1; | ||||
|  */ | ||||
| enum class CalloutFlag : uint32_t { | ||||
|     Telemetry = 0x1, | ||||
|     DRDDeprecation = 0x2, | ||||
| }; | ||||
| 
 | ||||
| static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { | ||||
| @ -488,6 +491,23 @@ bool GMainWindow::LoadROM(const QString& filename) { | ||||
| 
 | ||||
|     const Core::System::ResultStatus result{system.Load(*render_window, filename.toStdString())}; | ||||
| 
 | ||||
|     const auto drd_callout = | ||||
|         (UISettings::values.callout_flags & static_cast<u32>(CalloutFlag::DRDDeprecation)) == 0; | ||||
| 
 | ||||
|     if (result == Core::System::ResultStatus::Success && | ||||
|         system.GetAppLoader().GetFileType() == Loader::FileType::DeconstructedRomDirectory && | ||||
|         drd_callout) { | ||||
|         UISettings::values.callout_flags |= static_cast<u32>(CalloutFlag::DRDDeprecation); | ||||
|         QMessageBox::warning( | ||||
|             this, tr("Warning Outdated Game Format"), | ||||
|             tr("You are using the deconstructed ROM directory format for this game, which is an " | ||||
|                "outdated format that has been superseded by others such as NCA, NAX, XCI, or " | ||||
|                "NSP. Deconstructed ROM directories lack icons, metadata, and update " | ||||
|                "support.<br><br>For an explanation of the various Switch formats yuzu supports, <a " | ||||
|                "href='https://yuzu-emu.org/wiki/overview-of-switch-game-formats'>check out our " | ||||
|                "wiki</a>. This message will not be shown again.")); | ||||
|     } | ||||
| 
 | ||||
|     render_window->DoneCurrent(); | ||||
| 
 | ||||
|     if (result != Core::System::ResultStatus::Success) { | ||||
| @ -746,7 +766,8 @@ void GMainWindow::OnMenuLoadFolder() { | ||||
| 
 | ||||
| void GMainWindow::OnMenuInstallToNAND() { | ||||
|     const QString file_filter = | ||||
|         tr("Installable Switch File (*.nca *.xci);;Nintendo Content Archive (*.nca);;NX Cartridge " | ||||
|         tr("Installable Switch File (*.nca *.nsp *.xci);;Nintendo Content Archive " | ||||
|            "(*.nca);;Nintendo Submissions Package (*.nsp);;NX Cartridge " | ||||
|            "Image (*.xci)"); | ||||
|     QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), | ||||
|                                                     UISettings::values.roms_path, file_filter); | ||||
| @ -806,22 +827,34 @@ void GMainWindow::OnMenuInstallToNAND() { | ||||
|                QMessageBox::Yes; | ||||
|     }; | ||||
| 
 | ||||
|     if (filename.endsWith("xci", Qt::CaseInsensitive)) { | ||||
|         const auto xci = std::make_shared<FileSys::XCI>( | ||||
|             vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||||
|         if (xci->GetStatus() != Loader::ResultStatus::Success) { | ||||
|     if (filename.endsWith("xci", Qt::CaseInsensitive) || | ||||
|         filename.endsWith("nsp", Qt::CaseInsensitive)) { | ||||
| 
 | ||||
|         std::shared_ptr<FileSys::NSP> nsp; | ||||
|         if (filename.endsWith("nsp", Qt::CaseInsensitive)) { | ||||
|             nsp = std::make_shared<FileSys::NSP>( | ||||
|                 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||||
|             if (nsp->IsExtractedType()) | ||||
|                 failed(); | ||||
|         } else { | ||||
|             const auto xci = std::make_shared<FileSys::XCI>( | ||||
|                 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | ||||
|             nsp = xci->GetSecurePartitionNSP(); | ||||
|         } | ||||
| 
 | ||||
|         if (nsp->GetStatus() != Loader::ResultStatus::Success) { | ||||
|             failed(); | ||||
|             return; | ||||
|         } | ||||
|         const auto res = | ||||
|             Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy); | ||||
|             Service::FileSystem::GetUserNANDContents()->InstallEntry(nsp, false, qt_raw_copy); | ||||
|         if (res == FileSys::InstallResult::Success) { | ||||
|             success(); | ||||
|         } else { | ||||
|             if (res == FileSys::InstallResult::ErrorAlreadyExists) { | ||||
|                 if (overwrite()) { | ||||
|                     const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( | ||||
|                         xci, true, qt_raw_copy); | ||||
|                         nsp, true, qt_raw_copy); | ||||
|                     if (res2 == FileSys::InstallResult::Success) { | ||||
|                         success(); | ||||
|                     } else { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user
	 bunnei
						bunnei