aboutsummaryrefslogtreecommitdiff
path: root/src/core/file_sys/nca_metadata.cpp
blob: 324ecbc8c9a2e6a99514d6ecb08327770d2786e3 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <cstring>
#include "common/common_types.h"
#include "common/logging/log.h"
#include "common/swap.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs/vfs.h"

namespace FileSys {

CNMT::CNMT(VirtualFile file) {
    if (file->ReadObject(&header) != sizeof(CNMTHeader))
        return;

    // If type is {Application, Update, AOC} has opt-header.
    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.");
        }
    }

    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_)
    : header(std::move(header_)), opt_header(std::move(opt_header_)),
      content_records(std::move(content_records_)), meta_records(std::move(meta_records_)) {}

CNMT::~CNMT() = default;

const CNMTHeader& CNMT::GetHeader() const {
    return header;
}

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 {
    const bool has_opt_header =
        header.type >= TitleType::Application && header.type <= TitleType::AOC;
    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));
    memcpy(out.data(), &header, sizeof(CNMTHeader));

    // Optional Header
    if (has_opt_header) {
        memcpy(out.data() + sizeof(CNMTHeader), &opt_header, sizeof(OptionalHeader));
    }

    u64_le offset = header.table_offset;

    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