Browse Source
Merge pull request #1178 from DarkLordZach/nsp
Merge pull request #1178 from DarkLordZach/nsp
file_sys: Add Nintendo Submissions Package (NSP) file formatpull/15/merge
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 650 additions and 41 deletions
-
4src/core/CMakeLists.txt
-
16src/core/crypto/key_manager.cpp
-
2src/core/crypto/key_manager.h
-
38src/core/file_sys/card_image.cpp
-
8src/core/file_sys/card_image.h
-
12src/core/file_sys/control_metadata.cpp
-
9src/core/file_sys/control_metadata.h
-
21src/core/file_sys/registered_cache.cpp
-
7src/core/file_sys/registered_cache.h
-
236src/core/file_sys/submission_package.cpp
-
73src/core/file_sys/submission_package.h
-
4src/core/loader/deconstructed_rom_directory.cpp
-
14src/core/loader/loader.cpp
-
2src/core/loader/loader.h
-
135src/core/loader/nsp.cpp
-
54src/core/loader/nsp.h
-
7src/core/loader/xci.cpp
-
2src/yuzu/game_list.cpp
-
47src/yuzu/main.cpp
@ -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
|
|||
@ -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 |
|||
@ -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
|
|||
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue