2022-04-23 10:59:50 +02:00
|
|
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
|
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
2018-08-10 02:51:52 +02:00
|
|
|
|
2018-08-10 05:10:32 +02:00
|
|
|
#include <cstring>
|
2018-09-04 03:58:19 +02:00
|
|
|
#include "common/common_types.h"
|
2018-08-11 21:39:09 +02:00
|
|
|
#include "common/logging/log.h"
|
2018-08-10 02:51:52 +02:00
|
|
|
#include "common/swap.h"
|
|
|
|
#include "core/file_sys/nca_metadata.h"
|
2020-08-23 20:20:37 +02:00
|
|
|
#include "core/file_sys/vfs.h"
|
2018-08-10 02:51:52 +02:00
|
|
|
|
|
|
|
namespace FileSys {
|
|
|
|
|
2018-08-11 21:39:09 +02:00
|
|
|
CNMT::CNMT(VirtualFile file) {
|
|
|
|
if (file->ReadObject(&header) != sizeof(CNMTHeader))
|
2018-08-10 02:51:52 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
// If type is {Application, Update, AOC} has opt-header.
|
2018-08-11 21:39:09 +02:00
|
|
|
if (header.type >= TitleType::Application && header.type <= TitleType::AOC) {
|
|
|
|
if (file->ReadObject(&opt_header, sizeof(CNMTHeader)) != sizeof(OptionalHeader)) {
|
|
|
|
LOG_WARNING(Loader, "Failed to read optional header.");
|
2018-08-10 02:51:52 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-11 21:39:09 +02:00
|
|
|
for (u16 i = 0; i < header.number_content_entries; ++i) {
|
2018-08-10 02:51:52 +02:00
|
|
|
auto& next = content_records.emplace_back(ContentRecord{});
|
|
|
|
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) +
|
2018-08-11 21:39:09 +02:00
|
|
|
header.table_offset) != sizeof(ContentRecord)) {
|
2018-08-10 02:51:52 +02:00
|
|
|
content_records.erase(content_records.end() - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-11 21:39:09 +02:00
|
|
|
for (u16 i = 0; i < header.number_meta_entries; ++i) {
|
2018-08-10 02:51:52 +02:00
|
|
|
auto& next = meta_records.emplace_back(MetaRecord{});
|
|
|
|
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) +
|
2018-08-11 21:39:09 +02:00
|
|
|
header.table_offset) != sizeof(MetaRecord)) {
|
2018-08-10 02:51:52 +02:00
|
|
|
meta_records.erase(meta_records.end() - 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-16 07:46:30 +02:00
|
|
|
CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
|
|
|
|
std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_)
|
|
|
|
: header(std::move(header_)), opt_header(std::move(opt_header_)),
|
|
|
|
content_records(std::move(content_records_)), meta_records(std::move(meta_records_)) {}
|
2018-08-10 02:51:52 +02:00
|
|
|
|
2018-09-20 01:19:05 +02:00
|
|
|
CNMT::~CNMT() = default;
|
|
|
|
|
2018-08-10 02:51:52 +02:00
|
|
|
u64 CNMT::GetTitleID() const {
|
2018-08-11 21:39:09 +02:00
|
|
|
return header.title_id;
|
2018-08-10 02:51:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
u32 CNMT::GetTitleVersion() const {
|
2018-08-11 21:39:09 +02:00
|
|
|
return header.title_version;
|
2018-08-10 02:51:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
TitleType CNMT::GetType() const {
|
2018-08-11 21:39:09 +02:00
|
|
|
return header.type;
|
2018-08-10 02:51:52 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<ContentRecord>& CNMT::GetContentRecords() const {
|
|
|
|
return content_records;
|
|
|
|
}
|
|
|
|
|
|
|
|
const std::vector<MetaRecord>& CNMT::GetMetaRecords() const {
|
|
|
|
return meta_records;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool CNMT::UnionRecords(const CNMT& other) {
|
|
|
|
bool change = false;
|
|
|
|
for (const auto& rec : other.content_records) {
|
2018-08-10 05:10:32 +02:00
|
|
|
const auto iter = std::find_if(content_records.begin(), content_records.end(),
|
|
|
|
[&rec](const ContentRecord& r) {
|
|
|
|
return r.nca_id == rec.nca_id && r.type == rec.type;
|
|
|
|
});
|
2018-08-10 02:51:52 +02:00
|
|
|
if (iter == content_records.end()) {
|
|
|
|
content_records.emplace_back(rec);
|
2018-08-11 21:39:09 +02:00
|
|
|
++header.number_content_entries;
|
2018-08-10 02:51:52 +02:00
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (const auto& rec : other.meta_records) {
|
|
|
|
const auto iter =
|
2018-08-10 05:10:32 +02:00
|
|
|
std::find_if(meta_records.begin(), meta_records.end(), [&rec](const MetaRecord& r) {
|
2018-08-10 02:51:52 +02:00
|
|
|
return r.title_id == rec.title_id && r.title_version == rec.title_version &&
|
|
|
|
r.type == rec.type;
|
|
|
|
});
|
|
|
|
if (iter == meta_records.end()) {
|
|
|
|
meta_records.emplace_back(rec);
|
2018-08-11 21:39:09 +02:00
|
|
|
++header.number_meta_entries;
|
2018-08-10 02:51:52 +02:00
|
|
|
change = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return change;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::vector<u8> CNMT::Serialize() const {
|
2018-08-11 21:39:09 +02:00
|
|
|
const bool has_opt_header =
|
|
|
|
header.type >= TitleType::Application && header.type <= TitleType::AOC;
|
2018-08-12 21:55:44 +02:00
|
|
|
const auto dead_zone = header.table_offset + sizeof(CNMTHeader);
|
|
|
|
std::vector<u8> out(
|
|
|
|
std::max(sizeof(CNMTHeader) + (has_opt_header ? sizeof(OptionalHeader) : 0), dead_zone) +
|
|
|
|
content_records.size() * sizeof(ContentRecord) + meta_records.size() * sizeof(MetaRecord));
|
2018-08-11 21:39:09 +02:00
|
|
|
memcpy(out.data(), &header, sizeof(CNMTHeader));
|
|
|
|
|
|
|
|
// Optional Header
|
|
|
|
if (has_opt_header) {
|
|
|
|
memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
|
2018-08-10 02:51:52 +02:00
|
|
|
}
|
|
|
|
|
2020-10-13 14:10:50 +02:00
|
|
|
u64_le offset = header.table_offset;
|
2018-08-10 02:51:52 +02:00
|
|
|
|
|
|
|
for (const auto& rec : content_records) {
|
|
|
|
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord));
|
|
|
|
offset += sizeof(ContentRecord);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& rec : meta_records) {
|
|
|
|
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord));
|
|
|
|
offset += sizeof(MetaRecord);
|
|
|
|
}
|
|
|
|
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
} // namespace FileSys
|