diff options
| -rw-r--r-- | src/common/hex_util.cpp | 19 | ||||
| -rw-r--r-- | src/common/hex_util.h | 5 | ||||
| -rw-r--r-- | src/core/file_sys/ips_layer.cpp | 209 | ||||
| -rw-r--r-- | src/core/file_sys/ips_layer.h | 30 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.cpp | 82 | ||||
| -rw-r--r-- | src/core/file_sys/patch_manager.h | 1 |
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 | ||
| 21 | std::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 | |||
| 33 | std::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 | |||
| 21 | std::array<u8, 16> operator""_array16(const char* str, std::size_t len) { | 40 | std::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 | ||
| 15 | u8 ToHexNibble(char c1); | 16 | u8 ToHexNibble(char c1); |
| 16 | 17 | ||
| 18 | std::vector<u8> HexStringToVector(std::string_view str, bool little_endian); | ||
| 19 | |||
| 17 | template <std::size_t Size, bool le = false> | 20 | template <std::size_t Size, bool le = false> |
| 18 | std::array<u8, Size> HexStringToArray(std::string_view str) { | 21 | std::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 | ||
| 33 | std::string HexVectorToString(const std::vector<u8>& vector, bool upper = true); | ||
| 34 | |||
| 30 | template <std::size_t Size> | 35 | template <std::size_t Size> |
| 31 | std::string HexArrayToString(std::array<u8, Size> array, bool upper = true) { | 36 | std::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 | ||
| 20 | constexpr 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 | |||
| 18 | static IPSFileType IdentifyMagic(const std::vector<u8>& magic) { | 26 | static 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 | ||
| 96 | IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { | ||
| 97 | Parse(); | ||
| 98 | } | ||
| 99 | |||
| 100 | IPSwitchCompiler::~IPSwitchCompiler() = default; | ||
| 101 | |||
| 102 | std::array<u8, 32> IPSwitchCompiler::GetBuildID() const { | ||
| 103 | return nso_build_id; | ||
| 104 | } | ||
| 105 | |||
| 106 | bool IPSwitchCompiler::IsValid() const { | ||
| 107 | return valid; | ||
| 108 | } | ||
| 109 | |||
| 110 | static bool StartsWith(std::string_view base, std::string_view check) { | ||
| 111 | return base.size() >= check.size() && base.substr(0, check.size()) == check; | ||
| 112 | } | ||
| 113 | |||
| 114 | static 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 | |||
| 126 | void 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 | |||
| 142 | void 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 | |||
| 273 | VirtualFile 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 | ||
| 13 | VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips); | 13 | VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips); |
| 14 | 14 | ||
| 15 | class IPSwitchCompiler { | ||
| 16 | public: | ||
| 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 | |||
| 24 | private: | ||
| 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 | ||
| 76 | static std::vector<VirtualFile> CollectIPSPatches(const std::vector<VirtualDir>& patch_dirs, | 76 | static 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 | ||
| 99 | std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const { | 110 | std::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 | ||
| 149 | static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) { | 169 | static 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. |