2 changed files with 352 additions and 0 deletions
@ -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
|
|||
@ -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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue