From 5499d29cc2efe03da82a6159abdb5e951fedb9dd Mon Sep 17 00:00:00 2001 From: Martin Michelsen Date: Sat, 18 Nov 2023 21:33:45 -0800 Subject: [PATCH] add AR code translator shell --- CMakeLists.txt | 8 +- src/ARCodeTranslator-Stub.hh | 8 ++ src/ARCodeTranslator.cc | 143 +++++++++++++++++++++++++++++++++++ src/ARCodeTranslator.hh | 5 ++ src/Client.hh | 2 +- src/FunctionCompiler.cc | 2 +- src/FunctionCompiler.hh | 6 +- src/Main.cc | 18 ++++- 8 files changed, 185 insertions(+), 7 deletions(-) create mode 100644 src/ARCodeTranslator-Stub.hh create mode 100644 src/ARCodeTranslator.cc create mode 100644 src/ARCodeTranslator.hh diff --git a/CMakeLists.txt b/CMakeLists.txt index b771f642..d6db6386 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,7 +40,7 @@ find_package(resource_file QUIET) # Executable definition -add_executable(newserv +set(SOURCES src/AFSArchive.cc src/BattleParamsIndex.cc src/BMLArchive.cc @@ -112,6 +112,12 @@ add_executable(newserv src/Version.cc src/WordSelectTable.cc ) + +if(resource_file_FOUND) + set(SOURCES ${SOURCES} src/ARCodeTranslator.cc) +endif() + +add_executable(newserv ${SOURCES}) target_include_directories(newserv PUBLIC ${LIBEVENT_INCLUDE_DIR} ${Iconv_INCLUDE_DIRS}) target_link_libraries(newserv phosg ${LIBEVENT_LIBRARIES} ${Iconv_LIBRARIES} pthread) diff --git a/src/ARCodeTranslator-Stub.hh b/src/ARCodeTranslator-Stub.hh new file mode 100644 index 00000000..3989c757 --- /dev/null +++ b/src/ARCodeTranslator-Stub.hh @@ -0,0 +1,8 @@ +#pragma once + +#include +#include + +inline void run_ar_code_translator(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 new file mode 100644 index 00000000..73e13613 --- /dev/null +++ b/src/ARCodeTranslator.cc @@ -0,0 +1,143 @@ +#include "ARCodeTranslator.hh" + +#include +#include +#include + +using namespace std; + +void run_ar_code_translator(const std::string& initial_directory) { + string directory = initial_directory; + while (ends_with(directory, "/")) { + directory.resize(directory.size() - 1); + } + PrefixedLogger log("[ar-trans] "); + + unordered_map> files; + for (const auto& filename : list_directory(directory)) { + if (ends_with(filename, ".dol")) { + string name = filename.substr(0, filename.size() - 4); + string path = directory + "/" + filename; + files.emplace(name, new DOLFile(path.c_str())); + log.info("Loaded %s", name.c_str()); + } + } + + string source_filename; + shared_ptr source_file; + auto find_match = [&](std::shared_ptr target_file, uint32_t source_address) -> uint32_t { + const DOLFile::Section* source_section = nullptr; + for (const auto& sec : source_file->sections) { + if (source_address >= sec.address && source_address < sec.address + sec.data.size()) { + source_section = &sec; + break; + } + } + if (!source_section) { + throw runtime_error("source address not within any section"); + } + size_t source_offset = source_address - source_section->address; + size_t source_bytes_available_after = source_section->data.size() - source_offset; + log.info("(find_match) Source offset = %08zX with %08zX bytes available after", source_offset, source_bytes_available_after); + + for (size_t match_length = 4; + match_length < min(source_bytes_available_after, 0x100); + match_length += 4) { + size_t num_matches = 0; + size_t last_match_address = 0; + StringReader source_r(source_section->data.data() + source_offset, match_length); + for (const auto& target_section : target_file->sections) { + for (size_t target_section_offset = 0; + target_section_offset + match_length <= target_section.data.size(); + target_section_offset += 4) { + source_r.go(0); + StringReader target_r(target_section.data.data() + target_section_offset, match_length); + size_t z; + for (z = 0; z < match_length; z += 4) { + if (source_section->is_text) { + uint32_t source_opcode = source_r.get_u32b(); + uint32_t target_opcode = target_r.get_u32b(); + uint32_t source_class = source_opcode & 0xFC000000; + if (source_class != (target_opcode & 0xFC000000)) { + break; + } + if (source_class == 0x48000000) { + source_opcode &= 0xFC000003; + target_opcode &= 0xFC000003; + } else if (source_class == 0x40000000) { + source_opcode &= 0xFFFF0003; + target_opcode &= 0xFFFF0003; + } + if (source_opcode != target_opcode) { + break; + } + } else { + if (source_r.get_u32l() != target_r.get_u32l()) { + break; + } + } + } + if (z == match_length) { + num_matches++; + last_match_address = target_section.address + target_section_offset; + } + } + } + log.info("(find_match) For match length %zX, %zu matches found", 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"); + } + } + throw runtime_error("scan field too long; too many matches"); + }; + + while (!feof(stdin)) { + if (!source_filename.empty()) { + fprintf(stdout, "ar-trans:%s/%s> ", directory.c_str(), source_filename.c_str()); + } else { + fprintf(stdout, "ar-trans:%s> ", directory.c_str()); + } + fflush(stdout); + + string command = fgets(stdin); + try { + strip_trailing_whitespace(command); + auto tokens = split(command, ' '); + if (tokens.empty()) { + throw runtime_error("no command given"); + + } else if (tokens[0] == "use") { + source_filename = tokens.at(1); + source_file = files.at(source_filename); + + } else if (tokens[0] == "match") { + if (!source_file) { + throw runtime_error("no source file selected"); + } + + uint32_t source_addr = stoul(tokens.at(1), nullptr, 16); + for (const auto& it : files) { + if (it.second == source_file) { + log.info("(%s) %08" PRIX32, it.first.c_str(), source_addr); + } else { + try { + uint32_t match_addr = find_match(it.second, source_addr); + log.info("(%s) %08" PRIX32, it.first.c_str(), match_addr); + } catch (const exception& e) { + log.error("(%s) failed: %s", it.first.c_str(), e.what()); + } + } + } + + } else if (!tokens[0].empty()) { + throw runtime_error("unknown command"); + } + } catch (const exception& e) { + log.error("Failed: %s", e.what()); + } + } + + fputc('\n', stdout); +} diff --git a/src/ARCodeTranslator.hh b/src/ARCodeTranslator.hh new file mode 100644 index 00000000..8f68d332 --- /dev/null +++ b/src/ARCodeTranslator.hh @@ -0,0 +1,5 @@ +#pragma once + +#include + +void run_ar_code_translator(const std::string& directory); diff --git a/src/Client.hh b/src/Client.hh index 9cc920a4..06e9efac 100644 --- a/src/Client.hh +++ b/src/Client.hh @@ -214,7 +214,7 @@ struct Client : public std::enable_shared_from_this { // File loading state uint32_t dol_base_addr; - std::shared_ptr loading_dol_file; + std::shared_ptr loading_dol_file; std::unordered_map> sending_files; Client( diff --git a/src/FunctionCompiler.cc b/src/FunctionCompiler.cc index b2292b5e..500131bd 100644 --- a/src/FunctionCompiler.cc +++ b/src/FunctionCompiler.cc @@ -280,7 +280,7 @@ DOLFileIndex::DOLFileIndex(const string& directory) { string name = filename.substr(0, filename.size() - (is_compressed_dol ? 8 : 4)); try { - shared_ptr dol(new DOLFile()); + shared_ptr dol(new File()); dol->menu_item_id = next_menu_item_id++; dol->name = name; diff --git a/src/FunctionCompiler.hh b/src/FunctionCompiler.hh index 6b0982c4..db825e88 100644 --- a/src/FunctionCompiler.hh +++ b/src/FunctionCompiler.hh @@ -70,15 +70,15 @@ struct FunctionCodeIndex { }; struct DOLFileIndex { - struct DOLFile { + struct File { uint32_t menu_item_id; std::string name; std::string data; bool is_compressed; }; - std::vector> item_id_to_file; - std::unordered_map> name_to_file; + std::vector> item_id_to_file; + std::unordered_map> name_to_file; std::shared_ptr menu; DOLFileIndex() = default; diff --git a/src/Main.cc b/src/Main.cc index efe5eb5a..3b8dd0fa 100644 --- a/src/Main.cc +++ b/src/Main.cc @@ -14,6 +14,11 @@ #include #include +#ifdef HAVE_RESOURCE_FILE +#include "ARCodeTranslator.hh" +#else +#include "ARCodeTranslator-Stub.hh" +#endif #include "BMLArchive.hh" #include "CatSession.hh" #include "Compression.hh" @@ -337,6 +342,7 @@ enum class Behavior { GENERATE_ALL_DC_SERIAL_NUMBERS, INSPECT_DC_SERIAL_NUMBER, DC_SERIAL_NUMBER_SPEED_TEST, + AR_CODE_TRANSLATOR, }; static bool behavior_takes_input_filename(Behavior b) { @@ -382,7 +388,8 @@ static bool behavior_takes_input_filename(Behavior b) { (b == Behavior::PARSE_OBJECT_GRAPH) || (b == Behavior::REPLAY_LOG) || (b == Behavior::CAT_CLIENT) || - (b == Behavior::INSPECT_DC_SERIAL_NUMBER); + (b == Behavior::INSPECT_DC_SERIAL_NUMBER) || + (b == Behavior::AR_CODE_TRANSLATOR); } static bool behavior_takes_output_filename(Behavior b) { @@ -655,6 +662,8 @@ int main(int argc, char** argv) { behavior = Behavior::INSPECT_DC_SERIAL_NUMBER; } else if (!strcmp(argv[x], "dc-serial-number-speed-test")) { behavior = Behavior::DC_SERIAL_NUMBER_SPEED_TEST; + } else if (!strcmp(argv[x], "ar-code-translator")) { + behavior = Behavior::AR_CODE_TRANSLATOR; } else { throw invalid_argument(string_printf("unknown command: %s (try --help)", argv[x])); } @@ -1946,6 +1955,13 @@ int main(int argc, char** argv) { } break; + case Behavior::AR_CODE_TRANSLATOR: + if (!input_filename || !strcmp(input_filename, "-")) { + throw invalid_argument("a directory name is required"); + } + run_ar_code_translator(input_filename); + break; + case Behavior::REPLAY_LOG: case Behavior::RUN_SERVER: { bool is_replay = behavior == Behavior::REPLAY_LOG;