diff --git a/src/ARCodeTranslator-Stub.hh b/src/ARCodeTranslator-Stub.hh index fbebff15..f727fbd8 100644 --- a/src/ARCodeTranslator-Stub.hh +++ b/src/ARCodeTranslator-Stub.hh @@ -9,6 +9,10 @@ inline void run_ar_code_translator(const std::string&, const std::string&, const throw std::runtime_error("resource_file is not available; install it and rebuild newserv"); } +inline void run_xbe_patch_translator(const std::string&, const std::string&, const std::string&) { + throw std::runtime_error("resource_file is not available; install it and rebuild newserv"); +} + inline std::vector> diff_dol_files(const std::string&, const std::string&) { throw std::runtime_error("resource_file is not available; install it and rebuild newserv"); } diff --git a/src/ARCodeTranslator.cc b/src/ARCodeTranslator.cc index 6ec03e8f..7054df99 100644 --- a/src/ARCodeTranslator.cc +++ b/src/ARCodeTranslator.cc @@ -5,6 +5,7 @@ #include #include #include +#include using namespace std; @@ -63,11 +64,11 @@ public: } void set_source_file(const string& filename) { this->src_filename = filename; - this->src_file = files.at(this->src_filename); + this->src_file = this->files.at(this->src_filename); } void find_rtoc_global_regs() const { - for (const auto& it : files) { + for (const auto& it : this->files) { bool r2_high_found = false; bool r2_low_found = false; bool r13_high_found = false; @@ -258,7 +259,7 @@ public: } unordered_map results; - for (const auto& it : files) { + for (const auto& it : this->files) { if (it.second == this->src_file) { log.info("(%s) %08" PRIX32 " (from source)", it.first.c_str(), src_addr); results.emplace(it.first, src_addr); @@ -349,6 +350,231 @@ private: shared_ptr src_file; }; +class XBEPatchTranslator { +public: + enum class ExpandMethod { + FORWARD = 0, + BACKWARD, + BOTH, + }; + + static const char* name_for_expand_method(ExpandMethod method) { + switch (method) { + case ExpandMethod::FORWARD: + return "FORWARD"; + case ExpandMethod::BACKWARD: + return "BACKWARD"; + case ExpandMethod::BOTH: + return "BOTH"; + default: + throw logic_error("invalid expand method"); + } + } + + XBEPatchTranslator(const string& directory) + : log("[xbe-trans] "), + directory(directory) { + while (ends_with(this->directory, "/")) { + this->directory.pop_back(); + } + for (const auto& filename : list_directory(this->directory)) { + if (ends_with(filename, ".xbe")) { + string name = filename.substr(0, filename.size() - 4); + string path = directory + "/" + filename; + this->files.emplace(name, make_shared(path.c_str())); + this->log.info("Loaded %s", name.c_str()); + } + } + } + ~XBEPatchTranslator() = default; + + const string& get_source_filename() const { + return this->src_filename; + } + void set_source_file(const string& filename) { + this->src_filename = filename; + this->src_file = this->files.at(this->src_filename); + } + + uint32_t find_match( + shared_ptr dest_file, + uint32_t src_address, + uint32_t src_size, + ExpandMethod expand_method) const { + if (!this->src_file) { + throw runtime_error("no source file selected"); + } + + const XBEFile::Section* src_section = nullptr; + for (const auto& sec : this->src_file->sections) { + if (src_address >= sec.addr && src_address < sec.addr + sec.file_size) { + src_section = &sec; + break; + } + } + if (!src_section) { + throw runtime_error("source address not within any section"); + } + + const char* method_token = this->name_for_expand_method(expand_method); + + size_t src_offset = src_address - src_section->addr; + size_t src_bytes_available_before = src_offset; + size_t src_bytes_available_after = src_section->file_size - src_offset - src_size; + this->log.info("(find_match/%s) Source offset = %08zX with %zX/%zX bytes available before/after", + method_token, src_offset, src_bytes_available_before, src_bytes_available_after); + + size_t match_bytes_before = 0; + size_t match_bytes_after = 0; + while (match_bytes_before + match_bytes_after + src_size < 0x100) { + size_t num_matches = 0; + size_t last_match_address = 0; + size_t match_length = match_bytes_before + match_bytes_after + src_size; + auto src_r = this->src_file->read_from_addr(src_section->addr + src_offset - match_bytes_before, match_length); + for (const auto& dest_section : dest_file->sections) { + for (size_t dest_match_offset = 0; dest_match_offset + match_length <= dest_section.file_size; dest_match_offset++) { + src_r.go(0); + StringReader dest_r = dest_file->read_from_addr(dest_section.addr + dest_match_offset, match_length); + size_t z; + for (z = 0; z < match_length; z++) { + uint8_t src_data = src_r.get_u8(); + uint8_t dest_data = dest_r.get_u8(); + if (src_data != dest_data) { + break; + } + } + if (z == match_length) { + num_matches++; + last_match_address = dest_section.addr + dest_match_offset + match_bytes_before; + } + } + } + this->log.info("(find_match/%s) For match length %zX, %zu matches found", method_token, match_length, num_matches); + if (num_matches == 1) { + return last_match_address; + } else if (num_matches == 0) { + throw runtime_error("did not find exactly one match"); + } + bool can_expand_backward = false; + bool can_expand_forward = false; + switch (expand_method) { + case ExpandMethod::BACKWARD: + can_expand_backward = (src_bytes_available_before > match_bytes_before); + break; + case ExpandMethod::FORWARD: + can_expand_forward = (src_bytes_available_after > match_bytes_after); + break; + case ExpandMethod::BOTH: + can_expand_backward = (src_bytes_available_before > match_bytes_before); + can_expand_forward = (src_bytes_available_after > match_bytes_after); + break; + default: + throw logic_error("invalid expand method"); + } + if (!can_expand_backward && !can_expand_forward) { + throw runtime_error("no further expansion is allowed"); + } + if (can_expand_backward) { + match_bytes_before++; + } + if (can_expand_forward) { + match_bytes_after++; + } + } + throw runtime_error("scan field too long; too many matches"); + } + + void find_all_matches(uint32_t src_addr, uint32_t src_size) const { + if (!this->src_file) { + throw runtime_error("no source file selected"); + } + + unordered_map results; + for (const auto& it : files) { + if (it.second == this->src_file) { + log.info("(%s) %08" PRIX32 " (from source)", it.first.c_str(), src_addr); + results.emplace(it.first, src_addr); + } else { + + array, 3> futures; + static const array methods = { + ExpandMethod::FORWARD, + ExpandMethod::BACKWARD, + ExpandMethod::BOTH, + }; + for (size_t z = 0; z < methods.size(); z++) { + futures[z] = async(&XBEPatchTranslator::find_match, this, it.second, src_addr, src_size, methods[z]); + } + + unordered_set match_addrs; + for (size_t z = 0; z < futures.size(); z++) { + const char* method_name = this->name_for_expand_method(methods[z]); + try { + uint32_t ret = futures[z].get(); + log.info("(%s) (%s) %08" PRIX32, it.first.c_str(), method_name, ret); + match_addrs.emplace(ret); + } catch (const exception& e) { + log.error("(%s) (%s) failed: %s", it.first.c_str(), method_name, e.what()); + } + } + + if (match_addrs.empty()) { + log.error("(%s) no match found", it.first.c_str()); + } else if (match_addrs.size() > 1) { + log.error("(%s) different matches found by different methods", it.first.c_str()); + } else { + results.emplace(it.first, *match_addrs.begin()); + } + } + } + for (const auto& it : results) { + fprintf(stdout, "%s => %08" PRIX32 "\n", it.first.c_str(), it.second); + } + } + + void handle_command(const string& command) { + auto tokens = split(command, ' '); + if (tokens.empty()) { + throw runtime_error("no command given"); + } + strip_trailing_whitespace(tokens[tokens.size() - 1]); + + if (tokens[0] == "use") { + this->set_source_file(tokens.at(1)); + } else if (tokens[0] == "match") { + this->find_all_matches(stoul(tokens.at(1), nullptr, 16), stoul(tokens.at(2), nullptr, 16)); + } else if (!tokens[0].empty()) { + throw runtime_error("unknown command"); + } + } + + void run_shell() { + while (!feof(stdin)) { + if (!this->src_filename.empty()) { + fprintf(stdout, "ar-trans:%s/%s> ", this->directory.c_str(), this->src_filename.c_str()); + } else { + fprintf(stdout, "ar-trans:%s> ", this->directory.c_str()); + } + fflush(stdout); + + string command = fgets(stdin); + try { + this->handle_command(command); + } catch (const exception& e) { + this->log.error("Failed: %s", e.what()); + } + } + fputc('\n', stdout); + } + +private: + PrefixedLogger log; + string directory; + unordered_map> files; + string src_filename; + shared_ptr src_file; +}; + void run_ar_code_translator(const std::string& directory, const std::string& use_filename, const std::string& command) { ARCodeTranslator trans(directory); if (!use_filename.empty()) { @@ -362,6 +588,19 @@ void run_ar_code_translator(const std::string& directory, const std::string& use } } +void run_xbe_patch_translator(const std::string& directory, const std::string& use_filename, const std::string& command) { + XBEPatchTranslator trans(directory); + if (!use_filename.empty()) { + trans.set_source_file(use_filename); + } + + if (!command.empty()) { + trans.handle_command(command); + } else { + trans.run_shell(); + } +} + vector> diff_dol_files(const string& a_filename, const string& b_filename) { DOLFile a(a_filename.c_str()); DOLFile b(b_filename.c_str()); diff --git a/src/ARCodeTranslator.hh b/src/ARCodeTranslator.hh index 1fa475c4..4a0c1fb3 100644 --- a/src/ARCodeTranslator.hh +++ b/src/ARCodeTranslator.hh @@ -6,5 +6,6 @@ #include #include -void run_ar_code_translator(const std::string& initial_directory, const std::string& use_file, const std::string& command); +void run_ar_code_translator(const std::string& directory, const std::string& use_filename, const std::string& command); +void run_xbe_patch_translator(const std::string& directory, const std::string& use_filename, const std::string& command); std::vector> diff_dol_files(const std::string& a_filename, const std::string& b_filename); diff --git a/src/Main.cc b/src/Main.cc index 22c9650e..22e25091 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -2280,6 +2280,14 @@ Action a_ar_code_translator( } run_ar_code_translator(dir, args.get(2, false), args.get(3, false)); }); +Action a_xbe_patch_translator( + "xbe-patch-translator", nullptr, +[](Arguments& args) { + const string& dir = args.get(1, false); + if (dir.empty() || (dir == "-")) { + throw invalid_argument("a directory name is required"); + } + run_xbe_patch_translator(dir, args.get(2, false), args.get(3, false)); + }); Action a_diff_dol_files( "diff-dol-files", nullptr, +[](Arguments& args) {