summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar bunnei2018-10-08 12:30:33 -0400
committerGravatar GitHub2018-10-08 12:30:33 -0400
commit6b48ba52712e5ea9cadb1177c06cb3ca65492c38 (patch)
tree365bb902d31fd5d5d585fc2f5d9e9dac274b02ec
parentMerge pull request #1456 from ogniK5377/aoc-u-fixups (diff)
parentips_layer: Fix inaccuracies with comments and flags (diff)
downloadyuzu-6b48ba52712e5ea9cadb1177c06cb3ca65492c38.tar.gz
yuzu-6b48ba52712e5ea9cadb1177c06cb3ca65492c38.tar.xz
yuzu-6b48ba52712e5ea9cadb1177c06cb3ca65492c38.zip
Merge pull request #1424 from DarkLordZach/ips-witch
ips_layer: Add support for IPSwitch executable patches
-rw-r--r--src/common/hex_util.cpp19
-rw-r--r--src/common/hex_util.h5
-rw-r--r--src/core/file_sys/ips_layer.cpp209
-rw-r--r--src/core/file_sys/ips_layer.h30
-rw-r--r--src/core/file_sys/patch_manager.cpp82
-rw-r--r--src/core/file_sys/patch_manager.h1
6 files changed, 323 insertions, 23 deletions
diff --git a/src/common/hex_util.cpp b/src/common/hex_util.cpp
index 589ae5cbf..5b63f9e81 100644
--- a/src/common/hex_util.cpp
+++ b/src/common/hex_util.cpp
@@ -18,6 +18,25 @@ u8 ToHexNibble(char c1) {
18 return 0; 18 return 0;
19} 19}
20 20
21std::vector<u8> HexStringToVector(std::string_view str, bool little_endian) {
22 std::vector<u8> out(str.size() / 2);
23 if (little_endian) {
24 for (std::size_t i = str.size() - 2; i <= str.size(); i -= 2)
25 out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
26 } else {
27 for (std::size_t i = 0; i < str.size(); i += 2)
28 out[i / 2] = (ToHexNibble(str[i]) << 4) | ToHexNibble(str[i + 1]);
29 }
30 return out;
31}
32
33std::string HexVectorToString(const std::vector<u8>& vector, bool upper) {
34 std::string out;
35 for (u8 c : vector)
36 out += fmt::format(upper ? "{:02X}" : "{:02x}", c);
37 return out;
38}
39
21std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { 40std::array<u8, 16> operator""_array16(const char* str, std::size_t len) {
22 if (len != 32) { 41 if (len != 32) {
23 LOG_ERROR(Common, 42 LOG_ERROR(Common,
diff --git a/src/common/hex_util.h b/src/common/hex_util.h
index 863a5ccd9..68f003cb6 100644
--- a/src/common/hex_util.h
+++ b/src/common/hex_util.h
@@ -7,6 +7,7 @@
7#include <array> 7#include <array>
8#include <cstddef> 8#include <cstddef>
9#include <string> 9#include <string>
10#include <vector>
10#include <fmt/format.h> 11#include <fmt/format.h>
11#include "common/common_types.h" 12#include "common/common_types.h"
12 13
@@ -14,6 +15,8 @@ namespace Common {
14 15
15u8 ToHexNibble(char c1); 16u8 ToHexNibble(char c1);
16 17
18std::vector<u8> HexStringToVector(std::string_view str, bool little_endian);
19
17template <std::size_t Size, bool le = false> 20template <std::size_t Size, bool le = false>
18std::array<u8, Size> HexStringToArray(std::string_view str) { 21std::array<u8, Size> HexStringToArray(std::string_view str) {
19 std::array<u8, Size> out{}; 22 std::array<u8, Size> out{};
@@ -27,6 +30,8 @@ std::array<u8, Size> HexStringToArray(std::string_view str) {
27 return out; 30 return out;
28} 31}
29 32
33std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true);
34
30template <std::size_t Size> 35template <std::size_t Size>
31std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { 36std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) {
32 std::string out; 37 std::string out;
diff --git a/src/core/file_sys/ips_layer.cpp b/src/core/file_sys/ips_layer.cpp
index df933ee36..0cadbc375 100644
--- a/src/core/file_sys/ips_layer.cpp
+++ b/src/core/file_sys/ips_layer.cpp
@@ -2,7 +2,9 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <sstream>
5#include "common/assert.h" 6#include "common/assert.h"
7#include "common/hex_util.h"
6#include "common/swap.h" 8#include "common/swap.h"
7#include "core/file_sys/ips_layer.h" 9#include "core/file_sys/ips_layer.h"
8#include "core/file_sys/vfs_vector.h" 10#include "core/file_sys/vfs_vector.h"
@@ -15,6 +17,12 @@ enum class IPSFileType {
15 Error, 17 Error,
16}; 18};
17 19
20constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{
21 std::pair{"\\a", "\a"}, {"\\b", "\b"}, {"\\f", "\f"}, {"\\n", "\n"},
22 {"\\r", "\r"}, {"\\t", "\t"}, {"\\v", "\v"}, {"\\\\", "\\"},
23 {"\\\'", "\'"}, {"\\\"", "\""}, {"\\\?", "\?"},
24};
25
18static IPSFileType IdentifyMagic(const std::vector<u8>& magic) { 26static IPSFileType IdentifyMagic(const std::vector<u8>& magic) {
19 if (magic.size() != 5) 27 if (magic.size() != 5)
20 return IPSFileType::Error; 28 return IPSFileType::Error;
@@ -85,4 +93,205 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) {
85 return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); 93 return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
86} 94}
87 95
96IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) {
97 Parse();
98}
99
100IPSwitchCompiler::~IPSwitchCompiler() = default;
101
102std::array<u8, 32> IPSwitchCompiler::GetBuildID() const {
103 return nso_build_id;
104}
105
106bool IPSwitchCompiler::IsValid() const {
107 return valid;
108}
109
110static bool StartsWith(std::string_view base, std::string_view check) {
111 return base.size() >= check.size() && base.substr(0, check.size()) == check;
112}
113
114static std::string EscapeStringSequences(std::string in) {
115 for (const auto& seq : ESCAPE_CHARACTER_MAP) {
116 for (auto index = in.find(seq.first); index != std::string::npos;
117 index = in.find(seq.first, index)) {
118 in.replace(index, std::strlen(seq.first), seq.second);
119 index += std::strlen(seq.second);
120 }
121 }
122
123 return in;
124}
125
126void IPSwitchCompiler::ParseFlag(const std::string& line) {
127 if (StartsWith(line, "@flag offset_shift ")) {
128 // Offset Shift Flag
129 offset_shift = std::stoll(line.substr(19), nullptr, 0);
130 } else if (StartsWith(line, "@little-endian")) {
131 // Set values to read as little endian
132 is_little_endian = true;
133 } else if (StartsWith(line, "@big-endian")) {
134 // Set values to read as big endian
135 is_little_endian = false;
136 } else if (StartsWith(line, "@flag print_values")) {
137 // Force printing of applied values
138 print_values = true;
139 }
140}
141
142void IPSwitchCompiler::Parse() {
143 const auto bytes = patch_text->ReadAllBytes();
144 std::stringstream s;
145 s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
146
147 std::vector<std::string> lines;
148 std::string stream_line;
149 while (std::getline(s, stream_line)) {
150 // Remove a trailing \r
151 if (!stream_line.empty() && stream_line.back() == '\r')
152 stream_line.pop_back();
153 lines.push_back(std::move(stream_line));
154 }
155
156 for (std::size_t i = 0; i < lines.size(); ++i) {
157 auto line = lines[i];
158
159 // Remove midline comments
160 std::size_t comment_index = std::string::npos;
161 bool within_string = false;
162 for (std::size_t k = 0; k < line.size(); ++k) {
163 if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) {
164 within_string = !within_string;
165 } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) {
166 comment_index = k;
167 break;
168 }
169 }
170
171 if (!StartsWith(line, "//") && comment_index != std::string::npos) {
172 last_comment = line.substr(comment_index + 2);
173 line = line.substr(0, comment_index);
174 }
175
176 if (StartsWith(line, "@stop")) {
177 // Force stop
178 break;
179 } else if (StartsWith(line, "@nsobid-")) {
180 // NSO Build ID Specifier
181 auto raw_build_id = line.substr(8);
182 if (raw_build_id.size() != 0x40)
183 raw_build_id.resize(0x40, '0');
184 nso_build_id = Common::HexStringToArray<0x20>(raw_build_id);
185 } else if (StartsWith(line, "#")) {
186 // Mandatory Comment
187 LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}",
188 patch_text->GetName(), line.substr(1));
189 } else if (StartsWith(line, "//")) {
190 // Normal Comment
191 last_comment = line.substr(2);
192 if (last_comment.find_first_not_of(' ') == std::string::npos)
193 continue;
194 if (last_comment.find_first_not_of(' ') != 0)
195 last_comment = last_comment.substr(last_comment.find_first_not_of(' '));
196 } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) {
197 // Start of patch
198 const auto enabled = StartsWith(line, "@enabled");
199 if (i == 0)
200 return;
201 LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})",
202 patch_text->GetName(), last_comment, line.substr(1));
203
204 IPSwitchPatch patch{last_comment, enabled, {}};
205
206 // Read rest of patch
207 while (true) {
208 if (i + 1 >= lines.size())
209 break;
210 const auto patch_line = lines[++i];
211
212 // Start of new patch
213 if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) {
214 --i;
215 break;
216 }
217
218 // Check for a flag
219 if (StartsWith(patch_line, "@")) {
220 ParseFlag(patch_line);
221 continue;
222 }
223
224 // 11 - 8 hex digit offset + space + minimum two digit overwrite val
225 if (patch_line.length() < 11)
226 break;
227 auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16);
228 offset += offset_shift;
229
230 std::vector<u8> replace;
231 // 9 - first char of replacement val
232 if (patch_line[9] == '\"') {
233 // string replacement
234 auto end_index = patch_line.find('\"', 10);
235 if (end_index == std::string::npos || end_index < 10)
236 return;
237 while (patch_line[end_index - 1] == '\\') {
238 end_index = patch_line.find('\"', end_index + 1);
239 if (end_index == std::string::npos || end_index < 10)
240 return;
241 }
242
243 auto value = patch_line.substr(10, end_index - 10);
244 value = EscapeStringSequences(value);
245 replace.reserve(value.size());
246 std::copy(value.begin(), value.end(), std::back_inserter(replace));
247 } else {
248 // hex replacement
249 const auto value = patch_line.substr(9);
250 replace.reserve(value.size() / 2);
251 replace = Common::HexStringToVector(value, is_little_endian);
252 }
253
254 if (print_values) {
255 LOG_INFO(Loader,
256 "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} "
257 "with byte string '{}'",
258 patch_text->GetName(), offset, Common::HexVectorToString(replace));
259 }
260
261 patch.records.insert_or_assign(offset, std::move(replace));
262 }
263
264 patches.push_back(std::move(patch));
265 } else if (StartsWith(line, "@")) {
266 ParseFlag(line);
267 }
268 }
269
270 valid = true;
271}
272
273VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const {
274 if (in == nullptr || !valid)
275 return nullptr;
276
277 auto in_data = in->ReadAllBytes();
278
279 for (const auto& patch : patches) {
280 if (!patch.enabled)
281 continue;
282
283 for (const auto& record : patch.records) {
284 if (record.first >= in_data.size())
285 continue;
286 auto replace_size = record.second.size();
287 if (record.first + replace_size > in_data.size())
288 replace_size = in_data.size() - record.first;
289 for (std::size_t i = 0; i < replace_size; ++i)
290 in_data[i + record.first] = record.second[i];
291 }
292 }
293
294 return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory());
295}
296
88} // namespace FileSys 297} // namespace FileSys
diff --git a/src/core/file_sys/ips_layer.h b/src/core/file_sys/ips_layer.h
index 81c163494..57da00da8 100644
--- a/src/core/file_sys/ips_layer.h
+++ b/src/core/file_sys/ips_layer.h
@@ -12,4 +12,34 @@ namespace FileSys {
12 12
13VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips); 13VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips);
14 14
15class IPSwitchCompiler {
16public:
17 explicit IPSwitchCompiler(VirtualFile patch_text);
18 ~IPSwitchCompiler();
19
20 std::array<u8, 0x20> GetBuildID() const;
21 bool IsValid() const;
22 VirtualFile Apply(const VirtualFile& in) const;
23
24private:
25 void ParseFlag(const std::string& flag);
26 void Parse();
27
28 bool valid = false;
29
30 struct IPSwitchPatch {
31 std::string name;
32 bool enabled;
33 std::map<u32, std::vector<u8>> records;
34 };
35
36 VirtualFile patch_text;
37 std::vector<IPSwitchPatch> patches;
38 std::array<u8, 0x20> nso_build_id{};
39 bool is_little_endian = false;
40 s64 offset_shift = 0;
41 bool print_values = false;
42 std::string last_comment = "";
43};
44
15} // namespace FileSys 45} // namespace FileSys
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 1ac00ebb0..7b31a57a4 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -73,27 +73,38 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const {
73 return exefs; 73 return exefs;
74} 74}
75 75
76static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs, 76static std::vector<VirtualFile> CollectPatches(const std::vector<VirtualDir>& patch_dirs,
77 const std::string& build_id) { 77 const std::string& build_id) {
78 std::vector<VirtualFile> ips; 78 std::vector<VirtualFile> out;
79 ips.reserve(patch_dirs.size()); 79 out.reserve(patch_dirs.size());
80 for (const auto& subdir : patch_dirs) { 80 for (const auto& subdir : patch_dirs) {
81 auto exefs_dir = subdir->GetSubdirectory("exefs"); 81 auto exefs_dir = subdir->GetSubdirectory("exefs");
82 if (exefs_dir != nullptr) { 82 if (exefs_dir != nullptr) {
83 for (const auto& file : exefs_dir->GetFiles()) { 83 for (const auto& file : exefs_dir->GetFiles()) {
84 if (file->GetExtension() != "ips") 84 if (file->GetExtension() == "ips") {
85 continue; 85 auto name = file->GetName();
86 auto name = file->GetName(); 86 const auto p1 = name.substr(0, name.find('.'));
87 const auto p1 = name.substr(0, name.find('.')); 87 const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1);
88 const auto this_build_id = p1.substr(0, p1.find_last_not_of('0') + 1); 88
89 89 if (build_id == this_build_id)
90 if (build_id == this_build_id) 90 out.push_back(file);
91 ips.push_back(file); 91 } else if (file->GetExtension() == "pchtxt") {
92 IPSwitchCompiler compiler{file};
93 if (!compiler.IsValid())
94 continue;
95
96 auto this_build_id = Common::HexArrayToString(compiler.GetBuildID());
97 this_build_id =
98 this_build_id.substr(0, this_build_id.find_last_not_of('0') + 1);
99
100 if (build_id == this_build_id)
101 out.push_back(file);
102 }
92 } 103 }
93 } 104 }
94 } 105 }
95 106
96 return ips; 107 return out;
97} 108}
98 109
99std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { 110std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
@@ -115,15 +126,24 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
115 auto patch_dirs = load_dir->GetSubdirectories(); 126 auto patch_dirs = load_dir->GetSubdirectories();
116 std::sort(patch_dirs.begin(), patch_dirs.end(), 127 std::sort(patch_dirs.begin(), patch_dirs.end(),
117 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 128 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
118 const auto ips = CollectIPSPatches(patch_dirs, build_id); 129 const auto patches = CollectPatches(patch_dirs, build_id);
119 130
120 auto out = nso; 131 auto out = nso;
121 for (const auto& ips_file : ips) { 132 for (const auto& patch_file : patches) {
122 LOG_INFO(Loader, " - Appling IPS patch from mod \"{}\"", 133 if (patch_file->GetExtension() == "ips") {
123 ips_file->GetContainingDirectory()->GetParentDirectory()->GetName()); 134 LOG_INFO(Loader, " - Applying IPS patch from mod \"{}\"",
124 const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), ips_file); 135 patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
125 if (patched != nullptr) 136 const auto patched = PatchIPS(std::make_shared<VectorVfsFile>(out), patch_file);
126 out = patched->ReadAllBytes(); 137 if (patched != nullptr)
138 out = patched->ReadAllBytes();
139 } else if (patch_file->GetExtension() == "pchtxt") {
140 LOG_INFO(Loader, " - Applying IPSwitch patch from mod \"{}\"",
141 patch_file->GetContainingDirectory()->GetParentDirectory()->GetName());
142 const IPSwitchCompiler compiler{patch_file};
143 const auto patched = compiler.Apply(std::make_shared<VectorVfsFile>(out));
144 if (patched != nullptr)
145 out = patched->ReadAllBytes();
146 }
127 } 147 }
128 148
129 if (out.size() < 0x100) 149 if (out.size() < 0x100)
@@ -143,7 +163,7 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
143 std::sort(patch_dirs.begin(), patch_dirs.end(), 163 std::sort(patch_dirs.begin(), patch_dirs.end(),
144 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); }); 164 [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
145 165
146 return !CollectIPSPatches(patch_dirs, build_id).empty(); 166 return !CollectPatches(patch_dirs, build_id).empty();
147} 167}
148 168
149static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { 169static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
@@ -263,8 +283,24 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
263 if (mod_dir != nullptr && mod_dir->GetSize() > 0) { 283 if (mod_dir != nullptr && mod_dir->GetSize() > 0) {
264 for (const auto& mod : mod_dir->GetSubdirectories()) { 284 for (const auto& mod : mod_dir->GetSubdirectories()) {
265 std::string types; 285 std::string types;
266 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("exefs"))) 286
267 AppendCommaIfNotEmpty(types, "IPS"); 287 const auto exefs_dir = mod->GetSubdirectory("exefs");
288 if (IsDirValidAndNonEmpty(exefs_dir)) {
289 bool ips = false;
290 bool ipswitch = false;
291
292 for (const auto& file : exefs_dir->GetFiles()) {
293 if (file->GetExtension() == "ips")
294 ips = true;
295 else if (file->GetExtension() == "pchtxt")
296 ipswitch = true;
297 }
298
299 if (ips)
300 AppendCommaIfNotEmpty(types, "IPS");
301 if (ipswitch)
302 AppendCommaIfNotEmpty(types, "IPSwitch");
303 }
268 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs"))) 304 if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
269 AppendCommaIfNotEmpty(types, "LayeredFS"); 305 AppendCommaIfNotEmpty(types, "LayeredFS");
270 306
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index 2ae9322a1..eb6fc4607 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -36,6 +36,7 @@ public:
36 36
37 // Currently tracked NSO patches: 37 // Currently tracked NSO patches:
38 // - IPS 38 // - IPS
39 // - IPSwitch
39 std::vector<u8> PatchNSO(const std::vector<u8>& nso) const; 40 std::vector<u8> PatchNSO(const std::vector<u8>& nso) const;
40 41
41 // Checks to see if PatchNSO() will have any effect given the NSO's build ID. 42 // Checks to see if PatchNSO() will have any effect given the NSO's build ID.