diff --git a/OfflineModRecomp/main.cpp b/OfflineModRecomp/main.cpp index 88a8c60..80fba1f 100644 --- a/OfflineModRecomp/main.cpp +++ b/OfflineModRecomp/main.cpp @@ -139,9 +139,11 @@ int main(int argc, const char** argv) { RabbitizerConfig_Cfg.pseudos.pseudoNot = false; RabbitizerConfig_Cfg.pseudos.pseudoBal = false; + std::string recomp_include = "#include \"librecomp/recomp.h\""; + bool should_write_header = true; for (const auto& func : mod_context.base_context.functions) { - N64Recomp::recompile_function(mod_context.base_context, func, output_file, static_funcs_by_section, should_write_header); + N64Recomp::recompile_function(mod_context.base_context, func, recomp_include, output_file, static_funcs_by_section, should_write_header); should_write_header = false; } diff --git a/RecompModTool/main.cpp b/RecompModTool/main.cpp index 78441fe..dd860fe 100644 --- a/RecompModTool/main.cpp +++ b/RecompModTool/main.cpp @@ -307,9 +307,9 @@ N64Recomp::ModContext build_mod_context(const N64Recomp::Context& input_context, } // Check for special section names. - bool patch_section = cur_section.name == ".recomp_patch"; - bool force_patch_section = cur_section.name == ".recomp_force_patch"; - bool export_section = cur_section.name == ".recomp_export"; + bool patch_section = cur_section.name == N64Recomp::PatchSectionName; + bool force_patch_section = cur_section.name == N64Recomp::ForcedPatchSectionName; + bool export_section = cur_section.name == N64Recomp::ExportSectionName; // Add the functions from the current input section to the current output section. auto& section_out = ret.base_context.sections[output_section_index]; diff --git a/include/n64recomp.h b/include/n64recomp.h index 521421a..f919624 100644 --- a/include/n64recomp.h +++ b/include/n64recomp.h @@ -60,6 +60,9 @@ namespace N64Recomp { constexpr uint16_t SectionSelf = (uint16_t)-1; constexpr uint16_t SectionAbsolute = (uint16_t)-2; constexpr uint16_t SectionImport = (uint16_t)-3; // Imported symbols for mods + constexpr std::string_view PatchSectionName = ".recomp_patch"; + constexpr std::string_view ForcedPatchSectionName = ".recomp_force_patch"; + constexpr std::string_view ExportSectionName = ".recomp_export"; struct Section { uint32_t rom_addr = 0; uint32_t ram_addr = 0; diff --git a/src/config.cpp b/src/config.cpp index 56772ed..d96d7d8 100644 --- a/src/config.cpp +++ b/src/config.cpp @@ -408,6 +408,25 @@ N64Recomp::Config::Config(const char* path) { const toml::array* array = data_reference_syms_file_data.as_array(); data_reference_syms_file_paths = get_data_syms_paths(array, basedir); } + + // Control whether the recompiler emits exported symbol data. + std::optional allow_exports_opt = input_data["allow_exports"].value(); + if (allow_exports_opt.has_value()) { + allow_exports = allow_exports_opt.value(); + } + else { + allow_exports = false; + } + + // Enable patch recompilation strict mode, which ensures that patch functions are marked and that other functions are not marked as patches. + std::optional strict_patch_mode_opt = input_data["strict_patch_mode"].value(); + if (strict_patch_mode_opt.has_value()) { + strict_patch_mode = strict_patch_mode_opt.value(); + } + else { + // Default to strict patch mode if a function reference symbol file was provided. + strict_patch_mode = !func_reference_syms_file_path.empty(); + } } catch (const toml::parse_error& err) { std::cerr << "Syntax error parsing toml: " << *err.source().path << " (" << err.source().begin << "):\n" << err.description() << std::endl; diff --git a/src/config.h b/src/config.h index 988c00b..70bf0fa 100644 --- a/src/config.h +++ b/src/config.h @@ -42,6 +42,8 @@ namespace N64Recomp { bool single_file_output; bool use_absolute_symbols; bool unpaired_lo16_warnings; + bool allow_exports; + bool strict_patch_mode; std::filesystem::path elf_path; std::filesystem::path symbols_file_path; std::filesystem::path func_reference_syms_file_path; diff --git a/src/elf.cpp b/src/elf.cpp index 06e194d..e828174 100644 --- a/src/elf.cpp +++ b/src/elf.cpp @@ -431,7 +431,21 @@ ELFIO::section* read_sections(N64Recomp::Context& context, const N64Recomp::ElfP else { reloc_out.reference_symbol = false; reloc_out.target_section = rel_symbol_section_index; - rel_section_vram = context.sections[rel_symbol_section_index].ram_addr; + // Handle special sections. + if (rel_symbol_section_index >= context.sections.size()) { + switch (rel_symbol_section_index) { + case ELFIO::SHN_ABS: + rel_section_vram = 0; + break; + default: + fmt::print(stderr, "Reloc {} references symbol {} which is in an unknown section 0x{:04X}!\n", + i, rel_symbol_name, rel_symbol_section_index); + return nullptr; + } + } + else { + rel_section_vram = context.sections[rel_symbol_section_index].ram_addr; + } } // Reloc pairing, see MIPS System V ABI documentation page 4-18 (https://refspecs.linuxfoundation.org/elf/mipsabi.pdf) diff --git a/src/main.cpp b/src/main.cpp index f681673..72e152e 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -568,6 +568,10 @@ int main(int argc, char** argv) { open_new_output_file(); } + std::vector export_function_indices{}; + + bool failed_strict_mode = false; + //#pragma omp parallel for for (size_t i = 0; i < context.functions.size(); i++) { const auto& func = context.functions[i]; @@ -576,6 +580,33 @@ int main(int argc, char** argv) { fmt::print(func_header_file, "void {}(uint8_t* rdram, recomp_context* ctx);\n", func.name); bool result; + const auto& func_section = context.sections[func.section_index]; + // Apply strict patch mode validation if enabled. + if (config.strict_patch_mode) { + bool in_normal_patch_section = func_section.name == N64Recomp::PatchSectionName; + bool in_force_patch_section = func_section.name == N64Recomp::ForcedPatchSectionName; + bool in_patch_section = in_normal_patch_section || in_force_patch_section; + bool reference_symbol_found = context.reference_symbols_by_name.contains(func.name); + + // This is a patch function, but no corresponding symbol was found in the original symbol list. + if (in_patch_section && !reference_symbol_found) { + fmt::print(stderr, "Function {} is marked as a replacement, but no function with the same name was found in the reference symbols!\n", func.name); + failed_strict_mode = true; + continue; + } + // This is not a patch function, but it has the same name as a function in the original symbol list. + else if (!in_patch_section && reference_symbol_found) { + fmt::print(stderr, "Function {} is not marked as a replacement, but a function with the same name was found in the reference symbols!\n", func.name); + failed_strict_mode = true; + continue; + } + } + // Check if this is an export and add it to the list if exports are enabled. + if (config.allow_exports && func_section.name == N64Recomp::ExportSectionName) { + export_function_indices.push_back(i); + } + + // Recompile the function. if (config.single_file_output || config.functions_per_output_file > 1) { result = N64Recomp::recompile_function(context, func, config.recomp_include, current_output_file, static_funcs_by_section, false); if (!config.single_file_output) { @@ -598,6 +629,15 @@ int main(int argc, char** argv) { } } + if (failed_strict_mode) { + if (config.single_file_output || config.functions_per_output_file > 1) { + current_output_file.close(); + std::error_code ec; + std::filesystem::remove(config.output_func_path / config.elf_path.stem().replace_extension(".c"), ec); + } + exit_failure("Strict mode validation failed!\n"); + } + for (size_t section_index = 0; section_index < context.sections.size(); section_index++) { auto& section = context.sections[section_index]; auto& section_funcs = section.function_addrs; @@ -789,6 +829,23 @@ int main(int argc, char** argv) { } } fmt::print(overlay_file, "}};\n"); + + if (config.allow_exports) { + fmt::print(overlay_file, + "\n" + "static FunctionExport export_table[] = {{\n" + ); + + for (size_t func_index : export_function_indices) { + const auto& func = context.functions[func_index]; + fmt::print(overlay_file, " {{ \"{}\", 0x{:08X} }},\n", func.name, func.vram); + } + + // Add a dummy element at the end to ensure the array has a valid length because C doesn't allow zero-size arrays. + fmt::print(overlay_file, " {{ NULL, 0 }}\n"); + + fmt::print(overlay_file, "}};\n"); + } } if (!config.output_binary_path.empty()) { diff --git a/src/mod_symbols.cpp b/src/mod_symbols.cpp index a6aedca..0fb79b7 100644 --- a/src/mod_symbols.cpp +++ b/src/mod_symbols.cpp @@ -15,6 +15,7 @@ struct FileSubHeaderV1 { }; struct SectionHeaderV1 { + uint32_t flags; uint32_t file_offset; uint32_t vram; uint32_t rom_size;