3 changed files with 238 additions and 0 deletions
-
8src/core/CMakeLists.txt
-
125src/core/file_sys/nca_metadata.cpp
-
105src/core/file_sys/nca_metadata.h
@ -0,0 +1,125 @@ |
|||
// Copyright 2018 yuzu emulator team
|
|||
// Licensed under GPLv2 or any later version
|
|||
// Refer to the license.txt file included.
|
|||
|
|||
#include "common/common_funcs.h"
|
|||
#include "common/swap.h"
|
|||
#include "content_archive.h"
|
|||
#include "core/file_sys/nca_metadata.h"
|
|||
|
|||
namespace FileSys { |
|||
|
|||
CNMT::CNMT(VirtualFile file_) : file(std::move(file_)), header(std::make_unique<CNMTHeader>()) { |
|||
if (file->ReadObject(header.get()) != sizeof(CNMTHeader)) |
|||
return; |
|||
|
|||
// If type is {Application, Update, AOC} has opt-header.
|
|||
if (static_cast<u8>(header->type) >= 0x80 && static_cast<u8>(header->type) <= 0x82) { |
|||
opt_header = std::make_unique<OptionalHeader>(); |
|||
if (file->ReadObject(opt_header.get(), sizeof(CNMTHeader)) != sizeof(OptionalHeader)) { |
|||
opt_header = nullptr; |
|||
} |
|||
} |
|||
|
|||
for (u16 i = 0; i < header->number_content_entries; ++i) { |
|||
auto& next = content_records.emplace_back(ContentRecord{}); |
|||
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(ContentRecord) + |
|||
header->table_offset) != sizeof(ContentRecord)) { |
|||
content_records.erase(content_records.end() - 1); |
|||
} |
|||
} |
|||
|
|||
for (u16 i = 0; i < header->number_meta_entries; ++i) { |
|||
auto& next = meta_records.emplace_back(MetaRecord{}); |
|||
if (file->ReadObject(&next, sizeof(CNMTHeader) + i * sizeof(MetaRecord) + |
|||
header->table_offset) != sizeof(MetaRecord)) { |
|||
meta_records.erase(meta_records.end() - 1); |
|||
} |
|||
} |
|||
} |
|||
|
|||
CNMT::CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, |
|||
std::vector<MetaRecord> meta_records) |
|||
: file(nullptr), header(std::make_unique<CNMTHeader>(std::move(header))), |
|||
opt_header(std::make_unique<OptionalHeader>(std::move(opt_header))), |
|||
content_records(std::move(content_records)), meta_records(std::move(meta_records)) {} |
|||
|
|||
u64 CNMT::GetTitleID() const { |
|||
return header->title_id; |
|||
} |
|||
|
|||
u32 CNMT::GetTitleVersion() const { |
|||
return header->title_version; |
|||
} |
|||
|
|||
TitleType CNMT::GetType() const { |
|||
return header->type; |
|||
} |
|||
|
|||
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) { |
|||
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; }); |
|||
if (iter == content_records.end()) { |
|||
content_records.emplace_back(rec); |
|||
++header->number_content_entries; |
|||
change = true; |
|||
} |
|||
} |
|||
for (const auto& rec : other.meta_records) { |
|||
const auto iter = |
|||
std::find_if(meta_records.begin(), meta_records.end(), [rec](const MetaRecord& r) { |
|||
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); |
|||
++header->number_meta_entries; |
|||
change = true; |
|||
} |
|||
} |
|||
return change; |
|||
} |
|||
|
|||
std::vector<u8> CNMT::Serialize() const { |
|||
if (header == nullptr) |
|||
return {}; |
|||
std::vector<u8> out(sizeof(CNMTHeader)); |
|||
out.reserve(0x100); // Avoid resizing -- average size.
|
|||
memcpy(out.data(), header.get(), sizeof(CNMTHeader)); |
|||
if (opt_header != nullptr) { |
|||
out.resize(out.size() + sizeof(OptionalHeader)); |
|||
memcpy(out.data() + sizeof(CNMTHeader), opt_header.get(), sizeof(OptionalHeader)); |
|||
} |
|||
|
|||
auto offset = header->table_offset; |
|||
|
|||
const auto dead_zone = offset + sizeof(CNMTHeader) - out.size(); |
|||
if (dead_zone > 0) |
|||
out.resize(offset + sizeof(CNMTHeader)); |
|||
|
|||
for (const auto& rec : content_records) { |
|||
out.resize(out.size() + sizeof(ContentRecord)); |
|||
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(ContentRecord)); |
|||
offset += sizeof(ContentRecord); |
|||
} |
|||
|
|||
for (const auto& rec : meta_records) { |
|||
out.resize(out.size() + sizeof(MetaRecord)); |
|||
memcpy(out.data() + offset + sizeof(CNMTHeader), &rec, sizeof(MetaRecord)); |
|||
offset += sizeof(MetaRecord); |
|||
} |
|||
|
|||
return out; |
|||
} |
|||
} // namespace FileSys
|
|||
@ -0,0 +1,105 @@ |
|||
// Copyright 2018 yuzu emulator team |
|||
// Licensed under GPLv2 or any later version |
|||
// Refer to the license.txt file included. |
|||
|
|||
#pragma once |
|||
|
|||
#include <memory> |
|||
#include "core/file_sys/vfs.h" |
|||
|
|||
namespace FileSys { |
|||
class CNMT; |
|||
|
|||
struct CNMTHeader; |
|||
struct OptionalHeader; |
|||
|
|||
enum class TitleType : u8 { |
|||
SystemProgram = 0x01, |
|||
SystemDataArchive = 0x02, |
|||
SystemUpdate = 0x03, |
|||
FirmwarePackageA = 0x04, |
|||
FirmwarePackageB = 0x05, |
|||
Application = 0x80, |
|||
Update = 0x81, |
|||
AOC = 0x82, |
|||
DeltaTitle = 0x83, |
|||
}; |
|||
|
|||
enum class ContentRecordType : u8 { |
|||
Meta = 0, |
|||
Program = 1, |
|||
Data = 2, |
|||
Control = 3, |
|||
Manual = 4, |
|||
Legal = 5, |
|||
Patch = 6, |
|||
}; |
|||
|
|||
struct ContentRecord { |
|||
std::array<u8, 0x20> hash; |
|||
std::array<u8, 0x10> nca_id; |
|||
std::array<u8, 0x6> size; |
|||
ContentRecordType type; |
|||
INSERT_PADDING_BYTES(1); |
|||
}; |
|||
static_assert(sizeof(ContentRecord) == 0x38, "ContentRecord has incorrect size."); |
|||
|
|||
constexpr ContentRecord EMPTY_META_CONTENT_RECORD{{}, {}, {}, ContentRecordType::Meta, {}}; |
|||
|
|||
struct MetaRecord { |
|||
u64_le title_id; |
|||
u32_le title_version; |
|||
TitleType type; |
|||
u8 install_byte; |
|||
INSERT_PADDING_BYTES(2); |
|||
}; |
|||
static_assert(sizeof(MetaRecord) == 0x10, "MetaRecord has incorrect size."); |
|||
|
|||
struct OptionalHeader { |
|||
u64_le title_id; |
|||
u64_le minimum_version; |
|||
}; |
|||
static_assert(sizeof(OptionalHeader) == 0x10, "OptionalHeader has incorrect size."); |
|||
|
|||
struct CNMTHeader { |
|||
u64_le title_id; |
|||
u32_le title_version; |
|||
TitleType type; |
|||
INSERT_PADDING_BYTES(1); |
|||
u16_le table_offset; |
|||
u16_le number_content_entries; |
|||
u16_le number_meta_entries; |
|||
INSERT_PADDING_BYTES(12); |
|||
}; |
|||
static_assert(sizeof(CNMTHeader) == 0x20, "CNMTHeader has incorrect size."); |
|||
|
|||
// A class representing the format used by NCA metadata files, typically named {}.cnmt.nca or |
|||
// meta0.ncd. These describe which NCA's belong with which titles in the registered cache. |
|||
class CNMT { |
|||
public: |
|||
explicit CNMT(VirtualFile file); |
|||
CNMT(CNMTHeader header, OptionalHeader opt_header, std::vector<ContentRecord> content_records, |
|||
std::vector<MetaRecord> meta_records); |
|||
|
|||
u64 GetTitleID() const; |
|||
u32 GetTitleVersion() const; |
|||
TitleType GetType() const; |
|||
|
|||
const std::vector<ContentRecord>& GetContentRecords() const; |
|||
const std::vector<MetaRecord>& GetMetaRecords() const; |
|||
|
|||
bool UnionRecords(const CNMT& other); |
|||
std::vector<u8> Serialize() const; |
|||
|
|||
private: |
|||
VirtualFile file; |
|||
std::unique_ptr<CNMTHeader> header; |
|||
std::unique_ptr<OptionalHeader> opt_header; |
|||
std::vector<ContentRecord> content_records; |
|||
std::vector<MetaRecord> meta_records; |
|||
|
|||
// TODO(DarkLordZach): According to switchbrew, for Patch-type there is additional data |
|||
// after the table. This is not documented, unfortunately. |
|||
}; |
|||
|
|||
} // namespace FileSys |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue