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