summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Zach Hilman2018-08-25 19:03:03 -0400
committerGravatar Zach Hilman2018-09-04 16:22:25 -0400
commit54e7ddb93a1a0357c14d03aeb182c7c98bb9cebb (patch)
tree627dc546b221fa8a47465a4ee730fdb012580aeb /src
parentcontent_archive: Add BKTR header parsing to NCA (diff)
downloadyuzu-54e7ddb93a1a0357c14d03aeb182c7c98bb9cebb.tar.gz
yuzu-54e7ddb93a1a0357c14d03aeb182c7c98bb9cebb.tar.xz
yuzu-54e7ddb93a1a0357c14d03aeb182c7c98bb9cebb.zip
file_sys: Add BKTR patching mechanism
Diffstat (limited to 'src')
-rw-r--r--src/core/file_sys/nca_patch.cpp208
-rw-r--r--src/core/file_sys/nca_patch.h144
2 files changed, 352 insertions, 0 deletions
diff --git a/src/core/file_sys/nca_patch.cpp b/src/core/file_sys/nca_patch.cpp
new file mode 100644
index 000000000..dd684c38e
--- /dev/null
+++ b/src/core/file_sys/nca_patch.cpp
@@ -0,0 +1,208 @@
1// Copyright 2018 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "common/assert.h"
6#include "core/crypto/aes_util.h"
7#include "core/file_sys/nca_patch.h"
8
9namespace FileSys {
10
11BKTR::BKTR(VirtualFile base_romfs_, VirtualFile bktr_romfs_, RelocationBlock relocation_,
12 std::vector<RelocationBucket> relocation_buckets_, SubsectionBlock subsection_,
13 std::vector<SubsectionBucket> subsection_buckets_, bool is_encrypted_,
14 Core::Crypto::Key128 key_, u64 base_offset_, u64 ivfc_offset_,
15 std::array<u8, 8> section_ctr_)
16 : base_romfs(std::move(base_romfs_)), bktr_romfs(std::move(bktr_romfs_)),
17 relocation(relocation_), relocation_buckets(std::move(relocation_buckets_)),
18 subsection(subsection_), subsection_buckets(std::move(subsection_buckets_)),
19 encrypted(is_encrypted_), key(key_), base_offset(base_offset_), ivfc_offset(ivfc_offset_),
20 section_ctr(std::move(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
34size_t BKTR::Read(u8* data, size_t length, size_t offset) const {
35 // Read out of bounds.
36 if (offset >= relocation.size)
37 return 0;
38 const auto relocation = GetRelocationEntry(offset);
39 const auto section_offset = offset - relocation.address_patch + relocation.address_source;
40 const auto bktr_read = relocation.from_patch;
41
42 const auto next_relocation = GetNextRelocationEntry(offset);
43
44 if (offset + length <= next_relocation.address_patch) {
45 if (bktr_read) {
46 if (!encrypted) {
47 return bktr_romfs->Read(data, length, section_offset);
48 }
49
50 const auto subsection = GetSubsectionEntry(section_offset);
51 Core::Crypto::AESCipher<Core::Crypto::Key128> cipher(key, Core::Crypto::Mode::CTR);
52
53 // Calculate AES IV
54 std::vector<u8> iv(16);
55 auto subsection_ctr = subsection.ctr;
56 auto offset_iv = section_offset + base_offset;
57 for (u8 i = 0; i < 8; ++i)
58 iv[i] = section_ctr[0x8 - i - 1];
59 offset_iv >>= 4;
60 for (size_t i = 0; i < 8; ++i) {
61 iv[0xF - i] = static_cast<u8>(offset_iv & 0xFF);
62 offset_iv >>= 8;
63 }
64 for (size_t i = 0; i < 4; ++i) {
65 iv[0x7 - i] = static_cast<u8>(subsection_ctr & 0xFF);
66 subsection_ctr >>= 8;
67 }
68 cipher.SetIV(iv);
69
70 const auto next_subsection = GetNextSubsectionEntry(section_offset);
71
72 if (section_offset + length <= next_subsection.address_patch) {
73 const auto block_offset = section_offset & 0xF;
74 if (block_offset != 0) {
75 auto block = bktr_romfs->ReadBytes(0x10, section_offset & ~0xF);
76 cipher.Transcode(block.data(), block.size(), block.data(),
77 Core::Crypto::Op::Decrypt);
78 if (length + block_offset < 0x10) {
79 std::memcpy(data, block.data() + block_offset,
80 std::min(length, block.size()));
81 return std::min(length, block.size());
82 }
83
84 const auto read = 0x10 - block_offset;
85 std::memcpy(data, block.data() + block_offset, read);
86 return read + Read(data + read, length - read, offset + read);
87 }
88
89 const auto raw_read = bktr_romfs->Read(data, length, section_offset);
90 cipher.Transcode(data, raw_read, data, Core::Crypto::Op::Decrypt);
91 return raw_read;
92 } else {
93 const u64 partition = next_subsection.address_patch - section_offset;
94 return Read(data, partition, offset) +
95 Read(data + partition, length - partition, offset + partition);
96 }
97 } else {
98 ASSERT(section_offset > ivfc_offset, "Offset calculation negative.");
99 return base_romfs->Read(data, length, section_offset);
100 }
101 } else {
102 const u64 partition = next_relocation.address_patch - offset;
103 return Read(data, partition, offset) +
104 Read(data + partition, length - partition, offset + partition);
105 }
106}
107
108template <bool Subsection, typename BlockType, typename BucketType>
109std::pair<size_t, size_t> BKTR::SearchBucketEntry(u64 offset, BlockType block,
110 BucketType buckets) const {
111 if constexpr (Subsection) {
112 const auto last_bucket = buckets[block.number_buckets - 1];
113 if (offset >= last_bucket.entries[last_bucket.number_entries].address_patch)
114 return {block.number_buckets - 1, last_bucket.number_entries};
115 } else {
116 ASSERT_MSG(offset <= block.size, "Offset is out of bounds in BKTR relocation block.");
117 }
118
119 size_t bucket_id = 0;
120 for (size_t i = 1; i < block.number_buckets; ++i) {
121 if (block.base_offsets[i] <= offset)
122 ++bucket_id;
123 }
124
125 const auto bucket = buckets[bucket_id];
126
127 if (bucket.number_entries == 1)
128 return {bucket_id, 0};
129
130 size_t low = 0;
131 size_t mid = 0;
132 size_t high = bucket.number_entries - 1;
133 while (low <= high) {
134 mid = (low + high) / 2;
135 if (bucket.entries[mid].address_patch > offset) {
136 high = mid - 1;
137 } else {
138 if (mid == bucket.number_entries - 1 ||
139 bucket.entries[mid + 1].address_patch > offset) {
140 return {bucket_id, mid};
141 }
142
143 low = mid + 1;
144 }
145 }
146
147 UNREACHABLE_MSG("Offset could not be found in BKTR block.");
148}
149
150RelocationEntry BKTR::GetRelocationEntry(u64 offset) const {
151 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
152 return relocation_buckets[res.first].entries[res.second];
153}
154
155RelocationEntry BKTR::GetNextRelocationEntry(u64 offset) const {
156 const auto res = SearchBucketEntry<false>(offset, relocation, relocation_buckets);
157 const auto bucket = relocation_buckets[res.first];
158 if (res.second + 1 < bucket.entries.size())
159 return bucket.entries[res.second + 1];
160 return relocation_buckets[res.first + 1].entries[0];
161}
162
163SubsectionEntry BKTR::GetSubsectionEntry(u64 offset) const {
164 const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
165 return subsection_buckets[res.first].entries[res.second];
166}
167
168SubsectionEntry BKTR::GetNextSubsectionEntry(u64 offset) const {
169 const auto res = SearchBucketEntry<true>(offset, subsection, subsection_buckets);
170 const auto bucket = subsection_buckets[res.first];
171 if (res.second + 1 < bucket.entries.size())
172 return bucket.entries[res.second + 1];
173 return subsection_buckets[res.first + 1].entries[0];
174}
175
176std::string BKTR::GetName() const {
177 return base_romfs->GetName();
178}
179
180size_t BKTR::GetSize() const {
181 return relocation.size;
182}
183
184bool BKTR::Resize(size_t new_size) {
185 return false;
186}
187
188std::shared_ptr<VfsDirectory> BKTR::GetContainingDirectory() const {
189 return base_romfs->GetContainingDirectory();
190}
191
192bool BKTR::IsWritable() const {
193 return false;
194}
195
196bool BKTR::IsReadable() const {
197 return true;
198}
199
200size_t BKTR::Write(const u8* data, size_t length, size_t offset) {
201 return 0;
202}
203
204bool BKTR::Rename(std::string_view name) {
205 return base_romfs->Rename(name);
206}
207
208} // 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..8b8d0a4f5
--- /dev/null
+++ b/src/core/file_sys/nca_patch.h
@@ -0,0 +1,144 @@
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 "core/crypto/key_manager.h"
8#include "core/file_sys/romfs.h"
9#include "core/loader/loader.h"
10
11namespace FileSys {
12
13#pragma pack(push, 1)
14struct RelocationEntry {
15 u64_le address_patch;
16 u64_le address_source;
17 u32 from_patch;
18};
19#pragma pack(pop)
20static_assert(sizeof(RelocationEntry) == 0x14, "RelocationEntry has incorrect size.");
21
22struct RelocationBucketRaw {
23 INSERT_PADDING_BYTES(4);
24 u32_le number_entries;
25 u64_le end_offset;
26 std::array<RelocationEntry, 0x332> relocation_entries;
27 INSERT_PADDING_BYTES(8);
28};
29static_assert(sizeof(RelocationBucketRaw) == 0x4000, "RelocationBucketRaw has incorrect size.");
30
31// Vector version of RelocationBucketRaw
32struct RelocationBucket {
33 u32 number_entries;
34 u64 end_offset;
35 std::vector<RelocationEntry> entries;
36};
37
38struct RelocationBlock {
39 INSERT_PADDING_BYTES(4);
40 u32_le number_buckets;
41 u64_le size;
42 std::array<u64, 0x7FE> base_offsets;
43};
44static_assert(sizeof(RelocationBlock) == 0x4000, "RelocationBlock has incorrect size.");
45
46struct SubsectionEntry {
47 u64_le address_patch;
48 INSERT_PADDING_BYTES(0x4);
49 u32_le ctr;
50};
51static_assert(sizeof(SubsectionEntry) == 0x10, "SubsectionEntry has incorrect size.");
52
53struct SubsectionBucketRaw {
54 INSERT_PADDING_BYTES(4);
55 u32_le number_entries;
56 u64_le end_offset;
57 std::array<SubsectionEntry, 0x3FF> subsection_entries;
58};
59static_assert(sizeof(SubsectionBucketRaw) == 0x4000, "SubsectionBucketRaw has incorrect size.");
60
61// Vector version of SubsectionBucketRaw
62struct SubsectionBucket {
63 u32 number_entries;
64 u64 end_offset;
65 std::vector<SubsectionEntry> entries;
66};
67
68struct SubsectionBlock {
69 INSERT_PADDING_BYTES(4);
70 u32_le number_buckets;
71 u64_le size;
72 std::array<u64, 0x7FE> base_offsets;
73};
74static_assert(sizeof(SubsectionBlock) == 0x4000, "SubsectionBlock has incorrect size.");
75
76inline RelocationBucket ConvertRelocationBucketRaw(RelocationBucketRaw raw) {
77 return {raw.number_entries,
78 raw.end_offset,
79 {raw.relocation_entries.begin(), raw.relocation_entries.begin() + raw.number_entries}};
80}
81
82inline SubsectionBucket ConvertSubsectionBucketRaw(SubsectionBucketRaw raw) {
83 return {raw.number_entries,
84 raw.end_offset,
85 {raw.subsection_entries.begin(), raw.subsection_entries.begin() + raw.number_entries}};
86}
87
88class BKTR : public VfsFile {
89public:
90 BKTR(VirtualFile base_romfs, VirtualFile bktr_romfs, RelocationBlock relocation,
91 std::vector<RelocationBucket> relocation_buckets, SubsectionBlock subsection,
92 std::vector<SubsectionBucket> subsection_buckets, bool is_encrypted,
93 Core::Crypto::Key128 key, u64 base_offset, u64 ivfc_offset, std::array<u8, 8> section_ctr);
94
95 size_t Read(u8* data, size_t length, size_t offset) const override;
96
97 std::string GetName() const override;
98
99 size_t GetSize() const override;
100
101 bool Resize(size_t new_size) override;
102
103 std::shared_ptr<VfsDirectory> GetContainingDirectory() const override;
104
105 bool IsWritable() const override;
106
107 bool IsReadable() const override;
108
109 size_t Write(const u8* data, size_t length, size_t offset) override;
110
111 bool Rename(std::string_view name) override;
112
113private:
114 template <bool Subsection, typename BlockType, typename BucketType>
115 std::pair<size_t, size_t> SearchBucketEntry(u64 offset, BlockType block,
116 BucketType buckets) const;
117
118 RelocationEntry GetRelocationEntry(u64 offset) const;
119 RelocationEntry GetNextRelocationEntry(u64 offset) const;
120
121 SubsectionEntry GetSubsectionEntry(u64 offset) const;
122 SubsectionEntry GetNextSubsectionEntry(u64 offset) const;
123
124 RelocationBlock relocation;
125 std::vector<RelocationBucket> relocation_buckets;
126 SubsectionBlock subsection;
127 std::vector<SubsectionBucket> subsection_buckets;
128
129 // Should be the raw base romfs, decrypted.
130 VirtualFile base_romfs;
131 // Should be the raw BKTR romfs, (located at media_offset with size media_size).
132 VirtualFile bktr_romfs;
133
134 bool encrypted;
135 Core::Crypto::Key128 key;
136
137 // Base offset into NCA, used for IV calculation.
138 u64 base_offset;
139 // Distance between IVFC start and RomFS start, used for base reads
140 u64 ivfc_offset;
141 std::array<u8, 8> section_ctr;
142};
143
144} // namespace FileSys