file_sys: Add BKTR patching mechanism
This commit is contained in:
		
							
								
								
									
										208
									
								
								src/core/file_sys/nca_patch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								src/core/file_sys/nca_patch.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,208 @@ | ||||
| // Copyright 2018 yuzu emulator team | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "core/crypto/aes_util.h" | ||||
| #include "core/file_sys/nca_patch.h" | ||||
|  | ||||
| namespace FileSys { | ||||
|  | ||||
| BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_, | ||||
|            std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_, | ||||
|            std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_, | ||||
|            Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_, | ||||
|            std::array<u8, 8> section_ctr_) | ||||
|     : base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)), | ||||
|       relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)), | ||||
|       subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)), | ||||
|       encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_), | ||||
|       section_ctr(std::move(section_ctr_)) { | ||||
|     for (size_t i = 0; i < relocation.number_buckets - 1; ++i) { | ||||
|         relocation_buckets[i].entries.push_back({relocation.base_offsets[i + 1], 0, 0}); | ||||
|     } | ||||
|  | ||||
|     for (size_t i = 0; i < subsection.number_buckets - 1; ++i) { | ||||
|         subsection_buckets[i].entries.push_back({subsection_buckets[i + 1].entries[0].address_patch, | ||||
|                                                  {0}, | ||||
|                                                  subsection_buckets[i + 1].entries[0].ctr}); | ||||
|     } | ||||
|  | ||||
|     relocation_buckets.back().entries.push_back({relocation.size, 0, 0}); | ||||
| } | ||||
|  | ||||
| size_t BKTR::Read(u8* data, size_t length, size_t offset) const { | ||||
|     // Read out of bounds. | ||||
|     if (offset >= relocation.size) | ||||
|         return 0; | ||||
|     const auto relocation = GetRelocationEntry(offset); | ||||
|     const auto section_offset = offset - relocation.address_patch + relocation.address_source; | ||||
|     const auto bktr_read = relocation.from_patch; | ||||
|  | ||||
|     const auto next_relocation = GetNextRelocationEntry(offset); | ||||
|  | ||||
|     if (offset + length <= next_relocation.address_patch) { | ||||
|         if (bktr_read) { | ||||
|             if (!encrypted) { | ||||
|                 return bktr_romfs->Read(data, length, section_offset); | ||||
|             } | ||||
|  | ||||
|             const auto subsection = GetSubsectionEntry(section_offset); | ||||
|             Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR); | ||||
|  | ||||
|             // Calculate AES IV | ||||
|             std::vector<u8> iv(16); | ||||
|             auto subsection_ctr = subsection.ctr; | ||||
|             auto offset_iv = section_offset + base_offset; | ||||
|             for (u8 i = 0; i < 8; ++i) | ||||
|                 iv[i] = section_ctr[0x8 - i - 1]; | ||||
|             offset_iv >>= 4; | ||||
|             for (size_t i = 0; i < 8; ++i) { | ||||
|                 iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF); | ||||
|                 offset_iv >>= 8; | ||||
|             } | ||||
|             for (size_t i = 0; i < 4; ++i) { | ||||
|                 iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF); | ||||
|                 subsection_ctr >>= 8; | ||||
|             } | ||||
|             cipher.SetIV(iv); | ||||
|  | ||||
|             const auto next_subsection = GetNextSubsectionEntry(section_offset); | ||||
|  | ||||
|             if (section_offset + length <= next_subsection.address_patch) { | ||||
|                 const auto block_offset = section_offset & 0xF; | ||||
|                 if (block_offset != 0) { | ||||
|                     auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF); | ||||
|                     cipher.Transcode(block.data(), block.size(), block.data(), | ||||
|                                      Core::Crypto::Op::Decrypt); | ||||
|                     if (length + block_offset < 0x10) { | ||||
|                         std::memcpy(data, block.data() + block_offset, | ||||
|                                     std::min(length, block.size())); | ||||
|                         return std::min(length, block.size()); | ||||
|                     } | ||||
|  | ||||
|                     const auto read = 0x10 - block_offset; | ||||
|                     std::memcpy(data, block.data() + block_offset, read); | ||||
|                     return read + Read(data + read, length - read, offset + read); | ||||
|                 } | ||||
|  | ||||
|                 const auto raw_read = bktr_romfs->Read(data, length, section_offset); | ||||
|                 cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt); | ||||
|                 return raw_read; | ||||
|             } else { | ||||
|                 const u64 partition = next_subsection.address_patch - section_offset; | ||||
|                 return Read(data, partition, offset) + | ||||
|                        Read(data + partition, length - partition, offset + partition); | ||||
|             } | ||||
|         } else { | ||||
|             ASSERT(section_offset > ivfc_offset, "Offset calculation negative."); | ||||
|             return base_romfs->Read(data, length, section_offset); | ||||
|         } | ||||
|     } else { | ||||
|         const u64 partition = next_relocation.address_patch - offset; | ||||
|         return Read(data, partition, offset) + | ||||
|                Read(data + partition, length - partition, offset + partition); | ||||
|     } | ||||
| } | ||||
|  | ||||
| template <bool Subsection, typename BlockType, typename BucketType> | ||||
| std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block, | ||||
|                                                   BucketType buckets) const { | ||||
|     if constexpr (Subsection) { | ||||
|         const auto last_bucket = buckets[block.number_buckets - 1]; | ||||
|         if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch) | ||||
|             return {block.number_buckets - 1, last_bucket.number_entries}; | ||||
|     } else { | ||||
|         ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block."); | ||||
|     } | ||||
|  | ||||
|     size_t bucket_id = 0; | ||||
|     for (size_t i = 1; i < block.number_buckets; ++i) { | ||||
|         if (block.base_offsets[i] <= offset) | ||||
|             ++bucket_id; | ||||
|     } | ||||
|  | ||||
|     const auto bucket = buckets[bucket_id]; | ||||
|  | ||||
|     if (bucket.number_entries == 1) | ||||
|         return {bucket_id, 0}; | ||||
|  | ||||
|     size_t low = 0; | ||||
|     size_t mid = 0; | ||||
|     size_t high = bucket.number_entries - 1; | ||||
|     while (low <= high) { | ||||
|         mid = (low + high) / 2; | ||||
|         if (bucket.entries[mid].address_patch > offset) { | ||||
|             high = mid - 1; | ||||
|         } else { | ||||
|             if (mid == bucket.number_entries - 1 || | ||||
|                 bucket.entries[mid + 1].address_patch > offset) { | ||||
|                 return {bucket_id, mid}; | ||||
|             } | ||||
|  | ||||
|             low = mid + 1; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     UNREACHABLE_MSG("Offset could not be found in BKTR block."); | ||||
| } | ||||
|  | ||||
| RelocationEntry BKTR::GetRelocationEntry(u64 offset) const { | ||||
|     const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||||
|     return relocation_buckets[res.first].entries[res.second]; | ||||
| } | ||||
|  | ||||
| RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const { | ||||
|     const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets); | ||||
|     const auto bucket = relocation_buckets[res.first]; | ||||
|     if (res.second + 1 < bucket.entries.size()) | ||||
|         return bucket.entries[res.second + 1]; | ||||
|     return relocation_buckets[res.first + 1].entries[0]; | ||||
| } | ||||
|  | ||||
| SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const { | ||||
|     const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||||
|     return subsection_buckets[res.first].entries[res.second]; | ||||
| } | ||||
|  | ||||
| SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const { | ||||
|     const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets); | ||||
|     const auto bucket = subsection_buckets[res.first]; | ||||
|     if (res.second + 1 < bucket.entries.size()) | ||||
|         return bucket.entries[res.second + 1]; | ||||
|     return subsection_buckets[res.first + 1].entries[0]; | ||||
| } | ||||
|  | ||||
| std::string BKTR::GetName() const { | ||||
|     return base_romfs->GetName(); | ||||
| } | ||||
|  | ||||
| size_t BKTR::GetSize() const { | ||||
|     return relocation.size; | ||||
| } | ||||
|  | ||||
| bool BKTR::Resize(size_t new_size) { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const { | ||||
|     return base_romfs->GetContainingDirectory(); | ||||
| } | ||||
|  | ||||
| bool BKTR::IsWritable() const { | ||||
|     return false; | ||||
| } | ||||
|  | ||||
| bool BKTR::IsReadable() const { | ||||
|     return true; | ||||
| } | ||||
|  | ||||
| size_t BKTR::Write(const u8* data, size_t length, size_t offset) { | ||||
|     return 0; | ||||
| } | ||||
|  | ||||
| bool BKTR::Rename(std::string_view name) { | ||||
|     return base_romfs->Rename(name); | ||||
| } | ||||
|  | ||||
| } // namespace FileSys | ||||
							
								
								
									
										144
									
								
								src/core/file_sys/nca_patch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										144
									
								
								src/core/file_sys/nca_patch.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,144 @@ | ||||
| // Copyright 2018 yuzu emulator team | ||||
| // Licensed under GPLv2 or any later version | ||||
| // Refer to the license.txt file included. | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include "core/crypto/key_manager.h" | ||||
| #include "core/file_sys/romfs.h" | ||||
| #include "core/loader/loader.h" | ||||
|  | ||||
| namespace FileSys { | ||||
|  | ||||
| #pragma pack(push, 1) | ||||
| struct RelocationEntry { | ||||
|     u64_le address_patch; | ||||
|     u64_le address_source; | ||||
|     u32 from_patch; | ||||
| }; | ||||
| #pragma pack(pop) | ||||
| static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size."); | ||||
|  | ||||
| struct RelocationBucketRaw { | ||||
|     INSERT_PADDING_BYTES(4); | ||||
|     u32_le number_entries; | ||||
|     u64_le end_offset; | ||||
|     std::array<RelocationEntry, 0x332> relocation_entries; | ||||
|     INSERT_PADDING_BYTES(8); | ||||
| }; | ||||
| static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size."); | ||||
|  | ||||
| // Vector version of RelocationBucketRaw | ||||
| struct RelocationBucket { | ||||
|     u32 number_entries; | ||||
|     u64 end_offset; | ||||
|     std::vector<RelocationEntry> entries; | ||||
| }; | ||||
|  | ||||
| struct RelocationBlock { | ||||
|     INSERT_PADDING_BYTES(4); | ||||
|     u32_le number_buckets; | ||||
|     u64_le size; | ||||
|     std::array<u64, 0x7FE> base_offsets; | ||||
| }; | ||||
| static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size."); | ||||
|  | ||||
| struct SubsectionEntry { | ||||
|     u64_le address_patch; | ||||
|     INSERT_PADDING_BYTES(0x4); | ||||
|     u32_le ctr; | ||||
| }; | ||||
| static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size."); | ||||
|  | ||||
| struct SubsectionBucketRaw { | ||||
|     INSERT_PADDING_BYTES(4); | ||||
|     u32_le number_entries; | ||||
|     u64_le end_offset; | ||||
|     std::array<SubsectionEntry, 0x3FF> subsection_entries; | ||||
| }; | ||||
| static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size."); | ||||
|  | ||||
| // Vector version of SubsectionBucketRaw | ||||
| struct SubsectionBucket { | ||||
|     u32 number_entries; | ||||
|     u64 end_offset; | ||||
|     std::vector<SubsectionEntry> entries; | ||||
| }; | ||||
|  | ||||
| struct SubsectionBlock { | ||||
|     INSERT_PADDING_BYTES(4); | ||||
|     u32_le number_buckets; | ||||
|     u64_le size; | ||||
|     std::array<u64, 0x7FE> base_offsets; | ||||
| }; | ||||
| static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size."); | ||||
|  | ||||
| inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) { | ||||
|     return {raw.number_entries, | ||||
|             raw.end_offset, | ||||
|             {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}}; | ||||
| } | ||||
|  | ||||
| inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) { | ||||
|     return {raw.number_entries, | ||||
|             raw.end_offset, | ||||
|             {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}}; | ||||
| } | ||||
|  | ||||
| class BKTR : public VfsFile { | ||||
| public: | ||||
|     BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation, | ||||
|          std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection, | ||||
|          std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted, | ||||
|          Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr); | ||||
|  | ||||
|     size_t Read(u8* data, size_t length, size_t offset) const override; | ||||
|  | ||||
|     std::string GetName() const override; | ||||
|  | ||||
|     size_t GetSize() const override; | ||||
|  | ||||
|     bool Resize(size_t new_size) override; | ||||
|  | ||||
|     std::shared_ptr<VfsDirectory> GetContainingDirectory() const override; | ||||
|  | ||||
|     bool IsWritable() const override; | ||||
|  | ||||
|     bool IsReadable() const override; | ||||
|  | ||||
|     size_t Write(const u8* data, size_t length, size_t offset) override; | ||||
|  | ||||
|     bool Rename(std::string_view name) override; | ||||
|  | ||||
| private: | ||||
|     template <bool Subsection, typename BlockType, typename BucketType> | ||||
|     std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block, | ||||
|                                                 BucketType buckets) const; | ||||
|  | ||||
|     RelocationEntry GetRelocationEntry(u64 offset) const; | ||||
|     RelocationEntry GetNextRelocationEntry(u64 offset) const; | ||||
|  | ||||
|     SubsectionEntry GetSubsectionEntry(u64 offset) const; | ||||
|     SubsectionEntry GetNextSubsectionEntry(u64 offset) const; | ||||
|  | ||||
|     RelocationBlock relocation; | ||||
|     std::vector<RelocationBucket> relocation_buckets; | ||||
|     SubsectionBlock subsection; | ||||
|     std::vector<SubsectionBucket> subsection_buckets; | ||||
|  | ||||
|     // Should be the raw base romfs, decrypted. | ||||
|     VirtualFile base_romfs; | ||||
|     // Should be the raw BKTR romfs, (located at media_offset with size media_size). | ||||
|     VirtualFile bktr_romfs; | ||||
|  | ||||
|     bool encrypted; | ||||
|     Core::Crypto::Key128 key; | ||||
|  | ||||
|     // Base offset into NCA, used for IV calculation. | ||||
|     u64 base_offset; | ||||
|     // Distance between IVFC start and RomFS start, used for base reads | ||||
|     u64 ivfc_offset; | ||||
|     std::array<u8, 8> section_ctr; | ||||
| }; | ||||
|  | ||||
| } // namespace FileSys | ||||
		Reference in New Issue
	
	Block a user