summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar bunnei2018-09-05 18:06:11 -0400
committerGravatar GitHub2018-09-05 18:06:11 -0400
commita6ae7654105fe6ec46ff0bcabb714b8447b83899 (patch)
tree0ff4d2396cb0730ec5952181e4e67947b64832ec /src
parentMerge pull request #1245 from degasus/optimizations (diff)
parentbktr: Fix bucket overlap error (diff)
downloadyuzu-a6ae7654105fe6ec46ff0bcabb714b8447b83899.tar.gz
yuzu-a6ae7654105fe6ec46ff0bcabb714b8447b83899.tar.xz
yuzu-a6ae7654105fe6ec46ff0bcabb714b8447b83899.zip
Merge pull request #1179 from DarkLordZach/bktr
file_sys: Add support for BKTR format (Game Updates)
Diffstat (limited to 'src')
-rw-r--r--src/core/CMakeLists.txt4
-rw-r--r--src/core/crypto/aes_util.cpp14
-rw-r--r--src/core/crypto/ctr_encryption_layer.cpp2
-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
-rw-r--r--src/core/hle/service/filesystem/filesystem.cpp7
-rw-r--r--src/core/hle/service/filesystem/filesystem.h3
-rw-r--r--src/core/loader/deconstructed_rom_directory.cpp42
-rw-r--r--src/core/loader/deconstructed_rom_directory.h8
-rw-r--r--src/core/loader/loader.cpp13
-rw-r--r--src/core/loader/loader.h29
-rw-r--r--src/core/loader/nca.cpp8
-rw-r--r--src/core/loader/nca.h1
-rw-r--r--src/core/loader/nro.cpp5
-rw-r--r--src/core/loader/nro.h1
-rw-r--r--src/core/loader/nsp.cpp20
-rw-r--r--src/core/loader/xci.cpp18
-rw-r--r--src/core/telemetry_session.cpp24
-rw-r--r--src/yuzu/game_list.cpp72
-rw-r--r--src/yuzu/game_list.h1
-rw-r--r--src/yuzu/game_list_p.h2
-rw-r--r--src/yuzu/main.cpp20
32 files changed, 1132 insertions, 101 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 54afa6a87..7ddc87539 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -35,8 +35,12 @@ add_library(core STATIC
35 file_sys/mode.h 35 file_sys/mode.h
36 file_sys/nca_metadata.cpp 36 file_sys/nca_metadata.cpp
37 file_sys/nca_metadata.h 37 file_sys/nca_metadata.h
38 file_sys/nca_patch.cpp
39 file_sys/nca_patch.h
38 file_sys/partition_filesystem.cpp 40 file_sys/partition_filesystem.cpp
39 file_sys/partition_filesystem.h 41 file_sys/partition_filesystem.h
42 file_sys/patch_manager.cpp
43 file_sys/patch_manager.h
40 file_sys/program_metadata.cpp 44 file_sys/program_metadata.cpp
41 file_sys/program_metadata.h 45 file_sys/program_metadata.h
42 file_sys/registered_cache.cpp 46 file_sys/registered_cache.cpp
diff --git a/src/core/crypto/aes_util.cpp b/src/core/crypto/aes_util.cpp
index 72e4bed67..89ade5000 100644
--- a/src/core/crypto/aes_util.cpp
+++ b/src/core/crypto/aes_util.cpp
@@ -82,11 +82,25 @@ void AESCipher<Key, KeySize>::Transcode(const u8* src, size_t size, u8* dest, Op
82 } 82 }
83 } else { 83 } else {
84 const auto block_size = mbedtls_cipher_get_block_size(context); 84 const auto block_size = mbedtls_cipher_get_block_size(context);
85 if (size < block_size) {
86 std::vector<u8> block(block_size);
87 std::memcpy(block.data(), src, size);
88 Transcode(block.data(), block.size(), block.data(), op);
89 std::memcpy(dest, block.data(), size);
90 return;
91 }
85 92
86 for (size_t offset = 0; offset < size; offset += block_size) { 93 for (size_t offset = 0; offset < size; offset += block_size) {
87 auto length = std::min<size_t>(block_size, size - offset); 94 auto length = std::min<size_t>(block_size, size - offset);
88 mbedtls_cipher_update(context, src + offset, length, dest + offset, &written); 95 mbedtls_cipher_update(context, src + offset, length, dest + offset, &written);
89 if (written != length) { 96 if (written != length) {
97 if (length < block_size) {
98 std::vector<u8> block(block_size);
99 std::memcpy(block.data(), src + offset, length);
100 Transcode(block.data(), block.size(), block.data(), op);
101 std::memcpy(dest + offset, block.data(), length);
102 return;
103 }
90 LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.", 104 LOG_WARNING(Crypto, "Not all data was decrypted requested={:016X}, actual={:016X}.",
91 length, written); 105 length, written);
92 } 106 }
diff --git a/src/core/crypto/ctr_encryption_layer.cpp b/src/core/crypto/ctr_encryption_layer.cpp
index 3ea60dbd0..296fad419 100644
--- a/src/core/crypto/ctr_encryption_layer.cpp
+++ b/src/core/crypto/ctr_encryption_layer.cpp
@@ -21,7 +21,7 @@ size_t CTREncryptionLayer::Read(u8* data, size_t length, size_t offset) const {
21 UpdateIV(base_offset + offset); 21 UpdateIV(base_offset + offset);
22 std::vector<u8> raw = base->ReadBytes(length, offset); 22 std::vector<u8> raw = base->ReadBytes(length, offset);
23 cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt); 23 cipher.Transcode(raw.data(), raw.size(), data, Op::Decrypt);
24 return raw.size(); 24 return length;
25 } 25 }
26 26
27 // offset does not fall on block boundary (0x10) 27 // offset does not fall on block boundary (0x10)
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()) {
diff --git a/src/core/hle/service/filesystem/filesystem.cpp b/src/core/hle/service/filesystem/filesystem.cpp
index a4426af96..04c9d750f 100644
--- a/src/core/hle/service/filesystem/filesystem.cpp
+++ b/src/core/hle/service/filesystem/filesystem.cpp
@@ -10,6 +10,7 @@
10#include "core/file_sys/bis_factory.h" 10#include "core/file_sys/bis_factory.h"
11#include "core/file_sys/errors.h" 11#include "core/file_sys/errors.h"
12#include "core/file_sys/mode.h" 12#include "core/file_sys/mode.h"
13#include "core/file_sys/registered_cache.h"
13#include "core/file_sys/romfs_factory.h" 14#include "core/file_sys/romfs_factory.h"
14#include "core/file_sys/savedata_factory.h" 15#include "core/file_sys/savedata_factory.h"
15#include "core/file_sys/sdmc_factory.h" 16#include "core/file_sys/sdmc_factory.h"
@@ -307,6 +308,12 @@ ResultVal<FileSys::VirtualDir> OpenSDMC() {
307 return sdmc_factory->Open(); 308 return sdmc_factory->Open();
308} 309}
309 310
311std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents() {
312 return std::make_shared<FileSys::RegisteredCacheUnion>(
313 std::vector<std::shared_ptr<FileSys::RegisteredCache>>{
314 GetSystemNANDContents(), GetUserNANDContents(), GetSDMCContents()});
315}
316
310std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { 317std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() {
311 LOG_TRACE(Service_FS, "Opening System NAND Contents"); 318 LOG_TRACE(Service_FS, "Opening System NAND Contents");
312 319
diff --git a/src/core/hle/service/filesystem/filesystem.h b/src/core/hle/service/filesystem/filesystem.h
index 9ba0e2eab..793a7b06f 100644
--- a/src/core/hle/service/filesystem/filesystem.h
+++ b/src/core/hle/service/filesystem/filesystem.h
@@ -13,6 +13,7 @@
13namespace FileSys { 13namespace FileSys {
14class BISFactory; 14class BISFactory;
15class RegisteredCache; 15class RegisteredCache;
16class RegisteredCacheUnion;
16class RomFSFactory; 17class RomFSFactory;
17class SaveDataFactory; 18class SaveDataFactory;
18class SDMCFactory; 19class SDMCFactory;
@@ -45,6 +46,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space,
45 FileSys::SaveDataDescriptor save_struct); 46 FileSys::SaveDataDescriptor save_struct);
46ResultVal<FileSys::VirtualDir> OpenSDMC(); 47ResultVal<FileSys::VirtualDir> OpenSDMC();
47 48
49std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents();
50
48std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); 51std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents();
49std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); 52std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents();
50std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); 53std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents();
diff --git a/src/core/loader/deconstructed_rom_directory.cpp b/src/core/loader/deconstructed_rom_directory.cpp
index 1ae4bb656..2b8f78136 100644
--- a/src/core/loader/deconstructed_rom_directory.cpp
+++ b/src/core/loader/deconstructed_rom_directory.cpp
@@ -9,6 +9,7 @@
9#include "core/core.h" 9#include "core/core.h"
10#include "core/file_sys/content_archive.h" 10#include "core/file_sys/content_archive.h"
11#include "core/file_sys/control_metadata.h" 11#include "core/file_sys/control_metadata.h"
12#include "core/file_sys/patch_manager.h"
12#include "core/file_sys/romfs_factory.h" 13#include "core/file_sys/romfs_factory.h"
13#include "core/gdbstub/gdbstub.h" 14#include "core/gdbstub/gdbstub.h"
14#include "core/hle/kernel/kernel.h" 15#include "core/hle/kernel/kernel.h"
@@ -21,10 +22,19 @@
21 22
22namespace Loader { 23namespace Loader {
23 24
24AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_) 25AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_,
25 : AppLoader(std::move(file_)) { 26 bool override_update)
27 : AppLoader(std::move(file_)), override_update(override_update) {
26 const auto dir = file->GetContainingDirectory(); 28 const auto dir = file->GetContainingDirectory();
27 29
30 // Title ID
31 const auto npdm = dir->GetFile("main.npdm");
32 if (npdm != nullptr) {
33 const auto res = metadata.Load(npdm);
34 if (res == ResultStatus::Success)
35 title_id = metadata.GetTitleID();
36 }
37
28 // Icon 38 // Icon
29 FileSys::VirtualFile icon_file = nullptr; 39 FileSys::VirtualFile icon_file = nullptr;
30 for (const auto& language : FileSys::LANGUAGE_NAMES) { 40 for (const auto& language : FileSys::LANGUAGE_NAMES) {
@@ -66,8 +76,9 @@ AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys
66} 76}
67 77
68AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( 78AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(
69 FileSys::VirtualDir directory) 79 FileSys::VirtualDir directory, bool override_update)
70 : AppLoader(directory->GetFile("main")), dir(std::move(directory)) {} 80 : AppLoader(directory->GetFile("main")), dir(std::move(directory)),
81 override_update(override_update) {}
71 82
72FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) { 83FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) {
73 if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) { 84 if (FileSys::IsDirectoryExeFS(file->GetContainingDirectory())) {
@@ -89,7 +100,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
89 dir = file->GetContainingDirectory(); 100 dir = file->GetContainingDirectory();
90 } 101 }
91 102
92 const FileSys::VirtualFile npdm = dir->GetFile("main.npdm"); 103 // Read meta to determine title ID
104 FileSys::VirtualFile npdm = dir->GetFile("main.npdm");
93 if (npdm == nullptr) 105 if (npdm == nullptr)
94 return ResultStatus::ErrorMissingNPDM; 106 return ResultStatus::ErrorMissingNPDM;
95 107
@@ -97,6 +109,21 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
97 if (result != ResultStatus::Success) { 109 if (result != ResultStatus::Success) {
98 return result; 110 return result;
99 } 111 }
112
113 if (override_update) {
114 const FileSys::PatchManager patch_manager(metadata.GetTitleID());
115 dir = patch_manager.PatchExeFS(dir);
116 }
117
118 // Reread in case PatchExeFS affected the main.npdm
119 npdm = dir->GetFile("main.npdm");
120 if (npdm == nullptr)
121 return ResultStatus::ErrorMissingNPDM;
122
123 ResultStatus result2 = metadata.Load(npdm);
124 if (result2 != ResultStatus::Success) {
125 return result2;
126 }
100 metadata.Print(); 127 metadata.Print();
101 128
102 const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()}; 129 const FileSys::ProgramAddressSpaceType arch_bits{metadata.GetAddressSpaceType()};
@@ -119,7 +146,6 @@ ResultStatus AppLoader_DeconstructedRomDirectory::Load(
119 } 146 }
120 147
121 auto& kernel = Core::System::GetInstance().Kernel(); 148 auto& kernel = Core::System::GetInstance().Kernel();
122 title_id = metadata.GetTitleID();
123 process->program_id = metadata.GetTitleID(); 149 process->program_id = metadata.GetTitleID();
124 process->svc_access_mask.set(); 150 process->svc_access_mask.set();
125 process->resource_limit = 151 process->resource_limit =
@@ -170,4 +196,8 @@ ResultStatus AppLoader_DeconstructedRomDirectory::ReadTitle(std::string& title)
170 return ResultStatus::Success; 196 return ResultStatus::Success;
171} 197}
172 198
199bool AppLoader_DeconstructedRomDirectory::IsRomFSUpdatable() const {
200 return false;
201}
202
173} // namespace Loader 203} // namespace Loader
diff --git a/src/core/loader/deconstructed_rom_directory.h b/src/core/loader/deconstructed_rom_directory.h
index b20804f75..8a0dc1b1e 100644
--- a/src/core/loader/deconstructed_rom_directory.h
+++ b/src/core/loader/deconstructed_rom_directory.h
@@ -20,10 +20,12 @@ namespace Loader {
20 */ 20 */
21class AppLoader_DeconstructedRomDirectory final : public AppLoader { 21class AppLoader_DeconstructedRomDirectory final : public AppLoader {
22public: 22public:
23 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file); 23 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile main_file,
24 bool override_update = false);
24 25
25 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm' 26 // Overload to accept exefs directory. Must contain 'main' and 'main.npdm'
26 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory); 27 explicit AppLoader_DeconstructedRomDirectory(FileSys::VirtualDir directory,
28 bool override_update = false);
27 29
28 /** 30 /**
29 * Returns the type of the file 31 * Returns the type of the file
@@ -42,6 +44,7 @@ public:
42 ResultStatus ReadIcon(std::vector<u8>& buffer) override; 44 ResultStatus ReadIcon(std::vector<u8>& buffer) override;
43 ResultStatus ReadProgramId(u64& out_program_id) override; 45 ResultStatus ReadProgramId(u64& out_program_id) override;
44 ResultStatus ReadTitle(std::string& title) override; 46 ResultStatus ReadTitle(std::string& title) override;
47 bool IsRomFSUpdatable() const override;
45 48
46private: 49private:
47 FileSys::ProgramMetadata metadata; 50 FileSys::ProgramMetadata metadata;
@@ -51,6 +54,7 @@ private:
51 std::vector<u8> icon_data; 54 std::vector<u8> icon_data;
52 std::string name; 55 std::string name;
53 u64 title_id{}; 56 u64 title_id{};
57 bool override_update;
54}; 58};
55 59
56} // namespace Loader 60} // namespace Loader
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp
index 446adf557..fa43a2650 100644
--- a/src/core/loader/loader.cpp
+++ b/src/core/loader/loader.cpp
@@ -93,7 +93,7 @@ std::string GetFileTypeString(FileType type) {
93 return "unknown"; 93 return "unknown";
94} 94}
95 95
96constexpr std::array<const char*, 50> RESULT_MESSAGES{ 96constexpr std::array<const char*, 58> RESULT_MESSAGES{
97 "The operation completed successfully.", 97 "The operation completed successfully.",
98 "The loader requested to load is already loaded.", 98 "The loader requested to load is already loaded.",
99 "The operation is not implemented.", 99 "The operation is not implemented.",
@@ -143,7 +143,16 @@ constexpr std::array<const char*, 50> RESULT_MESSAGES{
143 "The AES Key Generation Source could not be found.", 143 "The AES Key Generation Source could not be found.",
144 "The SD Save Key Source could not be found.", 144 "The SD Save Key Source could not be found.",
145 "The SD NCA Key Source could not be found.", 145 "The SD NCA Key Source could not be found.",
146 "The NSP file is missing a Program-type NCA."}; 146 "The NSP file is missing a Program-type NCA.",
147 "The BKTR-type NCA has a bad BKTR header.",
148 "The BKTR Subsection entry is not located immediately after the Relocation entry.",
149 "The BKTR Subsection entry is not at the end of the media block.",
150 "The BKTR-type NCA has a bad Relocation block.",
151 "The BKTR-type NCA has a bad Subsection block.",
152 "The BKTR-type NCA has a bad Relocation bucket.",
153 "The BKTR-type NCA has a bad Subsection bucket.",
154 "The BKTR-type NCA is missing the base RomFS.",
155};
147 156
148std::ostream& operator<<(std::ostream& os, ResultStatus status) { 157std::ostream& operator<<(std::ostream& os, ResultStatus status) {
149 os << RESULT_MESSAGES.at(static_cast<size_t>(status)); 158 os << RESULT_MESSAGES.at(static_cast<size_t>(status));
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h
index be66b2257..843c4bb91 100644
--- a/src/core/loader/loader.h
+++ b/src/core/loader/loader.h
@@ -107,6 +107,14 @@ enum class ResultStatus : u16 {
107 ErrorMissingSDSaveKeySource, 107 ErrorMissingSDSaveKeySource,
108 ErrorMissingSDNCAKeySource, 108 ErrorMissingSDNCAKeySource,
109 ErrorNSPMissingProgramNCA, 109 ErrorNSPMissingProgramNCA,
110 ErrorBadBKTRHeader,
111 ErrorBKTRSubsectionNotAfterRelocation,
112 ErrorBKTRSubsectionNotAtEnd,
113 ErrorBadRelocationBlock,
114 ErrorBadSubsectionBlock,
115 ErrorBadRelocationBuckets,
116 ErrorBadSubsectionBuckets,
117 ErrorMissingBKTRBaseRomFS,
110}; 118};
111 119
112std::ostream& operator<<(std::ostream& os, ResultStatus status); 120std::ostream& operator<<(std::ostream& os, ResultStatus status);
@@ -197,13 +205,22 @@ public:
197 } 205 }
198 206
199 /** 207 /**
200 * Get the update RomFS of the application 208 * Get whether or not updates can be applied to the RomFS.
201 * Since the RomFS can be huge, we return a file reference instead of copying to a buffer 209 * By default, this is true, however for formats where it cannot be guaranteed that the RomFS is
202 * @param file The file containing the RomFS 210 * the base game it should be set to false.
203 * @return ResultStatus result of function 211 * @return bool whether or not updatable.
204 */ 212 */
205 virtual ResultStatus ReadUpdateRomFS(FileSys::VirtualFile& file) { 213 virtual bool IsRomFSUpdatable() const {
206 return ResultStatus::ErrorNotImplemented; 214 return true;
215 }
216
217 /**
218 * Gets the difference between the start of the IVFC header and the start of level 6 (RomFS)
219 * data. Needed for bktr patching.
220 * @return IVFC offset for romfs.
221 */
222 virtual u64 ReadRomFSIVFCOffset() const {
223 return 0;
207 } 224 }
208 225
209 /** 226 /**
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp
index c036a8a1c..6aaffae59 100644
--- a/src/core/loader/nca.cpp
+++ b/src/core/loader/nca.cpp
@@ -48,7 +48,7 @@ ResultStatus AppLoader_NCA::Load(Kernel::SharedPtr<Kernel::Process>& process) {
48 if (exefs == nullptr) 48 if (exefs == nullptr)
49 return ResultStatus::ErrorNoExeFS; 49 return ResultStatus::ErrorNoExeFS;
50 50
51 directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs); 51 directory_loader = std::make_unique<AppLoader_DeconstructedRomDirectory>(exefs, true);
52 52
53 const auto load_result = directory_loader->Load(process); 53 const auto load_result = directory_loader->Load(process);
54 if (load_result != ResultStatus::Success) 54 if (load_result != ResultStatus::Success)
@@ -71,6 +71,12 @@ ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) {
71 return ResultStatus::Success; 71 return ResultStatus::Success;
72} 72}
73 73
74u64 AppLoader_NCA::ReadRomFSIVFCOffset() const {
75 if (nca == nullptr)
76 return 0;
77 return nca->GetBaseIVFCOffset();
78}
79
74ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { 80ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) {
75 if (nca == nullptr || nca->GetStatus() != ResultStatus::Success) 81 if (nca == nullptr || nca->GetStatus() != ResultStatus::Success)
76 return ResultStatus::ErrorNotInitialized; 82 return ResultStatus::ErrorNotInitialized;
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h
index 326f84857..10be197c4 100644
--- a/src/core/loader/nca.h
+++ b/src/core/loader/nca.h
@@ -37,6 +37,7 @@ public:
37 ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override; 37 ResultStatus Load(Kernel::SharedPtr<Kernel::Process>& process) override;
38 38
39 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 39 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
40 u64 ReadRomFSIVFCOffset() const override;
40 ResultStatus ReadProgramId(u64& out_program_id) override; 41 ResultStatus ReadProgramId(u64& out_program_id) override;
41 42
42private: 43private:
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 77026b850..bb89a9da3 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -232,4 +232,9 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) {
232 title = nacp->GetApplicationName(); 232 title = nacp->GetApplicationName();
233 return ResultStatus::Success; 233 return ResultStatus::Success;
234} 234}
235
236bool AppLoader_NRO::IsRomFSUpdatable() const {
237 return false;
238}
239
235} // namespace Loader 240} // namespace Loader
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index bb01c9e25..96d2de305 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -39,6 +39,7 @@ public:
39 ResultStatus ReadProgramId(u64& out_program_id) override; 39 ResultStatus ReadProgramId(u64& out_program_id) override;
40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 40 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
41 ResultStatus ReadTitle(std::string& title) override; 41 ResultStatus ReadTitle(std::string& title) override;
42 bool IsRomFSUpdatable() const override;
42 43
43private: 44private:
44 bool LoadNro(FileSys::VirtualFile file, VAddr load_base); 45 bool LoadNro(FileSys::VirtualFile file, VAddr load_base);
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp
index 7c06239f2..291a9876d 100644
--- a/src/core/loader/nsp.cpp
+++ b/src/core/loader/nsp.cpp
@@ -9,6 +9,8 @@
9#include "core/file_sys/content_archive.h" 9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h" 10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/nca_metadata.h" 11#include "core/file_sys/nca_metadata.h"
12#include "core/file_sys/patch_manager.h"
13#include "core/file_sys/registered_cache.h"
12#include "core/file_sys/romfs.h" 14#include "core/file_sys/romfs.h"
13#include "core/file_sys/submission_package.h" 15#include "core/file_sys/submission_package.h"
14#include "core/hle/kernel/process.h" 16#include "core/hle/kernel/process.h"
@@ -28,24 +30,12 @@ AppLoader_NSP::AppLoader_NSP(FileSys::VirtualFile file)
28 return; 30 return;
29 31
30 const auto control_nca = 32 const auto control_nca =
31 nsp->GetNCA(nsp->GetFirstTitleID(), FileSys::ContentRecordType::Control); 33 nsp->GetNCA(nsp->GetProgramTitleID(), FileSys::ContentRecordType::Control);
32 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) 34 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
33 return; 35 return;
34 36
35 const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS()); 37 std::tie(nacp_file, icon_file) =
36 if (romfs == nullptr) 38 FileSys::PatchManager(nsp->GetProgramTitleID()).ParseControlNCA(control_nca);
37 return;
38
39 for (const auto& language : FileSys::LANGUAGE_NAMES) {
40 icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
41 if (icon_file != nullptr)
42 break;
43 }
44
45 const auto nacp_raw = romfs->GetFile("control.nacp");
46 if (nacp_raw == nullptr)
47 return;
48 nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
49} 39}
50 40
51AppLoader_NSP::~AppLoader_NSP() = default; 41AppLoader_NSP::~AppLoader_NSP() = default;
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp
index 75b998faa..16509229f 100644
--- a/src/core/loader/xci.cpp
+++ b/src/core/loader/xci.cpp
@@ -8,7 +8,9 @@
8#include "core/file_sys/card_image.h" 8#include "core/file_sys/card_image.h"
9#include "core/file_sys/content_archive.h" 9#include "core/file_sys/content_archive.h"
10#include "core/file_sys/control_metadata.h" 10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/patch_manager.h"
11#include "core/file_sys/romfs.h" 12#include "core/file_sys/romfs.h"
13#include "core/file_sys/submission_package.h"
12#include "core/hle/kernel/process.h" 14#include "core/hle/kernel/process.h"
13#include "core/loader/nca.h" 15#include "core/loader/nca.h"
14#include "core/loader/xci.h" 16#include "core/loader/xci.h"
@@ -20,21 +22,13 @@ AppLoader_XCI::AppLoader_XCI(FileSys::VirtualFile file)
20 nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) { 22 nca_loader(std::make_unique<AppLoader_NCA>(xci->GetProgramNCAFile())) {
21 if (xci->GetStatus() != ResultStatus::Success) 23 if (xci->GetStatus() != ResultStatus::Success)
22 return; 24 return;
25
23 const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control); 26 const auto control_nca = xci->GetNCAByType(FileSys::NCAContentType::Control);
24 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success) 27 if (control_nca == nullptr || control_nca->GetStatus() != ResultStatus::Success)
25 return; 28 return;
26 const auto romfs = FileSys::ExtractRomFS(control_nca->GetRomFS()); 29
27 if (romfs == nullptr) 30 std::tie(nacp_file, icon_file) =
28 return; 31 FileSys::PatchManager(xci->GetProgramTitleID()).ParseControlNCA(control_nca);
29 for (const auto& language : FileSys::LANGUAGE_NAMES) {
30 icon_file = romfs->GetFile("icon_" + std::string(language) + ".dat");
31 if (icon_file != nullptr)
32 break;
33 }
34 const auto nacp_raw = romfs->GetFile("control.nacp");
35 if (nacp_raw == nullptr)
36 return;
37 nacp_file = std::make_shared<FileSys::NACP>(nacp_raw);
38} 32}
39 33
40AppLoader_XCI::~AppLoader_XCI() = default; 34AppLoader_XCI::~AppLoader_XCI() = default;
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 65571b948..3730e85b8 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -7,6 +7,8 @@
7#include "common/file_util.h" 7#include "common/file_util.h"
8 8
9#include "core/core.h" 9#include "core/core.h"
10#include "core/file_sys/control_metadata.h"
11#include "core/file_sys/patch_manager.h"
10#include "core/loader/loader.h" 12#include "core/loader/loader.h"
11#include "core/settings.h" 13#include "core/settings.h"
12#include "core/telemetry_session.h" 14#include "core/telemetry_session.h"
@@ -88,12 +90,28 @@ TelemetrySession::TelemetrySession() {
88 std::chrono::system_clock::now().time_since_epoch()) 90 std::chrono::system_clock::now().time_since_epoch())
89 .count()}; 91 .count()};
90 AddField(Telemetry::FieldType::Session, "Init_Time", init_time); 92 AddField(Telemetry::FieldType::Session, "Init_Time", init_time);
91 std::string program_name; 93
92 const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadTitle(program_name)}; 94 u64 program_id{};
95 const Loader::ResultStatus res{System::GetInstance().GetAppLoader().ReadProgramId(program_id)};
93 if (res == Loader::ResultStatus::Success) { 96 if (res == Loader::ResultStatus::Success) {
94 AddField(Telemetry::FieldType::Session, "ProgramName", program_name); 97 AddField(Telemetry::FieldType::Session, "ProgramId", program_id);
98
99 std::string name;
100 System::GetInstance().GetAppLoader().ReadTitle(name);
101
102 if (name.empty()) {
103 auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
104 if (nacp != nullptr)
105 name = nacp->GetApplicationName();
106 }
107
108 if (!name.empty())
109 AddField(Telemetry::FieldType::Session, "ProgramName", name);
95 } 110 }
96 111
112 AddField(Telemetry::FieldType::Session, "ProgramFormat",
113 static_cast<u8>(System::GetInstance().GetAppLoader().GetFileType()));
114
97 // Log application information 115 // Log application information
98 Telemetry::AppendBuildInfo(field_collection); 116 Telemetry::AppendBuildInfo(field_collection);
99 117
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 3e2a5976b..a3b841684 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -21,6 +21,7 @@
21#include "core/file_sys/content_archive.h" 21#include "core/file_sys/content_archive.h"
22#include "core/file_sys/control_metadata.h" 22#include "core/file_sys/control_metadata.h"
23#include "core/file_sys/nca_metadata.h" 23#include "core/file_sys/nca_metadata.h"
24#include "core/file_sys/patch_manager.h"
24#include "core/file_sys/registered_cache.h" 25#include "core/file_sys/registered_cache.h"
25#include "core/file_sys/romfs.h" 26#include "core/file_sys/romfs.h"
26#include "core/file_sys/vfs_real.h" 27#include "core/file_sys/vfs_real.h"
@@ -232,6 +233,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, GMainWindow* parent)
232 item_model->insertColumns(0, COLUMN_COUNT); 233 item_model->insertColumns(0, COLUMN_COUNT);
233 item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name"); 234 item_model->setHeaderData(COLUMN_NAME, Qt::Horizontal, "Name");
234 item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility"); 235 item_model->setHeaderData(COLUMN_COMPATIBILITY, Qt::Horizontal, "Compatibility");
236 item_model->setHeaderData(COLUMN_ADD_ONS, Qt::Horizontal, "Add-ons");
235 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type"); 237 item_model->setHeaderData(COLUMN_FILE_TYPE, Qt::Horizontal, "File type");
236 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size"); 238 item_model->setHeaderData(COLUMN_SIZE, Qt::Horizontal, "Size");
237 239
@@ -454,6 +456,25 @@ static QString FormatGameName(const std::string& physical_name) {
454 return physical_name_as_qstring; 456 return physical_name_as_qstring;
455} 457}
456 458
459static QString FormatPatchNameVersions(const FileSys::PatchManager& patch_manager,
460 bool updatable = true) {
461 QString out;
462 for (const auto& kv : patch_manager.GetPatchVersionNames()) {
463 if (!updatable && kv.first == FileSys::PatchType::Update)
464 continue;
465
466 if (kv.second.empty()) {
467 out.append(fmt::format("{}\n", FileSys::FormatPatchTypeName(kv.first)).c_str());
468 } else {
469 out.append(fmt::format("{} ({})\n", FileSys::FormatPatchTypeName(kv.first), kv.second)
470 .c_str());
471 }
472 }
473
474 out.chop(1);
475 return out;
476}
477
457void GameList::RefreshGameDirectory() { 478void GameList::RefreshGameDirectory() {
458 if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) { 479 if (!UISettings::values.gamedir.isEmpty() && current_worker != nullptr) {
459 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); 480 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
@@ -462,26 +483,14 @@ void GameList::RefreshGameDirectory() {
462 } 483 }
463} 484}
464 485
465static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, 486static void GetMetadataFromControlNCA(const FileSys::PatchManager& patch_manager,
487 const std::shared_ptr<FileSys::NCA>& nca,
466 std::vector<u8>& icon, std::string& name) { 488 std::vector<u8>& icon, std::string& name) {
467 const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); 489 auto [nacp, icon_file] = patch_manager.ParseControlNCA(nca);
468 if (control_dir == nullptr) 490 if (icon_file != nullptr)
469 return; 491 icon = icon_file->ReadAllBytes();
470 492 if (nacp != nullptr)
471 const auto nacp_file = control_dir->GetFile("control.nacp"); 493 name = nacp->GetApplicationName();
472 if (nacp_file == nullptr)
473 return;
474 FileSys::NACP nacp(nacp_file);
475 name = nacp.GetApplicationName();
476
477 FileSys::VirtualFile icon_file = nullptr;
478 for (const auto& language : FileSys::LANGUAGE_NAMES) {
479 icon_file = control_dir->GetFile("icon_" + std::string(language) + ".dat");
480 if (icon_file != nullptr) {
481 icon = icon_file->ReadAllBytes();
482 break;
483 }
484 }
485} 494}
486 495
487GameListWorker::GameListWorker( 496GameListWorker::GameListWorker(
@@ -492,7 +501,8 @@ GameListWorker::GameListWorker(
492 501
493GameListWorker::~GameListWorker() = default; 502GameListWorker::~GameListWorker() = default;
494 503
495void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) { 504void GameListWorker::AddInstalledTitlesToGameList() {
505 const auto cache = Service::FileSystem::GetUnionContents();
496 const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application, 506 const auto installed_games = cache->ListEntriesFilter(FileSys::TitleType::Application,
497 FileSys::ContentRecordType::Program); 507 FileSys::ContentRecordType::Program);
498 508
@@ -507,14 +517,25 @@ void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::Regis
507 u64 program_id = 0; 517 u64 program_id = 0;
508 loader->ReadProgramId(program_id); 518 loader->ReadProgramId(program_id);
509 519
520 const FileSys::PatchManager patch{program_id};
510 const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control); 521 const auto& control = cache->GetEntry(game.title_id, FileSys::ContentRecordType::Control);
511 if (control != nullptr) 522 if (control != nullptr)
512 GetMetadataFromControlNCA(control, icon, name); 523 GetMetadataFromControlNCA(patch, control, icon, name);
524
525 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
526
527 // The game list uses this as compatibility number for untested games
528 QString compatibility("99");
529 if (it != compatibility_list.end())
530 compatibility = it->second.first;
531
513 emit EntryReady({ 532 emit EntryReady({
514 new GameListItemPath( 533 new GameListItemPath(
515 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name), 534 FormatGameName(file->GetFullPath()), icon, QString::fromStdString(name),
516 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), 535 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
517 program_id), 536 program_id),
537 new GameListItemCompat(compatibility),
538 new GameListItem(FormatPatchNameVersions(patch)),
518 new GameListItem( 539 new GameListItem(
519 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 540 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
520 new GameListItemSize(file->GetSize()), 541 new GameListItemSize(file->GetSize()),
@@ -580,12 +601,14 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
580 std::string name = " "; 601 std::string name = " ";
581 const auto res3 = loader->ReadTitle(name); 602 const auto res3 = loader->ReadTitle(name);
582 603
604 const FileSys::PatchManager patch{program_id};
605
583 if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success && 606 if (res1 != Loader::ResultStatus::Success && res3 != Loader::ResultStatus::Success &&
584 res2 == Loader::ResultStatus::Success) { 607 res2 == Loader::ResultStatus::Success) {
585 // Use from metadata pool. 608 // Use from metadata pool.
586 if (nca_control_map.find(program_id) != nca_control_map.end()) { 609 if (nca_control_map.find(program_id) != nca_control_map.end()) {
587 const auto nca = nca_control_map[program_id]; 610 const auto nca = nca_control_map[program_id];
588 GetMetadataFromControlNCA(nca, icon, name); 611 GetMetadataFromControlNCA(patch, nca, icon, name);
589 } 612 }
590 } 613 }
591 614
@@ -602,6 +625,7 @@ void GameListWorker::AddFstEntriesToGameList(const std::string& dir_path, unsign
602 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())), 625 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType())),
603 program_id), 626 program_id),
604 new GameListItemCompat(compatibility), 627 new GameListItemCompat(compatibility),
628 new GameListItem(FormatPatchNameVersions(patch, loader->IsRomFSUpdatable())),
605 new GameListItem( 629 new GameListItem(
606 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))), 630 QString::fromStdString(Loader::GetFileTypeString(loader->GetFileType()))),
607 new GameListItemSize(FileUtil::GetSize(physical_name)), 631 new GameListItemSize(FileUtil::GetSize(physical_name)),
@@ -621,9 +645,7 @@ void GameListWorker::run() {
621 stop_processing = false; 645 stop_processing = false;
622 watch_list.append(dir_path); 646 watch_list.append(dir_path);
623 FillControlMap(dir_path.toStdString()); 647 FillControlMap(dir_path.toStdString());
624 AddInstalledTitlesToGameList(Service::FileSystem::GetUserNANDContents()); 648 AddInstalledTitlesToGameList();
625 AddInstalledTitlesToGameList(Service::FileSystem::GetSystemNANDContents());
626 AddInstalledTitlesToGameList(Service::FileSystem::GetSDMCContents());
627 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0); 649 AddFstEntriesToGameList(dir_path.toStdString(), deep_scan ? 256 : 0);
628 nca_control_map.clear(); 650 nca_control_map.clear();
629 emit Finished(watch_list); 651 emit Finished(watch_list);
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index 84731464a..3fcb298ed 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -38,6 +38,7 @@ public:
38 enum { 38 enum {
39 COLUMN_NAME, 39 COLUMN_NAME,
40 COLUMN_COMPATIBILITY, 40 COLUMN_COMPATIBILITY,
41 COLUMN_ADD_ONS,
41 COLUMN_FILE_TYPE, 42 COLUMN_FILE_TYPE,
42 COLUMN_SIZE, 43 COLUMN_SIZE,
43 COLUMN_COUNT, // Number of columns 44 COLUMN_COUNT, // Number of columns
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index 4ddd8cd88..a70a151c5 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -239,7 +239,7 @@ private:
239 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list; 239 const std::unordered_map<std::string, std::pair<QString, QString>>& compatibility_list;
240 std::atomic_bool stop_processing; 240 std::atomic_bool stop_processing;
241 241
242 void AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache); 242 void AddInstalledTitlesToGameList();
243 void FillControlMap(const std::string& dir_path); 243 void FillControlMap(const std::string& dir_path);
244 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0); 244 void AddFstEntriesToGameList(const std::string& dir_path, unsigned int recursion = 0);
245}; 245};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 56bd3ee2e..dbe5bd8a4 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -32,6 +32,8 @@
32#include "core/crypto/key_manager.h" 32#include "core/crypto/key_manager.h"
33#include "core/file_sys/card_image.h" 33#include "core/file_sys/card_image.h"
34#include "core/file_sys/content_archive.h" 34#include "core/file_sys/content_archive.h"
35#include "core/file_sys/control_metadata.h"
36#include "core/file_sys/patch_manager.h"
35#include "core/file_sys/registered_cache.h" 37#include "core/file_sys/registered_cache.h"
36#include "core/file_sys/savedata_factory.h" 38#include "core/file_sys/savedata_factory.h"
37#include "core/file_sys/submission_package.h" 39#include "core/file_sys/submission_package.h"
@@ -592,8 +594,16 @@ void GMainWindow::BootGame(const QString& filename) {
592 594
593 std::string title_name; 595 std::string title_name;
594 const auto res = Core::System::GetInstance().GetGameName(title_name); 596 const auto res = Core::System::GetInstance().GetGameName(title_name);
595 if (res != Loader::ResultStatus::Success) 597 if (res != Loader::ResultStatus::Success) {
596 title_name = FileUtil::GetFilename(filename.toStdString()); 598 const u64 program_id = Core::System::GetInstance().CurrentProcess()->program_id;
599
600 const auto [nacp, icon_file] = FileSys::PatchManager(program_id).GetControlMetadata();
601 if (nacp != nullptr)
602 title_name = nacp->GetApplicationName();
603
604 if (title_name.empty())
605 title_name = FileUtil::GetFilename(filename.toStdString());
606 }
597 607
598 setWindowTitle(QString("yuzu %1| %4 | %2-%3") 608 setWindowTitle(QString("yuzu %1| %4 | %2-%3")
599 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc, 609 .arg(Common::g_build_name, Common::g_scm_branch, Common::g_scm_desc,
@@ -868,7 +878,11 @@ void GMainWindow::OnMenuInstallToNAND() {
868 } else { 878 } else {
869 const auto nca = std::make_shared<FileSys::NCA>( 879 const auto nca = std::make_shared<FileSys::NCA>(
870 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); 880 vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read));
871 if (nca->GetStatus() != Loader::ResultStatus::Success) { 881 const auto id = nca->GetStatus();
882
883 // Game updates necessary are missing base RomFS
884 if (id != Loader::ResultStatus::Success &&
885 id != Loader::ResultStatus::ErrorMissingBKTRBaseRomFS) {
872 failed(); 886 failed();
873 return; 887 return;
874 } 888 }