#ifndef __RECOMP_PORT__ #define __RECOMP_PORT__ #include #include #include #include #include #include #include #include #ifdef _MSC_VER inline uint32_t byteswap(uint32_t val) { return _byteswap_ulong(val); } #else constexpr uint32_t byteswap(uint32_t val) { return __builtin_bswap32(val); } #endif namespace N64Recomp { struct Function { uint32_t vram; uint32_t rom; std::vector words; std::string name; uint16_t section_index; bool ignored; bool reimplemented; bool stubbed; std::unordered_map function_hooks; Function(uint32_t vram, uint32_t rom, std::vector words, std::string name, uint16_t section_index, bool ignored = false, bool reimplemented = false, bool stubbed = false) : vram(vram), rom(rom), words(std::move(words)), name(std::move(name)), section_index(section_index), ignored(ignored), reimplemented(reimplemented), stubbed(stubbed) {} Function() = default; }; enum class RelocType : uint8_t { R_MIPS_NONE = 0, R_MIPS_16, R_MIPS_32, R_MIPS_REL32, R_MIPS_26, R_MIPS_HI16, R_MIPS_LO16, R_MIPS_GPREL16, }; struct Reloc { uint32_t address; uint32_t target_section_offset; uint32_t symbol_index; // Only used for reference symbols and special section symbols uint16_t target_section; RelocType type; bool reference_symbol; }; // Special section indices. constexpr uint16_t SectionAbsolute = (uint16_t)-2; constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods constexpr uint16_t SectionEvent = (uint16_t)-4; // Special section names. constexpr std::string_view PatchSectionName = ".recomp_patch"; constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch"; constexpr std::string_view ExportSectionName = ".recomp_export"; constexpr std::string_view EventSectionName = ".recomp_event"; constexpr std::string_view ImportSectionPrefix = ".recomp_import."; constexpr std::string_view CallbackSectionPrefix = ".recomp_callback."; // Special dependency names. constexpr std::string_view DependencySelf = "."; constexpr std::string_view DependencyBaseRecomp = "*"; struct Section { uint32_t rom_addr = 0; uint32_t ram_addr = 0; uint32_t size = 0; uint32_t bss_size = 0; // not populated when using a symbol toml std::vector function_addrs; // only used by the CLI (to find the size of static functions) std::vector relocs; std::string name; uint16_t bss_section_index = (uint16_t)-1; bool executable = false; bool relocatable = false; // TODO is this needed? relocs being non-empty should be an equivalent check. bool has_mips32_relocs = false; }; struct ReferenceSection { uint32_t rom_addr; uint32_t ram_addr; uint32_t size; bool relocatable; }; struct ReferenceSymbol { std::string name; uint16_t section_index; uint32_t section_offset; bool is_function; }; struct ElfParsingConfig { std::string bss_section_suffix; // Functions with manual size overrides std::unordered_map manually_sized_funcs; // The section names that were specified as relocatable std::unordered_set relocatable_sections; bool has_entrypoint; int32_t entrypoint_address; bool use_absolute_symbols; bool unpaired_lo16_warnings; bool all_sections_relocatable; }; struct DataSymbol { uint32_t vram; std::string name; DataSymbol(uint32_t vram, std::string&& name) : vram(vram), name(std::move(name)) {} }; using DataSymbolMap = std::unordered_map>; extern const std::unordered_set reimplemented_funcs; extern const std::unordered_set ignored_funcs; extern const std::unordered_set renamed_funcs; struct ImportSymbol { ReferenceSymbol base; size_t dependency_index; }; struct DependencyEvent { size_t dependency_index; std::string event_name; }; struct EventSymbol { ReferenceSymbol base; }; struct Callback { size_t function_index; size_t dependency_event_index; }; struct SymbolReference { // Reference symbol section index, or one of the special section indices such as SectionImport. uint16_t section_index; size_t symbol_index; }; enum class ReplacementFlags : uint32_t { Force = 1 << 0, }; inline ReplacementFlags operator&(ReplacementFlags lhs, ReplacementFlags rhs) { return ReplacementFlags(uint32_t(lhs) & uint32_t(rhs)); } inline ReplacementFlags operator|(ReplacementFlags lhs, ReplacementFlags rhs) { return ReplacementFlags(uint32_t(lhs) | uint32_t(rhs)); } struct FunctionReplacement { uint32_t func_index; uint32_t original_section_vrom; uint32_t original_vram; ReplacementFlags flags; }; class Context { private: //// Reference symbols (used for populating relocations for patches) // A list of the sections that contain the reference symbols. std::vector reference_sections; // A list of the reference symbols. std::vector reference_symbols; // Mapping of symbol name to reference symbol index. std::unordered_map reference_symbols_by_name; public: std::vector
sections; std::vector functions; // A list of the list of each function (by index in `functions`) in a given section std::vector> section_functions; // A mapping of vram address to every function with that address. std::unordered_map> functions_by_vram; // A mapping of bss section index to the corresponding non-bss section index. std::unordered_map bss_section_to_section; // The target ROM being recompiled, TODO move this outside of the context to avoid making a copy for mod contexts. // Used for reading relocations and for the output binary feature. std::vector rom; //// Only used by the CLI, TODO move this to a struct in the internal headers. // A mapping of function name to index in the functions vector std::unordered_map functions_by_name; //// Mod dependencies and their symbols //// Imported values // Mapping of dependency name to dependency index. std::unordered_map dependencies_by_name; // List of symbols imported from dependencies. std::vector import_symbols; // List of events imported from dependencies. std::vector dependency_events; // Mappings of dependency event name to the index in dependency_events, all indexed by dependency. std::vector> dependency_events_by_name; // Mappings of dependency import name to index in import_symbols, all indexed by dependency. std::vector> dependency_imports_by_name; //// Exported values // List of function replacements, which contains the original function to replace and the function index to replace it with. std::vector replacements; // Indices of every exported function. std::vector exported_funcs; // List of callbacks, which contains the function for the callback and the dependency event it attaches to. std::vector callbacks; // List of symbols from events, which contains the names of events that this context provides. std::vector event_symbols; // Causes functions to print their name to the console the first time they're called. bool trace_mode; // Imports sections and function symbols from a provided context into this context's reference sections and reference functions. bool import_reference_context(const Context& reference_context); // Reads a data symbol file and adds its contents into this context's reference data symbols. bool read_data_reference_syms(const std::filesystem::path& data_syms_file_path); static bool from_symbol_file(const std::filesystem::path& symbol_file_path, std::vector&& rom, Context& out, bool with_relocs); static bool from_elf_file(const std::filesystem::path& elf_file_path, Context& out, const ElfParsingConfig& flags, bool for_dumping_context, DataSymbolMap& data_syms_out, bool& found_entrypoint_out); Context() = default; bool add_dependency(const std::string& id) { if (dependencies_by_name.contains(id)) { return false; } size_t dependency_index = dependencies_by_name.size(); dependencies_by_name.emplace(id, dependency_index); dependency_events_by_name.resize(dependencies_by_name.size()); dependency_imports_by_name.resize(dependencies_by_name.size()); return true; } bool add_dependencies(const std::vector& new_dependencies) { dependencies_by_name.reserve(dependencies_by_name.size() + new_dependencies.size()); // Check if any of the dependencies already exist and fail if so. for (const std::string& dep : new_dependencies) { if (dependencies_by_name.contains(dep)) { return false; } } for (const std::string& dep : new_dependencies) { size_t dependency_index = dependencies_by_name.size(); dependencies_by_name.emplace(dep, dependency_index); } dependency_events_by_name.resize(dependencies_by_name.size()); dependency_imports_by_name.resize(dependencies_by_name.size()); return true; } bool find_dependency(const std::string& mod_id, size_t& dependency_index) { auto find_it = dependencies_by_name.find(mod_id); if (find_it != dependencies_by_name.end()) { dependency_index = find_it->second; } else { // Handle special dependency names. if (mod_id == DependencySelf || mod_id == DependencyBaseRecomp) { add_dependency(mod_id); dependency_index = dependencies_by_name[mod_id]; } else { return false; } } return true; } size_t find_function_by_vram_section(uint32_t vram, size_t section_index) const { auto find_it = functions_by_vram.find(vram); if (find_it == functions_by_vram.end()) { return (size_t)-1; } for (size_t function_index : find_it->second) { if (functions[function_index].section_index == section_index) { return function_index; } } return (size_t)-1; } bool has_reference_symbols() const { return !reference_symbols.empty() || !import_symbols.empty() || !event_symbols.empty(); } bool is_regular_reference_section(uint16_t section_index) const { return section_index != SectionImport && section_index != SectionEvent; } bool find_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { auto find_sym_it = reference_symbols_by_name.find(symbol_name); // Check if the symbol was found. if (find_sym_it == reference_symbols_by_name.end()) { return false; } ref_out = find_sym_it->second; return true; } bool reference_symbol_exists(const std::string& symbol_name) const { SymbolReference dummy_ref; return find_reference_symbol(symbol_name, dummy_ref); } bool find_regular_reference_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { SymbolReference ref_found; if (!find_reference_symbol(symbol_name, ref_found)) { return false; } // Ignore reference symbols in special sections. if (!is_regular_reference_section(ref_found.section_index)) { return false; } ref_out = ref_found; return true; } const ReferenceSymbol& get_reference_symbol(uint16_t section_index, size_t symbol_index) const { if (section_index == SectionImport) { return import_symbols[symbol_index].base; } else if (section_index == SectionEvent) { return event_symbols[symbol_index].base; } return reference_symbols[symbol_index]; } size_t num_regular_reference_symbols() { return reference_symbols.size(); } const ReferenceSymbol& get_regular_reference_symbol(size_t index) const { return reference_symbols[index]; } const ReferenceSymbol& get_reference_symbol(const SymbolReference& ref) const { return get_reference_symbol(ref.section_index, ref.symbol_index); } bool is_reference_section_relocatable(uint16_t section_index) const { if (section_index == SectionAbsolute) { return false; } else if (section_index == SectionImport || section_index == SectionEvent) { return true; } return reference_sections[section_index].relocatable; } bool add_reference_symbol(const std::string& symbol_name, uint16_t section_index, uint32_t vram, bool is_function) { uint32_t section_vram; if (section_index == SectionAbsolute) { section_vram = 0; } else if (section_index < reference_sections.size()) { section_vram = reference_sections[section_index].ram_addr; } // Invalid section index. else { return false; } // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so. reference_symbols_by_name.emplace(symbol_name, N64Recomp::SymbolReference{ .section_index = section_index, .symbol_index = reference_symbols.size() }); reference_symbols.emplace_back(N64Recomp::ReferenceSymbol{ .name = symbol_name, .section_index = section_index, .section_offset = vram - section_vram, .is_function = is_function }); return true; } void add_import_symbol(const std::string& symbol_name, size_t dependency_index) { // TODO Check if dependency_imports_by_name[dependency_index] already contains the name and show a conflict error if so. dependency_imports_by_name[dependency_index][symbol_name] = import_symbols.size(); import_symbols.emplace_back( N64Recomp::ImportSymbol { .base = N64Recomp::ReferenceSymbol { .name = symbol_name, .section_index = N64Recomp::SectionImport, .section_offset = 0, .is_function = true }, .dependency_index = dependency_index, } ); } bool find_import_symbol(const std::string& symbol_name, size_t dependency_index, SymbolReference& ref_out) const { if (dependency_index >= dependencies_by_name.size()) { return false; } auto find_it = dependency_imports_by_name[dependency_index].find(symbol_name); if (find_it == dependency_imports_by_name[dependency_index].end()) { return false; } ref_out.section_index = SectionImport; ref_out.symbol_index = find_it->second; return true; } void add_event_symbol(const std::string& symbol_name) { // TODO Check if reference_symbols_by_name already contains the name and show a conflict error if so. reference_symbols_by_name[symbol_name] = N64Recomp::SymbolReference { .section_index = N64Recomp::SectionEvent, .symbol_index = event_symbols.size() }; event_symbols.emplace_back( N64Recomp::EventSymbol { .base = N64Recomp::ReferenceSymbol { .name = symbol_name, .section_index = N64Recomp::SectionEvent, .section_offset = 0, .is_function = true } } ); } bool find_event_symbol(const std::string& symbol_name, SymbolReference& ref_out) const { SymbolReference ref_found; if (!find_reference_symbol(symbol_name, ref_found)) { return false; } // Ignore reference symbols that aren't in the event section. if (ref_found.section_index != SectionEvent) { return false; } ref_out = ref_found; return true; } bool add_dependency_event(const std::string& event_name, size_t dependency_index, size_t& dependency_event_index) { if (dependency_index >= dependencies_by_name.size()) { return false; } // Prevent adding the same event to a dependency twice. This isn't an error, since a mod could register // multiple callbacks to the same event. auto find_it = dependency_events_by_name[dependency_index].find(event_name); if (find_it != dependency_events_by_name[dependency_index].end()) { dependency_event_index = find_it->second; return true; } dependency_event_index = dependency_events.size(); dependency_events.emplace_back(DependencyEvent{ .dependency_index = dependency_index, .event_name = event_name }); dependency_events_by_name[dependency_index][event_name] = dependency_event_index; return true; } bool add_callback(size_t dependency_event_index, size_t function_index) { callbacks.emplace_back(Callback{ .function_index = function_index, .dependency_event_index = dependency_event_index }); return true; } uint32_t get_reference_section_vram(uint16_t section_index) const { if (section_index == N64Recomp::SectionAbsolute) { return 0; } else if (!is_regular_reference_section(section_index)) { return 0; } else { return reference_sections[section_index].ram_addr; } } uint32_t get_reference_section_rom(uint16_t section_index) const { if (section_index == N64Recomp::SectionAbsolute) { return (uint32_t)-1; } else if (!is_regular_reference_section(section_index)) { return (uint32_t)-1; } else { return reference_sections[section_index].rom_addr; } } void copy_reference_sections_from(const Context& rhs) { reference_sections = rhs.reference_sections; } }; bool recompile_function(const Context& context, const Function& func, std::ofstream& output_file, std::span> static_funcs, bool tag_reference_relocs); enum class ModSymbolsError { Good, NotASymbolFile, UnknownSymbolFileVersion, CorruptSymbolFile, FunctionOutOfBounds, }; ModSymbolsError parse_mod_symbols(std::span data, std::span binary, const std::unordered_map& sections_by_vrom, Context& context_out); std::vector symbols_to_bin_v1(const Context& mod_context); inline bool validate_mod_id(std::string_view str) { // Disallow empty ids. if (str.size() == 0) { return false; } // Allow special dependency ids. if (str == N64Recomp::DependencySelf || str == N64Recomp::DependencyBaseRecomp) { return true; } // These following rules basically describe C identifiers. There's no specific reason to enforce them besides colon (currently), // so this is just to prevent "weird" mod ids. // Check the first character, which must be alphabetical or an underscore. if (!isalpha(str[0]) && str[0] != '_') { return false; } // Check the remaining characters, which can be alphanumeric or underscore. for (char c : str.substr(1)) { if (!isalnum(c) && c != '_') { return false; } } return true; } inline bool validate_mod_id(const std::string& str) { return validate_mod_id(std::string_view{str}); } } #endif