9f82a9a244
Previously, we were reading the keys everytime a KeyManager object was created, causing yuzu to reread the keys file multiple hundreds of times when loading the game list. With this change, it is only loaded once. On my system, this decreased game list loading times by a factor of 20.
312 lines
8.9 KiB
C++
312 lines
8.9 KiB
C++
// Copyright 2018 yuzu emulator team
|
|
// Licensed under GPLv2 or any later version
|
|
// Refer to the license.txt file included.
|
|
|
|
#include <array>
|
|
#include <string>
|
|
|
|
#include <fmt/ostream.h>
|
|
|
|
#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/romfs.h"
|
|
#include "core/file_sys/submission_package.h"
|
|
#include "core/file_sys/vfs_concat.h"
|
|
#include "core/file_sys/vfs_offset.h"
|
|
#include "core/file_sys/vfs_vector.h"
|
|
#include "core/loader/loader.h"
|
|
|
|
namespace FileSys {
|
|
|
|
constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
|
|
constexpr std::array partition_names{
|
|
"update",
|
|
"normal",
|
|
"secure",
|
|
"logo",
|
|
};
|
|
|
|
XCI::XCI(VirtualFile file_)
|
|
: file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
|
|
partitions(partition_names.size()), partitions_raw(partition_names.size()) {
|
|
if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
|
|
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
|
return;
|
|
}
|
|
|
|
if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
|
|
status = Loader::ResultStatus::ErrorBadXCIHeader;
|
|
return;
|
|
}
|
|
|
|
PartitionFilesystem main_hfs(std::make_shared<OffsetVfsFile>(
|
|
file, file->GetSize() - header.hfs_offset, header.hfs_offset));
|
|
|
|
update_normal_partition_end = main_hfs.GetFileOffsets()["secure"];
|
|
|
|
if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
|
|
status = main_hfs.GetStatus();
|
|
return;
|
|
}
|
|
|
|
for (XCIPartition partition :
|
|
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
|
const auto partition_idx = static_cast<std::size_t>(partition);
|
|
auto raw = main_hfs.GetFile(partition_names[partition_idx]);
|
|
|
|
partitions_raw[static_cast<std::size_t>(partition)] = std::move(raw);
|
|
}
|
|
|
|
secure_partition = std::make_shared<NSP>(
|
|
main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]));
|
|
|
|
ncas = secure_partition->GetNCAsCollapsed();
|
|
program =
|
|
secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
|
|
program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
|
|
if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) {
|
|
program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
|
|
}
|
|
|
|
auto result = AddNCAFromPartition(XCIPartition::Normal);
|
|
if (result != Loader::ResultStatus::Success) {
|
|
status = result;
|
|
return;
|
|
}
|
|
|
|
if (GetFormatVersion() >= 0x2) {
|
|
result = AddNCAFromPartition(XCIPartition::Logo);
|
|
if (result != Loader::ResultStatus::Success) {
|
|
status = result;
|
|
return;
|
|
}
|
|
}
|
|
|
|
status = Loader::ResultStatus::Success;
|
|
}
|
|
|
|
XCI::~XCI() = default;
|
|
|
|
Loader::ResultStatus XCI::GetStatus() const {
|
|
return status;
|
|
}
|
|
|
|
Loader::ResultStatus XCI::GetProgramNCAStatus() const {
|
|
return program_nca_status;
|
|
}
|
|
|
|
VirtualDir XCI::GetPartition(XCIPartition partition) {
|
|
const auto id = static_cast<std::size_t>(partition);
|
|
if (partitions[id] == nullptr && partitions_raw[id] != nullptr) {
|
|
partitions[id] = std::make_shared<PartitionFilesystem>(partitions_raw[id]);
|
|
}
|
|
|
|
return partitions[static_cast<std::size_t>(partition)];
|
|
}
|
|
|
|
std::vector<VirtualDir> XCI::GetPartitions() {
|
|
std::vector<VirtualDir> out;
|
|
for (const auto& id :
|
|
{XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
|
|
const auto part = GetPartition(id);
|
|
if (part != nullptr) {
|
|
out.push_back(part);
|
|
}
|
|
}
|
|
return out;
|
|
}
|
|
|
|
std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
|
|
return secure_partition;
|
|
}
|
|
|
|
VirtualDir XCI::GetSecurePartition() {
|
|
return GetPartition(XCIPartition::Secure);
|
|
}
|
|
|
|
VirtualDir XCI::GetNormalPartition() {
|
|
return GetPartition(XCIPartition::Normal);
|
|
}
|
|
|
|
VirtualDir XCI::GetUpdatePartition() {
|
|
return GetPartition(XCIPartition::Update);
|
|
}
|
|
|
|
VirtualDir XCI::GetLogoPartition() {
|
|
return GetPartition(XCIPartition::Logo);
|
|
}
|
|
|
|
VirtualFile XCI::GetPartitionRaw(XCIPartition partition) const {
|
|
return partitions_raw[static_cast<std::size_t>(partition)];
|
|
}
|
|
|
|
VirtualFile XCI::GetSecurePartitionRaw() const {
|
|
return GetPartitionRaw(XCIPartition::Secure);
|
|
}
|
|
|
|
VirtualFile XCI::GetStoragePartition0() const {
|
|
return std::make_shared<OffsetVfsFile>(file, update_normal_partition_end, 0, "partition0");
|
|
}
|
|
|
|
VirtualFile XCI::GetStoragePartition1() const {
|
|
return std::make_shared<OffsetVfsFile>(file, file->GetSize() - update_normal_partition_end,
|
|
update_normal_partition_end, "partition1");
|
|
}
|
|
|
|
VirtualFile XCI::GetNormalPartitionRaw() const {
|
|
return GetPartitionRaw(XCIPartition::Normal);
|
|
}
|
|
|
|
VirtualFile XCI::GetUpdatePartitionRaw() const {
|
|
return GetPartitionRaw(XCIPartition::Update);
|
|
}
|
|
|
|
VirtualFile XCI::GetLogoPartitionRaw() const {
|
|
return GetPartitionRaw(XCIPartition::Logo);
|
|
}
|
|
|
|
u64 XCI::GetProgramTitleID() const {
|
|
return secure_partition->GetProgramTitleID();
|
|
}
|
|
|
|
u32 XCI::GetSystemUpdateVersion() {
|
|
const auto update = GetPartition(XCIPartition::Update);
|
|
if (update == nullptr)
|
|
return 0;
|
|
|
|
for (const auto& file : update->GetFiles()) {
|
|
NCA nca{file, nullptr, 0};
|
|
|
|
if (nca.GetStatus() != Loader::ResultStatus::Success)
|
|
continue;
|
|
|
|
if (nca.GetType() == NCAContentType::Meta && nca.GetTitleId() == 0x0100000000000816) {
|
|
const auto dir = nca.GetSubdirectories()[0];
|
|
const auto cnmt = dir->GetFile("SystemUpdate_0100000000000816.cnmt");
|
|
if (cnmt == nullptr)
|
|
continue;
|
|
|
|
CNMT cnmt_data{cnmt};
|
|
|
|
const auto metas = cnmt_data.GetMetaRecords();
|
|
if (metas.empty())
|
|
continue;
|
|
|
|
return metas[0].title_version;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
u64 XCI::GetSystemUpdateTitleID() const {
|
|
return 0x0100000000000816;
|
|
}
|
|
|
|
bool XCI::HasProgramNCA() const {
|
|
return program != nullptr;
|
|
}
|
|
|
|
VirtualFile XCI::GetProgramNCAFile() const {
|
|
if (!HasProgramNCA()) {
|
|
return nullptr;
|
|
}
|
|
|
|
return program->GetBaseFile();
|
|
}
|
|
|
|
const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
|
|
return ncas;
|
|
}
|
|
|
|
std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
|
|
const auto iter =
|
|
std::find_if(ncas.begin(), ncas.end(),
|
|
[type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; });
|
|
return iter == ncas.end() ? nullptr : *iter;
|
|
}
|
|
|
|
VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
|
|
auto nca = GetNCAByType(type);
|
|
if (nca != nullptr) {
|
|
return nca->GetBaseFile();
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
std::vector<VirtualFile> XCI::GetFiles() const {
|
|
return {};
|
|
}
|
|
|
|
std::vector<VirtualDir> XCI::GetSubdirectories() const {
|
|
return {};
|
|
}
|
|
|
|
std::string XCI::GetName() const {
|
|
return file->GetName();
|
|
}
|
|
|
|
VirtualDir XCI::GetParentDirectory() const {
|
|
return file->GetContainingDirectory();
|
|
}
|
|
|
|
VirtualDir XCI::ConcatenatedPseudoDirectory() {
|
|
const auto out = std::make_shared<VectorVfsDirectory>();
|
|
for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
|
|
const auto& part = GetPartition(part_id);
|
|
if (part == nullptr)
|
|
continue;
|
|
|
|
for (const auto& file : part->GetFiles())
|
|
out->AddFile(file);
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
std::array<u8, 0x200> XCI::GetCertificate() const {
|
|
std::array<u8, 0x200> out;
|
|
file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
|
|
return out;
|
|
}
|
|
|
|
Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
|
|
const auto partition_index = static_cast<std::size_t>(part);
|
|
const auto partition = GetPartition(part);
|
|
|
|
if (partition == nullptr) {
|
|
return Loader::ResultStatus::ErrorXCIMissingPartition;
|
|
}
|
|
|
|
for (const VirtualFile& file : partition->GetFiles()) {
|
|
if (file->GetExtension() != "nca") {
|
|
continue;
|
|
}
|
|
|
|
auto nca = std::make_shared<NCA>(file, nullptr, 0);
|
|
if (nca->IsUpdate()) {
|
|
continue;
|
|
}
|
|
if (nca->GetType() == NCAContentType::Program) {
|
|
program_nca_status = nca->GetStatus();
|
|
}
|
|
if (nca->GetStatus() == Loader::ResultStatus::Success) {
|
|
ncas.push_back(std::move(nca));
|
|
} else {
|
|
const u16 error_id = static_cast<u16>(nca->GetStatus());
|
|
LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
|
|
partition_names[partition_index], nca->GetName(), error_id,
|
|
nca->GetStatus());
|
|
}
|
|
}
|
|
|
|
return Loader::ResultStatus::Success;
|
|
}
|
|
|
|
u8 XCI::GetFormatVersion() {
|
|
return GetLogoPartition() == nullptr ? 0x1 : 0x2;
|
|
}
|
|
} // namespace FileSys
|