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/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

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) {
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) {

View File

@ -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>;

View File

@ -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;
}

View File

@ -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

View File

@ -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 {

View File

@ -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;

View File

@ -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);

View File

@ -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

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) {
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;
}

View File

@ -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));

View 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
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(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;

View File

@ -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));

View File

@ -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 {