diff options
| author | 2018-09-05 18:06:11 -0400 | |
|---|---|---|
| committer | 2018-09-05 18:06:11 -0400 | |
| commit | a6ae7654105fe6ec46ff0bcabb714b8447b83899 (patch) | |
| tree | 0ff4d2396cb0730ec5952181e4e67947b64832ec /src | |
| parent | Merge pull request #1245 from degasus/optimizations (diff) | |
| parent | bktr: Fix bucket overlap error (diff) | |
| download | yuzu-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')
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 | }; |
| 69 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); | 70 | static_assert(sizeof(RomFSSuperblock) == 0x200, "RomFSSuperblock has incorrect size."); |
| 70 | 71 | ||
| 72 | struct 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 | }; | ||
| 80 | static_assert(sizeof(BKTRHeader) == 0x20, "BKTRHeader has incorrect size."); | ||
| 81 | |||
| 82 | struct 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 | }; | ||
| 90 | static_assert(sizeof(BKTRSuperblock) == 0x200, "BKTRSuperblock has incorrect size."); | ||
| 91 | |||
| 71 | union NCASectionHeader { | 92 | union NCASectionHeader { |
| 72 | NCASectionRaw raw; | 93 | NCASectionRaw raw; |
| 73 | PFS0Superblock pfs0; | 94 | PFS0Superblock pfs0; |
| 74 | RomFSSuperblock romfs; | 95 | RomFSSuperblock romfs; |
| 96 | BKTRSuperblock bktr; | ||
| 75 | }; | 97 | }; |
| 76 | static_assert(sizeof(NCASectionHeader) == 0x200, "NCASectionHeader has incorrect size."); | 98 | static_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 | ||
| 193 | NCA::NCA(VirtualFile file_) : file(std::move(file_)) { | 218 | NCA::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 | ||
| 351 | u64 NCA::GetTitleId() const { | 494 | u64 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 | ||
| 500 | bool NCA::IsUpdate() const { | ||
| 501 | return is_update; | ||
| 502 | } | ||
| 503 | |||
| 357 | VirtualFile NCA::GetRomFS() const { | 504 | VirtualFile 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 | ||
| 369 | bool NCA::IsUpdate() const { | 516 | u64 NCA::GetBaseIVFCOffset() const { |
| 370 | return is_update; | 517 | return ivfc_offset; |
| 371 | } | 518 | } |
| 372 | 519 | ||
| 373 | bool NCA::ReplaceFileWithSubdirectory(VirtualFile file, VirtualDir dir) { | 520 | bool 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. |
| 80 | class NCA : public ReadOnlyVfsDirectory { | 80 | class NCA : public ReadOnlyVfsDirectory { |
| 81 | public: | 81 | public: |
| 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 | ||
| 100 | protected: | 103 | protected: |
| 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 | |||
| 9 | namespace FileSys { | ||
| 10 | |||
| 11 | BKTR::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 | |||
| 34 | BKTR::~BKTR() = default; | ||
| 35 | |||
| 36 | size_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 | |||
| 108 | template <bool Subsection, typename BlockType, typename BucketType> | ||
| 109 | std::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 | |||
| 148 | RelocationEntry 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 | |||
| 153 | RelocationEntry 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 | |||
| 161 | SubsectionEntry 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 | |||
| 166 | SubsectionEntry 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 | |||
| 174 | std::string BKTR::GetName() const { | ||
| 175 | return base_romfs->GetName(); | ||
| 176 | } | ||
| 177 | |||
| 178 | size_t BKTR::GetSize() const { | ||
| 179 | return relocation.size; | ||
| 180 | } | ||
| 181 | |||
| 182 | bool BKTR::Resize(size_t new_size) { | ||
| 183 | return false; | ||
| 184 | } | ||
| 185 | |||
| 186 | std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const { | ||
| 187 | return base_romfs->GetContainingDirectory(); | ||
| 188 | } | ||
| 189 | |||
| 190 | bool BKTR::IsWritable() const { | ||
| 191 | return false; | ||
| 192 | } | ||
| 193 | |||
| 194 | bool BKTR::IsReadable() const { | ||
| 195 | return true; | ||
| 196 | } | ||
| 197 | |||
| 198 | size_t BKTR::Write(const u8* data, size_t length, size_t offset) { | ||
| 199 | return 0; | ||
| 200 | } | ||
| 201 | |||
| 202 | bool 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 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | #pragma pack(push, 1) | ||
| 16 | struct RelocationEntry { | ||
| 17 | u64_le address_patch; | ||
| 18 | u64_le address_source; | ||
| 19 | u32 from_patch; | ||
| 20 | }; | ||
| 21 | #pragma pack(pop) | ||
| 22 | static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); | ||
| 23 | |||
| 24 | struct 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 | }; | ||
| 31 | static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); | ||
| 32 | |||
| 33 | // Vector version of RelocationBucketRaw | ||
| 34 | struct RelocationBucket { | ||
| 35 | u32 number_entries; | ||
| 36 | u64 end_offset; | ||
| 37 | std::vector<RelocationEntry> entries; | ||
| 38 | }; | ||
| 39 | |||
| 40 | struct RelocationBlock { | ||
| 41 | INSERT_PADDING_BYTES(4); | ||
| 42 | u32_le number_buckets; | ||
| 43 | u64_le size; | ||
| 44 | std::array<u64, 0x7FE> base_offsets; | ||
| 45 | }; | ||
| 46 | static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); | ||
| 47 | |||
| 48 | struct SubsectionEntry { | ||
| 49 | u64_le address_patch; | ||
| 50 | INSERT_PADDING_BYTES(0x4); | ||
| 51 | u32_le ctr; | ||
| 52 | }; | ||
| 53 | static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); | ||
| 54 | |||
| 55 | struct SubsectionBucketRaw { | ||
| 56 | INSERT_PADDING_BYTES(4); | ||
| 57 | u32_le number_entries; | ||
| 58 | u64_le end_offset; | ||
| 59 | std::array<SubsectionEntry, 0x3FF> subsection_entries; | ||
| 60 | }; | ||
| 61 | static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); | ||
| 62 | |||
| 63 | // Vector version of SubsectionBucketRaw | ||
| 64 | struct SubsectionBucket { | ||
| 65 | u32 number_entries; | ||
| 66 | u64 end_offset; | ||
| 67 | std::vector<SubsectionEntry> entries; | ||
| 68 | }; | ||
| 69 | |||
| 70 | struct SubsectionBlock { | ||
| 71 | INSERT_PADDING_BYTES(4); | ||
| 72 | u32_le number_buckets; | ||
| 73 | u64_le size; | ||
| 74 | std::array<u64, 0x7FE> base_offsets; | ||
| 75 | }; | ||
| 76 | static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); | ||
| 77 | |||
| 78 | inline 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 | |||
| 84 | inline 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 | |||
| 90 | class BKTR : public VfsFile { | ||
| 91 | public: | ||
| 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 | |||
| 116 | private: | ||
| 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 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | constexpr u64 SINGLE_BYTE_MODULUS = 0x100; | ||
| 16 | |||
| 17 | std::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 | |||
| 30 | constexpr std::array<const char*, 1> PATCH_TYPE_NAMES{ | ||
| 31 | "Update", | ||
| 32 | }; | ||
| 33 | |||
| 34 | std::string FormatPatchTypeName(PatchType type) { | ||
| 35 | return PATCH_TYPE_NAMES.at(static_cast<size_t>(type)); | ||
| 36 | } | ||
| 37 | |||
| 38 | PatchManager::PatchManager(u64 title_id) : title_id(title_id) {} | ||
| 39 | |||
| 40 | VirtualDir 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 | |||
| 63 | VirtualFile 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 | |||
| 89 | std::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 | |||
| 114 | std::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 | |||
| 124 | std::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 | |||
| 13 | namespace FileSys { | ||
| 14 | |||
| 15 | class NCA; | ||
| 16 | class NACP; | ||
| 17 | |||
| 18 | enum class TitleVersionFormat : u8 { | ||
| 19 | ThreeElements, ///< vX.Y.Z | ||
| 20 | FourElements, ///< vX.Y.Z.W | ||
| 21 | }; | ||
| 22 | |||
| 23 | std::string FormatTitleVersion(u32 version, | ||
| 24 | TitleVersionFormat format = TitleVersionFormat::ThreeElements); | ||
| 25 | |||
| 26 | enum class PatchType { | ||
| 27 | Update, | ||
| 28 | }; | ||
| 29 | |||
| 30 | std::string FormatPatchTypeName(PatchType type); | ||
| 31 | |||
| 32 | // A centralized class to manage patches to games. | ||
| 33 | class PatchManager { | ||
| 34 | public: | ||
| 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 | |||
| 58 | private: | ||
| 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 | ||
| 283 | boost::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 | |||
| 283 | VirtualFile RegisteredCache::GetEntryRaw(u64 title_id, ContentRecordType type) const { | 295 | VirtualFile 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 | |||
| 514 | RegisteredCacheUnion::RegisteredCacheUnion(std::vector<std::shared_ptr<RegisteredCache>> caches) | ||
| 515 | : caches(std::move(caches)) {} | ||
| 516 | |||
| 517 | void RegisteredCacheUnion::Refresh() { | ||
| 518 | for (const auto& c : caches) | ||
| 519 | c->Refresh(); | ||
| 520 | } | ||
| 521 | |||
| 522 | bool 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 | |||
| 528 | bool RegisteredCacheUnion::HasEntry(RegisteredCacheEntry entry) const { | ||
| 529 | return HasEntry(entry.title_id, entry.type); | ||
| 530 | } | ||
| 531 | |||
| 532 | boost::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 | |||
| 542 | VirtualFile 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 | |||
| 552 | VirtualFile RegisteredCacheUnion::GetEntryUnparsed(RegisteredCacheEntry entry) const { | ||
| 553 | return GetEntryUnparsed(entry.title_id, entry.type); | ||
| 554 | } | ||
| 555 | |||
| 556 | VirtualFile 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 | |||
| 566 | VirtualFile RegisteredCacheUnion::GetEntryRaw(RegisteredCacheEntry entry) const { | ||
| 567 | return GetEntryRaw(entry.title_id, entry.type); | ||
| 568 | } | ||
| 569 | |||
| 570 | std::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 | |||
| 577 | std::shared_ptr<NCA> RegisteredCacheUnion::GetEntry(RegisteredCacheEntry entry) const { | ||
| 578 | return GetEntry(entry.title_id, entry.type); | ||
| 579 | } | ||
| 580 | |||
| 581 | std::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 | |||
| 594 | std::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 | ||
| 46 | constexpr 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. |
| 47 | bool operator<(const RegisteredCacheEntry& lhs, const RegisteredCacheEntry& rhs); | 51 | bool 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 | */ |
| 62 | class RegisteredCache { | 66 | class RegisteredCache { |
| 67 | friend class RegisteredCacheUnion; | ||
| 68 | |||
| 63 | public: | 69 | public: |
| 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. | ||
| 143 | class RegisteredCacheUnion { | ||
| 144 | public: | ||
| 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 | |||
| 170 | private: | ||
| 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 | ||
| 24 | ResultVal<VirtualFile> RomFSFactory::OpenCurrentProcess() { | 31 | ResultVal<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 | ||
| 28 | ResultVal<VirtualFile> RomFSFactory::Open(u64 title_id, StorageId storage, ContentRecordType type) { | 39 | ResultVal<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 | ||
| 37 | private: | 37 | private: |
| 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 | ||
| 311 | std::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 | |||
| 310 | std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents() { | 317 | std::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 @@ | |||
| 13 | namespace FileSys { | 13 | namespace FileSys { |
| 14 | class BISFactory; | 14 | class BISFactory; |
| 15 | class RegisteredCache; | 15 | class RegisteredCache; |
| 16 | class RegisteredCacheUnion; | ||
| 16 | class RomFSFactory; | 17 | class RomFSFactory; |
| 17 | class SaveDataFactory; | 18 | class SaveDataFactory; |
| 18 | class SDMCFactory; | 19 | class SDMCFactory; |
| @@ -45,6 +46,8 @@ ResultVal<FileSys::VirtualDir> OpenSaveData(FileSys::SaveDataSpaceId space, | |||
| 45 | FileSys::SaveDataDescriptor save_struct); | 46 | FileSys::SaveDataDescriptor save_struct); |
| 46 | ResultVal<FileSys::VirtualDir> OpenSDMC(); | 47 | ResultVal<FileSys::VirtualDir> OpenSDMC(); |
| 47 | 48 | ||
| 49 | std::shared_ptr<FileSys::RegisteredCacheUnion> GetUnionContents(); | ||
| 50 | |||
| 48 | std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); | 51 | std::shared_ptr<FileSys::RegisteredCache> GetSystemNANDContents(); |
| 49 | std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); | 52 | std::shared_ptr<FileSys::RegisteredCache> GetUserNANDContents(); |
| 50 | std::shared_ptr<FileSys::RegisteredCache> GetSDMCContents(); | 53 | std::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 | ||
| 22 | namespace Loader { | 23 | namespace Loader { |
| 23 | 24 | ||
| 24 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory(FileSys::VirtualFile file_) | 25 | AppLoader_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 | ||
| 68 | AppLoader_DeconstructedRomDirectory::AppLoader_DeconstructedRomDirectory( | 78 | AppLoader_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 | ||
| 72 | FileType AppLoader_DeconstructedRomDirectory::IdentifyType(const FileSys::VirtualFile& file) { | 83 | FileType 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 | ||
| 199 | bool 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 | */ |
| 21 | class AppLoader_DeconstructedRomDirectory final : public AppLoader { | 21 | class AppLoader_DeconstructedRomDirectory final : public AppLoader { |
| 22 | public: | 22 | public: |
| 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 | ||
| 46 | private: | 49 | private: |
| 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 | ||
| 96 | constexpr std::array<const char*, 50> RESULT_MESSAGES{ | 96 | constexpr 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 | ||
| 148 | std::ostream& operator<<(std::ostream& os, ResultStatus status) { | 157 | std::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 | ||
| 112 | std::ostream& operator<<(std::ostream& os, ResultStatus status); | 120 | std::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 | ||
| 74 | u64 AppLoader_NCA::ReadRomFSIVFCOffset() const { | ||
| 75 | if (nca == nullptr) | ||
| 76 | return 0; | ||
| 77 | return nca->GetBaseIVFCOffset(); | ||
| 78 | } | ||
| 79 | |||
| 74 | ResultStatus AppLoader_NCA::ReadProgramId(u64& out_program_id) { | 80 | ResultStatus 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 | ||
| 42 | private: | 43 | private: |
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 | |||
| 236 | bool 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 | ||
| 43 | private: | 44 | private: |
| 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 | ||
| 51 | AppLoader_NSP::~AppLoader_NSP() = default; | 41 | AppLoader_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 | ||
| 40 | AppLoader_XCI::~AppLoader_XCI() = default; | 34 | AppLoader_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 | ||
| 459 | static 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 | |||
| 457 | void GameList::RefreshGameDirectory() { | 478 | void 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 | ||
| 465 | static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, | 486 | static 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 | ||
| 487 | GameListWorker::GameListWorker( | 496 | GameListWorker::GameListWorker( |
| @@ -492,7 +501,8 @@ GameListWorker::GameListWorker( | |||
| 492 | 501 | ||
| 493 | GameListWorker::~GameListWorker() = default; | 502 | GameListWorker::~GameListWorker() = default; |
| 494 | 503 | ||
| 495 | void GameListWorker::AddInstalledTitlesToGameList(std::shared_ptr<FileSys::RegisteredCache> cache) { | 504 | void 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 | } |