1 // Copyright 2018 yuzu emulator team
2 // Licensed under GPLv2 or any later version
3 // Refer to the license.txt file included.
4
5 #include <array>
6 #include <string>
7
8 #include <fmt/ostream.h>
9
10 #include "common/logging/log.h"
11 #include "core/crypto/key_manager.h"
12 #include "core/file_sys/card_image.h"
13 #include "core/file_sys/content_archive.h"
14 #include "core/file_sys/nca_metadata.h"
15 #include "core/file_sys/partition_filesystem.h"
16 #include "core/file_sys/submission_package.h"
17 #include "core/file_sys/vfs_concat.h"
18 #include "core/file_sys/vfs_offset.h"
19 #include "core/file_sys/vfs_vector.h"
20 #include "core/loader/loader.h"
21
22 namespace FileSys {
23
24 constexpr u64 GAMECARD_CERTIFICATE_OFFSET = 0x7000;
25 constexpr std::array partition_names{
26 "update",
27 "normal",
28 "secure",
29 "logo",
30 };
31
XCI(VirtualFile file_,std::size_t program_index)32 XCI::XCI(VirtualFile file_, std::size_t program_index)
33 : file(std::move(file_)), program_nca_status{Loader::ResultStatus::ErrorXCIMissingProgramNCA},
34 partitions(partition_names.size()),
35 partitions_raw(partition_names.size()), keys{Core::Crypto::KeyManager::Instance()} {
36 if (file->ReadObject(&header) != sizeof(GamecardHeader)) {
37 status = Loader::ResultStatus::ErrorBadXCIHeader;
38 return;
39 }
40
41 if (header.magic != Common::MakeMagic('H', 'E', 'A', 'D')) {
42 status = Loader::ResultStatus::ErrorBadXCIHeader;
43 return;
44 }
45
46 PartitionFilesystem main_hfs(std::make_shared<OffsetVfsFile>(
47 file, file->GetSize() - header.hfs_offset, header.hfs_offset));
48
49 update_normal_partition_end = main_hfs.GetFileOffsets()["secure"];
50
51 if (main_hfs.GetStatus() != Loader::ResultStatus::Success) {
52 status = main_hfs.GetStatus();
53 return;
54 }
55
56 for (XCIPartition partition :
57 {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
58 const auto partition_idx = static_cast<std::size_t>(partition);
59 auto raw = main_hfs.GetFile(partition_names[partition_idx]);
60
61 partitions_raw[static_cast<std::size_t>(partition)] = std::move(raw);
62 }
63
64 secure_partition = std::make_shared<NSP>(
65 main_hfs.GetFile(partition_names[static_cast<std::size_t>(XCIPartition::Secure)]),
66 program_index);
67
68 ncas = secure_partition->GetNCAsCollapsed();
69 program =
70 secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
71 program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
72 if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA) {
73 program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
74 }
75
76 auto result = AddNCAFromPartition(XCIPartition::Normal);
77 if (result != Loader::ResultStatus::Success) {
78 status = result;
79 return;
80 }
81
82 if (GetFormatVersion() >= 0x2) {
83 result = AddNCAFromPartition(XCIPartition::Logo);
84 if (result != Loader::ResultStatus::Success) {
85 status = result;
86 return;
87 }
88 }
89
90 status = Loader::ResultStatus::Success;
91 }
92
93 XCI::~XCI() = default;
94
GetStatus() const95 Loader::ResultStatus XCI::GetStatus() const {
96 return status;
97 }
98
GetProgramNCAStatus() const99 Loader::ResultStatus XCI::GetProgramNCAStatus() const {
100 return program_nca_status;
101 }
102
GetPartition(XCIPartition partition)103 VirtualDir XCI::GetPartition(XCIPartition partition) {
104 const auto id = static_cast<std::size_t>(partition);
105 if (partitions[id] == nullptr && partitions_raw[id] != nullptr) {
106 partitions[id] = std::make_shared<PartitionFilesystem>(partitions_raw[id]);
107 }
108
109 return partitions[static_cast<std::size_t>(partition)];
110 }
111
GetPartitions()112 std::vector<VirtualDir> XCI::GetPartitions() {
113 std::vector<VirtualDir> out;
114 for (const auto& id :
115 {XCIPartition::Update, XCIPartition::Normal, XCIPartition::Secure, XCIPartition::Logo}) {
116 const auto part = GetPartition(id);
117 if (part != nullptr) {
118 out.push_back(part);
119 }
120 }
121 return out;
122 }
123
GetSecurePartitionNSP() const124 std::shared_ptr<NSP> XCI::GetSecurePartitionNSP() const {
125 return secure_partition;
126 }
127
GetSecurePartition()128 VirtualDir XCI::GetSecurePartition() {
129 return GetPartition(XCIPartition::Secure);
130 }
131
GetNormalPartition()132 VirtualDir XCI::GetNormalPartition() {
133 return GetPartition(XCIPartition::Normal);
134 }
135
GetUpdatePartition()136 VirtualDir XCI::GetUpdatePartition() {
137 return GetPartition(XCIPartition::Update);
138 }
139
GetLogoPartition()140 VirtualDir XCI::GetLogoPartition() {
141 return GetPartition(XCIPartition::Logo);
142 }
143
GetPartitionRaw(XCIPartition partition) const144 VirtualFile XCI::GetPartitionRaw(XCIPartition partition) const {
145 return partitions_raw[static_cast<std::size_t>(partition)];
146 }
147
GetSecurePartitionRaw() const148 VirtualFile XCI::GetSecurePartitionRaw() const {
149 return GetPartitionRaw(XCIPartition::Secure);
150 }
151
GetStoragePartition0() const152 VirtualFile XCI::GetStoragePartition0() const {
153 return std::make_shared<OffsetVfsFile>(file, update_normal_partition_end, 0, "partition0");
154 }
155
GetStoragePartition1() const156 VirtualFile XCI::GetStoragePartition1() const {
157 return std::make_shared<OffsetVfsFile>(file, file->GetSize() - update_normal_partition_end,
158 update_normal_partition_end, "partition1");
159 }
160
GetNormalPartitionRaw() const161 VirtualFile XCI::GetNormalPartitionRaw() const {
162 return GetPartitionRaw(XCIPartition::Normal);
163 }
164
GetUpdatePartitionRaw() const165 VirtualFile XCI::GetUpdatePartitionRaw() const {
166 return GetPartitionRaw(XCIPartition::Update);
167 }
168
GetLogoPartitionRaw() const169 VirtualFile XCI::GetLogoPartitionRaw() const {
170 return GetPartitionRaw(XCIPartition::Logo);
171 }
172
GetProgramTitleID() const173 u64 XCI::GetProgramTitleID() const {
174 return secure_partition->GetProgramTitleID();
175 }
176
GetSystemUpdateVersion()177 u32 XCI::GetSystemUpdateVersion() {
178 const auto update = GetPartition(XCIPartition::Update);
179 if (update == nullptr)
180 return 0;
181
182 for (const auto& file : update->GetFiles()) {
183 NCA nca{file, nullptr, 0};
184
185 if (nca.GetStatus() != Loader::ResultStatus::Success)
186 continue;
187
188 if (nca.GetType() == NCAContentType::Meta && nca.GetTitleId() == 0x0100000000000816) {
189 const auto dir = nca.GetSubdirectories()[0];
190 const auto cnmt = dir->GetFile("SystemUpdate_0100000000000816.cnmt");
191 if (cnmt == nullptr)
192 continue;
193
194 CNMT cnmt_data{cnmt};
195
196 const auto metas = cnmt_data.GetMetaRecords();
197 if (metas.empty())
198 continue;
199
200 return metas[0].title_version;
201 }
202 }
203
204 return 0;
205 }
206
GetSystemUpdateTitleID() const207 u64 XCI::GetSystemUpdateTitleID() const {
208 return 0x0100000000000816;
209 }
210
HasProgramNCA() const211 bool XCI::HasProgramNCA() const {
212 return program != nullptr;
213 }
214
GetProgramNCAFile() const215 VirtualFile XCI::GetProgramNCAFile() const {
216 if (!HasProgramNCA()) {
217 return nullptr;
218 }
219
220 return program->GetBaseFile();
221 }
222
GetNCAs() const223 const std::vector<std::shared_ptr<NCA>>& XCI::GetNCAs() const {
224 return ncas;
225 }
226
GetNCAByType(NCAContentType type) const227 std::shared_ptr<NCA> XCI::GetNCAByType(NCAContentType type) const {
228 const auto iter =
229 std::find_if(ncas.begin(), ncas.end(),
230 [type](const std::shared_ptr<NCA>& nca) { return nca->GetType() == type; });
231 return iter == ncas.end() ? nullptr : *iter;
232 }
233
GetNCAFileByType(NCAContentType type) const234 VirtualFile XCI::GetNCAFileByType(NCAContentType type) const {
235 auto nca = GetNCAByType(type);
236 if (nca != nullptr) {
237 return nca->GetBaseFile();
238 }
239 return nullptr;
240 }
241
GetFiles() const242 std::vector<VirtualFile> XCI::GetFiles() const {
243 return {};
244 }
245
GetSubdirectories() const246 std::vector<VirtualDir> XCI::GetSubdirectories() const {
247 return {};
248 }
249
GetName() const250 std::string XCI::GetName() const {
251 return file->GetName();
252 }
253
GetParentDirectory() const254 VirtualDir XCI::GetParentDirectory() const {
255 return file->GetContainingDirectory();
256 }
257
ConcatenatedPseudoDirectory()258 VirtualDir XCI::ConcatenatedPseudoDirectory() {
259 const auto out = std::make_shared<VectorVfsDirectory>();
260 for (const auto& part_id : {XCIPartition::Normal, XCIPartition::Logo, XCIPartition::Secure}) {
261 const auto& part = GetPartition(part_id);
262 if (part == nullptr)
263 continue;
264
265 for (const auto& file : part->GetFiles())
266 out->AddFile(file);
267 }
268
269 return out;
270 }
271
GetCertificate() const272 std::array<u8, 0x200> XCI::GetCertificate() const {
273 std::array<u8, 0x200> out;
274 file->Read(out.data(), out.size(), GAMECARD_CERTIFICATE_OFFSET);
275 return out;
276 }
277
AddNCAFromPartition(XCIPartition part)278 Loader::ResultStatus XCI::AddNCAFromPartition(XCIPartition part) {
279 const auto partition_index = static_cast<std::size_t>(part);
280 const auto partition = GetPartition(part);
281
282 if (partition == nullptr) {
283 return Loader::ResultStatus::ErrorXCIMissingPartition;
284 }
285
286 for (const VirtualFile& file : partition->GetFiles()) {
287 if (file->GetExtension() != "nca") {
288 continue;
289 }
290
291 auto nca = std::make_shared<NCA>(file, nullptr, 0);
292 if (nca->IsUpdate()) {
293 continue;
294 }
295 if (nca->GetType() == NCAContentType::Program) {
296 program_nca_status = nca->GetStatus();
297 }
298 if (nca->GetStatus() == Loader::ResultStatus::Success) {
299 ncas.push_back(std::move(nca));
300 } else {
301 const u16 error_id = static_cast<u16>(nca->GetStatus());
302 LOG_CRITICAL(Loader, "Could not load NCA {}/{}, failed with error code {:04X} ({})",
303 partition_names[partition_index], nca->GetName(), error_id,
304 nca->GetStatus());
305 }
306 }
307
308 return Loader::ResultStatus::Success;
309 }
310
GetFormatVersion()311 u8 XCI::GetFormatVersion() {
312 return GetLogoPartition() == nullptr ? 0x1 : 0x2;
313 }
314 } // namespace FileSys
315