Merge pull request #1178 from DarkLordZach/nsp

file_sys: Add Nintendo Submissions Package (NSP) file format
This commit is contained in:
bunnei 2018-09-04 16:20:40 -04:00 committed by GitHub
commit faa9e066ab
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 650 additions and 41 deletions

View file

@ -49,6 +49,8 @@ add_library(core STATIC
file_sys/savedata_factory.h file_sys/savedata_factory.h
file_sys/sdmc_factory.cpp file_sys/sdmc_factory.cpp
file_sys/sdmc_factory.h file_sys/sdmc_factory.h
file_sys/submission_package.cpp
file_sys/submission_package.h
file_sys/vfs.cpp file_sys/vfs.cpp
file_sys/vfs.h file_sys/vfs.h
file_sys/vfs_concat.cpp file_sys/vfs_concat.cpp
@ -359,6 +361,8 @@ add_library(core STATIC
loader/nro.h loader/nro.h
loader/nso.cpp loader/nso.cpp
loader/nso.h loader/nso.h
loader/nsp.cpp
loader/nsp.h
loader/xci.cpp loader/xci.cpp
loader/xci.h loader/xci.h
memory.cpp memory.cpp

View file

@ -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) { 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(), s128_file_id.begin(), s128_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) { [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S128KeyType>> elem) {
return std::tie(elem.second.type, elem.second.field1, elem.second.field2) == return std::tie(elem.second.type, elem.second.field1, elem.second.field2) ==
std::tie(id, field1, field2); std::tie(id, field1, field2);
}); });
if (iter != s128_file_id.end()) if (iter2 != s128_file_id.end())
WriteKeyToFile(id == S128KeyType::Titlekey, iter->first, key); WriteKeyToFile(false, iter2->first, key);
s128_keys[{id, field1, field2}] = key; s128_keys[{id, field1, field2}] = key;
} }
void KeyManager::SetKey(S256KeyType id, Key256 key, u64 field1, u64 field2) { 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( const auto iter = std::find_if(
s256_file_id.begin(), s256_file_id.end(), s256_file_id.begin(), s256_file_id.end(),
[&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) { [&id, &field1, &field2](const std::pair<std::string, KeyIndex<S256KeyType>> elem) {

View file

@ -17,6 +17,8 @@ enum class ResultStatus : u16;
namespace Core::Crypto { namespace Core::Crypto {
constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
using Key128 = std::array<u8, 0x10>; using Key128 = std::array<u8, 0x10>;
using Key256 = std::array<u8, 0x20>; using Key256 = std::array<u8, 0x20>;
using SHA256Hash = std::array<u8, 0x20>; using SHA256Hash = std::array<u8, 0x20>;

View file

@ -10,7 +10,9 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/file_sys/card_image.h" #include "core/file_sys/card_image.h"
#include "core/file_sys/content_archive.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/partition_filesystem.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_offset.h" #include "core/file_sys/vfs_offset.h"
#include "core/loader/loader.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); 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_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); auto result = AddNCAFromPartition(XCIPartition::Update);
if (result != Loader::ResultStatus::Success) {
status = result;
return;
}
result = AddNCAFromPartition(XCIPartition::Update);
if (result != Loader::ResultStatus::Success) { if (result != Loader::ResultStatus::Success) {
status = result; status = result;
return; return;
@ -89,6 +95,10 @@ VirtualDir XCI::GetPartition(XCIPartition partition) const {
return partitions[static_cast<size_t>(partition)]; return partitions[static_cast<size_t>(partition)];
} }
std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
return secure_partition;
}
VirtualDir XCI::GetSecurePartition() const { VirtualDir XCI::GetSecurePartition() const {
return GetPartition(XCIPartition::Secure); return GetPartition(XCIPartition::Secure);
} }
@ -105,6 +115,20 @@ VirtualDir XCI::GetLogoPartition() const {
return GetPartition(XCIPartition::Logo); 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 { const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
return ncas; return ncas;
} }

View file

@ -19,6 +19,7 @@ namespace FileSys {
class NCA; class NCA;
enum class NCAContentType : u8; enum class NCAContentType : u8;
class NSP;
enum class GamecardSize : u8 { enum class GamecardSize : u8 {
S_1GB = 0xFA, S_1GB = 0xFA,
@ -71,11 +72,16 @@ public:
u8 GetFormatVersion() const; u8 GetFormatVersion() const;
VirtualDir GetPartition(XCIPartition partition) const; VirtualDir GetPartition(XCIPartition partition) const;
std::shared_ptr<NSP> GetSecurePartitionNSP() const;
VirtualDir GetSecurePartition() const; VirtualDir GetSecurePartition() const;
VirtualDir GetNormalPartition() const; VirtualDir GetNormalPartition() const;
VirtualDir GetUpdatePartition() const; VirtualDir GetUpdatePartition() const;
VirtualDir GetLogoPartition() 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; const std::vector<std::shared_ptr<NCA>>& GetNCAs() const;
std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const; std::shared_ptr<NCA> GetNCAByType(NCAContentType type) const;
VirtualFile GetNCAFileByType(NCAContentType type) const; VirtualFile GetNCAFileByType(NCAContentType type) const;
@ -101,6 +107,8 @@ private:
Loader::ResultStatus program_nca_status; Loader::ResultStatus program_nca_status;
std::vector<VirtualDir> partitions; std::vector<VirtualDir> partitions;
std::shared_ptr<NSP> secure_partition;
std::shared_ptr<NCA> program;
std::vector<std::shared_ptr<NCA>> ncas; std::vector<std::shared_ptr<NCA>> ncas;
}; };
} // namespace FileSys } // namespace FileSys

View file

@ -21,7 +21,17 @@ NACP::NACP(VirtualFile file) : raw(std::make_unique<RawNACP>()) {
} }
const LanguageEntry& NACP::GetLanguageEntry(Language language) const { 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 { std::string NACP::GetApplicationName(Language language) const {

View file

@ -9,6 +9,7 @@
#include <string> #include <string>
#include "common/common_funcs.h" #include "common/common_funcs.h"
#include "common/common_types.h" #include "common/common_types.h"
#include "common/swap.h"
#include "core/file_sys/vfs.h" #include "core/file_sys/vfs.h"
namespace FileSys { namespace FileSys {
@ -61,6 +62,8 @@ enum class Language : u8 {
Korean = 12, Korean = 12,
Taiwanese = 13, Taiwanese = 13,
Chinese = 14, Chinese = 14,
Default = 255,
}; };
static constexpr std::array<const char*, 15> LANGUAGE_NAMES = { static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
@ -75,9 +78,9 @@ static constexpr std::array<const char*, 15> LANGUAGE_NAMES = {
class NACP { class NACP {
public: public:
explicit NACP(VirtualFile file); explicit NACP(VirtualFile file);
const LanguageEntry& GetLanguageEntry(Language language = Language::AmericanEnglish) const; const LanguageEntry& GetLanguageEntry(Language language = Language::Default) const;
std::string GetApplicationName(Language language = Language::AmericanEnglish) const; std::string GetApplicationName(Language language = Language::Default) const;
std::string GetDeveloperName(Language language = Language::AmericanEnglish) const; std::string GetDeveloperName(Language language = Language::Default) const;
u64 GetTitleId() const; u64 GetTitleId() const;
std::string GetVersionString() const; std::string GetVersionString() const;

View file

@ -13,6 +13,7 @@
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/nca_metadata.h" #include "core/file_sys/nca_metadata.h"
#include "core/file_sys/registered_cache.h" #include "core/file_sys/registered_cache.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_concat.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
@ -358,17 +359,21 @@ std::vector<RegisteredCacheEntry> RegisteredCache::ListEntriesFilter(
return out; return out;
} }
static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const NcaID& id) { static std::shared_ptr<NCA> GetNCAFromNSPForID(std::shared_ptr<NSP> nsp, const NcaID& id) {
const auto filename = fmt::format("{}.nca", Common::HexArrayToString(id, false)); const auto file = nsp->GetFile(fmt::format("{}.nca", Common::HexArrayToString(id, false)));
const auto iter = if (file == nullptr)
std::find_if(xci->GetNCAs().begin(), xci->GetNCAs().end(), return nullptr;
[&filename](std::shared_ptr<NCA> nca) { return nca->GetName() == filename; }); return std::make_shared<NCA>(file);
return iter == xci->GetNCAs().end() ? nullptr : *iter;
} }
InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists,
const VfsCopyFunction& copy) { 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) { const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) {
return nca->GetType() == NCAContentType::Meta; 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 auto cnmt_file = section0->GetFiles()[0];
const CNMT cnmt(cnmt_file); const CNMT cnmt(cnmt_file);
for (const auto& record : cnmt.GetContentRecords()) { 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) if (nca == nullptr)
return InstallResult::ErrorCopyFailed; return InstallResult::ErrorCopyFailed;
const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id);

View file

@ -17,6 +17,7 @@
namespace FileSys { namespace FileSys {
class CNMT; class CNMT;
class NCA; class NCA;
class NSP;
class XCI; class XCI;
enum class ContentRecordType : u8; enum class ContentRecordType : u8;
@ -89,10 +90,12 @@ public:
boost::optional<ContentRecordType> record_type = boost::none, boost::optional<ContentRecordType> record_type = boost::none,
boost::optional<u64> title_id = boost::none) const; 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 // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
// is a meta NCA and all of them are accessible. // there is a meta NCA and all of them are accessible.
InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
const VfsCopyFunction& copy = &VfsRawCopy); 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 // 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 // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a

View 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

View 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

View file

@ -61,7 +61,6 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
if (nacp_file != nullptr) { if (nacp_file != nullptr) {
FileSys::NACP nacp(nacp_file); FileSys::NACP nacp(nacp_file);
title_id = nacp.GetTitleId();
name = nacp.GetApplicationName(); name = nacp.GetApplicationName();
} }
} }
@ -120,6 +119,7 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
} }
auto& kernel = Core::System::GetInstance().Kernel(); auto& kernel = Core::System::GetInstance().Kernel();
title_id = metadata.GetTitleID();
process->program_id = metadata.GetTitleID(); process->program_id = metadata.GetTitleID();
process->svc_access_mask.set(); process->svc_access_mask.set();
process->resource_limit = process->resource_limit =
@ -159,8 +159,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadIcon(std::vector<u8>& buff
} }
ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) { ResultStatus AppLoader_DeconstructedRomDirectory::ReadProgramId(u64& out_program_id) {
if (name.empty())
return ResultStatus::ErrorNoControl;
out_program_id = title_id; out_program_id = title_id;
return ResultStatus::Success; return ResultStatus::Success;
} }

View file

@ -15,6 +15,7 @@
#include "core/loader/nca.h" #include "core/loader/nca.h"
#include "core/loader/nro.h" #include "core/loader/nro.h"
#include "core/loader/nso.h" #include "core/loader/nso.h"
#include "core/loader/nsp.h"
#include "core/loader/xci.h" #include "core/loader/xci.h"
namespace Loader { namespace Loader {
@ -34,6 +35,7 @@ FileType IdentifyFile(FileSys::VirtualFile file) {
CHECK_TYPE(NCA) CHECK_TYPE(NCA)
CHECK_TYPE(XCI) CHECK_TYPE(XCI)
CHECK_TYPE(NAX) CHECK_TYPE(NAX)
CHECK_TYPE(NSP)
#undef CHECK_TYPE #undef CHECK_TYPE
@ -59,6 +61,8 @@ FileType GuessFromFilename(const std::string& name) {
return FileType::NCA; return FileType::NCA;
if (extension == "xci") if (extension == "xci")
return FileType::XCI; return FileType::XCI;
if (extension == "nsp")
return FileType::NSP;
return FileType::Unknown; return FileType::Unknown;
} }
@ -77,6 +81,8 @@ std::string GetFileTypeString(FileType type) {
return "XCI"; return "XCI";
case FileType::NAX: case FileType::NAX:
return "NAX"; return "NAX";
case FileType::NSP:
return "NSP";
case FileType::DeconstructedRomDirectory: case FileType::DeconstructedRomDirectory:
return "Directory"; return "Directory";
case FileType::Error: case FileType::Error:
@ -87,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
return "unknown"; return "unknown";
} }
constexpr std::array<const char*, 49> RESULT_MESSAGES{ constexpr std::array<const char*, 50> RESULT_MESSAGES{
"The operation completed successfully.", "The operation completed successfully.",
"The loader requested to load is already loaded.", "The loader requested to load is already loaded.",
"The operation is not implemented.", "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 AES Key Generation Source could not be found.",
"The SD Save Key Source could not be found.", "The SD Save Key Source could not be found.",
"The SD NCA 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) { std::ostream& operator<<(std::ostream& os, ResultStatus status) {
os << RESULT_MESSAGES.at(static_cast<size_t>(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: case FileType::NAX:
return std::make_unique<AppLoader_NAX>(std::move(file)); 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. // NX deconstructed ROM directory.
case FileType::DeconstructedRomDirectory: case FileType::DeconstructedRomDirectory:
return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file)); return std::make_unique<AppLoader_DeconstructedRomDirectory>(std::move(file));

View file

@ -29,6 +29,7 @@ enum class FileType {
NSO, NSO,
NRO, NRO,
NCA, NCA,
NSP,
XCI, XCI,
NAX, NAX,
DeconstructedRomDirectory, DeconstructedRomDirectory,
@ -105,6 +106,7 @@ enum class ResultStatus : u16 {
ErrorMissingAESKeyGenerationSource, ErrorMissingAESKeyGenerationSource,
ErrorMissingSDSaveKeySource, ErrorMissingSDSaveKeySource,
ErrorMissingSDNCAKeySource, ErrorMissingSDNCAKeySource,
ErrorNSPMissingProgramNCA,
}; };
std::ostream& operator<<(std::ostream& os, ResultStatus status); std::ostream& operator<<(std::ostream& os, ResultStatus status);

135
src/core/loader/nsp.cpp Normal file
View 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
View 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

View file

@ -17,8 +17,7 @@ namespace Loader {
AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file) AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
: AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)), : AppLoader(file), xci(std::make_unique<FileSys::XCI>(file)),
nca_loader(std::make_unique<AppLoader_NCA>( nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
xci->GetNCAFileByType(FileSys::NCAContentType::Program))) {
if (xci->GetStatus() != ResultStatus::Success) if (xci->GetStatus() != ResultStatus::Success)
return; return;
const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); 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) if (xci->GetProgramNCAStatus() != ResultStatus::Success)
return xci->GetProgramNCAStatus(); return xci->GetProgramNCAStatus();
const auto nca = xci->GetNCAFileByType(FileSys::NCAContentType::Program); const auto nca = xci->GetProgramNCA();
if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false)) if (nca == nullptr && !Core::Crypto::KeyManager::KeyFileExists(false))
return ResultStatus::ErrorMissingProductionKeyFile; return ResultStatus::ErrorMissingProductionKeyFile;
auto result = nca_loader->Load(process); const auto result = nca_loader->Load(process);
if (result != ResultStatus::Success) if (result != ResultStatus::Success)
return result; return result;

View file

@ -432,7 +432,7 @@ void GameList::LoadInterfaceLayout() {
item_model->sort(header->sortIndicatorSection(), header->sortIndicatorOrder()); 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) { static bool HasSupportedFileExtension(const std::string& file_name) {
const QFileInfo file = QFileInfo(QString::fromStdString(file_name)); const QFileInfo file = QFileInfo(QString::fromStdString(file_name));

View file

@ -34,7 +34,9 @@
#include "core/file_sys/content_archive.h" #include "core/file_sys/content_archive.h"
#include "core/file_sys/registered_cache.h" #include "core/file_sys/registered_cache.h"
#include "core/file_sys/savedata_factory.h" #include "core/file_sys/savedata_factory.h"
#include "core/file_sys/submission_package.h"
#include "core/file_sys/vfs_real.h" #include "core/file_sys/vfs_real.h"
#include "core/hle/kernel/process.h"
#include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h" #include "core/loader/loader.h"
#include "core/perf_stats.h" #include "core/perf_stats.h"
@ -76,6 +78,7 @@ __declspec(dllexport) int AmdPowerXpressRequestHighPerformance = 1;
*/ */
enum class CalloutFlag : uint32_t { enum class CalloutFlag : uint32_t {
Telemetry = 0x1, Telemetry = 0x1,
DRDDeprecation = 0x2,
}; };
static void ShowCalloutMessage(const QString& message, CalloutFlag flag) { 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 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(); render_window->DoneCurrent();
if (result != Core::System::ResultStatus::Success) { if (result != Core::System::ResultStatus::Success) {
@ -746,7 +766,8 @@ void GMainWindow::OnMenuLoadFolder() {
void GMainWindow::OnMenuInstallToNAND() { void GMainWindow::OnMenuInstallToNAND() {
const QString file_filter = 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)"); "Image (*.xci)");
QString filename = QFileDialog::getOpenFileName(this, tr("Install File"), QString filename = QFileDialog::getOpenFileName(this, tr("Install File"),
UISettings::values.roms_path, file_filter); UISettings::values.roms_path, file_filter);
@ -806,22 +827,34 @@ void GMainWindow::OnMenuInstallToNAND() {
QMessageBox::Yes; QMessageBox::Yes;
}; };
if (filename.endsWith("xci", Qt::CaseInsensitive)) { if (filename.endsWith("xci", Qt::CaseInsensitive) ||
const auto xci = std::make_shared<FileSys::XCI>( filename.endsWith("nsp", Qt::CaseInsensitive)) {
vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
if (xci->GetStatus() != Loader::ResultStatus::Success) { 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(); failed();
return; return;
} }
const auto res = 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) { if (res == FileSys::InstallResult::Success) {
success(); success();
} else { } else {
if (res == FileSys::InstallResult::ErrorAlreadyExists) { if (res == FileSys::InstallResult::ErrorAlreadyExists) {
if (overwrite()) { if (overwrite()) {
const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry(
xci, true, qt_raw_copy); nsp, true, qt_raw_copy);
if (res2 == FileSys::InstallResult::Success) { if (res2 == FileSys::InstallResult::Success) {
success(); success();
} else { } else {