diff --git a/CMakeLists.txt b/CMakeLists.txt index 11f6a7ec..b51b2876 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,7 +136,7 @@ set(SOURCES ) if(resource_file_FOUND) - set(SOURCES ${SOURCES} src/ARCodeTranslator.cc) + set(SOURCES ${SOURCES} src/AddressTranslator.cc) endif() add_executable(newserv ${SOURCES}) diff --git a/src/ARCodeTranslator.cc b/src/ARCodeTranslator.cc deleted file mode 100644 index 7054df99..00000000 --- a/src/ARCodeTranslator.cc +++ /dev/null @@ -1,640 +0,0 @@ -#include "ARCodeTranslator.hh" - -#include -#include -#include -#include -#include -#include - -using namespace std; - -class ARCodeTranslator { -public: - enum class ExpandMethod { - FORWARD = 0, - FORWARD_WITH_BARRIER, - BACKWARD, - BACKWARD_WITH_BARRIER, - BOTH, - BOTH_WITH_BARRIER, - BOTH_IGNORE_ORIGIN, - }; - - static const char* name_for_expand_method(ExpandMethod method) { - switch (method) { - case ExpandMethod::FORWARD: - return "FORWARD"; - case ExpandMethod::FORWARD_WITH_BARRIER: - return "FORWARD_WITH_BARRIER"; - case ExpandMethod::BACKWARD: - return "BACKWARD"; - case ExpandMethod::BACKWARD_WITH_BARRIER: - return "BACKWARD_WITH_BARRIER"; - case ExpandMethod::BOTH: - return "BOTH"; - case ExpandMethod::BOTH_WITH_BARRIER: - return "BOTH_WITH_BARRIER"; - case ExpandMethod::BOTH_IGNORE_ORIGIN: - return "BOTH_IGNORE_ORIGIN"; - default: - throw logic_error("invalid expand method"); - } - } - - ARCodeTranslator(const string& directory) - : log("[ar-trans] "), - directory(directory) { - while (ends_with(this->directory, "/")) { - this->directory.pop_back(); - } - for (const auto& filename : list_directory(this->directory)) { - if (ends_with(filename, ".dol")) { - 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()); - } - } - } - ~ARCodeTranslator() = 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); - } - - void find_rtoc_global_regs() const { - for (const auto& it : this->files) { - bool r2_high_found = false; - bool r2_low_found = false; - bool r13_high_found = false; - bool r13_low_found = false; - uint32_t r2 = 0; - uint32_t r13 = 0; - for (const auto& section : it.second->sections) { - if (!section.is_text) { - continue; - } - StringReader r(section.data); - while (!r.eof() && r.where()) { - uint32_t opcode = r.get_u32b(); - if ((opcode & 0xFFFF0000) == 0x3DA00000) { - if (r13_high_found) { - throw runtime_error("multiple values for r13_high"); - } - r13_high_found = true; - r13 |= (opcode << 16); - } else if ((opcode & 0xFFFF0000) == 0x3C400000) { - if (r2_high_found) { - throw runtime_error("multiple values for r2_high"); - } - r2_high_found = true; - r2 |= (opcode << 16); - } else if ((opcode & 0xFFFF0000) == 0x61AD0000) { - if (r13_low_found) { - throw runtime_error("multiple values for r13_low"); - } - r13_low_found = true; - r13 |= (opcode & 0xFFFF); - } else if ((opcode & 0xFFFF0000) == 0x60420000) { - if (r2_low_found) { - throw runtime_error("multiple values for r2_low"); - } - r2_low_found = true; - r2 |= (opcode & 0xFFFF); - } - } - } - if (r2_low_found && r2_high_found) { - fprintf(stderr, "(%s) r2 = %08" PRIX32 "\n", it.first.c_str(), r2); - } else { - fprintf(stderr, "(%s) r2 = __MISSING__\n", it.first.c_str()); - } - if (r13_low_found && r13_high_found) { - fprintf(stderr, "(%s) r13 = %08" PRIX32 "\n", it.first.c_str(), r13); - } else { - fprintf(stderr, "(%s) r13 = __MISSING__\n", it.first.c_str()); - } - } - } - - uint32_t find_match(shared_ptr dest_file, uint32_t src_address, ExpandMethod expand_method) const { - if (!this->src_file) { - throw runtime_error("no source file selected"); - } - - const DOLFile::Section* src_section = nullptr; - for (const auto& sec : this->src_file->sections) { - if (src_address >= sec.address && src_address < sec.address + sec.data.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->address; - size_t src_bytes_available_before = src_offset; - size_t src_bytes_available_after = src_section->data.size() - src_offset - 4; - 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 + 4 < 0x100) { - size_t num_matches = 0; - size_t last_match_address = 0; - size_t match_length = match_bytes_before + match_bytes_after + 4; - StringReader src_r(src_section->data.data() + 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 < dest_section.data.size(); - dest_match_offset += 4) { - src_r.go(0); - StringReader dest_r(dest_section.data.data() + dest_match_offset, match_length); - size_t z; - for (z = 0; z < match_length; z += 4) { - if (expand_method == ExpandMethod::BOTH_IGNORE_ORIGIN && z == match_bytes_before) { - src_r.skip(4); - dest_r.skip(4); - } else if (src_section->is_text) { - uint32_t src_opcode = src_r.get_u32b(); - uint32_t dest_opcode = dest_r.get_u32b(); - uint32_t src_class = src_opcode & 0xFC000000; - if (src_class != (dest_opcode & 0xFC000000)) { - break; - } - if (src_class == 0x48000000) { - // b +-offset - src_opcode &= 0xFC000003; - dest_opcode &= 0xFC000003; - } else if (((src_opcode & 0xAC1F0000) == 0x800D0000) || ((src_opcode & 0xAC1F0000) == 0x80020000)) { - // lwz/lfs rXX/fXX, [r2/r13 +- offset] OR stw/stfs [r2/r13 +- offset], rXX/fXX - src_opcode &= 0xFFFF0000; - dest_opcode &= 0xFFFF0000; - } - if (src_opcode != dest_opcode) { - break; - } - } else { - uint32_t src_data = src_r.get_u32b(); - uint32_t dest_data = dest_r.get_u32b(); - if ((src_data & 0xFE000000) == 0x80000000) { - src_data &= 0xFE000003; - } - if ((dest_data & 0xFE000000) == 0x80000000) { - dest_data &= 0xFE000003; - } - if (src_data != dest_data) { - break; - } - } - } - if (z == match_length) { - num_matches++; - last_match_address = dest_section.address + 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_WITH_BARRIER: - can_expand_backward = (src_r.pget_u32b(0) != 0x4E800020) && - (src_bytes_available_before >= match_bytes_before + 4); - break; - case ExpandMethod::BACKWARD: - can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4); - break; - case ExpandMethod::FORWARD_WITH_BARRIER: - can_expand_forward = (src_r.pget_u32b(src_r.size() - 4) != 0x4E800020) && - (src_bytes_available_after >= match_bytes_after + 4); - break; - case ExpandMethod::FORWARD: - can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4); - break; - case ExpandMethod::BOTH_WITH_BARRIER: - case ExpandMethod::BOTH_IGNORE_ORIGIN: - can_expand_backward = (src_r.pget_u32b(0) != 0x4E800020) && - (src_bytes_available_before >= match_bytes_before + 4); - can_expand_forward = (src_r.pget_u32b(src_r.size() - 4) != 0x4E800020) && - (src_bytes_available_after >= match_bytes_after + 4); - break; - case ExpandMethod::BOTH: - can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4); - can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4); - 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 += 4; - } - if (can_expand_forward) { - match_bytes_after += 4; - } - } - throw runtime_error("scan field too long; too many matches"); - } - - void find_all_matches(uint32_t src_addr) const { - if (!this->src_file) { - throw runtime_error("no source file selected"); - } - - unordered_map results; - 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); - } else { - - array, 7> futures; - static const array methods = { - ExpandMethod::FORWARD, - ExpandMethod::FORWARD_WITH_BARRIER, - ExpandMethod::BACKWARD, - ExpandMethod::BACKWARD_WITH_BARRIER, - ExpandMethod::BOTH, - ExpandMethod::BOTH_WITH_BARRIER, - ExpandMethod::BOTH_IGNORE_ORIGIN, - }; - for (size_t z = 0; z < methods.size(); z++) { - futures[z] = async(&ARCodeTranslator::find_match, this, it.second, src_addr, 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)); - } else if (tokens[0] == "find-globals") { - this->find_rtoc_global_regs(); - } 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; -}; - -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()) { - trans.set_source_file(use_filename); - } - - if (!command.empty()) { - trans.handle_command(command); - } else { - trans.run_shell(); - } -} - -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()); - auto a_mem = make_shared(); - auto b_mem = make_shared(); - a.load_into(a_mem); - b.load_into(b_mem); - - uint32_t min_addr = 0xFFFFFFFF; - uint32_t max_addr = 0x00000000; - for (const auto& sec : a.sections) { - min_addr = min(min_addr, sec.address); - max_addr = max(max_addr, sec.address + sec.data.size()); - } - for (const auto& sec : b.sections) { - min_addr = min(min_addr, sec.address); - max_addr = max(max_addr, sec.address + sec.data.size()); - } - - vector> ret; - for (uint32_t addr = min_addr; addr < max_addr; addr += 4) { - bool a_exists = a_mem->exists(addr, 4); - bool b_exists = b_mem->exists(addr, 4); - if (a_exists && b_exists) { - string a_value = a_mem->read(addr, 4); - string b_value = b_mem->read(addr, 4); - if (a_value != b_value) { - if (!ret.empty() && (ret.back().first + ret.back().second.size() == addr)) { - ret.back().second += b_value; - } else { - ret.emplace_back(make_pair(addr, b_value)); - } - } - } - } - return ret; -} diff --git a/src/ARCodeTranslator-Stub.hh b/src/AddressTranslator-Stub.hh similarity index 62% rename from src/ARCodeTranslator-Stub.hh rename to src/AddressTranslator-Stub.hh index f727fbd8..db11e375 100644 --- a/src/ARCodeTranslator-Stub.hh +++ b/src/AddressTranslator-Stub.hh @@ -5,11 +5,7 @@ #include #include -inline void run_ar_code_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 void run_xbe_patch_translator(const std::string&, const std::string&, const std::string&) { +inline void run_address_translator(const std::string&, const std::string&, const std::string&) { throw std::runtime_error("resource_file is not available; install it and rebuild newserv"); } diff --git a/src/AddressTranslator.cc b/src/AddressTranslator.cc new file mode 100644 index 00000000..b1a65780 --- /dev/null +++ b/src/AddressTranslator.cc @@ -0,0 +1,528 @@ +#include "AddressTranslator.hh" + +#include +#include +#include +#include +#include +#include + +using namespace std; + +class AddressTranslator { +public: + enum class ExpandMethod { + PPC_TEXT_FORWARD = 0, + PPC_TEXT_FORWARD_WITH_BARRIER, + PPC_TEXT_BACKWARD, + PPC_TEXT_BACKWARD_WITH_BARRIER, + PPC_TEXT_BOTH, + PPC_TEXT_BOTH_WITH_BARRIER, + PPC_TEXT_BOTH_IGNORE_ORIGIN, + PPC_DATA_FORWARD, + PPC_DATA_BACKWARD, + PPC_DATA_BOTH, + RAW_FORWARD, + RAW_BACKWARD, + RAW_BOTH, + }; + + static const char* name_for_expand_method(ExpandMethod method) { + switch (method) { + case ExpandMethod::PPC_TEXT_FORWARD: + return "PPC_TEXT_FORWARD"; + case ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER: + return "PPC_TEXT_FORWARD_WITH_BARRIER"; + case ExpandMethod::PPC_TEXT_BACKWARD: + return "PPC_TEXT_BACKWARD"; + case ExpandMethod::PPC_TEXT_BACKWARD_WITH_BARRIER: + return "PPC_TEXT_BACKWARD_WITH_BARRIER"; + case ExpandMethod::PPC_TEXT_BOTH: + return "PPC_TEXT_BOTH"; + case ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER: + return "PPC_TEXT_BOTH_WITH_BARRIER"; + case ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN: + return "PPC_TEXT_BOTH_IGNORE_ORIGIN"; + case ExpandMethod::PPC_DATA_FORWARD: + return "PPC_DATA_FORWARD"; + case ExpandMethod::PPC_DATA_BACKWARD: + return "PPC_DATA_BACKWARD"; + case ExpandMethod::PPC_DATA_BOTH: + return "PPC_DATA_BOTH"; + case ExpandMethod::RAW_FORWARD: + return "RAW_FORWARD"; + case ExpandMethod::RAW_BACKWARD: + return "RAW_BACKWARD"; + case ExpandMethod::RAW_BOTH: + return "RAW_BOTH"; + default: + throw logic_error("invalid expand method"); + } + } + + static bool is_ppc_expand_method(ExpandMethod method) { + switch (method) { + case ExpandMethod::PPC_TEXT_FORWARD: + case ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BACKWARD: + case ExpandMethod::PPC_TEXT_BACKWARD_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BOTH: + case ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN: + case ExpandMethod::PPC_DATA_FORWARD: + case ExpandMethod::PPC_DATA_BACKWARD: + case ExpandMethod::PPC_DATA_BOTH: + return true; + case ExpandMethod::RAW_FORWARD: + case ExpandMethod::RAW_BACKWARD: + case ExpandMethod::RAW_BOTH: + return false; + default: + throw logic_error("invalid expand method"); + } + } + + static bool is_ppc_data_expand_method(ExpandMethod method) { + switch (method) { + case ExpandMethod::PPC_DATA_FORWARD: + case ExpandMethod::PPC_DATA_BACKWARD: + case ExpandMethod::PPC_DATA_BOTH: + return true; + case ExpandMethod::PPC_TEXT_FORWARD: + case ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BACKWARD: + case ExpandMethod::PPC_TEXT_BACKWARD_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BOTH: + case ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN: + case ExpandMethod::RAW_FORWARD: + case ExpandMethod::RAW_BACKWARD: + case ExpandMethod::RAW_BOTH: + return false; + default: + throw logic_error("invalid expand method"); + } + } + + AddressTranslator(const string& directory) + : log("[addr-trans] "), + directory(directory), + enable_ppc(false) { + while (ends_with(this->directory, "/")) { + this->directory.pop_back(); + } + for (const auto& filename : list_directory(this->directory)) { + if (ends_with(filename, ".dol")) { + string name = filename.substr(0, filename.size() - 4); + string path = directory + "/" + filename; + DOLFile dol(path.c_str()); + auto mem = make_shared(); + dol.load_into(mem); + this->mems.emplace(name, mem); + this->enable_ppc = true; + this->log.info("Loaded %s", name.c_str()); + } else if (ends_with(filename, ".xbe")) { + string name = filename.substr(0, filename.size() - 4); + string path = directory + "/" + filename; + XBEFile xbe(path.c_str()); + auto mem = make_shared(); + xbe.load_into(mem); + this->mems.emplace(name, mem); + this->log.info("Loaded %s", name.c_str()); + } else if (ends_with(filename, ".bin")) { + string name = filename.substr(0, filename.size() - 4); + string path = directory + "/" + filename; + string data = load_file(path); + auto mem = make_shared(); + mem->allocate_at(0x8C010000, data.size()); + mem->memcpy(0x8C010000, data.data(), data.size()); + this->mems.emplace(name, mem); + this->log.info("Loaded %s", name.c_str()); + } + } + } + ~AddressTranslator() = default; + + const string& get_source_filename() const { + return this->src_filename; + } + void set_source_file(const string& filename) { + this->src_filename = filename; + this->src_mem = this->mems.at(this->src_filename); + } + + void find_ppc_rtoc_global_regs() const { + for (const auto& it : this->mems) { + bool r2_high_found = false; + bool r2_low_found = false; + bool r13_high_found = false; + bool r13_low_found = false; + uint32_t r2 = 0; + uint32_t r13 = 0; + for (const auto& block : it.second->allocated_blocks()) { + StringReader r = it.second->reader(block.first, block.second); + while (!r.eof() && r.where()) { + uint32_t opcode = r.get_u32b(); + if ((opcode & 0xFFFF0000) == 0x3DA00000) { + if (r13_high_found) { + throw runtime_error("multiple values for r13_high"); + } + r13_high_found = true; + r13 |= (opcode << 16); + } else if ((opcode & 0xFFFF0000) == 0x3C400000) { + if (r2_high_found) { + throw runtime_error("multiple values for r2_high"); + } + r2_high_found = true; + r2 |= (opcode << 16); + } else if ((opcode & 0xFFFF0000) == 0x61AD0000) { + if (r13_low_found) { + throw runtime_error("multiple values for r13_low"); + } + r13_low_found = true; + r13 |= (opcode & 0xFFFF); + } else if ((opcode & 0xFFFF0000) == 0x60420000) { + if (r2_low_found) { + throw runtime_error("multiple values for r2_low"); + } + r2_low_found = true; + r2 |= (opcode & 0xFFFF); + } + } + } + if (r2_low_found && r2_high_found) { + fprintf(stderr, "(%s) r2 = %08" PRIX32 "\n", it.first.c_str(), r2); + } else { + fprintf(stderr, "(%s) r2 = __MISSING__\n", it.first.c_str()); + } + if (r13_low_found && r13_high_found) { + fprintf(stderr, "(%s) r13 = %08" PRIX32 "\n", it.first.c_str(), r13); + } else { + fprintf(stderr, "(%s) r13 = __MISSING__\n", it.first.c_str()); + } + } + } + + uint32_t find_match( + shared_ptr dest_mem, + uint32_t src_addr, + uint32_t src_size, + ExpandMethod expand_method) const { + bool is_ppc = this->is_ppc_expand_method(expand_method); + bool is_ppc_data = this->is_ppc_data_expand_method(expand_method); + if (!this->src_mem) { + throw runtime_error("no source file selected"); + } + + if (src_size == 0) { + src_size = is_ppc ? 4 : 1; + } + + pair src_section = make_pair(0, 0); + for (const auto& sec : this->src_mem->allocated_blocks()) { + if (src_addr >= sec.first && src_addr + src_size <= sec.first + sec.second) { + src_section = sec; + break; + } + } + if (!src_section.second) { + 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_addr - src_section.first; + size_t src_bytes_available_before = src_offset; + size_t src_bytes_available_after = src_section.second - src_offset - 4; + 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 + 4 < 0x100) { + size_t num_matches = 0; + size_t last_match_address = 0; + size_t match_length = match_bytes_before + match_bytes_after + 4; + StringReader src_r = this->src_mem->reader(src_section.first + src_offset - match_bytes_before, match_length); + for (const auto& dest_section : dest_mem->allocated_blocks()) { + for (size_t dest_match_offset = 0; + dest_match_offset + match_length < dest_section.second; + dest_match_offset += (is_ppc ? 4 : 1)) { + src_r.go(0); + StringReader dest_r = dest_mem->reader(dest_section.first + dest_match_offset, match_length); + size_t z; + if (is_ppc) { + for (z = 0; z < match_length; z += 4) { + if ((expand_method == ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN) && (z == match_bytes_before)) { + src_r.skip(4); + dest_r.skip(4); + } else if (!is_ppc_data) { + uint32_t src_opcode = src_r.get_u32b(); + uint32_t dest_opcode = dest_r.get_u32b(); + uint32_t src_class = src_opcode & 0xFC000000; + if (src_class != (dest_opcode & 0xFC000000)) { + break; + } + if (src_class == 0x48000000) { + // b +-offset + src_opcode &= 0xFC000003; + dest_opcode &= 0xFC000003; + } else if (((src_opcode & 0xAC1F0000) == 0x800D0000) || ((src_opcode & 0xAC1F0000) == 0x80020000)) { + // lwz/lfs rXX/fXX, [r2/r13 +- offset] OR stw/stfs [r2/r13 +- offset], rXX/fXX + src_opcode &= 0xFFFF0000; + dest_opcode &= 0xFFFF0000; + } + if (src_opcode != dest_opcode) { + break; + } + } else { + uint32_t src_data = src_r.get_u32b(); + uint32_t dest_data = dest_r.get_u32b(); + if ((src_data & 0xFE000000) == 0x80000000) { + src_data &= 0xFE000003; + } + if ((dest_data & 0xFE000000) == 0x80000000) { + dest_data &= 0xFE000003; + } + if (src_data != dest_data) { + break; + } + } + } + } else { + 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.first + 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::PPC_TEXT_BACKWARD_WITH_BARRIER: + can_expand_backward = (src_r.pget_u32b(0) != 0x4E800020) && + (src_bytes_available_before >= match_bytes_before + 4); + break; + case ExpandMethod::PPC_TEXT_BACKWARD: + case ExpandMethod::PPC_DATA_BACKWARD: + can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4); + break; + case ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER: + can_expand_forward = (src_r.pget_u32b(src_r.size() - 4) != 0x4E800020) && + (src_bytes_available_after >= match_bytes_after + 4); + break; + case ExpandMethod::PPC_TEXT_FORWARD: + case ExpandMethod::PPC_DATA_FORWARD: + can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4); + break; + case ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER: + case ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN: + can_expand_backward = (src_r.pget_u32b(0) != 0x4E800020) && + (src_bytes_available_before >= match_bytes_before + 4); + can_expand_forward = (src_r.pget_u32b(src_r.size() - 4) != 0x4E800020) && + (src_bytes_available_after >= match_bytes_after + 4); + break; + case ExpandMethod::PPC_TEXT_BOTH: + case ExpandMethod::PPC_DATA_BOTH: + can_expand_backward = (src_bytes_available_before >= match_bytes_before + 4); + can_expand_forward = (src_bytes_available_after >= match_bytes_after + 4); + break; + case ExpandMethod::RAW_BACKWARD: + can_expand_backward = (src_bytes_available_before > match_bytes_before); + break; + case ExpandMethod::RAW_FORWARD: + can_expand_forward = (src_bytes_available_after > match_bytes_after); + break; + case ExpandMethod::RAW_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 += (is_ppc ? 4 : 1); + } + if (can_expand_forward) { + match_bytes_after += (is_ppc ? 4 : 1); + } + } + 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_mem) { + throw runtime_error("no source file selected"); + } + + map results; + for (const auto& it : this->mems) { + if (it.second == this->src_mem) { + log.info("(%s) %08" PRIX32 " (from source)", it.first.c_str(), src_addr); + results.emplace(it.first, src_addr); + + } else { + vector> futures; + static const vector ppc_methods = { + ExpandMethod::PPC_TEXT_FORWARD, + ExpandMethod::PPC_TEXT_FORWARD_WITH_BARRIER, + ExpandMethod::PPC_TEXT_BACKWARD, + ExpandMethod::PPC_TEXT_BACKWARD_WITH_BARRIER, + ExpandMethod::PPC_TEXT_BOTH, + ExpandMethod::PPC_TEXT_BOTH_WITH_BARRIER, + ExpandMethod::PPC_TEXT_BOTH_IGNORE_ORIGIN, + ExpandMethod::PPC_DATA_FORWARD, + ExpandMethod::PPC_DATA_BACKWARD, + ExpandMethod::PPC_DATA_BOTH, + }; + static const vector raw_methods = { + ExpandMethod::RAW_FORWARD, + ExpandMethod::RAW_BACKWARD, + ExpandMethod::RAW_BOTH, + }; + const auto& methods = this->enable_ppc ? ppc_methods : raw_methods; + for (size_t z = 0; z < methods.size(); z++) { + futures.emplace_back(async(&AddressTranslator::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), + tokens.size() >= 2 ? stoul(tokens.at(2), nullptr, 16) : 0); + } else if (tokens[0] == "find-ppc-globals") { + this->find_ppc_rtoc_global_regs(); + } else if (!tokens[0].empty()) { + throw runtime_error("unknown command"); + } + } + + void run_shell() { + while (!feof(stdin)) { + if (!this->src_filename.empty()) { + fprintf(stdout, "addr-trans:%s/%s> ", this->directory.c_str(), this->src_filename.c_str()); + } else { + fprintf(stdout, "addr-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> mems; + string src_filename; + shared_ptr src_mem; + bool enable_ppc; +}; + +void run_address_translator(const std::string& directory, const std::string& use_filename, const std::string& command) { + AddressTranslator 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()); + auto a_mem = make_shared(); + auto b_mem = make_shared(); + a.load_into(a_mem); + b.load_into(b_mem); + + uint32_t min_addr = 0xFFFFFFFF; + uint32_t max_addr = 0x00000000; + for (const auto& sec : a.sections) { + min_addr = min(min_addr, sec.address); + max_addr = max(max_addr, sec.address + sec.data.size()); + } + for (const auto& sec : b.sections) { + min_addr = min(min_addr, sec.address); + max_addr = max(max_addr, sec.address + sec.data.size()); + } + + vector> ret; + for (uint32_t addr = min_addr; addr < max_addr; addr += 4) { + bool a_exists = a_mem->exists(addr, 4); + bool b_exists = b_mem->exists(addr, 4); + if (a_exists && b_exists) { + string a_value = a_mem->read(addr, 4); + string b_value = b_mem->read(addr, 4); + if (a_value != b_value) { + if (!ret.empty() && (ret.back().first + ret.back().second.size() == addr)) { + ret.back().second += b_value; + } else { + ret.emplace_back(make_pair(addr, b_value)); + } + } + } + } + return ret; +} diff --git a/src/ARCodeTranslator.hh b/src/AddressTranslator.hh similarity index 59% rename from src/ARCodeTranslator.hh rename to src/AddressTranslator.hh index 4a0c1fb3..ba68bc52 100644 --- a/src/ARCodeTranslator.hh +++ b/src/AddressTranslator.hh @@ -6,6 +6,5 @@ #include #include -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); +void run_address_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 2efac173..55014063 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -17,9 +17,9 @@ #include #ifdef HAVE_RESOURCE_FILE -#include "ARCodeTranslator.hh" +#include "AddressTranslator.hh" #else -#include "ARCodeTranslator-Stub.hh" +#include "AddressTranslator-Stub.hh" #endif #include "BMLArchive.hh" #include "CatSession.hh" @@ -2334,21 +2334,13 @@ Action a_dc_serial_number_speed_test( } }); -Action a_ar_code_translator( - "ar-code-translator", nullptr, +[](Arguments& args) { +Action a_address_translator( + "address-translator", nullptr, +[](Arguments& args) { const string& dir = args.get(1, false); if (dir.empty() || (dir == "-")) { throw invalid_argument("a directory name is required"); } - 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)); + run_address_translator(dir, args.get(2, false), args.get(3, false)); }); Action a_diff_dol_files(