summaryrefslogtreecommitdiff
path: root/src/core/file_sys
diff options
context:
space:
mode:
Diffstat (limited to 'src/core/file_sys')
-rw-r--r--src/core/file_sys/card_image.cpp6
-rw-r--r--src/core/file_sys/content_archive.cpp179
-rw-r--r--src/core/file_sys/content_archive.h11
-rw-r--r--src/core/file_sys/nca_patch.cpp206
-rw-r--r--src/core/file_sys/nca_patch.h147
-rw-r--r--src/core/file_sys/patch_manager.cpp153
-rw-r--r--src/core/file_sys/patch_manager.h62
-rw-r--r--src/core/file_sys/registered_cache.cpp115
-rw-r--r--src/core/file_sys/registered_cache.h40
-rw-r--r--src/core/file_sys/romfs_factory.cpp13
-rw-r--r--src/core/file_sys/romfs_factory.h2
-rw-r--r--src/core/file_sys/submission_package.cpp5
12 files changed, 915 insertions, 24 deletions
diff --git a/src/core/file_sys/card_image.cpp b/src/core/file_sys/card_image.cpp
index 1bd3353e4..8218893b2 100644
--- a/src/core/file_sys/card_image.cpp
+++ b/src/core/file_sys/card_image.cpp
@@ -52,11 +52,11 @@ XCI::XCI(VirtualFile file_) : file(std::move(file_)), partitions(0x4) {
52 const auto secure_ncas = secure_partition->GetNCAsCollapsed(); 52 const auto secure_ncas = secure_partition->GetNCAsCollapsed();
53 std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas)); 53 std::copy(secure_ncas.begin(), secure_ncas.end(), std::back_inserter(ncas));
54 54
55 program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
56 program = 55 program =
57 secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program); 56 secure_partition->GetNCA(secure_partition->GetProgramTitleID(), ContentRecordType::Program);
58 if (program != nullptr) 57 program_nca_status = secure_partition->GetProgramStatus(secure_partition->GetProgramTitleID());
59 program_nca_status = program->GetStatus(); 58 if (program_nca_status == Loader::ResultStatus::ErrorNSPMissingProgramNCA)
59 program_nca_status = Loader::ResultStatus::ErrorXCIMissingProgramNCA;
60 60
61 auto result = AddNCAFromPartition(XCIPartition::Update); 61 auto result = AddNCAFromPartition(XCIPartition::Update);
62 if (result != Loader::ResultStatus::Success) { 62 if (result != Loader::ResultStatus::Success) {
diff --git a/src/core/file_sys/content_archive.cpp b/src/core/file_sys/content_archive.cpp
index 7cfb6f36b..79bfb6fec 100644
--- a/src/core/file_sys/content_archive.cpp
+++ b/src/core/file_sys/content_archive.cpp
@@ -12,6 +12,7 @@
12#include "core/crypto/aes_util.h" 12#include "core/crypto/aes_util.h"
13#include "core/crypto/ctr_encryption_layer.h" 13#include "core/crypto/ctr_encryption_layer.h"
14#include "core/file_sys/content_archive.h" 14#include "core/file_sys/content_archive.h"
15#include "core/file_sys/nca_patch.h"
15#include "core/file_sys/partition_filesystem.h" 16#include "core/file_sys/partition_filesystem.h"
16#include "core/file_sys/romfs.h" 17#include "core/file_sys/romfs.h"
17#include "core/file_sys/vfs_offset.h" 18#include "core/file_sys/vfs_offset.h"
@@ -68,10 +69,31 @@ struct RomFSSuperblock {
68}; 69};
69static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); 70static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size.");
70 71
72struct BKTRHeader {
73 u64_le offset;
74 u64_le size;
75 u32_le magic;
76 INSERT_PADDING_BYTES(0x4);
77 u32_le number_entries;
78 INSERT_PADDING_BYTES(0x4);
79};
80static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size.");
81
82struct BKTRSuperblock {
83 NCASectionHeaderBlock header_block;
84 IVFCHeader ivfc;
85 INSERT_PADDING_BYTES(0x18);
86 BKTRHeader relocation;
87 BKTRHeader subsection;
88 INSERT_PADDING_BYTES(0xC0);
89};
90static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size.");
91
71union NCASectionHeader { 92union NCASectionHeader {
72 NCASectionRaw raw; 93 NCASectionRaw raw;
73 PFS0Superblock pfs0; 94 PFS0Superblock pfs0;
74 RomFSSuperblock romfs; 95 RomFSSuperblock romfs;
96 BKTRSuperblock bktr;
75}; 97};
76static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); 98static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size.");
77 99
@@ -104,7 +126,7 @@ boost::optional<Core::Crypto::Key128> NCA::GetKeyAreaKey(NCASectionCryptoType ty
104 Core::Crypto::Key128 out; 126 Core::Crypto::Key128 out;
105 if (type == NCASectionCryptoType::XTS) 127 if (type == NCASectionCryptoType::XTS)
106 std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin()); 128 std::copy(key_area.begin(), key_area.begin() + 0x10, out.begin());
107 else if (type == NCASectionCryptoType::CTR) 129 else if (type == NCASectionCryptoType::CTR || type == NCASectionCryptoType::BKTR)
108 std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin()); 130 std::copy(key_area.begin() + 0x20, key_area.begin() + 0x30, out.begin());
109 else 131 else
110 LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}", 132 LOG_CRITICAL(Crypto, "Called GetKeyAreaKey on invalid NCASectionCryptoType type={:02X}",
@@ -154,6 +176,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
154 LOG_DEBUG(Crypto, "called with mode=NONE"); 176 LOG_DEBUG(Crypto, "called with mode=NONE");
155 return in; 177 return in;
156 case NCASectionCryptoType::CTR: 178 case NCASectionCryptoType::CTR:
179 // During normal BKTR decryption, this entire function is skipped. This is for the metadata,
180 // which uses the same CTR as usual.
181 case NCASectionCryptoType::BKTR:
157 LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset); 182 LOG_DEBUG(Crypto, "called with mode=CTR, starting_offset={:016X}", starting_offset);
158 { 183 {
159 boost::optional<Core::Crypto::Key128> key = boost::none; 184 boost::optional<Core::Crypto::Key128> key = boost::none;
@@ -190,7 +215,9 @@ VirtualFile NCA::Decrypt(NCASectionHeader s_header, VirtualFile in, u64 starting
190 } 215 }
191} 216}
192 217
193NCA::NCA(VirtualFile file_) : file(std::move(file_)) { 218NCA::NCA(VirtualFile file_, VirtualFile bktr_base_romfs_, u64 bktr_base_ivfc_offset)
219 : file(std::move(file_)),
220 bktr_base_romfs(bktr_base_romfs_ ? std::move(bktr_base_romfs_) : nullptr) {
194 status = Loader::ResultStatus::Success; 221 status = Loader::ResultStatus::Success;
195 222
196 if (file == nullptr) { 223 if (file == nullptr) {
@@ -265,22 +292,21 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
265 is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) { 292 is_update = std::find_if(sections.begin(), sections.end(), [](const NCASectionHeader& header) {
266 return header.raw.header.crypto_type == NCASectionCryptoType::BKTR; 293 return header.raw.header.crypto_type == NCASectionCryptoType::BKTR;
267 }) != sections.end(); 294 }) != sections.end();
295 ivfc_offset = 0;
268 296
269 for (std::ptrdiff_t i = 0; i < number_sections; ++i) { 297 for (std::ptrdiff_t i = 0; i < number_sections; ++i) {
270 auto section = sections[i]; 298 auto section = sections[i];
271 299
272 if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) { 300 if (section.raw.header.filesystem_type == NCASectionFilesystemType::ROMFS) {
273 const size_t romfs_offset = 301 const size_t base_offset =
274 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER + 302 header.section_tables[i].media_offset * MEDIA_OFFSET_MULTIPLIER;
275 section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset; 303 ivfc_offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
304 const size_t romfs_offset = base_offset + ivfc_offset;
276 const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size; 305 const size_t romfs_size = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].size;
277 auto dec = 306 auto raw = std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset);
278 Decrypt(section, std::make_shared<OffsetVfsFile>(file, romfs_size, romfs_offset), 307 auto dec = Decrypt(section, raw, romfs_offset);
279 romfs_offset); 308
280 if (dec != nullptr) { 309 if (dec == nullptr) {
281 files.push_back(std::move(dec));
282 romfs = files.back();
283 } else {
284 if (status != Loader::ResultStatus::Success) 310 if (status != Loader::ResultStatus::Success)
285 return; 311 return;
286 if (has_rights_id) 312 if (has_rights_id)
@@ -289,6 +315,117 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
289 status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey; 315 status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
290 return; 316 return;
291 } 317 }
318
319 if (section.raw.header.crypto_type == NCASectionCryptoType::BKTR) {
320 if (section.bktr.relocation.magic != Common::MakeMagic('B', 'K', 'T', 'R') ||
321 section.bktr.subsection.magic != Common::MakeMagic('B', 'K', 'T', 'R')) {
322 status = Loader::ResultStatus::ErrorBadBKTRHeader;
323 return;
324 }
325
326 if (section.bktr.relocation.offset + section.bktr.relocation.size !=
327 section.bktr.subsection.offset) {
328 status = Loader::ResultStatus::ErrorBKTRSubsectionNotAfterRelocation;
329 return;
330 }
331
332 const u64 size =
333 MEDIA_OFFSET_MULTIPLIER * (header.section_tables[i].media_end_offset -
334 header.section_tables[i].media_offset);
335 if (section.bktr.subsection.offset + section.bktr.subsection.size != size) {
336 status = Loader::ResultStatus::ErrorBKTRSubsectionNotAtEnd;
337 return;
338 }
339
340 const u64 offset = section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset;
341 RelocationBlock relocation_block{};
342 if (dec->ReadObject(&relocation_block, section.bktr.relocation.offset - offset) !=
343 sizeof(RelocationBlock)) {
344 status = Loader::ResultStatus::ErrorBadRelocationBlock;
345 return;
346 }
347 SubsectionBlock subsection_block{};
348 if (dec->ReadObject(&subsection_block, section.bktr.subsection.offset - offset) !=
349 sizeof(RelocationBlock)) {
350 status = Loader::ResultStatus::ErrorBadSubsectionBlock;
351 return;
352 }
353
354 std::vector<RelocationBucketRaw> relocation_buckets_raw(
355 (section.bktr.relocation.size - sizeof(RelocationBlock)) /
356 sizeof(RelocationBucketRaw));
357 if (dec->ReadBytes(relocation_buckets_raw.data(),
358 section.bktr.relocation.size - sizeof(RelocationBlock),
359 section.bktr.relocation.offset + sizeof(RelocationBlock) -
360 offset) !=
361 section.bktr.relocation.size - sizeof(RelocationBlock)) {
362 status = Loader::ResultStatus::ErrorBadRelocationBuckets;
363 return;
364 }
365
366 std::vector<SubsectionBucketRaw> subsection_buckets_raw(
367 (section.bktr.subsection.size - sizeof(SubsectionBlock)) /
368 sizeof(SubsectionBucketRaw));
369 if (dec->ReadBytes(subsection_buckets_raw.data(),
370 section.bktr.subsection.size - sizeof(SubsectionBlock),
371 section.bktr.subsection.offset + sizeof(SubsectionBlock) -
372 offset) !=
373 section.bktr.subsection.size - sizeof(SubsectionBlock)) {
374 status = Loader::ResultStatus::ErrorBadSubsectionBuckets;
375 return;
376 }
377
378 std::vector<RelocationBucket> relocation_buckets(relocation_buckets_raw.size());
379 std::transform(relocation_buckets_raw.begin(), relocation_buckets_raw.end(),
380 relocation_buckets.begin(), &ConvertRelocationBucketRaw);
381 std::vector<SubsectionBucket> subsection_buckets(subsection_buckets_raw.size());
382 std::transform(subsection_buckets_raw.begin(), subsection_buckets_raw.end(),
383 subsection_buckets.begin(), &ConvertSubsectionBucketRaw);
384
385 u32 ctr_low;
386 std::memcpy(&ctr_low, section.raw.section_ctr.data(), sizeof(ctr_low));
387 subsection_buckets.back().entries.push_back(
388 {section.bktr.relocation.offset, {0}, ctr_low});
389 subsection_buckets.back().entries.push_back({size, {0}, 0});
390
391 boost::optional<Core::Crypto::Key128> key = boost::none;
392 if (encrypted) {
393 if (has_rights_id) {
394 status = Loader::ResultStatus::Success;
395 key = GetTitlekey();
396 if (key == boost::none) {
397 status = Loader::ResultStatus::ErrorMissingTitlekey;
398 return;
399 }
400 } else {
401 key = GetKeyAreaKey(NCASectionCryptoType::BKTR);
402 if (key == boost::none) {
403 status = Loader::ResultStatus::ErrorMissingKeyAreaKey;
404 return;
405 }
406 }
407 }
408
409 if (bktr_base_romfs == nullptr) {
410 status = Loader::ResultStatus::ErrorMissingBKTRBaseRomFS;
411 return;
412 }
413
414 auto bktr = std::make_shared<BKTR>(
415 bktr_base_romfs, std::make_shared<OffsetVfsFile>(file, romfs_size, base_offset),
416 relocation_block, relocation_buckets, subsection_block, subsection_buckets,
417 encrypted, encrypted ? key.get() : Core::Crypto::Key128{}, base_offset,
418 bktr_base_ivfc_offset, section.raw.section_ctr);
419
420 // BKTR applies to entire IVFC, so make an offset version to level 6
421
422 files.push_back(std::make_shared<OffsetVfsFile>(
423 bktr, romfs_size, section.romfs.ivfc.levels[IVFC_MAX_LEVEL - 1].offset));
424 romfs = files.back();
425 } else {
426 files.push_back(std::move(dec));
427 romfs = files.back();
428 }
292 } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) { 429 } else if (section.raw.header.filesystem_type == NCASectionFilesystemType::PFS0) {
293 u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) * 430 u64 offset = (static_cast<u64>(header.section_tables[i].media_offset) *
294 MEDIA_OFFSET_MULTIPLIER) + 431 MEDIA_OFFSET_MULTIPLIER) +
@@ -304,6 +441,12 @@ NCA::NCA(VirtualFile file_) : file(std::move(file_)) {
304 dirs.push_back(std::move(npfs)); 441 dirs.push_back(std::move(npfs));
305 if (IsDirectoryExeFS(dirs.back())) 442 if (IsDirectoryExeFS(dirs.back()))
306 exefs = dirs.back(); 443 exefs = dirs.back();
444 } else {
445 if (has_rights_id)
446 status = Loader::ResultStatus::ErrorIncorrectTitlekeyOrTitlekek;
447 else
448 status = Loader::ResultStatus::ErrorIncorrectKeyAreaKey;
449 return;
307 } 450 }
308 } else { 451 } else {
309 if (status != Loader::ResultStatus::Success) 452 if (status != Loader::ResultStatus::Success)
@@ -349,11 +492,15 @@ NCAContentType NCA::GetType() const {
349} 492}
350 493
351u64 NCA::GetTitleId() const { 494u64 NCA::GetTitleId() const {
352 if (status != Loader::ResultStatus::Success) 495 if (is_update || status == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS)
353 return {}; 496 return header.title_id | 0x800;
354 return header.title_id; 497 return header.title_id;
355} 498}
356 499
500bool NCA::IsUpdate() const {
501 return is_update;
502}
503
357VirtualFile NCA::GetRomFS() const { 504VirtualFile NCA::GetRomFS() const {
358 return romfs; 505 return romfs;
359} 506}
@@ -366,8 +513,8 @@ VirtualFile NCA::GetBaseFile() const {
366 return file; 513 return file;
367} 514}
368 515
369bool NCA::IsUpdate() const { 516u64 NCA::GetBaseIVFCOffset() const {
370 return is_update; 517 return ivfc_offset;
371} 518}
372 519
373bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { 520bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) {
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 0ea666cac..00eca52da 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -79,7 +79,8 @@ bool IsValidNCA(const NCAHeader& header);
79// After construction, use GetStatus to determine if the file is valid and ready to be used. 79// After construction, use GetStatus to determine if the file is valid and ready to be used.
80class NCA : public ReadOnlyVfsDirectory { 80class NCA : public ReadOnlyVfsDirectory {
81public: 81public:
82 explicit NCA(VirtualFile file); 82 explicit NCA(VirtualFile file, VirtualFile bktr_base_romfs = nullptr,
83 u64 bktr_base_ivfc_offset = 0);
83 Loader::ResultStatus GetStatus() const; 84 Loader::ResultStatus GetStatus() const;
84 85
85 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override; 86 std::vector<std::shared_ptr<VfsFile>> GetFiles() const override;
@@ -89,13 +90,15 @@ public:
89 90
90 NCAContentType GetType() const; 91 NCAContentType GetType() const;
91 u64 GetTitleId() const; 92 u64 GetTitleId() const;
93 bool IsUpdate() const;
92 94
93 VirtualFile GetRomFS() const; 95 VirtualFile GetRomFS() const;
94 VirtualDir GetExeFS() const; 96 VirtualDir GetExeFS() const;
95 97
96 VirtualFile GetBaseFile() const; 98 VirtualFile GetBaseFile() const;
97 99
98 bool IsUpdate() const; 100 // Returns the base ivfc offset used in BKTR patching.
101 u64 GetBaseIVFCOffset() const;
99 102
100protected: 103protected:
101 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override; 104 bool ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) override;
@@ -112,14 +115,16 @@ private:
112 VirtualFile romfs = nullptr; 115 VirtualFile romfs = nullptr;
113 VirtualDir exefs = nullptr; 116 VirtualDir exefs = nullptr;
114 VirtualFile file; 117 VirtualFile file;
118 VirtualFile bktr_base_romfs;
119 u64 ivfc_offset;
115 120
116 NCAHeader header{}; 121 NCAHeader header{};
117 bool has_rights_id{}; 122 bool has_rights_id{};
118 bool is_update{};
119 123
120 Loader::ResultStatus status{}; 124 Loader::ResultStatus status{};
121 125
122 bool encrypted; 126 bool encrypted;
127 bool is_update;
123 128
124 Core::Crypto::KeyManager keys; 129 Core::Crypto::KeyManager keys;
125}; 130};
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
new file mode 100644
index 000000000..e0111bffc
--- /dev/null
+++ b/src/core/file_sys/nca_patch.cpp
@@ -0,0 +1,206 @@
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 "common/assert.h"
6#include "core/crypto/aes_util.h"
7#include "core/file_sys/nca_patch.h"
8
9namespace FileSys {
10
11BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
12 std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
13 std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
14 Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
15 std::array<u8, 8> section_ctr_)
16 : base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
17 relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
18 subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
19 encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
20 section_ctr(section_ctr_) {
21 for (size_t i = 0; i < relocation.number_buckets - 1; ++i) {
22 relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0});
23 }
24
25 for (size_t i = 0; i < subsection.number_buckets - 1; ++i) {
26 subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch,
27 {0},
28 subsection_buckets[i + 1].entries[0].ctr});
29 }
30
31 relocation_buckets.back().entries.push_back({relocation.size, 0, 0});
32}
33
34BKTR::~BKTR() = default;
35
36size_t BKTR::Read(u8* data, size_t length, size_t offset) const {
37 // Read out of bounds.
38 if (offset >= relocation.size)
39 return 0;
40 const auto relocation = GetRelocationEntry(offset);
41 const auto section_offset = offset - relocation.address_patch + relocation.address_source;
42 const auto bktr_read = relocation.from_patch;
43
44 const auto next_relocation = GetNextRelocationEntry(offset);
45
46 if (offset + length > next_relocation.address_patch) {
47 const u64 partition = next_relocation.address_patch - offset;
48 return Read(data, partition, offset) +
49 Read(data + partition, length - partition, offset + partition);
50 }
51
52 if (!bktr_read) {
53 ASSERT_MSG(section_offset >= ivfc_offset, "Offset calculation negative.");
54 return base_romfs->Read(data, length, section_offset - ivfc_offset);
55 }
56
57 if (!encrypted) {
58 return bktr_romfs->Read(data, length, section_offset);
59 }
60
61 const auto subsection = GetSubsectionEntry(section_offset);
62 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
63
64 // Calculate AES IV
65 std::vector<u8> iv(16);
66 auto subsection_ctr = subsection.ctr;
67 auto offset_iv = section_offset + base_offset;
68 for (size_t i = 0; i < section_ctr.size(); ++i)
69 iv[i] = section_ctr[0x8 - i - 1];
70 offset_iv >>= 4;
71 for (size_t i = 0; i < sizeof(u64); ++i) {
72 iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
73 offset_iv >>= 8;
74 }
75 for (size_t i = 0; i < sizeof(u32); ++i) {
76 iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
77 subsection_ctr >>= 8;
78 }
79 cipher.SetIV(iv);
80
81 const auto next_subsection = GetNextSubsectionEntry(section_offset);
82
83 if (section_offset + length > next_subsection.address_patch) {
84 const u64 partition = next_subsection.address_patch - section_offset;
85 return Read(data, partition, offset) +
86 Read(data + partition, length - partition, offset + partition);
87 }
88
89 const auto block_offset = section_offset & 0xF;
90 if (block_offset != 0) {
91 auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
92 cipher.Transcode(block.data(), block.size(), block.data(), Core::Crypto::Op::Decrypt);
93 if (length + block_offset < 0x10) {
94 std::memcpy(data, block.data() + block_offset, std::min(length, block.size()));
95 return std::min(length, block.size());
96 }
97
98 const auto read = 0x10 - block_offset;
99 std::memcpy(data, block.data() + block_offset, read);
100 return read + Read(data + read, length - read, offset + read);
101 }
102
103 const auto raw_read = bktr_romfs->Read(data, length, section_offset);
104 cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
105 return raw_read;
106}
107
108template <bool Subsection, typename BlockType, typename BucketType>
109std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
110 BucketType buckets) const {
111 if constexpr (Subsection) {
112 const auto last_bucket = buckets[block.number_buckets - 1];
113 if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
114 return {block.number_buckets - 1, last_bucket.number_entries};
115 } else {
116 ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
117 }
118
119 size_t bucket_id = std::count_if(block.base_offsets.begin() + 1,
120 block.base_offsets.begin() + block.number_buckets,
121 [&offset](u64 base_offset) { return base_offset <= offset; });
122
123 const auto bucket = buckets[bucket_id];
124
125 if (bucket.number_entries == 1)
126 return {bucket_id, 0};
127
128 size_t low = 0;
129 size_t mid = 0;
130 size_t high = bucket.number_entries - 1;
131 while (low <= high) {
132 mid = (low + high) / 2;
133 if (bucket.entries[mid].address_patch > offset) {
134 high = mid - 1;
135 } else {
136 if (mid == bucket.number_entries - 1 ||
137 bucket.entries[mid + 1].address_patch > offset) {
138 return {bucket_id, mid};
139 }
140
141 low = mid + 1;
142 }
143 }
144
145 UNREACHABLE_MSG("Offset could not be found in BKTR block.");
146}
147
148RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
149 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
150 return relocation_buckets[res.first].entries[res.second];
151}
152
153RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
154 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
155 const auto bucket = relocation_buckets[res.first];
156 if (res.second + 1 < bucket.entries.size())
157 return bucket.entries[res.second + 1];
158 return relocation_buckets[res.first + 1].entries[0];
159}
160
161SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
162 const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
163 return subsection_buckets[res.first].entries[res.second];
164}
165
166SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
167 const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
168 const auto bucket = subsection_buckets[res.first];
169 if (res.second + 1 < bucket.entries.size())
170 return bucket.entries[res.second + 1];
171 return subsection_buckets[res.first + 1].entries[0];
172}
173
174std::string BKTR::GetName() const {
175 return base_romfs->GetName();
176}
177
178size_t BKTR::GetSize() const {
179 return relocation.size;
180}
181
182bool BKTR::Resize(size_t new_size) {
183 return false;
184}
185
186std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
187 return base_romfs->GetContainingDirectory();
188}
189
190bool BKTR::IsWritable() const {
191 return false;
192}
193
194bool BKTR::IsReadable() const {
195 return true;
196}
197
198size_t BKTR::Write(const u8* data, size_t length, size_t offset) {
199 return 0;
200}
201
202bool BKTR::Rename(std::string_view name) {
203 return base_romfs->Rename(name);
204}
205
206} // namespace FileSys
diff --git a/src/core/file_sys/nca_patch.h b/src/core/file_sys/nca_patch.h
new file mode 100644
index 000000000..0d9ad95f5
--- /dev/null
+++ b/src/core/file_sys/nca_patch.h
@@ -0,0 +1,147 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <array>
8#include <vector>
9#include <common/common_funcs.h>
10#include "core/crypto/key_manager.h"
11#include "core/file_sys/romfs.h"
12
13namespace FileSys {
14
15#pragma pack(push, 1)
16struct RelocationEntry {
17 u64_le address_patch;
18 u64_le address_source;
19 u32 from_patch;
20};
21#pragma pack(pop)
22static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
23
24struct RelocationBucketRaw {
25 INSERT_PADDING_BYTES(4);
26 u32_le number_entries;
27 u64_le end_offset;
28 std::array<RelocationEntry, 0x332> relocation_entries;
29 INSERT_PADDING_BYTES(8);
30};
31static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
32
33// Vector version of RelocationBucketRaw
34struct RelocationBucket {
35 u32 number_entries;
36 u64 end_offset;
37 std::vector<RelocationEntry> entries;
38};
39
40struct RelocationBlock {
41 INSERT_PADDING_BYTES(4);
42 u32_le number_buckets;
43 u64_le size;
44 std::array<u64, 0x7FE> base_offsets;
45};
46static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
47
48struct SubsectionEntry {
49 u64_le address_patch;
50 INSERT_PADDING_BYTES(0x4);
51 u32_le ctr;
52};
53static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
54
55struct SubsectionBucketRaw {
56 INSERT_PADDING_BYTES(4);
57 u32_le number_entries;
58 u64_le end_offset;
59 std::array<SubsectionEntry, 0x3FF> subsection_entries;
60};
61static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
62
63// Vector version of SubsectionBucketRaw
64struct SubsectionBucket {
65 u32 number_entries;
66 u64 end_offset;
67 std::vector<SubsectionEntry> entries;
68};
69
70struct SubsectionBlock {
71 INSERT_PADDING_BYTES(4);
72 u32_le number_buckets;
73 u64_le size;
74 std::array<u64, 0x7FE> base_offsets;
75};
76static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
77
78inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
79 return {raw.number_entries,
80 raw.end_offset,
81 {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
82}
83
84inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
85 return {raw.number_entries,
86 raw.end_offset,
87 {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
88}
89
90class BKTR : public VfsFile {
91public:
92 BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
93 std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
94 std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
95 Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
96 ~BKTR() override;
97
98 size_t Read(u8* data, size_t length, size_t offset) const override;
99
100 std::string GetName() const override;
101
102 size_t GetSize() const override;
103
104 bool Resize(size_t new_size) override;
105
106 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
107
108 bool IsWritable() const override;
109
110 bool IsReadable() const override;
111
112 size_t Write(const u8* data, size_t length, size_t offset) override;
113
114 bool Rename(std::string_view name) override;
115
116private:
117 template <bool Subsection, typename BlockType, typename BucketType>
118 std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block,
119 BucketType buckets) const;
120
121 RelocationEntry GetRelocationEntry(u64 offset) const;
122 RelocationEntry GetNextRelocationEntry(u64 offset) const;
123
124 SubsectionEntry GetSubsectionEntry(u64 offset) const;
125 SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
126
127 RelocationBlock relocation;
128 std::vector<RelocationBucket> relocation_buckets;
129 SubsectionBlock subsection;
130 std::vector<SubsectionBucket> subsection_buckets;
131
132 // Should be the raw base romfs, decrypted.
133 VirtualFile base_romfs;
134 // Should be the raw BKTR romfs, (located at media_offset with size media_size).
135 VirtualFile bktr_romfs;
136
137 bool encrypted;
138 Core::Crypto::Key128 key;
139
140 // Base offset into NCA, used for IV calculation.
141 u64 base_offset;
142 // Distance between IVFC start and RomFS start, used for base reads
143 u64 ivfc_offset;
144 std::array<u8, 8> section_ctr;
145};
146
147} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
new file mode 100644
index 000000000..40675de35
--- /dev/null
+++ b/src/core/file_sys/patch_manager.cpp
@@ -0,0 +1,153 @@
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 "core/file_sys/content_archive.h"
6#include "core/file_sys/control_metadata.h"
7#include "core/file_sys/patch_manager.h"
8#include "core/file_sys/registered_cache.h"
9#include "core/file_sys/romfs.h"
10#include "core/hle/service/filesystem/filesystem.h"
11#include "core/loader/loader.h"
12
13namespace FileSys {
14
15constexpr u64 SINGLE_BYTE_MODULUS = 0x100;
16
17std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
18 std::array<u8, sizeof(u32)> bytes{};
19 bytes[0] = version % SINGLE_BYTE_MODULUS;
20 for (size_t i = 1; i < bytes.size(); ++i) {
21 version /= SINGLE_BYTE_MODULUS;
22 bytes[i] = version % SINGLE_BYTE_MODULUS;
23 }
24
25 if (format == TitleVersionFormat::FourElements)
26 return fmt::format("v{}.{}.{}.{}", bytes[3], bytes[2], bytes[1], bytes[0]);
27 return fmt::format("v{}.{}.{}", bytes[3], bytes[2], bytes[1]);
28}
29
30constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{
31 "Update",
32};
33
34std::string FormatPatchTypeName(PatchType type) {
35 return PATCH_TYPE_NAMES.at(static_cast<size_t>(type));
36}
37
38PatchManager::PatchManager(u64 title_id) : title_id(title_id) {}
39
40VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
41 LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id);
42
43 if (exefs == nullptr)
44 return exefs;
45
46 const auto installed = Service::FileSystem::GetUnionContents();
47
48 // Game Updates
49 const auto update_tid = GetUpdateTitleID(title_id);
50 const auto update = installed->GetEntry(update_tid, ContentRecordType::Program);
51 if (update != nullptr) {
52 if (update->GetStatus() == Loader::ResultStatus::ErrorMissingBKTRBaseRomFS &&
53 update->GetExeFS() != nullptr) {
54 LOG_INFO(Loader, " ExeFS: Update ({}) applied successfully",
55 FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
56 exefs = update->GetExeFS();
57 }
58 }
59
60 return exefs;
61}
62
63VirtualFile PatchManager::PatchRomFS(VirtualFile romfs, u64 ivfc_offset,
64 ContentRecordType type) const {
65 LOG_INFO(Loader, "Patching RomFS for title_id={:016X}, type={:02X}", title_id,
66 static_cast<u8>(type));
67
68 if (romfs == nullptr)
69 return romfs;
70
71 const auto installed = Service::FileSystem::GetUnionContents();
72
73 // Game Updates
74 const auto update_tid = GetUpdateTitleID(title_id);
75 const auto update = installed->GetEntryRaw(update_tid, type);
76 if (update != nullptr) {
77 const auto new_nca = std::make_shared<NCA>(update, romfs, ivfc_offset);
78 if (new_nca->GetStatus() == Loader::ResultStatus::Success &&
79 new_nca->GetRomFS() != nullptr) {
80 LOG_INFO(Loader, " RomFS: Update ({}) applied successfully",
81 FormatTitleVersion(installed->GetEntryVersion(update_tid).get_value_or(0)));
82 romfs = new_nca->GetRomFS();
83 }
84 }
85
86 return romfs;
87}
88
89std::map<PatchType, std::string> PatchManager::GetPatchVersionNames() const {
90 std::map<PatchType, std::string> out;
91 const auto installed = Service::FileSystem::GetUnionContents();
92
93 const auto update_tid = GetUpdateTitleID(title_id);
94 PatchManager update{update_tid};
95 auto [nacp, discard_icon_file] = update.GetControlMetadata();
96
97 if (nacp != nullptr) {
98 out[PatchType::Update] = nacp->GetVersionString();
99 } else {
100 if (installed->HasEntry(update_tid, ContentRecordType::Program)) {
101 const auto meta_ver = installed->GetEntryVersion(update_tid);
102 if (meta_ver == boost::none || meta_ver.get() == 0) {
103 out[PatchType::Update] = "";
104 } else {
105 out[PatchType::Update] =
106 FormatTitleVersion(meta_ver.get(), TitleVersionFormat::ThreeElements);
107 }
108 }
109 }
110
111 return out;
112}
113
114std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::GetControlMetadata() const {
115 const auto& installed{Service::FileSystem::GetUnionContents()};
116
117 const auto base_control_nca = installed->GetEntry(title_id, ContentRecordType::Control);
118 if (base_control_nca == nullptr)
119 return {};
120
121 return ParseControlNCA(base_control_nca);
122}
123
124std::pair<std::shared_ptr<NACP>, VirtualFile> PatchManager::ParseControlNCA(
125 const std::shared_ptr<NCA>& nca) const {
126 const auto base_romfs = nca->GetRomFS();
127 if (base_romfs == nullptr)
128 return {};
129
130 const auto romfs = PatchRomFS(base_romfs, nca->GetBaseIVFCOffset(), ContentRecordType::Control);
131 if (romfs == nullptr)
132 return {};
133
134 const auto extracted = ExtractRomFS(romfs);
135 if (extracted == nullptr)
136 return {};
137
138 auto nacp_file = extracted->GetFile("control.nacp");
139 if (nacp_file == nullptr)
140 nacp_file = extracted->GetFile("Control.nacp");
141
142 const auto nacp = nacp_file == nullptr ? nullptr : std::make_shared<NACP>(nacp_file);
143
144 VirtualFile icon_file;
145 for (const auto& language : FileSys::LANGUAGE_NAMES) {
146 icon_file = extracted->GetFile("icon_" + std::string(language) + ".dat");
147 if (icon_file != nullptr)
148 break;
149 }
150
151 return {nacp, icon_file};
152}
153} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
new file mode 100644
index 000000000..28c7ae136
--- /dev/null
+++ b/src/core/file_sys/patch_manager.h
@@ -0,0 +1,62 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <map>
8#include <string>
9#include "common/common_types.h"
10#include "core/file_sys/nca_metadata.h"
11#include "core/file_sys/vfs.h"
12
13namespace FileSys {
14
15class NCA;
16class NACP;
17
18enum class TitleVersionFormat : u8 {
19 ThreeElements, ///< vX.Y.Z
20 FourElements, ///< vX.Y.Z.W
21};
22
23std::string FormatTitleVersion(u32 version,
24 TitleVersionFormat format = TitleVersionFormat::ThreeElements);
25
26enum class PatchType {
27 Update,
28};
29
30std::string FormatPatchTypeName(PatchType type);
31
32// A centralized class to manage patches to games.
33class PatchManager {
34public:
35 explicit PatchManager(u64 title_id);
36
37 // Currently tracked ExeFS patches:
38 // - Game Updates
39 VirtualDir PatchExeFS(VirtualDir exefs) const;
40
41 // Currently tracked RomFS patches:
42 // - Game Updates
43 VirtualFile PatchRomFS(VirtualFile base, u64 ivfc_offset,
44 ContentRecordType type = ContentRecordType::Program) const;
45
46 // Returns a vector of pairs between patch names and patch versions.
47 // i.e. Update v80 will return {Update, 80}
48 std::map<PatchType, std::string> GetPatchVersionNames() const;
49
50 // Given title_id of the program, attempts to get the control data of the update and parse it,
51 // falling back to the base control data.
52 std::pair<std::shared_ptr<NACP>, VirtualFile> GetControlMetadata() const;
53
54 // Version of GetControlMetadata that takes an arbitrary NCA
55 std::pair<std::shared_ptr<NACP>, VirtualFile> ParseControlNCA(
56 const std::shared_ptr<NCA>& nca) const;
57
58private:
59 u64 title_id;
60};
61
62} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index cf6f77401..7361a67be 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -280,6 +280,18 @@ VirtualFile RegisteredCache::GetEntryUnparsed(RegisteredCacheEntry entry) const
280 return GetEntryUnparsed(entry.title_id, entry.type); 280 return GetEntryUnparsed(entry.title_id, entry.type);
281} 281}
282 282
283boost::optional<u32> RegisteredCache::GetEntryVersion(u64 title_id) const {
284 const auto meta_iter = meta.find(title_id);
285 if (meta_iter != meta.end())
286 return meta_iter->second.GetTitleVersion();
287
288 const auto yuzu_meta_iter = yuzu_meta.find(title_id);
289 if (yuzu_meta_iter != yuzu_meta.end())
290 return yuzu_meta_iter->second.GetTitleVersion();
291
292 return boost::none;
293}
294
283VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { 295VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const {
284 const auto id = GetNcaIDFromMetadata(title_id, type); 296 const auto id = GetNcaIDFromMetadata(title_id, type);
285 if (id == boost::none) 297 if (id == boost::none)
@@ -498,4 +510,107 @@ bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) {
498 kv.second.GetTitleID() == cnmt.GetTitleID(); 510 kv.second.GetTitleID() == cnmt.GetTitleID();
499 }) != yuzu_meta.end(); 511 }) != yuzu_meta.end();
500} 512}
513
514RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches)
515 : caches(std::move(caches)) {}
516
517void RegisteredCacheUnion::Refresh() {
518 for (const auto& c : caches)
519 c->Refresh();
520}
521
522bool RegisteredCacheUnion::HasEntry(u64 title_id, ContentRecordType type) const {
523 return std::any_of(caches.begin(), caches.end(), [title_id, type](const auto& cache) {
524 return cache->HasEntry(title_id, type);
525 });
526}
527
528bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const {
529 return HasEntry(entry.title_id, entry.type);
530}
531
532boost::optional<u32> RegisteredCacheUnion::GetEntryVersion(u64 title_id) const {
533 for (const auto& c : caches) {
534 const auto res = c->GetEntryVersion(title_id);
535 if (res != boost::none)
536 return res;
537 }
538
539 return boost::none;
540}
541
542VirtualFile RegisteredCacheUnion::GetEntryUnparsed(u64 title_id, ContentRecordType type) const {
543 for (const auto& c : caches) {
544 const auto res = c->GetEntryUnparsed(title_id, type);
545 if (res != nullptr)
546 return res;
547 }
548
549 return nullptr;
550}
551
552VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const {
553 return GetEntryUnparsed(entry.title_id, entry.type);
554}
555
556VirtualFile RegisteredCacheUnion::GetEntryRaw(u64 title_id, ContentRecordType type) const {
557 for (const auto& c : caches) {
558 const auto res = c->GetEntryRaw(title_id, type);
559 if (res != nullptr)
560 return res;
561 }
562
563 return nullptr;
564}
565
566VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const {
567 return GetEntryRaw(entry.title_id, entry.type);
568}
569
570std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(u64 title_id, ContentRecordType type) const {
571 const auto raw = GetEntryRaw(title_id, type);
572 if (raw == nullptr)
573 return nullptr;
574 return std::make_shared<NCA>(raw);
575}
576
577std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const {
578 return GetEntry(entry.title_id, entry.type);
579}
580
581std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntries() const {
582 std::vector<RegisteredCacheEntry> out;
583 for (const auto& c : caches) {
584 c->IterateAllMetadata<RegisteredCacheEntry>(
585 out,
586 [](const CNMT& c, const ContentRecord& r) {
587 return RegisteredCacheEntry{c.GetTitleID(), r.type};
588 },
589 [](const CNMT& c, const ContentRecord& r) { return true; });
590 }
591 return out;
592}
593
594std::vector<RegisteredCacheEntry> RegisteredCacheUnion::ListEntriesFilter(
595 boost::optional<TitleType> title_type, boost::optional<ContentRecordType> record_type,
596 boost::optional<u64> title_id) const {
597 std::vector<RegisteredCacheEntry> out;
598 for (const auto& c : caches) {
599 c->IterateAllMetadata<RegisteredCacheEntry>(
600 out,
601 [](const CNMT& c, const ContentRecord& r) {
602 return RegisteredCacheEntry{c.GetTitleID(), r.type};
603 },
604 [&title_type, &record_type, &title_id](const CNMT& c, const ContentRecord& r) {
605 if (title_type != boost::none && title_type.get() != c.GetType())
606 return false;
607 if (record_type != boost::none && record_type.get() != r.type)
608 return false;
609 if (title_id != boost::none && title_id.get() != c.GetTitleID())
610 return false;
611 return true;
612 });
613 }
614 return out;
615}
501} // namespace FileSys 616} // namespace FileSys
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h
index 467ceeef1..f487b0cf0 100644
--- a/src/core/file_sys/registered_cache.h
+++ b/src/core/file_sys/registered_cache.h
@@ -43,6 +43,10 @@ struct RegisteredCacheEntry {
43 std::string DebugInfo() const; 43 std::string DebugInfo() const;
44}; 44};
45 45
46constexpr u64 GetUpdateTitleID(u64 base_title_id) {
47 return base_title_id | 0x800;
48}
49
46// boost flat_map requires operator< for O(log(n)) lookups. 50// boost flat_map requires operator< for O(log(n)) lookups.
47bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); 51bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs);
48 52
@@ -60,6 +64,8 @@ bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs)
60 * 4GB splitting can be ignored.) 64 * 4GB splitting can be ignored.)
61 */ 65 */
62class RegisteredCache { 66class RegisteredCache {
67 friend class RegisteredCacheUnion;
68
63public: 69public:
64 // Parsing function defines the conversion from raw file to NCA. If there are other steps 70 // Parsing function defines the conversion from raw file to NCA. If there are other steps
65 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom 71 // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom
@@ -74,6 +80,8 @@ public:
74 bool HasEntry(u64 title_id, ContentRecordType type) const; 80 bool HasEntry(u64 title_id, ContentRecordType type) const;
75 bool HasEntry(RegisteredCacheEntry entry) const; 81 bool HasEntry(RegisteredCacheEntry entry) const;
76 82
83 boost::optional<u32> GetEntryVersion(u64 title_id) const;
84
77 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const; 85 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
78 VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const; 86 VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
79 87
@@ -131,4 +139,36 @@ private:
131 boost::container::flat_map<u64, CNMT> yuzu_meta; 139 boost::container::flat_map<u64, CNMT> yuzu_meta;
132}; 140};
133 141
142// Combines multiple RegisteredCaches (i.e. SysNAND, UserNAND, SDMC) into one interface.
143class RegisteredCacheUnion {
144public:
145 explicit RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches);
146
147 void Refresh();
148
149 bool HasEntry(u64 title_id, ContentRecordType type) const;
150 bool HasEntry(RegisteredCacheEntry entry) const;
151
152 boost::optional<u32> GetEntryVersion(u64 title_id) const;
153
154 VirtualFile GetEntryUnparsed(u64 title_id, ContentRecordType type) const;
155 VirtualFile GetEntryUnparsed(RegisteredCacheEntry entry) const;
156
157 VirtualFile GetEntryRaw(u64 title_id, ContentRecordType type) const;
158 VirtualFile GetEntryRaw(RegisteredCacheEntry entry) const;
159
160 std::shared_ptr<NCA> GetEntry(u64 title_id, ContentRecordType type) const;
161 std::shared_ptr<NCA> GetEntry(RegisteredCacheEntry entry) const;
162
163 std::vector<RegisteredCacheEntry> ListEntries() const;
164 // If a parameter is not boost::none, it will be filtered for from all entries.
165 std::vector<RegisteredCacheEntry> ListEntriesFilter(
166 boost::optional<TitleType> title_type = boost::none,
167 boost::optional<ContentRecordType> record_type = boost::none,
168 boost::optional<u64> title_id = boost::none) const;
169
170private:
171 std::vector<std::shared_ptr<RegisteredCache>> caches;
172};
173
134} // namespace FileSys 174} // namespace FileSys
diff --git a/src/core/file_sys/romfs_factory.cpp b/src/core/file_sys/romfs_factory.cpp
index 66f9786e0..d9d90939e 100644
--- a/src/core/file_sys/romfs_factory.cpp
+++ b/src/core/file_sys/romfs_factory.cpp
@@ -6,9 +6,13 @@
6#include "common/assert.h" 6#include "common/assert.h"
7#include "common/common_types.h" 7#include "common/common_types.h"
8#include "common/logging/log.h" 8#include "common/logging/log.h"
9#include "core/core.h"
9#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
11#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/patch_manager.h"
10#include "core/file_sys/registered_cache.h" 13#include "core/file_sys/registered_cache.h"
11#include "core/file_sys/romfs_factory.h" 14#include "core/file_sys/romfs_factory.h"
15#include "core/hle/kernel/process.h"
12#include "core/hle/service/filesystem/filesystem.h" 16#include "core/hle/service/filesystem/filesystem.h"
13#include "core/loader/loader.h" 17#include "core/loader/loader.h"
14 18
@@ -19,10 +23,17 @@ RomFSFactory::RomFSFactory(Loader::AppLoader& app_loader) {
19 if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) { 23 if (app_loader.ReadRomFS(file) != Loader::ResultStatus::Success) {
20 LOG_ERROR(Service_FS, "Unable to read RomFS!"); 24 LOG_ERROR(Service_FS, "Unable to read RomFS!");
21 } 25 }
26
27 updatable = app_loader.IsRomFSUpdatable();
28 ivfc_offset = app_loader.ReadRomFSIVFCOffset();
22} 29}
23 30
24ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { 31ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() {
25 return MakeResult<VirtualFile>(file); 32 if (!updatable)
33 return MakeResult<VirtualFile>(file);
34
35 const PatchManager patch_manager(Core::CurrentProcess()->program_id);
36 return MakeResult<VirtualFile>(patch_manager.PatchRomFS(file, ivfc_offset));
26} 37}
27 38
28ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { 39ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) {
diff --git a/src/core/file_sys/romfs_factory.h b/src/core/file_sys/romfs_factory.h
index f38ddc4f7..26b8f46cc 100644
--- a/src/core/file_sys/romfs_factory.h
+++ b/src/core/file_sys/romfs_factory.h
@@ -36,6 +36,8 @@ public:
36 36
37private: 37private:
38 VirtualFile file; 38 VirtualFile file;
39 bool updatable;
40 u64 ivfc_offset;
39}; 41};
40 42
41} // namespace FileSys 43} // namespace FileSys
diff --git a/src/core/file_sys/submission_package.cpp b/src/core/file_sys/submission_package.cpp
index bde879861..182b40698 100644
--- a/src/core/file_sys/submission_package.cpp
+++ b/src/core/file_sys/submission_package.cpp
@@ -60,8 +60,11 @@ NSP::NSP(VirtualFile file_)
60 for (const auto& outer_file : files) { 60 for (const auto& outer_file : files) {
61 if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") { 61 if (outer_file->GetName().substr(outer_file->GetName().size() - 9) == ".cnmt.nca") {
62 const auto nca = std::make_shared<NCA>(outer_file); 62 const auto nca = std::make_shared<NCA>(outer_file);
63 if (nca->GetStatus() != Loader::ResultStatus::Success) 63 if (nca->GetStatus() != Loader::ResultStatus::Success) {
64 program_status[nca->GetTitleId()] = nca->GetStatus();
64 continue; 65 continue;
66 }
67
65 const auto section0 = nca->GetSubdirectories()[0]; 68 const auto section0 = nca->GetSubdirectories()[0];
66 69
67 for (const auto& inner_file : section0->GetFiles()) { 70 for (const auto& inner_file : section0->GetFiles()) {