summaryrefslogtreecommitdiffstats
path: root/src/core/file_sys/registered_cache.h
blob: 5beceffb3ec3477c90c1a712f82824fa0a05b1ee (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
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.

#pragma once

#include <array>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <vector>
#include <boost/container/flat_map.hpp>
#include "common/common_types.h"
#include "core/file_sys/vfs.h"

namespace FileSys {
class CNMT;
class NCA;
class NSP;
class XCI;

enum class ContentRecordType : u8;
enum class TitleType : u8;

struct ContentRecord;

using NcaID = std::array<u8, 0x10>;
using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>;
using VfsCopyFunction = std::function<bool(const VirtualFile&, const VirtualFile&, size_t)>;

enum class InstallResult {
    Success,
    ErrorAlreadyExists,
    ErrorCopyFailed,
    ErrorMetaFailed,
};

struct RegisteredCacheEntry {
    u64 title_id;
    ContentRecordType type;

    std::string DebugInfo() const;
};

constexpr u64 GetUpdateTitleID(u64 base_title_id) {
    return base_title_id | 0x800;
}

// boost flat_map requires operator< for O(log(n)) lookups.
bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);

// std unique requires operator== to identify duplicates.
bool operator==(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
bool operator!=(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);

/*
 * A class that catalogues NCAs in the registered directory structure.
 * Nintendo's registered format follows this structure:
 *
 * Root
 *   | 000000XX <- XX is the ____ two digits of the NcaID
 *       | <hash>.nca <- hash is the NcaID (first half of SHA256 over entire file) (folder)
 *         | 00
 *         | 01 <- Actual content split along 4GB boundaries. (optional)
 *
 * (This impl also supports substituting the nca dir for an nca file, as that's more convenient
 * when 4GB splitting can be ignored.)
 */
class RegisteredCache {
    friend class RegisteredCacheUnion;

public:
    // Parsing function defines the conversion from raw file to NCA. If there are other steps
    // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
    // parsing function.
    explicit RegisteredCache(VirtualDir dir,
                             RegisteredCacheParsingFunction parsing_function =
                                 [](const VirtualFile& file, const NcaID& id) { return file; });
    ~RegisteredCache();

    void Refresh();

    bool HasEntry(u64 title_id, ContentRecordType type) const;
    bool HasEntry(RegisteredCacheEntry entry) const;

    boost::optional<u32> GetEntryVersion(u64 title_id) const;

    VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
    VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;

    VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
    VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;

    std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
    std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;

    std::vector<RegisteredCacheEntry> ListEntries() const;
    // If a parameter is not boost::none, it will be filtered for from all entries.
    std::vector<RegisteredCacheEntry> ListEntriesFilter(
        boost::optional<TitleType> title_type = boost::none,
        boost::optional<ContentRecordType> record_type = boost::none,
        boost::optional<u64> title_id = boost::none) const;

    // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure
    // there is a meta NCA and all of them are accessible.
    InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false,
                               const VfsCopyFunction& copy = &VfsRawCopy);
    InstallResult InstallEntry(std::shared_ptr<NSP> nsp, bool overwrite_if_exists = false,
                               const VfsCopyFunction& copy = &VfsRawCopy);

    // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this
    // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a
    // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there.
    // TODO(DarkLordZach): Author real meta-type NCAs and install those.
    InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type,
                               bool overwrite_if_exists = false,
                               const VfsCopyFunction& copy = &VfsRawCopy);

private:
    template <typename T>
    void IterateAllMetadata(std::vector<T>& out,
                            std::function<T(const CNMT&, const ContentRecord&)> proc,
                            std::function<bool(const CNMT&, const ContentRecord&)> filter) const;
    std::vector<NcaID> AccumulateFiles() const;
    void ProcessFiles(const std::vector<NcaID>& ids);
    void AccumulateYuzuMeta();
    boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const;
    VirtualFile GetFileAtID(NcaID id) const;
    VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const;
    InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy,
                                bool overwrite_if_exists,
                                boost::optional<NcaID> override_id = boost::none);
    bool RawInstallYuzuMeta(const CNMT& cnmt);

    VirtualDir dir;
    RegisteredCacheParsingFunction parser;
    // maps tid -> NcaID of meta
    boost::container::flat_map<u64, NcaID> meta_id;
    // maps tid -> meta
    boost::container::flat_map<u64, CNMT> meta;
    // maps tid -> meta for CNMT in yuzu_meta
    boost::container::flat_map<u64, CNMT> yuzu_meta;
};

// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
class RegisteredCacheUnion {
public:
    explicit RegisteredCacheUnion(std::vector<RegisteredCache*> caches);

    void Refresh();

    bool HasEntry(u64 title_id, ContentRecordType type) const;
    bool HasEntry(RegisteredCacheEntry entry) const;

    boost::optional<u32> GetEntryVersion(u64 title_id) const;

    VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
    VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;

    VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
    VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;

    std::unique_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
    std::unique_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;

    std::vector<RegisteredCacheEntry> ListEntries() const;
    // If a parameter is not boost::none, it will be filtered for from all entries.
    std::vector<RegisteredCacheEntry> ListEntriesFilter(
        boost::optional<TitleType> title_type = boost::none,
        boost::optional<ContentRecordType> record_type = boost::none,
        boost::optional<u64> title_id = boost::none) const;

private:
    std::vector<RegisteredCache*> caches;
};

} // namespace FileSys