mirror of
				https://git.suyu.dev/suyu/suyu
				synced 2025-10-26 13:42:48 -05:00 
			
		
		
		
	ips_layer: Add IPSwitchCompiler to process IPSwitch format
This commit is contained in:
		| @@ -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" | ||||
| @@ -85,4 +87,144 @@ VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips) { | ||||
|     return std::make_shared<VectorVfsFile>(in_data, in->GetName(), in->GetContainingDirectory()); | ||||
| } | ||||
|  | ||||
| IPSwitchCompiler::IPSwitchCompiler(VirtualFile patch_text_) | ||||
|     : valid(false), patch_text(std::move(patch_text_)), nso_build_id{}, is_little_endian(false), | ||||
|       offset_shift(0), print_values(false) { | ||||
|     Parse(); | ||||
| } | ||||
|  | ||||
| std::array<u8, 32> IPSwitchCompiler::GetBuildID() const { | ||||
|     return nso_build_id; | ||||
| } | ||||
|  | ||||
| bool IPSwitchCompiler::IsValid() const { | ||||
|     return valid; | ||||
| } | ||||
|  | ||||
| static bool StartsWith(const std::string& base, const std::string& check) { | ||||
|     return base.size() >= check.size() && base.substr(0, check.size()) == check; | ||||
| } | ||||
|  | ||||
| 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 line; | ||||
|     while (std::getline(s, line)) { | ||||
|         // Remove a trailing \r | ||||
|         if (!line.empty() && line[line.size() - 1] == '\r') | ||||
|             line = line.substr(0, line.size() - 1); | ||||
|         lines.push_back(line); | ||||
|     } | ||||
|  | ||||
|     for (std::size_t i = 0; i < lines.size(); ++i) { | ||||
|         auto line = lines[i]; | ||||
|         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, "@flag offset_shift ")) { | ||||
|             // Offset Shift Flag | ||||
|             offset_shift = std::stoull(line.substr(19), nullptr, 0); | ||||
|         } else if (StartsWith(line, "#")) { | ||||
|             // Mandatory Comment | ||||
|             LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Forced output comment: {}", | ||||
|                      patch_text->GetName(), line.substr(1)); | ||||
|         } 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; | ||||
|         } else if (StartsWith(line, "@enabled") || StartsWith(line, "@disabled")) { | ||||
|             // Start of patch | ||||
|             const auto enabled = StartsWith(line, "@enabled"); | ||||
|             if (i == 0) | ||||
|                 return; | ||||
|             const auto name = lines[i - 1].substr(3); | ||||
|             LOG_INFO(Loader, "[IPSwitchCompiler ('{}')] Parsing patch '{}' ({})", | ||||
|                      patch_text->GetName(), name, line.substr(1)); | ||||
|  | ||||
|             IPSwitchPatch patch{name, enabled, {}}; | ||||
|  | ||||
|             // Read rest of patch | ||||
|             while (true) { | ||||
|                 if (i + 1 >= lines.size()) | ||||
|                     break; | ||||
|                 line = lines[++i]; | ||||
|  | ||||
|                 // 11 - 8 hex digit offset + space + minimum two digit overwrite val | ||||
|                 if (line.length() < 11) | ||||
|                     break; | ||||
|                 auto offset = std::stoul(line.substr(0, 8), nullptr, 16); | ||||
|                 offset += offset_shift; | ||||
|  | ||||
|                 std::vector<u8> replace; | ||||
|                 // 9 - first char of replacement val | ||||
|                 if (line[9] == '\"') { | ||||
|                     // string replacement | ||||
|                     const auto end_index = line.find_last_of('\"'); | ||||
|                     if (end_index == std::string::npos || end_index < 10) | ||||
|                         return; | ||||
|                     const auto value = line.substr(10, end_index - 10); | ||||
|                     replace.reserve(value.size()); | ||||
|                     std::copy(value.begin(), value.end(), std::back_inserter(replace)); | ||||
|                 } else { | ||||
|                     // hex replacement | ||||
|                     const auto value = 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.emplace(offset, replace); | ||||
|             } | ||||
|  | ||||
|             patches.push_back(std::move(patch)); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     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 | ||||
|   | ||||
| @@ -12,4 +12,30 @@ namespace FileSys { | ||||
|  | ||||
| VirtualFile PatchIPS(const VirtualFile& in, const VirtualFile& ips); | ||||
|  | ||||
| class IPSwitchCompiler { | ||||
| public: | ||||
|     explicit IPSwitchCompiler(VirtualFile patch_text); | ||||
|     std::array<u8, 0x20> GetBuildID() const; | ||||
|     bool IsValid() const; | ||||
|     VirtualFile Apply(const VirtualFile& in) const; | ||||
|  | ||||
| private: | ||||
|     void Parse(); | ||||
|  | ||||
|     bool valid; | ||||
|  | ||||
|     struct IPSwitchPatch { | ||||
|         std::string name; | ||||
|         bool enabled; | ||||
|         std::map<u32, std::vector<u8>> records; | ||||
|     }; | ||||
|  | ||||
|     VirtualFile patch_text; | ||||
|     std::vector<IPSwitchPatch> patches; | ||||
|     std::array<u8, 0x20> nso_build_id; | ||||
|     bool is_little_endian; | ||||
|     u64 offset_shift; | ||||
|     bool print_values; | ||||
| }; | ||||
|  | ||||
| } // namespace FileSys | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Zach Hilman
					Zach Hilman