diff options
Diffstat (limited to 'src/core/file_sys/ips_layer.cpp')
-rw-r--r-- | src/core/file_sys/ips_layer.cpp | 209 |
1 files changed, 209 insertions, 0 deletions
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 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <sstream> #include "common/assert.h" +#include "common/hex_util.h" #include "common/swap.h" #include "core/file_sys/ips_layer.h" #include "core/file_sys/vfs_vector.h" @@ -15,6 +17,12 @@ enum class IPSFileType { Error, }; +constexpr std::array<std::pair<const char*, const char*>, 11> ESCAPE_CHARACTER_MAP{ + std::pair{"\\a", "\a"}, {"\\b", "\b"}, {"\\f", "\f"}, {"\\n", "\n"}, + {"\\r", "\r"}, {"\\t", "\t"}, {"\\v", "\v"}, {"\\\\", "\\"}, + {"\\\'", "\'"}, {"\\\"", "\""}, {"\\\?", "\?"}, +}; + static IPSFileType IdentifyMagic(const std::vector<u8>& magic) { if (magic.size() != 5) return IPSFileType::Error; @@ -85,4 +93,205 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); } +IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) : patch_text(std::move(patch_text_)) { + Parse(); +} + +IPSwitchCompiler::~IPSwitchCompiler() = default; + +std::array<u8, 32> IPSwitchCompiler::GetBuildID() const { + return nso_build_id; +} + +bool IPSwitchCompiler::IsValid() const { + return valid; +} + +static bool StartsWith(std::string_view base, std::string_view check) { + return base.size() >= check.size() && base.substr(0, check.size()) == check; +} + +static std::string EscapeStringSequences(std::string in) { + for (const auto& seq : ESCAPE_CHARACTER_MAP) { + for (auto index = in.find(seq.first); index != std::string::npos; + index = in.find(seq.first, index)) { + in.replace(index, std::strlen(seq.first), seq.second); + index += std::strlen(seq.second); + } + } + + return in; +} + +void IPSwitchCompiler::ParseFlag(const std::string& line) { + if (StartsWith(line, "@flag offset_shift ")) { + // Offset Shift Flag + offset_shift = std::stoll(line.substr(19), nullptr, 0); + } else if (StartsWith(line, "@little-endian")) { + // Set values to read as little endian + is_little_endian = true; + } else if (StartsWith(line, "@big-endian")) { + // Set values to read as big endian + is_little_endian = false; + } else if (StartsWith(line, "@flag print_values")) { + // Force printing of applied values + print_values = true; + } +} + +void IPSwitchCompiler::Parse() { + const auto bytes = patch_text->ReadAllBytes(); + std::stringstream s; + s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size()); + + std::vector<std::string> lines; + std::string stream_line; + while (std::getline(s, stream_line)) { + // Remove a trailing \r + if (!stream_line.empty() && stream_line.back() == '\r') + stream_line.pop_back(); + lines.push_back(std::move(stream_line)); + } + + for (std::size_t i = 0; i < lines.size(); ++i) { + auto line = lines[i]; + + // Remove midline comments + std::size_t comment_index = std::string::npos; + bool within_string = false; + for (std::size_t k = 0; k < line.size(); ++k) { + if (line[k] == '\"' && (k > 0 && line[k - 1] != '\\')) { + within_string = !within_string; + } else if (line[k] == '\\' && (k < line.size() - 1 && line[k + 1] == '\\')) { + comment_index = k; + break; + } + } + + if (!StartsWith(line, "//") && comment_index != std::string::npos) { + last_comment = line.substr(comment_index + 2); + line = line.substr(0, comment_index); + } + + if (StartsWith(line, "@stop")) { + // Force stop + break; + } else if (StartsWith(line, "@nsobid-")) { + // NSO Build ID Specifier + auto raw_build_id = line.substr(8); + if (raw_build_id.size() != 0x40) + raw_build_id.resize(0x40, '0'); + nso_build_id = Common::HexStringToArray<0x20>(raw_build_id); + } else if (StartsWith(line, "#")) { + // Mandatory Comment + LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}", + patch_text->GetName(), line.substr(1)); + } else if (StartsWith(line, "//")) { + // Normal Comment + last_comment = line.substr(2); + if (last_comment.find_first_not_of(' ') == std::string::npos) + continue; + if (last_comment.find_first_not_of(' ') != 0) + last_comment = last_comment.substr(last_comment.find_first_not_of(' ')); + } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) { + // Start of patch + const auto enabled = StartsWith(line, "@enabled"); + if (i == 0) + return; + LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})", + patch_text->GetName(), last_comment, line.substr(1)); + + IPSwitchPatch patch{last_comment, enabled, {}}; + + // Read rest of patch + while (true) { + if (i + 1 >= lines.size()) + break; + const auto patch_line = lines[++i]; + + // Start of new patch + if (StartsWith(patch_line, "@enabled") || StartsWith(patch_line, "@disabled")) { + --i; + break; + } + + // Check for a flag + if (StartsWith(patch_line, "@")) { + ParseFlag(patch_line); + continue; + } + + // 11 - 8 hex digit offset + space + minimum two digit overwrite val + if (patch_line.length() < 11) + break; + auto offset = std::stoul(patch_line.substr(0, 8), nullptr, 16); + offset += offset_shift; + + std::vector<u8> replace; + // 9 - first char of replacement val + if (patch_line[9] == '\"') { + // string replacement + auto end_index = patch_line.find('\"', 10); + if (end_index == std::string::npos || end_index < 10) + return; + while (patch_line[end_index - 1] == '\\') { + end_index = patch_line.find('\"', end_index + 1); + if (end_index == std::string::npos || end_index < 10) + return; + } + + auto value = patch_line.substr(10, end_index - 10); + value = EscapeStringSequences(value); + replace.reserve(value.size()); + std::copy(value.begin(), value.end(), std::back_inserter(replace)); + } else { + // hex replacement + const auto value = patch_line.substr(9); + replace.reserve(value.size() / 2); + replace = Common::HexStringToVector(value, is_little_endian); + } + + if (print_values) { + LOG_INFO(Loader, + "[IPSwitchCompiler ('{}')] - Patching value at offset 0x{:08X} " + "with byte string '{}'", + patch_text->GetName(), offset, Common::HexVectorToString(replace)); + } + + patch.records.insert_or_assign(offset, std::move(replace)); + } + + patches.push_back(std::move(patch)); + } else if (StartsWith(line, "@")) { + ParseFlag(line); + } + } + + valid = true; +} + +VirtualFile IPSwitchCompiler::Apply(const VirtualFile& in) const { + if (in == nullptr || !valid) + return nullptr; + + auto in_data = in->ReadAllBytes(); + + for (const auto& patch : patches) { + if (!patch.enabled) + continue; + + for (const auto& record : patch.records) { + if (record.first >= in_data.size()) + continue; + auto replace_size = record.second.size(); + if (record.first + replace_size > in_data.size()) + replace_size = in_data.size() - record.first; + for (std::size_t i = 0; i < replace_size; ++i) + in_data[i + record.first] = record.second[i]; + } + } + + return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); +} + } // namespace FileSys |